Refactor menu visibility and delivery validation for online orders; add backend action for online orders management

This commit is contained in:
Alaguraj0361 2026-06-10 12:10:50 +05:30
parent 9336d7d7b9
commit 6256ebd13d
9 changed files with 231 additions and 26 deletions

View File

@ -1,3 +1,4 @@
# dine360_dashboard/__init__.py
from . import controllers
from . import models

View File

@ -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

View File

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

View File

@ -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

View File

@ -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': [

View File

@ -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/<int:order_id>', 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)

View File

@ -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)'),

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Action to open Online Orders (Sale Orders from website) -->
<record id="action_backend_online_orders" model="ir.actions.act_window">
<field name="name">Online Orders</field>
<field name="res_model">sale.order</field>
<field name="view_mode">tree,kanban,form,calendar,pivot,graph,activity</field>
<field name="domain">[('order_source', '=', 'online')]</field>
<field name="context">{'default_order_source': 'online'}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No online orders found.
</p>
<p>
Orders placed on the website will appear here.
</p>
</field>
</record>
<!-- Menu Item at the Root Level -->
<menuitem id="menu_backend_online_orders_root"
name="Online Orders"
web_icon="dine360_dashboard,static/src/img/icons/website.svg"
sequence="20"
action="action_backend_online_orders"/>
</data>
</odoo>

View File

@ -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 &amp;&amp; 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 @@
</xpath>
</template>
<!-- 5. FINAL POLISH: Hide Shipping Info using CSS (Safest method) -->
<!-- 5. FINAL POLISH: Hide Shipping Info using CSS and Bypass JS validation -->
<template id="Shivasakthi_hide_shipping_on_payment" inherit_id="website.layout" name="Shivasakthi Hide Shipping on Payment">
<xpath expr="." position="inside">
<style>
/* Hide any row or div that contains shipping info or delivery methods in the checkout */
.js_shipping, .shipping_address, #shipping_and_billing, #delivery_carrier, #delivery_method, .js_delivery_carrier {
display: none !important;
height: 0 !important;
overflow: hidden !important;
opacity: 0 !important;
}
</style>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
if (window.location.pathname.includes('/shop/payment')) {
// Prevent Odoo's _handleCarrierUpdateResult from crashing if elements are missing (e.g. no taxes or delivery hidden)
const idsToEnsure = ['order_delivery', 'order_total_untaxed', 'order_total_taxes', 'order_total'];
idsToEnsure.forEach(id => {
if (!document.querySelector(`#${id} .monetary_field`)) {
let el = document.getElementById(id);
if (!el) {
el = document.createElement('div');
el.id = id;
el.style.display = 'none';
document.body.appendChild(el);
}
const span = document.createElement('span');
span.className = 'monetary_field oe_currency_value';
el.appendChild(span);
}
});
// Bypass Odoo's native JS delivery validation
const deliveryMethodDiv = document.getElementById('delivery_method');
if (deliveryMethodDiv) {
// Automatically select the first delivery method if not selected
const firstRadio = deliveryMethodDiv.querySelector('input[type="radio"]');
if (firstRadio &amp;&amp; !deliveryMethodDiv.querySelector('input[type="radio"]:checked')) {
firstRadio.checked = true;
}
// If there are still no carriers, rename the ID to bypass Odoo's strict check
if (!deliveryMethodDiv.querySelector('input[type="radio"]:checked')) {
deliveryMethodDiv.id = 'delivery_method_hidden';
}
}
}
});
</script>
</xpath>
</template>
</odoo>