[REF] contract: Several refactorings

- rename misnamed methods
- clarify _get_recurring_next_date
  First compute the next period end date,
  then derive the next invoice date from the next
  period stard and end date.
- handle max_date_end in _get_recurring_next_date
  This concentrates all next date calculation
  logic in one place, and will allow further simplifications.
- add next period start/end fields
  Add two computed field showing the next period
  start and end date. This improve the UX and will
  enable further simplifications in the code.
- refactor _get_period_to_invoice
  Move the part of the logic that compute the next
  period depending on the chosen next invoice date
  to _get_next_period_date_end.
This commit is contained in:
Stéphane Bidoul (ACSONE)
2019-12-06 09:19:18 +01:00
committed by Pedro M. Baeza
parent a9ed053585
commit a0b85a2b0e
3 changed files with 186 additions and 49 deletions

View File

@@ -41,6 +41,14 @@ class ContractLine(models.Model):
last_date_invoiced = fields.Date( last_date_invoiced = fields.Date(
string='Last Date Invoiced', readonly=True, copy=False 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( termination_notice_date = fields.Date(
string='Termination notice date', string='Termination notice date',
compute="_compute_termination_notice_date", compute="_compute_termination_notice_date",
@@ -356,25 +364,118 @@ class ContractLine(models.Model):
) )
@api.model @api.model
def _compute_first_recurring_next_date( def _get_recurring_next_date(
self, self,
date_start, next_period_date_start,
recurring_invoicing_type, recurring_invoicing_type,
recurring_rule_type, recurring_rule_type,
recurring_interval, recurring_interval,
max_date_end,
): ):
if recurring_rule_type == 'monthlylastday': next_period_date_end = self._get_next_period_date_end(
return date_start + self.get_relative_delta( next_period_date_start,
recurring_rule_type, recurring_interval - 1 recurring_invoicing_type,
) recurring_rule_type,
if recurring_invoicing_type == 'pre-paid': recurring_interval,
return date_start max_date_end=max_date_end,
return date_start + self.get_relative_delta(
recurring_rule_type, recurring_interval
) )
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 @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 self, date_start, auto_renew_rule_type, auto_renew_interval
): ):
return ( return (
@@ -396,7 +497,7 @@ class ContractLine(models.Model):
auto_renew""" auto_renew"""
for rec in self.filtered('is_auto_renew'): for rec in self.filtered('is_auto_renew'):
if rec.date_start: if rec.date_start:
rec.date_end = self.compute_first_date_end( rec.date_end = self._get_first_date_end(
rec.date_start, rec.date_start,
rec.auto_renew_rule_type, rec.auto_renew_rule_type,
rec.auto_renew_interval, rec.auto_renew_interval,
@@ -404,17 +505,19 @@ class ContractLine(models.Model):
@api.onchange( @api.onchange(
'date_start', 'date_start',
'date_end',
'recurring_invoicing_type', 'recurring_invoicing_type',
'recurring_rule_type', 'recurring_rule_type',
'recurring_interval', 'recurring_interval',
) )
def _onchange_date_start(self): def _onchange_date_start(self):
for rec in self.filtered('date_start'): 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.date_start,
rec.recurring_invoicing_type, rec.recurring_invoicing_type,
rec.recurring_rule_type, rec.recurring_rule_type,
rec.recurring_interval, rec.recurring_interval,
max_date_end=rec.date_end,
) )
@api.constrains('is_canceled', 'is_auto_renew') @api.constrains('is_canceled', 'is_auto_renew')
@@ -533,6 +636,8 @@ class ContractLine(models.Model):
def _get_period_to_invoice( def _get_period_to_invoice(
self, last_date_invoiced, recurring_next_date, stop_at_date_end=True 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() self.ensure_one()
first_date_invoiced = False first_date_invoiced = False
if not recurring_next_date: if not recurring_next_date:
@@ -542,24 +647,14 @@ class ContractLine(models.Model):
if last_date_invoiced if last_date_invoiced
else self.date_start else self.date_start
) )
if self.recurring_rule_type == 'monthlylastday': last_date_invoiced = self._get_next_period_date_end(
last_date_invoiced = recurring_next_date first_date_invoiced,
else: self.recurring_invoicing_type,
if self.recurring_invoicing_type == 'pre-paid': self.recurring_rule_type,
last_date_invoiced = ( self.recurring_interval,
recurring_next_date max_date_end=(self.date_end if stop_at_date_end else False),
+ self.get_relative_delta( next_invoice_date=recurring_next_date,
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
return first_date_invoiced, last_date_invoiced, recurring_next_date return first_date_invoiced, last_date_invoiced, recurring_next_date
@api.multi @api.multi
@@ -651,15 +746,22 @@ class ContractLine(models.Model):
) )
) )
new_date_start = rec.date_start + delay_delta 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, new_date_start,
rec.recurring_invoicing_type, rec.recurring_invoicing_type,
rec.recurring_rule_type, rec.recurring_rule_type,
rec.recurring_interval, rec.recurring_interval,
max_date_end=new_date_end
) )
if rec.date_end: rec.write({
rec.date_end += delay_delta "date_start": new_date_start,
rec.date_start = new_date_start "date_end": new_date_end,
"recurring_next_date": new_recurring_next_date,
})
@api.multi @api.multi
def stop(self, date_end, manual_renew_needed=False, post_message=True): def stop(self, date_end, manual_renew_needed=False, post_message=True):
@@ -712,11 +814,12 @@ class ContractLine(models.Model):
): ):
self.ensure_one() self.ensure_one()
if not recurring_next_date: if not recurring_next_date:
recurring_next_date = self._compute_first_recurring_next_date( recurring_next_date = self._get_recurring_next_date(
date_start, date_start,
self.recurring_invoicing_type, self.recurring_invoicing_type,
self.recurring_rule_type, self.recurring_rule_type,
self.recurring_interval, self.recurring_interval,
max_date_end=date_end,
) )
new_vals = self.read()[0] new_vals = self.read()[0]
new_vals.pop("id", None) new_vals.pop("id", None)
@@ -1023,7 +1126,7 @@ class ContractLine(models.Model):
def _get_renewal_dates(self): def _get_renewal_dates(self):
self.ensure_one() self.ensure_one()
date_start = self.date_end + relativedelta(days=1) 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 date_start, self.auto_renew_rule_type, self.auto_renew_interval
) )
return date_start, date_end return date_start, date_end

