From 500b76b8a81076c168df3a81f8de7704875cae2e Mon Sep 17 00:00:00 2001 From: Mathias Scheider Date: Sat, 12 Sep 2026 21:00:26 +0200 Subject: [PATCH] fix b4w real-world import --- src/main.py | 4 ++-- src/models/b4w.py | 11 +++++++++-- tests/factories.py | 10 ++++++++-- tests/test_model_b4wexport.py | 21 +++++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main.py b/src/main.py index 340b7d6..593626b 100644 --- a/src/main.py +++ b/src/main.py @@ -27,8 +27,8 @@ def main(): assert isinstance(giro_account, Account) ingest_comdirect( Path(args.input_comdirect), - account_iban=giro_account.iban, - account_currency=giro_account.currency, + account_iban=giro_account.iban or "", + account_currency=giro_account.currency or "", ) diff --git a/src/models/b4w.py b/src/models/b4w.py index 1e6d6f8..4e21961 100644 --- a/src/models/b4w.py +++ b/src/models/b4w.py @@ -27,7 +27,7 @@ class B4wExport(ExportBaseModel): sign: Sign = Field(alias="CdtDbtInd") reference: str = Field(alias="RmtInf") other_name: str = Field(alias="RmtdNm") - other_iban: str = Field(alias="RmtdAcctIBAN", pattern=IBAN_PATTERN) + other_iban: str | None = Field(alias="RmtdAcctIBAN", pattern=IBAN_PATTERN) category: Category | None = Field( alias="Category", examples=[ @@ -51,7 +51,7 @@ class B4wExport(ExportBaseModel): "CdtDbtInd": f"{self.sign!s}", "RmtInf": f"{self.reference}", "RmtdNm": f"{self.other_name}", - "RmtdAcctIBAN": f"{self.other_iban}", + "RmtdAcctIBAN": f"{self.other_iban or ''}", "Category": f"{self.category.to_str() if self.category else ''}", "Notes": f"{self.notes}", } @@ -77,6 +77,13 @@ class B4wExport(ExportBaseModel): def amount_signed(self) -> Decimal: return self.amount * self.sign.to_sign() + @field_validator("other_iban", mode="before") + @classmethod + def other_iban_blank_is_none(cls, iban_in: str | None) -> str | None: + if iban_in is None or (isinstance(iban_in, str) and not iban_in.strip()): + return None + return iban_in + @field_validator("category", mode="before") @classmethod def category_str_is_parsable( diff --git a/tests/factories.py b/tests/factories.py index e54af89..b4f9c73 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -34,6 +34,13 @@ def _iban() -> str: return B4wExportFactory.__faker__.iban() +def _other_iban() -> str: + faker = B4wExportFactory.__faker__ + if faker.pyint(min_value=0, max_value=100) < 50: + return "" + return faker.iban() + + class B4wExportFactory(ModelFactory[B4wExport]): __faker__ = Faker("de_DE") __use_examples__ = True @@ -43,7 +50,7 @@ class B4wExportFactory(ModelFactory[B4wExport]): Notes = Use(_sentence) RmtdNm = Use(_names) OwnrAcctIBAN = Use(_iban) - RmtdAcctIBAN = Use(_iban) + RmtdAcctIBAN = Use(_other_iban) class ComdirectGiroFactory(ModelFactory[ComdirectGiro]): @@ -61,4 +68,3 @@ class ComdirectCreditCardFactory(ModelFactory[ComdirectCreditCard]): class ComdirectDepotFactory(ModelFactory[ComdirectDepot]): __faker__ = Faker("de_DE") __use_example__ = True - diff --git a/tests/test_model_b4wexport.py b/tests/test_model_b4wexport.py index dd7a437..e8ab431 100644 --- a/tests/test_model_b4wexport.py +++ b/tests/test_model_b4wexport.py @@ -33,6 +33,27 @@ def test_b4w_export_account_iban_to_short(): _ = B4wExportFactory.build(OwnrAcctIBAN="DE123") +def test_b4w_export_other_iban_empty_is_none(): + data = B4wExportFactory.build(RmtdAcctIBAN="") + assert data.other_iban is None + + +def test_b4w_export_other_iban_blank_is_none(): + data = B4wExportFactory.build(RmtdAcctIBAN=" ") + assert data.other_iban is None + + +def test_b4w_export_other_iban_invalid(): + with pytest.raises(ValidationError): + _ = B4wExportFactory.build(RmtdAcctIBAN="not an IBAN") + + +def test_b4w_export_other_iban_repr_round_trip_empty(): + data = B4wExportFactory.build(RmtdAcctIBAN="") + data2 = eval(str(data)) + assert data == data2 + + def test_b4w_export_parameters_to_dict(): data = B4wExportFactory.build() params = data.parameters_to_dict()