mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
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:
committed by
Thierry Ducrest
parent
ec00f94209
commit
b114c80d27
@@ -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(
|
||||
_(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user