Browse Source

[IMP] - Replace is_suspended flag by manual_renew_needed

Add a computed field for the first date of the termination notice period

Adapt state compute and search method

[IMP] - Improve unit tests
13.0-mig-contract
sbejaoui 6 years ago
committed by Administrator
parent
commit
5109eb2d91
  1. 153
      contract/models/contract_line.py
  2. 614
      contract/tests/test_contract.py
  3. 8
      contract/views/contract_line.xml
  4. 10
      contract/wizards/contract_line_wizard.py
  5. 2
      contract/wizards/contract_line_wizard.xml

153
contract/models/contract_line.py

@ -33,6 +33,12 @@ class AccountAnalyticInvoiceLine(models.Model):
last_date_invoiced = fields.Date(
string='Last Date Invoiced', readonly=True, copy=False
)
termination_notice_date = fields.Date(
string='Termination notice date',
compute="_compute_termination_notice_date",
store=True,
copy=False,
)
create_invoice_visibility = fields.Boolean(
compute='_compute_create_invoice_visibility'
)
@ -55,7 +61,13 @@ class AccountAnalyticInvoiceLine(models.Model):
copy=False,
help="Contract Line origin of this one.",
)
is_suspended = fields.Boolean(string="Suspended", default=False)
manual_renew_needed = fields.Boolean(
string="Manual renew needed",
default=False,
help="This flag is used to make a difference between a definitive stop"
"and temporary one for which a user is not able to plan a"
"successor in advance",
)
is_plan_successor_allowed = fields.Boolean(
string="Plan successor allowed?", compute='_compute_allowed'
)
@ -76,8 +88,7 @@ class AccountAnalyticInvoiceLine(models.Model):
selection=[
('upcoming', 'Upcoming'),
('in-progress', 'In-progress'),
('suspension-planed', 'Suspension Planed'),
('suspended', 'Suspended'),
('to-renew', 'To renew'),
('upcoming-close', 'Upcoming Close'),
('closed', 'Closed'),
('canceled', 'Canceled'),
@ -94,6 +105,24 @@ class AccountAnalyticInvoiceLine(models.Model):
)
@api.multi
@api.depends(
'date_end',
'termination_notice_rule_type',
'termination_notice_interval',
)
def _compute_termination_notice_date(self):
for rec in self:
if rec.date_end:
rec.termination_notice_date = (
rec.date_end
- self.get_relative_delta(
rec.termination_notice_rule_type,
rec.termination_notice_interval,
)
)
@api.multi
@api.depends('is_canceled', 'date_start', 'date_end', 'is_auto_renew')
def _compute_state(self):
today = fields.Date.context_today(self)
for rec in self:
@ -111,17 +140,24 @@ class AccountAnalyticInvoiceLine(models.Model):
and (not rec.date_end or rec.date_end >= today)
):
# In period
if rec.is_suspended:
rec.state = 'suspension-planed'
continue
if rec.date_end and not rec.is_auto_renew:
if (
rec.termination_notice_date
and rec.termination_notice_date < today
and not rec.is_auto_renew
and not rec.manual_renew_needed
):
rec.state = 'upcoming-close'
else:
rec.state = 'in-progress'
continue
if rec.date_end and rec.date_end < today:
if rec.is_suspended and not rec.successor_contract_line_id:
rec.state = 'suspended'
# After
if (
rec.manual_renew_needed
and not rec.successor_contract_line_id
or rec.is_auto_renew
):
rec.state = 'to-renew'
else:
rec.state = 'closed'
@ -139,36 +175,28 @@ class AccountAnalyticInvoiceLine(models.Model):
"&",
"&",
"&",
"&",
('date_start', '<=', today),
('is_auto_renew', '=', True),
('is_suspended', '=', False),
('is_canceled', '=', False),
"|",
('date_end', '>=', today),
('date_end', '=', False),
]
if state == 'suspension-planed':
return [
"&",
"&",
"|",
"&",
('date_start', '<=', today),
('is_suspended', '=', True),
('is_canceled', '=', False),
'|',
('date_end', '>=', today),
('date_end', '=', False),
('is_auto_renew', '=', True),
('is_auto_renew', '=', False),
('termination_notice_date', '>', today),
]
if state == 'suspended':
if state == 'to-renew':
return [
"&",
"&",
"&",
('is_canceled', '=', False),
('date_end', '<', today),
"|",
"&",
('manual_renew_needed', '=', True),
('successor_contract_line_id', '=', False),
('is_suspended', '=', True),
('is_canceled', '=', False),
('is_auto_renew', '=', True),
]
if state == 'upcoming-close':
return [
@ -176,48 +204,48 @@ class AccountAnalyticInvoiceLine(models.Model):
"&",
"&",
"&",
"&",
('date_start', '<=', today),
('is_auto_renew', '=', False),
('is_suspended', '=', False),
('manual_renew_needed', '=', False),
('is_canceled', '=', False),
'|',
('termination_notice_date', '<', today),
('date_end', '>=', today),
('date_end', '=', False),
]
if state == 'closed':
return [
"&",
"&",
"&",
('is_canceled', '=', False),
('date_end', '<', today),
('is_auto_renew', '=', False),
"|",
"&",
('is_suspended', '=', True),
('manual_renew_needed', '=', True),
('successor_contract_line_id', '!=', False),
('is_suspended', '=', False),
('manual_renew_needed', '=', False),
]
if state == 'canceled':
return [('is_canceled', '=', True)]
@api.model
def _search_state(self, operator, value):
if operator == '!=' and not value:
return []
if operator == '=' and not value:
return [('id', '=', False)]
if operator == '=':
return self._get_state_domain(value)
if operator == '!=':
states = [
'upcoming',
'in-progress',
'suspension-planed',
'suspended',
'to-renew',
'upcoming-close',
'closed',
'canceled',
]
if operator == '!=' and not value:
return []
if operator == '=' and not value:
return [('id', '=', False)]
if operator == '=':
return self._get_state_domain(value)
if operator == '!=':
domain = []
for state in states:
if state != value:
@ -225,6 +253,20 @@ class AccountAnalyticInvoiceLine(models.Model):
domain.insert(0, '|')
domain.extend(self._get_state_domain(state))
return domain
if operator == 'in':
domain = []
if not value:
return [('id', '=', False)]
for state in value:
if domain:
domain.insert(0, '|')
domain.extend(self._get_state_domain(state))
return domain
if operator == 'not in':
return self._search_state(
'in', [state for state in states if state not in value]
)
@api.depends(
'date_start',
@ -583,7 +625,7 @@ class AccountAnalyticInvoiceLine(models.Model):
rec.date_start = new_date_start
@api.multi
def stop(self, date_end, is_suspended=False, post_message=True):
def stop(self, date_end, manual_renew_needed=False, post_message=True):
"""
Put date_end on contract line
We don't consider contract lines that end's before the new end date
@ -614,12 +656,15 @@ class AccountAnalyticInvoiceLine(models.Model):
{
'date_end': date_end,
'is_auto_renew': False,
"is_suspended": is_suspended,
"manual_renew_needed": manual_renew_needed,
}
)
else:
rec.write(
{'is_auto_renew': False, "is_suspended": is_suspended}
{
'is_auto_renew': False,
"manual_renew_needed": manual_renew_needed,
}
)
return True
@ -756,7 +801,7 @@ class AccountAnalyticInvoiceLine(models.Model):
)
rec.stop(
date_start - relativedelta(days=1),
is_suspended=True,
manual_renew_needed=True,
post_message=False,
)
contract_line |= rec.plan_successor(
@ -778,7 +823,7 @@ class AccountAnalyticInvoiceLine(models.Model):
rec.stop(
date_start - relativedelta(days=1),
is_suspended=True,
manual_renew_needed=True,
post_message=False,
)
contract_line |= rec.plan_successor(
@ -954,7 +999,7 @@ class AccountAnalyticInvoiceLine(models.Model):
res = self.env['account.analytic.invoice.line']
for rec in self:
is_auto_renew = rec.is_auto_renew
rec.stop(rec.date_end, post_message=False)
rec.is_auto_renew = False
date_start, date_end = rec._get_renewal_dates()
new_line = rec.plan_successor(
date_start, date_end, is_auto_renew, post_message=False
@ -982,21 +1027,13 @@ class AccountAnalyticInvoiceLine(models.Model):
('is_auto_renew', '=', True),
('is_canceled', '=', False),
('contract_id.recurring_invoices', '=', True),
('termination_notice_date', '<=', fields.Date.context_today(self)),
]
@api.model
def cron_renew_contract_line(self):
domain = self._contract_line_to_renew_domain()
to_renew = self
for contract_line in self.search(domain):
date_ref = fields.Date.context_today(
self
) + self.get_relative_delta(
contract_line.termination_notice_rule_type,
contract_line.termination_notice_interval,
)
if contract_line.date_end <= date_ref:
to_renew |= contract_line
to_renew = self.search(domain)
to_renew.renew()
@api.model

614
contract/tests/test_contract.py

@ -17,17 +17,19 @@ class TestContractBase(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestContractBase, cls).setUpClass()
cls.today = fields.Date.today()
cls.partner = cls.env.ref('base.res_partner_2')
cls.product = cls.env.ref('product.product_product_2')
cls.product.taxes_id += cls.env['account.tax'].search(
cls.product_1 = cls.env.ref('product.product_product_1')
cls.product_2 = cls.env.ref('product.product_product_2')
cls.product_1.taxes_id += cls.env['account.tax'].search(
[('type_tax_use', '=', 'sale')], limit=1
)
cls.product.description_sale = 'Test description sale'
cls.product_1.description_sale = 'Test description sale'
cls.line_template_vals = {
'product_id': cls.product.id,
'product_id': cls.product_1.id,
'name': 'Services from #START# to #END#',
'quantity': 1,
'uom_id': cls.product.uom_id.id,
'uom_id': cls.product_1.uom_id.id,
'price_unit': 100,
'discount': 50,
'recurring_rule_type': 'yearly',
@ -44,7 +46,7 @@ class TestContractBase(common.SavepointCase):
cls.env['product.pricelist.item'].create(
{
'pricelist_id': cls.partner.property_product_pricelist.id,
'product_id': cls.product.id,
'product_id': cls.product_1.id,
'compute_price': 'formula',
'base': 'list_price',
}
@ -69,10 +71,10 @@ class TestContractBase(common.SavepointCase):
0,
0,
{
'product_id': cls.product.id,
'product_id': cls.product_1.id,
'name': 'Services from #START# to #END#',
'quantity': 1,
'uom_id': cls.product.uom_id.id,
'uom_id': cls.product_1.uom_id.id,
'price_unit': 100,
'discount': 50,
'recurring_rule_type': 'monthly',
@ -86,10 +88,10 @@ class TestContractBase(common.SavepointCase):
)
cls.line_vals = {
'contract_id': cls.contract.id,
'product_id': cls.product.id,
'product_id': cls.product_1.id,
'name': 'Services from #START# to #END#',
'quantity': 1,
'uom_id': cls.product.uom_id.id,
'uom_id': cls.product_1.uom_id.id,
'price_unit': 100,
'discount': 50,
'recurring_rule_type': 'monthly',
@ -121,7 +123,7 @@ class TestContract(TestContractBase):
def test_automatic_price(self):
self.acct_line.automatic_price = True
self.product.list_price = 1100
self.product_1.list_price = 1100
self.assertEqual(self.acct_line.price_unit, 1100)
# Try to write other price
self.acct_line.price_unit = 10
@ -395,6 +397,8 @@ class TestContract(TestContractBase):
def test_onchange_contract_template_id(self):
"""It should change the contract values to match the template."""
self.contract.contract_template_id = False
self.contract._onchange_contract_template_id()
self.contract.contract_template_id = self.template
self.contract._onchange_contract_template_id()
res = {
@ -403,10 +407,10 @@ class TestContract(TestContractBase):
0,
0,
{
'product_id': self.product.id,
'product_id': self.product_1.id,
'name': 'Services from #START# to #END#',
'quantity': 1,
'uom_id': self.product.uom_id.id,
'uom_id': self.product_1.uom_id.id,
'price_unit': 100,
'discount': 50,
'recurring_rule_type': 'yearly',
@ -450,6 +454,15 @@ class TestContract(TestContractBase):
self.assertEqual(
self.contract.journal_id.company_id, self.contract.company_id
)
self.contract.type = 'purchase'
self.contract._onchange_contract_type()
self.assertFalse(
any(
self.contract.recurring_invoice_line_ids.mapped(
'automatic_price'
)
)
)
def test_contract_onchange_product_id_domain_blank(self):
"""It should return a blank UoM domain when no product."""
@ -463,7 +476,7 @@ class TestContract(TestContractBase):
res = line._onchange_product_id()
self.assertEqual(
res['domain']['uom_id'][0],
('category_id', '=', self.product.uom_id.category_id.id),
('category_id', '=', self.product_1.uom_id.category_id.id),
)
def test_contract_onchange_product_id_uom(self):
@ -502,9 +515,9 @@ class TestContract(TestContractBase):
"""It should create one invoice with same start and end date."""
self.acct_line.write(
{
'date_start': fields.Date.today(),
'date_end': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_start': self.today,
'date_end': self.today,
'recurring_next_date': self.today,
}
)
self.contract._compute_recurring_next_date()
@ -630,33 +643,40 @@ class TestContract(TestContractBase):
"""It should put end to the contract line"""
self.acct_line.write(
{
'date_start': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_end': fields.Date.today() + relativedelta(months=7),
'date_start': self.today - relativedelta(months=7),
'recurring_next_date': self.today - relativedelta(months=7),
'date_end': self.today - relativedelta(months=5),
'is_auto_renew': False,
}
)
with self.assertRaises(ValidationError):
self.acct_line.stop(self.today)
self.acct_line.write(
{
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=7),
'is_auto_renew': True,
}
)
self.acct_line.stop(fields.Date.today() + relativedelta(months=5))
self.acct_line.stop(self.today + relativedelta(months=5))
self.assertEqual(
self.acct_line.date_end,
fields.Date.today() + relativedelta(months=5),
self.acct_line.date_end, self.today + relativedelta(months=5)
)
def test_stop_upcoming_contract_line(self):
"""It should put end to the contract line"""
self.acct_line.write(
{
'date_start': fields.Date.today() + relativedelta(months=3),
'recurring_next_date': fields.Date.today()
+ relativedelta(months=3),
'date_end': fields.Date.today() + relativedelta(months=7),
'date_start': self.today + relativedelta(months=3),
'recurring_next_date': self.today + relativedelta(months=3),
'date_end': self.today + relativedelta(months=7),
'is_auto_renew': True,
}
)
self.acct_line.stop(fields.Date.today())
self.acct_line.stop(self.today)
self.assertEqual(
self.acct_line.date_end,
fields.Date.today() + relativedelta(months=7),
self.acct_line.date_end, self.today + relativedelta(months=7)
)
self.assertTrue(self.acct_line.is_canceled)
@ -664,56 +684,74 @@ class TestContract(TestContractBase):
"""Past contract line are ignored on stop"""
self.acct_line.write(
{
'date_end': fields.Date.today() + relativedelta(months=5),
'date_end': self.today + relativedelta(months=5),
'is_auto_renew': True,
}
)
self.acct_line.stop(fields.Date.today() + relativedelta(months=7))
self.acct_line.stop(self.today + relativedelta(months=7))
self.assertEqual(
self.acct_line.date_end,
fields.Date.today() + relativedelta(months=5),
self.acct_line.date_end, self.today + relativedelta(months=5)
)
def test_stop_contract_line_without_date_end(self):
"""Past contract line are ignored on stop"""
self.acct_line.write({'date_end': False, 'is_auto_renew': False})
self.acct_line.stop(fields.Date.today() + relativedelta(months=7))
self.acct_line.stop(self.today + relativedelta(months=7))
self.assertEqual(
self.acct_line.date_end,
fields.Date.today() + relativedelta(months=7),
self.acct_line.date_end, self.today + relativedelta(months=7)
)
def test_stop_plan_successor_wizard(self):
def test_stop_wizard(self):
self.acct_line.write(
{
'date_start': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_end': fields.Date.today() + relativedelta(months=5),
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=5),
'is_auto_renew': True,
}
)
wizard = self.env['account.analytic.invoice.line.wizard'].create(
{
'date_end': fields.Date.today() + relativedelta(months=7),
'date_end': self.today + relativedelta(months=3),
'contract_line_id': self.acct_line.id,
}
)
wizard.stop()
self.assertEqual(
self.acct_line.date_end,
fields.Date.today() + relativedelta(months=7),
self.acct_line.date_end, self.today + relativedelta(months=3)
)
self.assertFalse(self.acct_line.is_auto_renew)
def test_stop_plan_successor_contract_line_0(self):
successor_contract_line = self.acct_line.copy(
{
'date_start': self.today + relativedelta(months=5),
'recurring_next_date': self.today + relativedelta(months=5),
}
)
self.acct_line.write(
{
'successor_contract_line_id': successor_contract_line.id,
'is_auto_renew': False,
'date_end': self.today,
}
)
suspension_start = self.today + relativedelta(months=5)
suspension_end = self.today + relativedelta(months=6)
with self.assertRaises(ValidationError):
self.acct_line.stop_plan_successor(
suspension_start, suspension_end, True
)
def test_stop_plan_successor_contract_line_1(self):
"""
* contract line end's before the suspension period:
-> apply stop
"""
suspension_start = fields.Date.today() + relativedelta(months=5)
suspension_end = fields.Date.today() + relativedelta(months=6)
start_date = fields.Date.today()
end_date = fields.Date.today() + relativedelta(months=4)
suspension_start = self.today + relativedelta(months=5)
suspension_end = self.today + relativedelta(months=6)
start_date = self.today
end_date = self.today + relativedelta(months=4)
self.acct_line.write(
{
'date_start': start_date,
@ -739,10 +777,10 @@ class TestContract(TestContractBase):
- date_end: suspension.date_end + (contract_line.date_end
- suspension.date_start)
"""
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)
suspension_start = self.today + relativedelta(months=3)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today
end_date = self.today + relativedelta(months=4)
self.acct_line.write(
{
'date_start': start_date,
@ -769,7 +807,7 @@ class TestContract(TestContractBase):
new_line.date_start, suspension_end + relativedelta(days=1)
)
self.assertEqual(new_line.date_end, new_date_end)
self.assertTrue(self.acct_line.is_suspended)
self.assertTrue(self.acct_line.manual_renew_needed)
def test_stop_plan_successor_contract_line_3(self):
"""
@ -780,10 +818,10 @@ class TestContract(TestContractBase):
- date_end: suspension.date_end + (suspension.date_end
- suspension.date_start)
"""
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=6)
suspension_start = self.today + relativedelta(months=3)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today
end_date = self.today + relativedelta(months=6)
self.acct_line.write(
{
'date_start': start_date,
@ -810,7 +848,7 @@ class TestContract(TestContractBase):
new_line.date_start, suspension_end + relativedelta(days=1)
)
self.assertEqual(new_line.date_end, new_date_end)
self.assertTrue(self.acct_line.is_suspended)
self.assertTrue(self.acct_line.manual_renew_needed)
def test_stop_plan_successor_contract_line_3_without_end_date(self):
"""
@ -821,9 +859,9 @@ class TestContract(TestContractBase):
- date_end: suspension.date_end + (suspension.date_end
- suspension.date_start)
"""
suspension_start = fields.Date.today() + relativedelta(months=3)
suspension_end = fields.Date.today() + relativedelta(months=5)
start_date = fields.Date.today()
suspension_start = self.today + relativedelta(months=3)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today
end_date = False
self.acct_line.write(
{
@ -847,7 +885,7 @@ class TestContract(TestContractBase):
new_line.date_start, suspension_end + relativedelta(days=1)
)
self.assertFalse(new_line.date_end)
self.assertTrue(self.acct_line.is_suspended)
self.assertTrue(self.acct_line.manual_renew_needed)
def test_stop_plan_successor_contract_line_4(self):
"""
@ -855,10 +893,10 @@ class TestContract(TestContractBase):
-> apply delay
- delay: suspension.date_end - contract_line.end_date
"""
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=5)
start_date = fields.Date.today() + relativedelta(months=3)
end_date = fields.Date.today() + relativedelta(months=4)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today + relativedelta(months=3)
end_date = self.today + relativedelta(months=4)
self.acct_line.write(
{
'date_start': start_date,
@ -888,10 +926,10 @@ class TestContract(TestContractBase):
-> apply delay
- delay: suspension.date_end - contract_line.date_start
"""
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=5)
start_date = fields.Date.today() + relativedelta(months=3)
end_date = fields.Date.today() + relativedelta(months=6)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today + relativedelta(months=3)
end_date = self.today + relativedelta(months=6)
self.acct_line.write(
{
'date_start': start_date,
@ -921,9 +959,9 @@ class TestContract(TestContractBase):
-> apply delay
- delay: suspension.date_end - contract_line.date_start
"""
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=5)
start_date = fields.Date.today() + relativedelta(months=3)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today + relativedelta(months=3)
end_date = False
self.acct_line.write(
{
@ -952,10 +990,10 @@ class TestContract(TestContractBase):
-> apply delay
- delay: suspension.date_end - suspension.start_date
"""
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=3)
start_date = fields.Date.today() + relativedelta(months=4)
end_date = fields.Date.today() + relativedelta(months=6)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=3)
start_date = self.today + relativedelta(months=4)
end_date = self.today + relativedelta(months=6)
self.acct_line.write(
{
'date_start': start_date,
@ -987,9 +1025,9 @@ class TestContract(TestContractBase):
-> apply delay
- delay: suspension.date_end - suspension.start_date
"""
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=3)
start_date = fields.Date.today() + relativedelta(months=4)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=3)
start_date = self.today + relativedelta(months=4)
end_date = False
self.acct_line.write(
{
@ -1015,10 +1053,10 @@ class TestContract(TestContractBase):
self.assertFalse(new_line)
def test_stop_plan_successor_wizard(self):
suspension_start = fields.Date.today() + relativedelta(months=2)
suspension_end = fields.Date.today() + relativedelta(months=3)
start_date = fields.Date.today() + relativedelta(months=4)
end_date = fields.Date.today() + relativedelta(months=6)
suspension_start = self.today + relativedelta(months=2)
suspension_end = self.today + relativedelta(months=3)
start_date = self.today + relativedelta(months=4)
end_date = self.today + relativedelta(months=6)
self.acct_line.write(
{
'date_start': start_date,
@ -1053,15 +1091,15 @@ class TestContract(TestContractBase):
def test_plan_successor_contract_line(self):
self.acct_line.write(
{
'date_start': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_end': fields.Date.today() + relativedelta(months=3),
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=3),
'is_auto_renew': False,
}
)
self.acct_line.plan_successor(
fields.Date.today() + relativedelta(months=5),
fields.Date.today() + relativedelta(months=7),
self.today + relativedelta(months=5),
self.today + relativedelta(months=7),
True,
)
new_line = self.env['account.analytic.invoice.line'].search(
@ -1071,49 +1109,47 @@ class TestContract(TestContractBase):
self.assertTrue(new_line.is_auto_renew)
self.assertTrue(new_line, "should create a new contract line")
self.assertEqual(
new_line.date_start, fields.Date.today() + relativedelta(months=5)
new_line.date_start, self.today + relativedelta(months=5)
)
self.assertEqual(
new_line.date_end, fields.Date.today() + relativedelta(months=7)
new_line.date_end, self.today + relativedelta(months=7)
)
def test_overlap(self):
self.acct_line.write(
{
'date_start': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_end': fields.Date.today() + relativedelta(months=3),
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=3),
'is_auto_renew': False,
}
)
self.acct_line.plan_successor(
fields.Date.today() + relativedelta(months=5),
fields.Date.today() + relativedelta(months=7),
self.today + relativedelta(months=5),
self.today + relativedelta(months=7),
True,
)
new_line = self.env['account.analytic.invoice.line'].search(
[('predecessor_contract_line_id', '=', self.acct_line.id)]
)
with self.assertRaises(ValidationError):
new_line.date_start = fields.Date.today() + relativedelta(months=2)
new_line.date_start = self.today + relativedelta(months=2)
with self.assertRaises(ValidationError):
self.acct_line.date_end = fields.Date.today() + relativedelta(
months=6
)
self.acct_line.date_end = self.today + relativedelta(months=6)
def test_plan_successor_wizard(self):
self.acct_line.write(
{
'date_start': fields.Date.today(),
'recurring_next_date': fields.Date.today(),
'date_end': fields.Date.today() + relativedelta(months=2),
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=2),
'is_auto_renew': False,
}
)
wizard = self.env['account.analytic.invoice.line.wizard'].create(
{
'date_start': fields.Date.today() + relativedelta(months=3),
'date_end': fields.Date.today() + relativedelta(months=5),
'date_start': self.today + relativedelta(months=3),
'date_end': self.today + relativedelta(months=5),
'is_auto_renew': True,
'contract_line_id': self.acct_line.id,
}
@ -1126,23 +1162,35 @@ class TestContract(TestContractBase):
self.assertTrue(new_line.is_auto_renew)
self.assertTrue(new_line, "should create a new contract line")
self.assertEqual(
new_line.date_start, fields.Date.today() + relativedelta(months=3)
new_line.date_start, self.today + relativedelta(months=3)
)
self.assertEqual(
new_line.date_end, fields.Date.today() + relativedelta(months=5)
new_line.date_end, self.today + relativedelta(months=5)
)
def test_cancel(self):
self.acct_line.cancel()
self.assertTrue(self.acct_line.is_canceled)
self.acct_line.uncancel(fields.Date.today())
self.acct_line.uncancel(self.today)
self.assertFalse(self.acct_line.is_canceled)
def test_uncancel_wizard(self):
self.acct_line.cancel()
self.assertTrue(self.acct_line.is_canceled)
wizard = self.env['account.analytic.invoice.line.wizard'].create(
{
'recurring_next_date': self.today,
'contract_line_id': self.acct_line.id,
}
)
wizard.uncancel()
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)
suspension_start = self.today + relativedelta(months=3)
suspension_end = self.today + relativedelta(months=5)
start_date = self.today
end_date = self.today + relativedelta(months=4)
self.acct_line.write(
{
'date_start': start_date,
@ -1173,10 +1221,10 @@ class TestContract(TestContractBase):
)
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)
suspension_start = self.today + relativedelta(months=6)
suspension_end = self.today + relativedelta(months=7)
start_date = self.today
end_date = self.today + relativedelta(months=8)
self.acct_line.write(
{
'date_start': start_date,
@ -1191,8 +1239,8 @@ class TestContract(TestContractBase):
[('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)
suspension_start = self.today + relativedelta(months=4)
suspension_end = self.today + relativedelta(months=5)
self.acct_line.stop_plan_successor(
suspension_start, suspension_end, True
)
@ -1217,23 +1265,18 @@ class TestContract(TestContractBase):
)
def test_search_contract_line_to_renew(self):
self.acct_line.write(
{'date_end': fields.Date.today(), 'is_auto_renew': True}
)
self.acct_line.write({'date_end': self.today, 'is_auto_renew': True})
line_1 = self.acct_line.copy(
{'date_end': fields.Date.today() + relativedelta(months=1)}
{'date_end': self.today + relativedelta(months=1)}
)
line_2 = self.acct_line.copy(
{'date_end': fields.Date.today() - relativedelta(months=1)}
{'date_end': self.today - relativedelta(months=1)}
)
line_3 = self.acct_line.copy(
{'date_end': fields.Date.today() - relativedelta(months=2)}
{'date_end': self.today - relativedelta(months=2)}
)
self.acct_line.copy(
{
'date_end': fields.Date.today() + relativedelta(months=2),
'is_auto_renew': False,
}
line_4 = self.acct_line.copy(
{'date_end': self.today + relativedelta(months=2)}
)
to_renew = self.acct_line.search(
self.acct_line._contract_line_to_renew_domain()
@ -1241,9 +1284,15 @@ class TestContract(TestContractBase):
self.assertEqual(
set(to_renew), set((self.acct_line, line_1, line_2, line_3))
)
self.acct_line.cron_renew_contract_line()
self.assertTrue(self.acct_line.successor_contract_line_id)
self.assertTrue(line_1.successor_contract_line_id)
self.assertTrue(line_2.successor_contract_line_id)
self.assertTrue(line_3.successor_contract_line_id)
self.assertFalse(line_4.successor_contract_line_id)
def test_renew(self):
date_start = fields.Date.today() - relativedelta(months=9)
date_start = self.today - relativedelta(months=9)
date_end = (
date_start + relativedelta(months=12) - relativedelta(days=1)
)
@ -1252,7 +1301,7 @@ class TestContract(TestContractBase):
'is_auto_renew': True,
'date_start': date_start,
'recurring_next_date': date_start,
'date_end': fields.Date.today(),
'date_end': self.today,
}
)
self.acct_line._onchange_is_auto_renew()
@ -1301,6 +1350,7 @@ class TestContract(TestContractBase):
first, last = self.acct_line._get_invoiced_period()
self.assertEqual(first, to_date('2018-03-01'))
self.assertEqual(last, to_date('2018-03-15'))
self.acct_line.manual_renew_needed = True
def test_get_invoiced_period_monthly_pre_paid_2(self):
self.acct_line.date_start = '2018-01-05'
@ -1407,3 +1457,305 @@ class TestContract(TestContractBase):
def test_unlink(self):
with self.assertRaises(ValidationError):
self.acct_line.unlink()
def test_contract_line_state(self):
lines = self.env['account.analytic.invoice.line']
# upcoming
lines |= self.acct_line.copy(
{
'date_start': self.today + relativedelta(months=3),
'recurring_next_date': self.today + relativedelta(months=3),
'date_end': self.today + relativedelta(months=5),
}
)
# in-progress
lines |= self.acct_line.copy(
{
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=5),
}
)
# in-progress
lines |= self.acct_line.copy(
{
'date_start': self.today,
'recurring_next_date': self.today,
'date_end': self.today + relativedelta(months=5),
'manual_renew_needed': True,
}
)
# to-renew
lines |= self.acct_line.copy(
{
'date_start': self.today - relativedelta(months=5),
'recurring_next_date': self.today - relativedelta(months=5),
'date_end': self.today - relativedelta(months=2),
'manual_renew_needed': True,
}
)
# upcoming-close
lines |= self.acct_line.copy(
{
'date_start': self.today - relativedelta(months=5),
'recurring_next_date': self.today - relativedelta(months=5),
'date_end': self.today + relativedelta(days=20),
'is_auto_renew': False,
}
)
# closed
lines |= self.acct_line.copy(
{
'date_start': self.today - relativedelta(months=5),
'recurring_next_date': self.today - relativedelta(months=5),
'date_end': self.today - relativedelta(months=2),
'is_auto_renew': False,
}
)
# canceled
lines |= self.acct_line.copy(
{
'date_start': self.today - relativedelta(months=5),
'recurring_next_date': self.today - relativedelta(months=5),
'date_end': self.today - relativedelta(months=2),
'is_canceled': True,
}
)
states = [
'upcoming',
'in-progress',
'to-renew',
'upcoming-close',
'closed',
'canceled',
]
self.assertEqual(set(lines.mapped('state')), set(states))
for state in states:
lines = self.env['account.analytic.invoice.line'].search(
[('state', '=', state)]
)
self.assertEqual(len(set(lines.mapped('state'))), 1, state)
self.assertEqual(lines.mapped('state')[0], state, state)
for state in states:
lines = self.env['account.analytic.invoice.line'].search(
[('state', '!=', state)]
)
self.assertFalse(state in lines.mapped('state'))
lines = self.env['account.analytic.invoice.line'].search(
[('state', 'in', states)]
)
self.assertEqual(set(lines.mapped('state')), set(states))
lines = self.env['account.analytic.invoice.line'].search(
[('state', 'in', [])]
)
self.assertFalse(lines.mapped('state'))
with self.assertRaises(TypeError):
self.env['account.analytic.invoice.line'].search(
[('state', 'in', 'upcoming')]
)
lines = self.env['account.analytic.invoice.line'].search(
[('state', 'not in', [])]
)
self.assertEqual(set(lines.mapped('state')), set(states))
lines = self.env['account.analytic.invoice.line'].search(
[('state', 'not in', states)]
)
self.assertFalse(lines.mapped('state'))
lines = self.env['account.analytic.invoice.line'].search(
[('state', 'not in', ['upcoming', 'in-progress'])]
)
self.assertEqual(
set(lines.mapped('state')),
set(['to-renew', 'upcoming-close', 'closed', 'canceled']),
)
def test_check_auto_renew_contract_line_with_successor(self):
"""
A contract line with a successor can't be set to auto-renew
"""
successor_contract_line = self.acct_line.copy()
with self.assertRaises(ValidationError):
self.acct_line.write(
{
'is_auto_renew': True,
'successor_contract_line_id': successor_contract_line.id,
}
)
def test_check_no_date_end_contract_line_with_successor(self):
"""
A contract line with a successor must have a end date
"""
successor_contract_line = self.acct_line.copy()
with self.assertRaises(ValidationError):
self.acct_line.write(
{
'date_end': False,
'successor_contract_line_id': successor_contract_line.id,
}
)
def test_check_last_date_invoiced_1(self):
"""
start end can't be before the date of last invoice
"""
with self.assertRaises(ValidationError):
self.acct_line.write(
{
'last_date_invoiced': self.acct_line.date_start
- relativedelta(days=1)
}
)
def test_check_last_date_invoiced_2(self):
"""
start date can't be after the date of last invoice
"""
self.acct_line.write({'date_end': self.today})
with self.assertRaises(ValidationError):
self.acct_line.write(
{
'last_date_invoiced': self.acct_line.date_end
+ relativedelta(days=1)
}
)
def test_init_last_date_invoiced(self):
self.acct_line.write(
{'date_start': '2019-01-01', 'recurring_next_date': '2019-03-01'}
)
line_monthlylastday = self.acct_line.copy(
{
'recurring_rule_type': 'monthlylastday',
'recurring_next_date': '2019-03-31',
}
)
line_prepaid = self.acct_line.copy(
{
'recurring_invoicing_type': 'pre-paid',
'recurring_rule_type': 'monthly',
}
)
line_postpaid = self.acct_line.copy(
{
'recurring_invoicing_type': 'post-paid',
'recurring_rule_type': 'monthly',
}
)
lines = line_monthlylastday | line_prepaid | line_postpaid
lines.write({'last_date_invoiced': False})
self.assertFalse(any(lines.mapped('last_date_invoiced')))
lines._init_last_date_invoiced()
self.assertEqual(
line_monthlylastday.last_date_invoiced, to_date("2019-02-28")
)
self.assertEqual(
line_prepaid.last_date_invoiced, to_date("2019-02-28")
)
self.assertEqual(
line_postpaid.last_date_invoiced, to_date("2019-01-31")
)
def test_delay_invoiced_contract_line(self):
self.acct_line.write(
{
'last_date_invoiced': self.acct_line.date_start
+ relativedelta(days=1)
}
)
with self.assertRaises(ValidationError):
self.acct_line._delay(relativedelta(months=1))
def test_cancel_invoiced_contract_line(self):
self.acct_line.write(
{
'last_date_invoiced': self.acct_line.date_start
+ relativedelta(days=1)
}
)
with self.assertRaises(ValidationError):
self.acct_line.cancel()
def test_action_uncancel(self):
action = self.acct_line.action_uncancel()
self.assertEqual(
action['context']['default_contract_line_id'], self.acct_line.id
)
def test_action_plan_successor(self):
action = self.acct_line.action_plan_successor()
self.assertEqual(
action['context']['default_contract_line_id'], self.acct_line.id
)
def test_action_stop(self):
action = self.acct_line.action_stop()
self.assertEqual(
action['context']['default_contract_line_id'], self.acct_line.id
)
def test_action_stop_plan_successor(self):
action = self.acct_line.action_stop_plan_successor()
self.assertEqual(
action['context']['default_contract_line_id'], self.acct_line.id
)
def test_purchase_fields_view_get(self):
purchase_tree_view = self.env.ref(
'contract.account_analytic_invoice_line_purchase_view_tree'
)
purchase_form_view = self.env.ref(
'contract.account_analytic_invoice_line_purchase_view_form'
)
view = self.acct_line.with_context(
default_contract_type='purchase'
).fields_view_get(view_type='tree')
self.assertEqual(view['view_id'], purchase_tree_view.id)
view = self.acct_line.with_context(
default_contract_type='purchase'
).fields_view_get(view_type='form')
self.assertEqual(view['view_id'], purchase_form_view.id)
def test_sale_fields_view_get(self):
sale_form_view = self.env.ref(
'contract.account_analytic_invoice_line_sale_view_form'
)
view = self.acct_line.with_context(
default_contract_type='sale'
).fields_view_get(view_type='form')
self.assertEqual(view['view_id'], sale_form_view.id)
def test_contract_count_invoice(self):
self.contract.recurring_create_invoice()
self.contract.recurring_create_invoice()
self.contract.recurring_create_invoice()
self.contract._compute_invoice_count()
self.assertEqual(self.contract.invoice_count, 3)
def test_contract_count_invoice(self):
invoices = self.env['account.invoice']
invoices |= self.contract.recurring_create_invoice()
invoices |= self.contract.recurring_create_invoice()
invoices |= self.contract.recurring_create_invoice()
action = self.contract.action_show_invoices()
self.assertEqual(set(action['domain'][0][2]), set(invoices.ids))
def test_compute_create_invoice_visibility(self):
self.assertTrue(self.contract.create_invoice_visibility)
self.acct_line.write(
{
'date_start': '2018-01-01',
'date_end': '2018-12-31',
'last_date_invoiced': '2018-12-31',
'recurring_next_date': False,
}
)
self.assertFalse(self.acct_line.create_invoice_visibility)
self.assertFalse(self.contract.create_invoice_visibility)
def test_invoice_contract_without_lines(self):
self.contract.recurring_invoice_line_ids.cancel()
self.contract.recurring_invoice_line_ids.unlink()
self.assertFalse(self.contract.recurring_create_invoice())

8
contract/views/contract_line.xml

@ -26,6 +26,14 @@
<field name="date_end"
attrs="{'required': [('is_auto_renew', '=', True)]}"/>
</group>
<group groups="base.group_no_one">
<field name="last_date_invoiced" readonly="True"/>
<field name="termination_notice_date" readonly="True"/>
</group>
<group groups="base.group_no_one">
<field name="manual_renew_needed"/>
</group>
<group>
<field name="predecessor_contract_line_id"/>
</group>

10
contract/wizards/contract_line_wizard.py

@ -13,7 +13,13 @@ class AccountAnalyticInvoiceLineWizard(models.TransientModel):
date_end = fields.Date(string='Date End')
recurring_next_date = fields.Date(string='Next Invoice Date')
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
is_suspended = fields.Boolean(string="Is a suspension", default=False)
manual_renew_needed = fields.Boolean(
string="Manual renew needed",
default=False,
help="This flag is used to make a difference between a definitive stop"
"and temporary one for which a user is not able to plan a"
"successor in advance",
)
contract_line_id = fields.Many2one(
comodel_name="account.analytic.invoice.line",
string="Contract Line",
@ -25,7 +31,7 @@ class AccountAnalyticInvoiceLineWizard(models.TransientModel):
def stop(self):
for wizard in self:
wizard.contract_line_id.stop(
wizard.date_end, is_suspended=wizard.is_suspended
wizard.date_end, manual_renew_needed=wizard.manual_renew_needed
)
return True

2
contract/wizards/contract_line_wizard.xml

@ -12,7 +12,7 @@
<group>
<field name="contract_line_id" invisible="True"/>
<field string="Stop Date" name="date_end" required="True"/>
<field string="Is a suspension" name="is_suspended"/>
<field string="Is a suspension" name="manual_renew_needed"/>
</group>
<footer>
<button name="stop"

Loading…
Cancel
Save