Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72c2ee00cd | ||
|
|
cc7ba35c4d | ||
|
|
a0a4d6e8d9 | ||
|
|
c354523ae9 |
+1
-1
@@ -14,7 +14,7 @@ wheels/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
settings.py
|
||||
/settings.py
|
||||
|
||||
# Database instances
|
||||
*.db
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from tests.factories import B4wExportFactory
|
||||
|
||||
NUMBER_OF_ROWS = 50
|
||||
PATH_DEFAULT = Path("tests/test_data/b4w_export_dummy_01.csv")
|
||||
|
||||
|
||||
def save_csv(path: Path = PATH_DEFAULT):
|
||||
data = B4wExportFactory.batch(NUMBER_OF_ROWS)
|
||||
row_fields = list(data[0].parameters_to_dict())
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", newline="") as fid:
|
||||
writer = csv.DictWriter(fid, fieldnames=row_fields)
|
||||
writer.writeheader()
|
||||
for export in data:
|
||||
writer.writerow(export.parameters_to_dict())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
save_csv()
|
||||
+1
-1
@@ -10,7 +10,7 @@ class Currency(str, Enum):
|
||||
CHF = "CHF"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
return f"{self.value}"
|
||||
|
||||
def __repr__(self):
|
||||
return f"Currency.{self.name}"
|
||||
|
||||
+5
-5
@@ -7,6 +7,11 @@ from src.ingest._common import _reader_raw, _upsert
|
||||
from src.models.b4w import B4wDb, B4wExport, Category, CategoryDb
|
||||
|
||||
|
||||
def reader_b4w(path: Path) -> list[B4wExport]:
|
||||
raw = _reader_raw(path)
|
||||
return list(map(B4wExport.model_validate, raw))
|
||||
|
||||
|
||||
def upsert_category(
|
||||
session: Session, category: Category | None, do_commit=True
|
||||
) -> CategoryDb | None:
|
||||
@@ -65,8 +70,3 @@ def ingest_b4w(path: Path):
|
||||
for row in data:
|
||||
upsert_b4w(session, row, do_commit=False)
|
||||
session.commit()
|
||||
|
||||
|
||||
def reader_b4w(path: Path) -> list[B4wExport]:
|
||||
raw = _reader_raw(path)
|
||||
return list(map(B4wExport.model_validate, raw))
|
||||
|
||||
+78
-62
@@ -4,14 +4,17 @@ from pathlib import Path
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from settings import BANK_ACCOUNTS
|
||||
from src.db import get_session
|
||||
from src.ingest._common import _upsert
|
||||
from src.models._common import Csv2Dict, CsV2DictMulti
|
||||
from src.models.comdirect import (
|
||||
ComdirectCreditCard,
|
||||
ComdirectDepot,
|
||||
ComdirectGiro,
|
||||
ComdirectGiroDb,
|
||||
)
|
||||
from src.models.settings import Account, Source
|
||||
|
||||
PATTERN_ACCOUNT_NAME = r'^"Umsätze ([^"]+)";"Zeitraum: .*$'
|
||||
PATTERN_HEADER = r'"([^"]+)"'
|
||||
@@ -25,12 +28,63 @@ class EXPECT_LINE(Enum):
|
||||
DATA = auto()
|
||||
|
||||
|
||||
COMDIRECT_ACCOUNT_PARSERS = {
|
||||
"Girokonto": lambda row: ComdirectGiro.model_validate(row),
|
||||
"Visa-Karte (Kreditkarte)": lambda row: ComdirectCreditCard.model_validate(row),
|
||||
"Tagesgeld PLUS-Konto": lambda row: ComdirectGiro.model_validate(row),
|
||||
"Depot": lambda row: ComdirectDepot.model_validate(row),
|
||||
}
|
||||
type ComdirectExportModel = ComdirectGiro | ComdirectDepot | ComdirectCreditCard
|
||||
|
||||
|
||||
SOURCE_COMDIRECT = BANK_ACCOUNTS.get("comdirect")
|
||||
assert isinstance(SOURCE_COMDIRECT, Source)
|
||||
|
||||
SOURCE_PATH = SOURCE_COMDIRECT.source_path
|
||||
ACCOUNTS = SOURCE_COMDIRECT.accounts
|
||||
|
||||
COMDIRECT_ACCOUNT_PARSERS = {a.name: a.model.model_validate for a in ACCOUNTS}
|
||||
|
||||
|
||||
def reader_comdirect(path: Path) -> dict[str, list[ComdirectExportModel]]:
|
||||
result = {}
|
||||
raw = _reader_raw_comdirect(path)
|
||||
for account in ACCOUNTS:
|
||||
data_raw: Csv2Dict = raw.pop(account.name)
|
||||
data: list = list(map(account.model.model_validate, data_raw))
|
||||
result[account.name] = data
|
||||
assert not raw
|
||||
return result
|
||||
|
||||
|
||||
def _reader_raw_comdirect(path: Path) -> CsV2DictMulti:
|
||||
|
||||
with path.open(encoding="latin1") as fid:
|
||||
lines = fid.readlines()
|
||||
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
account_name = None
|
||||
result = {}
|
||||
for line in lines:
|
||||
if expect_next == EXPECT_LINE.ACCOUNT_NAME:
|
||||
account_name = re.findall(PATTERN_ACCOUNT_NAME, line)
|
||||
if account_name:
|
||||
account_name = account_name[0]
|
||||
result[account_name] = {}
|
||||
expect_next = EXPECT_LINE.BLANK
|
||||
elif expect_next == EXPECT_LINE.BLANK:
|
||||
if line == "\n":
|
||||
expect_next = EXPECT_LINE.HEADER
|
||||
elif expect_next == EXPECT_LINE.HEADER:
|
||||
header = re.findall(PATTERN_HEADER, line)
|
||||
if header:
|
||||
result[account_name] = []
|
||||
expect_next = EXPECT_LINE.DATA
|
||||
elif expect_next == EXPECT_LINE.DATA:
|
||||
data = re.findall(PATTERN_DATA, line)
|
||||
if data:
|
||||
result[account_name].append(
|
||||
{column: value for column, value in zip(header, data)}
|
||||
)
|
||||
else:
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
else:
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
return result
|
||||
|
||||
|
||||
def upsert_comdirect_giro(
|
||||
@@ -69,62 +123,24 @@ def upsert_comdirect_giro(
|
||||
|
||||
|
||||
def ingest_comdirect(path: Path, account_iban: str, account_currency: str):
|
||||
data = reader_comdirect(path).get("Girokonto")
|
||||
assert isinstance(data, list)
|
||||
with get_session() as session:
|
||||
for row in data:
|
||||
upsert_comdirect_giro(
|
||||
session,
|
||||
row,
|
||||
account_iban,
|
||||
account_currency,
|
||||
assert SOURCE_COMDIRECT
|
||||
data_collection = reader_comdirect(path)
|
||||
for account_name, data in data_collection.items():
|
||||
account = SOURCE_COMDIRECT.get_account(account_name)
|
||||
assert isinstance(account, Account)
|
||||
if account_name == "Girokonto":
|
||||
db_load_function = lambda session_, row_, account_: upsert_comdirect_giro(
|
||||
session=session_,
|
||||
data=row_,
|
||||
account_iban=account_.iban,
|
||||
account_currency=account_.currency,
|
||||
do_commit=False,
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
def reader_comdirect(path: Path) -> dict[str, list]:
|
||||
result = {}
|
||||
raw = _reader_raw_comdirect(path)
|
||||
for account_name, account_data in raw.items():
|
||||
parser = COMDIRECT_ACCOUNT_PARSERS.get(account_name)
|
||||
if parser is None:
|
||||
continue
|
||||
result[account_name] = list(map(parser, account_data))
|
||||
return result
|
||||
|
||||
|
||||
def _reader_raw_comdirect(path: Path):
|
||||
|
||||
with path.open(encoding="latin1") as fid:
|
||||
lines = fid.readlines()
|
||||
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
account_name = None
|
||||
result = {}
|
||||
for line in lines:
|
||||
if expect_next == EXPECT_LINE.ACCOUNT_NAME:
|
||||
account_name = re.findall(PATTERN_ACCOUNT_NAME, line)
|
||||
if account_name:
|
||||
account_name = account_name[0]
|
||||
result[account_name] = {}
|
||||
expect_next = EXPECT_LINE.BLANK
|
||||
elif expect_next == EXPECT_LINE.BLANK:
|
||||
if line == "\n":
|
||||
expect_next = EXPECT_LINE.HEADER
|
||||
elif expect_next == EXPECT_LINE.HEADER:
|
||||
header = re.findall(PATTERN_HEADER, line)
|
||||
if header:
|
||||
result[account_name] = []
|
||||
expect_next = EXPECT_LINE.DATA
|
||||
elif expect_next == EXPECT_LINE.DATA:
|
||||
data = re.findall(PATTERN_DATA, line)
|
||||
if data:
|
||||
result[account_name].append(
|
||||
{column: value for column, value in zip(header, data)}
|
||||
)
|
||||
else:
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
else:
|
||||
expect_next = EXPECT_LINE.ACCOUNT_NAME
|
||||
return result
|
||||
continue
|
||||
with get_session() as session:
|
||||
for row in data:
|
||||
data_validated = account.model.model_validate(row)
|
||||
assert isinstance(data_validated, account.model)
|
||||
db_load_function(session, row, account)
|
||||
session.commit()
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import date
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import Column, Date, Integer, Numeric, String
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
@@ -33,10 +34,36 @@ def _parse_date_german(date_str: str) -> date | None:
|
||||
return date(day=day, month=month, year=year)
|
||||
|
||||
|
||||
def date2str(value: date | None, default="--", date_format="%Y-%m-%d") -> str:
|
||||
if value:
|
||||
return value.strftime(date_format)
|
||||
return default
|
||||
|
||||
|
||||
def _parse_decimal_german(decimal_str: str) -> Decimal:
|
||||
return Decimal(decimal_str.replace(".", "").replace(",", "."))
|
||||
|
||||
|
||||
def decimal2str(value: Decimal) -> str:
|
||||
value = Decimal(f"{value:.2f}")
|
||||
int_part, _, frac = str(value).partition(".")
|
||||
groups: list[str] = []
|
||||
while len(int_part) > 3:
|
||||
groups.insert(0, int_part[-3:])
|
||||
int_part = int_part[:-3]
|
||||
groups.insert(0, int_part or "0")
|
||||
return f"{'.'.join(groups)},{frac or '00'}"
|
||||
|
||||
|
||||
type Csv2DictRow = dict[str, str]
|
||||
type Csv2Dict = list[Csv2DictRow]
|
||||
type CsV2DictMulti = dict[str, Csv2Dict]
|
||||
|
||||
|
||||
class ExportBaseModel(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class BaseModelDb(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
+40
-4
@@ -1,15 +1,22 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field, field_validator
|
||||
from pydantic import Field, computed_field, field_validator
|
||||
from sqlalchemy import Column, Date, ForeignKey, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from src.constants import IBAN_PATTERN, Currency
|
||||
from src.models._common import BaseModelDb, IngestdbBase, Sign, _hash
|
||||
from src.models._common import (
|
||||
BaseModelDb,
|
||||
ExportBaseModel,
|
||||
IngestdbBase,
|
||||
Sign,
|
||||
_hash,
|
||||
date2str,
|
||||
)
|
||||
|
||||
|
||||
class B4wExport(BaseModel):
|
||||
class B4wExport(ExportBaseModel):
|
||||
id: int = Field(alias="Id1", description="internal B4w ID")
|
||||
account_iban: str = Field(alias="OwnrAcctIBAN", pattern=IBAN_PATTERN)
|
||||
account_currency: Currency = Field(alias="OwnrAcctCcy")
|
||||
@@ -32,6 +39,35 @@ class B4wExport(BaseModel):
|
||||
)
|
||||
notes: str = Field(alias="Notes")
|
||||
|
||||
def parameters_to_dict(self) -> dict[str, str]:
|
||||
return {
|
||||
"Id1": f"{self.id}",
|
||||
"OwnrAcctIBAN": f"{self.account_iban!s}",
|
||||
"OwnrAcctCcy": f"{self.account_currency!s}",
|
||||
"ValDt": date2str(self.date_valid),
|
||||
"BookgDt": date2str(self.date_book),
|
||||
"Amt": f"{self.amount!s}",
|
||||
"AmtCcy": f"{self.amount_currency!s}",
|
||||
"CdtDbtInd": f"{self.sign!s}",
|
||||
"RmtInf": f"{self.reference}",
|
||||
"RmtdNm": f"{self.other_name}",
|
||||
"RmtdAcctIBAN": f"{self.other_iban}",
|
||||
"Category": f"{self.category.to_str() if self.category else ''}",
|
||||
"Notes": f"{self.notes}",
|
||||
}
|
||||
|
||||
def parameters_to_str(self):
|
||||
parameters = ", ".join(
|
||||
f"{key}='{value}'" for key, value in self.parameters_to_dict().items()
|
||||
)
|
||||
return parameters
|
||||
|
||||
def __repr__(self):
|
||||
return f"B4wExport({self.parameters_to_str()})"
|
||||
|
||||
def __str__(self):
|
||||
return f"B4wExport({self.parameters_to_str()})"
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def hash_id(self) -> str:
|
||||
@@ -56,7 +92,7 @@ class B4wExport(BaseModel):
|
||||
return category
|
||||
|
||||
|
||||
class Category(BaseModel):
|
||||
class Category(ExportBaseModel):
|
||||
name: str = Field(pattern=r"[^:\r\n]+")
|
||||
parent: Category | None = None
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field, field_validator
|
||||
from pydantic import Field, computed_field, field_validator
|
||||
from sqlalchemy import Column, Date, String
|
||||
|
||||
from src.models._common import (
|
||||
ExportBaseModel,
|
||||
IngestdbBase,
|
||||
_hash,
|
||||
_parse_date_german,
|
||||
@@ -12,7 +13,7 @@ from src.models._common import (
|
||||
)
|
||||
|
||||
|
||||
class ComdirectBase(BaseModel):
|
||||
class ComdirectBase(ExportBaseModel):
|
||||
date_val: date | None = Field(
|
||||
alias="Wertstellung (Valuta)", examples=["07.09.2026"]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.constants import IBAN_PATTERN, Currency
|
||||
from src.models.b4w import B4wExport
|
||||
from src.models.comdirect import ComdirectCreditCard, ComdirectDepot, ComdirectGiro
|
||||
|
||||
|
||||
class Account(BaseModel):
|
||||
name: str
|
||||
model: type[B4wExport | ComdirectGiro | ComdirectCreditCard | ComdirectDepot]
|
||||
iban: str | None = Field(pattern=IBAN_PATTERN, default=None)
|
||||
currency: Currency | None = None
|
||||
|
||||
|
||||
class Source(BaseModel):
|
||||
name: str
|
||||
source_path: Path
|
||||
accounts: list[Account]
|
||||
|
||||
def get_account_names(self) -> list[str]:
|
||||
return [a.name for a in self.accounts]
|
||||
|
||||
def get_account(self, name: str) -> Account | None:
|
||||
return next((a for a in self.accounts if a.name == name), None)
|
||||
@@ -0,0 +1,51 @@
|
||||
Id1,OwnrAcctIBAN,OwnrAcctCcy,ValDt,BookgDt,Amt,AmtCcy,CdtDbtInd,RmtInf,RmtdNm,RmtdAcctIBAN,Category,Notes
|
||||
546,BB94K6C321C502O,GBP,2025-12-29,2021-05-31,966249079068317412764571542407919514412758243437140957631412514241265507048511272714647319346008254.45380506761321740967397160412882982325923639777828329138314768657367549356366752060752331132,GBP,DBIT,xobiDcwzRKxgCdlMhOqA,tKVeYnvYCbpbogKulljk,TDgXpTthOVshHpNKRvbQ,Beruf:Gehalt:super Firma,WGCaHsVAXWVeCLsPbdrN
|
||||
3130,CY01AL0ZPFKXL0Z,CHF,2020-04-27,2025-04-02,-91380510785950845491826614519754504907024428700812006803291502570873.211019996196381685494424904026971409543,EUR,CRDT,aYEtztGZMJkkjbGNhrGl,CbvOzQGjcTWWXPrKfyjp,nOBawCuxZMLTiFKuZnsx,,MVznQDrIqmCmYrTDhBbp
|
||||
5356,AE43DPLDC66ENP6,USD,2026-01-09,2020-03-27,12495147566547030449243528706787437899952753255929949.510436466181518004685,EUR,DBIT,lckOKAAYRGuzCUqnXbfW,vOGbcayDxBlnejGwfLjA,xxjAuYKPhMmqaMfZDhAw,Lebenshaltung:Lebensmitteleinkäuft,iwUPRIJRxPSlRXcWDWNo
|
||||
5605,NS44V0K3D40LZEC,GBP,2021-12-22,2021-06-05,0.5943269284524336922642283862359244002375250667,CHF,CRDT,joJcLPuIFvxHxnpTMRZS,LRMmWOiXpcpNkZNCotyG,WawuMyUUpqZRmIWRpzOF,Freizeit,AUSRXmzbzyZwBREOZOgi
|
||||
9030,UL50MIF1W1KAS3C,USD,2024-03-13,2021-03-07,94229449126346152407566499816383306669237.8035079624692824996218720107585445554234173,USD,CRDT,AcQtetkWOZpHsvpFAFLY,sIieEkhvUFSOsLcLPzIq,nUOVSAvyyjaXaGrtwHkQ,Freizeit,xdNHKUVfoqzfwCykZIvd
|
||||
5139,KN98I6KMGOBJGDQ,GBP,2021-01-08,2021-12-18,-15902928538656294489371181513645784480574990906804212434837506917428638594538599376635051278636.78520642113315033999770538105015205921897385545146693383944328,USD,CRDT,lMrbuAYxXwuHurNyFYzg,PVoYOsiYrgiycTpYqwaq,EaPFovWogTiCIIVqPnHZ,Beruf:Gehalt:super Firma,rZVnmZqzUaqgrTejRQBv
|
||||
3262,VP35ANQ489KSRBJ,CHF,2022-02-11,2022-12-15,1456.3272931,EUR,DBIT,ZSDukJoElAhJnbqxCemY,gQZioaMYeNkeDIWUJQOJ,DaRRlkzDhfzwgqsmyKfI,,RmcCnGYIJwwXhUoDebtx
|
||||
6370,EG06AML3VX70NBZ,CHF,2020-10-30,2026-06-24,-85985373736852359001228845964.12923,GBP,CRDT,nGcVCPmxqcCWOwLCqNcL,ELTpZKLLPxtkBQpFfcXO,QqCDEBwHcqhYqNmrIjRJ,Lebenshaltung:Lebensmitteleinkäuft,MlltKALcNRksLbrBdqHB
|
||||
9610,LS00DDSRWKI2GCN,CHF,2022-12-24,2023-11-13,111302702519553016947918391140.83336651886149677841069515026199226910390124216959835264362430572949455458094205858204009469,GBP,DBIT,rIqXIlJHQUmNryiFhZgl,fmlShtEaPYxDxLPLVFtA,ZuEmBQavkZkliTgQslkX,Beruf:Gehalt:super Firma,QxxGfOhHEKlRIBtxLBzN
|
||||
7958,IM49W0MERUPAEYL,GBP,2024-09-23,2026-08-07,997654494541331560967.23894678707663633993031462271276158599265860951730613416835071421468,EUR,CRDT,lIFaiRDOHpPsLZYMvSLL,COjPCyRHmQJAJjXpHtoc,wLGNwAmIoguSFiPOwhop,Beruf:Gehalt:super Firma,wIVvfgWAGpXaNtmCMdtl
|
||||
81,NB13I5PF8RT1KBF,EUR,2023-10-01,2025-10-14,-623.171747883897331930600196592034302396711514091988656250527836008950413955561,EUR,CRDT,gKujLbFKPTJWNHCwLEbP,BNNSobmkOJYvruTmVpsY,JFTexNOzIHpIDYxFyxXC,Lebenshaltung:Lebensmitteleinkäuft,EXRfMRisDxjfvTFbbScl
|
||||
4306,MA16EMD5TXB6W2Q,CHF,2022-03-09,2022-07-04,1967316692371140884668.697876470482728723376342302791065723059022794527986321223057196172445638187219626476457610180,EUR,CRDT,PxRyYSeHHCnLsOGvkJBR,tdUmPUqXPyVAwcMonTOz,FkhuAmZMFEYHXfIRbYNa,,hoOqFYFhQZMNFflTTRzr
|
||||
1340,QC15X8QJ925LIIZ,USD,2023-05-13,2024-10-19,51002714193280774680213206683817944775146555520957438367304201512763096320304094279.5917694840241093438785611670208852758416238660282666099607713108189846992146195070603042071073,USD,CRDT,KXmqOKVhWFhNyeTrYcqi,xrXHIHUrhVotQHkfTSbR,XftIqQPwfIZaQWPgfvTq,Beruf:Gehalt:super Firma,aIBfbrbYXZILZipenuQs
|
||||
6971,IU89H1O656FOARG,CHF,2023-01-05,2023-01-26,-635472806525340497640508823188167.73094955967757797936541852850962382275616205624981776791984877714957051928106426532873075,EUR,CRDT,aBFnGYtfSndKKDcuKbOA,mFDeQxsPDxRwgSxHoPar,rWitPpvexQZfYaRknvVZ,Lebenshaltung:Lebensmitteleinkäuft,SdKWfeCRdmIuQtpYXpcP
|
||||
5765,YO97BHIS7F13W5T,CHF,2025-04-27,2024-09-23,-84170840.062065298147641969564638230620262747954,EUR,DBIT,PQzlBfLSMjHPhmzCNWlW,OAuifNsHLzDRYOshTltg,SrdmTqlAcjYZqspXUqWl,Beruf:Gehalt:super Firma,gdyHSZQRVGVDljyWukxF
|
||||
873,CU84CRHLBLDVPG4,USD,2024-09-10,2021-04-05,31708414063036408560039191005.628517576703091956518065559718890786318411169242277168731009729,EUR,DBIT,jEqWELcluqYzeoFKWrSv,rMeRFYSEneKpjTQxTAWE,irpXsNFkucPYaXhtiGVU,Beruf:Gehalt:super Firma,tDYCusMlvSzPxJtAoGwU
|
||||
7512,OK88REZY7LC3ZJO,USD,2024-06-20,2024-02-12,-91309800414850316424146855043265998301745409273389966051295090800556208516373.6106630,EUR,CRDT,RjKWAFvwkGwehDeVgAMe,ZBCofGxOxAuhCNdoRwJK,CBjHBmiutfFjnNqGNsys,Freizeit,ibeMySwyUuaKJmopdFeo
|
||||
468,ZI7268MWFCVKJET,GBP,2021-07-08,2023-08-04,-9566140937946527477623537138113287213.7764645148994252143659337484894589906652837718944384583356873367,GBP,DBIT,dHnVUadgIXaQokZAaddZ,MJpKJyJXAGVFHGXqyeLb,jgWKQYgfzCjzpWDqSfMV,Beruf:Gehalt:super Firma,rRrGzQHiNWxftPiobuBl
|
||||
6125,VX319YHC45XB9FI,CHF,2024-04-08,2023-11-02,-404943589.503,EUR,CRDT,DLgsRpbgvYUZiFwwfxYX,NFCMuqKchwfGolxUoszx,wToRonKwFFOWrnmfMWZd,Beruf:Gehalt:super Firma,EiYGrzzlsMweQPlFHdlx
|
||||
5352,WI30AMBG4K8GC5M,USD,2022-07-28,2023-07-12,298801846977579969716883410099510711907273580263884196580644541848777146559224.91128608265378630536063943513079726365933334413452853303576298418192825,GBP,DBIT,BkBNoWLKZmNptdlkcdFy,jdCcnjNmSYMVZYBAqXpk,KdVUoLgaiQoorPtFXfuD,Lebenshaltung:Lebensmitteleinkäuft,aPilIRVyMZhDaLreCtVB
|
||||
8157,AS89HYB3PJOCKGQ,USD,2021-12-11,2021-05-02,-5383257045022212194476332790.58249908408715153448695588386599010825479811059729154822,USD,CRDT,hLscFzoYamBLLTCOLtJX,mJzlPJMFlwXsvDWCdPEX,zcgAipzPSDbNvDgsUsdf,Lebenshaltung:Lebensmitteleinkäuft,KzpTvMfREqDcraLsnAFa
|
||||
8562,XF55JJHCFY9VFPN,EUR,2021-10-06,2023-03-07,58513152657357786429578134004715672662808018369796246403461323977035786109578790774134514968087.32196519574363119760206835296320588360455621,EUR,CRDT,WwygiOMjDsxlmBrgMBUD,CPkjRvCOcTGVQRxdVSco,WEmukvnPtZyCmopsKLeB,Freizeit,vNxjYBDrcjIJXRYsioVn
|
||||
6932,PK53O1QFHWMM80N,CHF,2021-11-22,2022-04-29,-35687720973389008791120348643747347909777063417264503383154431851328499756311481892.525588482723067939754169870785253637544209574930786950647614924757554933127740331928,USD,DBIT,ocyWAbkFrEGhLKcqIUrJ,eIdWToAESlHQGGnykQZI,urMJiqlsVuLqGbGmIOdR,Beruf:Gehalt:super Firma,wbVGbcIRqoAyhXPQFmbn
|
||||
5603,LM99PWTKWNVKFUK,CHF,2020-10-03,2024-10-23,137912574664116210905504437194561515849372037.5300228797878339289857585366614065212461747,CHF,DBIT,mJHgXdjzfGTiqdPFYKJu,iCPXYqUtKwBTilCrsJTM,AqLdwtlWBsQmQaBvwHii,Lebenshaltung:Lebensmitteleinkäuft,DVEvjvPzjBahqIEOddlq
|
||||
7707,LP36QSQ8LN9F6U8,EUR,2020-01-26,2026-07-22,20617377959559934081335048761128552951261867125124238167249671785612681.51317409949621954528827764556416450523039696133111537499104315164348776636640613432,GBP,CRDT,IcRLxekFBpooSGzJqqGw,rCFLSzlzvLBdEpXFPZfV,vkMfGTUYKzrINNryKNJN,,tjNBGXokHzSQvBVABxCV
|
||||
7964,WB16POIGIKIRSRB,GBP,2025-01-15,2026-07-14,19212833314539080562967961976059399140703968646971623411156202797393305194593.881945095397465505661922398,USD,DBIT,CoxjaXrcDJVITkyHjZzv,IunvUBVgzwgJVzAvOyKQ,BcYtrPUKMrJkEKNNnYyD,,awUfzbbQZVwNaLaTiFsr
|
||||
4725,AT598204ZYZODV1,EUR,2024-03-25,2024-08-09,82856650729233542595012846123790016186861935974808282995711867343684454016512697494.88917524421022914453765603187655206018877538527053112,EUR,DBIT,xlCBJxJOxqvLAjgICrbX,msAsDTuxgXrTntkliYmb,FXyFAuyPwdvXjTTNcPrz,Lebenshaltung:Lebensmitteleinkäuft,gwHdLpOEMZgALigVFmpM
|
||||
6050,TY84APSJTGLW0Y7,GBP,2020-09-04,2025-08-15,-3472009449932706821864839468227645291697670610604942221835405160979793916196159877775819802166678286.924897290769355741386357676998033855577136669559598846,GBP,DBIT,sqcQIypoDiHETEPLtswm,bkRKycwiqnHBrCWFDWvp,IsMCNagZuWQgjbHotLkZ,Lebenshaltung:Lebensmitteleinkäuft,PmxAqaLvtdKHOqufnPEr
|
||||
1636,TT56OLKXD9FFLHW,EUR,2022-04-05,2025-01-21,-8700251749693498471853131195688590000252839147762542676728724966.70275220280,CHF,CRDT,LYftAZAxwAbsKqKJJDox,AutZAoZFOVswxydiSajl,peXIkhZxuXokaHbIHulP,Freizeit,qAADExaKKWlrrLTDOeJd
|
||||
7753,BS67UNQVFAG259C,CHF,2024-06-05,2021-06-24,24861162756159054957.670585366670739666042308388630102242932146348848932417907909353461828706673705,EUR,CRDT,xZhCgZOvnVzrCDKLqbhK,XhDZhAWtaSbEctUEYLjT,aClycbHxjTAgkNnTRcuY,,LmeGwybyUMOrxHkjgLaZ
|
||||
6189,ZZ871MTLUU9A1RU,CHF,2021-08-01,2023-03-17,-409780077475199424796148695159372908162375055189181880910638677.248670538597146440670239645579725174792942268017121149370963751622201496,EUR,CRDT,GQlNICMrebnglDvBDTYS,CXDqKkEcjnJngYAqpByV,NHVCfnmSrnFolUjPSEIr,,uwEFhSBDVdAPtdhJavhW
|
||||
9624,AU87XCMRZZWGH86,USD,2024-09-02,2022-09-19,-442193693537141866795259204406415934110882954978333632301457685242868631049543099431520126945516367.745400551449,EUR,DBIT,cbaGAkeofJGsqaiNUEIY,ljZDuEzncPYZLDWNMgnH,EcVuFQSISKGSBDNIdqXJ,Beruf:Gehalt:super Firma,KhFiMeuprVqYAZtZQwIp
|
||||
9203,FX68NTN2D10VVKO,GBP,2022-02-15,2024-06-23,-534572334.4012791130107478240377375826129655674355510806820651340789640,USD,DBIT,tNihPvPVLoZiwWmdZZRZ,zJGOcjBqCiLbirDJCgVR,oeFcARYRdznzUieqiHxt,Lebenshaltung:Lebensmitteleinkäuft,ihcfdoHuUNdBWRSOyeBB
|
||||
3927,WH16E7SHB62XKIH,GBP,2023-10-04,2023-01-05,415593598910615971016549932285154.643848021414472037085778014035161865336120601218617113853923085787470877370,EUR,CRDT,MreRryaDFIhSFXkthwko,AqECnwyKoevMnedwdaFc,DXMnEnltpMbzNPWinMrk,Freizeit,IDiYkQfxVozPBqInPIQV
|
||||
6283,HU452U8CMEYLRNE,CHF,2022-04-25,2025-03-24,-31056127574668984711914.148062642678712152287686837162829872960394642611046974293342,CHF,CRDT,uvRVCtUcGqAYppkZfxar,VvKDwohUZmOQWHpTbbox,rGrLuSeVEZhbtgGBcOFx,Lebenshaltung:Lebensmitteleinkäuft,mNqLNmnFbTXThCWaoBpa
|
||||
4041,AR48BWK3T5RZ570,GBP,2025-10-10,2020-07-06,1812531959.04051886610531349,EUR,DBIT,VICxZqioNNFBXEnZvvHZ,HHKlheyBNtageACANISB,mPbiYFXCzowXbuRckcJU,Freizeit,jAtyPevPSKJdGTVjowmo
|
||||
7217,OZ17J7ZD7YG7O1A,USD,2024-01-23,2024-06-19,-968844607026826575302933681105715018281543501601087944362850708075305.66200706173538352524957865510877,GBP,CRDT,RQxftSphrPpwMKVlnKpn,uqNrspSFVXYYPveWyuNv,CWyQeuLcZPAuGlNWjGdM,Lebenshaltung:Lebensmitteleinkäuft,HAyMDEDnKqtDgfIYZDiC
|
||||
2615,WX91BRBHNDBXKU0,EUR,2021-10-30,2025-11-23,-8343466178334617252604602.569949636202667336077288532506921508691215873161097,CHF,CRDT,RyhUcmsrSAXNUaXevPMB,bFIZFcQCTivRZcMYhJAx,mfRHcvsWGYIMPHtWOYpm,Lebenshaltung:Lebensmitteleinkäuft,PHOtyKuWgmyZaxJYQPjO
|
||||
251,ZJ430BMO81CDA44,EUR,2025-08-08,2022-04-02,-944177666499883169717087768179297037355028367029688127.633698125531,USD,DBIT,lUaArRWPnlxRwoWKSDyZ,jFSXDqiPxpdkDkjduezx,ndEGQjdezYvakfXmSeer,Freizeit,NbDQJseGHjiMLMFmhlQX
|
||||
8432,RG9882TQXT40HDH,GBP,2020-05-29,2026-06-29,-4620473059978062834290693674666409952444643118783437106239403117968074841905810680891909.179130304762746994760945084576,GBP,DBIT,QakUGBIAuAmdabucXlOf,WlRItduFSMEjCwmkbVPV,haAnIWAPZnqWKJHZubtA,Freizeit,tZHnaMEcNMMxpvFZAdjx
|
||||
9952,JF78B330IL8AKEQ,CHF,2023-03-16,2023-01-31,-4493693464311613709632110286395250278421205912044580776714732064115337203268.547530,GBP,DBIT,hTIWMdfJKdCycCtcVZIa,OABZhgwiXUqfXMmSklHk,GpItXzAVpjsAvDFHxtHP,Freizeit,QbKsSKahDgJtFJQcPJyq
|
||||
3604,NJ18VUU9VH26T0A,GBP,2026-08-11,2020-04-09,0.64973001968427147068,EUR,CRDT,qupJQwFywzUjbEdwPDUA,uyOHMYgdPtMhcVdnxCZx,cLMmgjuVZtoDOwxoKrFt,Lebenshaltung:Lebensmitteleinkäuft,XQYYbtGKlEBIBnEWqecs
|
||||
7263,KT85HBZNJL59FFT,USD,2022-11-27,2024-07-15,-37374807.958562351366178447898590057117071635996571407457660485745838424652255349422449887484,GBP,DBIT,uHcJnYVJYKDQMoFMjGXC,PQGdgBCLypSRkHiJYdev,rGwrTRHsFHjLPYhFpMTJ,,dIxwCBlTvNJpYULlJAaS
|
||||
9675,BJ715E6CD1OIU6T,CHF,2020-12-16,2024-01-07,-5040941689111955157468104725713611935085598625070764074521071414015940351382113590909197304063575.4515354590973595602470431141798115590834959037133034277462703823,GBP,DBIT,HGLztVTGOpGnChJsjprC,qLFITvGssIbHlFBfuZyg,SWQbyfBlwfdSCnoAtwmn,Beruf:Gehalt:super Firma,jtabNPVEZOlBVnaIvamZ
|
||||
2631,BB0000NISDKBWM1,USD,2023-12-09,2021-09-25,35870065450695.1976,GBP,DBIT,gtwRCGANaKlcNLbLqXCW,qhkoHtHuLrwxdDjUitUM,SMAvrVhBeuNflTTfByZh,Lebenshaltung:Lebensmitteleinkäuft,JodnUWDKlXidVgYIIkZo
|
||||
7105,FM81K0IHYG2B1K8,CHF,2022-07-17,2021-04-08,7605971335219411809496337191273512429.19144246140951276630353263005965894263583159362498576671567919,GBP,DBIT,bvkiJapOkQoCqDQaeSsj,wcUfBTvFUoxaSRsaNbOk,wzNzdPXmfLEXEgEvtXBP,Lebenshaltung:Lebensmitteleinkäuft,mQFwQMQRmhadGjzUhyIP
|
||||
4791,XO29KPUGVXKV7P4,GBP,2025-05-09,2025-03-06,-3047323302463055726328888340075326228246153418725869094694662399.9369795962317797302779791691824533,CHF,CRDT,WPBXOTiXiJFuZyjStJxI,bKFooYlWkOIHkeDlcJxu,OXIINcOhpetOqPvcLwkf,Freizeit,jHrZgYXhlOxQlibgCCRq
|
||||
9872,NX87N16NYF88GY3,CHF,2024-09-17,2022-11-07,-7503175603096494776785070745277206265109071257232396346239801093970295294378754146262745092564314724.58147937995295429167115163695901432938,USD,DBIT,PskmmmqmEWvvjVwdTbpa,IKnFvpHdkVECQXMJgPEb,XgxsPRqDemqlLkHurTtC,Lebenshaltung:Lebensmitteleinkäuft,iDHDfFpVGofjugPlXzMa
|
||||
2685,DI50CT1EAUXO5ZQ,CHF,2020-11-27,2022-08-17,804255947854847664075204915880058282080805548244876707562721818851111658361963075718333712782.054580103972864189422158609687925786112724117046925451325120196326468662088148238188624,GBP,DBIT,EUSdiqhdVykkRxbeknBL,aUXqpaJrSZvchOtFcDZK,ruepxYDTWMuSxXoNiEBQ,Beruf:Gehalt:super Firma,tSCewecchqoQJuQXVXEr
|
||||
6119,MJ453EJXQAF5KF9,USD,2020-06-02,2020-07-17,18687868432897828853760232671664003846966802581954654987871.542014558313629353382101420030766,EUR,DBIT,HhAovEIGHJoxAVgFeCVG,iUoROrwSAhKLpKuJqQZl,xHvmkmSMiiuCtJVFZDpN,Freizeit,GgDhdWxjgwwJUmNQOEzl
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
@@ -29,3 +31,31 @@ def test_b4w_export_account_iban_to_long():
|
||||
def test_b4w_export_account_iban_to_short():
|
||||
with pytest.raises(ValidationError):
|
||||
_ = B4wExportFactory.build(OwnrAcctIBAN="DE123")
|
||||
|
||||
|
||||
def test_b4w_export_parameters_to_dict():
|
||||
data = B4wExportFactory.build()
|
||||
params = data.parameters_to_dict()
|
||||
expected = sorted(field.alias for field in B4wExport.model_fields.values())
|
||||
assert sorted(params) == expected
|
||||
assert "OwnrAcctCcy" in params
|
||||
assert all(isinstance(value, str) for value in params.values())
|
||||
|
||||
|
||||
def test_b4w_export_parameters_to_str():
|
||||
data = B4wExportFactory.build()
|
||||
params = data.parameters_to_str()
|
||||
components = [s.strip() for s in re.split(r",\s*(?=\w+=)", params)]
|
||||
param_names = [component.split("=")[0].strip() for component in components]
|
||||
param_values = [component.split("=")[1].strip() for component in components]
|
||||
assert "OwnrAcctCcy" in param_names
|
||||
assert all(s.startswith("'") and s.endswith("'") for s in param_values)
|
||||
|
||||
|
||||
def test_b4w_export_repr_round_trip():
|
||||
data_batch = B4wExportFactory.batch(size=10)
|
||||
assert isinstance(data_batch, list)
|
||||
for data in data_batch:
|
||||
data_str = str(data)
|
||||
data2 = eval(data_str)
|
||||
assert data == data2
|
||||
|
||||
Reference in New Issue
Block a user