Browse Source

Merge PR #434 into 12.0

Signed-off-by pedrobaeza
pull/422/merge
OCA-git-bot 5 years ago
parent
commit
755a03562b
  1. 2
      contract/__manifest__.py
  2. 10
      contract/migrations/12.0.5.0.0/pre-migration.py
  3. 34
      contract/models/abstract_contract_line.py
  4. 240
      contract/models/contract_line.py
  5. 473
      contract/tests/test_contract.py
  6. 4
      contract/views/abstract_contract_line.xml
  7. 2
      contract/views/contract_line.xml
  8. 1
      contract_sale_mandate/tests/test_contract_sale_mandate.py
  9. 10
      product_contract/migrations/12.0.3.0.0/pre-migration.py
  10. 1
      product_contract/tests/test_sale_order.py
  11. 3
      product_contract/views/product_template.xml
  12. 3
      product_contract/views/sale_order.xml

2
contract/__manifest__.py

@ -4,7 +4,7 @@
# Copyright 2016-2018 Tecnativa - Carlos Dauden # Copyright 2016-2018 Tecnativa - Carlos Dauden
# Copyright 2017 Tecnativa - Vicent Cubells # Copyright 2017 Tecnativa - Vicent Cubells
# Copyright 2016-2017 LasLabs Inc. # Copyright 2016-2017 LasLabs Inc.
# Copyright 2018 ACSONE SA/NV
# Copyright 2018-2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {

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

34
contract/models/abstract_contract_line.py

