fix(dashboard): resolve 500 internal server error on production server

- Add SafeStats fallback wrapper to prevent QWeb KeyError exceptions
- Safely resolve dictionary keys with .get() across all dashboard templates
- Add support for production SaaS master database 'dine360db'
- Add route mapping for '/dashboard' alongside '/'
- Remove invalid tracking attribute from restaurant.table is_occupied field
This commit is contained in:
Alaguraj0361 2026-10-12 12:42:32 +05:30
parent 1929e81878
commit 42abb214ac
6 changed files with 67 additions and 59 deletions

View File

@ -17,14 +17,19 @@ class MenuWrapper:
class SafeStats(dict): class SafeStats(dict):
def __missing__(self, key): def __missing__(self, key):
if any(term in key for term in ['list', 'recent', 'products', 'orders', 'tables', 'reservations']): if any(term in key for term in ['list', 'recent', 'products', 'orders', 'tables', 'reservations', 'floors', 'activities']):
return [] return []
if any(term in key for term in ['count', 'total', 'capacity', 'rate', 'active', 'provisioning', 'expired']): if any(term in key for term in ['count', 'total', 'capacity', 'rate', 'active', 'provisioning', 'expired', 'occupied', 'available', 'seats', 'percent']):
return 0 return 0
if 'formatted' in key: if any(term in key for term in ['formatted', 'value', 'avg', 'revenue', 'price', 'ticket']):
return '$0.00' return '₹0.00'
return '' return ''
def get(self, key, default=None):
if key in self:
return self[key]
return self.__missing__(key) if default is None else default
class CustomHome(Home): class CustomHome(Home):
@http.route('/web/login', type='http', auth="public") @http.route('/web/login', type='http', auth="public")
def web_login(self, *args, **kw): def web_login(self, *args, **kw):
@ -35,9 +40,12 @@ class CustomHome(Home):
def get_dashboard_context(active_page='dashboard'): def get_dashboard_context(active_page='dashboard'):
"""Helper to collect unified metrics and data for all Dine360 dashboard pages.""" """Helper to collect unified metrics and data for all Dine360 dashboard pages."""
db_name = request.env.cr.dbname or '' db_name = (request.env.cr.dbname or '').lower()
is_saas_master = ('Dine360Restaurants' in db_name) or ( is_saas_master = (
'saas.restaurant' in request.env and bool(request.env['saas.restaurant'].sudo().search_count([])) 'dine360restaurants' in db_name or
'dine360db' in db_name or
'saas' in db_name or
('saas.restaurant' in request.env)
) )
user = request.env.user.sudo() user = request.env.user.sudo()
@ -345,17 +353,17 @@ def get_dashboard_context(active_page='dashboard'):
'active_page': active_page, 'active_page': active_page,
'user_id': user, 'user_id': user,
'is_saas_master': is_saas_master, 'is_saas_master': is_saas_master,
'saas_stats': saas_stats, 'saas_stats': SafeStats(saas_stats),
'saas_dashboard_data': saas_dashboard_data, 'saas_dashboard_data': SafeStats(saas_dashboard_data),
'onboarded_restaurants': onboarded_restaurants, 'onboarded_restaurants': onboarded_restaurants,
'plans': plans, 'plans': plans,
'table_stats': table_stats, 'table_stats': SafeStats(table_stats),
'tables': tables_list, 'tables': tables_list,
'available_tables': [t for t in tables_list if not t.get('is_occupied')], 'available_tables': [t for t in tables_list if not t.get('is_occupied')],
'floors': floors_list, 'floors': floors_list,
'reservation_stats': reservation_stats, 'reservation_stats': SafeStats(reservation_stats),
'reservations': reservations_list, 'reservations': reservations_list,
'online_orders_data': online_orders_data, 'online_orders_data': SafeStats(online_orders_data),
'current_date_str': datetime.now().strftime('%d %b %Y'), 'current_date_str': datetime.now().strftime('%d %b %Y'),
'stats': stats, 'stats': stats,
'low_stock_products': low_stock, 'low_stock_products': low_stock,
@ -384,7 +392,7 @@ class Dine360DashboardController(http.Controller):
# ------------------------------------------------------------- # -------------------------------------------------------------
# 1. Main Home & Overview # 1. Main Home & Overview
# ------------------------------------------------------------- # -------------------------------------------------------------
@http.route('/', type='http', auth='user', website=True) @http.route(['/', '/dashboard'], type='http', auth='user', website=True)
def index(self, **kwargs): def index(self, **kwargs):
return render_dashboard(request) return render_dashboard(request)

