import logging from decimal import Decimal from enum import Enum from typing import Optional from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.DatabaseService import DatabaseService from src.ez_lan_manager.services.UserService import UserService from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus, CateringMenuItemsWithAmount from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory logger = logging.getLogger(__name__.split(".")[-1]) class CateringErrorType(Enum): INCLUDES_DISABLED_ITEM = 0 INSUFFICIENT_FUNDS = 1 GENERIC = 99 class CateringError(Exception): def __init__(self, message: str, error_type: CateringErrorType = CateringErrorType.GENERIC) -> None: self.message = message self.error_type = error_type class CateringService: def __init__(self, db_service: DatabaseService, accounting_service: AccountingService, user_service: UserService): self._db_service = db_service self._accounting_service = accounting_service self._user_service = user_service self.cached_cart: dict[int, list[CateringMenuItem]] = {} # ORDERS async def place_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool = True) -> CateringOrder: for menu_item in menu_items: if menu_item.is_disabled: raise CateringError("Order includes disabled items", CateringErrorType.INCLUDES_DISABLED_ITEM) user = await self._user_service.get_user(user_id) if not user: raise CateringError("User does not exist") total_price = sum([item.price * quantity for item, quantity in menu_items.items()], Decimal(0)) if await self._accounting_service.get_balance(user_id) < total_price: raise CateringError("Insufficient funds", CateringErrorType.INSUFFICIENT_FUNDS) order = await self._db_service.add_new_order(menu_items, user_id, is_delivery) if order: await self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}") logger.info( f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_decimal(total_price)}") # await self.cancel_order(order) # ToDo: Check if commented out before commit. Un-comment to auto-cancel every placed order return order async def update_order_status(self, order_id: int, new_status: CateringOrderStatus) -> bool: if new_status == CateringOrderStatus.CANCELED: # Cancelled orders need to be refunded raise CateringError("Orders cannot be canceled this way, use CateringService.cancel_order") return await self._db_service.change_order_status(order_id, new_status) async def get_orders(self) -> list[CateringOrder]: return await self._db_service.get_orders() async def get_orders_for_user(self, user_id: int) -> list[CateringOrder]: return await self._db_service.get_orders(user_id=user_id) async def get_orders_by_status(self, status: CateringOrderStatus) -> list[CateringOrder]: return await self._db_service.get_orders(status=status) async def cancel_order(self, order: CateringOrder) -> bool: change_result = await self._db_service.change_order_status(order.order_id, CateringOrderStatus.CANCELED) if change_result: await self._accounting_service.add_balance(order.customer.user_id, order.price, f"CATERING REFUND - {order.order_id}") return True return False # MENU ITEMS async def get_menu(self, category: Optional[CateringMenuItemCategory] = None) -> list[CateringMenuItem]: items = await self._db_service.get_menu_items() if not category: return items return list(filter(lambda item: item.category == category, items)) async def get_menu_item_by_id(self, menu_item_id: int) -> CateringMenuItem: item = await self._db_service.get_menu_item(menu_item_id) if not item: raise CateringError("Menu item not found") return item async def add_menu_item(self, name: str, info: str, price: Decimal, category: CateringMenuItemCategory, is_disabled: bool = False) -> CateringMenuItem: if new_item := await self._db_service.add_menu_item(name, info, price, category, is_disabled): return new_item raise CateringError(f"Could not add item '{name}' to the menu.") async def remove_menu_item(self, menu_item_id: int) -> bool: return await self._db_service.delete_menu_item(menu_item_id) async def change_menu_item(self, updated_item: CateringMenuItem) -> bool: return await self._db_service.update_menu_item(updated_item) async def disable_menu_item(self, menu_item_id: int) -> bool: try: item = await self.get_menu_item_by_id(menu_item_id) except CateringError: return False item.is_disabled = True return await self._db_service.update_menu_item(item) async def enable_menu_item(self, menu_item_id: int) -> bool: try: item = await self.get_menu_item_by_id(menu_item_id) except CateringError: return False item.is_disabled = False return await self._db_service.update_menu_item(item) async def disable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool: items = await self.get_menu(category=category) return all([self.disable_menu_item(item.item_id) for item in items]) async def enable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool: items = await self.get_menu(category=category) return all([self.enable_menu_item(item.item_id) for item in items]) # CART def save_cart(self, user_id: Optional[int], cart: list[CateringMenuItem]) -> None: if user_id: self.cached_cart[user_id] = cart def get_cart(self, user_id: Optional[int]) -> list[CateringMenuItem]: if user_id is None: return [] try: return self.cached_cart[user_id] except KeyError: return []