Allow to propagate lot from multiple BOM lines for variants

When defining a single BOM for a product template having multiple
product variants, we can have different components where the lot
number must be propagated for different product variants.

Therefore we need to allow to mark multiple BOM line with
propagate_lot_number, and to avoid complicating the check function
on the BOM lines, we ensure at the manufacturing order confirmation
that only a single component is set to propagate its lot number.
This commit is contained in:
Akim Juillerat
2023-01-03 18:27:53 +01:00
committed by Thierry Ducrest
parent ec00f94209
commit b114c80d27
5 changed files with 152 additions and 27 deletions

View File

@@ -39,16 +39,6 @@ class MrpBomLine(models.Model):
for line in self:
if not line.bom_id.lot_number_propagation:
continue
lines_to_propagate = line.bom_id.bom_line_ids.filtered(
lambda o: o.propagate_lot_number
)
if len(lines_to_propagate) > 1:
raise ValidationError(
_(
"Only one BoM line can propagate its lot/serial number "
"to the finished product."
)
)
if line.propagate_lot_number and line.product_id.tracking != "serial":
raise ValidationError(
_(

View File

@@ -70,9 +70,31 @@ class MrpProduction(models.Model):
def _set_lot_number_propagation_data_from_bom(self):
"""Copy information from BoM to the manufacturing order."""
for order in self:
order.is_lot_number_propagated = order.bom_id.lot_number_propagation
for move in order.move_raw_ids:
move.propagate_lot_number = move.bom_line_id.propagate_lot_number
propagate_lot = order.bom_id.lot_number_propagation
if not propagate_lot:
continue
order.is_lot_number_propagated = propagate_lot
propagate_move = order.move_raw_ids.filtered(
lambda m: m.bom_line_id.propagate_lot_number
)
if not propagate_move:
raise UserError(
_(
"Bill of material is marked for lot number propagation, but "
"there are no components propagating lot number. "
"Please check BOM configuration."
)
)
elif len(propagate_move) > 1:
raise UserError(
_(
"Bill of material is marked for lot number propagation, but "
"there are multiple components propagating lot number. "
"Please check BOM configuration."
)
)
else:
propagate_move.propagate_lot_number = True
def _post_inventory(self, cancel_backorder=False):
self._create_and_assign_propagated_lot_number()

View File

@@ -5,7 +5,7 @@ import random
import string
from odoo import fields
from odoo.tests import common
from odoo.tests import Form, common
class Common(common.TransactionCase):
@@ -17,12 +17,19 @@ class Common(common.TransactionCase):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.bom = cls.env.ref("mrp.mrp_bom_desk")
cls.bom_product_template = cls.env.ref(
"mrp.product_product_computer_desk_product_template"
)
cls.bom_product_product = cls.env.ref("mrp.product_product_computer_desk")
cls.product_tracked_by_lot = cls.env.ref(
"mrp.product_product_computer_desk_leg"
)
cls.product_tracked_by_sn = cls.env.ref(
"mrp.product_product_computer_desk_head"
)
cls.product_template_tracked_by_sn = cls.env.ref(
"mrp.product_product_computer_desk_head_product_template"
)
cls.line_tracked_by_lot = cls.bom.bom_line_ids.filtered(
lambda o: o.product_id == cls.product_tracked_by_lot
)
@@ -90,3 +97,65 @@ class Common(common.TransactionCase):
lambda q: q.location_id.parent_path in location.parent_path
)
return quants
@classmethod
def _add_color_and_legs_variants(cls, product_template):
color_attribute = cls.env.ref("product.product_attribute_2")
color_att_value_white = cls.env.ref("product.product_attribute_value_3")
color_att_value_black = cls.env.ref("product.product_attribute_value_4")
legs_attribute = cls.env.ref("product.product_attribute_1")
legs_att_value_steel = cls.env.ref("product.product_attribute_value_1")
legs_att_value_alu = cls.env.ref("product.product_attribute_value_2")
cls._add_variants(
product_template,
{
color_attribute: [color_att_value_white, color_att_value_black],
legs_attribute: [legs_att_value_steel, legs_att_value_alu],
},
)
@classmethod
def _add_variants(cls, product_template, attribute_values_dict):
for attribute, att_values_list in attribute_values_dict.items():
cls.env["product.template.attribute.line"].create(
{
"product_tmpl_id": product_template.id,
"attribute_id": attribute.id,
"value_ids": [
fields.Command.set([att_val.id for att_val in att_values_list])
],
}
)
@classmethod
def _create_bom_with_variants(cls):
attribute_values_dict = {
att_val.product_attribute_value_id.name: att_val.id
for att_val in cls.env["product.template.attribute.value"].search(
[("product_tmpl_id", "=", cls.bom_product_template.id)]
)
}
new_bom_form = Form(cls.env["mrp.bom"])
new_bom_form.product_tmpl_id = cls.bom_product_template
new_bom = new_bom_form.save()
bom_line_create_values = []
for product in cls.product_template_tracked_by_sn.product_variant_ids:
create_values = {"bom_id": new_bom.id}
create_values["product_id"] = product.id
att_values_commands = []
for att_value in product.product_template_attribute_value_ids:
att_values_commands.append(
fields.Command.link(attribute_values_dict[att_value.name])
)
create_values[
"bom_product_template_attribute_value_ids"
] = att_values_commands
bom_line_create_values.append(create_values)
cls.env["mrp.bom.line"].create(bom_line_create_values)
new_bom_form = Form(new_bom)
new_bom_form.lot_number_propagation = True
for line_position, _bom_line in enumerate(new_bom.bom_line_ids):
new_bom_line_form = new_bom_form.bom_line_ids.edit(line_position)
new_bom_line_form.propagate_lot_number = True
new_bom_line_form.save()
return new_bom_form.save()

View File

@@ -13,17 +13,6 @@ class TestMrpBom(Common):
self.bom.product_tmpl_id.tracking = "none"
self.assertFalse(self.bom.display_lot_number_propagation)
def test_bom_line_check_propagate_lot_number_multi(self):
form = Form(self.bom)
form.lot_number_propagation = True
# Flag more than one line to propagate
for i in range(len(form.bom_line_ids)):
line_form = form.bom_line_ids.edit(i)
line_form.propagate_lot_number = True
line_form.save()
with self.assertRaisesRegex(ValidationError, "Only one BoM"):
form.save()
def test_bom_line_check_propagate_lot_number_not_tracked(self):
form = Form(self.bom)
form.lot_number_propagation = True

View File

@@ -2,6 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo.exceptions import UserError
from odoo.fields import Command
from odoo.tests.common import Form
from .common import Common
@@ -12,15 +13,24 @@ class TestMrpProduction(Common):
def setUpClass(cls):
super().setUpClass()
# Configure the BoM to propagate lot number
cls._configure_bom()
cls.order = cls._create_order(cls.bom_product_product, cls.bom)
@classmethod
def _configure_bom(cls):
with Form(cls.bom) as form:
form.lot_number_propagation = True
line_form = form.bom_line_ids.edit(0) # Line tracked by SN
line_form.propagate_lot_number = True
line_form.save()
form.save()
@classmethod
def _create_order(cls, product, bom):
with Form(cls.env["mrp.production"]) as form:
form.bom_id = cls.bom
cls.order = form.save()
form.product_id = product
form.bom_id = bom
return form.save()
def _set_qty_done(self, order):
for line in order.move_raw_ids.move_line_ids:
@@ -46,3 +56,48 @@ class TestMrpProduction(Common):
self._set_qty_done(self.order)
self.order.button_mark_done()
self.assertEqual(self.order.lot_producing_id.name, self.LOT_NAME)
def test_confirm_with_variant_ok(self):
self._add_color_and_legs_variants(self.bom_product_template)
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
new_bom = self._create_bom_with_variants()
self.assertTrue(new_bom.lot_number_propagation)
# As all variants must have a single component
# where lot must be propagated, there should not be any error
for product in self.bom_product_template.product_variant_ids:
new_order = self._create_order(product, new_bom)
new_order.action_confirm()
def test_confirm_with_variant_multiple(self):
self._add_color_and_legs_variants(self.bom_product_template)
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
new_bom = self._create_bom_with_variants()
# Remove application on variant for first bom line
# with this only the first variant of the product template
# will have a single component where lot must be propagated
new_bom.bom_line_ids[0].bom_product_template_attribute_value_ids = [
Command.clear()
]
for cnt, product in enumerate(self.bom_product_template.product_variant_ids):
new_order = self._create_order(product, new_bom)
if cnt == 0:
new_order.action_confirm()
else:
with self.assertRaisesRegex(UserError, "multiple components"):
new_order.action_confirm()
def test_confirm_with_variant_no(self):
self._add_color_and_legs_variants(self.bom_product_template)
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
new_bom = self._create_bom_with_variants()
# Remove first bom line
# with this the first variant of the product template
# will not have any component where lot must be propagated
new_bom.bom_line_ids[0].unlink()
for cnt, product in enumerate(self.bom_product_template.product_variant_ids):
new_order = self._create_order(product, new_bom)
if cnt == 0:
with self.assertRaisesRegex(UserError, "no component"):
new_order.action_confirm()
else:
new_order.action_confirm()