Implement online order management with service mode selection, KDS integration, and dedicated POS order fields.

This commit is contained in:
Alaguraj0361 2026-03-17 14:56:47 +05:30
parent d8db1f9334
commit 216c627369
16 changed files with 243 additions and 2 deletions

View File

@ -25,8 +25,8 @@
</strong> </strong>
</div> </div>
<div class="ms-auto h5 mb-0"> <div class="ms-auto h5 mb-0">
<span class="badge rounded-pill bg-light text-dark border"> <span t-if="record.table_id.raw_value" class="badge rounded-pill bg-light text-dark border">
<i class="fa fa-cutlery me-1"/> <field name="table_id"/> <i class="fa fa-map-marker me-1"/> <field name="table_id"/>
</span> </span>
</div> </div>
</div> </div>

View File

@ -1 +1,2 @@
from . import models from . import models
from . import controllers

View File

@ -16,6 +16,8 @@
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/pos_order_views.xml', 'views/pos_order_views.xml',
'views/kds_override_views.xml', 'views/kds_override_views.xml',
'views/pos_config_views.xml',
'views/website_sale_templates.xml',
], ],
'assets': { 'assets': {
'point_of_sale._assets_pos': [ 'point_of_sale._assets_pos': [
@ -24,6 +26,9 @@
'dine360_online_orders/static/src/js/online_orders_navbar.js', 'dine360_online_orders/static/src/js/online_orders_navbar.js',
'dine360_online_orders/static/src/xml/online_orders_screen.xml', 'dine360_online_orders/static/src/xml/online_orders_screen.xml',
], ],
'web.assets_frontend': [
'dine360_online_orders/static/src/js/service_mode.js',
],
}, },
'installable': True, 'installable': True,
'application': False, 'application': False,

View File

@ -0,0 +1 @@
from . import main

View File

@ -0,0 +1,14 @@
from odoo import http
from odoo.http import request
class Dine360OnlineOrders(http.Controller):
@http.route('/shop/update_service_mode', type='json', auth="public", website=True)
def update_service_mode(self, service_mode, **post):
order = request.website.sale_get_order()
if order and service_mode in ['pickup', 'delivery', 'dine_in']:
order.sudo().write({
'dine360_service_mode': service_mode,
'dine360_order_source': 'web'
})
return True

View File

@ -1,2 +1,5 @@
from . import pos_order from . import pos_order
from . import sale_order from . import sale_order
from . import pos_config
from . import res_config_settings
from . import pos_order_line

View File

@ -0,0 +1,10 @@
from odoo import models, fields
class PosConfig(models.Model):
_inherit = 'pos.config'
is_kiosk = fields.Boolean(string='Is Self-Order Kiosk', default=False)
kiosk_service_mode = fields.Selection([
('pickup', 'Pickup'),
('dine_in', 'Dine-In')
], string='Default Kiosk Service Mode', default='dine_in')

View File

@ -31,6 +31,18 @@ class PosOrder(models.Model):
default=fields.Datetime.now default=fields.Datetime.now
) )
dine360_order_source = fields.Selection([
('web', 'Customer Self (Web)'),
('kiosk', 'Store Self (Kiosk)'),
('pos', 'Standard POS')
], string='Order Source', default='pos')
dine360_service_mode = fields.Selection([
('pickup', 'Pickup'),
('delivery', 'Delivery'),
('dine_in', 'Dine-In')
], string='Service Mode', default='dine_in')
@api.depends('partner_id', 'partner_id.name') @api.depends('partner_id', 'partner_id.name')
def _compute_online_customer_name(self): def _compute_online_customer_name(self):
for order in self: for order in self:
@ -80,6 +92,16 @@ class PosOrder(models.Model):
_logger.info("Online order %s rejected", self.name) _logger.info("Online order %s rejected", self.name)
return True return True
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'session_id' in vals:
session = self.env['pos.session'].browse(vals['session_id'])
if session.config_id.is_kiosk:
vals['dine360_order_source'] = 'kiosk'
vals['dine360_service_mode'] = session.config_id.kiosk_service_mode
return super().create(vals_list)
@api.model @api.model
def get_online_orders(self, config_id): def get_online_orders(self, config_id):
"""Fetch pending online orders for a specific POS config""" """Fetch pending online orders for a specific POS config"""
@ -112,6 +134,8 @@ class PosOrder(models.Model):
'amount_total': order.amount_total, 'amount_total': order.amount_total,
'date_order': order.date_order.isoformat() if order.date_order else '', 'date_order': order.date_order.isoformat() if order.date_order else '',
'sale_order_name': order.sale_order_id.name if order.sale_order_id else '', 'sale_order_name': order.sale_order_id.name if order.sale_order_id else '',
'service_mode': order.dine360_service_mode,
'order_source': order.dine360_order_source,
'note': order.note or '', 'note': order.note or '',
'lines': lines, 'lines': lines,
}) })

