750 lines
34 KiB
Python
750 lines
34 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 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 'N/A'
|
|
onboarded_restaurants.append({
|
|
'id': r.id,
|
|
'name': r.name,
|
|
'owner_name': r.owner_name or 'Admin',
|
|
'email': r.email or '',
|
|
'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 'Standard',
|
|
'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)
|
|
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
for r in request.env['saas.restaurant'].sudo().search([], order='create_date desc', limit=5):
|
|
recent_activities.append({
|
|
'title': f"Restaurant {r.status.capitalize() if r.status else 'Registered'}",
|
|
'subtitle': f"{r.name} ({r.database_name or 'N/A'})",
|
|
'time': r.create_date.strftime('%d %b, %H:%M') if getattr(r, 'create_date', False) else '',
|
|
'badge': (r.status or 'active').capitalize(),
|
|
'badge_type': 'completed' if r.status == 'active' else 'info',
|
|
'icon': 'fa fa-cutlery'
|
|
})
|
|
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:
|
|
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)
|
|
|
|
# 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)
|
|
|
|
# 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)
|
|
|
|
# 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': saas_stats,
|
|
'saas_dashboard_data': saas_dashboard_data,
|
|
'onboarded_restaurants': onboarded_restaurants,
|
|
'plans': plans,
|
|
'table_stats': table_stats,
|
|
'tables': tables_list,
|
|
'available_tables': [t for t in tables_list if not t.get('is_occupied')],
|
|
'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,
|
|
'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('/', 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 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',
|
|
'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:
|
|
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 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')]
|
|
)
|