diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index 3178f2409..b1fa39137 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -41,6 +41,14 @@ class ContractLine(models.Model): last_date_invoiced = fields.Date( string='Last Date Invoiced', readonly=True, copy=False ) + next_period_date_start = fields.Date( + string='Next Period Start', + compute='_compute_next_period_date_start', + ) + next_period_date_end = fields.Date( + string='Next Period End', + compute='_compute_next_period_date_end', + ) termination_notice_date = fields.Date( string='Termination notice date', compute="_compute_termination_notice_date", @@ -356,25 +364,118 @@ class ContractLine(models.Model): ) @api.model - def _compute_first_recurring_next_date( + def _get_recurring_next_date( self, - date_start, + next_period_date_start, recurring_invoicing_type, recurring_rule_type, recurring_interval, + max_date_end, ): - if recurring_rule_type == 'monthlylastday': - return date_start + self.get_relative_delta( - recurring_rule_type, recurring_interval - 1 - ) - if recurring_invoicing_type == 'pre-paid': - return date_start - return date_start + self.get_relative_delta( - recurring_rule_type, recurring_interval + next_period_date_end = self._get_next_period_date_end( + next_period_date_start, + recurring_invoicing_type, + recurring_rule_type, + recurring_interval, + max_date_end=max_date_end, ) + if not next_period_date_end: + return False + if recurring_rule_type == 'monthlylastday': + recurring_next_date = next_period_date_end + elif recurring_invoicing_type == 'pre-paid': + recurring_next_date = next_period_date_start + else: # post-paid + recurring_next_date = next_period_date_end + relativedelta(days=1) + return recurring_next_date @api.model - def compute_first_date_end( + def _get_next_period_date_end( + self, + next_period_date_start, + recurring_invoicing_type, + recurring_rule_type, + recurring_interval, + max_date_end, + next_invoice_date=False, + ): + """Compute the end date for the next period""" + if not next_period_date_start: + return False + if max_date_end and next_period_date_start > max_date_end: + # start is past max date end: there is no next period + return False + if not next_invoice_date: + # regular algorithm + if recurring_rule_type == 'monthlylastday': + next_period_date_end = ( + next_period_date_start + + self.get_relative_delta( + recurring_rule_type, recurring_interval - 1 + ) + ) + else: + next_period_date_end = ( + next_period_date_start + + self.get_relative_delta( + recurring_rule_type, recurring_interval + ) + - relativedelta(days=1) + ) + else: + # special algorithm when the next invoice date is forced + if recurring_rule_type == 'monthlylastday': + next_period_date_end = next_invoice_date + elif recurring_invoicing_type == 'pre-paid': + next_period_date_end = ( + next_invoice_date + + self.get_relative_delta( + recurring_rule_type, recurring_interval + ) + - relativedelta(days=1) + ) + else: # post-paid + next_period_date_end = next_invoice_date - relativedelta( + days=1 + ) + if max_date_end and next_period_date_end > max_date_end: + # end date is past max_date_end: trim it + next_period_date_end = max_date_end + return next_period_date_end + + @api.depends('last_date_invoiced', 'date_start', 'date_end') + def _compute_next_period_date_start(self): + for rec in self: + if rec.last_date_invoiced: + next_period_date_start = ( + rec.last_date_invoiced + relativedelta(days=1) + ) + else: + next_period_date_start = rec.date_start + 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 + + @api.depends( + 'next_period_date_start', + 'recurring_invoicing_type', + 'recurring_rule_type', + 'recurring_interval', + 'date_end', + ) + def _compute_next_period_date_end(self): + for rec in self: + rec.next_period_date_end = self._get_next_period_date_end( + rec.next_period_date_start, + rec.recurring_invoicing_type, + rec.recurring_rule_type, + rec.recurring_interval, + max_date_end=rec.date_end, + next_invoice_date=rec.recurring_next_date, + ) + + @api.model + def _get_first_date_end( self, date_start, auto_renew_rule_type, auto_renew_interval ): return ( @@ -396,7 +497,7 @@ class ContractLine(models.Model): auto_renew""" for rec in self.filtered('is_auto_renew'): if rec.date_start: - rec.date_end = self.compute_first_date_end( + rec.date_end = self._get_first_date_end( rec.date_start, rec.auto_renew_rule_type, rec.auto_renew_interval, @@ -404,17 +505,19 @@ class ContractLine(models.Model): @api.onchange( 'date_start', + 'date_end', 'recurring_invoicing_type', 'recurring_rule_type', 'recurring_interval', ) def _onchange_date_start(self): for rec in self.filtered('date_start'): - rec.recurring_next_date = self._compute_first_recurring_next_date( + rec.recurring_next_date = self._get_recurring_next_date( rec.date_start, rec.recurring_invoicing_type, rec.recurring_rule_type, rec.recurring_interval, + max_date_end=rec.date_end, ) @api.constrains('is_canceled', 'is_auto_renew') @@ -533,6 +636,8 @@ class ContractLine(models.Model): def _get_period_to_invoice( self, last_date_invoiced, recurring_next_date, stop_at_date_end=True ): + # TODO this method can now be removed, since + # TODO self.next_period_date_start/end have the same values self.ensure_one() first_date_invoiced = False if not recurring_next_date: @@ -542,24 +647,14 @@ class ContractLine(models.Model): if last_date_invoiced else self.date_start ) - if self.recurring_rule_type == 'monthlylastday': - last_date_invoiced = recurring_next_date - else: - if self.recurring_invoicing_type == 'pre-paid': - last_date_invoiced = ( - recurring_next_date - + self.get_relative_delta( - self.recurring_rule_type, self.recurring_interval - ) - - relativedelta(days=1) - ) - else: - last_date_invoiced = recurring_next_date - relativedelta( - days=1 - ) - if stop_at_date_end: - if self.date_end and self.date_end < last_date_invoiced: - last_date_invoiced = self.date_end + last_date_invoiced = self._get_next_period_date_end( + first_date_invoiced, + self.recurring_invoicing_type, + self.recurring_rule_type, + self.recurring_interval, + max_date_end=(self.date_end if stop_at_date_end else False), + next_invoice_date=recurring_next_date, + ) return first_date_invoiced, last_date_invoiced, recurring_next_date @api.multi @@ -651,15 +746,22 @@ class ContractLine(models.Model): ) ) new_date_start = rec.date_start + delay_delta - rec.recurring_next_date = self._compute_first_recurring_next_date( + if rec.date_end: + new_date_end = rec.date_end + delay_delta + else: + new_date_end = False + new_recurring_next_date = self._get_recurring_next_date( new_date_start, rec.recurring_invoicing_type, rec.recurring_rule_type, rec.recurring_interval, + max_date_end=new_date_end ) - if rec.date_end: - rec.date_end += delay_delta - rec.date_start = new_date_start + rec.write({ + "date_start": new_date_start, + "date_end": new_date_end, + "recurring_next_date": new_recurring_next_date, + }) @api.multi def stop(self, date_end, manual_renew_needed=False, post_message=True): @@ -712,11 +814,12 @@ class ContractLine(models.Model): ): self.ensure_one() if not recurring_next_date: - recurring_next_date = self._compute_first_recurring_next_date( + recurring_next_date = self._get_recurring_next_date( date_start, self.recurring_invoicing_type, self.recurring_rule_type, self.recurring_interval, + max_date_end=date_end, ) new_vals = self.read()[0] new_vals.pop("id", None) @@ -1023,7 +1126,7 @@ class ContractLine(models.Model): def _get_renewal_dates(self): self.ensure_one() date_start = self.date_end + relativedelta(days=1) - date_end = self.compute_first_date_end( + date_end = self._get_first_date_end( date_start, self.auto_renew_rule_type, self.auto_renew_interval ) return date_start, date_end diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 76ea41449..5a73c63e4 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -537,7 +537,7 @@ class TestContract(TestContractBase): 'There was an error and the view couldn\'t be opened.', ) - def test_compute_first_recurring_next_date(self): + def test_get_recurring_next_date(self): """Test different combination to compute recurring_next_date Combination format { @@ -547,6 +547,7 @@ class TestContract(TestContractBase): recurring_rule_type, # ('daily', 'weekly', 'monthly', # 'monthlylastday', 'yearly'), recurring_interval, # integer + max_date_end, # date ), } """ @@ -556,57 +557,88 @@ class TestContract(TestContractBase): recurring_invoicing_type, recurring_rule_type, recurring_interval, + max_date_end, ): - return "Error in %s every %d %s case, start with %s " % ( + return "Error in %s every %d %s case, start with %s (max_date_end=%s)" % ( recurring_invoicing_type, recurring_interval, recurring_rule_type, date_start, + max_date_end, ) combinations = [ ( to_date('2018-01-01'), - (to_date('2018-01-01'), 'pre-paid', 'monthly', 1), + (to_date('2018-01-01'), 'pre-paid', 'monthly', 1, + False), ), ( to_date('2018-01-01'), - (to_date('2018-01-01'), 'pre-paid', 'monthly', 2), + (to_date('2018-01-01'), 'pre-paid', 'monthly', 1, + to_date('2018-01-15')), + ), + ( + False, + (to_date('2018-01-16'), 'pre-paid', 'monthly', 1, + to_date('2018-01-15')), + ), + ( + to_date('2018-01-01'), + (to_date('2018-01-01'), 'pre-paid', 'monthly', 2, + False), ), ( to_date('2018-02-01'), - (to_date('2018-01-01'), 'post-paid', 'monthly', 1), + (to_date('2018-01-01'), 'post-paid', 'monthly', 1, + False), + ), + ( + to_date('2018-01-16'), + (to_date('2018-01-01'), 'post-paid', 'monthly', 1, + to_date('2018-01-15')), + ), + ( + False, + (to_date('2018-01-16'), 'post-paid', 'monthly', 1, + to_date('2018-01-15')), ), ( to_date('2018-03-01'), - (to_date('2018-01-01'), 'post-paid', 'monthly', 2), + (to_date('2018-01-01'), 'post-paid', 'monthly', 2, + False), ), ( to_date('2018-01-31'), - (to_date('2018-01-05'), 'post-paid', 'monthlylastday', 1), + (to_date('2018-01-05'), 'post-paid', 'monthlylastday', 1, + False), ), ( to_date('2018-01-31'), - (to_date('2018-01-06'), 'pre-paid', 'monthlylastday', 1), + (to_date('2018-01-06'), 'pre-paid', 'monthlylastday', 1, + False), ), ( to_date('2018-02-28'), - (to_date('2018-01-05'), 'pre-paid', 'monthlylastday', 2), + (to_date('2018-01-05'), 'pre-paid', 'monthlylastday', 2, + False), ), ( to_date('2018-01-05'), - (to_date('2018-01-05'), 'pre-paid', 'yearly', 1), + (to_date('2018-01-05'), 'pre-paid', 'yearly', 1, + False), ), ( to_date('2019-01-05'), - (to_date('2018-01-05'), 'post-paid', 'yearly', 1), + (to_date('2018-01-05'), 'post-paid', 'yearly', 1, + False), ), ] contract_line_env = self.env['contract.line'] for recurring_next_date, combination in combinations: self.assertEqual( recurring_next_date, - contract_line_env._compute_first_recurring_next_date( + contract_line_env._get_recurring_next_date( *combination ), error_message(*combination), diff --git a/contract/views/contract_line.xml b/contract/views/contract_line.xml index 2d14a81f8..85f96db4b 100644 --- a/contract/views/contract_line.xml +++ b/contract/views/contract_line.xml @@ -15,11 +15,13 @@ + +