diff --git a/mrp_production_auto_validate/README.rst b/mrp_production_auto_validate/README.rst index a67ffad07d3..493399f7187 100644 --- a/mrp_production_auto_validate/README.rst +++ b/mrp_production_auto_validate/README.rst @@ -87,6 +87,14 @@ Contributors - Sébastien Alix - Simone Orsi +- Chau Le + +Other credits +------------- + +The development of this module has been financially supported by: + +- Camptocamp Maintainers ----------- diff --git a/mrp_production_auto_validate/__manifest__.py b/mrp_production_auto_validate/__manifest__.py index 6e23d3211aa..c7040c17f73 100644 --- a/mrp_production_auto_validate/__manifest__.py +++ b/mrp_production_auto_validate/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Manufacturing Order Auto-Validate", "summary": "Manufacturing Order Auto-Validation when components are picked", - "version": "14.0.1.0.0", + "version": "18.0.1.0.0", "license": "AGPL-3", "website": "https://github.com/OCA/manufacture", "author": "Camptocamp, Odoo Community Association (OCA)", diff --git a/mrp_production_auto_validate/models/mrp_bom.py b/mrp_production_auto_validate/models/mrp_bom.py index e199cfb14d9..4748f2b1b2b 100644 --- a/mrp_production_auto_validate/models/mrp_bom.py +++ b/mrp_production_auto_validate/models/mrp_bom.py @@ -1,7 +1,7 @@ # Copyright 2022 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import _, api, fields, models +from odoo import api, fields, models class MrpBom(models.Model): @@ -15,24 +15,9 @@ class MrpBom(models.Model): "This behavior is available only if the warehouse is configured " "with 2 or 3 steps." ), - default=False, - ) - mo_auto_validation_warning = fields.Char( - string="Order Auto Validation (warning)", - compute="_compute_mo_auto_validation_warning", ) @api.onchange("type") def onchange_type_auto_validation(self): if self.type != "normal": - self.mo_auto_validation = self.mo_auto_validation_warning = False - - @api.depends("mo_auto_validation") - def _compute_mo_auto_validation_warning(self): - for bom in self: - bom.mo_auto_validation_warning = False - if bom.mo_auto_validation: - bom.mo_auto_validation_warning = _( - "The Quantity To Produce of an order is now " - "restricted to the BoM Quantity." - ) + self.mo_auto_validation = False diff --git a/mrp_production_auto_validate/models/mrp_production.py b/mrp_production_auto_validate/models/mrp_production.py index 7e46ae83b32..40c8c48e17d 100644 --- a/mrp_production_auto_validate/models/mrp_production.py +++ b/mrp_production_auto_validate/models/mrp_production.py @@ -3,7 +3,7 @@ import logging -from odoo import _, api, exceptions, fields, models, tools +from odoo import api, exceptions, fields, models, tools _logger = logging.getLogger(__name__) @@ -12,36 +12,38 @@ class MrpProduction(models.Model): _inherit = "mrp.production" auto_validate = fields.Boolean( - string="Auto Validate", compute="_compute_auto_validate", store=True, - states={"draft": [("readonly", False)]}, ) @api.constrains("bom_id", "auto_validate", "product_qty") def check_bom_auto_validate(self): + bypass_check = self.env.context.get("disable_check_mo_auto_validate") + if bypass_check: + return + for mo in self: - # FIXME: Handle different UOM between BOM and MO - qty_ok = ( - tools.float_compare( - mo.product_qty, - mo.bom_id.product_qty, - precision_rounding=mo.product_uom_id.rounding, + if mo.bom_id: + qty_in_mo_uom = mo.bom_id.product_uom_id._compute_quantity( + mo.bom_id.product_qty, mo.product_uom_id ) - == 0 - ) - bypass_check = self.env.context.get("disable_check_mo_auto_validate") - if bypass_check: - return - if mo.bom_id and mo.auto_validate and not qty_ok: - raise exceptions.ValidationError( - _( - "The quantity to produce is restricted to {qty} " - "as the BoM is configured with the " - "'Order Auto Validation' option." - ).format(qty=mo.bom_id.product_qty) + + qty_ok = tools.float_is_zero( + mo.product_qty - qty_in_mo_uom, + precision_rounding=mo.product_uom_id.rounding, ) + if mo.auto_validate and not qty_ok: + raise exceptions.ValidationError( + self.env._( + "The quantity to produce is restricted to %(qty)s %(uom)s " + "as the BoM is configured with the " + "'Order Auto Validation' option.", + qty=mo.bom_id.product_qty, + uom=mo.bom_id.product_uom_id.name, + ) + ) + @api.depends("bom_id.mo_auto_validation", "state") def _compute_auto_validate(self): for prod in self: @@ -79,12 +81,13 @@ def handle_mark_done_result(self, res): return True return handler(res) - def _handle_wiz_mrp_immediate_production(self, action): + def _handle_wiz_mrp_batch_produce(self, action): wiz_model = self.env[action["res_model"]].with_context( **action.get("context", {}) ) wiz = wiz_model.create({}) - return wiz.process() + wiz.action_generate_production_text() + return wiz.action_done() def _handle_wiz_mrp_production_backorder(self, action): wiz_model = self.env[action["res_model"]].with_context( @@ -153,15 +156,15 @@ def adapt_values_qty_for_auto_validation(self, values_list): procure_qty = values.get("product_qty") values["product_qty"] = bom_qty values["product_uom_id"] = bom_uom.id - msg = _( - "Quantity in procurement (%s %s) was increased to %s %s due to auto " + msg = self.env._( + "Quantity in procurement (%(product_qty)s %(product_uom_name)s) " + "was increased to %(bom_qty)s %(bom_uom_name)s due to auto " "validation feature preventing to create an MO with a different " - "qty than defined on the BOM." - ) % ( - procure_qty, - create_uom.display_name, - bom_qty, - bom_uom.display_name, + "qty than defined on the BOM.", + product_qty=procure_qty, + product_uom_name=create_uom.display_name, + bom_qty=bom_qty, + bom_uom_name=bom_uom.display_name, ) messages_to_post[len(new_values_list)] = msg new_values_list.append(values) @@ -175,16 +178,16 @@ def adapt_values_qty_for_auto_validation(self, values_list): new_values = values.copy() new_values["product_qty"] = bom_qty new_values["product_uom_id"] = bom_uom.id - msg = _( - "Quantity in procurement (%s %s) was split to multiple production " - "orders of %s %s due to auto validation feature preventing to " - "set a quantity to produce different than the quantity defined " - "on the Bill of Materials." - ) % ( - values.get("product_qty"), - create_uom.display_name, - bom_qty, - bom_uom.display_name, + msg = self.env._( + "Quantity in procurement (%(product_qty)s %(product_uom_name)s) " + "was split to multiple production orders of %(bom_qty)s " + "%(bom_uom_name)s due to auto validation feature preventing to set " + "a quantity to produce different than the quantity defined " + "on the Bill of Materials.", + product_qty=values.get("product_qty"), + product_uom_name=create_uom.display_name, + bom_qty=bom_qty, + bom_uom_name=bom_uom.display_name, ) messages_to_post[len(new_values_list)] = msg new_values_list.append(new_values) diff --git a/mrp_production_auto_validate/models/stock_picking.py b/mrp_production_auto_validate/models/stock_picking.py index 47c5b9c2734..60d12e91210 100644 --- a/mrp_production_auto_validate/models/stock_picking.py +++ b/mrp_production_auto_validate/models/stock_picking.py @@ -11,7 +11,7 @@ def _get_manufacturing_orders(self, states=None): self.ensure_one() if states is None: states = ("confirmed", "progress") - return self.move_lines.move_dest_ids.raw_material_production_id.filtered( + return self.move_ids.move_dest_ids.raw_material_production_id.filtered( lambda o: o.state in states ) diff --git a/mrp_production_auto_validate/readme/CONTRIBUTORS.md b/mrp_production_auto_validate/readme/CONTRIBUTORS.md index 3cd02954267..5a0f40f76fd 100644 --- a/mrp_production_auto_validate/readme/CONTRIBUTORS.md +++ b/mrp_production_auto_validate/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ - Sébastien Alix \<\> - Simone Orsi \<\> +- Chau Le \<\> diff --git a/mrp_production_auto_validate/readme/CREDITS.md b/mrp_production_auto_validate/readme/CREDITS.md new file mode 100644 index 00000000000..705d3b30cad --- /dev/null +++ b/mrp_production_auto_validate/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- Camptocamp diff --git a/mrp_production_auto_validate/static/description/index.html b/mrp_production_auto_validate/static/description/index.html index 7635a70d661..b9529e1b53b 100644 --- a/mrp_production_auto_validate/static/description/index.html +++ b/mrp_production_auto_validate/static/description/index.html @@ -386,7 +386,8 @@