View File

@ -0,0 +1,15 @@
from odoo import models, fields, api
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
dine360_order_source = fields.Selection(
related='order_id.dine360_order_source',
string='Order Source',
store=True
)
dine360_service_mode = fields.Selection(
related='order_id.dine360_service_mode',
string='Service Mode',
store=True
)

View File

@ -0,0 +1,7 @@
from odoo import models, fields
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
is_kiosk = fields.Boolean(related='pos_config_id.is_kiosk', readonly=False)
kiosk_service_mode = fields.Selection(related='pos_config_id.kiosk_service_mode', readonly=False)

View File

@ -12,6 +12,18 @@ class SaleOrderOnline(models.Model):
help='The POS order created from this website sale order' help='The POS order created from this website sale order'
) )
dine360_order_source = fields.Selection([
('web', 'Customer Self (Web)'),
('kiosk', 'Store Self (Kiosk)'),
('pos', 'Standard POS')
], string='Order Source', default='web')
dine360_service_mode = fields.Selection([
('pickup', 'Pickup'),
('delivery', 'Delivery'),
('dine_in', 'Dine-In')
], string='Service Mode', default='pickup')
def _create_pos_order_for_kds(self, sale_order): def _create_pos_order_for_kds(self, sale_order):
""" """
Override from dine360_kds to also mark the POS order as an online order. Override from dine360_kds to also mark the POS order as an online order.
@ -35,6 +47,8 @@ class SaleOrderOnline(models.Model):
'online_order_status': 'pending', 'online_order_status': 'pending',
'sale_order_id': sale_order.id, 'sale_order_id': sale_order.id,
'online_order_date': fields.Datetime.now(), 'online_order_date': fields.Datetime.now(),
'dine360_order_source': sale_order.dine360_order_source,
'dine360_service_mode': sale_order.dine360_service_mode,
}) })
# Link back to sale order # Link back to sale order

View File

@ -0,0 +1,62 @@
/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
import { jsonrpc } from "@web/core/network/rpc_service";
publicWidget.registry.ServiceModeSelector = publicWidget.Widget.extend({
selector: '#service_mode_selector',
events: {
'change input[name="dine360_service_mode"]': '_onChangeServiceMode',
},
start: function () {
// Init visual selection
this.$('input[name="dine360_service_mode"]:checked').closest('.service-option').find('.service-card')
.css({ 'border-color': '#FECD4F', 'background-color': '#fffdf6', 'box-shadow': '0 4px 10px rgba(254, 205, 79, 0.2)' });
return this._super.apply(this, arguments);
},
_onChangeServiceMode: function (ev) {
var $input = $(ev.currentTarget);
var mode = $input.val();
// Reset styles
this.$('.service-card').css({ 'border-color': '', 'background-color': '', 'box-shadow': '' });
// Apply active styles
$input.closest('.service-option').find('.service-card')
.css({ 'border-color': '#FECD4F', 'background-color': '#fffdf6', 'box-shadow': '0 4px 10px rgba(254, 205, 79, 0.2)' });
// Hide error if present
this.$('#service_mode_error').addClass('d-none');
// RPC Call to update order
jsonrpc('/shop/update_service_mode', {
service_mode: mode
});
}
});
// Intercept checkout to ensure a service mode is selected
publicWidget.registry.CartCheckoutValidation = publicWidget.Widget.extend({
selector: '.oe_cart',
events: {
'click a[href="/shop/checkout"]': '_onCheckoutClicked',
},
_onCheckoutClicked: function (ev) {
// If there's a selector on the page
if (this.$('#service_mode_selector').length > 0) {
var selectedMode = this.$('input[name="dine360_service_mode"]:checked').val();
if (!selectedMode) {
ev.preventDefault();
this.$('#service_mode_error').removeClass('d-none');
// Highlight the box
this.$('#service_mode_selector').css('border', '1px solid #dc3545').addClass('shake-animation');
setTimeout(() => {
this.$('#service_mode_selector').removeClass('shake-animation');
}, 500);
}
}
}
});

View File

