diff --git a/src/EzLanManager.py b/src/EzLanManager.py index 0571daa..e716252 100644 --- a/src/EzLanManager.py +++ b/src/EzLanManager.py @@ -11,6 +11,7 @@ from random import randint from src.ez_lan_manager.services.MailingService import MailingService from src.ez_lan_manager.services.NewsService import NewsService +from src.ez_lan_manager.services.TicketingService import TicketingService from src.ez_lan_manager.services.UserService import UserService from src.ez_lan_manager.types.News import News from src.ez_lan_manager.types.Transaction import Transaction @@ -21,13 +22,17 @@ logger = logging.getLogger(__name__.split(".")[-1]) if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) configuration_service = ConfigurationService(from_root("config.toml")) + lan_info = configuration_service.get_lan_info() db_config = configuration_service.get_database_configuration() db_service = DatabaseService(db_config) user_service = UserService(db_service) accounting_service = AccountingService(db_service) news_service = NewsService(db_service) mailing_service = MailingService(configuration_service.get_mailing_service_configuration()) - #mailing_service.send_email("Hallo von EZ LAN Mananger", "Grüße :)", "davidr.develop@gmail.com") + ticketing_service = TicketingService(lan_info, db_service, accounting_service) + + print(ticketing_service.refund_ticket(19)) + #print(ticketing_service.get_available_tickets()) #user_service.create_user("Alex", "alex@gmail.com", "MeinPasswort") diff --git a/src/ez_lan_manager/services/DatabaseService.py b/src/ez_lan_manager/services/DatabaseService.py index 370422f..26ab4c8 100644 --- a/src/ez_lan_manager/services/DatabaseService.py +++ b/src/ez_lan_manager/services/DatabaseService.py @@ -8,6 +8,7 @@ from mariadb import Cursor from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration from src.ez_lan_manager.types.News import News +from src.ez_lan_manager.types.Ticket import Ticket from src.ez_lan_manager.types.Transaction import Transaction from src.ez_lan_manager.types.User import User @@ -178,3 +179,75 @@ class DatabaseService: )) return results + + def get_tickets(self) -> list[Ticket]: + results = [] + cursor = self._get_cursor() + try: + cursor.execute("SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id;", ()) + except Exception as e: + logger.warning(f"Error fetching tickets: {e}") + return [] + + for ticket_raw in cursor.fetchall(): + user = self._map_db_result_to_user(ticket_raw[3:]) + results.append(Ticket( + ticket_id=ticket_raw[0], + category=ticket_raw[1], + purchase_date=ticket_raw[3], + owner=user + )) + + return results + + def get_ticket_for_user(self, user_id: int) -> Optional[Ticket]: + cursor = self._get_cursor() + try: + cursor.execute("SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id WHERE user_id=?;", (user_id, )) + except Exception as e: + logger.warning(f"Error fetching ticket for user: {e}") + return + + result = cursor.fetchone() + if not result: + return + + user = self._map_db_result_to_user(result[3:]) + return Ticket( + ticket_id=result[0], + category=result[1], + purchase_date=result[3], + owner=user + ) + + def generate_ticket_for_user(self, user_id: int, category: str) -> Optional[Ticket]: + cursor = self._get_cursor() + try: + cursor.execute("INSERT INTO tickets (ticket_category, user) VALUES (?, ?)", (category, user_id)) + self._connection.commit() + except Exception as e: + logger.warning(f"Error generating ticket for user: {e}") + return + + return self.get_ticket_for_user(user_id) + + def change_ticket_owner(self, ticket_id: int, new_owner_id: int) -> bool: + cursor = self._get_cursor() + try: + cursor.execute("UPDATE tickets SET user = ? WHERE ticket_id = ?;", (new_owner_id, ticket_id)) + affected_rows = cursor.rowcount + self._connection.commit() + except Exception as e: + logger.warning(f"Error transferring ticket to user: {e}") + return False + return bool(affected_rows) + + def delete_ticket(self, ticket_id: int) -> bool: + cursor = self._get_cursor() + try: + cursor.execute("DELETE FROM tickets WHERE ticket_id = ?;", (ticket_id, )) + self._connection.commit() + except Exception as e: + logger.warning(f"Error deleting ticket: {e}") + return False + return True diff --git a/src/ez_lan_manager/services/TicketingService.py b/src/ez_lan_manager/services/TicketingService.py index 3b92ad0..13975cb 100644 --- a/src/ez_lan_manager/services/TicketingService.py +++ b/src/ez_lan_manager/services/TicketingService.py @@ -1,7 +1,73 @@ import logging +from typing import Optional + +from src.ez_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError +from src.ez_lan_manager.services.DatabaseService import DatabaseService +from src.ez_lan_manager.types.ConfigurationTypes import LanInfo +from src.ez_lan_manager.types.Ticket import Ticket logger = logging.getLogger(__name__.split(".")[-1]) +class TicketNotAvailableError(Exception): + def __init__(self, category: str): + self.category = category + +class UserAlreadyHasTicketError(Exception): + pass + class TicketingService: - def __init__(self) -> None: - pass \ No newline at end of file + def __init__(self, lan_info: LanInfo, db_service: DatabaseService, accounting_service: AccountingService) -> None: + self._lan_info = lan_info + self._db_service = db_service + self._accounting_service = accounting_service + + def get_total_tickets(self) -> int: + return sum([self._lan_info.ticket_info.get_available_tickets(c) for c in self._lan_info.ticket_info.categories]) + + def get_available_tickets(self) -> dict[str, int]: + result = self._lan_info.ticket_info.total_available_tickets + all_tickets = self._db_service.get_tickets() + for ticket in all_tickets: + result[ticket.category] -= 1 + + return result + + def purchase_ticket(self, user_id: int, category: str) -> Ticket: + if category not in self._lan_info.ticket_info.categories or self.get_available_tickets()[category] < 1: + raise TicketNotAvailableError(category) + + user_balance = self._accounting_service.get_balance(user_id) + if self._lan_info.ticket_info.get_price(category) > user_balance: + raise InsufficientFundsError + + if self.get_user_ticket(user_id): + raise UserAlreadyHasTicketError + + if new_ticket := self._db_service.generate_ticket_for_user(user_id, category): + self._accounting_service.remove_balance( + user_id, + self._lan_info.ticket_info.get_price(new_ticket.category), + f"TICKET {new_ticket.ticket_id}" + ) + logger.debug(f"User {user_id} purchased ticket {new_ticket.ticket_id}") + return new_ticket + + raise RuntimeError("An unknown error occurred while purchasing ticket") + + def refund_ticket(self, user_id: int) -> bool: + user_ticket = self.get_user_ticket(user_id) + if not user_ticket: + return False + + if self._db_service.delete_ticket(user_ticket.ticket_id): + self._accounting_service.add_balance(user_id, self._lan_info.ticket_info.get_price(user_ticket.category), f"TICKET REFUND {user_ticket.ticket_id}") + logger.debug(f"User {user_id} refunded ticket {user_ticket.ticket_id}") + return True + + return False + + def transfer_ticket(self, ticket_id: int, user_id: int) -> bool: + return self._db_service.change_ticket_owner(ticket_id, user_id) + + def get_user_ticket(self, user_id: int) -> Optional[Ticket]: + return self._db_service.get_ticket_for_user(user_id) diff --git a/src/ez_lan_manager/types/ConfigurationTypes.py b/src/ez_lan_manager/types/ConfigurationTypes.py index b3152ec..edb32fa 100644 --- a/src/ez_lan_manager/types/ConfigurationTypes.py +++ b/src/ez_lan_manager/types/ConfigurationTypes.py @@ -1,3 +1,4 @@ +from copy import copy from dataclasses import dataclass from datetime import datetime @@ -30,6 +31,10 @@ class TicketInfo: except KeyError: raise NoSuchCategoryError + @property + def total_available_tickets(self): + return copy(self._available_tickets) + @dataclass(frozen=True) class MailingServiceConfiguration: smtp_server: str diff --git a/src/ez_lan_manager/types/Ticket.py b/src/ez_lan_manager/types/Ticket.py new file mode 100644 index 0000000..9b7a5e3 --- /dev/null +++ b/src/ez_lan_manager/types/Ticket.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from src.ez_lan_manager.types.User import User + + +@dataclass(frozen=True) +class Ticket: + ticket_id: int + category: str + purchase_date: datetime + owner: Optional[User] = None