| @@ -1,3 +1,5 @@ | |||||
| __pycache__ | |||||
| *.*~ | |||||
| *.pyc | *.pyc | ||||
| *.pyo | *.pyo | ||||
| *.sw[op] | |||||
| __pycache__ | |||||
| @@ -3,6 +3,7 @@ | |||||
| from __future__ import print_function | from __future__ import print_function | ||||
| import os, sys, argparse, datetime, subprocess | import os, sys, argparse, datetime, subprocess | ||||
| import locale | |||||
| import logging | import logging | ||||
| logging.basicConfig(level=logging.DEBUG) | logging.basicConfig(level=logging.DEBUG) | ||||
| log = logging.getLogger() | log = logging.getLogger() | ||||
| @@ -179,7 +180,10 @@ class Application: | |||||
| log.debug("Creating TeX invoice...") | log.debug("Creating TeX invoice...") | ||||
| self._check_path(self.tmp_path) | self._check_path(self.tmp_path) | ||||
| result = tempita.Template(open(tex_template).read()).substitute( | 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)) | open(tex_file, "w").write(str(result)) | ||||
| assert(os.path.exists(tex_file)) | assert(os.path.exists(tex_file)) | ||||
| @@ -193,8 +197,8 @@ class Application: | |||||
| os.rename(tmp_pdf_file, pdf_file) | os.rename(tmp_pdf_file, pdf_file) | ||||
| assert(os.path.exists(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): | def _check_path(self, path): | ||||
| if not os.path.exists(path): | if not os.path.exists(path): | ||||
| @@ -14,9 +14,13 @@ class Companies(List): | |||||
| Name -- full company name | Name -- full company name | ||||
| Address -- company address, repeat to get multiple lines | 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 | Comment -- additional information that you want to see on the invoice | ||||
| """ | |||||
| """ | |||||
| _directory = "companies" | _directory = "companies" | ||||
| _regex = re.compile("^(?P<name>[a-z0-9-]+)$") | _regex = re.compile("^(?P<name>[a-z0-9-]+)$") | ||||
| _template = "{name}" | _template = "{name}" | ||||
| @@ -24,7 +28,11 @@ class Companies(List): | |||||
| Name: | Name: | ||||
| Address: | Address: | ||||
| Address: | Address: | ||||
| Number: | |||||
| ICO: | |||||
| DIC: | |||||
| ICDPH: | |||||
| IBAN: | |||||
| SWIFT: | |||||
| """ | """ | ||||
| def _item_class(self): | def _item_class(self): | ||||
| return Company | return Company | ||||
| @@ -34,9 +42,9 @@ class Company(Item): | |||||
| return CompanyData | return CompanyData | ||||
| class CompanyData(Data): | class CompanyData(Data): | ||||
| _fields = ["name", "number", "ic", "bank_account"] | |||||
| _fields = ["name", "ico", "dic", "icdph", "iban", "swift"] | |||||
| _multivalue_fields = ["address", "comment"] | _multivalue_fields = ["address", "comment"] | ||||
| def _postprocess(self): | def _postprocess(self): | ||||
| self.rename_key("ic", "number") | |||||
| #self.rename_key("ic", "number") | |||||
| self.rename_key("comment", "comments") | self.rename_key("comment", "comments") | ||||
| @@ -13,12 +13,19 @@ class Invoices(List): | |||||
| When editing data files, you can use the following | When editing data files, you can use the following | ||||
| directives: | 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 | 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 | Note -- a note at the and of the invoice | ||||
| Advance -- amount of money already paid | |||||
| """ | """ | ||||
| data_template = """\ | data_template = """\ | ||||
| Item: | |||||
| Item:Description|Amount|Unit|Rate | |||||
| Issued: | |||||
| Delivered: | |||||
| Due: | |||||
| Advance: 0 | |||||
| """ | """ | ||||
| _directory = "income" | _directory = "income" | ||||
| _regex = re.compile("^(?P<date>[0-9]{8})-(?P<number>[0-9]{3})-(?P<company_name>[a-z0-9-]+)$") | _regex = re.compile("^(?P<date>[0-9]{8})-(?P<number>[0-9]{3})-(?P<company_name>[a-z0-9-]+)$") | ||||
| @@ -59,10 +66,10 @@ class Invoice(Item): | |||||
| self._selector["number"] = int(self.number) | self._selector["number"] = int(self.number) | ||||
| class InvoiceData(Data): | class InvoiceData(Data): | ||||
| _fields = ["due", "paid", "payment"] | |||||
| _fields = ["issued", "due", "delivered", "paid", "payment", "advance"] | |||||
| _multivalue_fields = ["item", "address", "note"] | _multivalue_fields = ["item", "address", "note"] | ||||
| _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$") | _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}" | _number_template = "{year}{number:03}" | ||||
| def _parse_date(self, date): | def _parse_date(self, date): | ||||
| @@ -90,14 +97,33 @@ class InvoiceData(Data): | |||||
| match = self._item_regex.match(item) | match = self._item_regex.match(item) | ||||
| if not match: | if not match: | ||||
| raise ValueError("Bad item format: {0}".format(item)) | 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"] | 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["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): | 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: | if self.due: | ||||
| try: | try: | ||||
| due = self._parse_date(self.due) | due = self._parse_date(self.due) | ||||
| @@ -108,8 +134,12 @@ class InvoiceData(Data): | |||||
| raise ValueError("Bad due format: {0}".format(self.due)) | raise ValueError("Bad due format: {0}".format(self.due)) | ||||
| else: | else: | ||||
| due = datetime.timedelta(14) | 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): | 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 | self._data["due"] = due | ||||
| @@ -1,10 +1,11 @@ | |||||
| \documentclass[10pt]{article} | \documentclass[10pt]{article} | ||||
| \usepackage[utf8]{inputenc} | \usepackage[utf8]{inputenc} | ||||
| \usepackage[T1]{fontenc} | \usepackage[T1]{fontenc} | ||||
| \usepackage[left=0.5cm,right=0.5cm,top=0.5cm,bottom=0.5cm,noheadfoot,nomarginpar,showframe]{geometry} | |||||
| \usepackage{lmodern} | \usepackage{lmodern} | ||||
| %\usepackage[czech]{babel} | |||||
| \usepackage{a4wide} | |||||
| \usepackage{tabularx} | \usepackage{tabularx} | ||||
| \usepackage{tabu} | |||||
| \usepackage{multirow} | |||||
| \usepackage{graphicx} | \usepackage{graphicx} | ||||
| \renewcommand{\familydefault}{\sfdefault} | \renewcommand{\familydefault}{\sfdefault} | ||||
| \setlength{\extrarowheight}{3pt} | \setlength{\extrarowheight}{3pt} | ||||
| @@ -12,16 +13,19 @@ | |||||
| \begin{document} | \begin{document} | ||||
| \footnotesize | \footnotesize | ||||
| \pagestyle{empty} | |||||
| \begin{center} | \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: | {{py: | ||||
| a1 = issuer.address[:] | a1 = issuer.address[:] | ||||
| a2 = customer.address[:] | a2 = customer.address[:] | ||||
| @@ -29,13 +33,29 @@ a1 += (len(a2)-len(a1))*[""] | |||||
| a2 += (len(a1)-len(a2))*[""] | a2 += (len(a1)-len(a2))*[""] | ||||
| }} | }} | ||||
| {{for f1, f2 in zip(a1, 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}} | {{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: | {{py: | ||||
| a1 = issuer.comments[:] | a1 = issuer.comments[:] | ||||
| a2 = customer.comments[:] | a2 = customer.comments[:] | ||||
| @@ -43,45 +63,71 @@ a1 += (len(a2)-len(a1))*[""] | |||||
| a2 += (len(a1)-len(a2))*[""] | a2 += (len(a1)-len(a2))*[""] | ||||
| }} | }} | ||||
| {{for f1, f2 in zip(a1, 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}} | {{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}} | {{if invoice.notes}} | ||||
| & & & \\ | |||||
| \hline | |||||
| \multicolumn{2}{X|}{} & \multicolumn{2}{X}{} \\ \hline | |||||
| & & & \\ | & & & \\ | ||||
| \bf Poznámky: & & & \\[1em] | \bf Poznámky: & & & \\[1em] | ||||
| {{for note in invoice.notes}} | {{for note in invoice.notes}} | ||||
| \multicolumn{4}{|l|}{\large {{note}}} \\ | |||||
| \multicolumn{4}{l}{\large {{note}}} \\ | |||||
| {{endfor}} | {{endfor}} | ||||
| {{endif}} | {{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 | \hline | ||||
| \multicolumn{4}{r}{} \\ | \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{tabularx} | ||||
| \end{center} | \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} | \end{document} | ||||