mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[MIG] product_contract: Migration to 14.0
This commit is contained in:
@@ -7,6 +7,7 @@ from collections import namedtuple
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
@@ -2370,6 +2371,7 @@ class TestContract(TestContractBase):
|
|||||||
to_date("2018-02-13"),
|
to_date("2018-02-13"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@freeze_time("2020-01-01 00:00:00")
|
||||||
def test_recurrency_propagation(self):
|
def test_recurrency_propagation(self):
|
||||||
# Existing contract
|
# Existing contract
|
||||||
vals = {
|
vals = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "Recurring - Product Contract",
|
"name": "Recurring - Product Contract",
|
||||||
"version": "13.0.1.0.0",
|
"version": "14.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)",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class ProductTemplate(models.Model):
|
|||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if "is_contract" in vals and vals["is_contract"] is False:
|
if "is_contract" in vals and vals["is_contract"] is False:
|
||||||
for company in self.env["res.company"].search([]):
|
for company in self.env["res.company"].search([]):
|
||||||
self.with_context(force_company=company.id).write(
|
self.with_company(company).write(
|
||||||
{"property_contract_template_id": False}
|
{"property_contract_template_id": False}
|
||||||
)
|
)
|
||||||
super().write(vals)
|
super().write(vals)
|
||||||
|
|||||||
@@ -16,15 +16,11 @@ class SaleOrder(models.Model):
|
|||||||
@api.constrains("state")
|
@api.constrains("state")
|
||||||
def check_contact_is_not_terminated(self):
|
def check_contact_is_not_terminated(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if (
|
if rec.state not in (
|
||||||
rec.state
|
|
||||||
not in (
|
|
||||||
"sale",
|
"sale",
|
||||||
"done",
|
"done",
|
||||||
"cancel",
|
"cancel",
|
||||||
)
|
) and rec.order_line.filtered("contract_id.is_terminated"):
|
||||||
and rec.order_line.filtered("contract_id.is_terminated")
|
|
||||||
):
|
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("You can't upsell or downsell a terminated contract")
|
_("You can't upsell or downsell a terminated contract")
|
||||||
)
|
)
|
||||||
@@ -81,8 +77,8 @@ class SaleOrder(models.Model):
|
|||||||
)
|
)
|
||||||
contract_templates = self.env["contract.template"]
|
contract_templates = self.env["contract.template"]
|
||||||
for order_line in line_to_create_contract:
|
for order_line in line_to_create_contract:
|
||||||
contract_template = order_line.product_id.with_context(
|
contract_template = order_line.product_id.with_company(
|
||||||
force_company=rec.company_id.id
|
rec.company_id
|
||||||
).property_contract_template_id
|
).property_contract_template_id
|
||||||
if not contract_template:
|
if not contract_template:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
@@ -94,8 +90,8 @@ class SaleOrder(models.Model):
|
|||||||
contract_templates |= contract_template
|
contract_templates |= contract_template
|
||||||
for contract_template in contract_templates:
|
for contract_template in contract_templates:
|
||||||
order_lines = line_to_create_contract.filtered(
|
order_lines = line_to_create_contract.filtered(
|
||||||
lambda r, template=contract_template: r.product_id.with_context(
|
lambda r, template=contract_template: r.product_id.with_company(
|
||||||
force_company=r.order_id.company_id.id
|
r.order_id.company_id
|
||||||
).property_contract_template_id
|
).property_contract_template_id
|
||||||
== template
|
== template
|
||||||
)
|
)
|
||||||
@@ -112,7 +108,7 @@ class SaleOrder(models.Model):
|
|||||||
return contracts
|
return contracts
|
||||||
|
|
||||||
def action_confirm(self):
|
def action_confirm(self):
|
||||||
""" If we have a contract in the order, set it up """
|
"""If we have a contract in the order, set it up"""
|
||||||
self.filtered(
|
self.filtered(
|
||||||
lambda order: (order.company_id.create_contract_at_sale_order_confirmation)
|
lambda order: (order.company_id.create_contract_at_sale_order_confirmation)
|
||||||
).action_create_contract()
|
).action_create_contract()
|
||||||
@@ -127,7 +123,7 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
def action_show_contracts(self):
|
def action_show_contracts(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
action = self.env.ref("contract.action_customer_contract").read()[0]
|
action = self.env.ref("contract.action_customer_contract").sudo().read()[0]
|
||||||
contracts = (
|
contracts = (
|
||||||
self.env["contract.line"]
|
self.env["contract.line"]
|
||||||
.search([("sale_order_line_id", "in", self.order_line.ids)])
|
.search([("sale_order_line_id", "in", self.order_line.ids)])
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ class SaleOrderLine(models.Model):
|
|||||||
@api.depends("product_id")
|
@api.depends("product_id")
|
||||||
def _compute_contract_template_id(self):
|
def _compute_contract_template_id(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.contract_template_id = rec.product_id.with_context(
|
rec.contract_template_id = rec.product_id.with_company(
|
||||||
force_company=rec.order_id.company_id.id
|
rec.order_id.company_id
|
||||||
).property_contract_template_id
|
).property_contract_template_id
|
||||||
|
|
||||||
def _get_auto_renew_rule_type(self):
|
def _get_auto_renew_rule_type(self):
|
||||||
@@ -95,9 +95,21 @@ class SaleOrderLine(models.Model):
|
|||||||
return "monthly"
|
return "monthly"
|
||||||
return self.recurring_rule_type
|
return self.recurring_rule_type
|
||||||
|
|
||||||
|
def _get_date_end(self):
|
||||||
|
self.ensure_one()
|
||||||
|
contract_line_model = self.env["contract.line"]
|
||||||
|
date_end = (
|
||||||
|
self.date_start
|
||||||
|
+ contract_line_model.get_relative_delta(
|
||||||
|
self._get_auto_renew_rule_type(),
|
||||||
|
int(self.product_uom_qty),
|
||||||
|
)
|
||||||
|
- relativedelta(days=1)
|
||||||
|
)
|
||||||
|
return date_end
|
||||||
|
|
||||||
@api.onchange("product_id")
|
@api.onchange("product_id")
|
||||||
def onchange_product(self):
|
def onchange_product(self):
|
||||||
contract_line_model = self.env["contract.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.product_uom_qty = rec.product_id.default_qty
|
||||||
@@ -105,14 +117,7 @@ class SaleOrderLine(models.Model):
|
|||||||
rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type
|
rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type
|
||||||
rec.date_start = rec.date_start or fields.Date.today()
|
rec.date_start = rec.date_start or fields.Date.today()
|
||||||
|
|
||||||
rec.date_end = (
|
rec.date_end = rec._get_date_end()
|
||||||
rec.date_start
|
|
||||||
+ contract_line_model.get_relative_delta(
|
|
||||||
rec._get_auto_renew_rule_type(),
|
|
||||||
int(rec.product_uom_qty),
|
|
||||||
)
|
|
||||||
- relativedelta(days=1)
|
|
||||||
)
|
|
||||||
rec.is_auto_renew = rec.product_id.is_auto_renew
|
rec.is_auto_renew = rec.product_id.is_auto_renew
|
||||||
if rec.is_auto_renew:
|
if rec.is_auto_renew:
|
||||||
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
||||||
@@ -120,19 +125,25 @@ class SaleOrderLine(models.Model):
|
|||||||
|
|
||||||
@api.onchange("date_start", "product_uom_qty", "recurring_rule_type")
|
@api.onchange("date_start", "product_uom_qty", "recurring_rule_type")
|
||||||
def onchange_date_start(self):
|
def onchange_date_start(self):
|
||||||
contract_line_model = self.env["contract.line"]
|
|
||||||
for rec in self.filtered("product_id.is_contract"):
|
for rec in self.filtered("product_id.is_contract"):
|
||||||
if not rec.date_start:
|
rec.date_end = rec._get_date_end() if rec.date_start else False
|
||||||
rec.date_end = False
|
|
||||||
else:
|
def _get_contract_line_qty(self):
|
||||||
rec.date_end = (
|
"""Returns the quantity to be put on new contract lines."""
|
||||||
rec.date_start
|
self.ensure_one()
|
||||||
+ contract_line_model.get_relative_delta(
|
# The quantity on the generated contract line is 1, as it
|
||||||
rec._get_auto_renew_rule_type(),
|
# correspond to the most common use cases:
|
||||||
int(rec.product_uom_qty),
|
# - quantity on the SO line = number of periods sold and unit
|
||||||
)
|
# price the price of one period, so the
|
||||||
- relativedelta(days=1)
|
# 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.
|
||||||
|
return 1.0
|
||||||
|
|
||||||
def _prepare_contract_line_values(
|
def _prepare_contract_line_values(
|
||||||
self, contract, predecessor_contract_line_id=False
|
self, contract, predecessor_contract_line_id=False
|
||||||
@@ -157,19 +168,7 @@ 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,
|
||||||
# The quantity on the generated contract line is 1, as it
|
"quantity": self._get_contract_line_qty(),
|
||||||
# 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,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
from odoo.fields import Date
|
from odoo.fields import Date
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.product1.with_context(force_company=self.sale.company_id.id).write(
|
self.product1.with_company(self.sale.company_id).write(
|
||||||
{
|
{
|
||||||
"is_contract": True,
|
"is_contract": True,
|
||||||
"default_qty": 12,
|
"default_qty": 12,
|
||||||
@@ -48,7 +48,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
"property_contract_template_id": self.contract_template1.id,
|
"property_contract_template_id": self.contract_template1.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.product2.with_context(force_company=self.sale.company_id.id).write(
|
self.product2.with_company(self.sale.company_id).write(
|
||||||
{
|
{
|
||||||
"is_contract": True,
|
"is_contract": True,
|
||||||
"property_contract_template_id": self.contract_template2.id,
|
"property_contract_template_id": self.contract_template2.id,
|
||||||
@@ -116,26 +116,16 @@ class TestSaleOrder(TransactionCase):
|
|||||||
other_company = self.env["res.company"].create(
|
other_company = self.env["res.company"].create(
|
||||||
{"name": "other company", "parent_id": self.sale.company_id.id}
|
{"name": "other company", "parent_id": self.sale.company_id.id}
|
||||||
)
|
)
|
||||||
|
with self.assertRaises(UserError):
|
||||||
self.sale.company_id = other_company
|
self.sale.company_id = other_company
|
||||||
with self.assertRaises(ValidationError):
|
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
|
|
||||||
def test_change_sale_company_2(self):
|
def test_change_sale_company_2(self):
|
||||||
"""Contract company must be the sale order company."""
|
"""Contract company must be the sale order company."""
|
||||||
self.assertTrue(self.sale.company_id)
|
self.assertTrue(self.sale.company_id)
|
||||||
other_company = self.env["res.company"].create(
|
|
||||||
{"name": "other company", "parent_id": self.sale.company_id.id}
|
|
||||||
)
|
|
||||||
self.product1.with_context(
|
|
||||||
force_company=other_company.id
|
|
||||||
).property_contract_template_id = self.contract_template1
|
|
||||||
self.product2.with_context(
|
|
||||||
force_company=other_company.id
|
|
||||||
).property_contract_template_id = self.contract_template2
|
|
||||||
self.sale.company_id = other_company
|
|
||||||
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(contracts.mapped("company_id"), other_company)
|
self.assertEqual(contracts.mapped("company_id"), self.sale.company_id)
|
||||||
|
|
||||||
def test_sale_order_invoice_status(self):
|
def test_sale_order_invoice_status(self):
|
||||||
"""
|
"""
|
||||||
@@ -353,7 +343,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_order_lines_with_the_same_contract_template(self):
|
def test_order_lines_with_the_same_contract_template(self):
|
||||||
"""It should create one contract with two lines grouped by contract
|
"""It should create one contract with two lines grouped by contract
|
||||||
template"""
|
template"""
|
||||||
self.product2.with_context(force_company=self.sale.company_id.id).write(
|
self.product2.with_company(self.sale.company_id).write(
|
||||||
{
|
{
|
||||||
"is_contract": True,
|
"is_contract": True,
|
||||||
"property_contract_template_id": self.contract_template1.id,
|
"property_contract_template_id": self.contract_template1.id,
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
# generated from manifests external_dependencies
|
# generated from manifests external_dependencies
|
||||||
|
python-dateutil
|
||||||
|
|||||||
1
setup/product_contract/odoo/addons/product_contract
Symbolic link
1
setup/product_contract/odoo/addons/product_contract
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../../product_contract
|
||||||
6
setup/product_contract/setup.py
Normal file
6
setup/product_contract/setup.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import setuptools
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['setuptools-odoo'],
|
||||||
|
odoo_addon=True,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user