add TicketBuying Feature
This commit is contained in:
parent
5ed1230fde
commit
e20ce6b78b
@ -25,14 +25,16 @@
|
|||||||
[tickets]
|
[tickets]
|
||||||
[tickets."NORMAL"]
|
[tickets."NORMAL"]
|
||||||
total_tickets=30
|
total_tickets=30
|
||||||
price=2500 # Eurocent
|
price=2500
|
||||||
description="Normales Ticket"
|
description="Normales Ticket"
|
||||||
|
additional_info="Berechtigt zur Nutzung eines regulären Platzes für die gesamte Dauer der LAN"
|
||||||
is_default=true
|
is_default=true
|
||||||
|
|
||||||
[tickets."LUXUS"]
|
[tickets."LUXUS"]
|
||||||
total_tickets=10
|
total_tickets=10
|
||||||
price=4000 # Eurocent
|
price=3500
|
||||||
description="Luxus Ticket"
|
description="Luxus Ticket"
|
||||||
|
additional_info="Berechtigt zur Nutzung eines verbesserten Platzes. Dieser ist mit einer höheren Internet-Bandbreite und einem Sitzkissen ausgestattet."
|
||||||
is_default=false
|
is_default=false
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
|
|||||||
@ -64,12 +64,12 @@ if __name__ == "__main__":
|
|||||||
Page(
|
Page(
|
||||||
name="BuyTicket",
|
name="BuyTicket",
|
||||||
page_url="buy_ticket",
|
page_url="buy_ticket",
|
||||||
build=lambda: pages.PlaceholderPage(placeholder_name="Tickets kaufen"),
|
build=pages.BuyTicketPage,
|
||||||
),
|
),
|
||||||
Page(
|
Page(
|
||||||
name="SeatingPlan",
|
name="SeatingPlan",
|
||||||
page_url="seating",
|
page_url="seating",
|
||||||
build=lambda: pages.PlaceholderPage(placeholder_name="Sitzplan"),
|
build=pages.SeatingPlanPage,
|
||||||
),
|
),
|
||||||
Page(
|
Page(
|
||||||
name="Catering",
|
name="Catering",
|
||||||
|
|||||||
88
src/ez_lan_manager/components/TicketBuyCard.py
Normal file
88
src/ez_lan_manager/components/TicketBuyCard.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from functools import partial
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
import rio
|
||||||
|
from rio import Component, Card, Column, Text, Row, Button, TextStyle, ProgressBar, event, Spacer
|
||||||
|
|
||||||
|
from src.ez_lan_manager import TicketingService
|
||||||
|
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||||
|
from src.ez_lan_manager.types.Ticket import Ticket
|
||||||
|
|
||||||
|
|
||||||
|
class TicketBuyCard(Component):
|
||||||
|
description: str
|
||||||
|
additional_info: str
|
||||||
|
price: int
|
||||||
|
category: str
|
||||||
|
pressed_cb: Callable
|
||||||
|
is_enabled: bool
|
||||||
|
total_tickets: int
|
||||||
|
user_ticket: Optional[Ticket]
|
||||||
|
available_tickets: int = 0
|
||||||
|
|
||||||
|
@event.on_populate
|
||||||
|
async def async_init(self) -> None:
|
||||||
|
self.available_tickets = await self.session[TicketingService].get_available_tickets_for_category(self.category)
|
||||||
|
|
||||||
|
def build(self) -> rio.Component:
|
||||||
|
ticket_description_style = TextStyle(
|
||||||
|
fill=self.session.theme.neutral_color,
|
||||||
|
font_size=1.2,
|
||||||
|
)
|
||||||
|
ticket_additional_info_style = TextStyle(
|
||||||
|
fill=self.session.theme.neutral_color,
|
||||||
|
font_size=0.8
|
||||||
|
)
|
||||||
|
ticket_owned_style = TextStyle(
|
||||||
|
fill=self.session.theme.success_color,
|
||||||
|
font_size=0.8
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
progress = self.available_tickets / self.total_tickets
|
||||||
|
except ZeroDivisionError:
|
||||||
|
progress = 0
|
||||||
|
progress_bar = ProgressBar(
|
||||||
|
progress=progress,
|
||||||
|
color=self.session.theme.success_color if progress > 0.25 else self.session.theme.danger_color,
|
||||||
|
margin_right=1,
|
||||||
|
grow_x=True
|
||||||
|
)
|
||||||
|
|
||||||
|
tickets_side_text = Text(
|
||||||
|
f"{self.available_tickets}/{self.total_tickets}",
|
||||||
|
align_x=1
|
||||||
|
)
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
Column(
|
||||||
|
Text(self.description, margin_left=1, margin_top=1, style=ticket_description_style),
|
||||||
|
Text("Du besitzt dieses Ticket!", margin_left=1, margin_top=1, style=ticket_owned_style) if self.user_ticket is not None and self.user_ticket.category == self.category else Spacer(),
|
||||||
|
Text(self.additional_info, margin_left=1, margin_top=1, style=ticket_additional_info_style, wrap=True),
|
||||||
|
Row(
|
||||||
|
progress_bar,
|
||||||
|
tickets_side_text,
|
||||||
|
margin_top=1,
|
||||||
|
margin_left=1,
|
||||||
|
margin_right=1
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
Text(f"{AccountingService.make_euro_string_from_int(self.price)}", margin_left=1, margin_top=1, grow_x=True),
|
||||||
|
Button(
|
||||||
|
Text("Kaufen", align_x=0.5, margin=0.4),
|
||||||
|
margin_right=1,
|
||||||
|
margin_top=1,
|
||||||
|
style="major",
|
||||||
|
shape="rounded",
|
||||||
|
on_press=partial(self.pressed_cb, self.category),
|
||||||
|
is_sensitive=self.is_enabled
|
||||||
|
),
|
||||||
|
margin_bottom=1
|
||||||
|
)
|
||||||
|
),
|
||||||
|
margin_left=3,
|
||||||
|
margin_right=3,
|
||||||
|
margin_bottom=1,
|
||||||
|
color=self.session.theme.hud_color,
|
||||||
|
corner_radius=0.2
|
||||||
|
)
|
||||||
@ -10,7 +10,7 @@ from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation
|
|||||||
class BasePage(Component):
|
class BasePage(Component):
|
||||||
content: Component
|
content: Component
|
||||||
|
|
||||||
@event.periodic(5)
|
@event.periodic(60)
|
||||||
async def check_db_conn(self) -> None:
|
async def check_db_conn(self) -> None:
|
||||||
is_healthy = await self.session[DatabaseService].is_healthy()
|
is_healthy = await self.session[DatabaseService].is_healthy()
|
||||||
if not is_healthy:
|
if not is_healthy:
|
||||||
|
|||||||
127
src/ez_lan_manager/pages/BuyTicketPage.py
Normal file
127
src/ez_lan_manager/pages/BuyTicketPage.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
from asyncio import sleep
|
||||||
|
from functools import partial
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button, Card, Popup
|
||||||
|
|
||||||
|
from src.ez_lan_manager import ConfigurationService, UserService, MailingService, AccountingService, TicketingService
|
||||||
|
from src.ez_lan_manager.components.AnimatedText import AnimatedText
|
||||||
|
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||||
|
from src.ez_lan_manager.components.TicketBuyCard import TicketBuyCard
|
||||||
|
from src.ez_lan_manager.pages import BasePage
|
||||||
|
from src.ez_lan_manager.services.AccountingService import InsufficientFundsError
|
||||||
|
from src.ez_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
|
||||||
|
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||||
|
from src.ez_lan_manager.types.Ticket import Ticket
|
||||||
|
from src.ez_lan_manager.types.User import User
|
||||||
|
|
||||||
|
|
||||||
|
class BuyTicketPage(Component):
|
||||||
|
user: Optional[User] = None
|
||||||
|
user_ticket: Optional[Ticket] = None
|
||||||
|
is_popup_open: bool = False
|
||||||
|
popup_message: str = ""
|
||||||
|
is_popup_success: bool = False
|
||||||
|
is_buying_enabled: bool = False
|
||||||
|
|
||||||
|
@event.on_populate
|
||||||
|
async def on_populate(self) -> None:
|
||||||
|
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Ticket kaufen")
|
||||||
|
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||||
|
if self.user is None: # No user logged in
|
||||||
|
self.is_buying_enabled = False
|
||||||
|
else: # User is logged in
|
||||||
|
possible_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
|
||||||
|
self.user_ticket = possible_ticket
|
||||||
|
if possible_ticket is not None: # User already has a ticket
|
||||||
|
self.is_buying_enabled = False
|
||||||
|
else:
|
||||||
|
self.is_buying_enabled = True
|
||||||
|
|
||||||
|
async def on_buy_pressed(self, category: str) -> None:
|
||||||
|
if not self.user:
|
||||||
|
return
|
||||||
|
self.is_buying_enabled = False
|
||||||
|
await self.force_refresh()
|
||||||
|
|
||||||
|
try:
|
||||||
|
t_s = self.session[TicketingService]
|
||||||
|
ticket = await t_s.purchase_ticket(self.user.user_id, category)
|
||||||
|
self.popup_message = f"Ticket erfolgreich gekauft. Deine Ticket-ID lautet: {ticket.ticket_id}."
|
||||||
|
self.is_popup_success = True
|
||||||
|
except TicketNotAvailableError:
|
||||||
|
self.popup_message = "Das ausgewählte Ticket ist nicht verfügbar."
|
||||||
|
self.is_popup_success = False
|
||||||
|
except InsufficientFundsError:
|
||||||
|
self.popup_message = "Dein Guthaben reicht nicht aus um dieses Ticket zu kaufen."
|
||||||
|
self.is_popup_success = False
|
||||||
|
except UserAlreadyHasTicketError:
|
||||||
|
self.popup_message = (f"Du besitzt bereits ein Ticket. Um dein aktuelles Ticket zu stornieren, kontaktiere bitte den Support unter "
|
||||||
|
f"{self.session[ConfigurationService].get_lan_info().organizer_mail}.")
|
||||||
|
self.is_popup_success = False
|
||||||
|
except RuntimeError:
|
||||||
|
self.popup_message = "Ein unbekannter Fehler ist aufgetreten."
|
||||||
|
self.is_popup_success = False
|
||||||
|
self.is_popup_open = True
|
||||||
|
await self.on_populate()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_popup_close_pressed(self) -> None:
|
||||||
|
self.is_popup_open = False
|
||||||
|
self.popup_message = ""
|
||||||
|
|
||||||
|
|
||||||
|
def build(self) -> Component:
|
||||||
|
ticket_infos = self.session[ConfigurationService].get_ticket_info()
|
||||||
|
header = Text(
|
||||||
|
"Tickets & Preise",
|
||||||
|
style=TextStyle(
|
||||||
|
fill=self.session.theme.background_color,
|
||||||
|
font_size=1.2
|
||||||
|
),
|
||||||
|
margin_top=2,
|
||||||
|
margin_bottom=2,
|
||||||
|
align_x=0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
return BasePage(
|
||||||
|
content=Column(
|
||||||
|
MainViewContentBox(
|
||||||
|
Column(
|
||||||
|
header,
|
||||||
|
Popup(
|
||||||
|
anchor=header,
|
||||||
|
content=Column(
|
||||||
|
Text(
|
||||||
|
self.popup_message,
|
||||||
|
style=TextStyle(font_size=1.1, fill=self.session.theme.success_color if self.is_popup_success else self.session.theme.danger_color),
|
||||||
|
wrap=True,
|
||||||
|
grow_y=True,
|
||||||
|
margin=1
|
||||||
|
),
|
||||||
|
Button("Bestätigen", shape="rounded", grow_y=False, on_press=self.on_popup_close_pressed),
|
||||||
|
min_width=34,
|
||||||
|
min_height=10
|
||||||
|
),
|
||||||
|
is_open=self.is_popup_open,
|
||||||
|
position="bottom",
|
||||||
|
margin=1,
|
||||||
|
corner_radius=0.2,
|
||||||
|
color=self.session.theme.primary_color
|
||||||
|
),
|
||||||
|
*[TicketBuyCard(
|
||||||
|
description=t.description,
|
||||||
|
additional_info=t.additional_info,
|
||||||
|
price=t.price,
|
||||||
|
category=t.category,
|
||||||
|
pressed_cb=self.on_buy_pressed,
|
||||||
|
is_enabled=self.is_buying_enabled,
|
||||||
|
total_tickets=t.total_tickets,
|
||||||
|
user_ticket=self.user_ticket
|
||||||
|
) for t in ticket_infos]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
align_y=0
|
||||||
|
),
|
||||||
|
grow_x=True
|
||||||
|
)
|
||||||
24
src/ez_lan_manager/pages/SeatingPlanPage.py
Normal file
24
src/ez_lan_manager/pages/SeatingPlanPage.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button
|
||||||
|
|
||||||
|
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
|
||||||
|
from src.ez_lan_manager.components.AnimatedText import AnimatedText
|
||||||
|
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||||
|
from src.ez_lan_manager.pages import BasePage
|
||||||
|
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||||
|
from src.ez_lan_manager.types.User import User
|
||||||
|
|
||||||
|
|
||||||
|
class SeatingPlanPage(Component):
|
||||||
|
@event.on_populate
|
||||||
|
async def on_populate(self) -> None:
|
||||||
|
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Sitzplan")
|
||||||
|
|
||||||
|
def build(self) -> Component:
|
||||||
|
return BasePage(
|
||||||
|
content=Column(
|
||||||
|
MainViewContentBox(),
|
||||||
|
MainViewContentBox(),
|
||||||
|
align_y=0
|
||||||
|
),
|
||||||
|
grow_x=True
|
||||||
|
)
|
||||||
@ -13,3 +13,5 @@ from .TournamentsPage import TournamentsPage
|
|||||||
from .GuestsPage import GuestsPage
|
from .GuestsPage import GuestsPage
|
||||||
from .CateringPage import CateringPage
|
from .CateringPage import CateringPage
|
||||||
from .DbErrorPage import DbErrorPage
|
from .DbErrorPage import DbErrorPage
|
||||||
|
from .SeatingPlanPage import SeatingPlanPage
|
||||||
|
from .BuyTicketPage import BuyTicketPage
|
||||||
|
|||||||
@ -90,6 +90,7 @@ class ConfigurationService:
|
|||||||
total_tickets=self._config["tickets"][value]["total_tickets"],
|
total_tickets=self._config["tickets"][value]["total_tickets"],
|
||||||
price=self._config["tickets"][value]["price"],
|
price=self._config["tickets"][value]["price"],
|
||||||
description=self._config["tickets"][value]["description"],
|
description=self._config["tickets"][value]["description"],
|
||||||
|
additional_info=self._config["tickets"][value]["additional_info"],
|
||||||
is_default=self._config["tickets"][value]["is_default"]
|
is_default=self._config["tickets"][value]["is_default"]
|
||||||
) for value in self._config["tickets"]])
|
) for value in self._config["tickets"]])
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
|
|||||||
@ -20,6 +20,7 @@ class TicketInfo:
|
|||||||
total_tickets: int
|
total_tickets: int
|
||||||
price: int
|
price: int
|
||||||
description: str
|
description: str
|
||||||
|
additional_info: str
|
||||||
is_default: bool
|
is_default: bool
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user