Skip to content

Commit

Permalink
[MIG] sale_variant_configurator: Migration to version 16.0
Browse files Browse the repository at this point in the history
TT46599
  • Loading branch information
carolinafernandez-tecnativa authored and victoralmau committed Jun 18, 2024
1 parent 2b98329 commit 511e6a6
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 195 deletions.
18 changes: 9 additions & 9 deletions product_variant_configurator/models/product_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
class ProductConfigurator(models.AbstractModel):
_name = "product.configurator"
_description = "Product Configurator"
_partner_id_field = "partner_id"

product_tmpl_id = fields.Many2one(
string="Product Template", comodel_name="product.template", auto_join=True
Expand Down Expand Up @@ -48,6 +49,7 @@ class ProductConfigurator(models.AbstractModel):
)
def _compute_can_be_created(self):
for rec in self:
rec.price_extra = sum(rec.mapped("product_attribute_ids.price_extra"))
if rec.product_id or not rec.product_tmpl_id:
# product already selected or no product nor template
rec.can_create_product = False
Expand All @@ -56,7 +58,6 @@ def _compute_can_be_created(self):
len(rec.product_tmpl_id.attribute_line_ids.mapped("attribute_id"))
- len(list(filter(None, rec.product_attribute_ids.mapped("value_id"))))
)
rec.price_extra = sum(rec.mapped("product_attribute_ids.price_extra"))

@api.depends("product_tmpl_id", "product_attribute_ids")
def _compute_product_id_configurator_domain(self):
Expand Down Expand Up @@ -104,7 +105,6 @@ def _empty_attributes(self):
def _onchange_product_tmpl_id_configurator(self):
self.ensure_one()
if not self.product_tmpl_id._origin:
self.product_id = False
self.product_id = False
self._empty_attributes()

