From 6256ebd13d84caa2c9cdbf0a374b9ac70dca10c9 Mon Sep 17 00:00:00 2001 From: Alaguraj0361 Date: Wed, 10 Jun 2026 12:10:50 +0530 Subject: [PATCH] Refactor menu visibility and delivery validation for online orders; add backend action for online orders management --- addons/dine360_dashboard/__init__.py | 1 + addons/dine360_dashboard/controllers/main.py | 37 +++----- addons/dine360_dashboard/models/__init__.py | 1 + addons/dine360_dashboard/models/ir_ui_menu.py | 34 ++++++++ addons/dine360_online_orders/__manifest__.py | 1 + .../dine360_online_orders/controllers/main.py | 51 +++++++++++ .../models/sale_order.py | 17 ++++ .../views/backend_online_orders.xml | 30 +++++++ .../views/checkout_address.xml | 85 ++++++++++++++++++- 9 files changed, 231 insertions(+), 26 deletions(-) create mode 100644 addons/dine360_dashboard/models/__init__.py create mode 100644 addons/dine360_dashboard/models/ir_ui_menu.py create mode 100644 addons/dine360_online_orders/views/backend_online_orders.xml diff --git a/addons/dine360_dashboard/__init__.py b/addons/dine360_dashboard/__init__.py index 991d9d3..8f97342 100644 --- a/addons/dine360_dashboard/__init__.py +++ b/addons/dine360_dashboard/__init__.py @@ -1,3 +1,4 @@ # dine360_dashboard/__init__.py from . import controllers +from . import models diff --git a/addons/dine360_dashboard/controllers/main.py b/addons/dine360_dashboard/controllers/main.py index 223a51b..e40ddd0 100644 --- a/addons/dine360_dashboard/controllers/main.py +++ b/addons/dine360_dashboard/controllers/main.py @@ -78,51 +78,40 @@ class ImageHome(Website): is_admin = request.env.user.has_group('base.group_system') is_kitchen = False + # User requested to hide all standard apps and POS/KDS. + # Only allow specific menus based on user request + admin tools. + allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Uber Integration', 'Apps', 'Settings'] + filtered_menus = [] seen_names = set() for menu in menus: - # 1. Hide "Apps" for non-admins - if (menu.name == 'Apps' or (menu.web_icon and menu.web_icon.startswith('base,'))) and not is_admin: + # Match strictly against allowed menus + if menu.name not in allowed_menus and menu.name != 'Table Reservation': continue - # 2. Hide "Kitchen (KDS)" for non-kitchen/non-admin users - if 'Kitchen' in menu.name or 'KDS' in menu.name: - if not (is_kitchen or is_admin): - continue + # Hide "Apps" and "Settings" for non-admins + if menu.name in ['Apps', 'Settings'] and not is_admin: + continue - # 3. De-duplicate by name + # De-duplicate by name if menu.name in seen_names: continue seen_names.add(menu.name) - # 4. Dynamic Icon Override (Dine360 Branding) - # This maps menu names to our custom SVG icons dynamically + # Dynamic Icon Override (Dine360 Branding) icon_mapping = { - 'Discuss': 'dine360_dashboard,static/src/img/icons/discuss.svg', - 'Calendar': 'dine360_dashboard,static/src/img/icons/calendar.svg', - 'Contacts': 'dine360_dashboard,static/src/img/icons/contacts.svg', - 'CRM': 'dine360_dashboard,static/src/img/icons/crm.svg', - 'Sales': 'dine360_dashboard,static/src/img/icons/sales.svg', - 'Dashboards': 'dine360_dashboard,static/src/img/icons/dashboards.svg', - 'Point of Sale': 'dine360_dashboard,static/src/img/icons/point_of_sale.svg', - 'Invoicing': 'dine360_dashboard,static/src/img/icons/invoicing.svg', - 'Website': 'dine360_dashboard,static/src/img/icons/website.svg', - 'Purchase': 'dine360_dashboard,static/src/img/icons/purchase.svg', - 'Inventory': 'dine360_dashboard,static/src/img/icons/inventory.svg', - 'Employees': 'dine360_dashboard,static/src/img/icons/employees.svg', 'Apps': 'dine360_dashboard,static/src/img/icons/apps.svg', 'Settings': 'dine360_dashboard,static/src/img/icons/settings.svg', - 'Kitchen (KDS)': 'dine360_dashboard,static/src/img/icons/kitchen_kds.svg', 'Table Reservation': 'dine360_dashboard,static/src/img/icons/table_reservation.svg', + 'Table Reservations': 'dine360_dashboard,static/src/img/icons/table_reservation.svg', 'Uber Integration': 'dine360_dashboard,static/src/img/icons/uber_integration.svg', + 'Online Orders': 'dine360_dashboard,static/src/img/icons/website.svg', } # Find the best match in the mapping current_name = menu.name for key, icon_path in icon_mapping.items(): if key.lower() in current_name.lower(): - # We use a virtual field assignment so it doesn't try to save to DB - # but the template picks it up menu.web_icon = icon_path break diff --git a/addons/dine360_dashboard/models/__init__.py b/addons/dine360_dashboard/models/__init__.py new file mode 100644 index 0000000..7930c4e --- /dev/null +++ b/addons/dine360_dashboard/models/__init__.py @@ -0,0 +1 @@ +from . import ir_ui_menu diff --git a/addons/dine360_dashboard/models/ir_ui_menu.py b/addons/dine360_dashboard/models/ir_ui_menu.py new file mode 100644 index 0000000..38d6e81 --- /dev/null +++ b/addons/dine360_dashboard/models/ir_ui_menu.py @@ -0,0 +1,34 @@ +from odoo import models, api + +class IrUiMenu(models.Model): + _inherit = 'ir.ui.menu' + + @api.model + def load_menus(self, debug): + """ + Override standard menu loading to hide unwanted root menus. + """ + menus = super(IrUiMenu, self).load_menus(debug) + + user = self.env.user + is_admin = user.has_group('base.group_system') + + allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Uber Integration', 'Apps', 'Settings'] + + if 'root' in menus and 'children' in menus['root']: + new_children = [] + for child_id in menus['root']['children']: + child_menu = menus.get(child_id) + if not child_menu: + continue + name = child_menu.get('name') + + # Allow matching menus (with fallback for Table Reservation naming variations) + if name in allowed_menus or name == 'Table Reservation': + # Hide Apps and Settings for non-admins + if name in ['Apps', 'Settings'] and not is_admin: + continue + new_children.append(child_id) + menus['root']['children'] = new_children + + return menus diff --git a/addons/dine360_online_orders/__manifest__.py b/addons/dine360_online_orders/__manifest__.py index 0a7ee94..d8ab0ca 100644 --- a/addons/dine360_online_orders/__manifest__.py +++ b/addons/dine360_online_orders/__manifest__.py @@ -18,6 +18,7 @@ 'views/kds_override_views.xml', 'views/pos_config_views.xml', 'views/website_sale_templates.xml', + 'views/backend_online_orders.xml', ], 'assets': { 'point_of_sale._assets_pos': [ diff --git a/addons/dine360_online_orders/controllers/main.py b/addons/dine360_online_orders/controllers/main.py index 279f82c..b0eb327 100644 --- a/addons/dine360_online_orders/controllers/main.py +++ b/addons/dine360_online_orders/controllers/main.py @@ -1,5 +1,6 @@ from odoo import http from odoo.http import request +from odoo.addons.website_sale.controllers.main import WebsiteSale class Dine360OnlineOrders(http.Controller): @@ -12,3 +13,53 @@ class Dine360OnlineOrders(http.Controller): 'order_source': 'online' }) return True + +class Dine360WebsiteSaleOnline(WebsiteSale): + @http.route(['/shop/payment'], type='http', auth="public", website=True, sitemap=False) + def shop_payment(self, **post): + order = request.website.sale_get_order() + if order and not order.carrier_id: + # Bypass delivery validation by assigning the first available delivery carrier + carriers = order._get_delivery_methods() + if not carriers: + carriers = request.env['delivery.carrier'].sudo().search([('is_published', '=', True)], limit=1) + + if carriers: + order.carrier_id = carriers[0].id + price = carriers[0].rate_shipment(order)['price'] if hasattr(carriers[0], 'rate_shipment') and order._get_delivery_methods() else 0.0 + order.set_delivery_line(carriers[0], price) + + return super(Dine360WebsiteSaleOnline, self).shop_payment(**post) + + @http.route('/shop/payment/transaction/', type='json', auth="public", website=True) + def shop_payment_transaction(self, order_id, access_token=None, **kwargs): + # Force bypass "No shipping method is selected" error during payment processing + order = request.env['sale.order'].sudo().browse(order_id) + if order and not order.carrier_id: + carriers = request.env['delivery.carrier'].sudo().search([('is_published', '=', True)], limit=1) + if carriers: + order.carrier_id = carriers[0].id + order.set_delivery_line(carriers[0], 0.0) + return super(Dine360WebsiteSaleOnline, self).shop_payment_transaction(order_id, access_token=access_token, **kwargs) + + @http.route(['/shop/address'], type='http', methods=['GET', 'POST'], auth="public", website=True, sitemap=False) + def address(self, **post): + # Override to inject dummy address values for pickup orders before Odoo validates them + if post.get('submitted') and post.get('fulfilment_type') == 'pickup': + if not post.get('street'): post['street'] = 'Store Pickup' + if not post.get('city'): post['city'] = 'Local' + if not post.get('zip'): post['zip'] = '00000' + if not post.get('country_id'): + country = request.env['res.country'].sudo().search([], limit=1) + post['country_id'] = str(country.id) if country else '' + + # Request.params must also be updated for Odoo 17 validation + for k, v in post.items(): + if k in ['street', 'city', 'zip', 'country_id']: + if hasattr(request.params, 'update'): + try: + request.params[k] = v + except TypeError: + pass + + return super(Dine360WebsiteSaleOnline, self).address(**post) diff --git a/addons/dine360_online_orders/models/sale_order.py b/addons/dine360_online_orders/models/sale_order.py index 6e54dc7..bc63b11 100644 --- a/addons/dine360_online_orders/models/sale_order.py +++ b/addons/dine360_online_orders/models/sale_order.py @@ -32,6 +32,23 @@ class SaleOrderOnline(models.Model): ('walk_in', 'Walk-In'), ], string='Fulfillment Type', default='pickup', tracking=True) + def _has_deliverable_products(self): + """ Bypass Odoo's native delivery carrier validation for store pickup orders """ + res = super(SaleOrderOnline, self)._has_deliverable_products() + if self.fulfilment_type == 'pickup': + return False + return res + + def _check_cart_is_ready_to_be_paid(self): + try: + return super(SaleOrderOnline, self)._check_cart_is_ready_to_be_paid() + except Exception as e: + if self.fulfilment_type == 'pickup' and type(e).__name__ == 'ValidationError': + err_str = str(e).lower() + if 'shipping method' in err_str or 'delivery' in err_str: + return True + raise e + payment_option = fields.Selection([ ('in_store', 'In Store'), ('terminal_in_store', 'Payment Terminal (In Store)'), diff --git a/addons/dine360_online_orders/views/backend_online_orders.xml b/addons/dine360_online_orders/views/backend_online_orders.xml new file mode 100644 index 0000000..1a279b6 --- /dev/null +++ b/addons/dine360_online_orders/views/backend_online_orders.xml @@ -0,0 +1,30 @@ + + + + + + + Online Orders + sale.order + tree,kanban,form,calendar,pivot,graph,activity + [('order_source', '=', 'online')] + {'default_order_source': 'online'} + +

+ No online orders found. +

+

+ Orders placed on the website will appear here. +

+
+
+ + + + +
+
diff --git a/addons/dine360_theme_shivasakthi/views/checkout_address.xml b/addons/dine360_theme_shivasakthi/views/checkout_address.xml index 90c4642..11012de 100644 --- a/addons/dine360_theme_shivasakthi/views/checkout_address.xml +++ b/addons/dine360_theme_shivasakthi/views/checkout_address.xml @@ -271,7 +271,30 @@ containers.forEach(c => c.style.display = 'none'); if (addrHeader) addrHeader.style.display = 'none'; if (msgDiv) msgDiv.style.display = 'none'; - // Remove required for pickup + + // Auto-fill dummy values instantly so Odoo's strict validation passes + const form = document.querySelector('form.checkout_autoformat'); + if (form) { + const fill = (n, v) => { + const el = form.querySelector(`[name="${n}"]`); + if (el) { + if (!el.dataset.oldVal) el.dataset.oldVal = el.value || ''; + if (el.tagName === 'SELECT') { + const opt = el.querySelector('option[value]:not([value="0"]):not([value=""])'); + if (opt) el.value = opt.value; + } else { + el.value = v; + } + } + }; + fill('street', 'Store Pickup'); + fill('city', 'Local'); + fill('zip', '00000'); + fill('country_id', '1'); + fill('state_id', '1'); + } + + // Prevent HTML5 validation from silently blocking submission on hidden fields containers.forEach(c => { c.querySelectorAll('input, select').forEach(i => i.removeAttribute('required')); }); @@ -279,6 +302,25 @@ containers.forEach(c => c.style.display = ''); if (addrHeader) addrHeader.style.display = ''; if (msgDiv) msgDiv.style.display = ''; + + // Restore original values if user switches back to delivery + const form = document.querySelector('form.checkout_autoformat'); + if (form) { + const restore = (n) => { + const el = form.querySelector(`[name="${n}"]`); + if (el && el.dataset.oldVal !== undefined) { + if (el.value === 'Store Pickup' || el.value === 'Local' || el.value === '00000') { + el.value = el.dataset.oldVal; + } + } + }; + restore('street'); + restore('city'); + restore('zip'); + restore('country_id'); + restore('state_id'); + } + // Restore required ['street', 'city', 'zip', 'country_id', 'state_id'].forEach(name => { const i = document.querySelector(`[name="${name}"]`); @@ -474,15 +516,54 @@ - +