From 49c85c197b7ee8e2c42e2ea840f8ec162d87a1a4 Mon Sep 17 00:00:00 2001 From: Ernesto Tejeda Date: Tue, 11 Feb 2020 12:27:33 -0500 Subject: [PATCH] [FIX] product_contract: set 'Contract template' field company depend --- .../migrations/12.0.4.0.0/pre-migration.py | 36 +++++++++++++++++++ product_contract/models/product_template.py | 20 ++++++----- product_contract/models/sale_order.py | 22 +++++++++--- product_contract/models/sale_order_line.py | 11 ++++-- product_contract/tests/test_product.py | 11 +++--- product_contract/tests/test_sale_order.py | 35 ++++++++++++------ product_contract/views/product_template.xml | 3 +- 7 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 product_contract/migrations/12.0.4.0.0/pre-migration.py diff --git a/product_contract/migrations/12.0.4.0.0/pre-migration.py b/product_contract/migrations/12.0.4.0.0/pre-migration.py new file mode 100644 index 000000000..f6e2351c0 --- /dev/null +++ b/product_contract/migrations/12.0.4.0.0/pre-migration.py @@ -0,0 +1,36 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + # Convert contract_template_id field of the product_template table + # to a company dependent field + model_name = "product.template" + model_table_name = "product_template" + origin_field_name = "contract_template_id" + destination_field_name = "property_contract_template_id" + # Add ir.model.fields entry + env.cr.execute( + "SELECT id FROM ir_model WHERE model = %s", (model_name, ), + ) + model_id = env.cr.fetchone()[0] + openupgrade.logged_query( + env.cr, """ + INSERT INTO ir_model_fields ( + model_id, model, name, field_description, ttype, state, relation + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s + )""", + (model_id, model_name, destination_field_name, 'OU', "many2one", + 'base', 'contract.template'), + ) + openupgrade.convert_to_company_dependent( + env, + model_name, + origin_field_name, + destination_field_name, + model_table_name, + ) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 2eb2eca56..3ce6bdc44 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -10,8 +10,10 @@ class ProductTemplate(models.Model): _inherit = 'product.template' is_contract = fields.Boolean('Is a contract') - contract_template_id = fields.Many2one( - comodel_name='contract.template', string='Contract Template' + property_contract_template_id = fields.Many2one( + comodel_name='contract.template', + string='Contract Template', + company_dependent=True, ) default_qty = fields.Integer(string="Default Quantity", default=1) recurring_rule_type = fields.Selection( @@ -58,13 +60,13 @@ class ProductTemplate(models.Model): help="Specify Interval for automatic renewal.", ) - @api.onchange('is_contract') - def _change_is_contract(self): - """ Clear the relation to contract_template_id when downgrading - product from contract - """ - if not self.is_contract: - self.contract_template_id = False + @api.multi + def write(self, vals): + if 'is_contract' in vals and vals['is_contract'] is False: + for company in self.env['res.company'].search([]): + self.with_context(force_company=company.id).write( + {'property_contract_template_id': False}) + super().write(vals) @api.constrains('is_contract', 'type') def _check_contract_product_type(self): diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 4331d8079..70c65ae8c 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -2,7 +2,8 @@ # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, api, models +from odoo import _, fields, api, models +from odoo.exceptions import ValidationError class SaleOrder(models.Model): @@ -70,12 +71,23 @@ class SaleOrder(models.Model): 'sale_order_line_id' ) ) - for contract_template in line_to_create_contract.mapped( - 'product_id.contract_template_id' - ): + for order_line in line_to_create_contract: + contract_template = order_line.product_id.with_context( + force_company=rec.company_id.id + ).property_contract_template_id + if not contract_template: + raise ValidationError( + _("You must specify a contract " + "template for '{}' product in '{}' company.").format( + order_line.product_id.name, + rec.company_id.name + ) + ) order_lines = line_to_create_contract.filtered( lambda r, template=contract_template: - r.product_id.contract_template_id == template + r.product_id.with_context( + force_company=r.order_id.company_id.id + ).property_contract_template_id == template ) contract = contract_model.create( rec._prepare_contract_value(contract_template) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index dc21fa9ad..02d0c91b2 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -19,8 +19,7 @@ class SaleOrderLine(models.Model): contract_template_id = fields.Many2one( comodel_name='contract.template', string='Contract Template', - related='product_id.product_tmpl_id.contract_template_id', - readonly=True, + compute='_compute_contract_template_id', ) recurring_rule_type = fields.Selection( [ @@ -68,6 +67,14 @@ class SaleOrderLine(models.Model): help="Specify Interval for automatic renewal.", ) + @api.multi + @api.depends('product_id') + def _compute_contract_template_id(self): + for rec in self: + rec.contract_template_id = rec.product_id.with_context( + force_company=rec.order_id.company_id.id + ).property_contract_template_id + @api.multi def _get_auto_renew_rule_type(self): """monthly last day don't make sense for auto_renew_rule_type""" diff --git a/product_contract/tests/test_product.py b/product_contract/tests/test_product.py index 64a125c87..e5ef52c8a 100644 --- a/product_contract/tests/test_product.py +++ b/product_contract/tests/test_product.py @@ -16,13 +16,14 @@ class TestProductTemplate(TransactionCase): ) def test_change_is_contract(self): - """ It should verify that the contract_template_id is removed - when is_contract is False """ + """ It should verify that the property_contract_template_id + field value is removed for all the companies when + is_contract is set to False """ self.service_product.is_contract = True - self.service_product.contract_template_id = self.contract.id + self.service_product.property_contract_template_id = self.contract.id self.service_product.is_contract = False - self.service_product.product_tmpl_id._change_is_contract() - self.assertEquals(len(self.service_product.contract_template_id), 0) + self.assertEquals(len( + self.service_product.property_contract_template_id), 0) def test_check_contract_product_type(self): """ diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index a71d95b81..36705af7c 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -38,19 +38,21 @@ class TestSaleOrder(TransactionCase): ], } ) - self.product1.write( + self.product1.with_context( + force_company=self.sale.company_id.id).write( { 'is_contract': True, 'default_qty': 12, 'recurring_rule_type': "monthlylastday", 'recurring_invoicing_type': "post-paid", - 'contract_template_id': self.contract_template1.id, + 'property_contract_template_id': self.contract_template1.id, } ) - self.product2.write( + self.product2.with_context( + force_company=self.sale.company_id.id).write( { 'is_contract': True, - 'contract_template_id': self.contract_template2.id, + 'property_contract_template_id': self.contract_template2.id, } ) self.order_line1 = self.sale.order_line.filtered( @@ -111,15 +113,28 @@ class TestSaleOrder(TransactionCase): contract_line.recurring_next_date, Date.to_date('2018-01-31') ) - def test_contract_company(self): - """ - contract company must be the sale order company and not the user one - """ + def test_change_sale_company(self): self.assertTrue(self.sale.company_id) other_company = self.env['res.company'].create( {'name': 'other company', 'parent_id': self.sale.company_id.id} ) self.sale.company_id = other_company + with self.assertRaises(ValidationError): + self.sale.action_confirm() + + def test_change_sale_company_2(self): + """Contract company must be the sale order company.""" + 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() contracts = self.sale.order_line.mapped('contract_id') self.assertEqual(contracts.mapped('company_id'), other_company) @@ -132,7 +147,7 @@ class TestSaleOrder(TransactionCase): self.assertEqual(self.order_line1.invoice_status, 'no') invoice = self.order_line1.contract_id.recurring_create_invoice() self.assertTrue(invoice) - self.assertEqual(self.order_line1.invoice_qty, 1) + self.assertEqual(self.order_line1.qty_invoiced, 1) self.assertEqual(self.order_line1.qty_to_invoice, 0) def test_action_confirm_without_contract_creation(self): @@ -217,7 +232,7 @@ class TestSaleOrder(TransactionCase): self.sale.action_confirm() self.assertEqual(self.order_line1.invoice_status, 'no') - def test_sale_order_invoice_status(self): + def test_sale_order_invoice_status_2(self): """Sale order with only contract product should have nothing to invoice status directtly""" self.sale.order_line.filtered( diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 59ad6e7eb..c6ecef126 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -24,8 +24,7 @@ name="contract" attrs="{'invisible': [('is_contract', '=', False)],}"> - +