@ -10,4 +10,31 @@
'|', ('order_id.is_online_order', '=', False), ('order_id.online_order_status', '!=', 'pending') '|', ('order_id.is_online_order', '=', False), ('order_id.online_order_status', '!=', 'pending')
]</field> ]</field>
</record> </record>
<!-- Extend KDS Kanban to show Service Mode -->
<record id="view_pos_order_line_kds_kanban_inherit" model="ir.ui.view">
<field name="name">pos.order.line.kds.kanban.inherit</field>
<field name="model">pos.order.line</field>
<field name="inherit_id" ref="dine360_kds.view_pos_order_line_kds_kanban"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='create_date']" position="after">
<field name="dine360_order_source"/>
<field name="dine360_service_mode"/>
</xpath>
<xpath expr="//div[hasclass('ms-auto')]" position="replace">
<div class="ms-auto h5 mb-0 d-flex flex-column align-items-end">
<span t-if="record.dine360_service_mode.raw_value" class="badge rounded-pill mb-1"
t-attf-class="{{record.dine360_service_mode.raw_value == 'pickup' ? 'bg-info' : (record.dine360_service_mode.raw_value == 'delivery' ? 'bg-primary' : 'bg-secondary')}} text-white">
<i t-if="record.dine360_service_mode.raw_value == 'pickup'" class="fa fa-shopping-basket me-1" title="Pickup"/>
<i t-if="record.dine360_service_mode.raw_value == 'delivery'" class="fa fa-truck me-1" title="Delivery"/>
<i t-if="record.dine360_service_mode.raw_value == 'dine_in'" class="fa fa-cutlery me-1" title="Dine-In"/>
<field name="dine360_service_mode"/>
</span>
<span t-if="record.table_id.raw_value" class="badge rounded-pill bg-light text-dark border">
<i class="fa fa-map-marker me-1" title="Table"/> <field name="table_id"/>
</span>
</div>
</xpath>
</field>
</record>
</odoo> </odoo>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_inherit_dine360" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.dine360</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="95"/>
<field name="inherit_id" ref="point_of_sale.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//block[@id='pos_interface_section']" position="inside">
<setting string="Self-Order Kiosk" help="Enable this POS as a self-order kiosk">
<field name="is_kiosk"/>
<div class="content-group" invisible="not is_kiosk">
<div class="mt16">
<label string="Default Service Mode" for="kiosk_service_mode" class="col-lg-3 o_light_label"/>
<field name="kiosk_service_mode"/>
</div>
</div>
</setting>
</xpath>
</field>
</record>
</odoo>

View File

@ -15,6 +15,11 @@
decoration-success="online_order_status=='confirmed'" decoration-success="online_order_status=='confirmed'"
decoration-danger="online_order_status=='rejected'"/> decoration-danger="online_order_status=='rejected'"/>
<field name="sale_order_id"/> <field name="sale_order_id"/>
<field name="dine360_order_source"/>
<field name="dine360_service_mode" widget="badge"
decoration-info="dine360_service_mode=='pickup'"
decoration-primary="dine360_service_mode=='delivery'"
decoration-muted="dine360_service_mode=='dine_in'"/>
<field name="config_id"/> <field name="config_id"/>
</tree> </tree>
</field> </field>
@ -33,6 +38,8 @@
decoration-success="online_order_status=='confirmed'" decoration-success="online_order_status=='confirmed'"
decoration-danger="online_order_status=='rejected'"/> decoration-danger="online_order_status=='rejected'"/>
<field name="sale_order_id" invisible="not is_online_order"/> <field name="sale_order_id" invisible="not is_online_order"/>
<field name="dine360_order_source" string="Source"/>
<field name="dine360_service_mode" string="Service"/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="cart_service_mode" inherit_id="website_sale.cart" name="Service Mode Selector">
<xpath expr="//t[@t-call='website_sale.cart_lines']" position="before">
<div id="service_mode_selector" class="mb-4 bg-white p-4 rounded-4 shadow-sm border" style="border-left: 5px solid #FECD4F !important;">
<h4 class="mb-3 fw-bold">How would you like your order?</h4>
<div class="d-flex gap-3">
<label class="service-option position-relative flex-fill cursor-pointer">
<input type="radio" name="dine360_service_mode" value="pickup" class="d-none" t-att-checked="'checked' if website_sale_order.dine360_service_mode == 'pickup' else None"/>
<div class="service-card p-3 rounded-3 border text-center transition-all">
<i class="fa fa-shopping-basket fs-3 mb-2 text-primary"></i>
<h6 class="mb-1 fw-bold">Pickup</h6>
<small class="text-muted">Pick up at store</small>
</div>
</label>
<label class="service-option position-relative flex-fill cursor-pointer">
<input type="radio" name="dine360_service_mode" value="delivery" class="d-none" t-att-checked="'checked' if website_sale_order.dine360_service_mode == 'delivery' else None"/>
<div class="service-card p-3 rounded-3 border text-center transition-all">
<i class="fa fa-truck fs-3 mb-2 text-info"></i>
<h6 class="mb-1 fw-bold">Delivery</h6>
<small class="text-muted">Delivered to you</small>
</div>
</label>
</div>
<div class="mt-3 small text-danger d-none" id="service_mode_error">Please select Pickup or Delivery to continue.</div>
</div>
</xpath>
</template>
</odoo>