diff --git a/docs/importers.rst b/docs/importers.rst index 95bd6a5..c94ad02 100644 --- a/docs/importers.rst +++ b/docs/importers.rst @@ -324,3 +324,15 @@ Import CSV from `Neon `__ from tariochbctools.importers.neon import importer as neonimp CONFIG = [neonimp.Importer("\d\d\d\d_account_statements\.csv", "Assets:Neon:CHF")] + + +Viseca One +---------- + +Import PDF from `Viseca One `__ + +.. code-block:: python + + from tariochbctools.importers.viseca import importer as visecaimp + + CONFIG = [visecaimp.Importer(r"Kontoauszug.*\.pdf", "Assets:Viseca:CHF")] diff --git a/src/tariochbctools/importers/viseca/__init__.py b/src/tariochbctools/importers/viseca/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tariochbctools/importers/viseca/importer.py b/src/tariochbctools/importers/viseca/importer.py new file mode 100644 index 0000000..aca4adc --- /dev/null +++ b/src/tariochbctools/importers/viseca/importer.py @@ -0,0 +1,95 @@ +import re +from datetime import datetime + +import camelot +from beancount.core import amount, data +from beancount.core.number import D +from beancount.ingest import importer +from beancount.ingest.importers.mixins import identifier + + +class Importer(identifier.IdentifyMixin, importer.ImporterProtocol): + """An importer for Viseca One Card Statement PDF files.""" + + def __init__(self, regexps, account): + identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)]) + self.account = account + self.currency = "CHF" + + def file_account(self, file): + return self.account + + def createEntry(self, file, date, amt, text): + meta = data.new_metadata(file.name, 0) + return data.Transaction( + meta, + date, + "*", + "", + text, + data.EMPTY_SET, + data.EMPTY_SET, + [ + data.Posting(self.account, amt, None, None, None, None), + ], + ) + + def extract(self, file, existing_entries): + entries = [] + + p = re.compile( + r"^(?P\d\d\.\d\d\.\d\d) (?P\d\d\.\d\d\.\d\d) (?P.*)$" + ) + + tables = camelot.read_pdf( + file.name, flavor="stream", table_areas=["65,450,575,100"] + ) + for table in tables: + df = table.df + + # skip incompatible tables + if df.columns.size != 4: + continue + + lastTrxDate = None + lastAmount = None + lastDetails = "" + for _, row in df.iterrows(): + dateValutaDetails, currency, amountCcy, amountChf = tuple(row) + + if ( + "XX XXXX" in dateValutaDetails + or "Kartenlimite" in dateValutaDetails + ): + continue + + trxDate = None + detail = "" + m = p.match(dateValutaDetails) + if m: + trxDate = m.group("valueDate") + detail = m.group("detail").strip() + " " + else: + detail = dateValutaDetails.strip() + " " + + if amountChf: + if lastTrxDate: + amt = None + if "-" in lastAmount: + amt = -amount.Amount(D(lastAmount.strip(" -")), "CHF") + else: + amt = amount.Amount(D(lastAmount), "CHF") + + book_date = datetime.strptime(lastTrxDate, "%d.%m.%y").date() + + entries.append( + self.createEntry(file, book_date, amt, lastDetails.strip()) + ) + + lastTrxDate = trxDate + lastAmount = amountChf + lastDetails = "" + + lastDetails += detail + " " + + return entries