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.

147 lines
5.1 KiB

  1. # Copyright 2019 ACSONE SA/NV
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from dateutil.relativedelta import relativedelta
  4. from odoo import api, fields, models
  5. from odoo.addons.queue_job.job import job
  6. QUEUE_CHANNEL = "root.CONTRACT_FORECAST"
  7. class ContractLine(models.Model):
  8. _inherit = "contract.line"
  9. forecast_period_ids = fields.One2many(
  10. comodel_name="contract.line.forecast.period",
  11. inverse_name="contract_line_id",
  12. string="Forecast Periods",
  13. required=False,
  14. )
  15. @api.multi
  16. def _prepare_contract_line_forecast_period(
  17. self, period_date_start, period_date_end, recurring_next_date
  18. ):
  19. self.ensure_one()
  20. return {
  21. "name": self._insert_markers(period_date_start, period_date_end),
  22. "contract_id": self.contract_id.id,
  23. "company_id": self.contract_id.company_id.id,
  24. "contract_line_id": self.id,
  25. "product_id": self.product_id.id,
  26. "date_start": period_date_start,
  27. "date_end": period_date_end,
  28. "date_invoice": recurring_next_date,
  29. "discount": self.discount,
  30. "price_unit": self.price_unit,
  31. "quantity": self._get_quantity_to_invoice(
  32. period_date_start, period_date_end, recurring_next_date
  33. ),
  34. }
  35. @api.multi
  36. def _get_contract_forecast_end_date(self):
  37. self.ensure_one()
  38. today = fields.Date.context_today(self)
  39. return today + self.get_relative_delta(
  40. self.contract_id.company_id.contract_forecast_rule_type,
  41. self.contract_id.company_id.contract_forecast_interval,
  42. )
  43. @api.multi
  44. def _get_generate_forecast_periods_criteria(self, period_date_end):
  45. self.ensure_one()
  46. if self.is_canceled or not self.active:
  47. return False
  48. contract_forecast_end_date = self._get_contract_forecast_end_date()
  49. if not self.date_end or self.is_auto_renew:
  50. return period_date_end < contract_forecast_end_date
  51. return (
  52. period_date_end < self.date_end
  53. and period_date_end < contract_forecast_end_date
  54. )
  55. @api.multi
  56. @job(default_channel=QUEUE_CHANNEL)
  57. def _generate_forecast_periods(self):
  58. values = []
  59. for rec in self:
  60. rec.forecast_period_ids.unlink()
  61. if rec.recurring_next_date:
  62. period_date_end = (
  63. rec.last_date_invoiced
  64. if rec.last_date_invoiced
  65. else rec.date_start - relativedelta(days=1)
  66. )
  67. while (
  68. period_date_end
  69. and rec._get_generate_forecast_periods_criteria(
  70. period_date_end
  71. )
  72. ):
  73. period_date_start = period_date_end + relativedelta(days=1)
  74. period_date_end = self.get_next_period_date_end(
  75. period_date_start,
  76. rec.recurring_rule_type,
  77. rec.recurring_interval,
  78. max_date_end=rec.date_end,
  79. )
  80. recurring_next_date = rec.get_next_invoice_date(
  81. period_date_start,
  82. rec.recurring_invoicing_type,
  83. rec.recurring_invoicing_offset,
  84. rec.recurring_rule_type,
  85. rec.recurring_interval,
  86. rec.date_end,
  87. )
  88. if period_date_end and recurring_next_date:
  89. values.append(
  90. rec._prepare_contract_line_forecast_period(
  91. period_date_start,
  92. period_date_end,
  93. recurring_next_date,
  94. )
  95. )
  96. return self.env["contract.line.forecast.period"].create(values)
  97. @api.model
  98. def create(self, values):
  99. contract_lines = super(ContractLine, self).create(values)
  100. for contract_line in contract_lines:
  101. contract_line.with_delay()._generate_forecast_periods()
  102. return contract_lines
  103. @api.model
  104. def _get_forecast_update_trigger_fields(self):
  105. return [
  106. "name",
  107. "sequence",
  108. "product_id",
  109. "date_start",
  110. "date_end",
  111. "quantity",
  112. "price_unit",
  113. "discount",
  114. "recurring_invoicing_type",
  115. "recurring_next_date",
  116. "recurring_rule_type",
  117. "recurring_interval",
  118. "is_canceled",
  119. "active",
  120. "is_auto_renew",
  121. ]
  122. @api.multi
  123. def write(self, values):
  124. res = super(ContractLine, self).write(values)
  125. if any(
  126. [
  127. field in values
  128. for field in self._get_forecast_update_trigger_fields()
  129. ]
  130. ):
  131. for rec in self:
  132. rec.with_delay()._generate_forecast_periods()
  133. return res