fix bugs, implement placing orders

This commit is contained in:
David Rodenkirchen 2024-09-03 17:12:36 +02:00
parent 30b32a4c02
commit 1ca7db6427
4 changed files with 93 additions and 24 deletions

View File

@ -1,19 +1,24 @@
from typing import Optional from asyncio import sleep, create_task
import rio import rio
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, PopupOpenOrCloseEvent
from src.ez_lan_manager.components.CateringCartItem import CateringCartItem from src.ez_lan_manager.components.CateringCartItem import CateringCartItem
from src.ez_lan_manager.components.CateringOrderItem import CateringOrderItem from src.ez_lan_manager.components.CateringOrderItem import CateringOrderItem
from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.CateringService import CateringService from src.ez_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType
from src.ez_lan_manager.types.CateringOrder import CateringOrder from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
POPUP_CLOSE_TIMEOUT_SECONDS = 3
class ShoppingCartAndOrders(Component): class ShoppingCartAndOrders(Component):
show_cart: bool = True show_cart: bool = True
orders: list[CateringOrder] = [] orders: list[CateringOrder] = []
order_button_loading: bool = False
popup_message: str = ""
popup_is_shown: bool = False
popup_is_error: bool = True
async def switch(self) -> None: async def switch(self) -> None:
self.show_cart = not self.show_cart self.show_cart = not self.show_cart
@ -40,15 +45,53 @@ class ShoppingCartAndOrders(Component):
if not user_id: if not user_id:
return return
cart = catering_service.get_cart(user_id) cart = catering_service.get_cart(user_id)
cart.append(await catering_service.get_menu_item_by_id(article_id)) item_to_add = await catering_service.get_menu_item_by_id(article_id)
cart.append(item_to_add)
catering_service.save_cart(user_id, cart) catering_service.save_cart(user_id, cart)
await self.force_refresh() await self.force_refresh()
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
await self.force_refresh()
await sleep(POPUP_CLOSE_TIMEOUT_SECONDS)
self.popup_is_shown = False
await self.force_refresh()
async def on_order_pressed(self) -> None:
self.order_button_loading = True
await self.force_refresh()
user_id = self.session[SessionStorage].user_id
cart = self.session[CateringService].get_cart(user_id)
if len(cart) < 1:
_ = create_task(self.show_popup("Warenkorb leer", True))
else:
items_with_amounts: CateringMenuItemsWithAmount = {}
for item in cart:
try:
items_with_amounts[item] += 1
except KeyError:
items_with_amounts[item] = 1
try:
await self.session[CateringService].place_order(items_with_amounts, user_id)
except CateringError as catering_error:
if catering_error.error_type == CateringErrorType.INCLUDES_DISABLED_ITEM:
_ = create_task(self.show_popup("Warenkorb enthält gesperrte Artikel", True))
elif catering_error.error_type == CateringErrorType.INSUFFICIENT_FUNDS:
_ = create_task(self.show_popup("Guthaben nicht ausreichend", True))
else:
_ = create_task(self.show_popup("Unbekannter Fehler", True))
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
self.order_button_loading = False
_ = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
def build(self) -> rio.Component: def build(self) -> rio.Component:
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService] catering_service = self.session[CateringService]
cart = catering_service.get_cart(user_id)
if self.show_cart: if self.show_cart:
cart = catering_service.get_cart(user_id)
cart_container = ScrollContainer( cart_container = ScrollContainer(
content=Column( content=Column(
*[CateringCartItem( *[CateringCartItem(
@ -66,6 +109,13 @@ class ShoppingCartAndOrders(Component):
) )
return Column( return Column(
cart_container, cart_container,
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), wrap=True, margin=2, justify="center", min_width=20),
is_open=self.popup_is_shown,
position="center",
color=self.session.theme.primary_color
),
Row( Row(
Text( Text(
text=f"Preis: {AccountingService.make_euro_string_from_int(sum(cart_item.price for cart_item in cart))}", text=f"Preis: {AccountingService.make_euro_string_from_int(sum(cart_item.price for cart_item in cart))}",
@ -98,7 +148,9 @@ class ShoppingCartAndOrders(Component):
margin_left=0, margin_left=0,
shape="rectangle", shape="rectangle",
style="major", style="major",
color="primary" color="primary",
on_press=self.on_order_pressed,
is_loading=self.order_button_loading
) )
) )
) )

View File

@ -14,6 +14,7 @@ from src.ez_lan_manager.types.SessionStorage import SessionStorage
class CateringPage(Component): class CateringPage(Component):
show_cart = True show_cart = True
all_menu_items: Optional[list[CateringMenuItem]] = None all_menu_items: Optional[list[CateringMenuItem]] = None
shopping_cart_and_orders: list[ShoppingCartAndOrders] = []
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed) self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed)
@ -28,7 +29,7 @@ class CateringPage(Component):
await self.force_refresh() await self.force_refresh()
async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None: async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None:
await self.shopping_cart_and_orders.switch() await self.shopping_cart_and_orders[0].switch()
@staticmethod @staticmethod
def get_menu_items_by_category(all_menu_items: list[CateringMenuItem], category: Optional[CateringMenuItemCategory]) -> list[CateringMenuItem]: def get_menu_items_by_category(all_menu_items: list[CateringMenuItem], category: Optional[CateringMenuItemCategory]) -> list[CateringMenuItem]:
@ -37,7 +38,11 @@ class CateringPage(Component):
def build(self) -> Component: def build(self) -> Component:
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
self.shopping_cart_and_orders = ShoppingCartAndOrders() if len(self.shopping_cart_and_orders) == 0:
self.shopping_cart_and_orders.append(ShoppingCartAndOrders())
if len(self.shopping_cart_and_orders) > 1:
self.shopping_cart_and_orders.clear()
self.shopping_cart_and_orders.append(ShoppingCartAndOrders())
switcher_bar = SwitcherBar( switcher_bar = SwitcherBar(
values=["cart", "orders"], values=["cart", "orders"],
names=["Warenkorb", "Bestellungen"], names=["Warenkorb", "Bestellungen"],
@ -63,7 +68,7 @@ class CateringPage(Component):
align_x=0.5 align_x=0.5
), ),
switcher_bar, switcher_bar,
self.shopping_cart_and_orders self.shopping_cart_and_orders[0]
) )
) if user_id else Spacer() ) if user_id else Spacer()
@ -82,7 +87,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -104,7 +109,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -126,7 +131,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -148,7 +153,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -170,7 +175,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -192,7 +197,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -214,7 +219,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -236,7 +241,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
@ -258,7 +263,7 @@ class CateringPage(Component):
article_name=catering_menu_item.name, article_name=catering_menu_item.name,
article_price=catering_menu_item.price, article_price=catering_menu_item.price,
article_id=catering_menu_item.item_id, article_id=catering_menu_item.item_id,
on_add_callback=self.shopping_cart_and_orders.on_add_item, on_add_callback=self.shopping_cart_and_orders[0].on_add_item,
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0

