summaryrefslogtreecommitdiff
path: root/fracture/invoice.py
diff options
context:
space:
mode:
Diffstat (limited to 'fracture/invoice.py')
-rw-r--r--fracture/invoice.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/fracture/invoice.py b/fracture/invoice.py
new file mode 100644
index 0000000..f0ab2f7
--- /dev/null
+++ b/fracture/invoice.py
@@ -0,0 +1,232 @@
+import sqlite3
+from datetime import date, datetime
+from configparser import ConfigParser
+import io
+
+class Tax:
+ def __init__(self, name="", ratio = 0.0):
+ self.name = name.upper()
+ self.ratio = ratio
+
+class Product:
+ def __init__(self, description, units=1.0,
+ price_unit=0.0, vat = 0.21):
+ if len(description) == 0:
+ raise ValueError("Product: No description provided")
+ self.description = description
+ self.units = units
+ self.price_unit = price_unit
+ self.vat = vat
+
+class Customer:
+ def __init__(self, name, id = "", address = ""):
+ if len(name) == 0:
+ raise ValueError("Customer: No name provided")
+ self.name = name
+ self.id = id
+ self.address = address
+
+class Invoice:
+ TYPES = {"sent", "received"}
+ SERIES = {}
+ ID_FORMAT = None
+ CURRENCY = None
+ CURRENCY_DECIMAL = 0
+ DB_FILE = None
+ DEFAULT_TAXES = ()
+
+ DEFAULT_PRODUCT_DESC = "Empty Product"
+ DEFAULT_CUSTOMER_DESC = "Empty Customer"
+
+ def __init__(self, type = None,
+ series = None,
+ idate = None,
+ notes= None,
+ products = None,
+ customers = None,
+ taxes = None):
+ # Initializes to empty state if not set
+ self.set_type(type or tuple(Invoice.TYPES)[0])
+ self.id = None
+ self.set_series(series or tuple(Invoice.SERIES.keys())[0])
+ self.date = idate or date.today()
+ self.notes = notes or ""
+ self.products = products or (Product(Invoice.DEFAULT_PRODUCT_DESC),
+ Product(Invoice.DEFAULT_PRODUCT_DESC))
+ self.customer = customers or Customer(Invoice.DEFAULT_CUSTOMER_DESC)
+ self.taxes = taxes or Invoice.DEFAULT_TAXES
+
+ def set_series(self, series):
+ if series not in Invoice.SERIES.keys():
+ raise ValueError ("Not valid series for Invoice. Valid are %s"
+ % Invoice.SERIES)
+ self.series = series
+
+ def set_type(self, type):
+ if type not in Invoice.TYPES:
+ raise ValueError ("Not valid type for Invoice. Valid are: %s"
+ % Invoice.TYPES)
+ self.type = type
+
+ def dump(self):
+ print(self.format_id())
+
+ def format_id(self):
+ return Invoice.ID_FORMAT(self.series, self.date, self.id)
+
+ def to_config(self):
+ strf = io.StringIO()
+ cfg = ConfigParser()
+ cfg["invoice"] = {
+ "series": self.series,
+ "date": self.date,
+ "type": self.type,
+ "notes": self.notes,
+ "customer-name": self.customer.name,
+ "customer-address": self.customer.address,
+ "customer-id": self.customer.id,
+ }
+ for i,p in enumerate(self.products):
+ cfg["product-"+str(i)] = {
+ "description": p.description,
+ "units": p.units,
+ "price_unit": p.price_unit,
+ "vat": p.vat
+ }
+
+ cfg["taxes"] = { x.name: x.ratio for x in self.taxes }
+
+ cfg.write(strf)
+ return strf.getvalue()
+
+
+ def from_config(self, config):
+ cfg = ConfigParser()
+ cfg.read_string(config)
+
+ # PRODUCTS SECTIONS
+ self.products = ()
+ for s in cfg.sections():
+ if not s.startswith("product"):
+ continue
+ desc = cfg[s]["description"]
+ un = float(cfg[s]["units"])
+ pu = float(cfg[s]["price_unit"])
+ vat = float(cfg[s]["vat"])
+ self.products += ( Product(desc,un,pu,vat) ,)
+ if cfg[s]["description"] == Invoice.DEFAULT_PRODUCT_DESC:
+ raise ValueError("Product name not set")
+ if len(self.products) == 0:
+ raise ValueError("No products assigned")
+
+ # TAXES SECTION
+ self.taxes = ()
+ if cfg.has_section("taxes"):
+ for x in cfg["taxes"]:
+ self.taxes += ( Tax(x, float(cfg["taxes"][x])) ,)
+
+ # INVOICE SECTION
+ if not cfg.has_section("invoice"):
+ raise ValueError("[invoice] section needed")
+ i = cfg["invoice"]
+ self.set_series(int(i["series"]))
+ self.date = datetime.strptime(i["date"], "%Y-%m-%d").date()
+ self.notes = i["notes"]
+ self.set_type(i["type"])
+ self.customer= Customer(i["customer-name"],
+ i["customer-address"],
+ i["customer-id"])
+ if i["customer-name"] == Invoice.DEFAULT_CUSTOMER_DESC:
+ raise ValueError("Customer name not set")
+
+ def persist(self):
+ conn = sqlite3.connect(Invoice.DB_FILE)
+ c = conn.cursor()
+ c.execute("""INSERT INTO invoices (
+ type,
+ series,
+ date,
+ notes,
+ customer_id,
+ customer_name,
+ customer_address
+ ) VALUES (?,?,?,?,?,?,?)""", (
+ self.type,
+ self.series,
+ self.date.strftime("%Y-%m-%d"),
+ self.notes,
+ self.customer.id,
+ self.customer.name,
+ self.customer.address
+ ))
+ self.id = c.lastrowid
+ for p in self.products:
+ c.execute("""INSERT INTO products (
+ invoice_id,
+ description,
+ units,
+ price_unit,
+ vat
+ ) VALUES (?,?,?,?,?)""",(
+ self.id,
+ p.description,
+ p.units,
+ p.price_unit,
+ p.vat
+ ))
+ for x in self.taxes:
+ c.execute("""INSERT INTO taxes (
+ invoice_id,
+ name,
+ ratio
+ ) VALUES (?,?,?)""", (self.id, x.name, x.ratio))
+
+ conn.commit()
+ conn.close()
+ return self.id
+
+ def load(id):
+ with sqlite3.connect(Invoice.DB_FILE) as conn:
+ conn.row_factory = sqlite3.Row
+
+ invoice = Invoice()
+
+ c = conn.cursor()
+
+ # PRODUCTS
+ products = ()
+ c.execute("""SELECT * FROM products WHERE invoice_id = ?""", (id,))
+ res = c.fetchone()
+ while res is not None:
+ desc = res["description"]
+ un = res["units"]
+ pu = res["price_unit"]
+ vat = res["vat"]
+ products += (Product(desc,un,pu,vat),)
+ res = c.fetchone()
+
+ # TAXES
+ taxes = ()
+ c.execute("""SELECT * FROM taxes WHERE invoice_id = ?""", (id,))
+ res = c.fetchone()
+ while res is not None:
+ taxes += (Tax(res["name"], res["ratio"]) ,)
+ res = c.fetchone()
+
+ # INVOICE
+ c.execute("""SELECT * FROM invoices WHERE id = ?""", (id,))
+ res = c.fetchone()
+ return Invoice(
+ res["type"],
+ res["series"],
+ res["date"],
+ res["notes"],
+ products,
+ Customer( res["customer_name"],
+ res["customer_id"],
+ res["customer_address"] ),
+ taxes)
+
+ def format(self):
+ # TODO
+ pass