826 lines
37 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', 'floors', 'activities']):
return []
if any(term in key for term in ['count', 'total', 'capacity', 'rate', 'active', 'provisioning', 'expired', 'occupied', 'available', 'seats', 'percent']):
return 0
if any(term in key for term in ['formatted', 'value', 'avg', 'revenue', 'price', 'ticket']):
return '₹0.00'
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):
@http.route('/web/login', type='http', auth="public", csrf=False, sitemap=False)
def web_login(self, *args, **kw):
response = super(CustomHome, self).web_login(*args, **kw)
if request.params.get('login_success') and request.session.uid:
redirect = kw.get('redirect') or request.params.get('redirect')
if not redirect:
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 '').lower()
is_saas_master = (
'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()
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)
try:
request.env.cr.rollback()
except Exception:
pass
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)
needed_fields = [
'id', 'name', 'owner_name', 'email', 'phone', 'city',
'database_name', 'subdomain', 'status', 'plan_id',
'billing_cycle', 'start_date', 'expiry_date'
]
fields_to_read = [f for f in needed_fields if f in request.env['saas.restaurant']._fields]
raw_data = records.read(fields_to_read)
for r_data in raw_data:
st = r_data.get('status') or 'active'
if st == 'active':
saas_stats['active'] += 1
elif st == 'provisioning':
saas_stats['provisioning'] += 1
elif st in ('expired', 'suspended', 'failed'):
saas_stats['expired'] += 1
exp_date = r_data.get('expiry_date')
expiry_fmt = exp_date.strftime('%d %b %Y') if hasattr(exp_date, 'strftime') else (str(exp_date) if exp_date else 'N/A')
plan_val = r_data.get('plan_id')
plan_name = plan_val[1] if isinstance(plan_val, (list, tuple)) and len(plan_val) > 1 else 'Standard'
r_id = r_data.get('id')
db_name_val = r_data.get('database_name') or ''
onboarded_restaurants.append({
'id': r_id,
'name': r_data.get('name') or '',
'owner_name': r_data.get('owner_name') or 'Admin',
'email': r_data.get('email') or '',
'phone': r_data.get('phone') or '',
'city': r_data.get('city') or '',
'database_name': db_name_val,
'subdomain': r_data.get('subdomain') or '',
'status': st,
'plan_name': plan_name,
'billing_cycle': 'Monthly' if r_data.get('billing_cycle') == 'monthly' else 'Annual',
'start_date': str(r_data.get('start_date') or ''),
'expiry_date': expiry_fmt,
'launch_url': f"/web/login?db={db_name_val}" if db_name_val 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)
try:
request.env.cr.rollback()
except Exception:
pass
first_res = onboarded_restaurants[0] if onboarded_restaurants else None
# SaaS master real activities and counts
currency = request.env.company.currency_id if request.env.company else None
symbol = currency.symbol if currency and currency.symbol else '₹'
res_count = 0
if 'restaurant.reservation' in request.env:
try:
res_count = request.env['restaurant.reservation'].sudo().search_count([])
except Exception:
try:
request.env.cr.rollback()
except Exception:
pass
orders_count = 0
total_sales = 0.0
if 'sale.order' in request.env:
try:
orders = request.env['sale.order'].sudo().search([])
orders_count += len(orders)
total_sales += sum(orders.mapped('amount_total'))
except Exception:
try:
request.env.cr.rollback()
except Exception:
pass
if 'pos.order' in request.env:
try:
pos_orders = request.env['pos.order'].sudo().search([])
orders_count += len(pos_orders)
total_sales += sum(pos_orders.mapped('amount_total'))
except Exception:
try:
request.env.cr.rollback()
except Exception:
pass
avg_val = (total_sales / orders_count) if orders_count else 0.0
recent_activities = []
if is_saas_master and 'saas.restaurant' in request.env:
try:
records = request.env['saas.restaurant'].sudo().search([], order='create_date desc', limit=5)
for r_data in records.read(['id', 'name', 'database_name', 'status', 'create_date']):
st = r_data.get('status') or 'active'
c_date = r_data.get('create_date')
time_str = c_date.strftime('%d %b, %H:%M') if hasattr(c_date, 'strftime') else (str(c_date) if c_date else '')
recent_activities.append({
'title': f"Restaurant {st.capitalize() if st else 'Registered'}",
'subtitle': f"{r_data.get('name')} ({r_data.get('database_name') or 'N/A'})",
'time': time_str,
'badge': (st or 'active').capitalize(),
'badge_type': 'completed' if st == 'active' else 'info',
'icon': 'fa fa-cutlery'
})
except Exception:
try:
request.env.cr.rollback()
except Exception:
pass
tot_restaurants = saas_stats.get('total', 0)
act_restaurants = saas_stats.get('active', 0)
onboard_pct = int((act_restaurants / tot_restaurants) * 100) if tot_restaurants > 0 else 0
saas_dashboard_data = {
'first_restaurant': first_res,
'reservations_count': res_count,
'orders_count': f"{orders_count:,}",
'avg_order_value': f"{symbol}{avg_val:,.2f}",
'onboarding_percent': onboard_pct,
'date_range': f"{(datetime.now() - timedelta(days=30)).strftime('%b %d, %Y')} - {datetime.now().strftime('%b %d, %Y')}",
'system_status': 'All systems operational',
'recent_activities': recent_activities,
}
# 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:
try:
request.env.cr.rollback()
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
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)
try:
request.env.cr.rollback()
except Exception:
pass
# 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 ''
date_fmt = r.start_time.strftime('%d %b %Y') if r.start_time else ''
whatsapp_url = getattr(r, 'whatsapp_url', False)
if not whatsapp_url and getattr(r, 'phone', False):
cleaned_phone = r.phone.replace('+', '').replace(' ', '').replace('-', '')
whatsapp_url = f"https://wa.me/{cleaned_phone}?text=Hello%20{r.customer_name or 'Guest'},%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 getattr(r, 'floor_id', False) else '',
'table_name': r.table_id.name if getattr(r, 'table_id', False) else (r.table_ids[0].name if getattr(r, 'table_ids', False) 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)
try:
request.env.cr.rollback()
except Exception:
pass
# 4. Online Orders Data
online_orders_data = {
'has_online_orders': False,
'pending_count': 0,
'confirmed_count': 0,
'today_count': 0,
'total_revenue_formatted': f"{symbol}0.00",
'avg_order_value': f"{symbol}0.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)]
all_online = SaleOrder.search(domain)
online_orders_data['has_online_orders'] = bool(all_online)
if 'online_order_status' in SaleOrder._fields:
pending_domain = domain + [('online_order_status', '=', 'pending')]
confirmed_domain = domain + [('online_order_status', '=', 'confirmed')]
cooking_domain = domain + [('online_order_status', '=', 'cooking')]
else:
pending_domain = domain + [('state', 'in', ['draft', 'sent'])]
confirmed_domain = domain + [('state', 'in', ['sale', 'done'])]
cooking_domain = []
online_orders_data['pending_count'] = SaleOrder.search_count(pending_domain)
online_orders_data['confirmed_count'] = SaleOrder.search_count(confirmed_domain)
online_orders_data['cooking_count'] = SaleOrder.search_count(cooking_domain) if cooking_domain else 0
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_orders = SaleOrder.search(domain + [('date_order', '>=', today_start)])
online_orders_data['today_count'] = len(today_orders) if today_orders else len(all_online)
revenue_orders = today_orders if today_orders else all_online
tot_rev = sum(revenue_orders.mapped('amount_total')) if revenue_orders else 0.0
online_orders_data['total_revenue_formatted'] = f"{symbol}{tot_rev:,.2f}"
if revenue_orders:
avg_ticket = tot_rev / len(revenue_orders)
online_orders_data['avg_order_value'] = f"{symbol}{avg_ticket:,.2f}"
recent = SaleOrder.search(domain, limit=20, 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', False) or ('confirmed' if o.state in ['sale', 'done'] else 'pending'),
'date': o.date_order.strftime('%d %b, %H:%M') if o.date_order else '',
'url': f"/web#id={o.id}&model=sale.order&view_type=form",
})
online_orders_data['orders'] = orders_list
except Exception as e:
_logger.warning("Error fetching sale.order: %s", e)
try:
request.env.cr.rollback()
except Exception:
pass
# 5. Backups Data
backups_list = []
if is_saas_master and 'saas.backup' in request.env:
try:
records = request.env['saas.backup'].sudo().search([], order='timestamp desc', limit=20)
for b in records:
db_name = b.database_name or (b.restaurant_id.database_name if getattr(b, 'restaurant_id', False) else '')
fname = b.backup_file or f"{db_name}_backup.zip"
backups_list.append({
'id': b.id,
'database_name': db_name,
'filename': fname,
'backup_type': b.storage_type.upper() if getattr(b, 'storage_type', False) else 'Local',
'size': 'Archive',
'date': b.timestamp.strftime('%d %b %Y, %I:%M %p') if getattr(b, 'timestamp', False) else '',
'download_url': b.s3_url or '#',
})
except Exception as e:
_logger.warning("Error fetching saas.backup: %s", e)
# 6. Low stock products
low_stock = []
if 'product.product' in request.env:
try:
low_stock = request.env['product.product'].sudo().search([
('type', '=', 'product'),
('qty_available', '<=', 5)
], limit=5)
except Exception:
pass
stats = SafeStats({
'company_name': company_name,
'low_stock_products': low_stock,
})
return {
'active_page': active_page,
'user_id': user,
'is_saas_master': is_saas_master,
'saas_stats': SafeStats(saas_stats),
'saas_dashboard_data': SafeStats(saas_dashboard_data),
'onboarded_restaurants': onboarded_restaurants,
'plans': plans,
'table_stats': SafeStats(table_stats),
'tables': tables_list,
'available_tables': [t for t in tables_list if not t.get('is_occupied')],
'floors': floors_list,
'reservation_stats': SafeStats(reservation_stats),
'reservations': reservations_list,
'online_orders_data': SafeStats(online_orders_data),
'current_date_str': datetime.now().strftime('%d %b %Y'),
'stats': stats,
'low_stock_products': low_stock,
'backups': backups_list,
}
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(['/', '/dashboard'], 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')
if values.get('is_saas_master'):
return request.redirect('/dashboard/restaurants')
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')
if values.get('is_saas_master'):
return request.redirect('/dashboard/restaurants')
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 Dashboard
return request.redirect('/dashboard')
@http.route(['/pos/web', '/pos/ui', '/pos/web/'], type='http', auth='user')
def redirect_pos_to_dashboard(self, **kwargs):
"""Redirect direct POS cashier access to Dine360 restaurant dashboard."""
return request.redirect('/dashboard')
# -------------------------------------------------------------
# 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():
table.write({'is_occupied': bool(is_occupied)})
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',
'is_admin_override': True,
'override_reason': 'Dashboard Booking',
}
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:
if not floor_id and 'restaurant.floor' in request.env:
first_floor = request.env['restaurant.floor'].sudo().search([], limit=1)
if first_floor:
floor_id = first_floor.id
import uuid
vals = {
'name': name or f"Table {request.env['restaurant.table'].sudo().search_count([]) + 1}",
'seats': seats,
'shape': shape,
'is_occupied': False,
'is_reservation_enabled': True,
'reservation_slot_duration': '1.0',
'identifier': str(uuid.uuid4())[:8],
}
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 status == 'confirmed':
if hasattr(order, 'action_accept_online_order'):
order.action_accept_online_order()
else:
if order.state in ['draft', 'sent']:
order.action_confirm()
order.write({'online_order_status': 'confirmed'})
elif status == 'cooking':
if hasattr(order, 'action_cook_online_order'):
order.action_cook_online_order()
else:
order.write({'online_order_status': 'cooking'})
if order.pos_order_id:
order.pos_order_id.write({'online_order_status': 'cooking'})
if hasattr(order.pos_order_id, 'lines'):
order.pos_order_id.lines.write({'preparation_status': 'preparing'})
elif status in ['delivered', 'ready']:
if hasattr(order, 'action_deliver_online_order'):
order.action_deliver_online_order()
else:
order.write({'online_order_status': 'delivered'})
if order.pos_order_id:
order.pos_order_id.write({'online_order_status': 'delivered'})
if hasattr(order.pos_order_id, 'lines'):
order.pos_order_id.lines.write({'preparation_status': 'ready'})
elif status == 'rejected':
if hasattr(order, 'action_reject_online_order'):
order.action_reject_online_order()
else:
order._action_cancel()
order.write({'online_order_status': 'rejected'})
else:
order.write({'online_order_status': status})
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')]
)