From e4fd895c5f50ff0afe40b806097d5008b8cc6fdf Mon Sep 17 00:00:00 2001 From: Alaguraj0361 Date: Tue, 6 Oct 2026 11:17:52 +0530 Subject: [PATCH] add dashboard views and controller for dine360 restaurant management --- addons/dine360_dashboard/controllers/main.py | 356 +++++++----------- .../dine360_dashboard/views/backups_page.xml | 21 +- .../dine360_dashboard/views/home_template.xml | 47 ++- addons/dine360_dashboard/views/plans_page.xml | 160 +++----- 4 files changed, 221 insertions(+), 363 deletions(-) diff --git a/addons/dine360_dashboard/controllers/main.py b/addons/dine360_dashboard/controllers/main.py index d8a9488..22032e4 100644 --- a/addons/dine360_dashboard/controllers/main.py +++ b/addons/dine360_dashboard/controllers/main.py @@ -62,13 +62,6 @@ def get_dashboard_context(active_page='dashboard'): 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') @@ -81,18 +74,18 @@ def get_dashboard_context(active_page='dashboard'): 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' + 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 'owner@dine360.com', + '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 'Starter', + '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, @@ -102,71 +95,66 @@ def get_dashboard_context(active_page='dashboard'): 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 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': 245, - 'orders_count': '1,248', - 'avg_order_value': '₹642', - 'onboarding_percent': 25, - 'date_range': 'Sep 26, 2026 - Oct 26, 2026', + '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': [ - { - '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' - } - ] + 'recent_activities': recent_activities, } # 2. Table & Floor Management Data @@ -195,7 +183,6 @@ def get_dashboard_context(active_page='dashboard'): 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 @@ -214,24 +201,6 @@ def get_dashboard_context(active_page='dashboard'): 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} @@ -245,12 +214,13 @@ def get_dashboard_context(active_page='dashboard'): 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' + 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 = 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!" + 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, @@ -261,8 +231,8 @@ def get_dashboard_context(active_page='dashboard'): '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 ''), + '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, @@ -272,88 +242,14 @@ def get_dashboard_context(active_page='dashboard'): 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", + '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', } @@ -362,7 +258,28 @@ def get_dashboard_context(active_page='dashboard'): 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') + + all_online = SaleOrder.search(domain) + online_orders_data['has_online_orders'] = bool(all_online) + + pending_domain = domain + [('state', 'in', ['draft', 'sent'])] + confirmed_domain = domain + [('state', 'in', ['sale', 'done'])] + online_orders_data['pending_count'] = SaleOrder.search_count(pending_domain) + online_orders_data['confirmed_count'] = SaleOrder.search_count(confirmed_domain) + + 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: @@ -373,61 +290,47 @@ def get_dashboard_context(active_page='dashboard'): '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', + '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 - 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', - } - ] + # 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_products': low_stock, }) return { @@ -440,14 +343,15 @@ def get_dashboard_context(active_page='dashboard'): 'plans': plans, 'table_stats': table_stats, 'tables': tables_list, - 'available_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, - 'backups': [], + 'low_stock_products': low_stock, + 'backups': backups_list, } def render_dashboard(request): diff --git a/addons/dine360_dashboard/views/backups_page.xml b/addons/dine360_dashboard/views/backups_page.xml index c84a020..f186154 100644 --- a/addons/dine360_dashboard/views/backups_page.xml +++ b/addons/dine360_dashboard/views/backups_page.xml @@ -104,23 +104,10 @@ - dine360_restaurant_shivasakthi - backup_shivasakthi_20261001.zip - Automated - 46.2 MB - 01 Oct 2026, 03:00 AM - - Download - - - - Dine360Restaurants - backup_master_20261001.zip - Automated - 52.8 MB - 01 Oct 2026, 03:15 AM - - Download + + +
No database backups found
+

Tenant backup archives will appear here once generated.

diff --git a/addons/dine360_dashboard/views/home_template.xml b/addons/dine360_dashboard/views/home_template.xml index 789743a..e2e0b6e 100644 --- a/addons/dine360_dashboard/views/home_template.xml +++ b/addons/dine360_dashboard/views/home_template.xml @@ -39,7 +39,7 @@
- Sep 26, 2026 - Oct 26, 2026 +