No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

150 líneas
5.0KB

  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 -- Description|Amount|Unit|Rate
  11. Issued -- Date when the invoice was issued in YYYY-MM-DD format
  12. Due -- Due date YYYY-MM-DD
  13. Delivered -- Date when the services were delivered in YYYY-MM-DD format
  14. Note -- a note at the and of the invoice
  15. Advance -- amount of money already paid
  16. Translate -- whether to use english translations for various text blocks
  17. """
  18. data_template = """\
  19. Item:Description|Amount|Unit|Rate
  20. Issued:
  21. Delivered:
  22. Due:
  23. Advance: 0
  24. """
  25. Translate: 0
  26. _directory = "income"
  27. _regex = re.compile("^(?P<date>[0-9]{8})-(?P<number>[0-9]{3})-(?P<company_name>[a-z0-9-]+)$")
  28. _template = "{date}-{number:03}-{company_name}"
  29. def _item_class(self):
  30. return Invoice
  31. def _select(self, selector):
  32. if isinstance(selector, str):
  33. match = self._regex.match(selector)
  34. if match:
  35. selector = match.groupdict()
  36. selector["number"] = int(selector["number"])
  37. else:
  38. try:
  39. selector = int(selector)
  40. except TypeError:
  41. raise ItemNotFoundError("Item not found: {0}".format(selector))
  42. return super(Invoices, self)._select(selector)
  43. def new(self, company_name):
  44. if company_name not in self._db.companies:
  45. raise ItemNotFoundError("Company '{0}' not found.".format(company_name))
  46. try:
  47. number = max(item.number for item in self) + 1
  48. except ValueError:
  49. number = 1
  50. date = time.strftime("%Y%m%d")
  51. name = self._template.format(**vars())
  52. return super(Invoices, self).new(name)
  53. class Invoice(Item):
  54. def _data_class(self):
  55. return InvoiceData
  56. def _postprocess(self):
  57. self._selector["number"] = int(self.number)
  58. class InvoiceData(Data):
  59. _fields = ["issued", "due", "delivered", "paid", "payment", "advance", "translate"]
  60. _multivalue_fields = ["itemheading", "item", "address", "note"]
  61. _date_regex = re.compile(r"^(\d{4})-?(\d{2})-?(\d{2})$")
  62. _item_regex = re.compile(r"^([^|]*)\|(-?\d+)\|([^|]*)\|(-?\d+)$")
  63. _number_template = "{year}{number:03}"
  64. def _parse_date(self, date):
  65. match = self._date_regex.match(date)
  66. if not match:
  67. raise ValueError("Bad date format: {0}".format(date))
  68. log.debug("Date match: {0}".format(match.groups()))
  69. return datetime.date(*(int(f) for f in match.groups()))
  70. def _postprocess(self):
  71. log.debug(self._data)
  72. self._postprocess_number()
  73. self._postprocess_items()
  74. self._postprocess_dates()
  75. self.rename_key("note", "notes")
  76. def _postprocess_number(self):
  77. self._data["number"] = self._number_template.format(
  78. year = self.year,
  79. number = self.number)
  80. def _postprocess_items(self):
  81. items = []
  82. for item in self.item:
  83. match = self._item_regex.match(item)
  84. if not match:
  85. raise ValueError("Bad item format: {0}".format(item))
  86. description, amount, unit, rate = match.groups()
  87. items.append((description, float(amount), unit, float(rate)))
  88. del self._data["item"]
  89. m_advance = float(self.advance or 0)
  90. m_sum = sum(item[1] * item[3] for item in items)
  91. self._data["items"] = items
  92. self._data["sum"] = m_sum
  93. self._data["advance"] = m_advance
  94. self._data["topay"] = m_sum - m_advance
  95. self._data["translate"] = bool(int(self.translate))
  96. def _postprocess_dates(self):
  97. if self.issued:
  98. try:
  99. issued = self._parse_date(self.issued)
  100. except ValueError:
  101. raise ValueError("Bad issued format: {0}".format(self.issued))
  102. else:
  103. issued = datetime.datetime.now()
  104. if self.delivered:
  105. try:
  106. delivered = self._parse_date(self.delivered)
  107. except ValueError:
  108. raise ValueError("Bad delivered format: {0}".format(self.delivered))
  109. else:
  110. delivered = datetime.datetime.now()
  111. if self.due:
  112. try:
  113. due = self._parse_date(self.due)
  114. except ValueError:
  115. try:
  116. due = datetime.timedelta(int(re.sub("^\+", "", self.due)))
  117. except ValueError:
  118. raise ValueError("Bad due format: {0}".format(self.due))
  119. else:
  120. due = datetime.timedelta(14)
  121. log.debug("Issued : {0}".format(issued))
  122. log.debug("Delivered: {0}".format(delivered))
  123. log.debug("Due : {0}".format(due))
  124. if isinstance(due, datetime.timedelta):
  125. due += issued
  126. self._data["issued"] = issued
  127. self._data["delivered"] = delivered
  128. self._data["due"] = due