from invoice import Invoice, Tax import db import datetime from functools import wraps from argparse import ArgumentParser as ap import tempfile import subprocess import sqlite3 from configparser import ConfigParser import os from os import path CONFIG_FILE = "" def load_config(): basedir = os.environ.get("XDG_CONFIG_HOME", None) or \ path.join(os.environ["HOME"], ".config") basedir = path.join(basedir, "fracture") confile = path.join(basedir, "config") global CONFIG_FILE CONFIG_FILE = confile conf = ConfigParser() conf.read(confile) # SERIES for k in conf["series"]: Invoice.SERIES[int(k)] = conf["series"][k] if Invoice.SERIES == {}: raise "Invoice series not configured correctly: no series found" # ID FORMAT FORMAT = conf["invoice"].get("id_format", '"%s/%s/%s" % (series, date.year, id)') def f(series, date, id): return eval(FORMAT, None, {"series": series, "date": date, "id": id}) Invoice.ID_FORMAT = f # CURRENCY Invoice.CURRENCY = conf["invoice"].get("currency", "€") Invoice.CURRENCY_DECIMAL = conf["invoice"].getint("currency_decimal", 2) # TEMPLATE ? # TODO # Or dump a json or something? templatefile = conf["invoice"]["template"] if not path.isabs(templatefile): templatefile = path.join(confile, templatefile) # INVOICE LEVEL TAXES (like IRPF in Spain) tax = [] for k in conf["taxes"]: tax.append(Tax(k, conf.getfloat("taxes",k))) Invoice.DEFAULT_TAXES = tuple(tax) # DATABASE Invoice.DB_FILE = path.join(basedir, "invoice.db") if not path.exists(Invoice.DB_FILE): db.create(Invoice.DB_FILE) def call_editor(filename): """ Edit filename with $EDITOR (fallback to Vim) """ if not os.path.exists(filename): raise FileNotFoundError("File not found: " + filename) process = subprocess.Popen([os.environ.get("EDITOR", "vim"), filename],) process.wait() def edit(contents): """ Edit temporary file with initial `contents` and return it"s edited content """ with tempfile.NamedTemporaryFile(mode="w", delete=False) as t: t.write(contents) call_editor(t.name) with open(t.name) as t: edited_content = t.read() if os.path.exists(t.name): os.remove(t.name) return edited_content def command(f): @wraps(f) def remove_func_arg(namespace): kwargs = vars(namespace) del kwargs["func"] f(**kwargs) return remove_func_arg @command def edit_config(): # TODO: Generate config file if not created call_editor(CONFIG_FILE) @command def new_invoice(): num = Invoice.from_config( edit( Invoice().to_config() )).persist() # print(num) # edit(Invoice.load(num).to_config()) @command def summarize(xlsx=False, year=None, quarter=None): invoices = Invoice.load_by_date(year, quarter) rows = map(lambda x: x.to_row(), invoices) if __name__ == "__main__": load_config() parser = ap(prog="fracture") parser.set_defaults(func=lambda: parser.print_help()) subparsers = parser.add_subparsers(title= "Subcommands", help="sub-command help") # New Invoice new_parser = subparsers.add_parser("new", aliases=["n"], help="a help") new_parser.set_defaults(func=new_invoice) # Configure fracture configure_parser = subparsers.add_parser("configure", aliases=["c","conf","config"], help="b help") configure_parser.set_defaults(func=edit_config) # Summary summary_parser = subparsers.add_parser("summary", aliases=["s", "sum"], help="Display summary for tax declarations") summary_parser.add_argument("--xlsx", action="store_true", help="Output as xlsx") summary_parser.add_argument("--quarter", type=int, help="Obtain the summary of the quarter") summary_parser.add_argument("--year", type=int, default=datetime.datetime.now().year, help="Obtain the summary of the year") summary_parser.set_defaults(func=summarize) # parse args = parser.parse_args() args.func(args)