new files for beancount 3

This commit is contained in:
Visual Studio Web Coder
2025-08-17 17:20:23 +00:00
parent e908bad8a1
commit 0f3d48e0b9
7 changed files with 46 additions and 237 deletions

View File

@@ -1,15 +1,14 @@
# config.py
from beangulp import register_importers
from importers.csvbank import Importer as CSVBankImporter
import sys
import os
def main():
ledger_file = 'ledger.beancount'
# Voeg de map van dit bestand toe aan de Python-zoekpaden.
# Dit zorgt ervoor dat je importer gevonden kan worden.
sys.path.insert(0, os.path.dirname(__file__))
register_importers([
CSVBankImporter('Assets:Bank:Checking', 'EUR'),
])
from beangulp.testing import main as beangulp_main
beangulp_main(ledger_file)
# De importregel hieronder zal nu correct werken
from postbank_csv_importer import MyCSVImporter
CONFIG = [
MyCSVImporter()
]
if __name__ == '__main__':
main()

35
importers/csvbank.py Normal file
View File

@@ -0,0 +1,35 @@
import os
from beangulp import mimetypes
from beangulp.importers import csvbase
from beangulp.testing import main as testing_main
class Importer(csvbase.Importer):
# TODO Deutscher CSV header überprüfen: Posting Date ; Description ; Note ; Amount, Balance
date = csvbase.Date('Posting Date', '%Y-%m-%d') # ! date-Format anpassen
narration = csvbase.Columns('Description', 'Note', sep='; ')
amount = csvbase.Amount('Amount')
balance = csvbase.Amount('Balance')
""" Date,Posting Date,Description,Note,Amount,Balance
2025-08-01,2025-08-01,Grocery Store,, -54.20,1245.80
2025-08-03,2025-08-03,Salary,, 2500.00,3745.80
"""
def identify(self, filepath):
mimetype, _ = mimetypes.guess_type(filepath)
if mimetype != 'text/csv':
return false
with open(filepath, encoding='utf-8') as f:
header = f.readline()
# ! TODO
return header.startswith('Date, Posting Date, Description, Amount')
def filename(self, filepath):
name = os.path.basename(filepath)
return f'csvbank.{name}'
# ? Testing
__name__ '__main__':
# Test-Runner und CLI
testing_main(Importer('Assets:Bank:Checking', 'EUR'))

1
lib64
View File

@@ -1 +0,0 @@
lib

View File

