Multiple importer and bulksort in the GUI

This commit is contained in:
fleaz 2020-03-18 00:22:12 +01:00
parent 60cbf9da2e
commit dfea791bb6
11 changed files with 133 additions and 53 deletions

2
.flake8 Normal file
View file

@ -0,0 +1,2 @@
[flake8]
max-line-length = 120

View file

@ -5,6 +5,7 @@ verify_ssl = true
[dev-packages] [dev-packages]
flake8 = "*" flake8 = "*"
autopep8 = "*"
[packages] [packages]
sqlalchemy = "*" sqlalchemy = "*"

9
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "25b9941c75b9e81082b3302534b779bedf7a24979213c97e7da0524201b2f333" "sha256": "731c3810c49b4b77cd9aac8450a97650031dc994b7367106567d351f96011a24"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -64,6 +64,13 @@
} }
}, },
"develop": { "develop": {
"autopep8": {
"hashes": [
"sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
],
"index": "pypi",
"version": "==1.5"
},
"entrypoints": { "entrypoints": {
"hashes": [ "hashes": [
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",

10
README.md Normal file
View file

@ -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

View file

View file

@ -1,47 +1,79 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import mt940 import mt940
import csv
import click import click
from datetime import datetime
from sqlalchemy import create_engine, desc from sqlalchemy import create_engine, desc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from models import Transaction from models import Transaction
import sys import sys
def convert_amount(t): @click.command(name="import")
return int(t.amount * 100) @click.option("--filetype", "-t", type=click.Choice(["dkb", "sparkasse-mt940", "bunq-csv"], case_sensitive=False))
@click.argument("filename", type=click.Path(exists=True))
@click.command(name='import') def command(filename, filetype):
@click.argument('filename', type=click.Path(exists=True))
def command(filename):
click.echo(click.format_filename(filename))
engine = create_engine("sqlite:///app.db") engine = create_engine("sqlite:///app.db")
Base = declarative_base()
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
session = Session() session = Session()
latest = session.query(Transaction).order_by(desc(Transaction.date)).first() 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") date = data["date"]
count = 0 amount = int(data["amount"].amount * 100)
for t in transactions: iban = data["applicant_iban"]
data = t.data name = data["applicant_name"]
description = data["purpose"]
date = data['date'] # if latest and data['date'] < latest.date:
amount = convert_amount(data['amount']) # print("Found transaction older than then oldest transction in the DB. Aborting")
iban = data['applicant_iban'] # sys.exit(1)
name = data['applicant_name']
description = data['purpose']
if latest and data['date'] < latest.date: new.append(Transaction(date=date, name=name, iban=iban, amount=amount, description=description))
print("Found transaction older than then oldest transction in the DB. Aborting") print(".", end="", flush=True)
sys.exit(1)
t = Transaction(date=date, name=name, iban=iban, amount=amount, description=description) elif filetype == "bunq-csv":
session.add(t) with open(click.format_filename(filename)) as fh:
session.commit() fh.readline() # Get rid of first line
count += 1 csv_reader = csv.reader(fh, delimiter=";")
print(".", end="", flush=True) count = 0
print() for line in csv_reader:
print(f"Imported {count} transactions") 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")

View file

@ -20,7 +20,7 @@ class Transaction(Base):
return self.amount > 0 return self.amount > 0
def pretty_amount(self): def pretty_amount(self):
return "{0:.2f}".format(self.amount/100) return "{0:.2f}".format(self.amount / 100)
def get_date(self, format): def get_date(self, format):
if format == "iso": if format == "iso":
@ -31,7 +31,6 @@ class Transaction(Base):
return "UNKNOWN FORMAT" return "UNKNOWN FORMAT"
class Category(Base): class Category(Base):
__tablename__ = "Category" __tablename__ = "Category"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)

View file

@ -1,11 +1,13 @@
#! /usr/bin/env python3 #! /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 import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from models import * from models import Category, Transaction
import click import click
from pprint import pprint
engine = create_engine("sqlite:///app.db") engine = create_engine("sqlite:///app.db")
Base = declarative_base() Base = declarative_base()
@ -13,41 +15,59 @@ Session = sessionmaker(bind=engine)
session = Session() session = Session()
@route('/') @route("/")
def index(): def index():
return template('index') return template("index")
@route('/categories')
@route("/categories")
def categories(): def categories():
categories = session.query(Category).all() categories = session.query(Category).all()
categories = [c for c in categories if c.is_child()] categories = [c for c in categories if c.is_child()]
return template('categories', categories=categories) return template("categories", categories=categories)
@route('/category/<name>')
@route("/category/<name>")
def category(name): def category(name):
c = session.query(Category).filter(Category.name==name).first() c = session.query(Category).filter(Category.name == name).first()
if c: 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: else:
transactions = [] transactions = []
return template('category', category=c,transactions=transactions) return template("category", category=c, transactions=transactions)
@route('/category/<name>/delete')
@route("/category/<name>/delete")
def category(name): def category(name):
c = session.query(Category).filter(Category.name==name).first() c = session.query(Category).filter(Category.name == name).first()
if c: if c:
session.delete(c) session.delete(c)
session.commit() session.commit()
return redirect("/categories") return redirect("/categories")
@route('/transactions')
@route("/transactions")
def transactions(): def transactions():
transactions = session.query(Transaction).all() 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(): def command():
run(host='localhost', port=8080, reloader=True,debug=True) run(host="localhost", port=8080, reloader=True, debug=True)

View file

@ -11,7 +11,7 @@ from prompt_toolkit.completion import FuzzyWordCompleter
from prompt_toolkit.shortcuts import prompt from prompt_toolkit.shortcuts import prompt
@click.command(name='sort') @click.command(name="sort")
def command(): def command():
engine = create_engine("sqlite:///app.db") engine = create_engine("sqlite:///app.db")
Base = declarative_base() Base = declarative_base()
@ -45,5 +45,3 @@ def command():
add_category(select) add_category(select)
print("-" * 20) print("-" * 20)

View file

@ -1,8 +1,16 @@
% rebase("base.tpl") % rebase("base.tpl")
<form action="/bulksort" method="POST">
<select name="category">
% for c in categories:
<option value="{{ c.id }}">{{ c.full_name() }}</option>
% end
</select>
<input type="submit">
<table class="table-auto"> <table class="table-auto">
<thead> <thead>
<tr> <tr>
<th class="px-4 py-2"></th>
<th class="px-4 py-2">Date</th> <th class="px-4 py-2">Date</th>
<th class="px-4 py-2">Sender/Empfänger</th> <th class="px-4 py-2">Sender/Empfänger</th>
<th class="px-4 py-2">Verwendungszweck</th> <th class="px-4 py-2">Verwendungszweck</th>
@ -13,6 +21,7 @@
<tbody> <tbody>
% for t in transactions: % for t in transactions:
<tr> <tr>
<td class="border px-4 py-2"><input type="checkbox" name="transaction" value="{{ t.id }}"></td>
<td class="border px-4 py-2">{{ t.get_date("de") }}</td> <td class="border px-4 py-2">{{ t.get_date("de") }}</td>
<td class="border px-4 py-2">{{ t.name }}</td> <td class="border px-4 py-2">{{ t.name }}</td>
<td class="border px-4 py-2">{{ t.description }}</td> <td class="border px-4 py-2">{{ t.description }}</td>
@ -26,3 +35,5 @@
% end % end
</tbody> </tbody>
</table> </table>
</form>