From 1360c046caf9865fa45a665ba79b184842802350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Mon, 13 May 2024 12:59:08 +0200 Subject: [PATCH 1/3] [IMP] product_supplierinfo_for_customer_sale: Add compatibility with Form() to change line name properly TT49073 Co-Authored-By: Pedro M. Baeza --- .../models/sale_order_line.py | 66 ++++++++------- ..._product_supplierinfo_for_customer_sale.py | 81 +++++++++---------- 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/product_supplierinfo_for_customer_sale/models/sale_order_line.py b/product_supplierinfo_for_customer_sale/models/sale_order_line.py index 67163c1cb90..d958d0b115e 100644 --- a/product_supplierinfo_for_customer_sale/models/sale_order_line.py +++ b/product_supplierinfo_for_customer_sale/models/sale_order_line.py @@ -1,6 +1,8 @@ # Copyright 2013-2017 Agile Business Group sagl # () # Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# Copyright 2024 Tecnativa - Víctor Martínez +# Copyright 2024 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import api, fields, models @@ -25,38 +27,40 @@ def _compute_product_customer_code(self): code = "" line.product_customer_code = code + def _update_description(self): + """Add the customer code in the description when applicable. + + This also takes from context the possible customerinfo already searched in + product_id_change for avoiding duplicated searches. + """ + if "customerinfo" in self.env.context: + customerinfo = self.env.context["customerinfo"] + else: + customerinfo = self.product_id._select_customerinfo( + partner=self.order_partner_id + ) + if customerinfo.product_code: + # Avoid to put the standard internal reference + self = self.with_context(display_default_code=False) + res = super()._update_description() + if customerinfo.product_code: + self.name = f"[{customerinfo.product_code}] {self.name}" + return res + @api.onchange("product_id") def _onchange_product_id_warning(self): - result = super(SaleOrderLine, self)._onchange_product_id_warning() - for line in self.filtered( - lambda sol: sol.product_id.product_tmpl_id.customer_ids - and sol.order_id.pricelist_id.item_ids - ): - product = line.product_id - items = self.env["product.pricelist.item"].search( - [ - ("pricelist_id", "=", line.order_id.pricelist_id.id), - ("compute_price", "=", "formula"), - ("base", "=", "partner"), - "|", - ("applied_on", "=", "3_global"), - "|", - "&", - ("categ_id", "=", product.categ_id.id), - ("applied_on", "=", "2_product_category"), - "|", - "&", - ("product_tmpl_id", "=", product.product_tmpl_id.id), - ("applied_on", "=", "1_product"), - "&", - ("product_id", "=", product.id), - ("applied_on", "=", "0_product_variant"), - ] - ) - if items: - supplierinfo = line.product_id._select_customerinfo( + """Inject the customerinfo in the context for not repeating the search in + _update_description + assign the mininum quantity if set. + """ + for line in self: + customerinfo = self.env["product.customerinfo"] + if line.product_id: + customerinfo = line.product_id._select_customerinfo( partner=line.order_partner_id ) - if supplierinfo and supplierinfo.min_qty: - line.product_uom_qty = supplierinfo.min_qty - return result + super( + SaleOrderLine, line.with_context(customerinfo=customerinfo) + )._onchange_product_id_warning() + if customerinfo and customerinfo.min_qty: + line.product_uom_qty = customerinfo.min_qty + return {} diff --git a/product_supplierinfo_for_customer_sale/tests/test_product_supplierinfo_for_customer_sale.py b/product_supplierinfo_for_customer_sale/tests/test_product_supplierinfo_for_customer_sale.py index 07aeb42e069..19fbc24ee7a 100644 --- a/product_supplierinfo_for_customer_sale/tests/test_product_supplierinfo_for_customer_sale.py +++ b/product_supplierinfo_for_customer_sale/tests/test_product_supplierinfo_for_customer_sale.py @@ -1,12 +1,14 @@ # Copyright 2017 ForgeFlow S.L. # (http://www.forgeflow.com) +# Copyright 2024 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests import Form from odoo.tests.common import TransactionCase class TestProductSupplierinfoForCustomerSale(TransactionCase): def setUp(self): - super(TestProductSupplierinfoForCustomerSale, self).setUp() + super().setUp() self.supplierinfo_model = self.env["product.supplierinfo"] self.customerinfo_model = self.env["product.customerinfo"] self.pricelist_item_model = self.env["product.pricelist.item"] @@ -79,13 +81,14 @@ def _create_pricelist_item(self, name, pricelist, product): ) def test_product_supplierinfo_for_customer_sale(self): - so = self.env["sale.order"].create( - {"partner_id": self.customer.id, "pricelist_id": self.pricelist.id} - ) - line = self.env["sale.order.line"].create( - {"product_id": self.product.id, "order_id": so.id} - ) - line._onchange_product_id_warning() + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product + order = order_form.save() + line = order.order_line + self.assertIn("00001", order.order_line.name) self.assertEqual( line.product_customer_code, self.customerinfo.product_code, @@ -98,13 +101,13 @@ def test_product_supplierinfo_for_customer_sale(self): ) def test_product_supplierinfo_for_customer_sale_variant(self): - so = self.env["sale.order"].create( - {"partner_id": self.customer.id, "pricelist_id": self.pricelist.id} - ) - line = self.env["sale.order.line"].create( - {"product_id": self.product_variant_1.id, "order_id": so.id} - ) - line._onchange_product_id_warning() + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_1 + order = order_form.save() + line = order.order_line self.assertEqual( line.product_customer_code, self.customerinfo.product_code, @@ -115,32 +118,26 @@ def test_product_supplierinfo_for_customer_sale_template(self): customerinfo = self._create_partnerinfo( "customer", self.customer, self.product_variant_2 ) - so = self.env["sale.order"].create( - {"partner_id": self.customer.id, "pricelist_id": self.pricelist.id} - ) - line = self.env["sale.order.line"].create( - {"product_id": self.product_variant_2.id, "order_id": so.id} - ) - line._onchange_product_id_warning() + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_2 + order = order_form.save() + line = order.order_line self.assertEqual( line.product_customer_code, customerinfo.product_code, "Error: Customer product code was not passed to sale order line", ) # Test with product without variants - so2 = self.env["sale.order"].create( - { - "partner_id": self.customer.id, - "pricelist_id": self.pricelist_template.id, - } - ) - line2 = self.env["sale.order.line"].create( - { - "product_id": self.product_template.product_variant_ids.id, - "order_id": so2.id, - } - ) - line2._onchange_product_id_warning() + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist_template + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_template.product_variant_ids[0] + order2 = order_form.save() + line2 = order2.order_line self.assertEqual( line2.product_customer_code, customerinfo.product_code, @@ -151,13 +148,13 @@ def test_product_supplierinfo_for_customer_sale_variant_wo_template(self): customerinfo = self._create_partnerinfo( "customer", self.customer, self.product_variant_2, empty_variant=True ) - so = self.env["sale.order"].create( - {"partner_id": self.customer.id, "pricelist_id": self.pricelist.id} - ) - line = self.env["sale.order.line"].create( - {"product_id": self.product_variant_2.id, "order_id": so.id} - ) - line._onchange_product_id_warning() + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_2 + order = order_form.save() + line = order.order_line self.assertEqual( line.product_customer_code, customerinfo.product_code, From 9e6d4265ef816c23c53d525e15758feca828f400 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Tue, 28 May 2024 16:36:38 +0200 Subject: [PATCH 2/3] [FIX] product_supplierinfo_for_customer_sale: Don't inject context in onchange The previous code was trying to avoid a double search on customerinfo passing by context the already searched customerinfo, but the ORM makes some artifacts (like duplicating one2many references with NewId records) on that cases, provoking the call chain to fail when comparing .id on NewId. The solution is to not pass anything by context and do the search 2 times. Anyway, the performance improvement was low, having data in cache. TT49073 --- .../models/sale_order_line.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/product_supplierinfo_for_customer_sale/models/sale_order_line.py b/product_supplierinfo_for_customer_sale/models/sale_order_line.py index d958d0b115e..dada36c0959 100644 --- a/product_supplierinfo_for_customer_sale/models/sale_order_line.py +++ b/product_supplierinfo_for_customer_sale/models/sale_order_line.py @@ -33,12 +33,11 @@ def _update_description(self): This also takes from context the possible customerinfo already searched in product_id_change for avoiding duplicated searches. """ - if "customerinfo" in self.env.context: - customerinfo = self.env.context["customerinfo"] - else: - customerinfo = self.product_id._select_customerinfo( - partner=self.order_partner_id - ) + # We need to repeat the search here, as passing the value by context or any + # other trick makes the ORM to do ugly things in "onchange" mode + customerinfo = self.product_id._select_customerinfo( + partner=self.order_partner_id + ) if customerinfo.product_code: # Avoid to put the standard internal reference self = self.with_context(display_default_code=False) @@ -52,15 +51,12 @@ def _onchange_product_id_warning(self): """Inject the customerinfo in the context for not repeating the search in _update_description + assign the mininum quantity if set. """ + res = super()._onchange_product_id_warning() for line in self: - customerinfo = self.env["product.customerinfo"] if line.product_id: customerinfo = line.product_id._select_customerinfo( partner=line.order_partner_id ) - super( - SaleOrderLine, line.with_context(customerinfo=customerinfo) - )._onchange_product_id_warning() - if customerinfo and customerinfo.min_qty: - line.product_uom_qty = customerinfo.min_qty - return {} + if customerinfo.min_qty: + line.product_uom_qty = customerinfo.min_qty + return res From 4038573fbd40e720f0e7d9c09f2355291cbbeeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Fri, 31 May 2024 11:58:09 +0200 Subject: [PATCH 3/3] [IMP] product_supplierinfo_for_customer_sale: Adapt code to 16.0 --- .../models/sale_order_line.py | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/product_supplierinfo_for_customer_sale/models/sale_order_line.py b/product_supplierinfo_for_customer_sale/models/sale_order_line.py index dada36c0959..7b1ec006be9 100644 --- a/product_supplierinfo_for_customer_sale/models/sale_order_line.py +++ b/product_supplierinfo_for_customer_sale/models/sale_order_line.py @@ -27,30 +27,26 @@ def _compute_product_customer_code(self): code = "" line.product_customer_code = code - def _update_description(self): - """Add the customer code in the description when applicable. - - This also takes from context the possible customerinfo already searched in - product_id_change for avoiding duplicated searches. - """ - # We need to repeat the search here, as passing the value by context or any - # other trick makes the ORM to do ugly things in "onchange" mode - customerinfo = self.product_id._select_customerinfo( - partner=self.order_partner_id - ) - if customerinfo.product_code: - # Avoid to put the standard internal reference - self = self.with_context(display_default_code=False) - res = super()._update_description() - if customerinfo.product_code: - self.name = f"[{customerinfo.product_code}] {self.name}" - return res + def _compute_name(self): + """We need to override the method with product_id is set so that the product + code is not added and add custom code of customerinfo.""" + empty_lines = self.filtered(lambda x: not x.product_id) + super(SaleOrderLine, empty_lines)._compute_name() + for item in self - empty_lines: + customerinfo = item.product_id._select_customerinfo( + partner=item.order_partner_id + ) + if customerinfo.product_code: + # Avoid to put the standard internal reference + item = item.with_context(display_default_code=False) + super(SaleOrderLine, item)._compute_name() + if customerinfo.product_code: + item.name = f"[{customerinfo.product_code}] {item.name}" + return @api.onchange("product_id") def _onchange_product_id_warning(self): - """Inject the customerinfo in the context for not repeating the search in - _update_description + assign the mininum quantity if set. - """ + """Assign the mininum quantity if set.""" res = super()._onchange_product_id_warning() for line in self: if line.product_id: