Browse Source

[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.
pull/434/head
Stéphane Bidoul (ACSONE) 5 years ago
parent
commit
557097be2d
No known key found for this signature in database GPG Key ID: BCAB2555446B5B92
  1. 10
      contract/migrations/12.0.5.0.0/pre-migration.py
  2. 35
      contract/models/contract_line.py
  3. 40
      contract/tests/test_contract.py
  4. 3
      contract/views/abstract_contract_line.xml
  5. 1
      contract_sale_mandate/tests/test_contract_sale_mandate.py
  6. 1
      product_contract/tests/test_sale_order.py
  7. 3
      product_contract/views/product_template.xml
  8. 3
      product_contract/views/sale_order.xml

10
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'
"""
)

35
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

40
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'

3
contract/views/abstract_contract_line.xml

@ -59,8 +59,7 @@
</div>
</group>
<group>
<field name="recurring_invoicing_type"
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/>
<field name="recurring_invoicing_type"/>
</group>
</group>
</sheet>

1
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,
}
)

1
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,
}
)

3
product_contract/views/product_template.xml

@ -33,8 +33,7 @@
</group>
<group>
<field name="default_qty"/>
<field name="recurring_invoicing_type"
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/>
<field name="recurring_invoicing_type"/>
</group>
</group>
<group>

3
product_contract/views/sale_order.xml

@ -58,8 +58,7 @@
<field name="recurring_rule_type"/>
</group>
<group attrs="{'invisible': [('is_contract', '=', False)]}">
<field name="recurring_invoicing_type"
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/>
<field name="recurring_invoicing_type"/>
</group>
<group attrs="{'invisible': [('is_contract', '=', False)]}">
<field name="date_start"

Loading…
Cancel
Save