View File

@@ -537,7 +537,7 @@ class TestContract(TestContractBase):
'There was an error and the view couldn\'t be opened.', '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 """Test different combination to compute recurring_next_date
Combination format Combination format
{ {
@@ -547,6 +547,7 @@ class TestContract(TestContractBase):
recurring_rule_type, # ('daily', 'weekly', 'monthly', recurring_rule_type, # ('daily', 'weekly', 'monthly',
# 'monthlylastday', 'yearly'), # 'monthlylastday', 'yearly'),
recurring_interval, # integer recurring_interval, # integer
max_date_end, # date
), ),
} }
""" """
@@ -556,57 +557,88 @@ class TestContract(TestContractBase):
recurring_invoicing_type, recurring_invoicing_type,
recurring_rule_type, recurring_rule_type,
recurring_interval, 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_invoicing_type,
recurring_interval, recurring_interval,
recurring_rule_type, recurring_rule_type,
date_start, date_start,
max_date_end,
) )
combinations = [ combinations = [
( (
to_date('2018-01-01'), 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'),
(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-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-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-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-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-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'),
(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('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'] contract_line_env = self.env['contract.line']
for recurring_next_date, combination in combinations: for recurring_next_date, combination in combinations:
self.assertEqual( self.assertEqual(
recurring_next_date, recurring_next_date,
contract_line_env._compute_first_recurring_next_date( contract_line_env._get_recurring_next_date(
*combination *combination
), ),
error_message(*combination), error_message(*combination),

View File

@@ -15,11 +15,13 @@
<group> <group>
<field name="create_invoice_visibility" invisible="1"/> <field name="create_invoice_visibility" invisible="1"/>
<field name="date_start" required="1"/> <field name="date_start" required="1"/>
<field name="next_period_date_start"/>
<field name="recurring_next_date"/> <field name="recurring_next_date"/>
</group> </group>
<group> <group>
<field name="date_end" <field name="date_end"
attrs="{'required': [('is_auto_renew', '=', True)]}"/> attrs="{'required': [('is_auto_renew', '=', True)]}"/>
<field name="next_period_date_end"/>
</group> </group>
<group groups="base.group_no_one"> <group groups="base.group_no_one">
<field name="last_date_invoiced" readonly="True"/> <field name="last_date_invoiced" readonly="True"/>