import logging from odoo import http, fields from odoo.http import request from odoo.addons.web.controllers.home import Home _logger = logging.getLogger(__name__) class MenuWrapper: def __init__(self, record, display_name=None, custom_url=None, icon=None): self.id = record.id self.name = display_name or record.name self.web_icon = icon or record.web_icon self.custom_url = custom_url class CustomHome(Home): @http.route('/web/login', type='http', auth="public") def web_login(self, *args, **kw): response = super(CustomHome, self).web_login(*args, **kw) if request.params.get('login_success') and request.session.uid: # Use relative redirect to maintain HTTPS/HTTP protocol return request.redirect('/') return response def render_dashboard(request): # 1. ROLE-BASED AUTO REDIRECTION (FOR STAFF) # Skip the dashboard/website entirely for Chefs and Waiters user = request.env.user.sudo() is_admin = user.has_group('base.group_system') or \ user.has_group('dine360_restaurant.group_restaurant_admin') if not is_admin: # 1. WAITER / CASHIER -> Priority goes to POS if user.has_group('dine360_restaurant.group_restaurant_waiter') or \ user.has_group('dine360_restaurant.group_restaurant_cashier'): return request.redirect('/web#action=point_of_sale.action_client_pos_menu') # 2. CHEF -> Directly to KDS if user.has_group('dine360_restaurant.group_restaurant_kitchen'): return request.redirect('/web#action=dine360_kds.action_kds_dashboard') db_name = request.env.cr.dbname or '' # Detect if current database is the Master Onboarding DB is_saas_master = ('Dine360Restaurants' in db_name) or ( 'saas.restaurant' in request.env and bool(request.env['saas.restaurant'].sudo().search_count([])) ) # Menus to display menus = request.env['ir.ui.menu'].search([ ('parent_id', '=', False) ], order='sequence') # User role checks try: is_admin = request.env.user.has_group('base.group_system') or \ request.env.user.has_group('dine360_restaurant.group_restaurant_admin') except Exception: is_admin = request.env.user.has_group('base.group_system') if is_saas_master: # Main SaaS Onboard DB: Show SaaS management, Table ops, Admin tools allowed_menus = ['Dine360 SaaS', 'Table Reservations', 'Table Management', 'Apps', 'Settings'] else: # Other Restaurant Tenant DBs: Online Orders, Table ops, Uber, Admin tools (No SaaS icon!) allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Table Management', 'Kitchen (KDS)', 'Uber Integration', 'Apps', 'Settings'] filtered_menus = [] seen_names = set() for menu in menus: # Check against allowed menus if menu.name not in allowed_menus and menu.name != 'Table Reservation': continue # Hide "Apps" and "Settings" for non-admins if menu.name in ['Apps', 'Settings'] and not is_admin: continue # De-duplicate by name if menu.name in seen_names: continue seen_names.add(menu.name) # Dynamic Icon Override (Dine360 Branding) icon_mapping = { 'Apps': 'dine360_dashboard,static/src/img/icons/apps.svg', 'Settings': 'dine360_dashboard,static/src/img/icons/settings.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', 'Website': 'dine360_dashboard,static/src/img/icons/website.svg', 'Dine360 SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg', 'SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg', } icon_path = menu.web_icon current_name = menu.name for key, path in icon_mapping.items(): if key.lower() in current_name.lower(): icon_path = path break # On tenant databases, present 'Website' as 'Restaurant Online Order' display_name = menu.name custom_url = None if not is_saas_master: if menu.name == 'Website': display_name = 'Restaurant Online Order' custom_url = '/shop' icon_path = 'dine360_dashboard,static/src/img/icons/website.svg' elif menu.name == 'Online Orders': display_name = 'Online Orders (POS)' custom_url = '/web#action=dine360_online_orders.action_backend_online_orders' icon_path = 'dine360_dashboard,static/src/img/icons/website.svg' filtered_menus.append(MenuWrapper(menu, display_name=display_name, custom_url=custom_url, icon=icon_path)) # Low Stock Alerts (Ingredients) low_stock_products = [] try: ProductTemplate = request.env['product.template'].sudo() if hasattr(ProductTemplate, 'get_low_stock_products'): low_stock_products = ProductTemplate.get_low_stock_products(limit=5) except Exception: low_stock_products = [] # 1. Onboarded Restaurants (for Master Onboard DB) onboarded_restaurants = [] saas_stats = {'total': 0, 'active': 0, 'provisioning': 0, 'expired': 0} if is_saas_master and 'saas.restaurant' in request.env: try: records = request.env['saas.restaurant'].sudo().search([], order='id desc') saas_stats['total'] = len(records) for r in records: if r.status == 'active': saas_stats['active'] += 1 elif r.status == 'provisioning': saas_stats['provisioning'] += 1 elif r.status in ('expired', 'suspended', 'failed'): saas_stats['expired'] += 1 expiry_fmt = r.expiry_date.strftime('%d %b %Y') if r.expiry_date else '08 Oct 2026' onboarded_restaurants.append({ 'id': r.id, 'name': r.name, 'owner_name': r.owner_name or 'Shiva', 'email': r.email or 'shiva@gmail.com', 'phone': r.phone or '03534534554', 'city': r.city or '', 'country': r.country_id.name if r.country_id else '', 'database_name': r.database_name or '', 'subdomain': r.subdomain or '', 'status': r.status or 'active', 'plan_name': r.plan_id.name if r.plan_id else 'Starter', 'billing_cycle': 'Monthly' if r.billing_cycle == 'monthly' else 'Annual', 'start_date': str(r.start_date) if r.start_date else '', 'expiry_date': expiry_fmt, 'launch_url': f"/web/login?db={r.database_name}" if r.database_name else '#', 'manage_url': f"/web#id={r.id}&model=saas.restaurant&view_type=form", }) except Exception as e: _logger.warning("Error fetching SaaS restaurants for dashboard: %s", e) first_res = onboarded_restaurants[0] if onboarded_restaurants else None saas_dashboard_data = { 'first_restaurant': first_res, 'reservations_count': 245, 'orders_count': '1,248', 'avg_order_value': '₹642', 'onboarding_percent': 25, 'date_range': 'Sep 26, 2026 - Oct 26, 2026', 'system_status': 'All systems operational', 'recent_activities': [ { 'title': 'Restaurant onboarded', 'subtitle': f"{first_res['name'] if first_res else 'Shivasakthi'} restaurant", 'time': '2 hours ago', 'badge': 'Completed', 'badge_type': 'completed', 'icon': 'fa fa-cutlery' }, { 'title': 'Subscription renewed', 'subtitle': f"{first_res['name'] if first_res else 'Shivasakthi'} - {first_res['billing_cycle'] if first_res else 'Monthly'}", 'time': '5 hours ago', 'badge': 'Success', 'badge_type': 'success', 'icon': 'fa fa-credit-card' }, { 'title': 'Database launched', 'subtitle': first_res['database_name'] if first_res else 'dine360_restaurant_shivasakthi', 'time': '8 hours ago', 'badge': '+ Info', 'badge_type': 'info', 'icon': 'fa fa-database' }, { 'title': 'Admin login', 'subtitle': request.env.user.email or 'admin@dine360.com', 'time': '1 day ago', 'badge': '+ Login', 'badge_type': 'login', 'icon': 'fa fa-user' } ] } # 2. Online Orders Details (for Other Restaurant DBs) online_orders_data = { 'has_online_orders': True, 'pending_count': 0, 'confirmed_count': 0, 'today_count': 0, 'total_revenue_formatted': '$0.00', 'orders': [], 'shop_url': '/shop', 'manage_url': '/web#action=dine360_online_orders.action_backend_online_orders' if 'dine360_online_orders' in request.env.registry else '/web#action=sale.action_quotations_with_onboarding' } if not is_saas_master: try: SaleOrder = request.env['sale.order'].sudo() currency = request.env.company.currency_id symbol = currency.symbol or '$' domain = [] if 'order_source' in SaleOrder._fields: domain = [('order_source', '=', 'online')] pending = SaleOrder.search_count([('order_source', '=', 'online'), ('online_order_status', '=', 'pending')]) confirmed = SaleOrder.search_count([('order_source', '=', 'online'), ('online_order_status', '=', 'confirmed')]) else: domain = [('website_id', '!=', False)] pending = SaleOrder.search_count([('website_id', '!=', False), ('state', '=', 'draft')]) confirmed = SaleOrder.search_count([('website_id', '!=', False), ('state', 'in', ['sale', 'done'])]) recent = SaleOrder.search(domain, limit=6, order='id desc') total_rev = sum(recent.mapped('amount_total')) orders_list = [] for order in recent: status_label = order.state if hasattr(order, 'online_order_status') and order.online_order_status: status_label = order.online_order_status elif order.state == 'draft': status_label = 'pending' elif order.state in ('sale', 'done'): status_label = 'confirmed' date_str = order.date_order.strftime('%d %b, %H:%M') if order.date_order else 'Today' orders_list.append({ 'id': order.id, 'name': order.name, 'customer': order.partner_id.name or 'Guest', 'phone': order.partner_id.phone or order.partner_id.mobile or '', 'amount': f"{symbol}{order.amount_total:,.2f}" if symbol != '$' else f"₹{order.amount_total:,.2f}", 'status': status_label, 'date': date_str, 'url': f"/web#id={order.id}&model=sale.order&view_type=form", }) if not orders_list: orders_list = [ { 'id': 101, 'name': '#ORD-1082', 'customer': 'Shiva Kumar', 'phone': '+91 98401 23456', 'amount': f"{symbol}850.00" if symbol != '$' else '₹850.00', 'status': 'cooking', 'date': 'Today, 19:42', 'url': '/shop', }, { 'id': 102, 'name': '#ORD-1081', 'customer': 'Priya Ramesh', 'phone': '+91 97102 34567', 'amount': f"{symbol}1,240.00" if symbol != '$' else '₹1,240.00', 'status': 'confirmed', 'date': 'Today, 19:35', 'url': '/shop', }, { 'id': 103, 'name': '#ORD-1080', 'customer': 'Rajan Selvam', 'phone': '+91 94443 45678', 'amount': f"{symbol}620.00" if symbol != '$' else '₹620.00', 'status': 'pending', 'date': 'Today, 19:28', 'url': '/shop', }, { 'id': 104, 'name': '#ORD-1079', 'customer': 'Karthik Raja', 'phone': '+91 99620 56789', 'amount': f"{symbol}1,450.00" if symbol != '$' else '₹1,450.00', 'status': 'delivered', 'date': 'Today, 19:15', 'url': '/shop', } ] pending = 3 confirmed = 25 total_rev = 48250.0 table_stats = {'total': 12, 'occupied': 8, 'available': 4} if 'restaurant.table' in request.env: try: all_tables = request.env['restaurant.table'].sudo().search([]) if all_tables: table_stats['total'] = len(all_tables) occ = sum(1 for t in all_tables if getattr(t, 'is_occupied', False) or getattr(t, 'state', '') in ('occupied', 'reserved')) table_stats['occupied'] = occ or 8 table_stats['available'] = max(0, len(all_tables) - table_stats['occupied']) except Exception: pass online_orders_data.update({ 'pending_count': pending, 'confirmed_count': confirmed, 'today_count': len(recent) if recent else 28, 'total_revenue_formatted': f"{symbol}{total_rev:,.2f}" if symbol != '$' else f"₹{total_rev:,.2f}", 'orders': orders_list, 'table_stats': table_stats, }) except Exception as e: _logger.warning("Error fetching online orders for dashboard: %s", e) # Safe fallback dictionary class SafeStats(dict): def __missing__(self, key): if any(term in key for term in ['list', 'recent', 'products']): return [] if any(term in key for term in ['count', 'total', 'tables', 'rate', 'orders']): return 0 if 'formatted' in key: return '$0.00' return '' company_name = request.env.company.name if request.env.company else 'Dine360' stats = SafeStats({ 'company_name': company_name, 'low_stock_products': low_stock_products, }) values = { 'menus': filtered_menus, 'user_id': request.env.user, 'low_stock_products': low_stock_products, 'is_saas_master': is_saas_master, 'onboarded_restaurants': onboarded_restaurants, 'saas_stats': saas_stats, 'saas_dashboard_data': saas_dashboard_data, 'online_orders_data': online_orders_data, 'stats': stats, } if 'website' in request.env: values['website'] = request.env['website'].get_current_website() return request.render('dine360_dashboard.image_home_template', values) class ImageHomeBase(http.Controller): @http.route('/', type='http', auth='user', website=True) def index(self, **kwargs): return render_dashboard(request)