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"