View File

@ -62,7 +62,7 @@
<span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Total Restaurants</div> <div class="o_kpi_label">Total Restaurants</div>
<div class="o_kpi_number"><t t-esc="saas_stats['total']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('total', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Currently onboarded</span> <span class="o_kpi_sub">Currently onboarded</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -80,7 +80,7 @@
<span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Active Subscriptions</div> <div class="o_kpi_label">Active Subscriptions</div>
<div class="o_kpi_number"><t t-esc="saas_stats['active']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('active', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Running smoothly</span> <span class="o_kpi_sub">Running smoothly</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -98,7 +98,7 @@
<span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Provisioning</div> <div class="o_kpi_label">Provisioning</div>
<div class="o_kpi_number"><t t-esc="saas_stats['provisioning']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('provisioning', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">In progress</span> <span class="o_kpi_sub">In progress</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -116,7 +116,7 @@
<span class="o_kpi_trend text-muted"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-muted"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Expired / Suspended</div> <div class="o_kpi_label">Expired / Suspended</div>
<div class="o_kpi_number"><t t-esc="saas_stats['expired']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('expired', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Requires attention</span> <span class="o_kpi_sub">Requires attention</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -197,7 +197,7 @@
<div> <div>
<div class="o_substat_title">Active Restaurants</div> <div class="o_substat_title">Active Restaurants</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="saas_stats['active']"/></span> <span class="o_substat_val"><t t-esc="saas_stats.get('active', 0)"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> 100%</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> 100%</span>
</div> </div>
</div> </div>
@ -210,7 +210,7 @@
<div> <div>
<div class="o_substat_title">Total Orders</div> <div class="o_substat_title">Total Orders</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="saas_dashboard_data['orders_count']"/></span> <span class="o_substat_val"><t t-esc="saas_dashboard_data.get('orders_count', 0)"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> +8%</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> +8%</span>
</div> </div>
</div> </div>
@ -223,7 +223,7 @@
<div> <div>
<div class="o_substat_title">Avg. Order Value</div> <div class="o_substat_title">Avg. Order Value</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="saas_dashboard_data['avg_order_value']"/></span> <span class="o_substat_val"><t t-esc="saas_dashboard_data.get('avg_order_value', '₹0.00')"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> +6%</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> +6%</span>
</div> </div>
</div> </div>
@ -344,7 +344,7 @@
<div class="o_card_header"> <div class="o_card_header">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<h3 class="o_card_title mb-0">Onboarded Restaurants</h3> <h3 class="o_card_title mb-0">Onboarded Restaurants</h3>
<span class="o_count_pill"><t t-esc="saas_stats['total']"/> Registered</span> <span class="o_count_pill"><t t-esc="saas_stats.get('total', 0)"/> Registered</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<button type="button" class="o_btn_red_small" data-open-modal="modalOnboardRestaurant"> <button type="button" class="o_btn_red_small" data-open-modal="modalOnboardRestaurant">
@ -507,7 +507,7 @@
<!-- Page Title & Status Header --> <!-- Page Title & Status Header -->
<section class="o_page_header"> <section class="o_page_header">
<div> <div>
<h1 class="o_heading_title"><t t-esc="stats['company_name']"/> Dashboard</h1> <h1 class="o_heading_title"><t t-esc="stats.get('company_name', 'Dine360')"/> Dashboard</h1>
<p class="o_heading_subtitle">Live restaurant operations, online orders, and table status</p> <p class="o_heading_subtitle">Live restaurant operations, online orders, and table status</p>
</div> </div>
<div class="o_header_controls"> <div class="o_header_controls">
@ -541,7 +541,7 @@
<span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +14%</span> <span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +14%</span>
</div> </div>
<div class="o_kpi_label">Online Orders</div> <div class="o_kpi_label">Online Orders</div>
<div class="o_kpi_number"><t t-esc="online_orders_data['today_count']"/></div> <div class="o_kpi_number"><t t-esc="online_orders_data.get('today_count', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Received from website</span> <span class="o_kpi_sub">Received from website</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -559,7 +559,7 @@
<span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +8%</span> <span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +8%</span>
</div> </div>
<div class="o_kpi_label">Today's Revenue</div> <div class="o_kpi_label">Today's Revenue</div>
<div class="o_kpi_number"><t t-esc="online_orders_data['total_revenue_formatted']"/></div> <div class="o_kpi_number"><t t-esc="online_orders_data.get('total_revenue_formatted', '₹0.00')"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Across POS &amp; Online</span> <span class="o_kpi_sub">Across POS &amp; Online</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -577,7 +577,7 @@
<span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> 67%</span> <span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> 67%</span>
</div> </div>
<div class="o_kpi_label">Occupied Tables</div> <div class="o_kpi_label">Occupied Tables</div>
<div class="o_kpi_number"><t t-esc="table_stats['occupied']"/> / <t t-esc="table_stats['total']"/></div> <div class="o_kpi_number"><t t-esc="table_stats.get('occupied', 0)"/> / <t t-esc="table_stats.get('total', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Active floor guests</span> <span class="o_kpi_sub">Active floor guests</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -595,7 +595,7 @@
<span class="o_kpi_trend text-danger"><i class="fa fa-exclamation-circle"/> Action</span> <span class="o_kpi_trend text-danger"><i class="fa fa-exclamation-circle"/> Action</span>
</div> </div>
<div class="o_kpi_label">Pending Orders</div> <div class="o_kpi_label">Pending Orders</div>
<div class="o_kpi_number"><t t-esc="online_orders_data['pending_count']"/></div> <div class="o_kpi_number"><t t-esc="online_orders_data.get('pending_count', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Requires kitchen action</span> <span class="o_kpi_sub">Requires kitchen action</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -676,7 +676,7 @@
<div> <div>
<div class="o_substat_title">Reservations</div> <div class="o_substat_title">Reservations</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="reservation_stats['total']"/></span> <span class="o_substat_val"><t t-esc="reservation_stats.get('total', 0)"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span>
</div> </div>
</div> </div>
@ -689,7 +689,7 @@
<div> <div>
<div class="o_substat_title">Online Orders</div> <div class="o_substat_title">Online Orders</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="online_orders_data['today_count']"/></span> <span class="o_substat_val"><t t-esc="online_orders_data.get('today_count', 0)"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span>
</div> </div>
</div> </div>
@ -702,7 +702,7 @@
<div> <div>
<div class="o_substat_title">Avg. Ticket Value</div> <div class="o_substat_title">Avg. Ticket Value</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="o_substat_val"><t t-esc="online_orders_data['avg_order_value']"/></span> <span class="o_substat_val"><t t-esc="online_orders_data.get('avg_order_value', '₹0.00')"/></span>
<span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span> <span class="o_substat_pct text-success"><i class="fa fa-arrow-up"/> Live</span>
</div> </div>
</div> </div>
@ -859,7 +859,7 @@
<div class="o_card_header"> <div class="o_card_header">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<h3 class="o_card_title mb-0">Live Restaurant Online Orders</h3> <h3 class="o_card_title mb-0">Live Restaurant Online Orders</h3>
<span class="o_count_pill"><t t-esc="online_orders_data['today_count']"/> Active</span> <span class="o_count_pill"><t t-esc="online_orders_data.get('today_count', 0)"/> Active</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<a href="/shop" target="_blank" class="o_btn_red_small"> <a href="/shop" target="_blank" class="o_btn_red_small">