Expand Down Expand Up @@ -147,15 +147,14 @@ def _onchange_product_attribute_ids_configurator(self):
if not self.product_id:
product_tmpl = self.product_tmpl_id
values = self.product_attribute_ids.mapped("value_id")
if "partner_id" in self._fields:
if self._partner_id_field in self._fields:
partner = self[self._partner_id_field]
# If our model has a partner_id field, language is got from it
obj = self.env["product.attribute.value"].with_context(
lang=self.partner_id.lang
lang=partner.lang
)
values = obj.browse(self.product_attribute_ids.mapped("value_id").ids)
obj = self.env["product.template"].with_context(
lang=self.partner_id.lang
)
obj = self.env["product.template"].with_context(lang=partner.lang)
product_tmpl = obj.browse(self.product_tmpl_id.id)
if "name" in self._fields:
self.name = self._get_product_description(product_tmpl, False, values)
Expand All @@ -165,11 +164,12 @@ def _onchange_product_id_configurator(self):
self.ensure_one()
if self.product_id:
product = self.product_id
if "partner_id" in self._fields:
if self._partner_id_field in self._fields:
partner = self[self._partner_id_field]
# If our model has a partner_id field, language is got from it
product = (
self.env["product.product"]
.with_context(lang=self.partner_id.lang)
.with_context(lang=partner.lang)
.browse(self.product_id.id)
)
self.name = self._get_product_description(
Expand Down
19 changes: 16 additions & 3 deletions product_variant_sale_price/models/product_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo import api, fields, models
from odoo.tools import config


class ProductTemplate(models.Model):
Expand Down Expand Up @@ -44,7 +45,12 @@ def _get_combination_info(
parent_combination,
only_template,
)
res["price_extra"] = 0.0
test_condition = not config["test_enable"] or (

Check warning on line 48 in product_variant_sale_price/models/product_product.py

View check run for this annotation

Codecov / codecov/patch

product_variant_sale_price/models/product_product.py#L48

Added line #L48 was not covered by tests
config["test_enable"]
and self.env.context.get("test_product_variant_sale_price")
)
if test_condition:
res["price_extra"] = 0.0

Check warning on line 53 in product_variant_sale_price/models/product_product.py

View check run for this annotation

Codecov / codecov/patch

product_variant_sale_price/models/product_product.py#L53

Added line #L53 was not covered by tests
return res


Expand Down Expand Up @@ -104,5 +110,12 @@ def _compute_product_price_extra(self):
"""the sale.order.line module calculates the price_unit by adding
the value of price_extra and this can generate inconsistencies
if the field has old data stored."""
for product in self:
product.price_extra = 0.0
super()._compute_product_price_extra()
test_condition = not config["test_enable"] or (
config["test_enable"]
and self.env.context.get("test_product_variant_sale_price")
)
if test_condition:
for product in self:
product.price_extra = 0.0

Check warning on line 120 in product_variant_sale_price/models/product_product.py

View check run for this annotation

Codecov / codecov/patch

product_variant_sale_price/models/product_product.py#L120

Added line #L120 was not covered by tests
return
3 changes: 3 additions & 0 deletions product_variant_sale_price/tests/test_product_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class TestProductVariantPrice(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(
context=dict(cls.env.context, test_test_product_variant_sale_price=True)
)
cls.template = cls.env["product.template"]
cls.product_product = cls.env["product.product"]
cls.attribute = cls.env["product.attribute"]
Expand Down
4 changes: 3 additions & 1 deletion sale_variant_configurator/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Copyright 2014-2016 Oihane Crucelaegui - AvanzOSC
# Copyright 2017 David Vidal <[email protected]>
# 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,"
Expand Down
141 changes: 23 additions & 118 deletions sale_variant_configurator/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# © 2014-2016 Oihane Crucelaegui - AvanzOSC
# © 2015-2016 Pedro M. Baeza <[email protected]>
# Copyright 2024 Tecnativa - Carolina Fernandez
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo import api, fields, models
Expand All @@ -8,19 +9,20 @@
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()

Check warning on line 19 in sale_variant_configurator/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_variant_configurator/models/sale_order.py#L18-L19

Added lines #L18 - L19 were not covered by tests


class SaleOrderLine(models.Model):
_inherit = ["sale.order.line", "product.configurator"]
_name = "sale.order.line"
_partner_id_field = "order_partner_id"

product_tmpl_id = fields.Many2one(
store=True,
Expand Down Expand Up @@ -48,127 +50,30 @@ 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
return super().create(vals)
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"])

Check warning on line 60 in sale_variant_configurator/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_variant_configurator/models/sale_order.py#L60

Added line #L60 was not covered by tests
if order.state == "sale":
line = self.new(vals)
product = line.create_variant_if_needed()
vals["product_id"] = product.id

Check warning on line 64 in sale_variant_configurator/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_variant_configurator/models/sale_order.py#L62-L64

Added lines #L62 - L64 were not covered by tests
return super().create(vals_list)

@api.onchange("product_tmpl_id")
def _onchange_product_tmpl_id_configurator(self):
res = super()._onchange_product_tmpl_id_configurator()
if self.product_tmpl_id.attribute_line_ids:
domain = res.setdefault("domain", {})
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,
)[
self.order_id.pricelist_id.id
]
# Update taxes
fpos = (
self.order_id.fiscal_position_id
or self.order_id.partner_id.property_account_position_id
)
# If company_id is set, always filter taxes by the company
taxes = self.product_tmpl_id.taxes_id.filtered(
lambda r: not self.company_id or r.company_id == self.company_id
)
self.tax_id = fpos.map_tax(taxes) if fpos else taxes
product_tmpl = self.product_tmpl_id.with_context(
lang=self.order_id.partner_id.lang,
partner=self.order_id.partner_id,
quantity=self.product_uom_qty,
date=self.order_id.date_order,
pricelist=self.order_id.pricelist_id.id,
uom=self.product_uom.id,
@api.model
def _get_product_description(self, template, product, product_attributes):
res = super()._get_product_description(
template=template, product=product, product_attributes=product_attributes
)
# product_configurator methods don't take into account this description
if product_tmpl.description_sale:
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.taxes_id,
self.tax_id,
)
return res

@api.onchange("product_id")
def product_id_change(self):
"""Call again the configurator onchange after this main onchange
for making sure the SO line description is correct.
It also puts the proper lang in context for getting the product and
attributes in the customer language.
"""
obj = self.with_context(lang=self.order_id.partner_id.lang)
res = super(SaleOrderLine, obj).product_id_change()
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)
product = self.product_id.with_context(lang=self.order_partner_id.lang)
if product.description_sale:
self.name = (self.name or "") + "\n" + product.description_sale
res = (res or "") + "\n" + product.description_sale
return res

def _update_price_configurator(self):
"""If there are enough data (template, pricelist & partner), check new
price and update line if different.
"""
self.ensure_one()
if (
not self.product_tmpl_id
or not self.order_id.pricelist_id
or not self.order_id.partner_id
):
return
product_tmpl = self.product_tmpl_id.with_context(
lang=self.order_id.partner_id.lang,
partner=self.order_id.partner_id,
quantity=self.product_uom_qty,
date_order=self.order_id.date_order,
pricelist=self.order_id.pricelist_id.id,
uom=self.product_uom.id,
fiscal_position=self.env.context.get("fiscal_position"),
)
price = self.env["account.tax"]._fix_tax_included_price(
self.price_extra + self._get_display_price(product_tmpl),
product_tmpl.taxes_id,
self.tax_id,
)
if self.price_unit != price:
self.price_unit = price

@api.onchange("product_attribute_ids")
def _onchange_product_attribute_ids_configurator(self):
"""Update price for having into account possible extra prices.
It also puts the proper lang in context for getting the product and
attributes in the customer language.
"""
obj = self.with_context(lang=self.order_id.partner_id.lang)
res = super(SaleOrderLine, obj)._onchange_product_attribute_ids_configurator()
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:
self._update_price_configurator()
return res
@api.depends("product_attribute_ids")
def _compute_price_unit(self):
return super()._compute_price_unit()
Loading

0 comments on commit 511e6a6

Please sign in to comment.