diff --git a/import.py b/import.py index 618cd3a..7116358 100644 --- a/import.py +++ b/import.py @@ -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() -] \ No newline at end of file + if __name__ == '__main__': + main() \ No newline at end of file diff --git a/importers/csvbank.py b/importers/csvbank.py new file mode 100644 index 0000000..68bcef9 --- /dev/null +++ b/importers/csvbank.py @@ -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')) diff --git a/lib64 b/lib64 deleted file mode 120000 index 7951405..0000000 --- a/lib64 +++ /dev/null @@ -1 +0,0 @@ -lib \ No newline at end of file diff --git a/postbank_csv_importer.py b/postbank_csv_importer.py deleted file mode 100644 index 1e31acb..0000000 --- a/postbank_csv_importer.py +++ /dev/null @@ -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" \ No newline at end of file diff --git a/test_Path.py.py b/test_Path.py.py deleted file mode 100644 index dda5fae..0000000 --- a/test_Path.py.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import os - -print("Python executable:", sys.executable) -print("sys.path (zoekpaden):") -for p in sys.path: - print(" ", p) \ No newline at end of file diff --git a/test_amount_parsing.py b/test_amount_parsing.py deleted file mode 100644 index d94e710..0000000 --- a/test_amount_parsing.py +++ /dev/null @@ -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() diff --git a/test_importer.py b/test_importer.py deleted file mode 100644 index c608caf..0000000 --- a/test_importer.py +++ /dev/null @@ -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)) \ No newline at end of file