Fix autosort
This commit is contained in:
parent
e49e8c51f5
commit
db555fa3b3
4 changed files with 50 additions and 41 deletions
37
autosort.py
37
autosort.py
|
@ -2,16 +2,13 @@
|
||||||
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 Category, Tag
|
from models import Tag, Transaction
|
||||||
from helper import get_session, add_category, get_rules
|
from helper import get_session, create_tag, get_rules
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import click
|
import click
|
||||||
import colorful as cf
|
import colorful as cf
|
||||||
|
|
||||||
def print_verbose(verbose, string):
|
|
||||||
if verbose:
|
|
||||||
print(string)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="autosort")
|
@click.command(name="autosort")
|
||||||
@click.option("--profile", "-p")
|
@click.option("--profile", "-p")
|
||||||
|
@ -25,19 +22,15 @@ def command(profile, dry_run, verbose):
|
||||||
print("Found {} unsorted transcations".format(len(unsorted)))
|
print("Found {} unsorted transcations".format(len(unsorted)))
|
||||||
new = []
|
new = []
|
||||||
|
|
||||||
for t in unsorted:
|
for transaction in unsorted:
|
||||||
cat_name = apply_rules(t, rules)
|
taglist = apply_rules(transaction, rules)
|
||||||
if cat_name:
|
if taglist:
|
||||||
short_name = t.name[:25] if len(t.name) > 25 else t.name
|
for tag_label in taglist:
|
||||||
short_desc = t.description[:45] if len(t.description) > 50 else t.description
|
tag = session.query(Tag).filter(Tag.name == tag_label).first()
|
||||||
print_verbose(verbose, "{:<25} | {:<50}| {:>10}€".format(short_name, short_desc, t.amount / 100))
|
if not tag:
|
||||||
print_verbose(verbose, cf.green(f"Matched '{cat_name}'"))
|
tag = create_tag(tag_label, profile, session)
|
||||||
cat_id = session.query(Category).filter(Category.name == cat_name).first()
|
transaction.tags.append(tag)
|
||||||
if not cat_id:
|
new.append(transaction)
|
||||||
cat_id = add_category(cat_name, profile, session)
|
|
||||||
t.category_id = cat_id
|
|
||||||
new.append(t)
|
|
||||||
print_verbose(verbose, "-" * 90)
|
|
||||||
|
|
||||||
print(f"Automatically matched {len(new)} transactions")
|
print(f"Automatically matched {len(new)} transactions")
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
|
@ -56,11 +49,11 @@ def apply_rules(t, rules):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if name_regex:
|
if name_regex:
|
||||||
if not name_regex.search(t.name):
|
if not re.search(name_regex, t.name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if desc_regex:
|
if desc_regex:
|
||||||
if not desc_regex.search(t.description):
|
if not re.search(desc_regext.description):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return r["category"]
|
return r["tags"].split(",")
|
||||||
|
|
23
cli
23
cli
|
@ -2,15 +2,16 @@
|
||||||
import click
|
import click
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import importer
|
import importer
|
||||||
# import serve
|
# import serve
|
||||||
# import autosort
|
import autosort
|
||||||
# import validate
|
import validate
|
||||||
# import info
|
# import info
|
||||||
# import report
|
# import report
|
||||||
|
|
||||||
from helper import build_database_filename, create_dirs
|
from helper import build_database_filename, create_dirs, build_rules_filename
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
@ -26,13 +27,19 @@ def cli():
|
||||||
@cli.command(name="init")
|
@cli.command(name="init")
|
||||||
@click.argument("profile_name", required=True)
|
@click.argument("profile_name", required=True)
|
||||||
def init(profile_name):
|
def init(profile_name):
|
||||||
filename = build_database_filename(profile_name)
|
db_path = build_database_filename(profile_name)
|
||||||
if os.path.exists(filename):
|
rule_path = build_rules_filename(profile_name)
|
||||||
|
|
||||||
|
if os.path.exists(db_path) and os.path.exists(rule_path):
|
||||||
print(f"Profile '{profile_name}' already exists")
|
print(f"Profile '{profile_name}' already exists")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
engine = create_engine(f"sqlite:///{filename}")
|
# database
|
||||||
|
engine = create_engine(f"sqlite:///{db_path}")
|
||||||
models.Base.metadata.create_all(engine)
|
models.Base.metadata.create_all(engine)
|
||||||
|
# rules
|
||||||
|
Path(rule_path).touch()
|
||||||
|
|
||||||
print(f"Sucessfully create the profile '{profile_name}'")
|
print(f"Sucessfully create the profile '{profile_name}'")
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,8 +48,8 @@ if __name__ == '__main__':
|
||||||
# cli.add_command(sort.command)
|
# cli.add_command(sort.command)
|
||||||
cli.add_command(importer.command)
|
cli.add_command(importer.command)
|
||||||
# cli.add_command(serve.command)
|
# cli.add_command(serve.command)
|
||||||
# cli.add_command(autosort.command)
|
cli.add_command(autosort.command)
|
||||||
# cli.add_command(validate.command)
|
cli.add_command(validate.command)
|
||||||
# cli.add_command(info.command)
|
# cli.add_command(info.command)
|
||||||
# cli.add_command(report.command)
|
# cli.add_command(report.command)
|
||||||
cli()
|
cli()
|
||||||
|
|
16
helper.py
16
helper.py
|
@ -20,7 +20,7 @@ DATA_DIR = os.path.join(XDG_DATA_HOME, "schmeckels")
|
||||||
|
|
||||||
|
|
||||||
def format_amount(cents):
|
def format_amount(cents):
|
||||||
amount = cents/100
|
amount = cents / 100
|
||||||
return f"{amount:,.2f}"
|
return f"{amount:,.2f}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,24 +33,24 @@ def create_dirs():
|
||||||
|
|
||||||
|
|
||||||
def build_database_filename(profile_name):
|
def build_database_filename(profile_name):
|
||||||
return f"{DATA_DIR}/databases/{profile_name}.db"
|
return f"{DATA_DIR}/{profile_name}.db"
|
||||||
|
|
||||||
|
|
||||||
def build_rules_filename(profile_name):
|
def build_rules_filename(profile_name):
|
||||||
return f"{DATA_DIR}/rules/{profile_name}.yaml"
|
return f"{CONFIG_DIR}/{profile_name}.yaml"
|
||||||
|
|
||||||
|
|
||||||
def check_single_profile():
|
def check_single_profile():
|
||||||
files = os.listdir(f"{DATA_DIR}/databases")
|
files = os.listdir(DATA_DIR)
|
||||||
if len(files) == 1:
|
if len(files) == 1:
|
||||||
return files.split(".")[0]
|
return files[0].split(".")[0]
|
||||||
else:
|
else:
|
||||||
print("--profile is required when you have more than one database.")
|
print("--profile is required when you have more than one database.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def list_profiles():
|
def list_profiles():
|
||||||
files = os.listdir(f"{DATA_DIR}/databases")
|
files = os.listdir(DATA_DIR)
|
||||||
return [x.split(".")[0] for x in files]
|
return [x.split(".")[0] for x in files]
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ def get_rules(profile_name):
|
||||||
def create_tag(name, profile, session):
|
def create_tag(name, profile, session):
|
||||||
t = session.query(models.Tag).filter(models.Tag.name == name).first()
|
t = session.query(models.Tag).filter(models.Tag.name == name).first()
|
||||||
if not t:
|
if not t:
|
||||||
c = models.Tag(name=name)
|
t = models.Tag(name=name)
|
||||||
session.add(t)
|
session.add(t)
|
||||||
session.commit()
|
session.commit()
|
||||||
return t.id
|
return t
|
||||||
|
|
15
models.py
15
models.py
|
@ -1,10 +1,17 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Table
|
||||||
from sqlalchemy.orm import relationship, backref
|
from sqlalchemy.orm import relationship, backref
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
association_table = Table(
|
||||||
|
"association",
|
||||||
|
Base.metadata,
|
||||||
|
Column("left_id", Integer, ForeignKey("Transaction.id")),
|
||||||
|
Column("right_id", Integer, ForeignKey("Tag.id")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Base):
|
class Transaction(Base):
|
||||||
__tablename__ = "Transaction"
|
__tablename__ = "Transaction"
|
||||||
|
@ -14,6 +21,7 @@ class Transaction(Base):
|
||||||
iban = Column(String)
|
iban = Column(String)
|
||||||
amount = Column(Integer)
|
amount = Column(Integer)
|
||||||
description = Column(String)
|
description = Column(String)
|
||||||
|
tags = relationship("Tag", secondary=association_table, back_populates="transactions")
|
||||||
|
|
||||||
def is_positive(self):
|
def is_positive(self):
|
||||||
return self.amount > 0
|
return self.amount > 0
|
||||||
|
@ -32,9 +40,10 @@ class Transaction(Base):
|
||||||
|
|
||||||
class Tag(Base):
|
class Tag(Base):
|
||||||
__tablename__ = "Tag"
|
__tablename__ = "Tag"
|
||||||
name = Column(String, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String, unique=True)
|
||||||
description = Column(String)
|
description = Column(String)
|
||||||
transactions = relationship("Transaction", backref="tags")
|
transactions = relationship("Transaction", secondary=association_table, back_populates="tags")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Tag {self.name}>"
|
return f"<Tag {self.name}>"
|
||||||
|
|
Loading…
Add table
Reference in a new issue