|
- #!/usr/bin/python3
-
- import os, sys, re, time, datetime
-
- import logging
- log = logging.getLogger()
-
- from invoice.db.base import *
-
- class Invoices(List):
- """Company invoice.
-
- When editing data files, you can use the following
- directives:
-
- 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
- Translate -- whether to use english translations for various text blocks
- """
- data_template = """\
- Item:Description|Amount|Unit|Rate
- Issued:
- Delivered:
- Due:
- Advance: 0
- """
- Translate: 0
- _directory = "income"
- _regex = re.compile("^(?P<date>[0-9]{8})-(?P<number>[0-9]{3})-(?P<company_name>[a-z0-9-]+)$")
- _template = "{date}-{number:03}-{company_name}"
-
- def _item_class(self):
- return Invoice
-
- def _select(self, selector):
- if isinstance(selector, str):
- match = self._regex.match(selector)
- if match:
- selector = match.groupdict()
- selector["number"] = int(selector["number"])
- else:
- try:
- selector = int(selector)
- except TypeError:
- raise ItemNotFoundError("Item not found: {0}".format(selector))
- return super(Invoices, self)._select(selector)
-
- def new(self, company_name):
- if company_name not in self._db.companies:
- raise ItemNotFoundError("Company '{0}' not found.".format(company_name))
- try:
- number = max(item.number for item in self) + 1
- except ValueError:
- number = 1
- date = time.strftime("%Y%m%d")
- name = self._template.format(**vars())
- return super(Invoices, self).new(name)
-
- class Invoice(Item):
- def _data_class(self):
- return InvoiceData
-
- def _postprocess(self):
- self._selector["number"] = int(self.number)
-
- class InvoiceData(Data):
- _fields = ["issued", "due", "delivered", "paid", "payment", "advance", "translate"]
- _multivalue_fields = ["itemheading", "item", "address", "note"]
- _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$")
- _item_regex = re.compile(r"^([^|]*)\|(-?\d+)\|([^|]*)\|(-?\d+)$")
- _number_template = "{year}{number:03}"
-
- def _parse_date(self, date):
- match = self._date_regex.match(date)
- if not match:
- raise ValueError("Bad date format: {0}".format(date))
- log.debug("Date match: {0}".format(match.groups()))
- return datetime.date(*(int(f) for f in match.groups()))
-
- def _postprocess(self):
- log.debug(self._data)
- self._postprocess_number()
- self._postprocess_items()
- self._postprocess_dates()
- self.rename_key("note", "notes")
-
- def _postprocess_number(self):
- self._data["number"] = self._number_template.format(
- year = self.year,
- number = self.number)
-
- def _postprocess_items(self):
- items = []
- for item in self.item:
- match = self._item_regex.match(item)
- if not match:
- raise ValueError("Bad item format: {0}".format(item))
- 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"] = m_sum
- self._data["advance"] = m_advance
- self._data["topay"] = m_sum - m_advance
-
- self._data["translate"] = bool(int(self.translate))
-
- def _postprocess_dates(self):
- 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)
- except ValueError:
- try:
- due = datetime.timedelta(int(re.sub("^\+", "", self.due)))
- except ValueError:
- raise ValueError("Bad due format: {0}".format(self.due))
- else:
- due = datetime.timedelta(14)
- log.debug("Issued : {0}".format(issued))
- log.debug("Delivered: {0}".format(delivered))
- log.debug("Due : {0}".format(due))
- if isinstance(due, datetime.timedelta):
- due += issued
-
- self._data["issued"] = issued
- self._data["delivered"] = delivered
- self._data["due"] = due
|