@@ -1,164 +0,0 @@
from beancount.ingest import importer
from beancount.core import data
from beancount.core.number import D
import csv
import datetime
class MyCSVImporter(importer.ImporterProtocol):
"""A Beancount importer for CSV files"""
def identify(self, file):
return file.name.endswith('.csv')
def file_account(self, file):
return "Assets:Bank:PostbankGiro"
def file_date(self, file):
"""Returns the unique date of the file, if any."""
# Optioneel: als je bestandsnaam een datum bevat
return None
def file_name(self, file):
"""Returns the unique name of the file, if any."""
# Optioneel: als je een unieke naam wilt baseren op het bestand
return None
def extract(self, file, existing_entries=None):
"""Extracts entries from a files."""
entries = []
# file.contents
with open(file.name, encoding='utf-8') as f:
csv_reader = csv.reader(f, delimiter=';')
# Gebruik enumerate() om een teller (index) te krijgen
for index, row in enumerate(csv_reader):
if len(row) < 18 or row[17] != "EUR":
continue
# De index begint bij 0, dus de 12e kolom is index 11.
# Boekingsdatum = index 0
# Ontvanger = index 3
# Omschrijving = index 4
# Bedrag = index 11
# Valuta = index 17
datum_str = row[0]
ontvanger = row[3]
omschrijving_str = row[4]
bedrag_str = row[11]
valuta = row[17]
dag, maand, jaar = datum_str.split('.')
transactie_datum = datetime.date(int(jaar), int(maand), int(dag))
payee = ontvanger
narration = omschrijving_str
# Verwijder eerst de duizendtallen-scheidingstekens (de punten)
bedrag_str = bedrag_str.replace('.', '')
# Vervang daarna de komma door een punt
bedrag_str = bedrag_str.replace(',', '.')
bedrag = D(bedrag_str)
currency = valuta
meta = data.new_metadata(file.name, index)
# Bepaal of het een inkomst of uitgave is op basis van het voorteken van het bedrag.
if bedrag < D(0):
# Uitgave: bedrag is negatief.
# Geld gaat van de bankrekening naar een uitgavenrekening.
tegenrekening = self._map_payee_to_account(payee + " " + omschrijving_str)
#FORMAT: data.Posting(account, units, cost=None, price=None, flag=None, meta=None)
postings = [
#data.Posting(self.file_account(file), bedrag, currency, None, None, None),
data.Posting(self.file_account(file), data.Amount(bedrag, currency), None, None, None, None),
#data.Posting(tegenrekening, -bedrag, currency, None, None, None),
data.Posting(tegenrekening, data.Amount(bedrag, currency), None, None, None, None),
]
else:
# Inkomsten: bedrag is positief.
# Geld gaat van een inkomstenrekening naar de bankrekening.
tegenrekening = self._map_payee_to_account(payee + " " + omschrijving_str)
#FORMAT: data.Posting(account, units, cost=None, price=None, flag=None, meta=None)
postings = [
#data.Posting(tegenrekening, -bedrag, currency, None, None, None),
data.Posting(tegenrekening, data.Amount(-bedrag, currency), None, None, None, None),
#data.Posting(self.file_account(file), bedrag, currency, None, None, None),
data.Posting(self.file_account(file), data.Amount(bedrag, currency), None, None, None, None),
]
transaction = data.Transaction(
meta=meta,
date=transactie_datum,
flag='*',
payee=payee,
narration=narration,
tags=frozenset(),
links=frozenset(),
postings=postings
)
entries.append(transaction)
return entries
def _map_payee_to_account(self, payee):
mapping = {
#INCOME postings
"Lohn": "Income:Salaris",
"Gehalt": "Income:Salaris",
"Landkreis Meissen":"Income:BasicIncome",
#EXPENSES postings
"Miete": "Expenses:Rent",
"Sachsen":"Expenses:Electricity",
"Kontoführung":"Expenses:Banking",
"AMAZON":"Expenses:Subscriptions",
"Allianz":"Expenses:Insurance",
"Autohof":"Expenses:Driving",
"Tankstelle":"Expenses:Driving",
"ESSO":"Expenses:Driving",
"ARAL":"Expenses:Driving",
"Yellowbrick":"Expenses:Driving:Parking",
"PH":"Expenses:Driving:Parking", # Narrow down
"eBay":"Expenses:Gadgets", # Differentiate
"MEDIA MARKT":"Expenses:Gadgets",
"Logic Pro":"Expenses:Gadgets",
"Thomas Klotsche":"Expenses:Household",
"POCO":"Expenses:Furniture",
"Tapete":"Expenses:Furniture",
"Deutsche Post AG":"Expenses:Postdelivery",
"Echtzeitüberw":"Expenses:Banking",
"Apotheke":"Expenses:Drugs",
"ALDI":"Expenses:Food",
"Lidl":"Expenses:Food",
"Bosch":"Expenses:Food", #BOSCH catering
"TRANSGOURMET":"Expenses:Food",
"Netto Marken":"Expenses:Food",
"Rewe":"Expenses:Food",
#Creditcards
"AMERICAN EXPRESS":"Expenses:Creditcard",
"CONSORS":"Expenses:Creditcard",
#SAVINGS postings
"Bitpanda":"Assets:Savings:Trade"
#DEBTS
#"111649731":"Debts:Basic income",
# Actually booked from Dutch Account
#"Duo studieschuld":"Debts:Student loan (NL)",
#"DUO Studienschuld":"Debts:Student loan (NL)",
#"Bundeskasse Halle":"Debts:Student loan (DE)"
}
for sleutelwoord, rekening in mapping.items():
if sleutelwoord.lower() in payee.lower():
return rekening
return "Expenses:Uncategorized"

View File

@@ -1,7 +0,0 @@
import sys
import os
print("Python executable:", sys.executable)
print("sys.path (zoekpaden):")
for p in sys.path:
print(" ", p)

View File

@@ -1,20 +0,0 @@
import unittest
from beancount.core.amount import D
class TestAmountParsing(unittest.TestCase):
def clean_bedrag_str(self, bedrag_str):
bedrag_str = bedrag_str.replace('.','')
bedrag_str = bedrag_str.replace(',', '.')
return bedrag_str
# Europese notatie - het werkt dus prima
def test_correct_amount_parsing(self):
test_bedrag = "1.699,62"
expected_decimal = D("1699.62")
cleaned_string = self.clean_bedrag_str(test_bedrag)
self.assertEqual(D(cleaned_string), expected_decimal)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,33 +0,0 @@
import os
from beancount.ingest import importer
from beancount.core import data
from beancount.ingest.importers import csv
from beancount.format import format_entry
from beancount.ingest import identify
# Een dummy-klasse die een bestand representeert, zoals Beancount dat zou doen.
class DummyFile:
def __init__(self, filename):
self.name = filename
from postbank_csv_importer import MyCSVImporter
importer = MyCSVImporter()
bestand_pad = 'PostbankGiro_25-01_08.csv'
# Controleer of de importer het bestand herkent.
if importer.identify(bestand_pad):
print("Importer identified the file. Extracting entries...")
# Roep de extract methode aan om de transacties te verwerken.
extracted_entries = importer.extract(bestand_pad)
else:
# We maken een dummy-bestand aan met testdata
with open(bestand_pad, 'w', encoding='utf-8') as f:
f.write("Datum;Omschrijving;Rekening;Tegenrekening;Bedrag;Valuta\n")
f.write("01-01-2025;Boodschappen;Bank;Supermarkt;50.00;EUR\n")
f.write("02-01-2025;Salaris;Werkgever;Bank;2500.00;EUR\n")
f.write("03-01-2025;Huur;Bank;Huisbaas;750.00;EUR\n")
extracted_entries = importer_instance.extract(DummyFile(bestand_pad), None)
print("; Geëxtraheerde transacties:")
for entry in extracted_entries:
print(format_entry(entry))