Refactor menu visibility and delivery validation for online orders; add backend action for online orders management
This commit is contained in:
parent
9336d7d7b9
commit
6256ebd13d
@ -1,3 +1,4 @@
|
||||
# dine360_dashboard/__init__.py
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
1
addons/dine360_dashboard/models/__init__.py
Normal file
1
addons/dine360_dashboard/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import ir_ui_menu
|
||||
34
addons/dine360_dashboard/models/ir_ui_menu.py
Normal file
34
addons/dine360_dashboard/models/ir_ui_menu.py
Normal 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
|
||||
@ -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': [
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)'),
|
||||
|
||||
30
addons/dine360_online_orders/views/backend_online_orders.xml
Normal file
30
addons/dine360_online_orders/views/backend_online_orders.xml
Normal 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>
|
||||
@ -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 @@
|
||||
</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 && !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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user