View File

@ -36,7 +36,7 @@
<span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> Live</span> <span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> Live</span>
</div> </div>
<div class="o_kpi_label">Total Bookings</div> <div class="o_kpi_label">Total Bookings</div>
<div class="o_kpi_number"><t t-esc="reservation_stats['total']"/></div> <div class="o_kpi_number"><t t-esc="reservation_stats.get('total', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">All scheduled guests</span> <span class="o_kpi_sub">All scheduled guests</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -54,7 +54,7 @@
<span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> Confirmed</span> <span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> Confirmed</span>
</div> </div>
<div class="o_kpi_label">Confirmed Bookings</div> <div class="o_kpi_label">Confirmed Bookings</div>
<div class="o_kpi_number"><t t-esc="reservation_stats['confirmed']"/></div> <div class="o_kpi_number"><t t-esc="reservation_stats.get('confirmed', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Ready for seating</span> <span class="o_kpi_sub">Ready for seating</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -72,7 +72,7 @@
<span class="o_kpi_trend text-warning">Pending</span> <span class="o_kpi_trend text-warning">Pending</span>
</div> </div>
<div class="o_kpi_label">Draft / Pending</div> <div class="o_kpi_label">Draft / Pending</div>
<div class="o_kpi_number"><t t-esc="reservation_stats['draft']"/></div> <div class="o_kpi_number"><t t-esc="reservation_stats.get('draft', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Requires confirmation</span> <span class="o_kpi_sub">Requires confirmation</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -90,7 +90,7 @@
<span class="o_kpi_trend text-primary">Completed</span> <span class="o_kpi_trend text-primary">Completed</span>
</div> </div>
<div class="o_kpi_label">Seated / Done</div> <div class="o_kpi_label">Seated / Done</div>
<div class="o_kpi_number"><t t-esc="reservation_stats['completed']"/></div> <div class="o_kpi_number"><t t-esc="reservation_stats.get('completed', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Guests dined</span> <span class="o_kpi_sub">Guests dined</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -103,11 +103,11 @@
<!-- Row 2: Toolbar (Filters & Search) --> <!-- Row 2: Toolbar (Filters & Search) -->
<div class="o_page_toolbar"> <div class="o_page_toolbar">
<div class="o_filter_pills"> <div class="o_filter_pills">
<div class="o_filter_pill active" data-filter="all">All Bookings (<t t-esc="reservation_stats['total']"/>)</div> <div class="o_filter_pill active" data-filter="all">All Bookings (<t t-esc="reservation_stats.get('total', 0)"/>)</div>
<div class="o_filter_pill" data-filter="confirmed">Confirmed (<t t-esc="reservation_stats['confirmed']"/>)</div> <div class="o_filter_pill" data-filter="confirmed">Confirmed (<t t-esc="reservation_stats.get('confirmed', 0)"/>)</div>
<div class="o_filter_pill" data-filter="draft">Draft / Pending (<t t-esc="reservation_stats['draft']"/>)</div> <div class="o_filter_pill" data-filter="draft">Draft / Pending (<t t-esc="reservation_stats.get('draft', 0)"/>)</div>
<div class="o_filter_pill" data-filter="completed">Completed (<t t-esc="reservation_stats['completed']"/>)</div> <div class="o_filter_pill" data-filter="completed">Completed (<t t-esc="reservation_stats.get('completed', 0)"/>)</div>
<div class="o_filter_pill" data-filter="cancelled">Cancelled (<t t-esc="reservation_stats['cancelled']"/>)</div> <div class="o_filter_pill" data-filter="cancelled">Cancelled (<t t-esc="reservation_stats.get('cancelled', 0)"/>)</div>
</div> </div>
<div class="o_toolbar_search"> <div class="o_toolbar_search">
@ -121,7 +121,7 @@
<div class="o_card_header"> <div class="o_card_header">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<h3 class="o_card_title mb-0">Live Guest Bookings List</h3> <h3 class="o_card_title mb-0">Live Guest Bookings List</h3>
<span class="o_count_pill"><t t-esc="reservation_stats['total']"/> Reservations</span> <span class="o_count_pill"><t t-esc="reservation_stats.get('total', 0)"/> Reservations</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<button type="button" class="o_btn_red_small" data-open-modal="modalNewReservation"> <button type="button" class="o_btn_red_small" data-open-modal="modalNewReservation">

