From 02134f61f5c450c57adb1a7eddda57c0dd137e59 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Mon, 19 Aug 2024 11:29:00 +0200 Subject: [PATCH] add AccountingService --- src/EzLanManager.py | 13 +++- .../services/AccountingService.py | 72 +++++++++++++++++++ .../services/DatabaseService.py | 44 ++++++++++-- src/ez_lan_manager/types/Transaction.py | 11 +++ src/ez_lan_manager/types/User.py | 1 - 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 src/ez_lan_manager/services/AccountingService.py create mode 100644 src/ez_lan_manager/types/Transaction.py diff --git a/src/EzLanManager.py b/src/EzLanManager.py index fa14888..3fbd57c 100644 --- a/src/EzLanManager.py +++ b/src/EzLanManager.py @@ -3,12 +3,14 @@ from datetime import datetime from from_root import from_root +from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.ConfigurationService import ConfigurationService from src.ez_lan_manager.services.DatabaseService import DatabaseService from random import randint from src.ez_lan_manager.services.UserService import UserService +from src.ez_lan_manager.types.Transaction import Transaction from src.ez_lan_manager.types.User import User logger = logging.getLogger(__name__.split(".")[-1]) @@ -19,7 +21,16 @@ if __name__ == "__main__": db_config = configuration_service.get_database_configuration() db_service = DatabaseService(db_config) user_service = UserService(db_service) - user_service.create_user("Mamfred", "Peter@peterson.com", "MamaHalloDoo") + accounting_service = AccountingService(db_service, user_service) + #user_service.create_user("Mamfred", "Peter@peterson.com", "MamaHalloDoo") + # db_service.add_transaction(Transaction( + # user_id=19, + # value=50, + # is_debit=True, + # reference="Ein teures Bier", + # transaction_date=datetime.now() + # )) + #print(accounting_service.remove_balance(19, 150, "EinsFuffzig")) # print(db_service.create_user(f"TestUser{randint(0, 9999)}", f"TestMail{randint(0, 9999)}", "pw123")) # print(db_service.update_user( # User(user_id=19, user_name='TestUser838', user_mail='TestMail3142', user_password='pw123', user_first_name=None, user_last_name=None, diff --git a/src/ez_lan_manager/services/AccountingService.py b/src/ez_lan_manager/services/AccountingService.py new file mode 100644 index 0000000..e2a0ba0 --- /dev/null +++ b/src/ez_lan_manager/services/AccountingService.py @@ -0,0 +1,72 @@ +import logging +from datetime import datetime + +from src.ez_lan_manager.services.DatabaseService import DatabaseService +from src.ez_lan_manager.services.UserService import UserService +from src.ez_lan_manager.types.Transaction import Transaction + +logger = logging.getLogger(__name__.split(".")[-1]) + +class InsufficientFundsError(Exception): + pass + +class AccountingService: + def __init__(self, db_service: DatabaseService, user_service: UserService) -> None: + self._db_service = db_service + self._user_service = user_service + + def add_balance(self, user_id: int, balance_to_add: int, reference: str) -> int: + self._db_service.add_transaction(Transaction( + user_id=user_id, + value=balance_to_add, + is_debit=False, + reference=reference, + transaction_date=datetime.now() + )) + logger.debug(f"Added balance of {self.make_euro_string_from_int(balance_to_add)} to user with ID {user_id}") + return self.get_balance(user_id) + + def remove_balance(self, user_id: int, balance_to_remove: int, reference: str) -> int: + current_balance = self.get_balance(user_id) + if (current_balance - balance_to_remove) < 0: + raise InsufficientFundsError + self._db_service.add_transaction(Transaction( + user_id=user_id, + value=balance_to_remove, + is_debit=True, + reference=reference, + transaction_date=datetime.now() + )) + logger.debug(f"Removed balance of {self.make_euro_string_from_int(balance_to_remove)} to user with ID {user_id}") + return self.get_balance(user_id) + + def get_balance(self, user_id: int) -> int: + balance_buffer = 0 + for transaction in self._db_service.get_all_transactions_for_user(user_id): + if transaction.is_debit: + balance_buffer -= transaction.value + else: + balance_buffer += transaction.value + return balance_buffer + + @staticmethod + def make_euro_string_from_int(cent_int: int) -> str: + """ Internally, all money values are cents as ints. Only when showing them to the user we generate a string. Prevents float inaccuracy. """ + as_str = str(cent_int) + if as_str[0] == "-": + is_negative = True + as_str = as_str[1:] + else: + is_negative = False + + if len(as_str) == 1: + result = f"0.0{as_str} €" + elif len(as_str) == 2: + result = f"0.{as_str} €" + else: + result = f"{as_str[:-2]}.{as_str[-2:]} €" + + if is_negative: + result = f"-{result}" + + return result diff --git a/src/ez_lan_manager/services/DatabaseService.py b/src/ez_lan_manager/services/DatabaseService.py index bfd12e5..a72eea7 100644 --- a/src/ez_lan_manager/services/DatabaseService.py +++ b/src/ez_lan_manager/services/DatabaseService.py @@ -6,6 +6,7 @@ import mariadb from mariadb import Cursor from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration +from src.ez_lan_manager.types.Transaction import Transaction from src.ez_lan_manager.types.User import User logger = logging.getLogger(__name__.split(".")[-1]) @@ -49,8 +50,7 @@ class DatabaseService: is_team_member=bool(data[8]), is_admin=bool(data[9]), created_at=data[10], - last_updated_at=data[11], - balance=int(data[12]) + last_updated_at=data[11] ) def get_user_by_name(self, user_name: str) -> Optional[User]: @@ -96,13 +96,49 @@ class DatabaseService: try: cursor.execute( "UPDATE users SET user_name=?, user_mail=?, user_password=?, user_first_name=?, user_last_name=?, user_birth_date=?, " - "is_active=?, is_team_member=?, is_admin=?, balance=? WHERE (user_id=?)", (user.user_name, user.user_mail.lower(), user.user_password, + "is_active=?, is_team_member=?, is_admin=? WHERE (user_id=?)", (user.user_name, user.user_mail.lower(), user.user_password, user.user_first_name, user.user_last_name, user.user_birth_day, user.is_active, user.is_team_member, user.is_admin, - user.balance, user.user_id) + user.user_id) ) self._connection.commit() except mariadb.IntegrityError as e: logger.warning(f"Aborted duplication entry: {e}") raise DuplicationError return user + + def add_transaction(self, transaction: Transaction) -> Optional[Transaction]: + cursor = self._get_cursor() + try: + cursor.execute( + "INSERT INTO transactions (user_id, value, is_debit, transaction_date, transaction_reference) " + "VALUES (?, ?, ?, ?, ?)", + (transaction.user_id, transaction.value, transaction.is_debit, transaction.transaction_date, transaction.reference) + ) + self._connection.commit() + except Exception as e: + logger.warning(f"Error adding Transaction: {e}") + return + + return transaction + + def get_all_transactions_for_user(self, user_id: int) -> list[Transaction]: + transactions = [] + + cursor = self._get_cursor() + try: + cursor.execute("SELECT * FROM transactions WHERE user_id=?", (user_id,)) + result = cursor.fetchall() + except mariadb.Error as e: + logger.error(f"Error getting all transactions for user: {e}") + return [] + + for transaction_raw in result: + transactions.append(Transaction( + user_id=user_id, + value=int(transaction_raw[2]), + is_debit=bool(transaction_raw[3]), + transaction_date=transaction_raw[4], + reference=transaction_raw[5] + )) + return transactions diff --git a/src/ez_lan_manager/types/Transaction.py b/src/ez_lan_manager/types/Transaction.py new file mode 100644 index 0000000..18758cd --- /dev/null +++ b/src/ez_lan_manager/types/Transaction.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from datetime import datetime + + +@dataclass(frozen=True) +class Transaction: + user_id: int + value: int + is_debit: bool + reference: str + transaction_date: datetime diff --git a/src/ez_lan_manager/types/User.py b/src/ez_lan_manager/types/User.py index bb03b9c..164abac 100644 --- a/src/ez_lan_manager/types/User.py +++ b/src/ez_lan_manager/types/User.py @@ -17,4 +17,3 @@ class User: is_admin: bool created_at: datetime last_updated_at: datetime - balance: int