mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[ADD] - Add renewal process with termination notice
This commit is contained in:
@@ -16,3 +16,12 @@ class AccountAbstractAnalyticContractLine(models.AbstractModel):
|
|||||||
)
|
)
|
||||||
self.recurring_interval = self.product_id.recurring_interval
|
self.recurring_interval = self.product_id.recurring_interval
|
||||||
self.date_start = fields.Date.today()
|
self.date_start = fields.Date.today()
|
||||||
|
self.is_auto_renew = self.product_id.is_auto_renew
|
||||||
|
self.auto_renew_interval = self.product_id.auto_renew_interval
|
||||||
|
self.auto_renew_rule_type = self.product_id.auto_renew_rule_type
|
||||||
|
self.termination_notice_interval = (
|
||||||
|
self.product_id.termination_notice_interval
|
||||||
|
)
|
||||||
|
self.termination_notice_rule_type = (
|
||||||
|
self.product_id.termination_notice_rule_type
|
||||||
|
)
|
||||||
|
|||||||
@@ -37,6 +37,26 @@ class ProductTemplate(models.Model):
|
|||||||
string='Repeat Every',
|
string='Repeat Every',
|
||||||
help="Repeat every (Days/Week/Month/Year)",
|
help="Repeat every (Days/Week/Month/Year)",
|
||||||
)
|
)
|
||||||
|
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||||
|
auto_renew_interval = fields.Integer(
|
||||||
|
default=1,
|
||||||
|
string='Renew Every',
|
||||||
|
help="Renew every (Days/Week/Month/Year)",
|
||||||
|
)
|
||||||
|
auto_renew_rule_type = fields.Selection(
|
||||||
|
[('monthly', 'Month(s)'), ('yearly', 'Year(s)')],
|
||||||
|
default='yearly',
|
||||||
|
string='Renewal type',
|
||||||
|
help="Specify Interval for automatic renewal.",
|
||||||
|
)
|
||||||
|
termination_notice_interval = fields.Integer(
|
||||||
|
default=1, string='Termination Notice Before'
|
||||||
|
)
|
||||||
|
termination_notice_rule_type = fields.Selection(
|
||||||
|
[('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')],
|
||||||
|
default='monthly',
|
||||||
|
string='Termination Notice type',
|
||||||
|
)
|
||||||
|
|
||||||
@api.onchange('is_contract')
|
@api.onchange('is_contract')
|
||||||
def _change_is_contract(self):
|
def _change_is_contract(self):
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ class SaleOrderLine(models.Model):
|
|||||||
help="Repeat every (Days/Week/Month/Year)",
|
help="Repeat every (Days/Week/Month/Year)",
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
date_start = fields.Date(string='Date Start')
|
date_start = fields.Date(string='Date Start',)
|
||||||
date_end = fields.Date(string='Date End', index=True)
|
date_end = fields.Date(string='Date End',)
|
||||||
|
|
||||||
contract_line_id = fields.Many2one(
|
contract_line_id = fields.Many2one(
|
||||||
comodel_name="account.analytic.invoice.line",
|
comodel_name="account.analytic.invoice.line",
|
||||||
@@ -56,6 +56,11 @@ class SaleOrderLine(models.Model):
|
|||||||
required=False,
|
required=False,
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
|
is_auto_renew = fields.Boolean(
|
||||||
|
string="Auto Renew",
|
||||||
|
related="product_id.is_auto_renew",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
@api.onchange('product_id')
|
@api.onchange('product_id')
|
||||||
def onchange_product(self):
|
def onchange_product(self):
|
||||||
@@ -65,7 +70,14 @@ class SaleOrderLine(models.Model):
|
|||||||
self.product_id.recurring_invoicing_type
|
self.product_id.recurring_invoicing_type
|
||||||
)
|
)
|
||||||
self.recurring_interval = self.product_id.recurring_interval
|
self.recurring_interval = self.product_id.recurring_interval
|
||||||
self.date_start = fields.Date.today()
|
self.date_start = self.date_start or fields.Date.today()
|
||||||
|
if self.product_id.is_auto_renew:
|
||||||
|
self.date_end = self.date_start + self.env[
|
||||||
|
'account.analytic.invoice.line'
|
||||||
|
].get_relative_delta(
|
||||||
|
self.product_id.auto_renew_rule_type,
|
||||||
|
self.product_id.auto_renew_interval,
|
||||||
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_contract_line_values(self, contract):
|
def _prepare_contract_line_values(self, contract):
|
||||||
@@ -91,6 +103,13 @@ class SaleOrderLine(models.Model):
|
|||||||
'recurring_interval': self.recurring_interval,
|
'recurring_interval': self.recurring_interval,
|
||||||
'recurring_invoicing_type': self.recurring_invoicing_type,
|
'recurring_invoicing_type': self.recurring_invoicing_type,
|
||||||
'recurring_rule_type': self.recurring_rule_type,
|
'recurring_rule_type': self.recurring_rule_type,
|
||||||
|
'is_auto_renew': self.product_id.is_auto_renew,
|
||||||
|
'auto_renew_interval': self.product_id.auto_renew_interval,
|
||||||
|
'auto_renew_rule_type': self.product_id.auto_renew_rule_type,
|
||||||
|
'termination_notice_interval':
|
||||||
|
self.product_id.termination_notice_interval,
|
||||||
|
'termination_notice_rule_type':
|
||||||
|
self.product_id.termination_notice_rule_type,
|
||||||
'contract_id': contract.id,
|
'contract_id': contract.id,
|
||||||
'sale_order_line_id': self.id,
|
'sale_order_line_id': self.id,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.product1.write(
|
self.product1.write(
|
||||||
{
|
{
|
||||||
'is_contract': True,
|
'is_contract': True,
|
||||||
|
'is_auto_renew': True,
|
||||||
'contract_template_id': self.contract_template1.id,
|
'contract_template_id': self.contract_template1.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -52,6 +53,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.order_line1 = self.sale.order_line.filtered(
|
self.order_line1 = self.sale.order_line.filtered(
|
||||||
lambda l: l.product_id == self.product1
|
lambda l: l.product_id == self.product1
|
||||||
)
|
)
|
||||||
|
self.order_line1.date_start = '2018-01-01'
|
||||||
self.contract = self.env["account.analytic.account"].create(
|
self.contract = self.env["account.analytic.account"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Contract 2",
|
"name": "Test Contract 2",
|
||||||
@@ -88,9 +90,14 @@ class TestSaleOrder(TransactionCase):
|
|||||||
contract"""
|
contract"""
|
||||||
self.assertTrue(self.sale.is_contract)
|
self.assertTrue(self.sale.is_contract)
|
||||||
|
|
||||||
|
def test_action_confirm_auto_renew_without_date_end(self):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
self.sale.action_confirm()
|
||||||
|
|
||||||
def test_action_confirm(self):
|
def test_action_confirm(self):
|
||||||
""" It should create a contract for each contract template used in
|
""" It should create a contract for each contract template used in
|
||||||
order_line """
|
order_line """
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
contracts = self.sale.order_line.mapped('contract_id')
|
contracts = self.sale.order_line.mapped('contract_id')
|
||||||
self.assertEqual(len(contracts), 2)
|
self.assertEqual(len(contracts), 2)
|
||||||
@@ -102,12 +109,14 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_sale_contract_count(self):
|
def test_sale_contract_count(self):
|
||||||
"""It should count contracts as many different contract template used
|
"""It should count contracts as many different contract template used
|
||||||
in order_line"""
|
in order_line"""
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.sale.contract_count, 2)
|
self.assertEqual(self.sale.contract_count, 2)
|
||||||
|
|
||||||
def test_onchange_product(self):
|
def test_onchange_product(self):
|
||||||
""" It should get recurrence invoicing info to the sale line from
|
""" It should get recurrence invoicing info to the sale line from
|
||||||
its product """
|
its product """
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.recurring_rule_type,
|
self.order_line1.recurring_rule_type,
|
||||||
self.product1.recurring_rule_type,
|
self.product1.recurring_rule_type,
|
||||||
@@ -120,6 +129,10 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.order_line1.recurring_invoicing_type,
|
self.order_line1.recurring_invoicing_type,
|
||||||
self.product1.recurring_invoicing_type,
|
self.product1.recurring_invoicing_type,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.order_line1.date_end,
|
||||||
|
Date.to_date('2019-01-01'),
|
||||||
|
)
|
||||||
|
|
||||||
def test_check_contract_sale_partner(self):
|
def test_check_contract_sale_partner(self):
|
||||||
"""Can't link order line to a partner contract different then the
|
"""Can't link order line to a partner contract different then the
|
||||||
@@ -155,6 +168,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_sale_order_line_invoice_status(self):
|
def test_sale_order_line_invoice_status(self):
|
||||||
"""Sale order line for contract product should have nothing to
|
"""Sale order line for contract product should have nothing to
|
||||||
invoice as status"""
|
invoice as status"""
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.order_line1.invoice_status, 'no')
|
self.assertEqual(self.order_line1.invoice_status, 'no')
|
||||||
|
|
||||||
@@ -164,6 +178,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.sale.order_line.filtered(
|
self.sale.order_line.filtered(
|
||||||
lambda line: not line.product_id.is_contract
|
lambda line: not line.product_id.is_contract
|
||||||
).unlink()
|
).unlink()
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.sale.invoice_status, 'no')
|
self.assertEqual(self.sale.invoice_status, 'no')
|
||||||
|
|
||||||
@@ -171,6 +186,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
"""Should not invoice contract product on sale order create invoice"""
|
"""Should not invoice contract product on sale order create invoice"""
|
||||||
self.product2.is_contract = False
|
self.product2.is_contract = False
|
||||||
self.product2.invoice_policy = 'order'
|
self.product2.invoice_policy = 'order'
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.sale.action_invoice_create()
|
self.sale.action_invoice_create()
|
||||||
self.assertEqual(len(self.sale.invoice_ids), 1)
|
self.assertEqual(len(self.sale.invoice_ids), 1)
|
||||||
@@ -181,6 +197,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
|
|
||||||
def test_link_contract_invoice_to_sale_order(self):
|
def test_link_contract_invoice_to_sale_order(self):
|
||||||
"""It should link contract invoice to sale order"""
|
"""It should link contract invoice to sale order"""
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
invoice = self.order_line1.contract_id.recurring_create_invoice()
|
invoice = self.order_line1.contract_id.recurring_create_invoice()
|
||||||
self.assertTrue(invoice in self.sale.invoice_ids)
|
self.assertTrue(invoice in self.sale.invoice_ids)
|
||||||
@@ -189,8 +206,14 @@ class TestSaleOrder(TransactionCase):
|
|||||||
"""Should stop contract line at sale order line start date"""
|
"""Should stop contract line at sale order line start date"""
|
||||||
self.order_line1.contract_id = self.contract
|
self.order_line1.contract_id = self.contract
|
||||||
self.order_line1.contract_line_id = self.contract_line
|
self.order_line1.contract_line_id = self.contract_line
|
||||||
self.order_line1.date_start = "2018-01-01"
|
self.contract_line.date_end = "2019-01-01"
|
||||||
|
self.contract_line.is_auto_renew = "2019-01-01"
|
||||||
|
self.order_line1.date_start = "2018-06-01"
|
||||||
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.contract_line.date_end, Date.to_date("2018-01-01")
|
self.contract_line.date_end, Date.to_date("2018-06-01")
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.contract_line.is_auto_renew
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,24 +19,45 @@
|
|||||||
<label for="is_contract"/>
|
<label for="is_contract"/>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//group[@name='group_standard_price']"
|
<xpath expr="//notebook" position="inside">
|
||||||
position="inside">
|
<page string="Contract"
|
||||||
|
attrs="{'invisible': [('is_contract', '=', False)],}">
|
||||||
|
<group>
|
||||||
<field name="contract_template_id"
|
<field name="contract_template_id"
|
||||||
attrs="{'invisible': [('is_contract', '=', False)],
|
attrs="{'required':[('is_contract', '=', True)]}"/>
|
||||||
'required':[('is_contract', '=', True)]}"/>
|
|
||||||
<field name="recurring_invoicing_type"
|
<field name="recurring_invoicing_type"
|
||||||
attrs="{'invisible': [('is_contract', '=', False)],
|
attrs="{'required':[('is_contract', '=', True)]}"/>
|
||||||
'required':[('is_contract', '=', True)]}"/>
|
|
||||||
|
|
||||||
<label for="recurring_interval" attrs="{'invisible': [('is_contract', '=', False)],
|
<label for="recurring_interval" attrs="{'required':[('is_contract', '=', True)]}"/>
|
||||||
'required':[('is_contract', '=', True)]}"/>
|
<div attrs="{'required':[('is_contract', '=', True)]}">
|
||||||
<div attrs="{'invisible': [('is_contract', '=', False)],
|
|
||||||
'required':[('is_contract', '=', True)]}">
|
|
||||||
<field name="recurring_interval"
|
<field name="recurring_interval"
|
||||||
class="oe_inline" nolabel="1"/>
|
class="oe_inline" nolabel="1"/>
|
||||||
<field name="recurring_rule_type"
|
<field name="recurring_rule_type"
|
||||||
class="oe_inline" nolabel="1"/>
|
class="oe_inline" nolabel="1"/>
|
||||||
</div>
|
</div>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="is_auto_renew"/>
|
||||||
|
<label for="auto_renew_interval" attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
|
'required':[('is_contract', '=', True)]}"/>
|
||||||
|
<div attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
|
'required':[('is_auto_renew', '=', True)]}">
|
||||||
|
<field name="auto_renew_interval"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
<field name="auto_renew_rule_type"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<label for="termination_notice_interval" attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
|
'required':[('is_contract', '=', True)]}"/>
|
||||||
|
<div attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
|
'required':[('is_auto_renew', '=', True)]}">
|
||||||
|
<field name="termination_notice_interval"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
<field name="termination_notice_rule_type"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
<field name="contract_line_id"
|
<field name="contract_line_id"
|
||||||
attrs="{'invisible': [('is_contract', '=', False)]}"
|
attrs="{'invisible': [('is_contract', '=', False)]}"
|
||||||
domain="[('contract_id','=',contract_id)]"/>
|
domain="[('contract_id','=',contract_id)]"/>
|
||||||
|
<field name="is_auto_renew"
|
||||||
|
invisible="1"/>
|
||||||
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/form//field[@name='tax_id']/parent::group"
|
<xpath expr="//field[@name='order_line']/form//field[@name='tax_id']/parent::group"
|
||||||
@@ -58,10 +60,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="date_start"/>
|
<field name="date_start" attrs="{'required': [('is_contract', '=', True)]}"/>
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="date_end"/>
|
<field name="date_end" attrs="{'required': [('is_auto_renew', '=', True)]}"/>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/tree//field[@name='price_total']"
|
<xpath expr="//field[@name='order_line']/tree//field[@name='price_total']"
|
||||||
|
|||||||
Reference in New Issue
Block a user