View File

@ -38,7 +38,7 @@
<span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-danger"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Total Restaurants</div> <div class="o_kpi_label">Total Restaurants</div>
<div class="o_kpi_number"><t t-esc="saas_stats['total']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('total', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Currently onboarded</span> <span class="o_kpi_sub">Currently onboarded</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -58,7 +58,7 @@
<span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-success"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Active Subscriptions</div> <div class="o_kpi_label">Active Subscriptions</div>
<div class="o_kpi_number"><t t-esc="saas_stats['active']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('active', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Running smoothly</span> <span class="o_kpi_sub">Running smoothly</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -76,7 +76,7 @@
<span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-warning"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Provisioning</div> <div class="o_kpi_label">Provisioning</div>
<div class="o_kpi_number"><t t-esc="saas_stats['provisioning']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('provisioning', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">In progress</span> <span class="o_kpi_sub">In progress</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -94,7 +94,7 @@
<span class="o_kpi_trend text-secondary"><i class="fa fa-arrow-up"/> +0%</span> <span class="o_kpi_trend text-secondary"><i class="fa fa-arrow-up"/> +0%</span>
</div> </div>
<div class="o_kpi_label">Expired / Suspended</div> <div class="o_kpi_label">Expired / Suspended</div>
<div class="o_kpi_number"><t t-esc="saas_stats['expired']"/></div> <div class="o_kpi_number"><t t-esc="saas_stats.get('expired', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Requires attention</span> <span class="o_kpi_sub">Requires attention</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -107,10 +107,10 @@
<!-- Row 2: Toolbar (Filters & Search) --> <!-- Row 2: Toolbar (Filters & Search) -->
<div class="o_page_toolbar"> <div class="o_page_toolbar">
<div class="o_filter_pills"> <div class="o_filter_pills">
<div class="o_filter_pill active" data-filter="all">All Restaurants (<t t-esc="saas_stats['total']"/>)</div> <div class="o_filter_pill active" data-filter="all">All Restaurants (<t t-esc="saas_stats.get('total', 0)"/>)</div>
<div class="o_filter_pill" data-filter="active">Active (<t t-esc="saas_stats['active']"/>)</div> <div class="o_filter_pill" data-filter="active">Active (<t t-esc="saas_stats.get('active', 0)"/>)</div>
<div class="o_filter_pill" data-filter="provisioning">Provisioning (<t t-esc="saas_stats['provisioning']"/>)</div> <div class="o_filter_pill" data-filter="provisioning">Provisioning (<t t-esc="saas_stats.get('provisioning', 0)"/>)</div>
<div class="o_filter_pill" data-filter="expired">Expired / Inactive (<t t-esc="saas_stats['expired']"/>)</div> <div class="o_filter_pill" data-filter="expired">Expired / Inactive (<t t-esc="saas_stats.get('expired', 0)"/>)</div>
</div> </div>
<div class="o_toolbar_search"> <div class="o_toolbar_search">
@ -124,7 +124,7 @@
<div class="o_card_header"> <div class="o_card_header">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<h3 class="o_card_title mb-0">Onboarded Restaurant Databases</h3> <h3 class="o_card_title mb-0">Onboarded Restaurant Databases</h3>
<span class="o_count_pill"><t t-esc="saas_stats['total']"/> Registered</span> <span class="o_count_pill"><t t-esc="saas_stats.get('total', 0)"/> Registered</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<button type="button" class="o_btn_red_small" data-open-modal="modalOnboardRestaurant"> <button type="button" class="o_btn_red_small" data-open-modal="modalOnboardRestaurant">
@ -154,7 +154,7 @@
<path d="M18 2C14.5 2 12 4.5 12 7.5C10 7.8 8.5 9.5 8.5 11.5C8.5 13.2 9.6 14.6 11.2 15.1C11.1 15.5 11 16 11 16.5C11 19.5 13.5 22 16.5 22H19.5C22.5 22 25 19.5 25 16.5C25 16 24.9 15.5 24.8 15.1C26.4 14.6 27.5 13.2 27.5 11.5C27.5 9.5 26 7.8 24 7.5C24 4.5 21.5 2 18 2Z" fill="none" stroke="#e50000" stroke-width="2.5"/> <path d="M18 2C14.5 2 12 4.5 12 7.5C10 7.8 8.5 9.5 8.5 11.5C8.5 13.2 9.6 14.6 11.2 15.1C11.1 15.5 11 16 11 16.5C11 19.5 13.5 22 16.5 22H19.5C22.5 22 25 19.5 25 16.5C25 16 24.9 15.5 24.8 15.1C26.4 14.6 27.5 13.2 27.5 11.5C27.5 9.5 26 7.8 24 7.5C24 4.5 21.5 2 18 2Z" fill="none" stroke="#e50000" stroke-width="2.5"/>
<path d="M12 22V25C12 26 13 27 14 27H22C23 27 24 26 24 25V22" stroke="#e50000" stroke-width="2.5"/> <path d="M12 22V25C12 26 13 27 14 27H22C23 27 24 26 24 25V22" stroke="#e50000" stroke-width="2.5"/>
</svg> </svg>
<span class="o_res_avatar_letter"><t t-esc="restaurant['name'][0].upper() if restaurant['name'] else 'R'"/></span> <span class="o_res_avatar_letter"><t t-esc="restaurant['name'][0].upper() if restaurant.get('name') else 'R'"/></span>
</div> </div>
<div> <div>
<div class="o_res_title"><t t-esc="restaurant['name']"/></div> <div class="o_res_title"><t t-esc="restaurant['name']"/></div>

