From 62d826b782aff80fdbb5c496b47cfce6645eb768 Mon Sep 17 00:00:00 2001 From: Simon Maillard Date: Thu, 11 Jun 2026 12:19:49 +0000 Subject: [PATCH] [FIX] contract: remove circular dependency on recurring_next_date Break the cascade that overwrites user-edited recurring_next_date on contract lines whenever the contract was saved. Three changes (aligned with the 18.0 logic): 1. abstract_contract_line: remove contract_id.recurring_next_date from @api.depends of _compute_recurring_next_date. Only contract_id.line_recurrence remains. 2. contract_line: remove _compute_next_period_date_start override that had cross-line dependencies (contract_id.last_date_invoiced, contract_id.contract_line_ids.last_date_invoiced). 3. contract_line: make date_end a plain (non-computed) field and remove _compute_date_end, breaking the cascade: contract.date_end -> line.date_end -> npds -> rnd. Add regression tests. --- contract/models/abstract_contract_line.py | 2 +- contract/models/contract_line.py | 32 +---------------- contract/tests/test_contract.py | 44 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/contract/models/abstract_contract_line.py b/contract/models/abstract_contract_line.py index 11d5524142..33e08721c8 100644 --- a/contract/models/abstract_contract_line.py +++ b/contract/models/abstract_contract_line.py @@ -168,8 +168,8 @@ def _compute_recurring_interval(self): def _compute_date_start(self): self._set_recurrence_field("date_start") + @api.depends("contract_id.line_recurrence") # pylint: disable=missing-return - @api.depends("contract_id.recurring_next_date", "contract_id.line_recurrence") def _compute_recurring_next_date(self): super()._compute_recurring_next_date() self._set_recurrence_field("recurring_next_date") diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index 008a6a1d0b..eca9901c73 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -45,7 +45,7 @@ class ContractLine(models.Model): company_id = fields.Many2one(related="contract_id.company_id") currency_id = fields.Many2one(related="contract_id.currency_id") date_start = fields.Date(required=True) - date_end = fields.Date(compute="_compute_date_end", store=True, readonly=False) + date_end = fields.Date(index=True) termination_notice_date = fields.Date( compute="_compute_termination_notice_date", store=True, @@ -111,36 +111,6 @@ class ContractLine(models.Model): readonly=True, ) - @api.depends( - "last_date_invoiced", - "date_start", - "date_end", - "contract_id.last_date_invoiced", - "contract_id.contract_line_ids.last_date_invoiced", - ) - # pylint: disable=missing-return - def _compute_next_period_date_start(self): - """Rectify next period date start if another line in the contract has been - already invoiced previously when the recurrence is by contract. - """ - rest = self.filtered(lambda x: x.contract_id.line_recurrence) - for rec in self - rest: - lines = rec.contract_id.contract_line_ids - if not rec.last_date_invoiced and any(lines.mapped("last_date_invoiced")): - next_period_date_start = max( - lines.filtered("last_date_invoiced").mapped("last_date_invoiced") - ) + relativedelta(days=1) - if rec.date_end and next_period_date_start > rec.date_end: - next_period_date_start = False - rec.next_period_date_start = next_period_date_start - else: - rest |= rec - super(ContractLine, rest)._compute_next_period_date_start() - - @api.depends("contract_id.date_end", "contract_id.line_recurrence") - def _compute_date_end(self): - self._set_recurrence_field("date_end") - @api.depends( "date_end", "termination_notice_rule_type", diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 51dfddaa49..0f8e392684 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -1117,6 +1117,50 @@ def test_recurring_next_date(self): min(self.contract.contract_line_ids.mapped("recurring_next_date")), ) + def test_write_contract_line_recurring_next_date_modal(self): + """When line_recurrence=True, writing to a line's recurring_next_date + should preserve the user's value and not overwrite it + with next_period_date_start.""" + line = self.acct_line + original_rnd = line.recurring_next_date + original_npds = line.next_period_date_start + contract = self.contract + + # Simulate user setting a custom date + custom_rnd = original_rnd + relativedelta(days=10) + line.write({"recurring_next_date": custom_rnd}) + + self.assertEqual( + line.recurring_next_date, + custom_rnd, + "User's custom recurring_next_date was overwritten!", + ) + self.assertEqual( + contract.recurring_next_date, + custom_rnd, + "Contract's rnd should be min of lines (the custom value)", + ) + # next_period_date_start should NOT have changed + self.assertEqual( + line.next_period_date_start, + original_npds, + "next_period_date_start changed unexpectedly", + ) + + def test_write_contract_line_recurring_next_date_form_edit(self): + """When line_recurrence=True, editing recurring_next_date via Form.edit(0) + should preserve the user's value.""" + line = self.acct_line + manually_set_date = line.recurring_next_date + relativedelta(days=10) + with Form(self.contract) as f_contract: + with f_contract.contract_line_ids.edit(0) as f_cline: + f_cline.recurring_next_date = manually_set_date.strftime("%Y-%m-%d") + self.assertEqual( + line.recurring_next_date, + manually_set_date, + "User's custom recurring_next_date was overwritten by Form save!", + ) + def test_date_end(self): """recurring next date for a contract is the min for all lines""" self.acct_line.date_end = "2018-01-01"