diff --git a/.gitignore b/.gitignore index 008af35..df3f861 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -__pycache__ +*.*~ *.pyc *.pyo +*.sw[op] +__pycache__ diff --git a/lib/invoice/cli.py b/lib/invoice/cli.py index bf645e3..6a869a4 100644 --- a/lib/invoice/cli.py +++ b/lib/invoice/cli.py @@ -3,6 +3,7 @@ from __future__ import print_function import os, sys, argparse, datetime, subprocess +import locale import logging logging.basicConfig(level=logging.DEBUG) log = logging.getLogger() @@ -179,7 +180,10 @@ class Application: log.debug("Creating TeX invoice...") self._check_path(self.tmp_path) result = tempita.Template(open(tex_template).read()).substitute( - invoice=invoice_data, issuer=issuer_data, customer=customer_data) + invoice=invoice_data, + issuer=issuer_data, + customer=customer_data, + eur=lambda x: '{:20,.2f} EUR'.format(x).replace(',', '\\,').replace('.', ',')) open(tex_file, "w").write(str(result)) assert(os.path.exists(tex_file)) @@ -193,8 +197,8 @@ class Application: os.rename(tmp_pdf_file, pdf_file) assert(os.path.exists(pdf_file)) - log.debug("Running PDF viewer...") - subprocess.call((self.pdf_program, pdf_file)) + #log.debug("Running PDF viewer...") + #subprocess.call((self.pdf_program, pdf_file)) def _check_path(self, path): if not os.path.exists(path): diff --git a/lib/invoice/db/companies.py b/lib/invoice/db/companies.py index f75d6cc..b8f8b7f 100644 --- a/lib/invoice/db/companies.py +++ b/lib/invoice/db/companies.py @@ -14,9 +14,13 @@ class Companies(List): Name -- full company name Address -- company address, repeat to get multiple lines - Number -- identification number + ICO -- identification number + DIC -- tax identification number + ICDPH + IBAN + SWIFT Comment -- additional information that you want to see on the invoice - """ + """ _directory = "companies" _regex = re.compile("^(?P[a-z0-9-]+)$") _template = "{name}" @@ -24,7 +28,11 @@ class Companies(List): Name: Address: Address: -Number: +ICO: +DIC: +ICDPH: +IBAN: +SWIFT: """ def _item_class(self): return Company @@ -34,9 +42,9 @@ class Company(Item): return CompanyData class CompanyData(Data): - _fields = ["name", "number", "ic", "bank_account"] + _fields = ["name", "ico", "dic", "icdph", "iban", "swift"] _multivalue_fields = ["address", "comment"] def _postprocess(self): - self.rename_key("ic", "number") + #self.rename_key("ic", "number") self.rename_key("comment", "comments") diff --git a/lib/invoice/db/invoices.py b/lib/invoice/db/invoices.py index a66c82a..d27b87c 100644 --- a/lib/invoice/db/invoices.py +++ b/lib/invoice/db/invoices.py @@ -13,12 +13,19 @@ class Invoices(List): When editing data files, you can use the following directives: - Item -- price, followed by ':', optional whitespace and item description + Item -- Description|Amount|Unit|Rate + Issued -- Date when the invoice was issued in YYYY-MM-DD format Due -- Due date YYYY-MM-DD + Delivered -- Date when the services were delivered in YYYY-MM-DD format Note -- a note at the and of the invoice + Advance -- amount of money already paid """ data_template = """\ -Item: +Item:Description|Amount|Unit|Rate +Issued: +Delivered: +Due: +Advance: 0 """ _directory = "income" _regex = re.compile("^(?P[0-9]{8})-(?P[0-9]{3})-(?P[a-z0-9-]+)$") @@ -59,10 +66,10 @@ class Invoice(Item): self._selector["number"] = int(self.number) class InvoiceData(Data): - _fields = ["due", "paid", "payment"] + _fields = ["issued", "due", "delivered", "paid", "payment", "advance"] _multivalue_fields = ["item", "address", "note"] _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$") - _item_regex = re.compile(r"^(-?\d+)[:;]\s*(.*)$") + _item_regex = re.compile(r"^([^|]*)\|(-?\d+)\|([^|]*)\|(-?\d+)$") _number_template = "{year}{number:03}" def _parse_date(self, date): @@ -90,14 +97,33 @@ class InvoiceData(Data): match = self._item_regex.match(item) if not match: raise ValueError("Bad item format: {0}".format(item)) - price, description = match.groups() - items.append((description, int(price))) + description, amount, unit, rate = match.groups() + items.append((description, float(amount), unit, float(rate))) del self._data["item"] + m_advance = float(self.advance or 0) + m_sum = sum(item[1] * item[3] for item in items) self._data["items"] = items - self._data["sum"] = sum(item[1] for item in items) + self._data["sum"] = m_sum + self._data["advance"] = m_advance + self._data["topay"] = m_sum - m_advance def _postprocess_dates(self): - date = self._parse_date(self._item.date) + if self.issued: + try: + issued = self._parse_date(self.issued) + except ValueError: + raise ValueError("Bad issued format: {0}".format(self.issued)) + else: + issued = datetime.datetime.now() + + if self.delivered: + try: + delivered = self._parse_date(self.delivered) + except ValueError: + raise ValueError("Bad delivered format: {0}".format(self.delivered)) + else: + delivered = datetime.datetime.now() + if self.due: try: due = self._parse_date(self.due) @@ -108,8 +134,12 @@ class InvoiceData(Data): raise ValueError("Bad due format: {0}".format(self.due)) else: due = datetime.timedelta(14) - log.debug("Due: {0}".format(due)) + log.debug("Issued : {0}".format(issued)) + log.debug("Delivered: {0}".format(delivered)) + log.debug("Due : {0}".format(due)) if isinstance(due, datetime.timedelta): - due += date - self._data["date"] = date + due += issued + + self._data["issued"] = issued + self._data["delivered"] = delivered self._data["due"] = due diff --git a/templates/invoice.tex b/templates/invoice.tex index 5b11d51..1f2f920 100644 --- a/templates/invoice.tex +++ b/templates/invoice.tex @@ -1,10 +1,11 @@ \documentclass[10pt]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} +\usepackage[left=0.5cm,right=0.5cm,top=0.5cm,bottom=0.5cm,noheadfoot,nomarginpar,showframe]{geometry} \usepackage{lmodern} -%\usepackage[czech]{babel} -\usepackage{a4wide} \usepackage{tabularx} +\usepackage{tabu} +\usepackage{multirow} \usepackage{graphicx} \renewcommand{\familydefault}{\sfdefault} \setlength{\extrarowheight}{3pt} @@ -12,16 +13,19 @@ \begin{document} \footnotesize +\pagestyle{empty} + \begin{center} -\begin{tabularx}{\textwidth}{|XXXX|} -\cline{3-4} -\multicolumn{2}{X}{} & \multicolumn{2}{|X|}{} \\ -\multicolumn{2}{X}{} & \multicolumn{2}{|l|}{\large Faktura: \hfill {{invoice.number}}} \\ -\multicolumn{2}{X}{} & \multicolumn{2}{|X|}{} \\ -\hline -& & & \\ -\bf Dodavatel: & & \bf Odběratel & \\[1em] -\multicolumn{2}{|l}{\large\bf {{issuer.name}}} & \multicolumn{2}{l|}{\large\bf {{customer.name}}} \\ +\begin{tabularx}{\textwidth}{XXXX} + +&&& \\ +\multicolumn{4}{l}{\bf \large Faktúra: \hfill {{invoice.number}}} \\ +&&& \\ \hline +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ +\multicolumn{2}{l|}{\bf Dodávateľ:} & \multicolumn{2}{X}{\bf Odberateľ} \\ +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ + +\multicolumn{2}{l|}{\large\bf {{issuer.name}}} & \multicolumn{2}{l}{\large\bf {{customer.name}}} \\ {{py: a1 = issuer.address[:] a2 = customer.address[:] @@ -29,13 +33,29 @@ a1 += (len(a2)-len(a1))*[""] a2 += (len(a1)-len(a2))*[""] }} {{for f1, f2 in zip(a1, a2)}} -\multicolumn{2}{|l}{\large {{f1}}} & \multicolumn{2}{l|}{\large {{f2}}} \\ +\multicolumn{2}{l|}{\large {{f1}}} & \multicolumn{2}{l}{\large {{f2}}} \\ {{endfor}} -& & & \\ -\multicolumn{2}{|l}{IČ: {{issuer.number}}} & \multicolumn{2}{l|}{IČ: {{customer.number}}} \\ -& & & \\ +% COMPANY ID NUMBERS +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ + \multicolumn{2}{l|}{IČO: {{issuer.ico}}} & \multicolumn{2}{l}{IČO: {{if customer.icdph}}\hspace{0.3cm}{{endif}} {{customer.ico}}} \\ + \multicolumn{2}{l|}{DIČ: {{issuer.dic}}} & \multicolumn{2}{l}{DIČ: {{if customer.icdph}}\hspace{0.3cm}{{endif}} {{customer.dic}}} \\ +{{if issuer.icdph or customer.icdph}} + {{if issuer.icdph}} + \multicolumn{2}{l|}{IČDPH: {{issuer.icdph}}} & + {{else}} + \multicolumn{2}{l|}{Neplatca DPH} & + {{endif}} + {{if customer.icdph}} + \multicolumn{2}{l}{IČDPH: {{customer.icdph}}} \\ + {{else}} + \multicolumn{2}{l}{} \\ + {{endif}} +{{endif}} + +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ +% Comments {{py: a1 = issuer.comments[:] a2 = customer.comments[:] @@ -43,45 +63,71 @@ a1 += (len(a2)-len(a1))*[""] a2 += (len(a1)-len(a2))*[""] }} {{for f1, f2 in zip(a1, a2)}} -\multicolumn{2}{|l}{ {{f1}}} & \multicolumn{2}{l|}{ {{f2}}} \\ +\multicolumn{2}{l|}{ \footnotesize {{f1}} } & \multicolumn{2}{l}{ \footnotesize {{f2}} } \\ {{endfor}} +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ -& & & \\ -\hline -& & & \\ -\bf Platební podmínky: & & & \\[1em] -\large Forma úhrady: & \large {{"hotově" if invoice.payment=="cash" else "převodem"}} & \large Datum vystavení: & \multicolumn{1}{r|}{\large {{invoice.date.strftime("%d.%m.%Y")}}} \\ -\large Číslo účtu: & \large {{issuer.bank_account}} & \multicolumn{2}{l|}{\large\bf Datum splatnosti: \hfill {{invoice.due.strftime("%d.%m.%Y")}}} \\ -\large Variabilní symbol: & \large {{invoice.number}} & & \\ - +\multicolumn{2}{l|}{\bf Platobné podmienky:} & & \\ +Forma úhrady: & \multicolumn{1}{l|}{ {{"v~hotovosti" if invoice.payment=="cash" else "prevodom"}}} & & \\ +IBAN: & \multicolumn{1}{l|}{ {{issuer.iban}}} & Dátum vystavenia: & \multicolumn{1}{r}{ {{invoice.issued.strftime("%d.%m.%Y")}}} \\ +SWIFT/BIC: & \multicolumn{1}{l|}{ {{issuer.swift}} } & \multicolumn{2}{l}{Dátum dodania: \hfill {{invoice.delivered.strftime("%d.%m.%Y")}}} \\ +Variabilný symbol: & \multicolumn{1}{l|}{ {{invoice.number}}} & \multicolumn{2}{l}{\large\bf Dátum splatnosti: \hfill {{invoice.due.strftime("%d.%m.%Y")}}} \\ +% COMMENTS {{if invoice.notes}} -& & & \\ -\hline +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ \hline & & & \\ \bf Poznámky: & & & \\[1em] {{for note in invoice.notes}} -\multicolumn{4}{|l|}{\large {{note}}} \\ +\multicolumn{4}{l}{\large {{note}}} \\ {{endfor}} {{endif}} +\multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ \hline + & & & \\ -\hline -& & & \\ -\bf Fakturujeme vám: & & & \\[1em] -{{for item in invoice.items}} -\multicolumn{4}{|l|}{\normalsize {{item[0]}} \hfill {{item[1]}} Kč} \\ -{{endfor}} +\multicolumn{4}{l}{\bf Faktúrujeme Vám za služby podľa Rámcovej zmluvy} \\ & & & \\ -\hline -& & & \\ -\multicolumn{3}{|l}{\large\bf Celkem k úhradě:} & \multicolumn{1}{r|}{\large\bf {{invoice.sum}} Kč} \\ + +% ITEMS +\multicolumn{4}{X}{% + \hspace*{-1.35\tabcolsep} + \begin{tabu} to \textwidth {% + >{\hsize=2.5\hsize}X[l]% + >{\hsize=0.5\hsize}X[r]% + >{\hsize=0.5\hsize}X[r]% + >{\hsize=0.5\hsize}X[r]% + >{\hsize=0.5\hsize}X[r]} + \normalsize \bf Popis položky & \bf Množstvo & \bf MJ & \bf Cena za MJ & \bf Celková cena \\ + {{for item in invoice.items}} + {{item[0]}} & {{item[1]}} & {{item[2]}} & {{eur(item[3])}} & {{eur(item[1] * item[3])}} \\ + {{endfor}} + \end{tabu} +} \\ + +%& & & \\ +%\hline +%& & & \\ +%\multicolumn{3}{l}{\large\bf Celkom k úhrade:} & \multicolumn{1}{r}{\large\bf {{eur(invoice.sum)}} } \\ & & & \\ \hline \multicolumn{4}{r}{} \\ -\multicolumn{4}{r}{\includegraphics[scale=1]{../signature.png}\hspace{.4cm} } \\ +%\multicolumn{4}{r}{\includegraphics[scale=1]{../signature.jpg}\hspace{.4cm} } \\ \end{tabularx} \end{center} +\vfill + +% Footer +\centering +\begin{tabu} to \textwidth {XX|XX|X|X[r]} +\hline + Vyhotovil: Peter Hatina && Prevzal: && Celková suma: & {{eur(invoice.sum)}} \\ \cline{5-6} + \multirow{2}{*}{\hspace*{1.0cm}\includegraphics[scale=0.3]{../signature.jpg} } && && Uhradené zálohami: & {{eur(invoice.advance)}} \\ \cline{5-6} + && && Zostáva uhradiť: & {{eur(invoice.topay)}} \\ \cline{5-6} + && && & \\ + && && \multirow{-2}{*}{K úhrade:} & \multirow{-2}{*}{ \bf \large {{eur(invoice.topay)}}\phantom} \\ +\end{tabu} + \end{document}