From 55eccc2e4be83d3e8a092eaba0b737b07dad2528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Sat, 22 Oct 2022 13:01:04 +0200 Subject: [PATCH] fixup! [ADD] mrp_lot_number_propagation --- mrp_lot_number_propagation/models/__init__.py | 2 + mrp_lot_number_propagation/models/mrp_bom.py | 16 ++++- .../models/mrp_bom_line.py | 34 +++++----- .../models/product_product.py | 13 ++++ .../models/product_template.py | 42 +++++++++++++ .../tests/test_mrp_bom.py | 63 ++++++++++++++++--- .../tests/test_mrp_production.py | 8 ++- 7 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 mrp_lot_number_propagation/models/product_product.py create mode 100644 mrp_lot_number_propagation/models/product_template.py diff --git a/mrp_lot_number_propagation/models/__init__.py b/mrp_lot_number_propagation/models/__init__.py index 6028eeffd..b92e6ec25 100644 --- a/mrp_lot_number_propagation/models/__init__.py +++ b/mrp_lot_number_propagation/models/__init__.py @@ -1,4 +1,6 @@ from . import mrp_bom from . import mrp_bom_line from . import mrp_production +from . import product_product +from . import product_template from . import stock_move diff --git a/mrp_lot_number_propagation/models/mrp_bom.py b/mrp_lot_number_propagation/models/mrp_bom.py index e9e574891..a084dcf12 100644 --- a/mrp_lot_number_propagation/models/mrp_bom.py +++ b/mrp_lot_number_propagation/models/mrp_bom.py @@ -1,7 +1,8 @@ # Copyright 2022 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import api, fields, models, tools +from odoo import _, api, fields, models, tools +from odoo.exceptions import ValidationError class MrpBom(models.Model): @@ -72,3 +73,16 @@ class MrpBom(models.Model): def onchange_display_lot_number_propagation(self): if not self.display_lot_number_propagation: self.lot_number_propagation = False + + @api.constrains("lot_number_propagation") + def _check_propagate_lot_number(self): + for bom in self: + if not bom.lot_number_propagation: + continue + if not bom.bom_line_ids.filtered("propagate_lot_number"): + raise ValidationError( + _( + "With 'Lot Number Propagation' enabled, a line has " + "to be configured with the 'Propagate Lot Number' option." + ) + ) diff --git a/mrp_lot_number_propagation/models/mrp_bom_line.py b/mrp_lot_number_propagation/models/mrp_bom_line.py index 093cf0979..b8b02a403 100644 --- a/mrp_lot_number_propagation/models/mrp_bom_line.py +++ b/mrp_lot_number_propagation/models/mrp_bom_line.py @@ -26,12 +26,7 @@ class MrpBomLine(models.Model): and line.bom_id.lot_number_propagation ) - @api.constrains( - "propagate_lot_number", - "bom_id.lot_number_propagation", - "product_id.tracking", - "bom_id.product_tmpl_id.tracking", - ) + @api.constrains("propagate_lot_number") def _check_propagate_lot_number(self): """ This function should check: @@ -42,21 +37,22 @@ class MrpBomLine(models.Model): tracking type as the finished product """ 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 line.bom_id.lot_number_propagation: - if len(lines_to_propagate) > 1: - raise ValidationError( - _( - "Only one BoM line can propagate its lot/serial number " - "to the finished product." - ) + 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( - _( - "Only components tracked by serial number can propagate " - "its lot/serial number to the finished product." - ) + ) + if line.propagate_lot_number and line.product_id.tracking != "serial": + raise ValidationError( + _( + "Only components tracked by serial number can propagate " + "its lot/serial number to the finished product." ) + ) diff --git a/mrp_lot_number_propagation/models/product_product.py b/mrp_lot_number_propagation/models/product_product.py new file mode 100644 index 000000000..69c72b19d --- /dev/null +++ b/mrp_lot_number_propagation/models/product_product.py @@ -0,0 +1,13 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import api, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.constrains("tracking") + def _check_bom_propagate_lot_number(self): + for product in self: + product.product_tmpl_id._check_bom_propagate_lot_number() diff --git a/mrp_lot_number_propagation/models/product_template.py b/mrp_lot_number_propagation/models/product_template.py new file mode 100644 index 000000000..7a42adbb5 --- /dev/null +++ b/mrp_lot_number_propagation/models/product_template.py @@ -0,0 +1,42 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + @api.constrains("tracking") + def _check_bom_propagate_lot_number(self): + """Block tracking type updates if the product is used by a BoM.""" + for product in self: + if product.tracking == "serial": + continue + # Check BoMs + for bom in product.bom_ids: + if bom.lot_number_propagation: + raise ValidationError( + _( + "A BoM propagating serial numbers requires " + "this product to be tracked as such." + ) + ) + # Check lines of BoMs + bom_lines = self.env["mrp.bom.line"].search( + [ + ("product_id", "in", product.product_variant_ids.ids), + ("propagate_lot_number", "=", True), + ("bom_id.lot_number_propagation", "=", True), + ] + ) + if bom_lines: + boms = "\n- ".join(bom_lines.mapped("bom_id.display_name")) + boms = "\n- " + boms + raise ValidationError( + _( + "This component is configured to propagate its " + "serial number in the following Bill of Materials:{boms}'" + ).format(boms=boms) + ) diff --git a/mrp_lot_number_propagation/tests/test_mrp_bom.py b/mrp_lot_number_propagation/tests/test_mrp_bom.py index 49f644aed..18a4604f7 100644 --- a/mrp_lot_number_propagation/tests/test_mrp_bom.py +++ b/mrp_lot_number_propagation/tests/test_mrp_bom.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo.exceptions import ValidationError +from odoo.tests.common import Form from .common import Common @@ -13,24 +14,70 @@ class TestMrpBom(Common): self.assertFalse(self.bom.display_lot_number_propagation) def test_bom_line_check_propagate_lot_number_multi(self): - self.bom.lot_number_propagation = True + 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"): - self.bom.bom_line_ids.write({"propagate_lot_number": True}) + form.save() def test_bom_line_check_propagate_lot_number_not_tracked(self): - self.bom.lot_number_propagation = True + form = Form(self.bom) + form.lot_number_propagation = True # Flag a line that can't be propagated + line_form = form.bom_line_ids.edit(2) # line without tracking + line_form.propagate_lot_number = True + line_form.save() with self.assertRaisesRegex(ValidationError, "Only components tracked"): - self.line_no_tracking.propagate_lot_number = True + form.save() def test_bom_line_check_propagate_lot_number_tracked_by_lot(self): - self.bom.lot_number_propagation = True + form = Form(self.bom) + form.lot_number_propagation = True # Flag a line tracked by lot (not SN) which is not supported + line_form = form.bom_line_ids.edit(1) + line_form.propagate_lot_number = True + line_form.save() with self.assertRaisesRegex(ValidationError, "Only components tracked"): - self.line_tracked_by_lot.propagate_lot_number = True + form.save() def test_bom_line_check_propagate_lot_number_same_tracking(self): - self.bom.lot_number_propagation = True + form = Form(self.bom) + form.lot_number_propagation = True # Flag a line whose tracking type is the same than the finished product - self.line_tracked_by_sn.propagate_lot_number = True + line_form = form.bom_line_ids.edit(0) + line_form.propagate_lot_number = True + line_form.save() + form.save() + + def test_bom_check_propagate_lot_number(self): + # Configure the BoM to propagate the lot/SN without enabling any line + with self.assertRaisesRegex(ValidationError, "a line has to be configured"): + self.bom.lot_number_propagation = True + + def test_reset_tracking_on_bom_product(self): + # Configure the BoM to propagate the lot/SN + with Form(self.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() + # Reset the tracking on the finished product + with self.assertRaisesRegex(ValidationError, "A BoM propagating"): + self.bom.product_tmpl_id.tracking = "none" + + def test_reset_tracking_on_bom_component(self): + # Configure the BoM to propagate the lot/SN + with Form(self.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() + # Reset the tracking on the component which propagates the SN + with self.assertRaisesRegex(ValidationError, "This component is"): + self.line_tracked_by_sn.product_id.tracking = "none" diff --git a/mrp_lot_number_propagation/tests/test_mrp_production.py b/mrp_lot_number_propagation/tests/test_mrp_production.py index fd16b1802..ffbad6446 100644 --- a/mrp_lot_number_propagation/tests/test_mrp_production.py +++ b/mrp_lot_number_propagation/tests/test_mrp_production.py @@ -12,8 +12,12 @@ class TestMrpProduction(Common): def setUpClass(cls): super().setUpClass() # Configure the BoM to propagate lot number - cls.bom.lot_number_propagation = True - cls.line_tracked_by_sn.propagate_lot_number = True + 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() with Form(cls.env["mrp.production"]) as form: form.bom_id = cls.bom cls.order = form.save()