View File

@ -1,4 +1,5 @@
import logging import logging
from enum import Enum
from typing import Optional from typing import Optional
from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.AccountingService import AccountingService
@ -9,9 +10,15 @@ from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, Catering
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
class CateringErrorType(Enum):
INCLUDES_DISABLED_ITEM = 0
INSUFFICIENT_FUNDS = 1
GENERIC = 99
class CateringError(Exception): class CateringError(Exception):
def __init__(self, message: str) -> None: def __init__(self, message: str, error_type: CateringErrorType = CateringErrorType.GENERIC) -> None:
self.message = message self.message = message
self.error_type = error_type
class CateringService: class CateringService:
@ -26,7 +33,7 @@ class CateringService:
async def place_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool = True) -> CateringOrder: async def place_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool = True) -> CateringOrder:
for menu_item in menu_items: for menu_item in menu_items:
if menu_item.is_disabled: if menu_item.is_disabled:
raise CateringError("Order includes disabled items") raise CateringError("Order includes disabled items", CateringErrorType.INCLUDES_DISABLED_ITEM)
user = await self._user_service.get_user(user_id) user = await self._user_service.get_user(user_id)
if not user: if not user:
@ -34,12 +41,13 @@ class CateringService:
total_price = sum([item.price * quantity for item, quantity in menu_items.items()]) total_price = sum([item.price * quantity for item, quantity in menu_items.items()])
if await self._accounting_service.get_balance(user_id) < total_price: if await self._accounting_service.get_balance(user_id) < total_price:
raise CateringError("Insufficient funds") raise CateringError("Insufficient funds", CateringErrorType.INSUFFICIENT_FUNDS)
order = await self._db_service.add_new_order(menu_items, user_id, is_delivery) order = await self._db_service.add_new_order(menu_items, user_id, is_delivery)
if order: if order:
await self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}") 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_int(total_price)}") logger.info(f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_int(total_price)}")
# await self.cancel_order(order) # ToDo: Check if commented out before commit. Un-comment to auto-cancel every placed order
return order return order
async def update_order_status(self, order_id: int, new_status: CateringOrderStatus) -> bool: async def update_order_status(self, order_id: int, new_status: CateringOrderStatus) -> bool:
@ -58,7 +66,8 @@ class CateringService:
return await self._db_service.get_orders(status=status) return await self._db_service.get_orders(status=status)
async def cancel_order(self, order: CateringOrder) -> bool: async def cancel_order(self, order: CateringOrder) -> bool:
if self._db_service.change_order_status(order.order_id, CateringOrderStatus.CANCELED): 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}") await self._accounting_service.add_balance(order.customer.user_id, order.price, f"CATERING REFUND - {order.order_id}")
return True return True
return False return False

View File

@ -27,4 +27,7 @@ class CateringOrder:
@property @property
def price(self) -> int: def price(self) -> int:
return sum([item.price for item in self.items.keys()]) total = 0
for item, amount in self.items.items():
total += (item.price * amount)
return total