From 04afaf5169afc8d0736ca04d311b9d857d346171 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 29 Nov 2024 13:32:35 +0100 Subject: [PATCH] [IMP] product_contract: add recurrence interval --- product_contract/models/product_template.py | 10 +++++++ product_contract/models/sale_order_line.py | 26 ++++++++--------- .../models/sale_order_line_contract_mixin.py | 29 ++++++++++++------- .../contract_configurator_controller.esm.js | 2 ++ .../static/src/js/sale_product_field.esm.js | 1 + product_contract/tests/test_sale_order.py | 26 +++++++---------- product_contract/views/product_template.xml | 1 + product_contract/views/sale_order.xml | 2 ++ .../product_contract_configurator_views.xml | 4 ++- 9 files changed, 61 insertions(+), 40 deletions(-) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 7a4be429d4..a75d29a231 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -16,6 +16,16 @@ class ProductTemplate(models.Model): company_dependent=True, ) default_qty = fields.Integer(string="Recurrence Number", default=1) + recurrence_interval = fields.Selection( + [ + ("monthly", "Month(s)"), + ("quarterly", "Quarter(s)"), + ("semesterly", "Semester(s)"), + ("yearly", "Year(s)"), + ], + default="monthly", + help="Specify Interval for contract duration.", + ) recurring_rule_type = fields.Selection( [ ("daily", "Day(s)"), diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index bcbd6e06f5..cd53494b8a 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -163,26 +163,23 @@ def _compute_qty_to_invoice(self): def _set_contract_line_start_date(self): """Set date start of lines using it's method and the confirmation date.""" for line in self: - if ( - line.contract_start_date_method == "manual" - or line.recurring_rule_type in ["daily", "weekly", "monthlylastday"] - ): + if line.contract_start_date_method == "manual": continue is_end = "end_" in line.contract_start_date_method today = fields.Date.today() month_period = month = today.month - month_nb = MONTH_NB_MAPPING[line.recurring_rule_type] + month_nb = MONTH_NB_MAPPING[line.recurrence_interval] # The period number is started by 0 to be able to calculate the month period_number = (month - 1) // month_nb - if line.recurring_rule_type == "yearly": + if line.recurrence_interval == "yearly": month_period = 1 - elif line.recurring_rule_type != "monthly": + elif line.recurrence_interval != "monthly": # Checking quarterly and semesterly month_period = period_number * month_nb + 1 forced_month = 0 - if line.recurring_rule_type != "monthly": + if line.recurrence_interval != "monthly": forced_value = int( - line.product_id[f"force_month_{line.recurring_rule_type}"] + line.product_id[f"force_month_{line.recurrence_interval}"] ) if forced_value: # When the selected period is yearly, the period_number field is @@ -208,6 +205,7 @@ def _set_contract_line_start_date(self): "date_start", "date_end", "recurring_rule_type", + "recurrence_interval", "recurring_invoicing_type", ) def _compute_name(self): @@ -230,21 +228,21 @@ def _compute_name(self): ) date_text = f"{start_method_label}" if ( - line.recurring_rule_type != "monthly" - and line.product_id[f"force_month_{line.recurring_rule_type}"] + line.recurrence_interval != "monthly" + and line.product_id[f"force_month_{line.recurrence_interval}"] ): field_info = dict( self.env["product.template"] - ._fields[f"force_month_{line.recurring_rule_type}"] + ._fields[f"force_month_{line.recurrence_interval}"] .get_description(self.env) ) field_selection = dict(field_info.get("selection")) force_month_label = field_selection.get( - line.product_id[f"force_month_{line.recurring_rule_type}"] + line.product_id[f"force_month_{line.recurrence_interval}"] ) date_text += f" ({force_month_label})" field_info = dict( - self._fields["recurring_rule_type"].get_description(self.env) + self._fields["recurrence_interval"].get_description(self.env) ) field_selection = dict(field_info.get("selection")) recurring_rule_label = field_selection.get(line.recurring_rule_type) diff --git a/product_contract/models/sale_order_line_contract_mixin.py b/product_contract/models/sale_order_line_contract_mixin.py index 2497f4cd2b..3a53977d0c 100644 --- a/product_contract/models/sale_order_line_contract_mixin.py +++ b/product_contract/models/sale_order_line_contract_mixin.py @@ -29,6 +29,20 @@ class SaleOrderLineContractMixin(models.AbstractModel): store=True, readonly=False, ) + recurrence_interval = fields.Selection( + [ + ("monthly", "Month(s)"), + ("quarterly", "Quarter(s)"), + ("semesterly", "Semester(s)"), + ("yearly", "Year(s)"), + ], + default="monthly", + help="Specify Interval for contract duration.", + compute="_compute_product_contract_data", + precompute=True, + store=True, + readonly=False, + ) recurring_rule_type = fields.Selection( [ ("daily", "Day(s)"), @@ -40,7 +54,7 @@ class SaleOrderLineContractMixin(models.AbstractModel): ("yearly", "Year(s)"), ], default="monthly", - string="Recurrence", + string="Invoice Every", help="Specify Interval for automatic invoice generation.", compute="_compute_product_contract_data", precompute=True, @@ -157,6 +171,7 @@ def _compute_product_contract_data(self): "recurrence_number": 0, "recurring_rule_type": False, "recurring_invoicing_type": False, + "recurrence_interval": False, "is_auto_renew": False, "auto_renew_interval": False, "auto_renew_rule_type": False, @@ -168,6 +183,7 @@ def _compute_product_contract_data(self): "recurrence_number": p.default_qty, "recurring_rule_type": p.recurring_rule_type, "recurring_invoicing_type": p.recurring_invoicing_type, + "recurrence_interval": p.recurrence_interval, "is_auto_renew": p.is_auto_renew, "auto_renew_interval": p.auto_renew_interval, "auto_renew_rule_type": p.auto_renew_rule_type, @@ -181,25 +197,18 @@ def _compute_contract_line_date_start(self): if rec.contract_start_date_method == "manual": rec.date_start = rec.date_start or fields.Date.today() - @api.depends("date_start", "recurring_rule_type", "recurrence_number") + @api.depends("date_start", "recurrence_interval", "recurrence_number") def _compute_contract_line_date_end(self): for rec in self: rec.date_end = rec._get_date_end() if rec.date_start else False - def _get_auto_renew_rule_type(self): - """monthly last day don't make sense for auto_renew_rule_type""" - self.ensure_one() - if self.recurring_rule_type == "monthlylastday": - return "monthly" - return self.recurring_rule_type - def _get_date_end(self): self.ensure_one() contract_line_model = self.env["contract.line"] date_end = ( self.date_start + contract_line_model.get_relative_delta( - self._get_auto_renew_rule_type(), self.recurrence_number + self.recurrence_interval, self.recurrence_number ) - relativedelta(days=1) ) diff --git a/product_contract/static/src/js/contract_configurator_controller.esm.js b/product_contract/static/src/js/contract_configurator_controller.esm.js index c742a94df5..fb07e3d3b2 100644 --- a/product_contract/static/src/js/contract_configurator_controller.esm.js +++ b/product_contract/static/src/js/contract_configurator_controller.esm.js @@ -16,6 +16,7 @@ export class ProductContractConfiguratorController extends formView.Controller { product_uom_qty, recurrence_number, recurring_rule_type, + recurrence_interval, contract_id, date_start, date_end, @@ -31,6 +32,7 @@ export class ProductContractConfiguratorController extends formView.Controller { product_uom_qty, recurrence_number, recurring_rule_type, + recurrence_interval, contract_id, date_start, date_end, diff --git a/product_contract/static/src/js/sale_product_field.esm.js b/product_contract/static/src/js/sale_product_field.esm.js index 49f34041d4..843c27dcc4 100644 --- a/product_contract/static/src/js/sale_product_field.esm.js +++ b/product_contract/static/src/js/sale_product_field.esm.js @@ -31,6 +31,7 @@ patch(SaleOrderLineProductField.prototype, { default_company_id: this.props.record.model.root.data.company_id[0], default_recurrence_number: this.props.record.data.recurrence_number, default_recurring_rule_type: this.props.record.data.recurring_rule_type, + default_recurrence_interval: this.props.record.data.recurrence_interval, default_product_uom_qty: this.props.record.data.product_uom_qty, default_contract_id: this.props.record.data.contract_id[0], default_date_start: this.props.record.data.date_start, diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 9a0177de91..96d40f4671 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -100,6 +100,7 @@ def setUpClass(cls): } ) cls.contract_line = cls.contract.contract_line_ids[0] + cls.sale.order_line._compute_product_contract_data() def test_compute_is_contract(self): """Sale Order should have is_contract true if one of its lines is @@ -109,7 +110,6 @@ def test_compute_is_contract(self): def test_action_confirm(self): """It should create a contract for each contract template used in order_line""" - self.order_line1._compute_auto_renew() self.sale.action_confirm() contracts = self.sale.order_line.mapped("contract_id") self.assertEqual(len(contracts), 2) @@ -155,7 +155,6 @@ def test_action_confirm_without_contract_creation(self): """It should create a contract for each contract template used in order_line""" self.sale.company_id.create_contract_at_sale_order_confirmation = False - self.order_line1._compute_auto_renew() self.sale.action_confirm() self.assertEqual(len(self.sale.order_line.mapped("contract_id")), 0) self.assertTrue(self.sale.need_contract_creation) @@ -174,14 +173,12 @@ def test_action_confirm_without_contract_creation(self): def test_sale_contract_count(self): """It should count contracts as many different contract template used in order_line""" - self.order_line1._compute_auto_renew() self.sale.action_confirm() self.assertEqual(self.sale.contract_count, 2) def test_onchange_product(self): """It should get recurrence invoicing info to the sale line from its product""" - self.order_line1._compute_auto_renew() self.assertEqual( self.order_line1.recurring_rule_type, self.product1.recurring_rule_type, @@ -229,7 +226,6 @@ def test_no_contract_proudct(self): def test_sale_order_line_invoice_status(self): """Sale order line for contract product should have nothing to invoice as status""" - self.order_line1._compute_auto_renew() self.sale.action_confirm() self.assertEqual(self.order_line1.invoice_status, "no") @@ -239,7 +235,7 @@ def test_sale_order_invoice_status_2(self): self.sale.order_line.filtered( lambda line: not line.product_id.is_contract ).unlink() - self.order_line1._compute_auto_renew() + self.sale.action_confirm() self.assertEqual(self.sale.invoice_status, "no") @@ -247,7 +243,7 @@ def test_sale_order_create_invoice(self): """Should not invoice contract product on sale order create invoice""" self.product2.is_contract = False self.product2.invoice_policy = "order" - self.order_line1._compute_auto_renew() + self.sale.action_confirm() self.sale._create_invoices() self.assertEqual(len(self.sale.invoice_ids), 1) @@ -258,7 +254,7 @@ def test_sale_order_create_invoice(self): def test_link_contract_invoice_to_sale_order(self): """It should link contract invoice to sale order""" - self.order_line1._compute_auto_renew() + self.sale.action_confirm() invoice = self.order_line1.contract_id.recurring_create_invoice() self.assertTrue(invoice in self.sale.invoice_ids) @@ -270,7 +266,7 @@ def test_contract_upsell(self): self.contract_line.date_end = Date.today() + relativedelta(months=4) self.contract_line.is_auto_renew = True self.order_line1.date_start = "2018-06-01" - self.order_line1._compute_auto_renew() + self.sale.action_confirm() self.assertEqual(self.contract_line.date_end, Date.to_date("2018-05-31")) self.assertFalse(self.contract_line.is_auto_renew) @@ -296,7 +292,7 @@ def test_contract_upsell_2(self): } ) self.order_line1.date_start = "2018-06-01" - self.order_line1._compute_auto_renew() + self.sale.action_confirm() self.assertFalse(self.contract_line.date_end) self.assertTrue(self.contract_line.is_canceled) @@ -362,7 +358,6 @@ def test_order_lines_with_the_same_contract_template(self): "property_contract_template_id": self.contract_template1.id, } ) - self.sale.order_line._compute_auto_renew() self.sale.action_confirm() contracts = self.sale.order_line.mapped("contract_id") self.assertEqual(len(contracts), 1) @@ -375,20 +370,20 @@ def test_order_lines_with_the_same_contract_template(self): self.assertEqual(len(contracts), 1) def _create_contract_product( - self, recurring_rule_type, contract_start_date_method, force_month=False + self, recurrence_interval, contract_start_date_method, force_month=False ): product = self.env["product.product"].create( { "name": "Contract Test", "type": "service", "is_contract": True, - "recurring_rule_type": recurring_rule_type, + "recurrence_interval": recurrence_interval, "contract_start_date_method": contract_start_date_method, "property_contract_template_id": self.contract_template1.id, } ) - if recurring_rule_type != "monthly": - product[f"force_month_{recurring_rule_type}"] = force_month + if recurrence_interval != "monthly": + product[f"force_month_{recurrence_interval}"] = force_month return product def _create_and_confirm_sale(self, product): @@ -406,6 +401,7 @@ def _create_and_confirm_sale(self, product): ], } ) + sale.order_line._compute_product_contract_data() sale.action_confirm() return sale diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 0de57a86d4..13ffd30395 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -27,6 +27,7 @@ + diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index e9874831e8..220bc3d9da 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -63,6 +63,7 @@ /> + @@ -114,6 +115,7 @@ optional="hide" /> + diff --git a/product_contract/wizards/product_contract_configurator_views.xml b/product_contract/wizards/product_contract_configurator_views.xml index 72eb0f279b..42f30fa4c6 100644 --- a/product_contract/wizards/product_contract_configurator_views.xml +++ b/product_contract/wizards/product_contract_configurator_views.xml @@ -23,12 +23,13 @@ + - +