Spaces:
Sleeping
Sleeping
| """ | |
| Optimized database queries for better performance | |
| """ | |
| from sqlalchemy.orm import Session, joinedload, selectinload | |
| from sqlalchemy import and_, or_, func, text | |
| from typing import List, Optional, Dict, Any | |
| from datetime import datetime, timedelta | |
| import logging | |
| from ..database import Order, OrderItem, Dish, Person, Table | |
| logger = logging.getLogger(__name__) | |
| class OptimizedQueryService: | |
| """Service for optimized database queries with caching and performance improvements""" | |
| def __init__(self): | |
| self.query_cache = {} | |
| self.cache_ttl = { | |
| 'menu': 300, # 5 minutes | |
| 'categories': 900, # 15 minutes | |
| 'specials': 300, # 5 minutes | |
| 'offers': 300, # 5 minutes | |
| } | |
| def get_menu_optimized(self, db: Session, category: Optional[str] = None) -> List[Dict]: | |
| """Optimized menu query with eager loading and caching""" | |
| try: | |
| # Build optimized query | |
| query = db.query(Dish).filter( | |
| Dish.is_visible == True | |
| ) | |
| if category and category != 'All': | |
| query = query.filter(Dish.category == category) | |
| # Order by category and name for consistent results | |
| query = query.order_by(Dish.category, Dish.name) | |
| # Execute query | |
| dishes = query.all() | |
| # Convert to dict for JSON serialization | |
| result = [] | |
| for dish in dishes: | |
| dish_dict = { | |
| 'id': dish.id, | |
| 'name': dish.name, | |
| 'description': dish.description, | |
| 'price': float(dish.price), | |
| 'category': dish.category, | |
| 'image_path': dish.image_path, | |
| 'is_offer': dish.is_offer, | |
| 'discount': float(dish.discount) if dish.discount else 0, | |
| 'is_visible': dish.is_visible, | |
| 'created_at': dish.created_at.isoformat() if dish.created_at else None | |
| } | |
| result.append(dish_dict) | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in get_menu_optimized: {str(e)}") | |
| raise | |
| def get_orders_optimized(self, db: Session, person_id: Optional[int] = None, | |
| table_number: Optional[int] = None, | |
| status: Optional[str] = None) -> List[Dict]: | |
| """Optimized order query with eager loading of related data""" | |
| try: | |
| # Build base query with eager loading | |
| query = db.query(Order).options( | |
| selectinload(Order.items).selectinload(OrderItem.dish), | |
| joinedload(Order.person) | |
| ) | |
| # Apply filters | |
| filters = [] | |
| if person_id: | |
| filters.append(Order.person_id == person_id) | |
| if table_number: | |
| filters.append(Order.table_number == table_number) | |
| if status: | |
| filters.append(Order.status == status) | |
| if filters: | |
| query = query.filter(and_(*filters)) | |
| # Order by creation time (newest first) | |
| query = query.order_by(Order.created_at.desc()) | |
| # Execute query | |
| orders = query.all() | |
| # Convert to dict with optimized serialization | |
| result = [] | |
| for order in orders: | |
| order_dict = { | |
| 'id': order.id, | |
| 'table_number': order.table_number, | |
| 'unique_id': order.unique_id, | |
| 'person_id': order.person_id, | |
| 'status': order.status, | |
| 'created_at': order.created_at.isoformat() if order.created_at else None, | |
| 'updated_at': order.updated_at.isoformat() if order.updated_at else None, | |
| 'items': [] | |
| } | |
| # Add order items | |
| for item in order.items: | |
| item_dict = { | |
| 'id': item.id, | |
| 'dish_id': item.dish_id, | |
| 'dish_name': item.dish.name if item.dish else 'Unknown', | |
| 'quantity': item.quantity, | |
| 'price': float(item.price), | |
| 'remarks': item.remarks, | |
| 'position': item.position | |
| } | |
| order_dict['items'].append(item_dict) | |
| result.append(order_dict) | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in get_orders_optimized: {str(e)}") | |
| raise | |
| def get_chef_orders_optimized(self, db: Session, status: str) -> List[Dict]: | |
| """Optimized chef order query with minimal data transfer""" | |
| try: | |
| # Use raw SQL for better performance on chef queries | |
| sql = text(""" | |
| SELECT | |
| o.id, | |
| o.table_number, | |
| o.status, | |
| o.created_at, | |
| o.updated_at, | |
| COUNT(oi.id) as item_count, | |
| GROUP_CONCAT( | |
| CONCAT(d.name, ' (', oi.quantity, ')') | |
| SEPARATOR ', ' | |
| ) as items_summary | |
| FROM orders o | |
| LEFT JOIN order_items oi ON o.id = oi.order_id | |
| LEFT JOIN dishes d ON oi.dish_id = d.id | |
| WHERE o.status = :status | |
| GROUP BY o.id, o.table_number, o.status, o.created_at, o.updated_at | |
| ORDER BY o.created_at ASC | |
| """) | |
| result = db.execute(sql, {'status': status}).fetchall() | |
| # Convert to dict | |
| orders = [] | |
| for row in result: | |
| order_dict = { | |
| 'id': row.id, | |
| 'table_number': row.table_number, | |
| 'status': row.status, | |
| 'created_at': row.created_at.isoformat() if row.created_at else None, | |
| 'updated_at': row.updated_at.isoformat() if row.updated_at else None, | |
| 'item_count': row.item_count, | |
| 'items_summary': row.items_summary or '' | |
| } | |
| orders.append(order_dict) | |
| return orders | |
| except Exception as e: | |
| logger.error(f"Error in get_chef_orders_optimized: {str(e)}") | |
| # Fallback to regular query | |
| return self._get_chef_orders_fallback(db, status) | |
| def _get_chef_orders_fallback(self, db: Session, status: str) -> List[Dict]: | |
| """Fallback method for chef orders if raw SQL fails""" | |
| try: | |
| orders = db.query(Order).options( | |
| selectinload(Order.items).selectinload(OrderItem.dish) | |
| ).filter(Order.status == status).order_by(Order.created_at.asc()).all() | |
| result = [] | |
| for order in orders: | |
| items_summary = ', '.join([ | |
| f"{item.dish.name if item.dish else 'Unknown'} ({item.quantity})" | |
| for item in order.items | |
| ]) | |
| order_dict = { | |
| 'id': order.id, | |
| 'table_number': order.table_number, | |
| 'status': order.status, | |
| 'created_at': order.created_at.isoformat() if order.created_at else None, | |
| 'updated_at': order.updated_at.isoformat() if order.updated_at else None, | |
| 'item_count': len(order.items), | |
| 'items_summary': items_summary | |
| } | |
| result.append(order_dict) | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in chef orders fallback: {str(e)}") | |
| raise | |
| def get_table_status_optimized(self, db: Session) -> List[Dict]: | |
| """Optimized table status query""" | |
| try: | |
| # Use raw SQL for better performance | |
| sql = text(""" | |
| SELECT | |
| t.table_number, | |
| t.is_occupied, | |
| t.current_order_id, | |
| t.updated_at, | |
| o.status as order_status, | |
| COUNT(oi.id) as item_count | |
| FROM tables t | |
| LEFT JOIN orders o ON t.current_order_id = o.id | |
| LEFT JOIN order_items oi ON o.id = oi.order_id | |
| GROUP BY t.table_number, t.is_occupied, t.current_order_id, t.updated_at, o.status | |
| ORDER BY t.table_number | |
| """) | |
| result = db.execute(sql).fetchall() | |
| tables = [] | |
| for row in result: | |
| table_dict = { | |
| 'table_number': row.table_number, | |
| 'is_occupied': bool(row.is_occupied), | |
| 'current_order_id': row.current_order_id, | |
| 'updated_at': row.updated_at.isoformat() if row.updated_at else None, | |
| 'order_status': row.order_status, | |
| 'item_count': row.item_count or 0 | |
| } | |
| tables.append(table_dict) | |
| return tables | |
| except Exception as e: | |
| logger.error(f"Error in get_table_status_optimized: {str(e)}") | |
| raise | |
| def get_analytics_data_optimized(self, db: Session, start_date: datetime, | |
| end_date: datetime) -> Dict[str, Any]: | |
| """Optimized analytics query with aggregations""" | |
| try: | |
| # Use raw SQL for complex aggregations | |
| sql = text(""" | |
| SELECT | |
| DATE(o.created_at) as order_date, | |
| COUNT(DISTINCT o.id) as total_orders, | |
| COUNT(DISTINCT o.table_number) as unique_tables, | |
| SUM(oi.quantity * oi.price) as total_revenue, | |
| AVG(oi.quantity * oi.price) as avg_order_value, | |
| d.category, | |
| COUNT(oi.id) as items_sold | |
| FROM orders o | |
| JOIN order_items oi ON o.id = oi.order_id | |
| JOIN dishes d ON oi.dish_id = d.id | |
| WHERE o.created_at BETWEEN :start_date AND :end_date | |
| AND o.status = 'paid' | |
| GROUP BY DATE(o.created_at), d.category | |
| ORDER BY order_date DESC, d.category | |
| """) | |
| result = db.execute(sql, { | |
| 'start_date': start_date, | |
| 'end_date': end_date | |
| }).fetchall() | |
| # Process results | |
| analytics = { | |
| 'daily_stats': {}, | |
| 'category_stats': {}, | |
| 'summary': { | |
| 'total_orders': 0, | |
| 'total_revenue': 0, | |
| 'avg_order_value': 0 | |
| } | |
| } | |
| for row in result: | |
| date_str = row.order_date.isoformat() | |
| if date_str not in analytics['daily_stats']: | |
| analytics['daily_stats'][date_str] = { | |
| 'orders': row.total_orders, | |
| 'revenue': float(row.total_revenue), | |
| 'avg_value': float(row.avg_order_value), | |
| 'unique_tables': row.unique_tables | |
| } | |
| category = row.category | |
| if category not in analytics['category_stats']: | |
| analytics['category_stats'][category] = { | |
| 'items_sold': 0, | |
| 'revenue': 0 | |
| } | |
| analytics['category_stats'][category]['items_sold'] += row.items_sold | |
| return analytics | |
| except Exception as e: | |
| logger.error(f"Error in get_analytics_data_optimized: {str(e)}") | |
| raise | |
| # Create singleton instance | |
| optimized_queries = OptimizedQueryService() | |