from __future__ import annotations import logging from asyncio import sleep from functools import partial from typing import Optional from decimal import Decimal from beanie import PydanticObjectId from rio import Component, Column, Row, Text, Spacer, page, Rectangle, GuardEvent, FlowContainer, List, PointerEventListener, Overlay, Link, Switch, SwitchChangeEvent from rio.event import on_populate from elm.types import UserSession, User, Seat from elm.services import AccountingService, ReceiptPrintingService from elm.components import ElmButton from elm.types.CateringTypes import CateringOrder, CateringOrderStatus, CateringMenuItem, CateringMenuItemCategory logger = logging.getLogger(__name__.split(".")[-1]) def catering_admin_page_guard(event: GuardEvent) -> Optional[str]: try: if event.session[UserSession].is_team_member: return None return "/" except KeyError: return "/" @page(name="Cateringverwaltung", url_segment="catering-admin", guard=catering_admin_page_guard) class CateringAdminPage(Component): open_orders: List[CateringOrder] = List() all_users: list[User] = [] all_seats: list[Seat] = [] all_menu_items: list[CateringMenuItem] = [] edited_order: Optional[CateringOrder] = None @on_populate async def on_populate(self) -> None: self.all_users = await User.find_all().to_list() self.all_seats = await Seat.find_all(fetch_links=True).to_list() self.all_menu_items = await CateringMenuItem.find_all(fetch_links=True).to_list() self.open_orders = List(await CateringOrder.find_many( { "status": { "$nin": [ CateringOrderStatus.COMPLETED, CateringOrderStatus.CANCELED, ] } } ).to_list()) await sleep(5) self.session.create_task(self.on_populate()) def get_name_for_user_id(self, id_: PydanticObjectId) -> str: return next(filter(lambda user: user.id == id_ ,self.all_users)).user_name def get_seat_for_user_id(self, id_: PydanticObjectId) -> str: try: found_seat: Optional[Seat] = next(filter(lambda seat: seat.user is not None and seat.user.id == id_, self.all_seats), None) if found_seat: return found_seat.seat_id return "-" except Exception: return "-" async def on_order_pressed(self, order: CateringOrder) -> None: self.edited_order = order async def change_order_status(self, new_status: CateringOrderStatus) -> None: if not self.edited_order: return if new_status == CateringOrderStatus.CANCELED: pass if self.edited_order.status == new_status: self.edited_order = None return if new_status == CateringOrderStatus.CANCELED: user = await User.find_one(User.id == self.edited_order.customer_id) if not user: self.edited_order = None return price = Decimal(0) for item in self.edited_order.items: price += item.final_unit_price await self.session[AccountingService].add_balance(user.user_name, price, f"CATERING REFUND - {str(self.edited_order.id)[-5:]}") self.edited_order.status = new_status await self.edited_order.save() self.open_orders = List(await CateringOrder.find_many( { "status": { "$nin": [ CateringOrderStatus.COMPLETED, CateringOrderStatus.CANCELED, ] } } ).to_list()) self.edited_order = None async def print_receipt(self) -> None: if not self.edited_order: return user = await User.find_one(User.id == self.edited_order.customer_id) if not user: self.edited_order = None return self.session.create_task(self.session[ReceiptPrintingService].print_order(user, self.edited_order)) self.edited_order = None @staticmethod async def change_item_active(event: SwitchChangeEvent, item: CateringMenuItem) -> None: item.active = event.is_on await item.save() def build(self) -> Component: if self.edited_order: overlay = [ Overlay( content=Rectangle( content=Column( Text(f"Status ändern - Bestellung {str(self.edited_order.id)[-5:]}", margin_bottom=1), *[ElmButton(text=CateringOrder.translate_order_status(status), on_press=partial(self.change_order_status, status)) for status in CateringOrderStatus], Row(ElmButton(text="Bon drucken", on_press=self.print_receipt), ElmButton(text="Abbrechen", on_press=lambda: self.__setattr__("edited_order", None)), spacing=1, margin_top=2), spacing=0.5, margin=1 ), fill=self.session.theme.box_color, stroke_width=0.2, stroke_color=self.session.theme.box_border_color, align_x=0.5, align_y=0.5 ) ) ] else: overlay = [] return Row( *overlay, Rectangle( content=Column( Rectangle( content=Rectangle( content=Row( Text("Offene Bestellungen", margin=0.5, selectable=False, overflow="wrap"), Link(content="Neue Bestellung", target_url="./new-pos-order") ), fill=self.session.theme.header_box_background_color, margin=0.4 ), stroke_width=0.1, stroke_color=self.session.theme.box_border_color, ), FlowContainer( *[PointerEventListener( content=Rectangle( content=Column( Row(Text(f"ID:", font_size=1.2), Text(str(order.id)[-5:], justify="right", font_size=1.2)), Row(Text("Nutzer:", font_size=1.2), Text(self.get_name_for_user_id(order.customer_id), font_size=1.2, justify="right")), Row(Text(f"Sitzplatz:", font_size=1.2), Text(self.get_seat_for_user_id(order.customer_id), font_size=1.2, justify="right")), Row(Text(f"Status:", font_size=1.2), Text(CateringOrder.translate_order_status(order.status), font_size=1.2, justify="right"), margin_bottom=2), *[Text(item.name, overflow="ellipsize") for item in order.items], margin=0.5, spacing=0.2 ), stroke_color=self.session.theme.primary_color, stroke_width=0.1, cursor="pointer", hover_stroke_color=self.session.theme.warning_color, hover_stroke_width=0.1, min_width=30 ), on_press=lambda event, order=order: self.on_order_pressed(order), ) for order in self.open_orders], Spacer(), spacing=1, margin=1 ), Spacer() ), fill=self.session.theme.box_color, stroke_width=0.1, stroke_color=self.session.theme.box_border_color, min_width=25, grow_x=True, margin_right=1 ), Rectangle( content=Column( Rectangle( content=Rectangle( content=Text("Speisekarte", margin=0.5, selectable=False, overflow="wrap"), fill=self.session.theme.header_box_background_color, margin=0.4 ), stroke_width=0.1, stroke_color=self.session.theme.box_border_color, ), Column( *[Column( Text(text=category.value, margin_bottom=1, margin_top=0.5, fill=self.session.theme.primary_color), *[Rectangle( content=Row(Text(text=item.name, overflow="ellipsize", grow_x=True), Switch(is_on=item.active, on_change=lambda event, item=item: self.change_item_active(event, item)), margin=0.1), stroke_width=0.1, stroke_color=self.session.theme.box_border_color ) for item in filter(lambda i: i.category == category, self.all_menu_items)], spacing=0.5 ) for category in CateringMenuItemCategory], spacing=0.5, margin=1 ), Spacer() ), fill=self.session.theme.box_color, stroke_width=0.1, stroke_color=self.session.theme.box_border_color, min_width=25 ), margin=1 )