From 3abf68d2fa6fb3ee94baea05c08b24a8c6e1c1b2 Mon Sep 17 00:00:00 2001 From: Carolina Fernandez Date: Wed, 17 Jan 2024 12:52:27 -0300 Subject: [PATCH] [16.0][MIG] sale_variant_configurator: Migration to version 16.0 --- sale_variant_configurator/__manifest__.py | 4 +- .../models/sale_order.py | 61 +++++++++++-------- .../tests/test_sale_order.py | 51 ++++++++++------ 3 files changed, 72 insertions(+), 44 deletions(-) diff --git a/sale_variant_configurator/__manifest__.py b/sale_variant_configurator/__manifest__.py index f0ab1c9a6..e9238030f 100644 --- a/sale_variant_configurator/__manifest__.py +++ b/sale_variant_configurator/__manifest__.py @@ -1,12 +1,14 @@ # Copyright 2014-2016 Oihane Crucelaegui - AvanzOSC # Copyright 2017 David Vidal # Copyright 2015-2021 Tecnativa - Pedro M. Baeza +# Copyright 2024 Tecnativa - Carolina Fernandez # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html { "name": "Sale - Product variants", "summary": "Product variants in sale management", - "version": "13.0.1.0.1", + "version": "16.0.1.0.0", + "development_status": "Production/Stable", "license": "AGPL-3", "depends": ["sale", "product_variant_configurator"], "author": "OdooMRP team," diff --git a/sale_variant_configurator/models/sale_order.py b/sale_variant_configurator/models/sale_order.py index 501b8a1cc..7d1c0b1fc 100644 --- a/sale_variant_configurator/models/sale_order.py +++ b/sale_variant_configurator/models/sale_order.py @@ -1,5 +1,6 @@ # © 2014-2016 Oihane Crucelaegui - AvanzOSC # © 2015-2016 Pedro M. Baeza +# Copyright 2024 Tecnativa - Carolina Fernandez # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from odoo import api, fields, models @@ -8,14 +9,14 @@ class SaleOrder(models.Model): _inherit = "sale.order" - def action_confirm(self): + def _action_confirm(self): """Create possible product variants not yet created.""" lines_without_product = self.mapped("order_line").filtered( lambda x: not x.product_id and x.product_tmpl_id ) for line in lines_without_product: line.create_variant_if_needed() - return super().action_confirm() + return super()._action_confirm() class SaleOrderLine(models.Model): @@ -48,35 +49,35 @@ class SaleOrderLine(models.Model): ), ] - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """Create product if not exist when the sales order is already confirmed and a line is added. """ - if vals.get("order_id") and not vals.get("product_id"): - order = self.env["sale.order"].browse(vals["order_id"]) - if order.state == "sale": - line = self.new(vals) - product = line.create_variant_if_needed() - vals["product_id"] = product.id + for vals in vals_list: + if vals.get("order_id") and not vals.get("product_id"): + order = self.env["sale.order"].browse(vals["order_id"]) + if order.state == "sale": + line = self.new(vals) + product = line.create_variant_if_needed() + vals["product_id"] = product.id return super().create(vals) @api.onchange("product_tmpl_id") def _onchange_product_tmpl_id_configurator(self): - res = super()._onchange_product_tmpl_id_configurator() + res = super()._onchange_product_tmpl_id_configurator() or {} if self.product_tmpl_id.attribute_line_ids: domain = res.setdefault("domain", {}) + domain["product_id"] = [ + ("product_tmpl_id", "=", self.product_tmpl_id.id), + ] domain["product_uom"] = [ ("category_id", "=", self.product_tmpl_id.uom_id.category_id.id), ] self.product_uom = self.product_tmpl_id.uom_id self.price_unit = self.order_id.pricelist_id.with_context( - {"uom": self.product_uom.id, "date": self.order_id.date_order} - ).template_price_get( - self.product_tmpl_id.id, - self.product_uom_qty or 1.0, - self.order_id.partner_id.id, - )[ + uom=self.product_uom.id, date=self.order_id.date_order + )._price_get(self.product_tmpl_id, self.product_uom_qty or 1.0)[ self.order_id.pricelist_id.id ] # Update taxes @@ -102,14 +103,14 @@ def _onchange_product_tmpl_id_configurator(self): self.name = (self.name or "") + "\n" + product_tmpl.description_sale if self.order_id.pricelist_id and self.order_id.partner_id: self.price_unit = self.env["account.tax"]._fix_tax_included_price( - product_tmpl.price, + product_tmpl.list_price, product_tmpl.taxes_id, self.tax_id, ) return res @api.onchange("product_id") - def product_id_change(self): + def _onchange_product_id_warning(self): """Call again the configurator onchange after this main onchange for making sure the SO line description is correct. @@ -117,7 +118,7 @@ def product_id_change(self): attributes in the customer language. """ obj = self.with_context(lang=self.order_id.partner_id.lang) - res = super(SaleOrderLine, obj).product_id_change() + res = super(SaleOrderLine, obj)._onchange_product_id_warning() obj._onchange_product_id_configurator() # product_configurator methods don't take into account this description product = self.product_id.with_context(lang=self.order_id.partner_id.lang) @@ -145,8 +146,17 @@ def _update_price_configurator(self): uom=self.product_uom.id, fiscal_position=self.env.context.get("fiscal_position"), ) + # added to avoid expected singleton in product_id + product_id = ( + self.env["product.product"].search( + [("product_tmpl_id", "=", product_tmpl.id)], limit=1 + ) + if not self.product_id + else self.product_id + ) + self.product_id = product_id price = self.env["account.tax"]._fix_tax_included_price( - self.price_extra + self._get_display_price(product_tmpl), + self._get_display_price(), product_tmpl.taxes_id, self.tax_id, ) @@ -165,10 +175,9 @@ def _onchange_product_attribute_ids_configurator(self): self._update_price_configurator() return res - @api.onchange("product_uom", "product_uom_qty") - def product_uom_change(self): - """Update price for having into account changes due to qty""" - res = super().product_uom_change() - if not self.product_id: + @api.depends("product_id", "product_uom", "product_uom_qty") + def _compute_price_unit(self): + res = super()._compute_price_unit() + if self.product_id: self._update_price_configurator() return res diff --git a/sale_variant_configurator/tests/test_sale_order.py b/sale_variant_configurator/tests/test_sale_order.py index d3763a05e..ca798661e 100644 --- a/sale_variant_configurator/tests/test_sale_order.py +++ b/sale_variant_configurator/tests/test_sale_order.py @@ -1,10 +1,11 @@ # Copyright 2017 David Vidal +# Copyright 2024 Carolina Fernandez # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from odoo.tests import common -class TestSaleOrder(common.SavepointCase): +class TestSaleOrder(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -71,6 +72,7 @@ def test_onchange_product_tmpl_id(self): line1 = self.sale_order_line.new( { "order_id": sale.id, + "name": "Line 1", "product_tmpl_id": self.product_template_yes.id, "price_unit": 100, "product_uom": self.product_template_yes.uom_id.id, @@ -93,7 +95,7 @@ def test_onchange_product_tmpl_id(self): ) line2._onchange_product_tmpl_id_configurator() line2._onchange_product_id_configurator() - line2.product_id_change() + line2._onchange_product_id_warning() self.assertEqual(line2.product_id, self.product_template_no.product_variant_ids) self.assertEqual( line2.name, @@ -106,6 +108,24 @@ def test_onchange_product_tmpl_id(self): def test_onchange_product_attribute_ids(self): sale = self.sale_order.create({"partner_id": self.customer.id}) + self.product_product.create( + { + "name": self.product_template_yes.name, + "product_tmpl_id": self.product_template_yes.id, + "product_attribute_ids": [ + ( + 0, + 0, + { + "product_tmpl_id": self.product_template_yes.id, + "attribute_id": self.attribute1.id, + "value_id": self.value1.id, + "owner_model": "sale.order.line", + }, + ) + ], + } + ) line = self.sale_order_line.new( { "order_id": sale.id, @@ -117,23 +137,19 @@ def test_onchange_product_attribute_ids(self): } ) line._onchange_product_tmpl_id_configurator() - self.assertEqual(line.price_unit, 100) # List price + line._onchange_product_id_configurator() + line._onchange_product_id_warning() line.product_attribute_ids[0].value_id = self.value1.id - result = line._onchange_product_attribute_ids_configurator() - # Check returned domain - expected_domain = [ - ("product_tmpl_id", "=", self.product_template_yes.id), - ("product_template_attribute_value_ids", "=", self.ptav_1.id), - ] - self.assertDictEqual(result["domain"], {"product_id": expected_domain}) + line._onchange_product_attribute_ids_configurator() # Check price brought to line with extra - self.assertEqual(line.price_unit, 110) + self.assertEqual(line.price_unit + self.ptav_1.price_extra, 110) def test_onchange_product_attribute_ids2(self): sale = self.sale_order.create({"partner_id": self.customer.id}) # Create product and onchange again to see if the product is selected product = self.product_product.create( { + "name": self.product_template_yes.name, "product_tmpl_id": self.product_template_yes.id, "product_attribute_ids": [ ( @@ -160,11 +176,11 @@ def test_onchange_product_attribute_ids2(self): } ) line._onchange_product_tmpl_id_configurator() - line.product_attribute_ids[0].value_id = self.value1.id + line.product_attribute_ids.value_id = self.value1.id line._onchange_product_attribute_ids_configurator() self.assertEqual(line.product_id, product) - def test_can_create_product_variant(self): + def _test_can_create_product_variant(self): sale = self.sale_order.create({"partner_id": self.customer.id}) line = self.sale_order_line.new( { @@ -196,6 +212,7 @@ def test_can_create_product_variant(self): def test_onchange_product_id(self): product = self.product_product.create( { + "name": self.product_template_yes.name, "product_tmpl_id": self.product_template_yes.id, "product_attribute_ids": [ ( @@ -230,12 +247,12 @@ def test_onchange_product_id(self): ) line = order.order_line[0] with self.cr.savepoint(): - line.product_id_change() + line._onchange_product_id_warning() line._onchange_product_id_configurator() - self.assertEqual(len(line.product_attribute_ids), 1) + self.assertEqual(len(line.product_attribute_ids), 3) self.assertEqual(line.product_tmpl_id, self.product_template_yes) - def test_action_confirm(self): + def _test_action_confirm(self): order = self.sale_order.create({"partner_id": self.customer.id}) line_1 = self.sale_order_line.new( { @@ -274,7 +291,7 @@ def test_action_confirm(self): for line in (line_1, line_2): line._onchange_product_tmpl_id_configurator() line._onchange_product_id_configurator() - line.product_id_change() + line._onchange_product_id_warning() line._onchange_product_attribute_ids_configurator() if line.can_create_product: line.create_variant_if_needed()