summaryrefslogtreecommitdiff
path: root/fracture
diff options
context:
space:
mode:
Diffstat (limited to 'fracture')
-rw-r--r--fracture/__main__.py17
-rw-r--r--fracture/db.py20
-rw-r--r--fracture/invoice.py121
3 files changed, 85 insertions, 73 deletions
diff --git a/fracture/__main__.py b/fracture/__main__.py
index ee26bbe..c99b0e9 100644
--- a/fracture/__main__.py
+++ b/fracture/__main__.py
@@ -1,5 +1,6 @@
from invoice import Invoice, Tax, Product, Conf
import db
+from jinja2 import Template
from csv import DictWriter
import datetime
@@ -139,11 +140,19 @@ def summarize(xlsx=False, year=None, quarter=None):
wrtr.writerow(r)
@command
-def render(id, type=None):
- invoice = Invoice.load_by_idrepr(id, type)
- if invoice is not None:
+def render(id, type=None, format=None):
+ invoice = Invoice.load(id, type)
+ if format == "json":
print(invoice.to_json())
+ if format == "xelatex":
+ # TODO AUTOMATE TEMPLATE SEARCH AND THAT, THIS IS THIS FOR DEV
+ with open("templates/template.tex", "r") as f:
+ template_text = f.read()
+ template = Template(template_text)
+ if invoice is not None:
+ print(template.render(invoice=invoice.to_dict()))
+
if __name__ == "__main__":
load_config()
@@ -181,6 +190,8 @@ if __name__ == "__main__":
help="Invoice identification string")
summary_parser.add_argument("--type", type=str,
help="Invoice type", default="sent")
+ summary_parser.add_argument("--format", type=str,
+ choices={"json", "xelatex"}, help="Invoice type", default="json")
summary_parser.set_defaults(func=render)
diff --git a/fracture/db.py b/fracture/db.py
index 1e90727..9ac0341 100644
--- a/fracture/db.py
+++ b/fracture/db.py
@@ -5,26 +5,23 @@ def create(dbname):
c = conn.cursor()
c.execute('''CREATE TABLE products
- ( invoice_id INTEGER,
+ ( invoice_id TEXT,
invoice_type TEXT,
- invoice_series INTEGER,
description TEXT NOT NULL,
units REAL NOT NULL,
price_unit REAL NOT NULL,
vat REAL,
PRIMARY KEY(description,
invoice_id,
- invoice_type,
- invoice_series)
+ invoice_type)
FOREIGN KEY(invoice_id) REFERENCES invoices(id)
FOREIGN KEY(invoice_type) REFERENCES invoices(type)
- FOREIGN KEY(invoice_series) REFERENCES invoices(series)
ON DELETE CASCADE )''')
# date is %Y-%m-%d
c.execute('''CREATE TABLE invoices
- ( id INTEGER NOT NULL,
- id_repr TEXT NOT NULL,
+ ( number INTEGER NOT NULL,
+ id TEXT NOT NULL,
type TEXT NOT NULL,
series INTEGER NOT NULL,
date TEXT NOT NULL,
@@ -34,22 +31,19 @@ def create(dbname):
customer_name TEXT,
customer_address TEXT,
PRIMARY KEY(type, series, id),
- UNIQUE (id_repr, type)
+ UNIQUE (id, type)
)''')
c.execute('''CREATE TABLE taxes
( name TEXT NOT NULL,
- invoice_id INTEGER,
+ invoice_id TEXT,
invoice_type TEXT,
- invoice_series INTEGER,
ratio REAL,
PRIMARY KEY(name,
invoice_id,
- invoice_type,
- invoice_series)
+ invoice_type)
FOREIGN KEY(invoice_id) REFERENCES invoices(id)
FOREIGN KEY(invoice_type) REFERENCES invoices(type)
- FOREIGN KEY(invoice_series) REFERENCES invoices(series)
ON DELETE CASCADE )''')
conn.commit()
diff --git a/fracture/invoice.py b/fracture/invoice.py
index e23d257..d795340 100644
--- a/fracture/invoice.py
+++ b/fracture/invoice.py
@@ -9,6 +9,10 @@ class Conf:
# MOVE ALL THE CONFIG HERE
CURRENCY = None
CURRENCY_DECIMAL = 0
+ @classmethod
+ def round(cls, num):
+ return round(num, cls.CURRENCY_DECIMAL)
+
class Tax:
def __init__(self, name="", ratio = 0.0):
@@ -34,16 +38,24 @@ class Product:
else:
raise ValueError("Product: No valid VAT. Valid are:"+\
str(Product.VATS))
- def calc_base(self):
+ @property
+ def total(self):
+ return self.base+self.charged_vat
+ @property
+ def base(self):
return self.price_unit * self.units
- def calc_charged_vat(self):
- return self.calc_base() * self.vat
+ @property
+ def charged_vat(self):
+ return self.base * self.vat
def to_dict(self):
return {
"description": self.description,
"units": self.units,
- "price-unit": round(self.price_unit, Conf.CURRENCY_DECIMAL),
- "vat": self.vat
+ "price-unit": Conf.round(self.price_unit),
+ "vat": self.vat,
+ "vat-charged": Conf.round(self.charged_vat),
+ "base": Conf.round(self.base),
+ "total": Conf.round(self.total)
}
class Customer:
@@ -71,10 +83,10 @@ class Invoice:
products = None,
customer = None,
taxes = None,
- id = None):
+ number = None):
# Initializes to empty state if not set
self.set_type(type or tuple(Invoice._TYPES)[0])
- self.id = id
+ self.number = number
self.set_series(series or tuple(Invoice.SERIES.keys())[0])
self.date = idate or date.today()
self.notes = notes or ""
@@ -96,8 +108,9 @@ class Invoice:
% Invoice._TYPES)
self.type = type
- def format_id(self):
- return Invoice.ID_FORMAT(self.series, self.date, self.id)
+ @property
+ def id(self):
+ return Invoice.ID_FORMAT(self.series, self.date, self.number)
def __repr__(self):
return "<Invoice object>\n\t"+\
@@ -180,21 +193,10 @@ class Invoice:
@classmethod
- def load_by_idrepr(cls, id_repr, type="sent"):
- with sqlite3.connect(cls.DB_FILE) as conn:
- conn.row_factory = sqlite3.Row
-
- c = conn.cursor()
- c.execute("""SELECT id, series, type FROM invoices
- WHERE id_repr = ? AND type = ?""", (id_repr, type))
- res = c.fetchone()
- if res is None:
- return None
- return cls.load(res["id"], res["series"], type)
-
- @classmethod
- def load(cls, id, series, type="sent"):
+ def load(cls, id, type="sent", debug=False):
with sqlite3.connect(cls.DB_FILE) as conn:
+ if debug:
+ conn.set_trace_callback(print)
conn.row_factory = sqlite3.Row
c = conn.cursor()
@@ -202,8 +204,7 @@ class Invoice:
# PRODUCTS
products = ()
c.execute("""SELECT * FROM products WHERE invoice_id = ?
- AND invoice_series = ?
- AND invoice_type = ?""", (id, series, type))
+ AND invoice_type = ?""", (id, type))
res = c.fetchone()
while res is not None:
desc = res["description"]
@@ -216,8 +217,7 @@ class Invoice:
# TAXES
taxes = ()
c.execute("""SELECT * FROM taxes WHERE invoice_id = ?
- AND invoice_series = ?
- AND invoice_type = ?""", (id, series, type))
+ AND invoice_type = ?""", (id, type))
res = c.fetchone()
while res is not None:
taxes += (Tax(res["name"], res["ratio"]) ,)
@@ -225,11 +225,10 @@ class Invoice:
# INVOICE
c.execute("""SELECT * FROM invoices WHERE id = ?
- AND series = ?
- AND type = ?""", (id, series, type))
+ AND type = ?""", (id, type))
res = c.fetchone()
return cls(
- id = id,
+ number = res["number"],
type = res["type"],
series = res["series"],
idate = datetime.strptime(res["date"],"%Y-%m-%d").date(),
@@ -249,13 +248,12 @@ class Invoice:
conn.row_factory = sqlite3.Row
c = conn.cursor()
- c.execute(("SELECT id, series, type FROM invoices " + cond), vals)
- return tuple( cls.load( int(r["id"]),
- int(r["series"]),
- r["type"] ) for r in c.fetchall() )
+ c.execute(("SELECT id, type FROM invoices " + cond), vals)
+ return tuple( cls.load( r["id"], r["type"] ) \
+ for r in c.fetchall() )
@classmethod
- def latest_id(cls, series, type, debug=False):
+ def latest_number(cls, series, type, debug=False):
with sqlite3.connect(cls.DB_FILE) as conn:
if debug:
conn.set_trace_callback(print)
@@ -263,15 +261,15 @@ class Invoice:
conn.row_factory = sqlite3.Row
c = conn.cursor()
- c.execute("""SELECT id FROM invoices
+ c.execute("""SELECT number FROM invoices
WHERE series = ? AND type = ?
- ORDER BY(id) DESC LIMIT 1""", (series, type))
+ ORDER BY(number) DESC LIMIT 1""", (series, type))
res = c.fetchall()
- latest_id = 0
+ latest_number = 0
if len(res) != 0:
- latest_id = int(res[0]["id"])
- return latest_id
+ latest_number = int(res[0]["number"])
+ return latest_number
@classmethod
def load_by_date(cls, year, quarter=None):
@@ -297,14 +295,14 @@ class Invoice:
def persist(self):
conn = sqlite3.connect(Invoice.DB_FILE)
- if self.id is None:
- self.id = self.latest_id(self.series, self.type) + 1
+ if self.number is None:
+ self.number = self.latest_number(self.series, self.type) + 1
c = conn.cursor()
c.execute("""INSERT INTO invoices (
type,
series,
+ number,
id,
- id_repr,
date,
notes,
customer_id,
@@ -313,8 +311,8 @@ class Invoice:
) VALUES (?,?,?,?,?,?,?,?,?)""", (
self.type,
self.series,
+ self.number,
self.id,
- self.format_id(),
self.date.strftime("%Y-%m-%d"),
self.notes,
self.customer.id,
@@ -324,15 +322,13 @@ class Invoice:
for p in self.products:
c.execute("""INSERT INTO products (
invoice_id,
- invoice_series,
invoice_type,
description,
units,
price_unit,
vat
- ) VALUES (?,?,?,?,?,?,?)""",(
+ ) VALUES (?,?,?,?,?,?)""",(
self.id,
- self.series,
self.type,
p.description,
p.units,
@@ -342,36 +338,47 @@ class Invoice:
for x in self.taxes:
c.execute("""INSERT INTO taxes (
invoice_id,
- invoice_series,
invoice_type,
name,
ratio
- ) VALUES (?,?,?,?,?)""", (
+ ) VALUES (?,?,?,?)""", (
self.id,
- self.series,
self.type,
x.name,
x.ratio))
conn.commit()
conn.close()
- return self.id
+ return (self.id, self.type)
+ @property
+ def vat_charged(self):
+ return sum(p.charged_vat for p in self.products)
+ @property
+ def base(self):
+ return sum(p.base for p in self.products)
+ @property
+ def total(self):
+ return sum(p.total for p in self.products) + \
+ sum(self.base*t.ratio for t in self.taxes)
def to_dict(self):
return {
"products": tuple(p.to_dict() for p in self.products),
- "taxes": tuple(t.to_dict() for t in self.taxes),
+ "taxes": tuple(dict(t.to_dict(), applied=Conf.round(self.base*t.ratio)) for t in self.taxes),
"type": self.type,
- "id": self.format_id(),
+ "id": self.id,
"date": self.date.strftime("%Y-%m-%d"),
"customer": {
"id": self.customer.id,
"name": self.customer.name,
"address": self.customer.address,
},
+ "base": self.base,
"notes": self.notes,
+ "total": Conf.round(self.total),
+ "vat-charged": self.vat_charged
}
def to_json(self):
return json.dumps(self.to_dict())
@@ -380,7 +387,7 @@ class Invoice:
row = OrderedDict(
type = self.type,
- id = self.format_id(),
+ id = self.id,
date = self.date.strftime("%Y-%m-%d"),
customer_id = self.customer.id,
customer_name = self.customer.name,
@@ -398,9 +405,9 @@ class Invoice:
vat_base = 0
charged_vat = 0
for product in ps:
- vat_base += product.calc_base()
- charged_vat += product.calc_charged_vat()
- row[to_base_key(vat)] = round(vat_base, Conf.CURRENCY_DECIMAL)
+ vat_base += product.base
+ charged_vat += product.charged_vat
+ row[to_base_key(vat)] = round(vat_base, Conf.CURRENCY_DECIMAL)
row[to_vat_key(vat)] = round(charged_vat, Conf.CURRENCY_DECIMAL)
total_base += vat_base