diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..79a16af --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 \ No newline at end of file diff --git a/Pipfile b/Pipfile index 81b4abd..67249df 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ verify_ssl = true [dev-packages] flake8 = "*" +autopep8 = "*" [packages] sqlalchemy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index cc6f4f2..579854a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "25b9941c75b9e81082b3302534b779bedf7a24979213c97e7da0524201b2f333" + "sha256": "731c3810c49b4b77cd9aac8450a97650031dc994b7367106567d351f96011a24" }, "pipfile-spec": 6, "requires": { @@ -64,6 +64,13 @@ } }, "develop": { + "autopep8": { + "hashes": [ + "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43" + ], + "index": "pypi", + "version": "==1.5" + }, "entrypoints": { "hashes": [ "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff1d496 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# schmeckels + +## ToDo + +- Profile via config files im home +- Graphen + - Linechart Verlauf über Monat (Gesamtetrag) + - Piechart verschiedene Kategorien +- Automatcher für Regeln +- setup.py diff --git a/main.py b/cli similarity index 100% rename from main.py rename to cli diff --git a/create.py b/create.py index 2b1ecbf..d835372 100644 --- a/create.py +++ b/create.py @@ -11,4 +11,4 @@ session = Session() from models import * -Base.metadata.create_all(engine) \ No newline at end of file +Base.metadata.create_all(engine) diff --git a/importer.py b/importer.py index 6964a00..28b5161 100644 --- a/importer.py +++ b/importer.py @@ -1,47 +1,79 @@ #! /usr/bin/env python3 import mt940 +import csv import click +from datetime import datetime from sqlalchemy import create_engine, desc -from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from models import Transaction import sys -def convert_amount(t): - return int(t.amount * 100) - -@click.command(name='import') -@click.argument('filename', type=click.Path(exists=True)) -def command(filename): - click.echo(click.format_filename(filename)) +@click.command(name="import") +@click.option("--filetype", "-t", type=click.Choice(["dkb", "sparkasse-mt940", "bunq-csv"], case_sensitive=False)) +@click.argument("filename", type=click.Path(exists=True)) +def command(filename, filetype): engine = create_engine("sqlite:///app.db") - Base = declarative_base() - Session = sessionmaker(bind=engine) session = Session() latest = session.query(Transaction).order_by(desc(Transaction.date)).first() + new = [] + if filetype == "sparkasse-mt940": + transactions = mt940.parse(click.format_filename(filename)) + for t in transactions: + data = t.data - transactions = mt940.parse("2020-01.TXT") - count = 0 - for t in transactions: - data = t.data + date = data["date"] + amount = int(data["amount"].amount * 100) + iban = data["applicant_iban"] + name = data["applicant_name"] + description = data["purpose"] - date = data['date'] - amount = convert_amount(data['amount']) - iban = data['applicant_iban'] - name = data['applicant_name'] - description = data['purpose'] + # if latest and data['date'] < latest.date: + # print("Found transaction older than then oldest transction in the DB. Aborting") + # sys.exit(1) - if latest and data['date'] < latest.date: - print("Found transaction older than then oldest transction in the DB. Aborting") - sys.exit(1) + new.append(Transaction(date=date, name=name, iban=iban, amount=amount, description=description)) + print(".", end="", flush=True) - t = Transaction(date=date, name=name, iban=iban, amount=amount, description=description) - session.add(t) - session.commit() - count += 1 - print(".", end="", flush=True) - print() - print(f"Imported {count} transactions") + elif filetype == "bunq-csv": + with open(click.format_filename(filename)) as fh: + fh.readline() # Get rid of first line + csv_reader = csv.reader(fh, delimiter=";") + count = 0 + for line in csv_reader: + date = datetime.strptime(line[0], "%Y-%m-%d") + amount = int(float(line[2].replace(",", "")) * 100) + iban = line[4] + name = line[5] + description = line[6] + + # if latest and date < latest.date: + # print("Found transaction older than then oldest transction in the DB. Aborting") + # sys.exit(1) + + new.append(Transaction(date=date, name=name, iban=iban, amount=amount, description=description)) + print(".", end="", flush=True) + + elif filetype == "dkb": + with open(click.format_filename(filename), 'r', encoding='utf8') as fh: + fh.readline() # Get rid of first line + csv_reader = csv.reader(fh, delimiter=";") + for line in csv_reader: + date = datetime.strptime(line[0], "%Y-%m-%d") + amount = line[5] + iban = line[4] + name = line[3] + description = line[4] + + # if latest and date < latest.date: + # print("Found transaction older than then oldest transction in the DB. Aborting") + # sys.exit(1) + + new.append(Transaction(date=date, name=name, iban=iban, amount=amount, description=description)) + print(".", end="", flush=True) + + session.bulk_save_objects(new) + session.commit() + print(f"Imported {len(new)} transactions") diff --git a/models.py b/models.py index 39e684e..d10dbbf 100644 --- a/models.py +++ b/models.py @@ -18,9 +18,9 @@ class Transaction(Base): def is_positive(self): return self.amount > 0 - + def pretty_amount(self): - return "{0:.2f}".format(self.amount/100) + return "{0:.2f}".format(self.amount / 100) def get_date(self, format): if format == "iso": @@ -31,7 +31,6 @@ class Transaction(Base): return "UNKNOWN FORMAT" - class Category(Base): __tablename__ = "Category" id = Column(Integer, primary_key=True) diff --git a/serve.py b/serve.py index 02b667a..ab46f3c 100644 --- a/serve.py +++ b/serve.py @@ -1,11 +1,13 @@ #! /usr/bin/env python3 -from bottle import route, run, template, redirect +from bottle import route, run, template, redirect, request, post from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base -from models import * +from models import Category, Transaction import click +from pprint import pprint + engine = create_engine("sqlite:///app.db") Base = declarative_base() @@ -13,41 +15,59 @@ Session = sessionmaker(bind=engine) session = Session() -@route('/') +@route("/") def index(): - return template('index') + return template("index") -@route('/categories') + +@route("/categories") def categories(): categories = session.query(Category).all() categories = [c for c in categories if c.is_child()] - return template('categories', categories=categories) + return template("categories", categories=categories) -@route('/category/') + +@route("/category/") def category(name): - c = session.query(Category).filter(Category.name==name).first() + c = session.query(Category).filter(Category.name == name).first() if c: - transactions = session.query(Transaction).filter(Transaction.category_id==c.id).all() + transactions = session.query(Transaction).filter(Transaction.category_id == c.id).all() else: transactions = [] - return template('category', category=c,transactions=transactions) + return template("category", category=c, transactions=transactions) -@route('/category//delete') + +@route("/category//delete") def category(name): - c = session.query(Category).filter(Category.name==name).first() + c = session.query(Category).filter(Category.name == name).first() if c: session.delete(c) session.commit() return redirect("/categories") -@route('/transactions') + +@route("/transactions") def transactions(): transactions = session.query(Transaction).all() - return template('transactions', transactions=transactions) + categories = session.query(Category).all() + childs = [c for c in categories if c.is_child()] + return template("transactions", transactions=transactions, categories=childs) -@click.command(name='serve') +@post("/bulksort") +def buksort(): + cid = request.forms.get("category") + transaction_ids = request.forms.getall("transaction") + for id in transaction_ids: + t = session.query(Transaction).get(id) + t.category__id = cid + session.add(t) + print(f"Sorted {len(transaction_ids)} transactions into category {cid}") + session.commit() + return redirect("/transactions") + +@click.command(name="serve") def command(): - run(host='localhost', port=8080, reloader=True,debug=True) + run(host="localhost", port=8080, reloader=True, debug=True) diff --git a/sort.py b/sort.py index 9429866..a50fa56 100644 --- a/sort.py +++ b/sort.py @@ -11,7 +11,7 @@ from prompt_toolkit.completion import FuzzyWordCompleter from prompt_toolkit.shortcuts import prompt -@click.command(name='sort') +@click.command(name="sort") def command(): engine = create_engine("sqlite:///app.db") Base = declarative_base() @@ -45,5 +45,3 @@ def command(): add_category(select) print("-" * 20) - - diff --git a/views/transactions.tpl b/views/transactions.tpl index 933b529..9a4bcc6 100644 --- a/views/transactions.tpl +++ b/views/transactions.tpl @@ -1,8 +1,16 @@ % rebase("base.tpl") +
+ + + @@ -13,6 +21,7 @@ % for t in transactions: + @@ -26,3 +35,5 @@ % end
Date Sender/Empfänger Verwendungszweck
{{ t.get_date("de") }} {{ t.name }} {{ t.description }}
+ +