@ -70,9 +70,20 @@ class ContractAbstractContractLine(models.AbstractModel):
[('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], [('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')],
default='pre-paid', default='pre-paid',
string='Invoicing type', string='Invoicing type',
help="Specify if process date is 'from' or 'to' invoicing date",
help=(
"Specify if the invoice must be generated at the beginning "
"(pre-paid) or end (post-paid) of the period."
),
required=True, required=True,
) )
recurring_invoicing_offset = fields.Integer(
compute="_compute_recurring_invoicing_offset",
string="Invoicing offset",
help=(
"Number of days to offset the invoice from the period end "
"date (in post-paid mode) or start date (in pre-paid mode)."
)
)
recurring_interval = fields.Integer( recurring_interval = fields.Integer(
default=1, default=1,
string='Invoice Every', string='Invoice Every',
@ -115,6 +126,27 @@ class ContractAbstractContractLine(models.AbstractModel):
ondelete='cascade', ondelete='cascade',
) )
@api.model
def _get_default_recurring_invoicing_offset(
self, recurring_invoicing_type, recurring_rule_type
):
if (
recurring_invoicing_type == 'pre-paid'
or recurring_rule_type == 'monthlylastday'
):
return 0
else:
return 1
@api.depends('recurring_invoicing_type', 'recurring_rule_type')
def _compute_recurring_invoicing_offset(self):
for rec in self:
rec.recurring_invoicing_offset = (
self._get_default_recurring_invoicing_offset(
rec.recurring_invoicing_type, rec.recurring_rule_type
)
)
@api.depends( @api.depends(
'automatic_price', 'automatic_price',
'specific_price', 'specific_price',

240
contract/models/contract_line.py

@ -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",
@ -361,20 +369,140 @@ class ContractLine(models.Model):
date_start, date_start,
recurring_invoicing_type, recurring_invoicing_type,
recurring_rule_type, recurring_rule_type,
recurring_interval
):
# deprecated method for backward compatibility
return self.get_next_invoice_date(
date_start,
recurring_invoicing_type,
self._get_default_recurring_invoicing_offset(
recurring_invoicing_type, recurring_rule_type
),
recurring_rule_type,
recurring_interval, recurring_interval,
max_date_end=False,
)
@api.model
def get_next_invoice_date(
self,
next_period_date_start,
recurring_invoicing_type,
recurring_invoicing_offset,
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
next_period_date_end = self.get_next_period_date_end(
next_period_date_start,
recurring_rule_type,
recurring_interval,
max_date_end=max_date_end,
) )
if not next_period_date_end:
return False
if recurring_invoicing_type == 'pre-paid': if recurring_invoicing_type == 'pre-paid':
return date_start
return date_start + self.get_relative_delta(
recurring_next_date = (
next_period_date_start
+ relativedelta(days=recurring_invoicing_offset)
)
else: # post-paid
recurring_next_date = (
next_period_date_end
+ relativedelta(days=recurring_invoicing_offset)
)
return recurring_next_date
@api.model
def get_next_period_date_end(
self,
next_period_date_start,
recurring_rule_type,
recurring_interval,
max_date_end,
next_invoice_date=False,
recurring_invoicing_type=False,
recurring_invoicing_offset=False,
):
"""Compute the end date for the next period.
The next period normally depends on recurrence options only.
It is however possible to provide it a next invoice date, in
which case this method can adjust the next period based on that
too. In that scenario it required the invoicing type and offset
arguments.
"""
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
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_invoicing_type == 'pre-paid':
next_period_date_end = (
next_invoice_date
- relativedelta(days=recurring_invoicing_offset)
+ self.get_relative_delta(
recurring_rule_type, recurring_interval recurring_rule_type, recurring_interval
) )
- relativedelta(days=1)
)
else: # post-paid
next_period_date_end = (
next_invoice_date
- relativedelta(days=recurring_invoicing_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
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_invoicing_offset',
'recurring_rule_type',
'recurring_interval',
'date_end',
'recurring_next_date',
)
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_rule_type,
rec.recurring_interval,
max_date_end=rec.date_end,
next_invoice_date=rec.recurring_next_date,
recurring_invoicing_type=rec.recurring_invoicing_type,
recurring_invoicing_offset=rec.recurring_invoicing_offset,
)
@api.model @api.model
def compute_first_date_end(
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 +524,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 +532,20 @@ 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_next_invoice_date(
rec.date_start, rec.date_start,
rec.recurring_invoicing_type, rec.recurring_invoicing_type,
rec.recurring_invoicing_offset,
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,33 +664,25 @@ 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
if not recurring_next_date: if not recurring_next_date:
return first_date_invoiced, last_date_invoiced, recurring_next_date
return False, False, False
first_date_invoiced = ( first_date_invoiced = (
last_date_invoiced + relativedelta(days=1) last_date_invoiced + relativedelta(days=1)
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 = 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
last_date_invoiced = self.get_next_period_date_end(
first_date_invoiced,
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,
recurring_invoicing_type=self.recurring_invoicing_type,
recurring_invoicing_offset=self.recurring_invoicing_offset,
) )
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
@ -580,23 +703,19 @@ class ContractLine(models.Model):
@api.multi @api.multi
def _update_recurring_next_date(self): def _update_recurring_next_date(self):
for rec in self: for rec in self:
old_date = rec.recurring_next_date
new_date = old_date + self.get_relative_delta(
rec.recurring_rule_type, rec.recurring_interval
last_date_invoiced = rec.next_period_date_end
recurring_next_date = rec.get_next_invoice_date(
last_date_invoiced + relativedelta(days=1),
rec.recurring_invoicing_type,
rec.recurring_invoicing_offset,
rec.recurring_rule_type,
rec.recurring_interval,
max_date_end=rec.date_end,
) )
if rec.recurring_rule_type == 'monthlylastday':
last_date_invoiced = old_date
elif rec.recurring_invoicing_type == 'post-paid':
last_date_invoiced = old_date - relativedelta(days=1)
elif rec.recurring_invoicing_type == 'pre-paid':
last_date_invoiced = new_date - relativedelta(days=1)
if rec.date_end and last_date_invoiced >= rec.date_end:
rec.last_date_invoiced = rec.date_end
rec.recurring_next_date = False
else:
rec.last_date_invoiced = last_date_invoiced
rec.recurring_next_date = new_date
rec.write({
"recurring_next_date": recurring_next_date,
"last_date_invoiced": last_date_invoiced,
})
@api.multi @api.multi
def _init_last_date_invoiced(self): def _init_last_date_invoiced(self):
@ -609,8 +728,9 @@ class ContractLine(models.Model):
last_date_invoiced = ( last_date_invoiced = (
rec.recurring_next_date rec.recurring_next_date
- self.get_relative_delta( - self.get_relative_delta(
rec.recurring_rule_type, rec.recurring_interval
rec.recurring_rule_type, rec.recurring_interval - 1
) )
- relativedelta(days=1)
) )
elif rec.recurring_invoicing_type == 'post-paid': elif rec.recurring_invoicing_type == 'post-paid':
last_date_invoiced = ( last_date_invoiced = (
@ -618,12 +738,18 @@ class ContractLine(models.Model):
- self.get_relative_delta( - self.get_relative_delta(
rec.recurring_rule_type, rec.recurring_interval rec.recurring_rule_type, rec.recurring_interval
) )
) - relativedelta(days=1)
- relativedelta(days=1)
)
if last_date_invoiced > rec.date_start: if last_date_invoiced > rec.date_start:
rec.last_date_invoiced = last_date_invoiced rec.last_date_invoiced = last_date_invoiced
@api.model @api.model
def get_relative_delta(self, recurring_rule_type, interval): def get_relative_delta(self, recurring_rule_type, interval):
"""Return a relativedelta for one period.
When added to the first day of the period,
it gives the first day of the next period.
"""
if recurring_rule_type == 'daily': if recurring_rule_type == 'daily':
return relativedelta(days=interval) return relativedelta(days=interval)
elif recurring_rule_type == 'weekly': elif recurring_rule_type == 'weekly':
@ -631,7 +757,7 @@ class ContractLine(models.Model):
elif recurring_rule_type == 'monthly': elif recurring_rule_type == 'monthly':
return relativedelta(months=interval) return relativedelta(months=interval)
elif recurring_rule_type == 'monthlylastday': elif recurring_rule_type == 'monthlylastday':
return relativedelta(months=interval, day=31)
return relativedelta(months=interval, day=1)
else: else:
return relativedelta(years=interval) return relativedelta(years=interval)
@ -651,15 +777,23 @@ 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_next_invoice_date(
new_date_start, new_date_start,
rec.recurring_invoicing_type, rec.recurring_invoicing_type,
rec.recurring_invoicing_offset,
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.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 @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 +846,13 @@ 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_next_invoice_date(
date_start, date_start,
self.recurring_invoicing_type, self.recurring_invoicing_type,
self.recurring_invoicing_offset,
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 +1159,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

473
contract/tests/test_contract.py

@ -2,6 +2,7 @@
# Copyright 2018 Tecnativa - Pedro M. Baeza # Copyright 2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from collections import namedtuple
from datetime import timedelta from datetime import timedelta
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import fields from odoo import fields
@ -247,7 +248,7 @@ class TestContract(TestContractBase):
self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced) self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced)
def test_contract_monthly_lastday(self): def test_contract_monthly_lastday(self):
recurring_next_date = to_date('2018-03-31')
recurring_next_date = to_date('2018-02-28')
last_date_invoiced = to_date('2018-02-22') last_date_invoiced = to_date('2018-02-22')
self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_next_date = '2018-02-22'
self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_invoicing_type = 'post-paid'
@ -279,7 +280,7 @@ class TestContract(TestContractBase):
) )
self.contract.recurring_create_invoice() self.contract.recurring_create_invoice()
self.assertEqual( self.assertEqual(
self.acct_line.recurring_next_date, to_date('2018-04-01')
self.acct_line.recurring_next_date, to_date('2018-3-16')
) )
self.assertEqual( self.assertEqual(
self.acct_line.last_date_invoiced, to_date('2018-02-28') self.acct_line.last_date_invoiced, to_date('2018-02-28')
@ -537,7 +538,34 @@ 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_default_recurring_invoicing_offset(self):
clm = self.env['contract.line']
self.assertEqual(
clm._get_default_recurring_invoicing_offset(
"pre-paid", "monthly"
),
0
)
self.assertEqual(
clm._get_default_recurring_invoicing_offset(
"post-paid", "monthly"
),
1
)
self.assertEqual(
clm._get_default_recurring_invoicing_offset(
"pre-paid", "monthlylastday"
),
0
)
self.assertEqual(
clm._get_default_recurring_invoicing_offset(
"post-paid", "monthlylastday"
),
0
)
def test_get_next_invoice_date(self):
"""Test different combination to compute recurring_next_date """Test different combination to compute recurring_next_date
Combination format Combination format
{ {
@ -547,6 +575,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
), ),
} }
""" """
@ -554,64 +583,415 @@ class TestContract(TestContractBase):
def error_message( def error_message(
date_start, date_start,
recurring_invoicing_type, recurring_invoicing_type,
recurring_invoicing_offset,
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-%d every %d %s case, "
"start with %s (max_date_end=%s)" % (
recurring_invoicing_type, recurring_invoicing_type,
recurring_invoicing_offset,
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', 0, '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', 0, 'monthly', 1,
to_date('2018-01-15')),
),
(
False,
(to_date('2018-01-16'), 'pre-paid', 0, 'monthly', 1,
to_date('2018-01-15')),
),
(
to_date('2018-01-01'),
(to_date('2018-01-01'), 'pre-paid', 0, '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', 1, 'monthly', 1,
False),
),
(
to_date('2018-01-16'),
(to_date('2018-01-01'), 'post-paid', 1, 'monthly', 1,
to_date('2018-01-15')),
),
(
False,
(to_date('2018-01-16'), 'post-paid', 1, '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', 1, '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', 0, 'monthlylastday', 1,
False),
), ),
( (
to_date('2018-01-31'),
(to_date('2018-01-06'), 'pre-paid', 'monthlylastday', 1),
to_date('2018-01-06'),
(to_date('2018-01-06'), 'pre-paid', 0, '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'), 'post-paid', 0, 'monthlylastday', 2,
False),
),
(
to_date('2018-01-05'),
(to_date('2018-01-05'), 'pre-paid', 0, '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', 0, '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', 1, '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_next_invoice_date(
*combination *combination
), ),
error_message(*combination), error_message(*combination),
) )
def test_next_invoicing_period(self):
"""Test different combination for next invoicing period
{
(
'recurring_next_date', # date
'next_period_date_start', # date
'next_period_date_end' # date
): (
date_start, # date
date_end, # date
last_date_invoiced, # date
recurring_next_date, # date
recurring_invoicing_type, # ('pre-paid','post-paid',)
recurring_rule_type, # ('daily', 'weekly', 'monthly',
# 'monthlylastday', 'yearly'),
recurring_interval, # integer
max_date_end, # date
),
}
"""
def _update_contract_line(
case,
date_start,
date_end,
last_date_invoiced,
recurring_next_date,
recurring_invoicing_type,
recurring_rule_type,
recurring_interval,
max_date_end,
):
self.acct_line.write(
{
'date_start': date_start,
'date_end': date_end,
'last_date_invoiced': last_date_invoiced,
'recurring_next_date': recurring_next_date,
'recurring_invoicing_type': recurring_invoicing_type,
'recurring_rule_type': recurring_rule_type,
'recurring_interval': recurring_interval,
'max_date_end': max_date_end,
}
)
def _get_result():
return Result(
recurring_next_date=self.acct_line.recurring_next_date,
next_period_date_start=self.acct_line.next_period_date_start,
next_period_date_end=self.acct_line.next_period_date_end,
)
def _error_message(
case,
date_start,
date_end,
last_date_invoiced,
recurring_next_date,
recurring_invoicing_type,
recurring_rule_type,
recurring_interval,
max_date_end,
):
return (
"Error in case %s:"
"date_start: %s, "
"date_end: %s, "
"last_date_invoiced: %s, "
"recurring_next_date: %s, "
"recurring_invoicing_type: %s, "
"recurring_rule_type: %s, "
"recurring_interval: %s, "
"max_date_end: %s, "
) % (
case,
date_start,
date_end,
last_date_invoiced,
recurring_next_date,
recurring_invoicing_type,
recurring_rule_type,
recurring_interval,
max_date_end,
)
Result = namedtuple(
'Result',
[
'recurring_next_date',
'next_period_date_start',
'next_period_date_end',
],
)
Combination = namedtuple(
'Combination',
[
'case',
'date_start',
'date_end',
'last_date_invoiced',
'recurring_next_date',
'recurring_invoicing_type',
'recurring_rule_type',
'recurring_interval',
'max_date_end',
],
)
combinations = {
Result(
recurring_next_date=to_date('2019-01-01'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-31'),
): Combination(
case="1",
date_start='2019-01-01',
date_end=False,
last_date_invoiced=False,
recurring_next_date='2019-01-01',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-01'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-15'),
): Combination(
case="2",
date_start='2019-01-01',
date_end='2019-01-15',
last_date_invoiced=False,
recurring_next_date='2019-01-01',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-05'),
next_period_date_start=to_date('2019-01-05'),
next_period_date_end=to_date('2019-01-15'),
): Combination(
case="3",
date_start='2019-01-05',
date_end='2019-01-15',
last_date_invoiced=False,
recurring_next_date='2019-01-05',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-05'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-15'),
): Combination(
case="4",
date_start='2019-01-01',
date_end='2019-01-15',
last_date_invoiced=False,
recurring_next_date='2019-01-05',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-02-01'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-31'),
): Combination(
case="5",
date_start='2019-01-01',
date_end=False,
last_date_invoiced=False,
recurring_next_date='2019-02-01',
recurring_invoicing_type='post-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-02-01'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-15'),
): Combination(
case="6",
date_start='2019-01-01',
date_end='2019-01-15',
last_date_invoiced=False,
recurring_next_date='2019-02-01',
recurring_invoicing_type='post-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-02-01'),
next_period_date_start=to_date('2019-01-05'),
next_period_date_end=to_date('2019-01-31'),
): Combination(
case="7",
date_start='2019-01-05',
date_end=False,
last_date_invoiced=False,
recurring_next_date='2019-02-01',
recurring_invoicing_type='post-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-05'),
next_period_date_start=to_date('2019-01-01'),
next_period_date_end=to_date('2019-01-15'),
): Combination(
case="8",
date_start='2019-01-01',
date_end='2019-01-15',
last_date_invoiced=False,
recurring_next_date='2019-01-05',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-01'),
next_period_date_start=to_date('2018-12-16'),
next_period_date_end=to_date('2019-01-31'),
): Combination(
case="9",
date_start='2018-01-01',
date_end='2020-01-15',
last_date_invoiced='2018-12-15',
recurring_next_date='2019-01-01',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2019-01-01'),
next_period_date_start=to_date('2018-12-16'),
next_period_date_end=to_date('2018-12-31'),
): Combination(
case="10",
date_start='2018-01-01',
date_end='2020-01-15',
last_date_invoiced='2018-12-15',
recurring_next_date='2019-01-01',
recurring_invoicing_type='post-paid',
recurring_rule_type='monthly',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2018-12-31'),
next_period_date_start=to_date('2018-12-16'),
next_period_date_end=to_date('2018-12-31'),
): Combination(
case="11",
date_start='2018-01-01',
date_end='2020-01-15',
last_date_invoiced='2018-12-15',
recurring_next_date='2018-12-31',
recurring_invoicing_type='post-paid',
recurring_rule_type='monthlylastday',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2018-12-16'),
next_period_date_start=to_date('2018-12-16'),
next_period_date_end=to_date('2018-12-31'),
): Combination(
case="12",
date_start='2018-01-01',
date_end='2020-01-15',
last_date_invoiced='2018-12-15',
recurring_next_date='2018-12-16',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthlylastday',
recurring_interval=1,
max_date_end=False,
),
Result(
recurring_next_date=to_date('2018-01-05'),
next_period_date_start=to_date('2018-01-05'),
next_period_date_end=to_date('2018-03-31'),
): Combination(
case="12",
date_start='2018-01-05',
date_end='2020-01-15',
last_date_invoiced=False,
recurring_next_date='2018-01-05',
recurring_invoicing_type='pre-paid',
recurring_rule_type='monthlylastday',
recurring_interval=3,
max_date_end=False,
),
}
for result, combination in combinations.items():
_update_contract_line(*combination)
self.assertEqual(
result, _get_result(), _error_message(*combination)
)
def test_recurring_next_date(self): def test_recurring_next_date(self):
"""recurring next date for a contract is the min for all lines""" """recurring next date for a contract is the min for all lines"""
self.contract.recurring_create_invoice() self.contract.recurring_create_invoice()
@ -1331,7 +1711,7 @@ class TestContract(TestContractBase):
len(invoice_lines), 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.date_start = '2018-01-05'
self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_invoicing_type = 'post-paid'
self.acct_line.recurring_rule_type = 'monthlylastday' self.acct_line.recurring_rule_type = 'monthlylastday'
@ -1362,6 +1742,67 @@ class TestContract(TestContractBase):
self.assertEqual(last, to_date('2018-03-15')) self.assertEqual(last, to_date('2018-03-15'))
self.acct_line.manual_renew_needed = True 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.assertEqual(recurring_next_date, to_date('2018-01-05'))
self.assertEqual(
self.acct_line.recurring_next_date, to_date('2018-01-05')
)
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.assertEqual(recurring_next_date, to_date('2018-02-01'))
self.assertEqual(
self.acct_line.recurring_next_date, to_date('2018-02-01')
)
self.assertEqual(
self.acct_line.last_date_invoiced, 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-03-01'))
self.assertEqual(last, to_date('2018-03-15'))
self.assertEqual(recurring_next_date, to_date('2018-03-01'))
self.assertEqual(
self.acct_line.recurring_next_date, to_date('2018-03-01')
)
self.assertEqual(
self.acct_line.last_date_invoiced, 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.assertFalse(first)
self.assertFalse(last)
self.assertFalse(recurring_next_date)
self.assertFalse(self.acct_line.recurring_next_date)
self.assertEqual(
self.acct_line.last_date_invoiced, to_date('2018-03-15')
)
def test_get_period_to_invoice_monthly_pre_paid_2(self): def test_get_period_to_invoice_monthly_pre_paid_2(self):
self.acct_line.date_start = '2018-01-05' self.acct_line.date_start = '2018-01-05'
self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_invoicing_type = 'pre-paid'

4
contract/views/abstract_contract_line.xml

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

2
contract/views/contract_line.xml

@ -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"/>

1
contract_sale_mandate/tests/test_contract_sale_mandate.py

@ -26,6 +26,7 @@ class TestContractSaleMandate(TestContractBase):
'is_contract': True, 'is_contract': True,
'default_qty': 12, 'default_qty': 12,
'recurring_rule_type': "monthlylastday", 'recurring_rule_type': "monthlylastday",
'recurring_invoicing_type': "post-paid",
'contract_template_id': cls.contract_template1.id, 'contract_template_id': cls.contract_template1.id,
} }
) )

10
product_contract/migrations/12.0.3.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 product_template
SET recurring_invoicing_type = 'post-paid'
WHERE recurring_rule_type = 'monthlylastday'
"""
)

1
product_contract/tests/test_sale_order.py

@ -43,6 +43,7 @@ class TestSaleOrder(TransactionCase):
'is_contract': True, 'is_contract': True,
'default_qty': 12, 'default_qty': 12,
'recurring_rule_type': "monthlylastday", 'recurring_rule_type': "monthlylastday",
'recurring_invoicing_type': "post-paid",
'contract_template_id': self.contract_template1.id, 'contract_template_id': self.contract_template1.id,
} }
) )

3
product_contract/views/product_template.xml

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

3
product_contract/views/sale_order.xml

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

Loading…
Cancel
Save