diff --git a/mrp_bom_assign_auto/README.rst b/mrp_bom_assign_auto/README.rst new file mode 100644 index 00000000000..2fed3bd9c55 --- /dev/null +++ b/mrp_bom_assign_auto/README.rst @@ -0,0 +1,79 @@ +=================== +MRP BOM Assign Auto +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:68c6ea3dbba98c56e84e26543b557d40d45679f4c0cc6cadf00d2c9205440810 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/15.0/mrp_bom_assign_auto + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-mrp_bom_assign_auto + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module checks BoM and picks the first one where all its components are +available. In case not found returns odoo one by default. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +- [Tecnativa](https://www.tecnativa.com): + - Eduardo Ezerouali + + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_assign_auto/__init__.py b/mrp_bom_assign_auto/__init__.py new file mode 100644 index 00000000000..faa1ac858dd --- /dev/null +++ b/mrp_bom_assign_auto/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2025 Tecnativa - Eduardo Ezerouali +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from . import models diff --git a/mrp_bom_assign_auto/__manifest__.py b/mrp_bom_assign_auto/__manifest__.py new file mode 100644 index 00000000000..7d2dbe8221c --- /dev/null +++ b/mrp_bom_assign_auto/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2025 Tecnativa - Eduardo Ezerouali +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "MRP BOM Assign Auto", + "version": "15.0.1.0.0", + "category": "Manufacturing", + "summary": "Auto select th first BoM that has all components available", + "website": "https://github.com/OCA/manufacture", + "author": "Tecnativa," "Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": ["mrp"], + "data": [ + "views/res_config_settings_views.xml", + ], +} diff --git a/mrp_bom_assign_auto/models/__init__.py b/mrp_bom_assign_auto/models/__init__.py new file mode 100644 index 00000000000..32c98db110e --- /dev/null +++ b/mrp_bom_assign_auto/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Tecnativa - Eduardo Ezerouali +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from . import mrp_bom +from . import res_config_settings diff --git a/mrp_bom_assign_auto/models/mrp_bom.py b/mrp_bom_assign_auto/models/mrp_bom.py new file mode 100644 index 00000000000..2fb9ad5431c --- /dev/null +++ b/mrp_bom_assign_auto/models/mrp_bom.py @@ -0,0 +1,70 @@ +# Copyright 2025 Tecnativa - Eduardo Ezerouali +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from collections import defaultdict + +from odoo import api, models +from odoo.tools import config, float_compare + + +class MrpBom(models.Model): + _inherit = "mrp.bom" + + @api.model + def _bom_find(self, products, picking_type=None, company_id=False, bom_type=False): + test_condition = config["test_enable"] and not self.env.context.get( + "test_mrp_bom_assign_auto" + ) + if test_condition or self.env.context.get("no_mrp_bom_assign_auto"): + return super()._bom_find( + products, + picking_type=picking_type, + company_id=company_id, + bom_type=bom_type, + ) + params = self.env["ir.config_parameter"].sudo() + bom_available = defaultdict(lambda: self.env["mrp.bom"]) + domain = self._bom_find_domain( + products, + picking_type=picking_type, + company_id=company_id, + bom_type=bom_type, + ) + boms = self.search(domain, order="sequence, product_id, id") + bom_products = list(set(boms.bom_line_ids.mapped("product_id").ids)) + type_stock_check = params.get_param( + "mrp_bom_assign_auto.bom_stock_check", "free_qty" + ) + product_available = ( + self.env["product.product"].browse(bom_products).read([type_stock_check]) + ) + product_available_map = { + rec["id"]: rec[type_stock_check] for rec in product_available + } + for bom in boms: + for line in bom.bom_line_ids: + required = line.product_qty + component = line.product_id + available = product_available_map.get(line.product_id.id) + fully_available = True + if ( + float_compare( + available, + required, + precision_rounding=component.uom_id.rounding, + ) + < 0 + ): + fully_available = False + break + if fully_available: + bom_available[products] = bom + break + if not any(bom_available.values()): + return super()._bom_find( + products, + picking_type=picking_type, + company_id=company_id, + bom_type=bom_type, + ) + return bom_available diff --git a/mrp_bom_assign_auto/models/res_config_settings.py b/mrp_bom_assign_auto/models/res_config_settings.py new file mode 100644 index 00000000000..53bf2309be5 --- /dev/null +++ b/mrp_bom_assign_auto/models/res_config_settings.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + """Add option for user to decide bom auto check by + virtual_available o free_qty""" + + _inherit = "res.config.settings" + + bom_stock_check = fields.Selection( + selection=[("virtual_available", "Vitual Available"), ("free_qty", "Free Qty")], + default="free_qty", + config_parameter="mrp_bom_assign_auto.bom_stock_check", + ) diff --git a/mrp_bom_assign_auto/readme/CONTRIBUTORS.rst b/mrp_bom_assign_auto/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..5d4dc32f186 --- /dev/null +++ b/mrp_bom_assign_auto/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +- [Tecnativa](https://www.tecnativa.com): + - Eduardo Ezerouali + diff --git a/mrp_bom_assign_auto/readme/DESCRIPTION.rst b/mrp_bom_assign_auto/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..acd4564ce6a --- /dev/null +++ b/mrp_bom_assign_auto/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module checks BoM and picks the first one where all its components are +available. In case not found returns odoo one by default. diff --git a/mrp_bom_assign_auto/static/description/index.html b/mrp_bom_assign_auto/static/description/index.html new file mode 100644 index 00000000000..c06d663a203 --- /dev/null +++ b/mrp_bom_assign_auto/static/description/index.html @@ -0,0 +1,425 @@ + + + + + +MRP BOM Assign Auto + + + +
+

MRP BOM Assign Auto

+ + +

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runboat

+

This module checks BoM and picks the first one where all its components are +available. In case not found returns odoo one by default.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/mrp_bom_assign_auto/tests/__init__.py b/mrp_bom_assign_auto/tests/__init__.py new file mode 100644 index 00000000000..f59e129f3e0 --- /dev/null +++ b/mrp_bom_assign_auto/tests/__init__.py @@ -0,0 +1 @@ +from . import test_bom_auto_assing diff --git a/mrp_bom_assign_auto/tests/test_bom_auto_assing.py b/mrp_bom_assign_auto/tests/test_bom_auto_assing.py new file mode 100644 index 00000000000..bcd34758dac --- /dev/null +++ b/mrp_bom_assign_auto/tests/test_bom_auto_assing.py @@ -0,0 +1,104 @@ +# Copyright 2025 Tecnativa - Eduardo Ezerouali +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.tests.common import TransactionCase + + +class TestMrpBomFind(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, test_mrp_bom_assign_auto=True)) + Bom = cls.env["mrp.bom"] + BomLine = cls.env["mrp.bom.line"] + Product = cls.env["product.product"] + Quant = cls.env["stock.quant"] + cls.product_a = Product.create( + { + "name": "Product A", + "type": "product", + "standard_price": 10, + } + ) + cls.product_b = Product.create( + { + "name": "Product B", + "type": "product", + "standard_price": 5, + } + ) + cls.product_c = Product.create( + { + "name": "Product B", + "type": "product", + "standard_price": 5, + } + ) + cls.bom_a = Bom.create( + { + "product_tmpl_id": cls.product_a.product_tmpl_id.id, + "product_qty": 1.0, + "type": "normal", + } + ) + cls.bom_b = Bom.create( + { + "product_tmpl_id": cls.product_a.product_tmpl_id.id, + "product_qty": 1.0, + "type": "normal", + } + ) + BomLine.create( + { + "bom_id": cls.bom_a.id, + "product_id": cls.product_b.id, + "product_qty": 5.0, + } + ) + BomLine.create( + { + "bom_id": cls.bom_b.id, + "product_id": cls.product_c.id, + "product_qty": 5.0, + } + ) + cls.stock_location = cls.env.ref("stock.stock_location_stock") + Quant.create( + { + "product_id": cls.product_b.id, + "location_id": cls.stock_location.id, + "quantity": 10.0, + } + ) + Quant.create( + { + "product_id": cls.product_c.id, + "location_id": cls.stock_location.id, + "quantity": 2.0, + } + ) + + def test_bom_find_with_sufficient_stock(self): + res = self.env["mrp.bom"]._bom_find(self.product_a) + self.assertIn(self.product_a, res.keys()) + self.assertEqual(res[self.product_a], self.bom_a) + + def test_bom_find_with_insufficient_stock(self): + # Chnage stock so it has to take second BoM + quant_b = self.env["stock.quant"].search( + [ + ("product_id", "=", self.product_b.id), + ("location_id", "=", self.stock_location.id), + ] + ) + quant_c = self.env["stock.quant"].search( + [ + ("product_id", "=", self.product_c.id), + ("location_id", "=", self.stock_location.id), + ] + ) + quant_b.write({"quantity": 2.0}) + quant_c.write({"quantity": 5.0}) + res = self.env["mrp.bom"]._bom_find(self.product_a) + self.assertIn(self.product_a, res.keys()) + self.assertEqual(res[self.product_a], self.bom_b) diff --git a/mrp_bom_assign_auto/views/res_config_settings_views.xml b/mrp_bom_assign_auto/views/res_config_settings_views.xml new file mode 100644 index 00000000000..b40c699fb3c --- /dev/null +++ b/mrp_bom_assign_auto/views/res_config_settings_views.xml @@ -0,0 +1,24 @@ + + + + res.config.settings.view.form + res.config.settings + + + + +
+
+
+
+
+
+
+
diff --git a/setup/mrp_bom_assign_auto/odoo/addons/mrp_bom_assign_auto b/setup/mrp_bom_assign_auto/odoo/addons/mrp_bom_assign_auto new file mode 120000 index 00000000000..0ea4947b406 --- /dev/null +++ b/setup/mrp_bom_assign_auto/odoo/addons/mrp_bom_assign_auto @@ -0,0 +1 @@ +../../../../mrp_bom_assign_auto \ No newline at end of file diff --git a/setup/mrp_bom_assign_auto/setup.py b/setup/mrp_bom_assign_auto/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/mrp_bom_assign_auto/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)