mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[IMP] - Simplify sale order line creation for contract product
This commit is contained in:
@@ -7,7 +7,9 @@
|
|||||||
'version': '12.0.1.0.0',
|
'version': '12.0.1.0.0',
|
||||||
'category': 'Contract Management',
|
'category': 'Contract Management',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'author': "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)",
|
'author': "LasLabs, "
|
||||||
|
"ACSONE SA/NV, "
|
||||||
|
"Odoo Community Association (OCA)",
|
||||||
'website': 'https://github.com/oca/contract',
|
'website': 'https://github.com/oca/contract',
|
||||||
'depends': ['product', 'contract_sale'],
|
'depends': ['product', 'contract_sale'],
|
||||||
'data': [
|
'data': [
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
rec.recurring_invoicing_type = (
|
rec.recurring_invoicing_type = (
|
||||||
rec.product_id.recurring_invoicing_type
|
rec.product_id.recurring_invoicing_type
|
||||||
)
|
)
|
||||||
rec.recurring_interval = rec.product_id.recurring_interval
|
rec.recurring_interval = 1
|
||||||
rec.is_auto_renew = rec.product_id.is_auto_renew
|
rec.is_auto_renew = rec.product_id.is_auto_renew
|
||||||
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
rec.auto_renew_interval = rec.product_id.default_qty
|
||||||
rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type
|
rec.auto_renew_rule_type = rec.product_id.recurring_rule_type
|
||||||
rec.termination_notice_interval = (
|
rec.termination_notice_interval = (
|
||||||
rec.product_id.termination_notice_interval
|
rec.product_id.termination_notice_interval
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ProductTemplate(models.Model):
|
|||||||
contract_template_id = fields.Many2one(
|
contract_template_id = fields.Many2one(
|
||||||
comodel_name='account.analytic.contract', string='Contract Template'
|
comodel_name='account.analytic.contract', string='Contract Template'
|
||||||
)
|
)
|
||||||
|
default_qty = fields.Integer(string="Default Quantity")
|
||||||
recurring_rule_type = fields.Selection(
|
recurring_rule_type = fields.Selection(
|
||||||
[
|
[
|
||||||
('daily', 'Day(s)'),
|
('daily', 'Day(s)'),
|
||||||
@@ -23,7 +23,7 @@ class ProductTemplate(models.Model):
|
|||||||
('yearly', 'Year(s)'),
|
('yearly', 'Year(s)'),
|
||||||
],
|
],
|
||||||
default='monthly',
|
default='monthly',
|
||||||
string='Recurrence',
|
string='Invoice Every',
|
||||||
help="Specify Interval for automatic invoice generation.",
|
help="Specify Interval for automatic invoice generation.",
|
||||||
)
|
)
|
||||||
recurring_invoicing_type = fields.Selection(
|
recurring_invoicing_type = fields.Selection(
|
||||||
@@ -32,23 +32,7 @@ class ProductTemplate(models.Model):
|
|||||||
string='Invoicing type',
|
string='Invoicing type',
|
||||||
help="Specify if process date is 'from' or 'to' invoicing date",
|
help="Specify if process date is 'from' or 'to' invoicing date",
|
||||||
)
|
)
|
||||||
recurring_interval = fields.Integer(
|
|
||||||
default=1,
|
|
||||||
string='Repeat Every',
|
|
||||||
help="Repeat every (Days/Week/Month/Year)",
|
|
||||||
)
|
|
||||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
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(
|
termination_notice_interval = fields.Integer(
|
||||||
default=1, string='Termination Notice Before'
|
default=1, string='Termination Notice Before'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ class SaleOrderLine(models.Model):
|
|||||||
('yearly', 'Year(s)'),
|
('yearly', 'Year(s)'),
|
||||||
],
|
],
|
||||||
default='monthly',
|
default='monthly',
|
||||||
string='Recurrence',
|
string='Invoice Every',
|
||||||
help="Specify Interval for automatic invoice generation.",
|
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
recurring_invoicing_type = fields.Selection(
|
recurring_invoicing_type = fields.Selection(
|
||||||
@@ -42,12 +41,6 @@ class SaleOrderLine(models.Model):
|
|||||||
help="Specify if process date is 'from' or 'to' invoicing date",
|
help="Specify if process date is 'from' or 'to' invoicing date",
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
recurring_interval = fields.Integer(
|
|
||||||
default=1,
|
|
||||||
string='Repeat Every',
|
|
||||||
help="Repeat every (Days/Week/Month/Year)",
|
|
||||||
copy=False,
|
|
||||||
)
|
|
||||||
date_start = fields.Date(string='Date Start')
|
date_start = fields.Date(string='Date Start')
|
||||||
date_end = fields.Date(string='Date End')
|
date_end = fields.Date(string='Date End')
|
||||||
|
|
||||||
@@ -57,48 +50,42 @@ 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):
|
||||||
contract_line_env = self.env['account.analytic.invoice.line']
|
contract_line_env = self.env['account.analytic.invoice.line']
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.product_id.is_contract:
|
if rec.product_id.is_contract:
|
||||||
|
rec.product_uom_qty = rec.product_id.default_qty
|
||||||
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
||||||
rec.recurring_invoicing_type = (
|
rec.recurring_invoicing_type = (
|
||||||
rec.product_id.recurring_invoicing_type
|
rec.product_id.recurring_invoicing_type
|
||||||
)
|
)
|
||||||
rec.recurring_interval = rec.product_id.recurring_interval
|
|
||||||
rec.date_start = rec.date_start or fields.Date.today()
|
rec.date_start = rec.date_start or fields.Date.today()
|
||||||
if rec.product_id.is_auto_renew:
|
rec.date_end = (
|
||||||
rec.date_end = (
|
rec.date_start
|
||||||
rec.date_start
|
+ contract_line_env.get_relative_delta(
|
||||||
+ contract_line_env.get_relative_delta(
|
rec.product_id.recurring_rule_type,
|
||||||
rec.product_id.auto_renew_rule_type,
|
int(rec.product_uom_qty),
|
||||||
rec.product_id.auto_renew_interval,
|
|
||||||
)
|
|
||||||
- relativedelta(days=1)
|
|
||||||
)
|
)
|
||||||
|
- relativedelta(days=1)
|
||||||
|
)
|
||||||
|
|
||||||
@api.onchange('date_start')
|
@api.onchange('date_start', 'product_uom_qty', 'recurring_rule_type')
|
||||||
def onchange_date_start(self):
|
def onchange_date_start(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.product_id.is_auto_renew:
|
if not rec.date_start:
|
||||||
if not rec.date_start:
|
rec.date_end = False
|
||||||
rec.date_end = False
|
else:
|
||||||
else:
|
rec.date_end = (
|
||||||
rec.date_end = (
|
rec.date_start
|
||||||
rec.date_start
|
+ self.env[
|
||||||
+ self.env[
|
'account.analytic.invoice.line'
|
||||||
'account.analytic.invoice.line'
|
].get_relative_delta(
|
||||||
].get_relative_delta(
|
rec.recurring_rule_type, int(rec.product_uom_qty)
|
||||||
rec.product_id.auto_renew_rule_type,
|
|
||||||
rec.product_id.auto_renew_interval,
|
|
||||||
)
|
|
||||||
- relativedelta(days=1)
|
|
||||||
)
|
)
|
||||||
|
- relativedelta(days=1)
|
||||||
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _prepare_contract_line_values(
|
def _prepare_contract_line_values(
|
||||||
@@ -111,7 +98,7 @@ class SaleOrderLine(models.Model):
|
|||||||
self.date_start or fields.Date.today(),
|
self.date_start or fields.Date.today(),
|
||||||
self.recurring_invoicing_type,
|
self.recurring_invoicing_type,
|
||||||
self.recurring_rule_type,
|
self.recurring_rule_type,
|
||||||
self.recurring_interval,
|
int(self.product_uom_qty),
|
||||||
)
|
)
|
||||||
termination_notice_interval = (
|
termination_notice_interval = (
|
||||||
self.product_id.termination_notice_interval
|
self.product_id.termination_notice_interval
|
||||||
@@ -123,19 +110,31 @@ class SaleOrderLine(models.Model):
|
|||||||
'sequence': self.sequence,
|
'sequence': self.sequence,
|
||||||
'product_id': self.product_id.id,
|
'product_id': self.product_id.id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'quantity': self.product_uom_qty,
|
# The quantity on the generated contract line is 1, as it
|
||||||
|
# correspond to the most common use cases:
|
||||||
|
# - quantity on the SO line = number of periods sold and unit
|
||||||
|
# price the price of one period, so the
|
||||||
|
# total amount of the SO corresponds to the planned value
|
||||||
|
# of the contract; in this case the quantity on the contract
|
||||||
|
# line must be 1
|
||||||
|
# - quantity on the SO line = number of hours sold,
|
||||||
|
# automatic invoicing of the actual hours through a variable
|
||||||
|
# quantity formula, in which case the quantity on the contract
|
||||||
|
# line is not used
|
||||||
|
# Other use cases are easy to implement by overriding this method.
|
||||||
|
'quantity': 1.0,
|
||||||
'uom_id': self.product_uom.id,
|
'uom_id': self.product_uom.id,
|
||||||
'price_unit': self.price_unit,
|
'price_unit': self.price_unit,
|
||||||
'discount': self.discount,
|
'discount': self.discount,
|
||||||
'date_end': self.date_end,
|
'date_end': self.date_end,
|
||||||
'date_start': self.date_start or fields.Date.today(),
|
'date_start': self.date_start or fields.Date.today(),
|
||||||
'recurring_next_date': recurring_next_date,
|
'recurring_next_date': recurring_next_date,
|
||||||
'recurring_interval': self.recurring_interval,
|
'recurring_interval': 1,
|
||||||
'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,
|
'is_auto_renew': self.product_id.is_auto_renew,
|
||||||
'auto_renew_interval': self.product_id.auto_renew_interval,
|
'auto_renew_interval': self.product_uom_qty,
|
||||||
'auto_renew_rule_type': self.product_id.auto_renew_rule_type,
|
'auto_renew_rule_type': self.product_id.recurring_rule_type,
|
||||||
'termination_notice_interval': termination_notice_interval,
|
'termination_notice_interval': termination_notice_interval,
|
||||||
'termination_notice_rule_type': termination_notice_rule_type,
|
'termination_notice_rule_type': termination_notice_rule_type,
|
||||||
'contract_id': contract.id,
|
'contract_id': contract.id,
|
||||||
@@ -153,8 +152,9 @@ class SaleOrderLine(models.Model):
|
|||||||
rec.date_start - relativedelta(days=1)
|
rec.date_start - relativedelta(days=1)
|
||||||
)
|
)
|
||||||
new_contract_line = contract_line_env.create(
|
new_contract_line = contract_line_env.create(
|
||||||
rec._prepare_contract_line_values(contract,
|
rec._prepare_contract_line_values(
|
||||||
rec.contract_line_id)
|
contract, rec.contract_line_id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if rec.contract_line_id:
|
if rec.contract_line_id:
|
||||||
rec.contract_line_id.successor_contract_line_id = (
|
rec.contract_line_id.successor_contract_line_id = (
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.product1.write(
|
self.product1.write(
|
||||||
{
|
{
|
||||||
'is_contract': True,
|
'is_contract': True,
|
||||||
'is_auto_renew': True,
|
'default_qty': 12,
|
||||||
'contract_template_id': self.contract_template1.id,
|
'contract_template_id': self.contract_template1.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -55,6 +55,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
lambda l: l.product_id == self.product1
|
lambda l: l.product_id == self.product1
|
||||||
)
|
)
|
||||||
self.order_line1.date_start = '2018-01-01'
|
self.order_line1.date_start = '2018-01-01'
|
||||||
|
self.order_line1.product_uom_qty = 12
|
||||||
pricelist = self.sale.partner_id.property_product_pricelist.id
|
pricelist = self.sale.partner_id.property_product_pricelist.id
|
||||||
self.contract = self.env["account.analytic.account"].create(
|
self.contract = self.env["account.analytic.account"].create(
|
||||||
{
|
{
|
||||||
@@ -91,10 +92,6 @@ 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 """
|
||||||
@@ -122,10 +119,6 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.order_line1.recurring_rule_type,
|
self.order_line1.recurring_rule_type,
|
||||||
self.product1.recurring_rule_type,
|
self.product1.recurring_rule_type,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
|
||||||
self.order_line1.recurring_interval,
|
|
||||||
self.product1.recurring_interval,
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.recurring_invoicing_type,
|
self.order_line1.recurring_invoicing_type,
|
||||||
self.product1.recurring_invoicing_type,
|
self.product1.recurring_invoicing_type,
|
||||||
@@ -228,10 +221,8 @@ class TestSaleOrder(TransactionCase):
|
|||||||
{
|
{
|
||||||
'recurring_rule_type': 'monthly',
|
'recurring_rule_type': 'monthly',
|
||||||
'recurring_invoicing_type': 'pre-paid',
|
'recurring_invoicing_type': 'pre-paid',
|
||||||
'recurring_interval': '2',
|
|
||||||
'is_auto_renew': True,
|
'is_auto_renew': True,
|
||||||
'auto_renew_interval': '6',
|
'default_qty': 12,
|
||||||
'auto_renew_rule_type': 'monthly',
|
|
||||||
'termination_notice_interval': '6',
|
'termination_notice_interval': '6',
|
||||||
'termination_notice_rule_type': 'weekly',
|
'termination_notice_rule_type': 'weekly',
|
||||||
}
|
}
|
||||||
@@ -249,9 +240,9 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.contract_line.recurring_invoicing_type, 'pre-paid'
|
self.contract_line.recurring_invoicing_type, 'pre-paid'
|
||||||
)
|
)
|
||||||
self.assertEqual(self.contract_line.recurring_interval, 2)
|
self.assertEqual(self.contract_line.recurring_interval, 1)
|
||||||
self.assertEqual(self.contract_line.is_auto_renew, True)
|
self.assertEqual(self.contract_line.is_auto_renew, True)
|
||||||
self.assertEqual(self.contract_line.auto_renew_interval, 6)
|
self.assertEqual(self.contract_line.auto_renew_interval, 12)
|
||||||
self.assertEqual(self.contract_line.auto_renew_rule_type, 'monthly')
|
self.assertEqual(self.contract_line.auto_renew_rule_type, 'monthly')
|
||||||
self.assertEqual(self.contract_line.termination_notice_interval, 6)
|
self.assertEqual(self.contract_line.termination_notice_interval, 6)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@@ -29,30 +29,16 @@
|
|||||||
</group>
|
</group>
|
||||||
<group name="recurrence_info">
|
<group name="recurrence_info">
|
||||||
<group>
|
<group>
|
||||||
<label for="recurring_interval"/>
|
<field name="recurring_rule_type"/>
|
||||||
<div>
|
|
||||||
<field name="recurring_interval"
|
|
||||||
class="oe_inline" nolabel="1"/>
|
|
||||||
<field name="recurring_rule_type"
|
|
||||||
class="oe_inline" nolabel="1"/>
|
|
||||||
</div>
|
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
|
<field name="default_qty"/>
|
||||||
<field name="recurring_invoicing_type"
|
<field name="recurring_invoicing_type"
|
||||||
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/>
|
attrs="{'invisible': [('recurring_rule_type', '=', 'monthlylastday')]}"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="is_auto_renew"/>
|
<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)],
|
<label for="termination_notice_interval" attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
'required':[('is_contract', '=', True)]}"/>
|
'required':[('is_contract', '=', True)]}"/>
|
||||||
<div attrs="{'invisible': [('is_auto_renew', '=', False)],
|
<div attrs="{'invisible': [('is_auto_renew', '=', False)],
|
||||||
|
|||||||
@@ -34,8 +34,7 @@
|
|||||||
<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"
|
||||||
@@ -45,13 +44,7 @@
|
|||||||
attrs="{'invisible': [('is_contract', '=', False)]}"/>
|
attrs="{'invisible': [('is_contract', '=', False)]}"/>
|
||||||
|
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<label for="recurring_interval"/>
|
<field name="recurring_rule_type"/>
|
||||||
<div>
|
|
||||||
<field name="recurring_interval"
|
|
||||||
class="oe_inline" nolabel="1"/>
|
|
||||||
<field name="recurring_rule_type"
|
|
||||||
class="oe_inline" nolabel="1"/>
|
|
||||||
</div>
|
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="recurring_invoicing_type"
|
<field name="recurring_invoicing_type"
|
||||||
@@ -62,8 +55,7 @@
|
|||||||
attrs="{'required': [('is_contract', '=', True)]}"/>
|
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" required="True"/>
|
||||||
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