Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

110 Zeilen
3.5KB

  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 ':' and description
  11. Due -- Due date YYYY-MM-DD
  12. Note -- a note at the and of the invoice
  13. """
  14. data_template = """\
  15. Item: 0000: Item summary
  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 not match:
  26. raise ItemNotFoundError("Item not found: {}".format(selector))
  27. selector = match.groupdict()
  28. selector["number"] = int(selector["number"])
  29. return super(Invoices, self)._select(selector)
  30. def new(self, company_name):
  31. if company_name not in self._db.companies:
  32. raise ItemNotFoundError("Company '{}' not found.".format(company_name))
  33. try:
  34. number = max(item.number for item in self) + 1
  35. except ValueError:
  36. number = 1
  37. date = time.strftime("%Y%m%d")
  38. name = self._template.format(**vars())
  39. return super(Invoices, self).new(name)
  40. class Invoice(Item):
  41. def _data_class(self):
  42. return InvoiceData
  43. def _postprocess(self):
  44. self._selector["number"] = int(self.number)
  45. class InvoiceData(Data):
  46. _fields = ["payment"]
  47. _multivalue_fields = ["item", "address", "note"]
  48. _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$")
  49. _item_regex = re.compile(r"^(\d+)[:;]\s*(.*)$")
  50. _number_template = "{year}{number:03}"
  51. def _parse_date(self, date):
  52. match = self._date_regex.match(self.date)
  53. if not match:
  54. raise ValueError("Bad date format: {}".format(date))
  55. return datetime.date(*(int(f) for f in match.groups()))
  56. def _postprocess(self):
  57. log.debug(self._data)
  58. self._postprocess_number()
  59. self._postprocess_items()
  60. self._postprocess_dates()
  61. self.rename_key("note", "notes")
  62. def _postprocess_number(self):
  63. self._data["number"] = self._number_template.format(
  64. year = self.year,
  65. number = self.number)
  66. def _postprocess_items(self):
  67. items = []
  68. for item in self.item:
  69. match = self._item_regex.match(item)
  70. if not match:
  71. raise ValueError("Bad item format: {}".format(item))
  72. price, description = match.groups()
  73. items.append((description, int(price)))
  74. del self._data["item"]
  75. self._data["items"] = items
  76. self._data["sum"] = sum(item[1] for item in items)
  77. def _postprocess_dates(self):
  78. date = self._parse_date(self._item.date)
  79. if "due" in self._data:
  80. try:
  81. due = self._parse_date(self.due)
  82. except ValueError:
  83. try:
  84. due = datetime.timedelta(int(re.sub("^+", "", self.due)))
  85. except ValueError:
  86. raise ValueError("Bad due format: {}".format(self.due))
  87. else:
  88. due = datetime.timedelta(14)
  89. if isinstance(due, datetime.timedelta):
  90. due += date
  91. self._data["date"] = date
  92. self._data["due"] = due