diff --git a/src/models.py b/src/models.py index 323e49c..441f3f6 100644 --- a/src/models.py +++ b/src/models.py @@ -102,28 +102,51 @@ class Category(BaseModel): return f'Category.from_str("{self.__str__()}")' +def _parse_date_german(date_str: str) -> date | None: + if date_str in ["offen", "neu", "--"]: + return None + day, month, year = list(map(int, date_str.split("."))) + return date(day=day, month=month, year=year) + +def _parse_decimal_german(decimal_str: str) -> Decimal: + return Decimal(decimal_str.replace(".", "").replace(",", ".")) + + class ComdirectBase(BaseModel): - date_book: str = Field(alias="Buchungstag", examples=["07.09.2026"]) - amount_eur: str = Field(alias="Umsatz in EUR", examples=["32,43"]) + date_val: date | None = Field(alias="Wertstellung (Valuta)", examples=["07.09.2026"]) + amount_eur: Decimal = Field(alias="Umsatz in EUR", examples=["32,43"]) + + @field_validator("date_val", mode="before") + @classmethod + def parse_date_val(cls, date_str: str) -> date | None: + return _parse_date_german(date_str) + + @field_validator("amount_eur", mode="before") + @classmethod + def parse_amount(cls, decimal_str: str) -> Decimal: + return _parse_decimal_german(decimal_str) -class ComdirectCommon(ComdirectBase): +class ComdirectBaseBook(ComdirectBase): + date_book: date | None = Field(alias="Buchungstag", examples=["07.09.2026"]) + + @field_validator("date_book", mode="before") + @classmethod + def parse_date_book(cls, date_str: str) -> date | None: + return _parse_date_german(date_str) + + +class ComdirectGiro(ComdirectBaseBook): type: str = Field( alias="Vorgang", examples=["Lastschrift / Belastung", "Kartenumsatz"] ) text: str = Field(alias="Buchungstext") -class ComdirectGiro(ComdirectCommon): - # "Buchungstag";"Wertstellung (Valuta)";"Vorgang";"Buchungstext";"Umsatz in EUR"; - # "Buchungstag";"Wertstellung (Valuta)";"Vorgang";"Buchungstext";"Umsatz in EUR"; - date_val: str = Field(alias="Wertstellung (Valuta)", examples=["07.09.2026"]) - - -class ComdirectCreditCard(ComdirectCommon): +class ComdirectCreditCard(ComdirectBaseBook): # "Buchungstag";"Umsatztag";"Vorgang";"Referenz";"Buchungstext";"Umsatz in EUR"; - date_book: str = Field(alias="Buchungstag", examples=["07.09.2026"]) - date_val: str = Field(alias="Umsatztag", default="") + date_book: date | None = Field(alias="Buchungstag", examples=["07.09.2026"]) + date_val: date | None = Field(alias="Umsatztag", default=None) type: str = Field( alias="Vorgang", examples=["Lastschrift / Belastung", "Kartenumsatz"], @@ -131,14 +154,18 @@ class ComdirectCreditCard(ComdirectCommon): ) reference: str = Field(alias="Referenz", default="") text: str = Field(alias="Buchungstext", default="") - amount_eur: str = Field(alias="Umsatz in EUR", examples=["32,43"], default="") + amount_eur: Decimal | None = Field(alias="Umsatz in EUR", examples=["32,43"], default=None) -class ComdirectDepot(BaseModel): - # "Buchungstag";"Geschäftstag";"Stück / Nom.";"Bezeichnung";"WKN";"Währung";"Ausführungskurs";"Umsatz in EUR"; - date_val: str = Field(alias="Geschäftstag") - num: str = Field(alias="Stück / Nom.") +class ComdirectDepot(ComdirectBase): + date_val: date | None = Field(alias="Geschäftstag") + num: Decimal = Field(alias="Stück / Nom.") name: str = Field(alias="Bezeichnung") wkn: str = Field(alias="WKN") currency: str = Field(alias="Währung") - spot_price: str = Field(alias="Ausführungskurs") + spot_price: Decimal = Field(alias="Ausführungskurs") + + @field_validator("num", "spot_price", mode="before") + @classmethod + def parse_decimals(cls, decimal_in: str) -> Decimal: + return _parse_decimal_german(decimal_in) diff --git a/tests/test_reader.py b/tests/test_reader.py index 5b0727e..9bc4911 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,10 +1,20 @@ from pathlib import Path +from src.models import ComdirectCreditCard, ComdirectDepot, ComdirectGiro from src.reader import reader_comdirect +from src.settings.account_ingest import ACCOUNT_INGESTION_FUNCTIONS -def test_reader(): +def test_reader_comdirect(): + bank_name = "comdirect" + assert bank_name in ACCOUNT_INGESTION_FUNCTIONS test_path = Path(".local/data/comdirect/umsaetze_9773063005_20260906-2043.csv") assert test_path.exists() data = reader_comdirect(test_path) assert data + assert len(data) == len(ACCOUNT_INGESTION_FUNCTIONS.get(bank_name)) + assert set(data) == set(ACCOUNT_INGESTION_FUNCTIONS.get(bank_name)) + assert all(isinstance(row, ComdirectGiro) for row in data["Girokonto"]) + assert all(isinstance(row, ComdirectCreditCard) for row in data["Visa-Karte (Kreditkarte)"]) + assert all(isinstance(row, ComdirectGiro) for row in data["Tagesgeld PLUS-Konto"]) + assert all(isinstance(row, ComdirectDepot) for row in data["Depot"])