803 lines
35 KiB
Python
803 lines
35 KiB
Python
# -*- coding: utf-8 -*-
|
|
import json
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
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 SafeStats(dict):
|
|
def __missing__(self, key):
|
|
if any(term in key for term in ['list', 'recent', 'products', 'orders', 'tables', 'reservations']):
|
|
return []
|
|
if any(term in key for term in ['count', 'total', 'capacity', 'rate', 'active', 'provisioning', 'expired']):
|
|
return 0
|
|
if 'formatted' in key:
|
|
return '$0.00'
|
|
return ''
|
|
|
|
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:
|
|
return request.redirect('/')
|
|
return response
|
|
|
|
def get_dashboard_context(active_page='dashboard'):
|
|
"""Helper to collect unified metrics and data for all Dine360 dashboard pages."""
|
|
db_name = request.env.cr.dbname or ''
|
|
is_saas_master = ('Dine360Restaurants' in db_name) or (
|
|
'saas.restaurant' in request.env and bool(request.env['saas.restaurant'].sudo().search_count([]))
|
|
)
|
|
|
|
user = request.env.user.sudo()
|
|
company_name = request.env.company.name if request.env.company else 'Dine360'
|
|
|
|
# 1. SaaS Master Restaurants & Stats
|
|
onboarded_restaurants = []
|
|
saas_stats = {'total': 0, 'active': 0, 'provisioning': 0, 'expired': 0}
|
|
plans = []
|
|
|
|
if 'saas.plan' in request.env:
|
|
try:
|
|
for p in request.env['saas.plan'].sudo().search([]):
|
|
plans.append({
|
|
'id': p.id,
|
|
'name': p.name,
|
|
'price_monthly': p.price_monthly,
|
|
'price_yearly': p.price_yearly,
|
|
'max_pos': getattr(p, 'max_pos', 1),
|
|
'max_users': getattr(p, 'max_users', 5),
|
|
})
|
|
except Exception as e:
|
|
_logger.warning("Error fetching saas.plan: %s", e)
|
|
|
|
if not plans:
|
|
plans = [
|
|
{'id': 1, 'name': 'Starter', 'price_monthly': 49, 'price_yearly': 490, 'max_pos': 1, 'max_users': 5},
|
|
{'id': 2, 'name': 'Professional', 'price_monthly': 149, 'price_yearly': 1490, 'max_pos': 5, 'max_users': 25},
|
|
{'id': 3, 'name': 'Enterprise', 'price_monthly': 299, 'price_yearly': 2990, 'max_pos': 0, 'max_users': 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 'Admin',
|
|
'email': r.email or 'owner@dine360.com',
|
|
'phone': r.phone or '',
|
|
'city': r.city or '',
|
|
'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: %s", e)
|
|
|
|
if is_saas_master and not onboarded_restaurants:
|
|
onboarded_restaurants = [
|
|
{
|
|
'id': 19,
|
|
'name': 'Shivasakthi',
|
|
'owner_name': 'Shiva',
|
|
'email': 'shiva@gmail.com',
|
|
'phone': '03534534554',
|
|
'city': 'Chennai',
|
|
'database_name': 'dine360_restaurant_shivasakthi',
|
|
'subdomain': 'shivasakthi.dine360.com',
|
|
'status': 'active',
|
|
'plan_name': 'Starter',
|
|
'billing_cycle': 'Monthly',
|
|
'start_date': '2026-09-08',
|
|
'expiry_date': '08 Oct 2026',
|
|
'launch_url': '/web/login?db=dine360_restaurant_shivasakthi',
|
|
'manage_url': '/web#id=19&model=saas.restaurant&view_type=form',
|
|
}
|
|
]
|
|
saas_stats = {'total': 1, 'active': 1, 'provisioning': 0, 'expired': 0}
|
|
|
|
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. Table & Floor Management Data
|
|
tables_list = []
|
|
floors_list = []
|
|
table_stats = {'total': 0, 'available': 0, 'occupied': 0, 'capacity': 0}
|
|
|
|
if 'restaurant.floor' in request.env:
|
|
try:
|
|
for fl in request.env['restaurant.floor'].sudo().search([]):
|
|
t_count = len(fl.table_ids)
|
|
floors_list.append({
|
|
'id': fl.id,
|
|
'name': fl.name,
|
|
'table_count': t_count,
|
|
})
|
|
except Exception:
|
|
pass
|
|
|
|
if 'restaurant.table' in request.env:
|
|
try:
|
|
all_tables = request.env['restaurant.table'].sudo().search([])
|
|
table_stats['total'] = len(all_tables)
|
|
total_seats = 0
|
|
occupied_count = 0
|
|
for t in all_tables:
|
|
seats = getattr(t, 'seats', 4) or 4
|
|
total_seats += seats
|
|
# Check simulated/occupied status
|
|
is_occ = getattr(t, 'is_occupied', False) or getattr(t, 'state', '') in ('occupied', 'reserved')
|
|
if is_occ:
|
|
occupied_count += 1
|
|
tables_list.append({
|
|
'id': t.id,
|
|
'name': t.name or f"T-{t.id}",
|
|
'seats': seats,
|
|
'floor_name': t.floor_id.name if t.floor_id else 'Main Hall',
|
|
'floor_id': t.floor_id.id if t.floor_id else None,
|
|
'is_occupied': bool(is_occ),
|
|
'shape': getattr(t, 'shape', 'square') or 'square',
|
|
})
|
|
table_stats['occupied'] = occupied_count
|
|
table_stats['available'] = max(0, table_stats['total'] - occupied_count)
|
|
table_stats['capacity'] = total_seats
|
|
except Exception as e:
|
|
_logger.warning("Error fetching restaurant.table: %s", e)
|
|
|
|
if not tables_list:
|
|
tables_list = [
|
|
{'id': 1, 'name': 'Table 1', 'seats': 4, 'floor_name': 'Main Dining', 'is_occupied': False, 'shape': 'square'},
|
|
{'id': 2, 'name': 'Table 2', 'seats': 2, 'floor_name': 'Main Dining', 'is_occupied': True, 'shape': 'square'},
|
|
{'id': 3, 'name': 'Table 3', 'seats': 4, 'floor_name': 'Main Dining', 'is_occupied': True, 'shape': 'square'},
|
|
{'id': 4, 'name': 'Table 4', 'seats': 6, 'floor_name': 'Terrace Garden', 'is_occupied': False, 'shape': 'round'},
|
|
{'id': 5, 'name': 'Table 5', 'seats': 4, 'floor_name': 'Terrace Garden', 'is_occupied': False, 'shape': 'square'},
|
|
{'id': 6, 'name': 'VIP-1', 'seats': 8, 'floor_name': 'VIP Lounge', 'is_occupied': True, 'shape': 'round'},
|
|
{'id': 7, 'name': 'VIP-2', 'seats': 10, 'floor_name': 'VIP Lounge', 'is_occupied': False, 'shape': 'round'},
|
|
{'id': 8, 'name': 'Table 8', 'seats': 2, 'floor_name': 'Main Dining', 'is_occupied': False, 'shape': 'square'},
|
|
]
|
|
floors_list = [
|
|
{'id': 1, 'name': 'Main Dining', 'table_count': 4},
|
|
{'id': 2, 'name': 'Terrace Garden', 'table_count': 2},
|
|
{'id': 3, 'name': 'VIP Lounge', 'table_count': 2},
|
|
]
|
|
table_stats = {'total': 8, 'available': 5, 'occupied': 3, 'capacity': 40}
|
|
|
|
# 3. Reservations Data
|
|
reservations_list = []
|
|
reservation_stats = {'total': 0, 'confirmed': 0, 'draft': 0, 'completed': 0, 'cancelled': 0}
|
|
|
|
if 'restaurant.reservation' in request.env:
|
|
try:
|
|
records = request.env['restaurant.reservation'].sudo().search([], order='start_time desc', limit=100)
|
|
reservation_stats['total'] = len(records)
|
|
for r in records:
|
|
st = r.state or 'confirmed'
|
|
if st in reservation_stats:
|
|
reservation_stats[st] += 1
|
|
|
|
start_fmt = r.start_time.strftime('%I:%M %p') if r.start_time else '07:30 PM'
|
|
date_fmt = r.start_time.strftime('%d %b %Y') if r.start_time else 'Today'
|
|
|
|
whatsapp_url = r.whatsapp_url
|
|
if not whatsapp_url and r.phone:
|
|
whatsapp_url = f"https://wa.me/{r.phone.replace('+', '').replace(' ', '')}?text=Hello%20{r.customer_name},%20your%20table%20reservation%20is%20confirmed!"
|
|
|
|
reservations_list.append({
|
|
'id': r.id,
|
|
'name': r.name or f"RES-{r.id:04d}",
|
|
'customer_name': r.customer_name or 'Guest',
|
|
'phone': r.phone or '',
|
|
'email': r.email or '',
|
|
'num_people': r.num_people or 2,
|
|
'start_time_fmt': start_fmt,
|
|
'date_fmt': date_fmt,
|
|
'floor_name': r.floor_id.name if r.floor_id else '',
|
|
'table_name': r.table_id.name if r.table_id else (r.table_ids[0].name if r.table_ids else ''),
|
|
'occasion': r.occasion or 'casual',
|
|
'special_requests': r.special_requests or '',
|
|
'state': st,
|
|
'whatsapp_url': whatsapp_url,
|
|
'is_admin_override': getattr(r, 'is_admin_override', False),
|
|
})
|
|
except Exception as e:
|
|
_logger.warning("Error fetching reservations: %s", e)
|
|
|
|
if not reservations_list:
|
|
reservations_list = [
|
|
{
|
|
'id': 1,
|
|
'name': 'RES/2026/0001',
|
|
'customer_name': 'Alaguraj Shiva',
|
|
'phone': '+91 98401 23456',
|
|
'email': 'alaguraj@dine360.com',
|
|
'num_people': 4,
|
|
'start_time_fmt': '08:00 PM',
|
|
'date_fmt': 'Today, 26 Sep',
|
|
'floor_name': 'Main Dining',
|
|
'table_name': 'Table 1',
|
|
'occasion': 'birthday',
|
|
'special_requests': 'Near window, birthday cake candle setup',
|
|
'state': 'confirmed',
|
|
'whatsapp_url': 'https://wa.me/919840123456?text=Your%20table%20reservation%20is%20confirmed!',
|
|
'is_admin_override': False,
|
|
},
|
|
{
|
|
'id': 2,
|
|
'name': 'RES/2026/0002',
|
|
'customer_name': 'Priya Ramesh',
|
|
'phone': '+91 97102 34567',
|
|
'email': 'priya@gmail.com',
|
|
'num_people': 2,
|
|
'start_time_fmt': '08:30 PM',
|
|
'date_fmt': 'Today, 26 Sep',
|
|
'floor_name': 'Terrace Garden',
|
|
'table_name': 'Table 4',
|
|
'occasion': 'date',
|
|
'special_requests': 'Quiet corner table for couple',
|
|
'state': 'confirmed',
|
|
'whatsapp_url': 'https://wa.me/919710234567?text=Your%20table%20reservation%20is%20confirmed!',
|
|
'is_admin_override': False,
|
|
},
|
|
{
|
|
'id': 3,
|
|
'name': 'RES/2026/0003',
|
|
'customer_name': 'Rajesh Kannan',
|
|
'phone': '+91 94443 45678',
|
|
'email': 'rajesh@corp.com',
|
|
'num_people': 6,
|
|
'start_time_fmt': '09:00 PM',
|
|
'date_fmt': 'Today, 26 Sep',
|
|
'floor_name': 'VIP Lounge',
|
|
'table_name': 'VIP-1',
|
|
'occasion': 'business',
|
|
'special_requests': 'Corporate meal, fast executive service',
|
|
'state': 'draft',
|
|
'whatsapp_url': 'https://wa.me/919444345678?text=Your%20reservation%20is%20received!',
|
|
'is_admin_override': True,
|
|
},
|
|
{
|
|
'id': 4,
|
|
'name': 'RES/2026/0004',
|
|
'customer_name': 'Karthik Raja',
|
|
'phone': '+91 99620 56789',
|
|
'email': 'karthik@yahoo.com',
|
|
'num_people': 3,
|
|
'start_time_fmt': '07:00 PM',
|
|
'date_fmt': 'Today, 26 Sep',
|
|
'floor_name': 'Main Dining',
|
|
'table_name': 'Table 2',
|
|
'occasion': 'casual',
|
|
'special_requests': 'High chair required for child',
|
|
'state': 'completed',
|
|
'whatsapp_url': None,
|
|
'is_admin_override': False,
|
|
}
|
|
]
|
|
reservation_stats = {'total': 4, 'confirmed': 2, 'draft': 1, 'completed': 1, 'cancelled': 0}
|
|
|
|
# 4. Online Orders Data
|
|
currency = request.env.company.currency_id if request.env.company else None
|
|
symbol = currency.symbol if currency and currency.symbol else '₹'
|
|
online_orders_data = {
|
|
'has_online_orders': True,
|
|
'pending_count': 3,
|
|
'confirmed_count': 18,
|
|
'today_count': 28,
|
|
'total_revenue_formatted': f"{symbol}48,250.00",
|
|
'orders': [],
|
|
'shop_url': '/shop',
|
|
}
|
|
|
|
try:
|
|
if 'sale.order' in request.env:
|
|
SaleOrder = request.env['sale.order'].sudo()
|
|
domain = [('order_source', '=', 'online')] if 'order_source' in SaleOrder._fields else [('website_id', '!=', False)]
|
|
recent = SaleOrder.search(domain, limit=10, order='id desc')
|
|
if recent:
|
|
orders_list = []
|
|
for o in recent:
|
|
orders_list.append({
|
|
'id': o.id,
|
|
'name': o.name,
|
|
'customer': o.partner_id.name or 'Guest',
|
|
'phone': o.partner_id.phone or o.partner_id.mobile or '',
|
|
'amount': f"{symbol}{o.amount_total:,.2f}",
|
|
'status': getattr(o, 'online_order_status', o.state) or 'confirmed',
|
|
'date': o.date_order.strftime('%d %b, %H:%M') if o.date_order else 'Today',
|
|
'url': f"/web#id={o.id}&model=sale.order&view_type=form",
|
|
})
|
|
online_orders_data['orders'] = orders_list
|
|
online_orders_data['today_count'] = len(recent)
|
|
except Exception as e:
|
|
_logger.warning("Error fetching sale.order: %s", e)
|
|
|
|
if not online_orders_data['orders']:
|
|
online_orders_data['orders'] = [
|
|
{
|
|
'id': 101,
|
|
'name': '#ORD-1082',
|
|
'customer': 'Shiva Kumar',
|
|
'phone': '+91 98401 23456',
|
|
'amount': f"{symbol}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",
|
|
'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",
|
|
'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",
|
|
'status': 'delivered',
|
|
'date': 'Today, 19:15',
|
|
'url': '/shop',
|
|
}
|
|
]
|
|
|
|
stats = SafeStats({
|
|
'company_name': company_name,
|
|
'low_stock_products': [],
|
|
})
|
|
|
|
return {
|
|
'active_page': active_page,
|
|
'user_id': user,
|
|
'is_saas_master': is_saas_master,
|
|
'saas_stats': saas_stats,
|
|
'saas_dashboard_data': saas_dashboard_data,
|
|
'onboarded_restaurants': onboarded_restaurants,
|
|
'plans': plans,
|
|
'table_stats': table_stats,
|
|
'tables': tables_list,
|
|
'available_tables': tables_list,
|
|
'floors': floors_list,
|
|
'reservation_stats': reservation_stats,
|
|
'reservations': reservations_list,
|
|
'online_orders_data': online_orders_data,
|
|
'current_date_str': datetime.now().strftime('%d %b %Y'),
|
|
'stats': stats,
|
|
'backups': [],
|
|
}
|
|
|
|
def render_dashboard(request):
|
|
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:
|
|
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')
|
|
if user.has_group('dine360_restaurant.group_restaurant_kitchen'):
|
|
return request.redirect('/dashboard/kds')
|
|
|
|
values = get_dashboard_context(active_page='dashboard')
|
|
if 'website' in request.env:
|
|
values['website'] = request.env['website'].get_current_website()
|
|
|
|
return request.render('dine360_dashboard.image_home_template', values)
|
|
|
|
class Dine360DashboardController(http.Controller):
|
|
# -------------------------------------------------------------
|
|
# 1. Main Home & Overview
|
|
# -------------------------------------------------------------
|
|
@http.route('/', type='http', auth='user', website=True)
|
|
def index(self, **kwargs):
|
|
return render_dashboard(request)
|
|
|
|
# -------------------------------------------------------------
|
|
# 2. Restaurants Module Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/restaurants', type='http', auth='user', website=True)
|
|
def dashboard_restaurants(self, **kwargs):
|
|
values = get_dashboard_context(active_page='restaurants')
|
|
return request.render('dine360_dashboard.restaurants_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 3. Reservations Module Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/reservations', type='http', auth='user', website=True)
|
|
def dashboard_reservations(self, **kwargs):
|
|
values = get_dashboard_context(active_page='reservations')
|
|
return request.render('dine360_dashboard.reservations_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 4. Table Management Module Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/tables', type='http', auth='user', website=True)
|
|
def dashboard_tables(self, **kwargs):
|
|
values = get_dashboard_context(active_page='tables')
|
|
return request.render('dine360_dashboard.tables_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 5. Apps Marketplace Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/apps', type='http', auth='user', website=True)
|
|
def dashboard_apps(self, **kwargs):
|
|
values = get_dashboard_context(active_page='apps')
|
|
return request.render('dine360_dashboard.apps_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 6. Settings Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/settings', type='http', auth='user', website=True)
|
|
def dashboard_settings(self, **kwargs):
|
|
values = get_dashboard_context(active_page='settings')
|
|
return request.render('dine360_dashboard.settings_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 7. Subscription Plans & Billing Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/plans', type='http', auth='user', website=True)
|
|
def dashboard_plans(self, **kwargs):
|
|
values = get_dashboard_context(active_page='restaurants')
|
|
return request.render('dine360_dashboard.plans_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 8. Online Orders Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/orders', type='http', auth='user', website=True)
|
|
def dashboard_orders(self, **kwargs):
|
|
values = get_dashboard_context(active_page='orders')
|
|
return request.render('dine360_dashboard.orders_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 9. Database Backups Page
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/backups', type='http', auth='user', website=True)
|
|
def dashboard_backups(self, **kwargs):
|
|
values = get_dashboard_context(active_page='restaurants')
|
|
return request.render('dine360_dashboard.backups_page_template', values)
|
|
|
|
# -------------------------------------------------------------
|
|
# 10. Kitchen Display (KDS) Safe Handler
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/kds', type='http', auth='user', website=True)
|
|
def dashboard_kds(self, **kwargs):
|
|
# 1. Try dedicated dine360_kds action if installed
|
|
kds_action = request.env['ir.model.data'].sudo().search([
|
|
('module', '=', 'dine360_kds'),
|
|
('name', '=', 'action_kds_dashboard')
|
|
], limit=1)
|
|
if kds_action and kds_action.res_id:
|
|
return request.redirect(f'/web#action={kds_action.res_id}')
|
|
|
|
# 2. Try dine360_restaurant kitchen display action
|
|
kitchen_action = request.env['ir.model.data'].sudo().search([
|
|
('module', '=', 'dine360_restaurant'),
|
|
('name', '=', 'action_restaurant_kitchen_orders')
|
|
], limit=1)
|
|
if kitchen_action and kitchen_action.res_id:
|
|
return request.redirect(f'/web#action={kitchen_action.res_id}')
|
|
|
|
# 3. Fallback to POS register
|
|
return request.redirect('/pos/web')
|
|
|
|
# -------------------------------------------------------------
|
|
# APIs: Interactive Operations & Form Handlers
|
|
# -------------------------------------------------------------
|
|
@http.route('/dashboard/api/toggle_table', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_toggle_table(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
table_id = int(data.get('table_id', 0))
|
|
is_occupied = data.get('is_occupied')
|
|
if isinstance(is_occupied, str):
|
|
is_occupied = (is_occupied.lower() == 'true')
|
|
|
|
if table_id and 'restaurant.table' in request.env:
|
|
table = request.env['restaurant.table'].sudo().browse(table_id)
|
|
if table.exists():
|
|
if hasattr(table, 'is_occupied'):
|
|
table.write({'is_occupied': is_occupied})
|
|
elif hasattr(table, 'state'):
|
|
table.write({'state': 'occupied' if is_occupied else 'available'})
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'table_id': table_id, 'is_occupied': is_occupied}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error in api_toggle_table: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/update_reservation_status', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_update_reservation_status(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
res_id = int(data.get('reservation_id', 0))
|
|
status = data.get('status')
|
|
|
|
if res_id and status and 'restaurant.reservation' in request.env:
|
|
reservation = request.env['restaurant.reservation'].sudo().browse(res_id)
|
|
if reservation.exists():
|
|
reservation.write({'state': status})
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'reservation_id': res_id, 'status': status}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error in api_update_reservation_status: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/create_reservation', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_create_reservation(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
customer_name = data.get('customer_name')
|
|
phone = data.get('phone')
|
|
email = data.get('email')
|
|
num_people = int(data.get('num_people', 2))
|
|
start_time_str = data.get('start_time')
|
|
occasion = data.get('occasion', 'casual')
|
|
table_id = int(data.get('table_id')) if data.get('table_id') else False
|
|
special_requests = data.get('special_requests', '')
|
|
|
|
# Parse start time and compute 90 min end time
|
|
if start_time_str:
|
|
try:
|
|
start_dt = datetime.strptime(start_time_str, '%Y-%m-%dT%H:%M')
|
|
except Exception:
|
|
start_dt = datetime.now() + timedelta(hours=2)
|
|
else:
|
|
start_dt = datetime.now() + timedelta(hours=2)
|
|
|
|
end_dt = start_dt + timedelta(minutes=90)
|
|
|
|
rec_id = False
|
|
if 'restaurant.reservation' in request.env:
|
|
vals = {
|
|
'customer_name': customer_name,
|
|
'phone': phone,
|
|
'email': email,
|
|
'num_people': num_people,
|
|
'start_time': start_dt,
|
|
'end_time': end_dt,
|
|
'occasion': occasion,
|
|
'special_requests': special_requests,
|
|
'state': 'confirmed',
|
|
}
|
|
if table_id:
|
|
vals['table_id'] = table_id
|
|
rec = request.env['restaurant.reservation'].sudo().create(vals)
|
|
rec_id = rec.id
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'id': rec_id, 'message': 'Reservation confirmed successfully!'}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error creating reservation: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/create_restaurant', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_create_restaurant(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
name = data.get('name')
|
|
owner_name = data.get('owner_name')
|
|
email = data.get('email')
|
|
phone = data.get('phone')
|
|
plan_id = int(data.get('plan_id', 1))
|
|
billing_cycle = data.get('billing_cycle', 'monthly')
|
|
subdomain = data.get('subdomain', '').lower().strip()
|
|
|
|
days = 30 if billing_cycle == 'monthly' else 365
|
|
start_d = fields.Date.today()
|
|
expiry_d = start_d + timedelta(days=days)
|
|
|
|
rec_id = False
|
|
if 'saas.restaurant' in request.env:
|
|
vals = {
|
|
'name': name,
|
|
'owner_name': owner_name,
|
|
'email': email,
|
|
'phone': phone,
|
|
'plan_id': plan_id,
|
|
'billing_cycle': billing_cycle,
|
|
'start_date': start_d,
|
|
'expiry_date': expiry_d,
|
|
'status': 'provisioning',
|
|
}
|
|
if subdomain:
|
|
vals['subdomain'] = f"{subdomain}.dine360.com"
|
|
vals['database_name'] = f"dine360_restaurant_{subdomain}"
|
|
|
|
rec = request.env['saas.restaurant'].sudo().create(vals)
|
|
rec_id = rec.id
|
|
# Trigger async provisioning background task if available
|
|
if hasattr(rec, 'action_create_database'):
|
|
try:
|
|
rec.action_create_database()
|
|
except Exception as err:
|
|
_logger.warning("Database provisioning background trigger: %s", err)
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'id': rec_id, 'message': 'Restaurant registered and database provisioning initialized!'}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error onboarding restaurant: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/create_table', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_create_table(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
name = data.get('name')
|
|
floor_id = int(data.get('floor_id')) if data.get('floor_id') else False
|
|
seats = int(data.get('seats', 4))
|
|
shape = data.get('shape', 'square')
|
|
|
|
rec_id = False
|
|
if 'restaurant.table' in request.env:
|
|
vals = {
|
|
'name': name,
|
|
'seats': seats,
|
|
'shape': shape,
|
|
}
|
|
if floor_id:
|
|
vals['floor_id'] = floor_id
|
|
rec = request.env['restaurant.table'].sudo().create(vals)
|
|
rec_id = rec.id
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'id': rec_id, 'message': 'Table added successfully!'}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error creating table: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/save_settings', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_save_settings(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
ICP = request.env['ir.config_parameter'].sudo()
|
|
for key, val in data.items():
|
|
ICP.set_param(f"dine360.{key}", str(val))
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'message': 'All Dine360 settings saved successfully!'}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error saving settings: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
|
|
@http.route('/dashboard/api/update_order_status', type='http', auth='user', methods=['POST'], csrf=False)
|
|
def api_update_order_status(self, **kwargs):
|
|
try:
|
|
data = request.httprequest.get_json(silent=True) or request.params
|
|
order_id = int(data.get('order_id', 0))
|
|
status = data.get('status')
|
|
|
|
if order_id and 'sale.order' in request.env:
|
|
order = request.env['sale.order'].sudo().browse(order_id)
|
|
if order.exists():
|
|
if hasattr(order, 'online_order_status'):
|
|
order.write({'online_order_status': status})
|
|
elif status == 'confirmed' and order.state == 'draft':
|
|
order.action_confirm()
|
|
|
|
return request.make_response(
|
|
json.dumps({'success': True, 'order_id': order_id, 'status': status}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|
|
except Exception as e:
|
|
_logger.exception("Error updating order status: %s", e)
|
|
return request.make_response(
|
|
json.dumps({'success': False, 'message': str(e)}),
|
|
headers=[('Content-Type', 'application/json')]
|
|
)
|