boekhouding-beancount/import
2025-06-27 15:20:37 +02:00

154 lines
4.3 KiB
Python
Executable file

#!/usr/bin/env python3
from os.path import basename
from beangulp import Ingest
from beangulp import mimetypes
from beangulp.importers import csvbase
from beangulp.testing import main
from beancount.core.data import Transaction, Posting
from deelnemers import deelnemers
participants = deelnemers()
class RaboAmount(csvbase.Amount):
def __init__(self, col):
super().__init__(col, subs={"\\+": "", ",": "."})
class Importer(csvbase.Importer):
encoding = "iso-8859-15"
date = csvbase.Date("Datum", "%Y-%m-%d")
payee = csvbase.Columns("Naam tegenpartij", "Tegenrekening IBAN/BBAN", sep="; ")
narration = csvbase.Columns(
"Omschrijving-1", "Omschrijving-2", "Omschrijving-3", sep=" "
)
amount = RaboAmount("Bedrag")
balance = RaboAmount("Saldo na trn")
def identify(self, filepath):
mimetype, encoding = mimetypes.guess_type(filepath)
if mimetype != "text/csv":
return False
with open(filepath, "rb") as fd:
head = fd.read(1024)
return head.startswith(
b'"IBAN/BBAN","Munt","BIC","Volgnr","Datum","Rentedatum","Bedrag","Saldo na trn","Tegenrekening IBAN/BBAN"'
)
def filename(self, filepath):
return "rabobank." + basename(filepath)
def guess_contra(entry):
posting = entry.postings[0]
def have_keyword(*keywords):
l_description = entry.narration.lower()
return any(kw in l_description for kw in keywords)
if entry.payee == "Kosten" or (
entry.payee == "Rabobank; NL89RABO0111741386"
and entry.narration.startswith("Kosten")
):
return "Uitgaven:Bankkosten"
if entry.payee == "Stichting Bitlair; NL09RABO3159222187":
return "Activa:Spaarrekening"
if (
entry.payee == "Symbiose Beheer B.V.; NL84ABNA0630587221"
and posting.units.number < -3400
):
return "Uitgaven:Huur"
if (
entry.payee == "Stichting EventInfra; NL59RABO0312574800"
and entry.narration == "Verhuur opslagruimte "
):
return "Activa:Debiteuren:EventInfra"
if (
entry.payee == "JONGE ONDERZOEKERS AMERS; NL30ABNA0119774844"
and 590 < posting.units.number < 700
):
return "Activa:Debiteuren:DJO"
if (
entry.payee == "Furthermore B.V.; NL06REVO8135470894"
and 600 < posting.units.number < 800
):
return "Activa:Debiteuren:UnicornDept"
if entry.payee == "Gebr. Schurman GmbH; DE68441600141300702500":
return "Uitgaven:Bar:Mate"
if entry.payee == "Sligro ZB 5037":
return "Uitgaven:Bar:Snacks"
if entry.payee == "STATIEGELD NEDERLAND; NL59ABNA0100073913":
return "Uitgaven:Bar"
if entry.narration.startswith("Decla") and posting.units.number < 0:
return "Passiva:Declaraties"
if have_keyword(
"deposit",
"revbank",
" bar ",
"kassa vulling",
"desposit",
"(RB QR)",
"rev-bank",
" bank",
"elena transfer",
"barsaldo",
):
return "Passiva:RevBank"
if entry.payee == "Stichting Mollie Payments; NL70CITI2032329018":
return "Passiva:RevBank"
for p in participants:
if (
hasattr(p, "maandbedrag")
and hasattr(p, "ibans")
and p.maandbedrag == posting.units.number
and any(iban in entry.payee for iban in p.ibans)
):
return f"Activa:Debiteuren:Deelnemers:{p.nickname.title().replace('_', '')}"
return None
def classify_hook(extracted_entries_list, ledger_entries):
def _classify(entry):
if not isinstance(entry, Transaction):
return entry
contra = guess_contra(entry)
posting = entry.postings[0]
entry.postings.append(
Posting(
contra or "Inkomsten:TODO",
-posting.units,
posting.cost,
posting.price,
None if contra else "!",
None,
)
)
return entry
return [
(filename, [_classify(entry) for entry in entries], account, importer)
for filename, entries, account, importer in extracted_entries_list
]
if __name__ == "__main__":
importers = [Importer("Activa:Betaalrekening", "EUR")]
hooks = [classify_hook]
main = Ingest(importers, hooks)
main()