mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
@@ -4,7 +4,7 @@
|
||||
# Copyright 2016-2018 Tecnativa - Carlos Dauden
|
||||
# Copyright 2017 Tecnativa - Vicent Cubells
|
||||
# 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).
|
||||
|
||||
{
|
||||
|
||||
10
contract/migrations/12.0.5.0.0/pre-migration.py
Normal file
10
contract/migrations/12.0.5.0.0/pre-migration.py
Normal file
@@ -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'
|
||||
"""
|
||||
)
|
||||
@@ -70,9 +70,20 @@ class ContractAbstractContractLine(models.AbstractModel):
|
||||
[('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')],
|
||||
default='pre-paid',
|
||||
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,
|
||||
)
|
||||
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(
|
||||
default=1,
|
||||
string='Invoice Every',
|
||||
@@ -115,6 +126,27 @@ class ContractAbstractContractLine(models.AbstractModel):
|
||||
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(
|
||||
'automatic_price',
|
||||
'specific_price',
|
||||
|
||||
@@ -41,6 +41,14 @@ class ContractLine(models.Model):
|
||||
last_date_invoiced = fields.Date(
|
||||
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(
|
||||
string='Termination notice date',
|
||||
compute="_compute_termination_notice_date",
|
||||
@@ -361,20 +369,140 @@ class ContractLine(models.Model):
|
||||
date_start,
|
||||
recurring_invoicing_type,
|
||||
recurring_rule_type,
|
||||
recurring_interval,
|
||||
recurring_interval
|
||||
):
|
||||
if recurring_rule_type == 'monthlylastday':
|
||||
return date_start + self.get_relative_delta(
|
||||
recurring_rule_type, recurring_interval - 1
|
||||
)
|
||||
if recurring_invoicing_type == 'pre-paid':
|
||||
return date_start
|
||||
return date_start + self.get_relative_delta(
|
||||
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,
|
||||
max_date_end=False,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def compute_first_date_end(
|
||||
def get_next_invoice_date(
|
||||
self,
|
||||
next_period_date_start,
|
||||
recurring_invoicing_type,
|
||||
recurring_invoicing_offset,
|
||||
recurring_rule_type,
|
||||
recurring_interval,
|
||||
max_date_end,
|
||||
):
|
||||
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':
|
||||
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
|
||||
)
|
||||
- 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
|
||||
def _get_first_date_end(
|
||||
self, date_start, auto_renew_rule_type, auto_renew_interval
|
||||
):
|
||||
return (
|
||||
@@ -396,7 +524,7 @@ class ContractLine(models.Model):
|
||||
auto_renew"""
|
||||
for rec in self.filtered('is_auto_renew'):
|
||||
if rec.date_start:
|
||||
rec.date_end = self.compute_first_date_end(
|
||||
rec.date_end = self._get_first_date_end(
|
||||
rec.date_start,
|
||||
rec.auto_renew_rule_type,
|
||||
rec.auto_renew_interval,
|
||||
@@ -404,17 +532,20 @@ class ContractLine(models.Model):
|
||||
|
||||
@api.onchange(
|
||||
'date_start',
|
||||
'date_end',
|
||||
'recurring_invoicing_type',
|
||||
'recurring_rule_type',
|
||||
'recurring_interval',
|
||||
)
|
||||
def _onchange_date_start(self):
|
||||
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.recurring_invoicing_type,
|
||||
rec.recurring_invoicing_offset,
|
||||
rec.recurring_rule_type,
|
||||
rec.recurring_interval,
|
||||
max_date_end=rec.date_end,
|
||||
)
|
||||
|
||||
@api.constrains('is_canceled', 'is_auto_renew')
|
||||
@@ -533,33 +664,25 @@ class ContractLine(models.Model):
|
||||
def _get_period_to_invoice(
|
||||
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()
|
||||
first_date_invoiced = False
|
||||
if not recurring_next_date:
|
||||
return first_date_invoiced, last_date_invoiced, recurring_next_date
|
||||
return False, False, False
|
||||
first_date_invoiced = (
|
||||
last_date_invoiced + relativedelta(days=1)
|
||||
if last_date_invoiced
|
||||
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
|
||||
)
|
||||
if stop_at_date_end:
|
||||
if self.date_end and self.date_end < last_date_invoiced:
|
||||
last_date_invoiced = self.date_end
|
||||
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,
|
||||
)
|
||||
return first_date_invoiced, last_date_invoiced, recurring_next_date
|
||||
|
||||
@api.multi
|
||||
@@ -580,23 +703,19 @@ class ContractLine(models.Model):
|
||||
@api.multi
|
||||
def _update_recurring_next_date(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
|
||||
def _init_last_date_invoiced(self):
|
||||
@@ -609,8 +728,9 @@ class ContractLine(models.Model):
|
||||
last_date_invoiced = (
|
||||
rec.recurring_next_date
|
||||
- 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':
|
||||
last_date_invoiced = (
|
||||
@@ -618,12 +738,18 @@ class ContractLine(models.Model):
|
||||
- self.get_relative_delta(
|
||||
rec.recurring_rule_type, rec.recurring_interval
|
||||
)
|
||||
) - relativedelta(days=1)
|
||||
- relativedelta(days=1)
|
||||
)
|
||||
if last_date_invoiced > rec.date_start:
|
||||
rec.last_date_invoiced = last_date_invoiced
|
||||
|
||||
@api.model
|
||||
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':
|
||||
return relativedelta(days=interval)
|
||||
elif recurring_rule_type == 'weekly':
|
||||
@@ -631,7 +757,7 @@ class ContractLine(models.Model):
|
||||
elif recurring_rule_type == 'monthly':
|
||||
return relativedelta(months=interval)
|
||||
elif recurring_rule_type == 'monthlylastday':
|
||||
return relativedelta(months=interval, day=31)
|
||||
return relativedelta(months=interval, day=1)
|
||||
else:
|
||||
return relativedelta(years=interval)
|
||||
|
||||
@@ -651,15 +777,23 @@ class ContractLine(models.Model):
|
||||
)
|
||||
)
|
||||
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,
|
||||
rec.recurring_invoicing_type,
|
||||
rec.recurring_invoicing_offset,
|
||||
rec.recurring_rule_type,
|
||||
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
|
||||
def stop(self, date_end, manual_renew_needed=False, post_message=True):
|
||||
@@ -712,11 +846,13 @@ class ContractLine(models.Model):
|
||||
):
|
||||
self.ensure_one()
|
||||
if not recurring_next_date:
|
||||
recurring_next_date = self._compute_first_recurring_next_date(
|
||||
recurring_next_date = self.get_next_invoice_date(
|
||||
date_start,
|
||||
self.recurring_invoicing_type,
|
||||
self.recurring_invoicing_offset,
|
||||
self.recurring_rule_type,
|
||||
self.recurring_interval,
|
||||
max_date_end=date_end,
|
||||
)
|
||||
new_vals = self.read()[0]
|
||||
new_vals.pop("id", None)
|
||||
@@ -1023,7 +1159,7 @@ class ContractLine(models.Model):
|
||||
def _get_renewal_dates(self):
|
||||
self.ensure_one()
|
||||
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
|
||||
)
|
||||
return date_start, date_end
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Copyright 2018 Tecnativa - Pedro M. Baeza
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import fields
|
||||
@@ -247,7 +248,7 @@ class TestContract(TestContractBase):
|
||||
self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced)
|
||||
|
||||
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')
|
||||
self.acct_line.recurring_next_date = '2018-02-22'
|
||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||
@@ -279,7 +280,7 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
self.contract.recurring_create_invoice()
|
||||
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.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.',
|
||||
)
|
||||
|
||||
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
|
||||
Combination format
|
||||
{
|
||||
@@ -547,6 +575,7 @@ class TestContract(TestContractBase):
|
||||
recurring_rule_type, # ('daily', 'weekly', 'monthly',
|
||||
# 'monthlylastday', 'yearly'),
|
||||
recurring_interval, # integer
|
||||
max_date_end, # date
|
||||
),
|
||||
}
|
||||
"""
|
||||
@@ -554,64 +583,415 @@ class TestContract(TestContractBase):
|
||||
def error_message(
|
||||
date_start,
|
||||
recurring_invoicing_type,
|
||||
recurring_invoicing_offset,
|
||||
recurring_rule_type,
|
||||
recurring_interval,
|
||||
max_date_end,
|
||||
):
|
||||
return "Error in %s every %d %s case, start with %s " % (
|
||||
recurring_invoicing_type,
|
||||
recurring_interval,
|
||||
recurring_rule_type,
|
||||
date_start,
|
||||
return (
|
||||
"Error in %s-%d every %d %s case, "
|
||||
"start with %s (max_date_end=%s)" % (
|
||||
recurring_invoicing_type,
|
||||
recurring_invoicing_offset,
|
||||
recurring_interval,
|
||||
recurring_rule_type,
|
||||
date_start,
|
||||
max_date_end,
|
||||
)
|
||||
)
|
||||
|
||||
combinations = [
|
||||
(
|
||||
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'), '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-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-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-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-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', 'yearly', 1),
|
||||
(to_date('2018-01-05'), 'pre-paid', 0, 'monthlylastday', 2,
|
||||
False),
|
||||
),
|
||||
(
|
||||
to_date('2018-01-05'),
|
||||
(to_date('2018-01-05'), 'pre-paid', 0, 'yearly', 1,
|
||||
False),
|
||||
),
|
||||
(
|
||||
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']
|
||||
for recurring_next_date, combination in combinations:
|
||||
self.assertEqual(
|
||||
recurring_next_date,
|
||||
contract_line_env._compute_first_recurring_next_date(
|
||||
contract_line_env.get_next_invoice_date(
|
||||
*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):
|
||||
"""recurring next date for a contract is the min for all lines"""
|
||||
self.contract.recurring_create_invoice()
|
||||
@@ -1331,7 +1711,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'
|
||||
@@ -1362,6 +1742,67 @@ 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.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):
|
||||
self.acct_line.date_start = '2018-01-05'
|
||||
self.acct_line.recurring_invoicing_type = 'pre-paid'
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
</div>
|
||||
</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>
|
||||
</sheet>
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
<group>
|
||||
<field name="create_invoice_visibility" invisible="1"/>
|
||||
<field name="date_start" required="1"/>
|
||||
<field name="next_period_date_start"/>
|
||||
<field name="recurring_next_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_end"
|
||||
attrs="{'required': [('is_auto_renew', '=', True)]}"/>
|
||||
<field name="next_period_date_end"/>
|
||||
</group>
|
||||
<group groups="base.group_no_one">
|
||||
<field name="last_date_invoiced" readonly="True"/>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
10
product_contract/migrations/12.0.3.0.0/pre-migration.py
Normal file
10
product_contract/migrations/12.0.3.0.0/pre-migration.py
Normal file
@@ -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'
|
||||
"""
|
||||
)
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user