From 107eac58e9f492d79b770ccf42b08f62096d52dd Mon Sep 17 00:00:00 2001 From: JonDerThan <32145552+JonDerThan@users.noreply.github.com> Date: Sun, 18 Jan 2026 19:53:40 +0100 Subject: [PATCH] feat(C24): add mapping for C24 --- csv2ofx/mappings/c24.py | 95 +++++++++++++++++++++++++++++++++++++++++ data/converted/c24.ofx | 71 ++++++++++++++++++++++++++++++ data/test/c24.csv | 4 ++ tests/test_cli.py | 5 +++ 4 files changed, 175 insertions(+) create mode 100644 csv2ofx/mappings/c24.py create mode 100644 data/converted/c24.ofx create mode 100644 data/test/c24.csv diff --git a/csv2ofx/mappings/c24.py b/csv2ofx/mappings/c24.py new file mode 100644 index 0000000..31d41dc --- /dev/null +++ b/csv2ofx/mappings/c24.py @@ -0,0 +1,95 @@ +"""Mapping for the German C24 bank. + +The C24 bank supports sub-accounts. Every transaction in the CSV file has a +user-configurable account name "Kontoname". Transactions from all sub-accounts +are stored chronologically in this list. The transaction list includes both +intrabank transfers between these accounts ("internal") as well as all other +transactions ("external"). + +OFX files have a special way of identifying intrabank transfers `` +but the file can either only include intrabank transfers or normal ones. The +expected behaviour would be to list all transactions, with intrabank and normal +transactions having the same formatting. In some cases however, the user may +want to have a properly formatted list of intrabank transfers as well. Toward +this, the environment variable `TRANSACTIONS` can be used. + +Possible `TRANSACTIONS` env var values: +- "all" (default): Include all transactions as normal statements. There is no + way to differentiate intrabank transfers. +- "internal": Only include intrabank transfers. +- "external": Only include normal transfers. Accounts will have weird balances. +""" + +from __future__ import annotations + +from operator import itemgetter +from os import environ +from sys import stderr + +_INCLUDE_INTERNAL_TX = True +_INCLUDE_EXTERNAL_TX = True +tmp = environ.get("TRANSACTIONS", "all").lower() +if tmp == "internal": + _INCLUDE_INTERNAL_TX = True + _INCLUDE_EXTERNAL_TX = False +elif tmp == "external": + _INCLUDE_INTERNAL_TX = False + _INCLUDE_EXTERNAL_TX = True +elif tmp != "all": + print("TRANSACTIONS: invalid environment variable value!", file=stderr) + + +# "Pocket-Umbuchung" is a manual transfer between sub accounts, "Sparen" is when +# the user configured automatic transfer rules at certain events. +def _is_split_tx(tx: dict[str, str]) -> bool: + tx_type = tx["Transaktionstyp"] + return tx_type in {"Pocket-Umbuchung", "Sparen"} + + +def _get_split_recipient(tx: dict[str, str]) -> str | None: + if not _is_split_tx(tx): + return None + split_recipient = tx["Zahlungsempfänger"] + return split_recipient + + +def _filter_tx(tx: dict[str, str]) -> bool: + is_split = _is_split_tx(tx) + if is_split: + return _INCLUDE_INTERNAL_TX + else: + return _INCLUDE_EXTERNAL_TX + + +# We MUST NOT use the "Kontonummer" as account ID, because for transfers between +# sub accounts there is no recipient "Kontonummer". By using the default +# generated hash of the sender name "Kontoname" and the recipient name +# "Zahlungsempfänger", internal transfers can be associated correctly. +mapping = { + "has_header": True, + "currency": "EUR", + "delimiter": ",", + "bank": "C24", + "account": itemgetter("Kontoname"), + "date": itemgetter("Buchungsdatum"), + "parse_fmt": "%d.%m.%Y", + "amount": lambda tx: tx["Betrag"].replace(".", "").replace(",", "."), + "desc": itemgetter("Verwendungszweck"), + "payee": itemgetter("Zahlungsempfänger"), + "filter": _filter_tx, + # Setting this entry to anything other than `None` implicitly toggles the + # intrabank transfer output, which cannot include normal/external + # statements. Sets this getter only when not including these normal + # statements. + "split_account": _get_split_recipient if not _INCLUDE_EXTERNAL_TX else None, + # I don't like how the values below just get added to the memo. Feel free + # to re-include them. + # + # An automatic/manual categorization of the transaction. + # "class": itemgetter("Unterkategorie"), + # + # This seems to be a more human-readable name of the payee. In credit card + # statements, this may be the name of the credit card owner while the payee + # ("Zahlungsempfänger") is the name of the credit card provider. + # "notes": itemgetter("Beschreibung"), +} diff --git a/data/converted/c24.ofx b/data/converted/c24.ofx new file mode 100644 index 0000000..0e90db5 --- /dev/null +++ b/data/converted/c24.ofx @@ -0,0 +1,71 @@ +DATA:OFXSGML +ENCODING:UTF-8 + + + + + 0 + INFO + + 20161031112908 + ENG + + + + + + + 0 + INFO + + + EUR + + 78a1d6227c674fedd66d7165ce318750 + 6eea69609b81079b892c61e79829798b + CHECKING + + + 19700101 + 20260118 + + DEBIT + 20251205000000 + -123.45 + 50424a98e3219cee1726a03dd03b52f1 + My savings account + To be donated + + + + + EUR + + 78a1d6227c674fedd66d7165ce318750 + 011a79ab1760c0682e01cdf35eb5ed71 + SAVINGS + + + 19700101 + 20260118 + + CREDIT + 20251205000000 + 123.45 + 2021f0587851c9d9f868034f5ea0854d + My main account + To be donated + + + DEBIT + 20260105000000 + -50.00 + 80e455fe30ba0c2243aa64f0b0ce5a31 + Ärzte ohne Grenzen + Ihre Spendernummer 1234567 SEPA-Mandat 1234567-001 Vielen Dank für Ihre Spende + + + + + + diff --git a/data/test/c24.csv b/data/test/c24.csv new file mode 100644 index 0000000..af7fcf3 --- /dev/null +++ b/data/test/c24.csv @@ -0,0 +1,4 @@ +Transaktionstyp,Buchungsdatum,Karteneinsatz,Betrag,Zahlungsempfänger,IBAN,BIC,Verwendungszweck,Beschreibung,Kontonummer,Kontoname,Kategorie,Unterkategorie,Bargeldabhebung +Pocket-Umbuchung,05.12.2025,,"-123,45 €",My savings account,,,To be donated,,0987654321,My main account,Umbuchung,Umbuchung +Pocket-Umbuchung,05.12.2025,,"123,45 €",My main account,,,To be donated,FULL_LEGAL_NAME,1234567890,My savings account,Umbuchung,Umbuchung +SEPA-Lastschrift,05.01.2026,,"-50,00 €",Ärzte ohne Grenzen,PAYEE_IBAN,PAYEE_BIC,Ihre Spendernummer 1234567 SEPA-Mandat 1234567-001 Vielen Dank für Ihre Spende,"Medecins Sans Frontieres (MSF) - Ärzte ohne Grenzen, Dt. Sektion",1234567890,My savings account,Finanzen & Steuern,Spende diff --git a/tests/test_cli.py b/tests/test_cli.py index de7123b..ba7625d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -113,6 +113,11 @@ 'n26-fr.csv', 'n26.ofx', ), + ( + ['-o', '-m c24', "-e 20260118"], + 'c24.csv', + 'c24.ofx', + ), ]