You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

invoices.py 3.6KB

9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/usr/bin/python3
  2. import os, sys, re, time, datetime
  3. import logging
  4. log = logging.getLogger()
  5. from invoice.db.base import *
  6. class Invoices(List):
  7. """Company invoice.
  8. When editing data files, you can use the following
  9. directives:
  10. Item -- price, followed by ':', optional whitespace and item description
  11. Due -- Due date YYYY-MM-DD
  12. Note -- a note at the and of the invoice
  13. """
  14. data_template = """\
  15. Item:
  16. """
  17. _directory = "income"
  18. _regex = re.compile("^(?P<date>[0-9]{8})-(?P<number>[0-9]{3})-(?P<company_name>[a-z0-9-]+)$")
  19. _template = "{date}-{number:03}-{company_name}"
  20. def _item_class(self):
  21. return Invoice
  22. def _select(self, selector):
  23. if isinstance(selector, str):
  24. match = self._regex.match(selector)
  25. if match:
  26. selector = match.groupdict()
  27. selector["number"] = int(selector["number"])
  28. else:
  29. try:
  30. selector = int(selector)
  31. except TypeError:
  32. raise ItemNotFoundError("Item not found: {}".format(selector))
  33. return super(Invoices, self)._select(selector)
  34. def new(self, company_name):
  35. if company_name not in self._db.companies:
  36. raise ItemNotFoundError("Company '{}' not found.".format(company_name))
  37. try:
  38. number = max(item.number for item in self) + 1
  39. except ValueError:
  40. number = 1
  41. date = time.strftime("%Y%m%d")
  42. name = self._template.format(**vars())
  43. return super(Invoices, self).new(name)
  44. class Invoice(Item):
  45. def _data_class(self):
  46. return InvoiceData
  47. def _postprocess(self):
  48. self._selector["number"] = int(self.number)
  49. class InvoiceData(Data):
  50. _fields = ["due", "paid", "payment"]
  51. _multivalue_fields = ["item", "address", "note"]
  52. _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$")
  53. _item_regex = re.compile(r"^(-?\d+)[:;]\s*(.*)$")
  54. _number_template = "{year}{number:03}"
  55. def _parse_date(self, date):
  56. match = self._date_regex.match(self.date)
  57. if not match:
  58. raise ValueError("Bad date format: {}".format(date))
  59. return datetime.date(*(int(f) for f in match.groups()))
  60. def _postprocess(self):
  61. log.debug(self._data)
  62. self._postprocess_number()
  63. self._postprocess_items()
  64. self._postprocess_dates()
  65. self.rename_key("note", "notes")
  66. def _postprocess_number(self):
  67. self._data["number"] = self._number_template.format(
  68. year = self.year,
  69. number = self.number)
  70. def _postprocess_items(self):
  71. items = []
  72. for item in self.item:
  73. match = self._item_regex.match(item)
  74. if not match:
  75. raise ValueError("Bad item format: {}".format(item))
  76. price, description = match.groups()
  77. items.append((description, int(price)))
  78. del self._data["item"]
  79. self._data["items"] = items
  80. self._data["sum"] = sum(item[1] for item in items)
  81. def _postprocess_dates(self):
  82. date = self._parse_date(self._item.date)
  83. if "due" in self._data:
  84. try:
  85. due = self._parse_date(self.due)
  86. except ValueError:
  87. try:
  88. due = datetime.timedelta(int(re.sub("^+", "", self.due)))
  89. except ValueError:
  90. raise ValueError("Bad due format: {}".format(self.due))
  91. else:
  92. due = datetime.timedelta(14)
  93. if isinstance(due, datetime.timedelta):
  94. due += date
  95. self._data["date"] = date
  96. self._data["due"] = due