6 Commits

Author SHA1 Message Date
David Rodenkirchen 5d67238575 bump version 2026-04-18 16:31:37 +02:00
David Rodenkirchen fb5a5b0608 add FFA handling 2026-04-18 16:31:21 +02:00
David Rodenkirchen c0e39a2beb implement initial JSON generation at tournament start 2026-04-18 16:24:48 +02:00
David Rodenkirchen b071c30ce0 remove print 2026-04-18 16:23:49 +02:00
David Rodenkirchen db237b3535 handle byes 2026-04-18 16:08:57 +02:00
David Rodenkirchen a62f289ce8 Enable starting tournaments and displaying tournament tree 2026-04-18 15:53:56 +02:00
12 changed files with 84 additions and 683 deletions
+1 -1
View File
@@ -1 +1 @@
0.5.2
0.4.0
-1
View File
@@ -31,7 +31,6 @@ services:
- database:/var/lib/mysql
- ./sql/create_database.sql:/docker-entrypoint-initdb.d/init.sql
- ./sql:/sql
- ./tournament_data:/opt/ezgg-lan-manager/tournament_data
volumes:
+5 -11
View File
@@ -36,13 +36,13 @@ if __name__ == "__main__":
async def on_session_start(session: Session) -> None:
# Use this line to fake being any user without having to log in
# session.attach(UserSession(id=uuid4(), user_id=30, is_team_member=True))
session.attach(UserSession(id=uuid4(), user_id=30, is_team_member=True))
await session.set_title(lan_info.name)
session.attach(RefreshService())
if session[LocalData].stored_session_token:
user_session = session[LocalDataService].verify_token(session[LocalData].stored_session_token)
if user_session is not None:
session.attach(user_session)
# if session[LocalData].stored_session_token:
# user_session = session[LocalDataService].verify_token(session[LocalData].stored_session_token)
# if user_session is not None:
# session.attach(user_session)
async def on_app_start(a: App) -> None:
init_result = await a.default_attachments[4].init_db_pool()
@@ -156,12 +156,6 @@ if __name__ == "__main__":
build=pages.ManageCateringPage,
guard=team_guard
),
ComponentPage(
name="NewPosOrderPage",
url_segment="new-pos-order",
build=pages.NewPosOrderPage,
guard=team_guard
),
ComponentPage(
name="ManageTournamentsPage",
url_segment="manage-tournaments",
@@ -1,30 +1,29 @@
import logging
from asyncio import create_task, sleep
from functools import partial
from typing import Optional, Callable
from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button, Rectangle, Popup, Icon, Color
from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button
from src.ezgg_lan_manager import ReceiptPrintingService
from src.ezgg_lan_manager.components.StatusChangePopup import StatusChangePopup
from src.ezgg_lan_manager.services.CateringService import CateringService
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
from src.ezgg_lan_manager.types.Seat import Seat
logger = logging.getLogger(__name__.split(".")[-1])
class CateringManagementOrderDisplayStatusButton(Component):
status: CateringOrderStatus
clicked_cb: Callable
def build(self) -> Component:
return Button(
content=Text(
CateringOrder.translate_order_status(self.status)
),
shape="rectangle",
on_press=partial(self.clicked_cb, self.status)
)
class CateringManagementOrderDisplay(Component):
order: CateringOrder
seat: Optional[Seat]
clicked_cb: Callable
status_change_popup_open: bool = False
def reprint_order(self) -> None:
create_task(self.session[ReceiptPrintingService].print_order(self.order.customer, self.order))
def open_status_change_popup(self) -> None:
self.status_change_popup_open = True
def format_order_status(self, status: CateringOrderStatus) -> Text:
status_text = CateringOrder.translate_order_status(status)
@@ -37,12 +36,14 @@ class CateringManagementOrderDisplay(Component):
return Text(text=status_text, style=TextStyle(fill=color))
async def change_status(self, new_status: CateringOrderStatus) -> Optional[str]:
await sleep(1)
async def status_button_clicked(self, new_status: CateringOrderStatus) -> None:
if self.order.status == CateringOrderStatus.CANCELED:
return
logger.debug(f"Status of order with ID {self.order.order_id} changing from {self.order.status} to {new_status}")
if self.order.status == CateringOrderStatus.CANCELED: # Can not un-cancel
return "Stornierte Bestellungen können nicht angepasst werden"
if new_status == CateringOrderStatus.CANCELED:
# ToDo: Hier sollten wir nochmal nachfragen ob der Bediener sich wirklich sicher ist,
# und anwarnen das eine stornierte Bestellung nicht ent-storniert werden kann.
pass
if self.order.status != new_status:
if new_status == CateringOrderStatus.CANCELED:
@@ -60,58 +61,43 @@ class CateringManagementOrderDisplay(Component):
is_delivery=self.order.is_delivery
)
self.status_change_popup_open = False
def build(self) -> Component:
card = Card(
content=Column(
Row(
Text(f"ID: {self.order.order_id}", margin_left=0.3, margin_top=0.2, justify="center", style=TextStyle(font_size=1.2)),
),
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, margin_bottom=0.5),
Spacer(),
Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.5),
),
Row(
Rectangle(
content=Button(
content=Text("Beleg drucken", justify="left"),
shape="rectangle",
on_press=self.reprint_order
),
stroke_width=0.1
),
Rectangle(
content=Button(
content=Text("Status ändern", justify="right"),
shape="rectangle",
on_press=self.open_status_change_popup
),
stroke_width=0.1
),
)
),
color=self.session.theme.hud_color,
colorize_on_hover=True,
margin=1
)
status_change_popup = StatusChangePopup(card, self.status_change_popup_open, self.change_status)
return PointerEventListener(
content=status_change_popup,
content=Card(
content=Column(
Row(
Text(f"ID: {self.order.order_id}", margin_left=0.3, margin_top=0.2, justify="center", style=TextStyle(font_size=1.2)),
),
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, margin_bottom=0.5),
Spacer(),
Text("Geliefert" if self.order.is_delivery else "Abgeholt", margin_right=0.3, margin_bottom=0.5),
),
Row(
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.RECEIVED, self.status_button_clicked),
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.CANCELED, self.status_button_clicked),
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.EN_ROUTE, self.status_button_clicked)
),
Row(
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.READY_FOR_PICKUP, self.status_button_clicked),
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.COMPLETED, self.status_button_clicked),
CateringManagementOrderDisplayStatusButton(CateringOrderStatus.DELAYED, self.status_button_clicked),
)
),
color=self.session.theme.hud_color,
colorize_on_hover=True,
margin=1
),
on_press=partial(self.clicked_cb, self.order)
)
@@ -1,92 +0,0 @@
from functools import partial
from typing import Callable, Optional
from rio import Column, Row, Text, Button, Component, Icon, Popup, Rectangle, Color, Tooltip, PointerEventListener, PointerEvent, ProgressCircle
from src.ezgg_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder
ICONS_BY_STATUS = {
CateringOrderStatus.RECEIVED: "material/move_to_inbox",
CateringOrderStatus.DELAYED: "material/hourglass_top",
CateringOrderStatus.READY_FOR_PICKUP: "material/takeout_dining",
CateringOrderStatus.EN_ROUTE: "material/local_shipping",
CateringOrderStatus.COMPLETED: "material/check_circle",
CateringOrderStatus.CANCELED: "material/cancel",
}
class StatusChangeButton(Component):
status: CateringOrderStatus
clicked_cb: Callable
def build(self) -> Component:
return Tooltip(
anchor=PointerEventListener(
content=Rectangle(
fill=Color.TRANSPARENT,
content=Column(
Icon(
icon=ICONS_BY_STATUS[self.status]
)
),
stroke_width=0.1,
stroke_color=Color.TRANSPARENT,
hover_stroke_width=0.1,
hover_stroke_color=Color.BLACK
),
on_press=partial(self.clicked_cb, self.status)
),
tip=Text(text=CateringOrder.translate_order_status(self.status)),
position="top"
)
class StatusChangePopup(Component):
anchor: Component
popup_open: bool
status_should_change_cb: Callable
response: Optional[str] = None
is_loading: bool = False
async def handle_button_clicked(self, status: CateringOrderStatus, _: PointerEvent) -> None:
self.is_loading = True
self.response = await self.status_should_change_cb(status)
self.is_loading = False
def close(self) -> None:
self.popup_open = False
def build(self) -> Component:
if self.is_loading:
content = Row(
ProgressCircle(margin=1)
)
elif self.response:
content = Row(
Text(text=self.response, justify="center", overflow="wrap", margin=1)
)
else:
content = Row(
StatusChangeButton(CateringOrderStatus.RECEIVED, self.handle_button_clicked),
StatusChangeButton(CateringOrderStatus.DELAYED, self.handle_button_clicked),
StatusChangeButton(CateringOrderStatus.READY_FOR_PICKUP, self.handle_button_clicked),
StatusChangeButton(CateringOrderStatus.EN_ROUTE, self.handle_button_clicked),
StatusChangeButton(CateringOrderStatus.COMPLETED, self.handle_button_clicked),
StatusChangeButton(CateringOrderStatus.CANCELED, self.handle_button_clicked),
spacing=0.5,
margin=0.5
)
return Popup(
anchor=self.anchor,
content=Rectangle(
content=Column(
content,
Button(content=Text(text="Abbrechen", justify="center", fill=self.session.theme.secondary_color), shape="rectangle", style="colored-text", on_press=self.close),
proportions=[2.5, 1]
),
fill=self.session.theme.hud_color,
min_width=34,
min_height=8.3
),
is_open=self.popup_open
)
@@ -3,7 +3,7 @@ from dataclasses import field, dataclass
from datetime import datetime
from typing import Optional, Callable
from rio import Column, Component, event, TextStyle, Text, Spacer, PointerEvent, Button, Popup, Card, Row, Rectangle, Color, PointerEventListener
from rio import Column, Component, event, TextStyle, Text, Spacer, PointerEvent, Button, Popup, Card, Row
from src.ezgg_lan_manager import ConfigurationService, CateringService, SeatingService, AccountingService
from src.ezgg_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
@@ -106,7 +106,6 @@ class ManageCateringPage(Component):
return sorted_list
async def order_clicked(self, order: CateringOrder, _: PointerEvent) -> None:
await self.update_orders()
self.order_popup_order = order
self.order_popup_open = True
@@ -121,7 +120,7 @@ class ManageCateringPage(Component):
font_size=1.2
),
margin_top=2,
margin_bottom=1,
margin_bottom=2,
align_x=0.5
)
popup = Popup(
@@ -133,26 +132,7 @@ class ManageCateringPage(Component):
)
return Column(
MainViewContentBox(
Column(
popup,
PointerEventListener(
content=Rectangle(
content=Text(text="Neue Bestellung anlegen", fill=Color.WHITE, justify="center", margin=0.3),
margin_bottom=1,
margin_right=5,
margin_left=5,
fill=self.session.theme.secondary_color,
hover_fill=self.session.theme.hud_color,
stroke_width=0.2,
stroke_color=Color.TRANSPARENT,
hover_stroke_width=0.2,
hover_stroke_color=self.session.theme.background_color,
cursor="pointer",
transition_time=0.1
),
on_press=lambda _: self.session.navigate_to("new-pos-order")
)
)
Column(popup)
),
MainViewContentBox(
Column(
@@ -11,7 +11,7 @@ from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBo
from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, TournamentError
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus
logger = logging.getLogger(__name__.split(".")[-1])
@@ -29,10 +29,7 @@ class ManageTournamentsPage(Component):
async def on_start_pressed(self, tournament_id: int) -> None:
logger.info(f"Starting tournament with ID {tournament_id}")
try:
await self.session[TournamentService].start_tournament(tournament_id)
except TournamentError as e:
logger.error(f"Error trying to start tournament: {e}")
await self.session[TournamentService].start_tournament(tournament_id)
async def on_cancel_pressed(self, tournament_id: int) -> None:
logger.info(f"Canceling tournament with ID {tournament_id}")
@@ -95,16 +92,8 @@ class ManageTournamentsPage(Component):
font_size=1.2
),
margin_top=2,
margin_bottom=1,
align_x=0.5
),
Button(
content="Cache erneuern",
shape="rectangle",
style="colored-text",
margin_bottom=2,
align_x=0.5,
on_press=self.session[TournamentService].queue_cache_renewal
align_x=0.5
),
*tournament_rows
)
+10 -34
View File
@@ -4,7 +4,7 @@ from typing import Optional
from rio import Column, Component, event, TextStyle, Text, TextInput, ThemeContextSwitcher, Grid, \
PointerEventListener, PointerEvent, Rectangle, Color, TextInputChangeEvent, Spacer, Row, Switch, \
SwitchChangeEvent, EventHandler, Icon
SwitchChangeEvent, EventHandler
from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService, SeatingService, MailingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
@@ -73,18 +73,12 @@ class ManageUsersPage(Component):
seat = await self.session[SeatingService].get_user_seat(self.selected_user.user_id)
self.user_seat = seat.seat_id if seat else "-"
self.is_user_account_locked = not self.selected_user.is_active
await self.on_search_parameters_changed(TextInputChangeEvent(self.selected_user.user_name))
async def on_search_parameters_changed(self, e: TextInputChangeEvent) -> None:
self.search_results = list(
filter(lambda user: (e.text.lower() in user.user_name.lower()) or e.text.lower() in str(user.user_id),
self.all_users))
async def reset_view(self, _: PointerEvent) -> None:
self.selected_user = None
self.search_results = self.all_users
await self.on_search_parameters_changed(TextInputChangeEvent(""))
async def change_account_active(self, _: SwitchChangeEvent) -> None:
self.selected_user.is_active = not self.is_user_account_locked
await self.session[UserService].update_user(self.selected_user)
@@ -104,7 +98,7 @@ class ManageUsersPage(Component):
if transaction.is_debit:
try:
new_total_balance = await self.session[AccountingService].remove_balance(
await self.session[AccountingService].remove_balance(
transaction.user_id,
transaction.value,
transaction.reference
@@ -128,7 +122,6 @@ class ManageUsersPage(Component):
self.accounting_section_result_text = f"Guthaben {'entfernt' if transaction.is_debit else 'hinzugefügt'}!"
self.accounting_section_result_success = True
self.user_account_balance = self.session[AccountingService].make_euro_string_from_decimal(new_total_balance)
def build(self) -> Component:
return Column(
@@ -171,32 +164,15 @@ class ManageUsersPage(Component):
),
MainViewContentBox(
Column(
Row(
Spacer(),
PointerEventListener(
content=Icon("material/cancel", fill="background", min_width=2.5, margin_top=1, margin_right=1),
on_press=self.reset_view
)
),
Row(
Text(
text=f"Konto von ",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
justify="right"
Text(
text="Konto & Sitzplatz",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
Text(
text=self.selected_user.user_name,
style=TextStyle(
fill=self.session.theme.hud_color,
font_size=1.2
),
justify="left"
),
margin_top=0.5,
margin_bottom=2
margin_top=2,
margin_bottom=2,
align_x=0.5
),
Row(
Text(
@@ -1,417 +0,0 @@
import logging
from asyncio import sleep, create_task
from decimal import Decimal
from typing import Optional, Callable
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, ProgressCircle, ScrollContainer, Row, Popup, List, Rectangle, PointerEventListener, \
PointerEvent, TextInput, TextInputChangeEvent
from src.ezgg_lan_manager import ConfigurationService, CateringService, AccountingService
from src.ezgg_lan_manager.components.CateringCartItem import CateringCartItem
from src.ezgg_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.services.CateringService import CateringError, CateringErrorType
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
from src.ezgg_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount
from src.ezgg_lan_manager.types.UserSession import UserSession
POPUP_CLOSE_TIMEOUT_SECONDS = 3
logger = logging.getLogger(__name__.split(".")[-1])
class Cart(Component):
cart: List[CateringMenuItem]
user_id: Optional[int]
clear_cb: Callable
order_button_loading: bool = False
popup_message: str = ""
popup_is_shown: bool = False
popup_is_error: bool = True
async def on_remove_item(self, list_id: int) -> None:
try:
self.cart.pop(list_id)
except IndexError:
return
async def on_empty_cart_pressed(self, _: PointerEvent) -> None:
self.cart.clear()
async def show_popup(self, text: str, is_error: bool) -> None:
self.popup_is_error = is_error
self.popup_message = text
self.popup_is_shown = True
self.force_refresh()
await sleep(POPUP_CLOSE_TIMEOUT_SECONDS)
self.popup_is_shown = False
self.force_refresh()
async def on_order_pressed(self, _: PointerEvent) -> None:
if self.user_id is None:
return
self.order_button_loading = True
self.force_refresh()
show_popup_task = None
if len(self.cart) < 1:
show_popup_task = create_task(self.show_popup("Warenkorb leer", True))
else:
items_with_amounts: CateringMenuItemsWithAmount = {}
for item in self.cart:
try:
items_with_amounts[item] += 1
except KeyError:
items_with_amounts[item] = 1
try:
await self.session[CateringService].place_order(items_with_amounts, self.user_id, is_delivery=False)
except CateringError as catering_error:
logger.error(catering_error)
if catering_error.error_type == CateringErrorType.INCLUDES_DISABLED_ITEM:
show_popup_task = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True))
elif catering_error.error_type == CateringErrorType.INSUFFICIENT_FUNDS:
show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True))
else:
show_popup_task = create_task(self.show_popup(f"Unbekannter Fehler: {catering_error}", True))
else:
self.cart.clear()
self.user_id = None
await self.clear_cb()
self.order_button_loading = False
if not show_popup_task:
show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
def build(self) -> Component:
cart_container = ScrollContainer(
content=Column(
*[CateringCartItem(
article_name=cart_item.name,
article_price=cart_item.price,
article_id=cart_item.item_id,
remove_item_cb=self.on_remove_item,
list_id=idx
) for idx, cart_item in enumerate(self.cart)],
Spacer(grow_y=True)
),
min_height=8,
min_width=33,
margin=1
)
return Column(
Popup(
anchor=cart_container,
content=Text(self.popup_message, style=TextStyle(fill=self.session.theme.danger_color if self.popup_is_error else self.session.theme.success_color), overflow="wrap", margin=2, justify="center", min_width=20),
is_open=self.popup_is_shown,
position="center",
color=self.session.theme.primary_color
),
Row(
Text(
text=f"Preis: {AccountingService.make_euro_string_from_decimal(sum((cart_item.price for cart_item in self.cart), Decimal(0)))}",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.8
),
margin=1
),
PointerEventListener(
content=Rectangle(
content=Text(
"Warenkorb leeren",
style=TextStyle(fill=self.session.theme.danger_color, font_size=0.9),
justify="center",
margin=0.2
),
hover_fill=self.session.theme.hud_color,
transition_time=0.1,
margin=0.5,
cursor="pointer"
),
on_press=self.on_empty_cart_pressed
),
PointerEventListener(
content=Rectangle(
content=Text(
"Bestellen",
style=TextStyle(fill=self.session.theme.success_color, font_size=0.9),
justify="center",
margin=0.2
),
hover_fill=self.session.theme.hud_color if self.user_id is not None else self.session.theme.danger_color,
transition_time=0.1,
margin=0.5,
cursor="pointer" if self.user_id is not None else "not-allowed"
),
on_press=self.on_order_pressed
)
)
)
class NewPosOrderPage(Component):
user_id_input_value: str = ""
user_id: Optional[int] = None
all_menu_items: Optional[list[CateringMenuItem]] = None
cart: List[CateringMenuItem] = List()
@event.on_populate
async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Neue Bestellung anlegen")
self.all_menu_items = await self.session[CateringService].get_menu()
async def on_user_logged_in_status_changed(self) -> None:
self.force_refresh()
async def on_user_id_input_change(self, change_event: TextInputChangeEvent) -> None:
try:
id_ = int(change_event.text)
except ValueError:
return
self.user_id = id_
async def on_add(self, article_id: int) -> None:
try:
menu_item = await self.session[CateringService].get_menu_item_by_id(article_id)
except CateringError as e:
logger.error(e)
return
self.cart.append(menu_item)
@staticmethod
def get_menu_items_by_category(all_menu_items: list[CateringMenuItem], category: Optional[CateringMenuItemCategory]) -> list[CateringMenuItem]:
return list(filter(lambda item: item.category == category, all_menu_items))
async def clear_user_id_input(self) -> None:
self.user_id_input_value = ""
def build(self) -> Component:
try:
is_team_member = self.session[UserSession].is_team_member
except KeyError:
is_team_member = False
shopping_cart_container = MainViewContentBox(
Column(
Text(
text="Neue Bestellung anlegen",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
margin_bottom=0.5,
align_x=0.5
),
TextInput(text=self.bind().user_id_input_value, label="Nutzer ID", on_change=self.on_user_id_input_change, change_delay=1, margin_bottom=0.5, margin_left=5, margin_right=5),
Cart(cart=self.cart, user_id=self.user_id, clear_cb=self.clear_user_id_input)
)
) if is_team_member else Spacer()
menu = [MainViewContentBox(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=2,
margin_bottom=2
)
)] if not self.all_menu_items else [MainViewContentBox(
Revealer(
header="Snacks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.SNACK))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Frühstück",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BREAKFAST))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Hauptspeisen",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.MAIN_COURSE))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Desserts",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.DESSERT))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Wasser & Softdrinks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_NON_ALCOHOLIC))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Alkoholische Getränke",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_ALCOHOLIC))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Cocktails & Longdrinks",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_COCKTAIL))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Shots",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_SHOT))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
),
MainViewContentBox(
Revealer(
header="Sonstiges",
content=Column(
*[CateringSelectionItem(
article_name=catering_menu_item.name,
article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id,
on_add_callback=self.on_add,
is_sensitive=not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.NON_FOOD))],
),
header_style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin=1,
align_y=0.5
)
)]
return Column(shopping_cart_container, *menu, align_y=0)
-1
View File
@@ -26,4 +26,3 @@ from .ConwayPage import ConwayPage
from .TeamsPage import TeamsPage
from .AdminNavigationPage import AdminNavigationPage
from .TournamentTreePage import TournamentTreePage
from .NewPosOrderPage import NewPosOrderPage
@@ -15,15 +15,11 @@ class ReceiptPrintingService:
self._seating_service = seating_service
self._config = config
self._dev_mode_enabled = dev_mode_enabled
self._url = f"http://{self._config.host}:{self._config.port}/{self._config.order_print_endpoint}"
async def print_order(self, user: User, order: CateringOrder) -> None:
seat = await self._seating_service.get_user_seat(user.user_id)
if seat is None:
seat_id = await self._seating_service.get_user_seat(user.user_id)
if not seat_id:
seat_id = " - "
else:
seat_id = str(seat.seat_id)
menu_items_payload = []
for item, amount in order.items.items():
@@ -39,19 +35,14 @@ class ReceiptPrintingService:
"seat_id": seat_id,
"items": menu_items_payload
}
logger.info(f"Sending print order to {self._url}: {payload}")
try:
response = requests.post(
self._url,
requests.post(
f"http://{self._config.host}:{self._config.port}/{self._config.order_print_endpoint}",
json=payload,
headers={"x-password": self._config.password},
timeout=2.0
headers={"x-password": self._config.password}
)
if response.status_code != 200:
logger.error(f"Received an error with code {response.status_code}: {response.text}")
except Exception as e:
if self._dev_mode_enabled:
logger.info("An error occurred trying to print a receipt: %s", e)
logger.info("An error occurred trying to print a receipt:", e)
return
logger.error("An error occurred trying to print a receipt: %s", e)
logger.error("An error occurred trying to print a receipt:", e)
@@ -21,10 +21,6 @@ class TournamentService:
# Crude cache mechanism. If performance suffers, maybe implement a queue with Single-Owner-Pattern or a Lock
self._cache: dict[int, Tournament] = {}
self._cache_dirty: bool = True # Setting this flag invokes cache update on next read
async def queue_cache_renewal(self) -> None:
# Used in admin UI to provoke cache renewal after direct database access
self._cache_dirty = True
async def _update_cache(self) -> None:
tournaments = await self._db_service.get_all_tournaments()