368 lines
16 KiB
Python

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)