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.

144 lines
5.0 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. last_date_invoiced = (
  63. rec.last_date_invoiced
  64. if rec.last_date_invoiced
  65. else rec.date_start - relativedelta(days=1)
  66. )
  67. period_date_end = last_date_invoiced
  68. recurring_next_date = rec.recurring_next_date
  69. while rec._get_generate_forecast_periods_criteria(
  70. period_date_end
  71. ):
  72. period_dates = rec._get_period_to_invoice(
  73. last_date_invoiced,
  74. recurring_next_date,
  75. stop_at_date_end=not rec.is_auto_renew,
  76. )
  77. period_date_start, period_date_end, recurring_next_date = (
  78. period_dates
  79. )
  80. values.append(
  81. rec._prepare_contract_line_forecast_period(
  82. period_date_start,
  83. period_date_end,
  84. recurring_next_date,
  85. )
  86. )
  87. last_date_invoiced = period_date_end
  88. recurring_next_date = (
  89. recurring_next_date
  90. + self.get_relative_delta(
  91. rec.recurring_rule_type, rec.recurring_interval
  92. )
  93. )
  94. return self.env["contract.line.forecast.period"].create(values)
  95. @api.model
  96. def create(self, values):
  97. contract_lines = super(ContractLine, self).create(values)
  98. for contract_line in contract_lines:
  99. contract_line.with_delay()._generate_forecast_periods()
  100. return contract_lines
  101. @api.model
  102. def _get_forecast_update_trigger_fields(self):
  103. return [
  104. "name",
  105. "sequence",
  106. "product_id",
  107. "date_start",
  108. "date_end",
  109. "quantity",
  110. "price_unit",
  111. "discount",
  112. "recurring_invoicing_type",
  113. "recurring_next_date",
  114. "recurring_rule_type",
  115. "recurring_interval",
  116. "is_canceled",
  117. "active",
  118. "is_auto_renew",
  119. ]
  120. @api.multi
  121. def write(self, values):
  122. res = super(ContractLine, self).write(values)
  123. if any(
  124. [
  125. field in values
  126. for field in self._get_forecast_update_trigger_fields()
  127. ]
  128. ):
  129. for rec in self:
  130. rec.with_delay()._generate_forecast_periods()
  131. return res