diff --git a/contract/data/contract_line_constraints.py b/contract/data/contract_line_constraints.py
index 74e3eb2f..94b5c8b9 100644
--- a/contract/data/contract_line_constraints.py
+++ b/contract/data/contract_line_constraints.py
@@ -6,7 +6,14 @@ from odoo.fields import Date
CRITERIA = namedtuple(
'CRITERIA',
- ['WHEN', 'HAS_DATE_END', 'IS_AUTO_RENEW', 'HAS_SUCCESSOR', 'CANCELED'],
+ [
+ 'WHEN',
+ 'HAS_DATE_END',
+ 'IS_AUTO_RENEW',
+ 'HAS_SUCCESSOR',
+ 'PREDECESSOR_HAS_SUCCESSOR',
+ 'CANCELED',
+ ],
)
ALLOWED = namedtuple(
'ALLOWED',
@@ -19,6 +26,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=True,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -32,6 +40,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=True,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -45,6 +54,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=True,
@@ -58,6 +68,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=False,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -71,6 +82,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=True,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -84,6 +96,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=True,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -97,6 +110,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=True,
@@ -110,6 +124,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=False,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -123,6 +138,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=True,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -136,6 +152,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=True,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -149,6 +166,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=True,
IS_AUTO_RENEW=False,
HAS_SUCCESSOR=False,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=False,
): ALLOWED(
PLAN_SUCCESSOR=True,
@@ -162,6 +180,7 @@ CRITERIA_ALLOWED_DICT = {
HAS_DATE_END=None,
IS_AUTO_RENEW=None,
HAS_SUCCESSOR=None,
+ PREDECESSOR_HAS_SUCCESSOR=False,
CANCELED=True,
): ALLOWED(
PLAN_SUCCESSOR=False,
@@ -170,6 +189,20 @@ CRITERIA_ALLOWED_DICT = {
CANCEL=False,
UN_CANCEL=True,
),
+ CRITERIA(
+ WHEN=None,
+ HAS_DATE_END=None,
+ IS_AUTO_RENEW=None,
+ HAS_SUCCESSOR=None,
+ PREDECESSOR_HAS_SUCCESSOR=True,
+ CANCELED=True,
+ ): ALLOWED(
+ PLAN_SUCCESSOR=False,
+ STOP_PLAN_SUCCESSOR=False,
+ STOP=False,
+ CANCEL=False,
+ UN_CANCEL=False,
+ ),
}
@@ -187,16 +220,31 @@ def compute_criteria(
date_end,
is_auto_renew,
successor_contract_line_id,
+ predecessor_contract_line_id,
is_canceled,
):
if is_canceled:
- return CRITERIA(
- WHEN=None,
- HAS_DATE_END=None,
- IS_AUTO_RENEW=None,
- HAS_SUCCESSOR=None,
- CANCELED=True,
- )
+ if (
+ not predecessor_contract_line_id
+ or not predecessor_contract_line_id.successor_contract_line_id
+ ):
+ return CRITERIA(
+ WHEN=None,
+ HAS_DATE_END=None,
+ IS_AUTO_RENEW=None,
+ HAS_SUCCESSOR=None,
+ PREDECESSOR_HAS_SUCCESSOR=False,
+ CANCELED=True,
+ )
+ else:
+ return CRITERIA(
+ WHEN=None,
+ HAS_DATE_END=None,
+ IS_AUTO_RENEW=None,
+ HAS_SUCCESSOR=None,
+ PREDECESSOR_HAS_SUCCESSOR=True,
+ CANCELED=True,
+ )
when = compute_when(date_start, date_end)
has_date_end = date_end if not date_end else True
is_auto_renew = is_auto_renew
@@ -207,6 +255,7 @@ def compute_criteria(
HAS_DATE_END=has_date_end,
IS_AUTO_RENEW=is_auto_renew,
HAS_SUCCESSOR=has_successor,
+ PREDECESSOR_HAS_SUCCESSOR=None,
CANCELED=canceled,
)
@@ -216,6 +265,7 @@ def get_allowed(
date_end,
is_auto_renew,
successor_contract_line_id,
+ predecessor_contract_line_id,
is_canceled,
):
criteria = compute_criteria(
@@ -223,6 +273,7 @@ def get_allowed(
date_end,
is_auto_renew,
successor_contract_line_id,
+ predecessor_contract_line_id,
is_canceled,
)
if criteria in CRITERIA_ALLOWED_DICT:
diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py
index 9d96ac4e..6d4cb7ed 100644
--- a/contract/models/contract_line.py
+++ b/contract/models/contract_line.py
@@ -1,6 +1,7 @@
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
@@ -91,25 +92,28 @@ class AccountAnalyticInvoiceLine(models.Model):
'date_end',
'is_auto_renew',
'successor_contract_line_id',
+ 'predecessor_contract_line_id',
'is_canceled',
)
def _compute_allowed(self):
for rec in self:
- allowed = get_allowed(
- rec.date_start,
- rec.date_end,
- rec.is_auto_renew,
- rec.successor_contract_line_id,
- rec.is_canceled,
- )
- if allowed:
- rec.is_plan_successor_allowed = allowed.PLAN_SUCCESSOR
- rec.is_stop_plan_successor_allowed = (
- allowed.STOP_PLAN_SUCCESSOR
+ if rec.date_start:
+ allowed = get_allowed(
+ rec.date_start,
+ rec.date_end,
+ rec.is_auto_renew,
+ rec.successor_contract_line_id,
+ rec.predecessor_contract_line_id,
+ rec.is_canceled,
)
- rec.is_stop_allowed = allowed.STOP
- rec.is_cancel_allowed = allowed.CANCEL
- rec.is_un_cancel_allowed = allowed.UN_CANCEL
+ if allowed:
+ rec.is_plan_successor_allowed = allowed.PLAN_SUCCESSOR
+ rec.is_stop_plan_successor_allowed = (
+ allowed.STOP_PLAN_SUCCESSOR
+ )
+ rec.is_stop_allowed = allowed.STOP
+ rec.is_cancel_allowed = allowed.CANCEL
+ rec.is_un_cancel_allowed = allowed.UN_CANCEL
@api.constrains('is_auto_renew', 'successor_contract_line_id', 'date_end')
def _check_allowed(self):
@@ -185,8 +189,12 @@ class AccountAnalyticInvoiceLine(models.Model):
"""Date end should be auto-computed if a contract line is set to
auto_renew"""
for rec in self.filtered('is_auto_renew'):
- rec.date_end = self.date_start + self.get_relative_delta(
- rec.auto_renew_rule_type, rec.auto_renew_interval
+ rec.date_end = (
+ self.date_start
+ + self.get_relative_delta(
+ rec.auto_renew_rule_type, rec.auto_renew_interval
+ )
+ - relativedelta(days=1)
)
@api.onchange(
@@ -255,22 +263,24 @@ class AccountAnalyticInvoiceLine(models.Model):
def _compute_create_invoice_visibility(self):
today = fields.Date.today()
for line in self:
- if today < line.date_start:
- line.create_invoice_visibility = False
- elif not line.date_end:
- line.create_invoice_visibility = True
- elif line.recurring_next_date:
- if line.recurring_invoicing_type == 'pre-paid':
- line.create_invoice_visibility = (
- line.recurring_next_date <= line.date_end
- )
- else:
- line.create_invoice_visibility = (
- line.recurring_next_date
- - line.get_relative_delta(
- line.recurring_rule_type, line.recurring_interval
+ if line.date_start:
+ if today < line.date_start:
+ line.create_invoice_visibility = False
+ elif not line.date_end:
+ line.create_invoice_visibility = True
+ elif line.recurring_next_date:
+ if line.recurring_invoicing_type == 'pre-paid':
+ line.create_invoice_visibility = (
+ line.recurring_next_date <= line.date_end
)
- ) <= line.date_end
+ else:
+ line.create_invoice_visibility = (
+ line.recurring_next_date
+ - line.get_relative_delta(
+ line.recurring_rule_type,
+ line.recurring_interval,
+ )
+ ) <= line.date_end
@api.model
def recurring_create_invoice(self, contract=False):
@@ -577,9 +587,9 @@ class AccountAnalyticInvoiceLine(models.Model):
for rec in self:
if rec.date_start >= date_start:
if rec.date_start < date_end:
- delay = date_end - rec.date_start
+ delay = (date_end - rec.date_start) + timedelta(days=1)
else:
- delay = date_end - date_start
+ delay = (date_end - date_start) + timedelta(days=1)
rec.delay(delay)
contract_line |= rec
else:
@@ -626,6 +636,9 @@ class AccountAnalyticInvoiceLine(models.Model):
)
)
contract.message_post(body=msg)
+ self.mapped('predecessor_contract_line_id').write(
+ {'successor_contract_line_id': False}
+ )
return self.write({'is_canceled': True})
@api.multi
@@ -644,9 +657,14 @@ class AccountAnalyticInvoiceLine(models.Model):
)
)
contract.message_post(body=msg)
- return self.write(
- {'is_canceled': False, 'recurring_next_date': recurring_next_date}
- )
+ for rec in self:
+ if rec.predecessor_contract_line_id:
+ rec.predecessor_contract_line_id.successor_contract_line_id = (
+ rec
+ )
+ rec.is_canceled = False
+ rec.recurring_next_date = recurring_next_date
+ return True
@api.multi
def action_uncancel(self):
@@ -739,9 +757,13 @@ class AccountAnalyticInvoiceLine(models.Model):
@api.multi
def _get_renewal_dates(self):
self.ensure_one()
- date_start = self.date_end
- date_end = date_start + self.get_relative_delta(
- self.auto_renew_rule_type, self.auto_renew_interval
+ date_start = self.date_end + relativedelta(days=1)
+ date_end = (
+ date_start
+ + self.get_relative_delta(
+ self.auto_renew_rule_type, self.auto_renew_interval
+ )
+ - relativedelta(days=1)
)
return date_start, date_end
diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py
index 283e5c85..2dea7bc2 100644
--- a/contract/tests/test_contract.py
+++ b/contract/tests/test_contract.py
@@ -2,6 +2,7 @@
# Copyright 2018 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import ValidationError
@@ -828,10 +829,11 @@ class TestContract(TestContractBase):
)
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - start_date),
+ start_date + (suspension_end - start_date) + timedelta(days=1),
)
self.assertEqual(
- self.acct_line.date_end, end_date + (suspension_end - start_date)
+ self.acct_line.date_end,
+ end_date + (suspension_end - start_date) + timedelta(days=1),
)
new_line = self.env['account.analytic.invoice.line'].search(
[('predecessor_contract_line_id', '=', self.acct_line.id)]
@@ -860,10 +862,11 @@ class TestContract(TestContractBase):
)
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - start_date),
+ start_date + (suspension_end - start_date) + timedelta(days=1),
)
self.assertEqual(
- self.acct_line.date_end, end_date + (suspension_end - start_date)
+ self.acct_line.date_end,
+ end_date + (suspension_end - start_date) + timedelta(days=1),
)
new_line = self.env['account.analytic.invoice.line'].search(
[('predecessor_contract_line_id', '=', self.acct_line.id)]
@@ -893,7 +896,7 @@ class TestContract(TestContractBase):
)
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - start_date),
+ start_date + (suspension_end - start_date) + timedelta(days=1),
)
self.assertFalse(self.acct_line.date_end)
new_line = self.env['account.analytic.invoice.line'].search(
@@ -923,11 +926,13 @@ class TestContract(TestContractBase):
)
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - suspension_start),
+ start_date
+ + (suspension_end - suspension_start)
+ + timedelta(days=1),
)
self.assertEqual(
self.acct_line.date_end,
- end_date + (suspension_end - suspension_start),
+ end_date + (suspension_end - suspension_start) + timedelta(days=1),
)
new_line = self.env['account.analytic.invoice.line'].search(
[('predecessor_contract_line_id', '=', self.acct_line.id)]
@@ -957,7 +962,9 @@ class TestContract(TestContractBase):
)
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - suspension_start),
+ start_date
+ + (suspension_end - suspension_start)
+ + timedelta(days=1),
)
self.assertFalse(self.acct_line.date_end)
new_line = self.env['account.analytic.invoice.line'].search(
@@ -988,11 +995,13 @@ class TestContract(TestContractBase):
wizard.stop_plan_successor()
self.assertEqual(
self.acct_line.date_start,
- start_date + (suspension_end - suspension_start),
+ start_date
+ + (suspension_end - suspension_start)
+ + timedelta(days=1),
)
self.assertEqual(
self.acct_line.date_end,
- end_date + (suspension_end - suspension_start),
+ end_date + (suspension_end - suspension_start) + timedelta(days=1),
)
new_line = self.env['account.analytic.invoice.line'].search(
[('predecessor_contract_line_id', '=', self.acct_line.id)]
@@ -1087,6 +1096,62 @@ class TestContract(TestContractBase):
self.acct_line.uncancel(fields.Date.today())
self.assertFalse(self.acct_line.is_canceled)
+ def test_cancel_uncancel_with_predecessor(self):
+ suspension_start = fields.Date.today() + relativedelta(months=3)
+ suspension_end = fields.Date.today() + relativedelta(months=5)
+ start_date = fields.Date.today()
+ end_date = fields.Date.today() + relativedelta(months=4)
+ self.acct_line.write(
+ {
+ 'date_start': start_date,
+ 'recurring_next_date': start_date,
+ 'date_end': end_date,
+ }
+ )
+ self.acct_line.stop_plan_successor(
+ suspension_start, suspension_end, True
+ )
+ self.assertEqual(self.acct_line.date_end, suspension_start)
+ new_line = self.env['account.analytic.invoice.line'].search(
+ [('predecessor_contract_line_id', '=', self.acct_line.id)]
+ )
+ self.assertEqual(self.acct_line.successor_contract_line_id, new_line)
+ new_line.cancel()
+ self.assertTrue(new_line.is_canceled)
+ self.assertFalse(self.acct_line.successor_contract_line_id)
+ self.assertEqual(new_line.predecessor_contract_line_id, self.acct_line)
+ new_line.uncancel(suspension_end)
+ self.assertFalse(new_line.is_canceled)
+ self.assertEqual(self.acct_line.successor_contract_line_id, new_line)
+ self.assertEqual(new_line.recurring_next_date, suspension_end)
+
+ def test_cancel_uncancel_with_predecessor_has_successor(self):
+ suspension_start = fields.Date.today() + relativedelta(months=6)
+ suspension_end = fields.Date.today() + relativedelta(months=7)
+ start_date = fields.Date.today()
+ end_date = fields.Date.today() + relativedelta(months=8)
+ self.acct_line.write(
+ {
+ 'date_start': start_date,
+ 'recurring_next_date': start_date,
+ 'date_end': end_date,
+ }
+ )
+ self.acct_line.stop_plan_successor(
+ suspension_start, suspension_end, True
+ )
+ new_line = self.env['account.analytic.invoice.line'].search(
+ [('predecessor_contract_line_id', '=', self.acct_line.id)]
+ )
+ new_line.cancel()
+ suspension_start = fields.Date.today() + relativedelta(months=4)
+ suspension_end = fields.Date.today() + relativedelta(months=5)
+ self.acct_line.stop_plan_successor(
+ suspension_start, suspension_end, True
+ )
+ with self.assertRaises(ValidationError):
+ new_line.uncancel(suspension_end)
+
def test_check_has_not_date_end_has_successor(self):
self.acct_line.write({'date_end': False, 'is_auto_renew': False})
with self.assertRaises(ValidationError):
@@ -1126,8 +1191,10 @@ class TestContract(TestContractBase):
)
def test_renew(self):
+ self.acct_line._onchange_is_auto_renew()
+ self.assertEqual(self.acct_line.date_end, to_date('2018-12-31'))
new_line = self.acct_line.renew()
self.assertFalse(self.acct_line.is_auto_renew)
self.assertTrue(new_line.is_auto_renew)
self.assertEqual(new_line.date_start, to_date('2019-01-01'))
- self.assertEqual(new_line.date_end, to_date('2020-01-01'))
+ self.assertEqual(new_line.date_end, to_date('2019-12-31'))
diff --git a/contract/views/contract_line.xml b/contract/views/contract_line.xml
index 96875c9f..e2356306 100644
--- a/contract/views/contract_line.xml
+++ b/contract/views/contract_line.xml
@@ -131,6 +131,7 @@