View File

@ -32,7 +32,7 @@
<span class="o_kpi_trend text-danger">Configured</span> <span class="o_kpi_trend text-danger">Configured</span>
</div> </div>
<div class="o_kpi_label">Total Tables</div> <div class="o_kpi_label">Total Tables</div>
<div class="o_kpi_number"><t t-esc="table_stats['total']"/></div> <div class="o_kpi_number"><t t-esc="table_stats.get('total', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Across all dining zones</span> <span class="o_kpi_sub">Across all dining zones</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -50,7 +50,7 @@
<span class="o_kpi_trend text-success">Ready</span> <span class="o_kpi_trend text-success">Ready</span>
</div> </div>
<div class="o_kpi_label">Available Tables</div> <div class="o_kpi_label">Available Tables</div>
<div class="o_kpi_number"><t t-esc="table_stats['available']"/></div> <div class="o_kpi_number"><t t-esc="table_stats.get('available', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Ready for walk-in guests</span> <span class="o_kpi_sub">Ready for walk-in guests</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -68,7 +68,7 @@
<span class="o_kpi_trend text-danger">Dining</span> <span class="o_kpi_trend text-danger">Dining</span>
</div> </div>
<div class="o_kpi_label">Occupied Tables</div> <div class="o_kpi_label">Occupied Tables</div>
<div class="o_kpi_number"><t t-esc="table_stats['occupied']"/></div> <div class="o_kpi_number"><t t-esc="table_stats.get('occupied', 0)"/></div>
<div class="o_kpi_footer"> <div class="o_kpi_footer">
<span class="o_kpi_sub">Currently in service</span> <span class="o_kpi_sub">Currently in service</span>
<svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none"> <svg class="o_sparkline" viewBox="0 0 100 30" preserveAspectRatio="none">
@ -99,14 +99,14 @@
<!-- Row 2: Floor Filters & Search --> <!-- Row 2: Floor Filters & Search -->
<div class="o_page_toolbar"> <div class="o_page_toolbar">
<div class="o_filter_pills"> <div class="o_filter_pills">
<div class="o_filter_pill active" data-filter="all">All Floors (<t t-esc="table_stats['total']"/>)</div> <div class="o_filter_pill active" data-filter="all">All Floors (<t t-esc="table_stats.get('total', 0)"/>)</div>
<t t-foreach="floors" t-as="fl"> <t t-foreach="floors" t-as="fl">
<div class="o_filter_pill" t-att-data-filter="fl['name'].lower()"> <div class="o_filter_pill" t-att-data-filter="fl.get('name', '').lower()">
<t t-esc="fl['name']"/> (<t t-esc="fl['table_count']"/>) <t t-esc="fl.get('name', '')"/> (<t t-esc="fl.get('table_count', 0)"/>)
</div> </div>
</t> </t>
<div class="o_filter_pill" data-filter="available">Available Only (<t t-esc="table_stats['available']"/>)</div> <div class="o_filter_pill" data-filter="available">Available Only (<t t-esc="table_stats.get('available', 0)"/>)</div>
<div class="o_filter_pill" data-filter="occupied">Occupied Only (<t t-esc="table_stats['occupied']"/>)</div> <div class="o_filter_pill" data-filter="occupied">Occupied Only (<t t-esc="table_stats.get('occupied', 0)"/>)</div>
</div> </div>
<div class="o_toolbar_search"> <div class="o_toolbar_search">

View File

@ -3,7 +3,7 @@ from odoo import models, fields, api
class RestaurantTable(models.Model): class RestaurantTable(models.Model):
_inherit = 'restaurant.table' _inherit = 'restaurant.table'
is_occupied = fields.Boolean(string='Is Occupied', default=False, tracking=True) is_occupied = fields.Boolean(string='Is Occupied', default=False)
is_reservation_enabled = fields.Boolean(string='Enabled for Reservation', default=True) is_reservation_enabled = fields.Boolean(string='Enabled for Reservation', default=True)
min_party_size = fields.Integer(string='Min Party Size', default=1) min_party_size = fields.Integer(string='Min Party Size', default=1)
max_party_size = fields.Integer(string='Max Party Size', default=1) max_party_size = fields.Integer(string='Max Party Size', default=1)