From 7649f5b3853429cd8da1911d81490d7d1276182d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul=20=28ACSONE=29?= Date: Sun, 8 Dec 2019 12:31:04 +0100 Subject: [PATCH] [IMP] contract: support pre-paid for monthlylastday monthlylastday is (almost) not a special case anymore \o/. montlylastday is simply a montly period where the periods are aligned on month boundaries. The last bit of special casing is that postpaid generates invoice the day after the last dasy of the period, except for monthlylastday where the invoice is generated on the last day of the period. This last exception will disappear when we put the offset under user control. This is a breaking change because the post-paid/pre-paid mode becomes relevant for monthlylastday invoicing. The field becomes visible in the UI. Code that generate monthlylastday contract lines must now correctly set the pre-paid/post-paid mode too. Some tests have had to be adapted to reflect that. --- .../migrations/12.0.5.0.0/pre-migration.py | 10 +++++ contract/models/contract_line.py | 35 +++++++++++----- contract/tests/test_contract.py | 40 ++++++++++++++++++- contract/views/abstract_contract_line.xml | 3 +- 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 contract/migrations/12.0.5.0.0/pre-migration.py diff --git a/contract/migrations/12.0.5.0.0/pre-migration.py b/contract/migrations/12.0.5.0.0/pre-migration.py new file mode 100644 index 000000000..ca208e9dd --- /dev/null +++ b/contract/migrations/12.0.5.0.0/pre-migration.py @@ -0,0 +1,10 @@ +def migrate(cr, version): + # pre-paid/post-paid becomes significant for monthlylastday too, + # make sure it has the value that was implied for previous versions. + cr.execute( + """\ + UPDATE contract_line + SET recurring_invoicing_type = 'post-paid' + WHERE recurring_rule_type = 'monthlylastday' + """ + ) diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index 93711c5d3..1a5fa3d08 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -380,6 +380,22 @@ class ContractLine(models.Model): max_date_end=False, ) + @api.model + def _get_offset(self, recurring_invoicing_type, recurring_rule_type): + """Return a relativedelta to offset the invoice date compared + to the period start or end date. + + This method will disappear when the offset becomes user controlled. + """ + if ( + recurring_invoicing_type == 'pre-paid' + or recurring_rule_type == 'monthlylastday' + ): + offset = 0 + else: + offset = 1 + return relativedelta(days=offset) + @api.model def _get_recurring_next_date( self, @@ -398,12 +414,11 @@ class ContractLine(models.Model): ) 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 + offset = self._get_offset(recurring_invoicing_type, recurring_rule_type) + if recurring_invoicing_type == 'pre-paid': + recurring_next_date = next_period_date_start + offset else: # post-paid - recurring_next_date = next_period_date_end + relativedelta(days=1) + recurring_next_date = next_period_date_end + offset return recurring_next_date @api.model @@ -433,20 +448,18 @@ class ContractLine(models.Model): ) 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': + offset = self._get_offset(recurring_invoicing_type, recurring_rule_type) + if recurring_invoicing_type == 'pre-paid': next_period_date_end = ( next_invoice_date + - offset + 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 - ) + next_period_date_end = next_invoice_date - offset 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 diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 925ab2cee..e6bf09ecf 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -614,12 +614,17 @@ class TestContract(TestContractBase): False), ), ( - to_date('2018-01-31'), + to_date('2018-01-06'), (to_date('2018-01-06'), 'pre-paid', 'monthlylastday', 1, False), ), ( to_date('2018-02-28'), + (to_date('2018-01-05'), 'post-paid', 'monthlylastday', 2, + False), + ), + ( + to_date('2018-01-05'), (to_date('2018-01-05'), 'pre-paid', 'monthlylastday', 2, False), ), @@ -1363,7 +1368,7 @@ class TestContract(TestContractBase): len(invoice_lines), ) - def test_get_period_to_invoice_monthlylastday(self): + def test_get_period_to_invoice_monthlylastday_postpaid(self): self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' @@ -1394,6 +1399,37 @@ class TestContract(TestContractBase): self.assertEqual(last, to_date('2018-03-15')) self.acct_line.manual_renew_needed = True + def test_get_period_to_invoice_monthlylastday_prepaid(self): + self.acct_line.date_start = '2018-01-05' + self.acct_line.recurring_invoicing_type = 'pre-paid' + self.acct_line.recurring_rule_type = 'monthlylastday' + self.acct_line.date_end = '2018-03-15' + self.acct_line._onchange_date_start() + first, last, recurring_next_date = \ + self.acct_line._get_period_to_invoice( + self.acct_line.last_date_invoiced, + self.acct_line.recurring_next_date, + ) + self.assertEqual(first, to_date('2018-01-05')) + self.assertEqual(last, to_date('2018-01-31')) + self.contract.recurring_create_invoice() + first, last, recurring_next_date = \ + self.acct_line._get_period_to_invoice( + self.acct_line.last_date_invoiced, + self.acct_line.recurring_next_date, + ) + self.assertEqual(first, to_date('2018-02-01')) + self.assertEqual(last, to_date('2018-02-28')) + self.contract.recurring_create_invoice() + first, last, recurring_next_date = \ + self.acct_line._get_period_to_invoice( + self.acct_line.last_date_invoiced, + self.acct_line.recurring_next_date, + ) + self.assertEqual(first, to_date('2018-03-01')) + self.assertEqual(last, to_date('2018-03-15')) + self.acct_line.manual_renew_needed = True + def test_get_period_to_invoice_monthly_pre_paid_2(self): self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' diff --git a/contract/views/abstract_contract_line.xml b/contract/views/abstract_contract_line.xml index b66077eeb..351ded65f 100644 --- a/contract/views/abstract_contract_line.xml +++ b/contract/views/abstract_contract_line.xml @@ -59,8 +59,7 @@ - +