Manufacturing Order Auto-Validate

  • Credits
  • @@ -442,10 +443,18 @@

    Contributors

    + +
    +

    Other credits

    +

    The development of this module has been financially supported by:

    +
      +
    • Camptocamp
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association diff --git a/mrp_production_auto_validate/tests/test_auto_validate.py b/mrp_production_auto_validate/tests/test_auto_validate.py index 1a59dc126fd..1c82647e886 100644 --- a/mrp_production_auto_validate/tests/test_auto_validate.py +++ b/mrp_production_auto_validate/tests/test_auto_validate.py @@ -2,14 +2,15 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo.exceptions import ValidationError -from odoo.tests.common import Form, SavepointCase +from odoo.tests import Form +from odoo.addons.base.tests.common import BaseCommon -class TestManufacturingOrderAutoValidate(SavepointCase): + +class TestManufacturingOrderAutoValidate(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) # Configure the WH to manufacture in at least two steps to get a # "pick components" transfer operation cls.wh = cls.env.ref("stock.warehouse0") @@ -48,33 +49,29 @@ def _create_manufacturing_order(cls, bom, product_qty=1): form.bom_id = bom form.product_qty = product_qty order = form.save() - order.invalidate_cache() + order.invalidate_model() return order @classmethod def _validate_picking(cls, picking, moves=None): """Validate a stock transfer. - `moves` can be set with a list of tuples [(move, quantity_done)] to + `moves` can be set with a list of tuples [(move, quantity)] to process the transfer partially. """ if moves is None: moves = [] - for move in picking.move_lines: + for move in picking.move_ids: # Try to match a move to set a given qty for move2, qty_done in moves: if move == move2: - move.quantity_done = qty_done + move.quantity = qty_done break else: - move.quantity_done = move.product_uom_qty + move.quantity = move.product_uom_qty + move.picked = True picking._action_done() - def test_bom_alert(self): - self.assertIn( - "restricted to the BoM Quantity", self.bom.mo_auto_validation_warning - ) - def test_get_manufacturing_orders_pbm(self): """Get the MO from transfers in a 2 steps configuration.""" # WH already configured as 'Pick components and then manufacture (2 steps)' @@ -91,10 +88,12 @@ def test_get_manufacturing_orders_pbm_sam(self): picking_pick = order.picking_ids.filtered( lambda o: "Pick" in o.picking_type_id.name ) + self.assertEqual(picking_pick._get_manufacturing_orders(), order) + picking_pick.action_assign() + self._validate_picking(picking_pick) picking_store = order.picking_ids.filtered( lambda o: "Store" in o.picking_type_id.name ) - self.assertEqual(picking_pick._get_manufacturing_orders(), order) self.assertFalse(picking_store._get_manufacturing_orders()) def test_create_order_too_much_qty_to_produce(self): @@ -154,9 +153,9 @@ def test_auto_validate_with_not_enough_components(self): picking = order.picking_ids picking.action_assign() self.assertEqual(picking.state, "assigned") - self._validate_picking(picking, moves=[(picking.move_lines, 1)]) + self._validate_picking(picking, moves=[(picking.move_ids, 1)]) self.assertEqual(picking.state, "done") - self.assertEqual(picking.backorder_ids.move_lines.product_uom_qty, 3) + self.assertEqual(picking.backorder_ids.move_ids.product_uom_qty, 3) # Check that no MO gets validated in the process order_done = picking._get_manufacturing_orders(states=("done",)) self.assertFalse(order_done) diff --git a/mrp_production_auto_validate/views/mrp_bom.xml b/mrp_production_auto_validate/views/mrp_bom.xml index 9af8eb1f5a6..6469b3b86fb 100644 --- a/mrp_production_auto_validate/views/mrp_bom.xml +++ b/mrp_production_auto_validate/views/mrp_bom.xml @@ -8,18 +8,16 @@ - - + diff --git a/mrp_production_auto_validate/wizards/__init__.py b/mrp_production_auto_validate/wizards/__init__.py index 7a6fec6a96b..a521c7c6f44 100644 --- a/mrp_production_auto_validate/wizards/__init__.py +++ b/mrp_production_auto_validate/wizards/__init__.py @@ -1 +1,2 @@ +from . import mrp_batch_produce from . import mrp_production_backorder diff --git a/mrp_production_auto_validate/wizards/mrp_batch_produce.py b/mrp_production_auto_validate/wizards/mrp_batch_produce.py new file mode 100644 index 00000000000..0b3311bdf7b --- /dev/null +++ b/mrp_production_auto_validate/wizards/mrp_batch_produce.py @@ -0,0 +1,14 @@ +# Copyright 2025 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class MrpBatchProduct(models.TransientModel): + _inherit = "mrp.batch.produce" + + def action_done(self): + # Bypass the 'auto_validate' constraint regarding qty to produce + # when creating a backorder. + self = self.with_context(disable_check_mo_auto_validate=True) + return super().action_done()