add basic overview page for catering management

This commit is contained in:
David Rodenkirchen 2025-01-27 09:34:23 +01:00
parent d86a3da135
commit 0a2647bcf8
6 changed files with 136 additions and 7 deletions

View File

@ -25,6 +25,6 @@ class CateringCartItem(Component):
return Row( return Row(
Text(self.ellipsize_string(self.article_name), align_x=0, overflow="wrap", min_width=19, style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)), Text(self.ellipsize_string(self.article_name), align_x=0, overflow="wrap", min_width=19, style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
Text(AccountingService.make_euro_string_from_int(self.article_price), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)), Text(AccountingService.make_euro_string_from_int(self.article_price), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
IconButton(icon="material/close", size=2, color=self.session.theme.danger_color, style="plain-text", on_press=lambda: self.remove_item_cb(self.list_id)), IconButton(icon="material/close", min_size=2, color=self.session.theme.danger_color, style="plain-text", on_press=lambda: self.remove_item_cb(self.list_id)),
proportions=(19, 5, 2) proportions=(19, 5, 2)
) )

View File

@ -0,0 +1,47 @@
from typing import Optional
from rio import Component, Row, Card, Column, Text, TextStyle, Spacer
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ez_lan_manager.types.Seat import Seat
class CateringManagementOrderDisplay(Component):
order: CateringOrder
seat: Optional[Seat]
def format_order_status(self, status: CateringOrderStatus) -> Text:
status_text = CateringOrder.translate_order_status(status)
color = self.session.theme.warning_color
if status == CateringOrderStatus.DELAYED or status == CateringOrderStatus.CANCELED:
color = self.session.theme.danger_color
elif status == CateringOrderStatus.COMPLETED:
color = self.session.theme.success_color
return Text(text=status_text, style=TextStyle(fill=color))
def build(self) -> Component:
return Card(
content=Column(
Row(
Text(f"Status: ", margin_left=0.3, margin_top=0.2),
self.format_order_status(self.order.status),
Spacer(),
Text(self.order.order_date.strftime("%d.%m. - %H:%M Uhr"), margin_right=0.3),
),
Row(
Text(f"Gast: {self.order.customer.user_name}", margin_left=0.3),
Spacer(),
Text(f"Sitzplatz: {'-' if not self.seat else self.seat.seat_id}", margin_right=0.3),
),
Row(
Text("Diese Bestellung wird:", margin_left=0.3),
Spacer(),
Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.2),
)
),
color=self.session.theme.hud_color,
colorize_on_hover=True,
margin=1
)

View File

@ -65,8 +65,9 @@ class ShoppingCartAndOrders(Component):
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
cart = self.session[CateringService].get_cart(user_id) cart = self.session[CateringService].get_cart(user_id)
show_popup_task = None
if len(cart) < 1: if len(cart) < 1:
_ = create_task(self.show_popup("Warenkorb leer", True)) show_popup_task = create_task(self.show_popup("Warenkorb leer", True))
else: else:
items_with_amounts: CateringMenuItemsWithAmount = {} items_with_amounts: CateringMenuItemsWithAmount = {}
for item in cart: for item in cart:
@ -78,14 +79,15 @@ class ShoppingCartAndOrders(Component):
await self.session[CateringService].place_order(items_with_amounts, user_id) await self.session[CateringService].place_order(items_with_amounts, user_id)
except CateringError as catering_error: except CateringError as catering_error:
if catering_error.error_type == CateringErrorType.INCLUDES_DISABLED_ITEM: if catering_error.error_type == CateringErrorType.INCLUDES_DISABLED_ITEM:
_ = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True)) show_popup_task = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True))
elif catering_error.error_type == CateringErrorType.INSUFFICIENT_FUNDS: elif catering_error.error_type == CateringErrorType.INSUFFICIENT_FUNDS:
_ = create_task(self.show_popup("Guthaben nicht ausreichend", True)) show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True))
else: else:
_ = create_task(self.show_popup("Unbekannter Fehler", True)) show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True))
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, []) self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
self.order_button_loading = False self.order_button_loading = False
_ = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False)) if not show_popup_task:
show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
async def _create_order_info_modal(self, order: CateringOrder) -> None: async def _create_order_info_modal(self, order: CateringOrder) -> None:
def build_dialog_content() -> rio.Component: def build_dialog_content() -> rio.Component:

