From 557097be2d710e724dcc52f4be37ffba1e5ce2b1 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 +- .../tests/test_contract_sale_mandate.py | 1 + product_contract/tests/test_sale_order.py | 1 + product_contract/views/product_template.xml | 3 +- product_contract/views/sale_order.xml | 3 +- 8 files changed, 77 insertions(+), 19 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 00000000..ca208e9d --- /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 93711c5d..1a5fa3d0 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 925ab2ce..e6bf09ec 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 b66077ee..351ded65 100644 --- a/contract/views/abstract_contract_line.xml +++ b/contract/views/abstract_contract_line.xml @@ -59,8 +59,7 @@ - + diff --git a/contract_sale_mandate/tests/test_contract_sale_mandate.py b/contract_sale_mandate/tests/test_contract_sale_mandate.py index eb36adcd..04515133 100644 --- a/contract_sale_mandate/tests/test_contract_sale_mandate.py +++ b/contract_sale_mandate/tests/test_contract_sale_mandate.py @@ -26,6 +26,7 @@ class TestContractSaleMandate(TestContractBase): 'is_contract': True, 'default_qty': 12, 'recurring_rule_type': "monthlylastday", + 'recurring_invoicing_type': "post-paid", 'contract_template_id': cls.contract_template1.id, } ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 7a630cbb..307cf908 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -43,6 +43,7 @@ class TestSaleOrder(TransactionCase): 'is_contract': True, 'default_qty': 12, 'recurring_rule_type': "monthlylastday", + 'recurring_invoicing_type': "post-paid", 'contract_template_id': self.contract_template1.id, } ) diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 400afa99..057ec95b 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -33,8 +33,7 @@ - + diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index e5da25fe..4fd1b90b 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -58,8 +58,7 @@ - +