Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions csv2ofx/mappings/c24.py
Original file line number Diff line number Diff line change
@@ -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 `<INTRATRNRS>`
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"),
}
71 changes: 71 additions & 0 deletions data/converted/c24.ofx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
DATA:OFXSGML
ENCODING:UTF-8
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<DTSERVER>20161031112908</DTSERVER>
<LANGUAGE>ENG</LANGUAGE>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID></TRNUID>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<STMTRS>
<CURDEF>EUR</CURDEF>
<BANKACCTFROM>
<BANKID>78a1d6227c674fedd66d7165ce318750</BANKID>
<ACCTID>6eea69609b81079b892c61e79829798b</ACCTID>
<ACCTTYPE>CHECKING</ACCTTYPE>
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>19700101</DTSTART>
<DTEND>20260118</DTEND>
<STMTTRN>
<TRNTYPE>DEBIT</TRNTYPE>
<DTPOSTED>20251205000000</DTPOSTED>
<TRNAMT>-123.45</TRNAMT>
<FITID>50424a98e3219cee1726a03dd03b52f1</FITID>
<NAME>My savings account</NAME>
<MEMO>To be donated</MEMO>
</STMTTRN>
</BANKTRANLIST>
</STMTRS>
<STMTRS>
<CURDEF>EUR</CURDEF>
<BANKACCTFROM>
<BANKID>78a1d6227c674fedd66d7165ce318750</BANKID>
<ACCTID>011a79ab1760c0682e01cdf35eb5ed71</ACCTID>
<ACCTTYPE>SAVINGS</ACCTTYPE>
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>19700101</DTSTART>
<DTEND>20260118</DTEND>
<STMTTRN>
<TRNTYPE>CREDIT</TRNTYPE>
<DTPOSTED>20251205000000</DTPOSTED>
<TRNAMT>123.45</TRNAMT>
<FITID>2021f0587851c9d9f868034f5ea0854d</FITID>
<NAME>My main account</NAME>
<MEMO>To be donated</MEMO>
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT</TRNTYPE>
<DTPOSTED>20260105000000</DTPOSTED>
<TRNAMT>-50.00</TRNAMT>
<FITID>80e455fe30ba0c2243aa64f0b0ce5a31</FITID>
<NAME>Ärzte ohne Grenzen</NAME>
<MEMO>Ihre Spendernummer 1234567 SEPA-Mandat 1234567-001 Vielen Dank für Ihre Spende</MEMO>
</STMTTRN>
</BANKTRANLIST>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>
4 changes: 4 additions & 0 deletions data/test/c24.csv
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@
'n26-fr.csv',
'n26.ofx',
),
(
['-o', '-m c24', "-e 20260118"],
'c24.csv',
'c24.ofx',
),
]


Expand Down