View File

@ -65,6 +65,8 @@ class UserInfoBox(Component):
self.session[AccountingService].add_update_hook(self.update) self.session[AccountingService].add_update_hook(self.update)
async def update(self) -> None: async def update(self) -> None:
if not self.user:
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id) self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id)
self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id) self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id) self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id)

View File

@ -1,16 +1,52 @@
import logging import logging
from dataclasses import field, dataclass
from datetime import datetime
from typing import Optional
from rio import Column, Component, event, TextStyle, Text, Spacer from rio import Column, Component, event, TextStyle, Text, Spacer
from src.ez_lan_manager import ConfigurationService from src.ez_lan_manager import ConfigurationService, CateringService, SeatingService
from src.ez_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ez_lan_manager.types.Seat import Seat
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@dataclass
class CateringOrderWithSeat:
catering_order: CateringOrder
seat: Optional[Seat]
class ManageCateringPage(Component): class ManageCateringPage(Component):
all_orders: list[CateringOrderWithSeat] = field(default_factory=list)
last_updated: Optional[datetime] = None
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering Verwaltung") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering Verwaltung")
self.all_orders = await self.populate_seating(await self.session[CateringService].get_orders())
self.last_updated = datetime.now()
@event.periodic(30)
async def update_orders(self) -> None:
polled_orders = await self.session[CateringService].get_orders()
self.all_orders = await self.populate_seating(polled_orders)
self.last_updated = datetime.now()
async def populate_seating(self, orders: list[CateringOrder]) -> list[CateringOrderWithSeat]:
result = []
for order in orders:
seat = await self.session[SeatingService].get_user_seat(order.customer.user_id)
result.append(CateringOrderWithSeat(catering_order=order, seat=seat))
return result
def get_all_pending_orders(self) -> list[CateringOrderWithSeat]:
filtered_list = list(filter(lambda o: o.catering_order.status != CateringOrderStatus.COMPLETED and o.catering_order.status != CateringOrderStatus.CANCELED, self.all_orders))
sorted_list = sorted(filtered_list, key=lambda o: o.catering_order.order_date)
return sorted_list
def build(self) -> Component: def build(self) -> Component:
return Column( return Column(
@ -28,5 +64,30 @@ class ManageCateringPage(Component):
) )
) )
), ),
MainViewContentBox(
Column(
Text(
text="Offene Bestellungen",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
margin_bottom=0.2,
align_x=0.5
),
Text(
text=f"Letzte Aktualisierung: {'-' if not self.last_updated else self.last_updated.strftime('%H:%M:%S')}",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.7
),
margin_top=0.2,
margin_bottom=2,
align_x=0.5
),
*[CateringManagementOrderDisplay(v.catering_order, v.seat) for v in self.get_all_pending_orders()],
)
),
Spacer() Spacer()
) )

View File

@ -31,3 +31,20 @@ class CateringOrder:
for item, amount in self.items.items(): for item, amount in self.items.items():
total += (item.price * amount) total += (item.price * amount)
return total return total
@staticmethod
def translate_order_status(status: CateringOrderStatus) -> str:
if status == CateringOrderStatus.RECEIVED:
return "Eingegangen"
elif status == CateringOrderStatus.DELAYED:
return "Verzögert"
elif status == CateringOrderStatus.READY_FOR_PICKUP:
return "Abholbereit"
elif status == CateringOrderStatus.EN_ROUTE:
return "In Zustellung"
elif status == CateringOrderStatus.COMPLETED:
return "Abgeschlossen"
elif status == CateringOrderStatus.CANCELED:
return "Storniert"
else:
raise RuntimeError("Unknown CateringOrderStatus:", status)