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.

143 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 AccountAnalyticInvoiceLine(models.Model):
  8. _inherit = "account.analytic.invoice.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. "contract_line_id": self.id,
  24. "product_id": self.product_id.id,
  25. "date_start": period_date_start,
  26. "date_end": period_date_end,
  27. "date_invoice": recurring_next_date,
  28. "discount": self.discount,
  29. "price_unit": self.price_unit,
  30. "quantity": self._get_quantity_to_invoice(
  31. period_date_start, period_date_end, recurring_next_date
  32. ),
  33. }
  34. @api.multi
  35. def _get_contract_forecast_end_date(self):
  36. self.ensure_one()
  37. today = fields.Date.context_today(self)
  38. return today + self.get_relative_delta(
  39. self.contract_id.company_id.contract_forecast_rule_type,
  40. self.contract_id.company_id.contract_forecast_interval,
  41. )
  42. @api.multi
  43. def _get_generate_forecast_periods_criteria(self, period_date_end):
  44. self.ensure_one()
  45. if self.is_canceled or not self.active:
  46. return False
  47. contract_forecast_end_date = self._get_contract_forecast_end_date()
  48. if not self.date_end or self.is_auto_renew:
  49. return period_date_end < contract_forecast_end_date
  50. return (
  51. period_date_end < self.date_end
  52. and period_date_end < contract_forecast_end_date
  53. )
  54. @api.multi
  55. @job(default_channel=QUEUE_CHANNEL)
  56. def _generate_forecast_periods(self):
  57. values = []
  58. for rec in self:
  59. rec.forecast_period_ids.unlink()
  60. if rec.recurring_next_date:
  61. last_date_invoiced = (
  62. rec.last_date_invoiced
  63. if rec.last_date_invoiced
  64. else rec.date_start - relativedelta(days=1)
  65. )
  66. period_date_end = last_date_invoiced
  67. recurring_next_date = rec.recurring_next_date
  68. while rec._get_generate_forecast_periods_criteria(
  69. period_date_end
  70. ):
  71. period_dates = rec._get_period_to_invoice(
  72. last_date_invoiced,
  73. recurring_next_date,
  74. stop_at_date_end=not rec.is_auto_renew,
  75. )
  76. period_date_start, period_date_end, recurring_next_date = (
  77. period_dates
  78. )
  79. values.append(
  80. rec._prepare_contract_line_forecast_period(
  81. period_date_start,
  82. period_date_end,
  83. recurring_next_date,
  84. )
  85. )
  86. last_date_invoiced = period_date_end
  87. recurring_next_date = (
  88. recurring_next_date
  89. + self.get_relative_delta(
  90. rec.recurring_rule_type, rec.recurring_interval
  91. )
  92. )
  93. return self.env["contract.line.forecast.period"].create(values)
  94. @api.model
  95. def create(self, values):
  96. contract_lines = super(AccountAnalyticInvoiceLine, self).create(values)
  97. for contract_line in contract_lines:
  98. contract_line.with_delay()._generate_forecast_periods()
  99. return contract_lines
  100. @api.model
  101. def _get_forecast_update_trigger_fields(self):
  102. return [
  103. "name",
  104. "sequence",
  105. "product_id",
  106. "date_start",
  107. "date_end",
  108. "quantity",
  109. "price_unit",
  110. "discount",
  111. "recurring_invoicing_type",
  112. "recurring_next_date",
  113. "recurring_rule_type",
  114. "recurring_interval",
  115. "is_canceled",
  116. "active",
  117. "is_auto_renew",
  118. ]
  119. @api.multi
  120. def write(self, values):
  121. res = super(AccountAnalyticInvoiceLine, self).write(values)
  122. if any(
  123. [
  124. field in values
  125. for field in self._get_forecast_update_trigger_fields()
  126. ]
  127. ):
  128. for rec in self:
  129. rec.with_delay()._generate_forecast_periods()
  130. return res