forked from alaguraj/odoo-testing-addons
Implement online order management with service mode selection, KDS integration, and dedicated POS order fields.
This commit is contained in:
parent
d8db1f9334
commit
216c627369
@ -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>
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
from . import models
|
from . import models
|
||||||
|
from . import controllers
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
1
addons/dine360_online_orders/controllers/__init__.py
Normal file
1
addons/dine360_online_orders/controllers/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import main
|
||||||
14
addons/dine360_online_orders/controllers/main.py
Normal file
14
addons/dine360_online_orders/controllers/main.py
Normal 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
|
||||||
@ -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
|
||||||
|
|||||||
10
addons/dine360_online_orders/models/pos_config.py
Normal file
10
addons/dine360_online_orders/models/pos_config.py
Normal 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')
|
||||||
@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
15
addons/dine360_online_orders/models/pos_order_line.py
Normal file
15
addons/dine360_online_orders/models/pos_order_line.py
Normal 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
|
||||||
|
)
|
||||||
@ -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)
|
||||||
@ -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
|
||||||
|
|||||||
62
addons/dine360_online_orders/static/src/js/service_mode.js
Normal file
62
addons/dine360_online_orders/static/src/js/service_mode.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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>
|
||||||
|
|||||||
22
addons/dine360_online_orders/views/pos_config_views.xml
Normal file
22
addons/dine360_online_orders/views/pos_config_views.xml
Normal 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>
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
Loading…
x
Reference in New Issue
Block a user