From b963308678e7f6f9ba4e94ad6ca4059fb2dfc7fe Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Fri, 22 Oct 2021 12:45:05 +0200 Subject: [PATCH] [ADD] 12.0 stock_mts_mto_rule_mrp This module allows using the split_procurement action on Manufacturing routes, especially when using 2-step manufacturing --- stock_mts_mto_rule_mrp/README.rst | 57 ++++++++ stock_mts_mto_rule_mrp/__init__.py | 1 + stock_mts_mto_rule_mrp/__manifest__.py | 19 +++ stock_mts_mto_rule_mrp/models/__init__.py | 1 + .../models/mrp_production.py | 65 +++++++++ stock_mts_mto_rule_mrp/readme/CONFIGURE.rst | 14 ++ .../readme/CONTRIBUTORS.rst | 1 + stock_mts_mto_rule_mrp/readme/DESCRIPTION.rst | 5 + stock_mts_mto_rule_mrp/readme/HISTORY.rst | 6 + stock_mts_mto_rule_mrp/readme/USAGE.rst | 1 + stock_mts_mto_rule_mrp/tests/__init__.py | 1 + .../tests/test_mto_mts_route_mrp.py | 125 ++++++++++++++++++ 12 files changed, 296 insertions(+) create mode 100644 stock_mts_mto_rule_mrp/README.rst create mode 100644 stock_mts_mto_rule_mrp/__init__.py create mode 100644 stock_mts_mto_rule_mrp/__manifest__.py create mode 100644 stock_mts_mto_rule_mrp/models/__init__.py create mode 100644 stock_mts_mto_rule_mrp/models/mrp_production.py create mode 100644 stock_mts_mto_rule_mrp/readme/CONFIGURE.rst create mode 100644 stock_mts_mto_rule_mrp/readme/CONTRIBUTORS.rst create mode 100644 stock_mts_mto_rule_mrp/readme/DESCRIPTION.rst create mode 100644 stock_mts_mto_rule_mrp/readme/HISTORY.rst create mode 100644 stock_mts_mto_rule_mrp/readme/USAGE.rst create mode 100644 stock_mts_mto_rule_mrp/tests/__init__.py create mode 100644 stock_mts_mto_rule_mrp/tests/test_mto_mts_route_mrp.py diff --git a/stock_mts_mto_rule_mrp/README.rst b/stock_mts_mto_rule_mrp/README.rst new file mode 100644 index 000000000..3e081ebb6 --- /dev/null +++ b/stock_mts_mto_rule_mrp/README.rst @@ -0,0 +1,57 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +========================== +Stock MTS+MTO Rule for MRP +========================== + +This module adds support on mrp manufacturing orders for the MTS+MTO rule from stock_mts_mto_rule module (from https://github.com/OCA/stock-logistics-warehouse) + +Configuration +============= + +XXX + +Usage +===== + +XXX + +Known issues +============ + +XXX + +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 smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Alexandre Fayolle + +Do not contact contributors directly about support or help with technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/stock_mts_mto_rule_mrp/__init__.py b/stock_mts_mto_rule_mrp/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_mts_mto_rule_mrp/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_mts_mto_rule_mrp/__manifest__.py b/stock_mts_mto_rule_mrp/__manifest__.py new file mode 100644 index 000000000..c9ee5c08b --- /dev/null +++ b/stock_mts_mto_rule_mrp/__manifest__.py @@ -0,0 +1,19 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Stock MTS+MTO Rule for manufacturing', + 'summary': 'Add support for MTS+MTO route on manufacturing', + 'version': '12.0.1.0.0', + 'development_status': 'Alpha', + 'category': 'Warehouse', + 'website': 'https://github.com/OCA/manufacture', + 'author': 'Camptocamp,Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'application': False, + 'installable': True, + 'depends': [ + 'stock_mts_mto_rule', + 'mrp' + ], + 'auto_install': True, +} diff --git a/stock_mts_mto_rule_mrp/models/__init__.py b/stock_mts_mto_rule_mrp/models/__init__.py new file mode 100644 index 000000000..a9e5f13e4 --- /dev/null +++ b/stock_mts_mto_rule_mrp/models/__init__.py @@ -0,0 +1 @@ +from . import mrp_production diff --git a/stock_mts_mto_rule_mrp/models/mrp_production.py b/stock_mts_mto_rule_mrp/models/mrp_production.py new file mode 100644 index 000000000..2f1cf254d --- /dev/null +++ b/stock_mts_mto_rule_mrp/models/mrp_production.py @@ -0,0 +1,65 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models +from odoo.tools import float_compare, float_is_zero + + +class MrpProduction(models.Model): + _inherit = "mrp.production" + + @api.multi + def _adjust_procure_method(self): + # we call super()._adjust_procure_method first and then come back to + # check for split_procurement rules on the move. + super()._adjust_procure_method() + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + for move in self.move_raw_ids: + product = move.product_id + routes = ( + product.route_ids + + product.route_from_categ_ids + + move.warehouse_id.route_ids + ) + # find if we have a "split_procurement" rule in the routes + split_rule = self.env["stock.rule"].search( + [ + ("route_id", "in", [x.id for x in routes]), + ("location_src_id", "=", move.location_id.id), + ("location_id", "=", move.location_dest_id.id), + ("action", "=", "split_procurement") + ], + limit=1 + ) + if split_rule: + product_qty = move.product_uom_qty + uom = move.product_id.uom_id + needed_qty = split_rule.get_mto_qty_to_order( + move.product_id, product_qty, uom, values=None + ) + if float_is_zero(needed_qty, precision_digits=precision): + # no additional product -> MTS + move.procure_method = split_rule.mts_rule_id.procure_method + elif float_compare(needed_qty, product_qty, + precision_digits=precision) == 0.0: + # no stock -> MTO + move.procure_method = split_rule.mto_rule_id.procure_method + else: + # partial MTS, remainder MTO + mts_qty = product_qty - needed_qty + mts_rule = split_rule.mts_rule_id + mto_rule = split_rule.mto_rule_id + move.update( + { + "procure_method": mts_rule.procure_method, + "product_uom_qty": mts_qty + } + ) + # create the MTO move, attached to same MO + move.copy( + default={ + "procure_method": mto_rule.procure_method, + "product_uom_qty": needed_qty + } + ) diff --git a/stock_mts_mto_rule_mrp/readme/CONFIGURE.rst b/stock_mts_mto_rule_mrp/readme/CONFIGURE.rst new file mode 100644 index 000000000..f28ba4f6a --- /dev/null +++ b/stock_mts_mto_rule_mrp/readme/CONFIGURE.rst @@ -0,0 +1,14 @@ +To use the MTS + MTO rules for manufacturing on a warehouse: + +1. configure the warehouse to use "Pick components and then manufacture (2 steps)" +2. Find the route "Pick components and then manufacture" of the + warehouse. There should be 2 rules: one to pull products from Stock and + bring it to PreProduction (using MTS), and one to pull products from + PreProduction and to bring it to Production (MTO). You need to create 2 new + rules: + + * one route to pull products from PreProduction and bring it to Production (MTS), using the Manufacturing operation type + * one route to choose between MTS and MTO, with Operation Type: + Manufacturing, Source location PreProduction, Destination Production, MTS + rule: the one created in the previous step, MTO rule: the preexisting MTO + rule pulling from PreProduction to Production diff --git a/stock_mts_mto_rule_mrp/readme/CONTRIBUTORS.rst b/stock_mts_mto_rule_mrp/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..8785f72c0 --- /dev/null +++ b/stock_mts_mto_rule_mrp/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexandre Fayolle diff --git a/stock_mts_mto_rule_mrp/readme/DESCRIPTION.rst b/stock_mts_mto_rule_mrp/readme/DESCRIPTION.rst new file mode 100644 index 000000000..86eaa09e8 --- /dev/null +++ b/stock_mts_mto_rule_mrp/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module adds support on mrp manufacturing orders for the MTS+MTO rule from +stock_mts_mto_rule module (from https://github.com/OCA/stock-logistics-warehouse) + +You can then configure a pick before manufacture route which will only pick if +there is no stock left in the source location of the manufacturing order. diff --git a/stock_mts_mto_rule_mrp/readme/HISTORY.rst b/stock_mts_mto_rule_mrp/readme/HISTORY.rst new file mode 100644 index 000000000..084118837 --- /dev/null +++ b/stock_mts_mto_rule_mrp/readme/HISTORY.rst @@ -0,0 +1,6 @@ +12.0.1.0.0 (2021-10-22) +~~~~~~~~~~~~~~~~~~~~~~~ + +**Features** + +- Initial release diff --git a/stock_mts_mto_rule_mrp/readme/USAGE.rst b/stock_mts_mto_rule_mrp/readme/USAGE.rst new file mode 100644 index 000000000..591338a44 --- /dev/null +++ b/stock_mts_mto_rule_mrp/readme/USAGE.rst @@ -0,0 +1 @@ +Once the routes are configured, there is nothing special to do. diff --git a/stock_mts_mto_rule_mrp/tests/__init__.py b/stock_mts_mto_rule_mrp/tests/__init__.py new file mode 100644 index 000000000..6c17e1442 --- /dev/null +++ b/stock_mts_mto_rule_mrp/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mto_mts_route_mrp diff --git a/stock_mts_mto_rule_mrp/tests/test_mto_mts_route_mrp.py b/stock_mts_mto_rule_mrp/tests/test_mto_mts_route_mrp.py new file mode 100644 index 000000000..a6c9cd888 --- /dev/null +++ b/stock_mts_mto_rule_mrp/tests/test_mto_mts_route_mrp.py @@ -0,0 +1,125 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestMtoMtsRouteMRP(TransactionCase): + + def setUp(self): + super().setUp() + wh = self.env.ref("stock.warehouse0") + self.wh = wh + wh.manufacture_steps = "pbm" # 2 step manufacturing + route = self.env["stock.location.route"].search( + [("warehouse_ids", "=", wh.id), + ("name", "like", "% Pick components and then manufacture")] + ) + mto_rule = route.rule_ids.filtered( + lambda r: r.location_id.usage == "production" + ) + mto_rule.name = "%s MTO" % mto_rule.name + mts_rule = mto_rule.copy({"name": mto_rule.name.replace("MTO", "MTS"), + "procure_method": "make_to_stock"}) + # mts_mto_rule + self.env["stock.rule"].create( + { + "route_id": route.id, + "name": "choose MTS MTO", + "action": "split_procurement", + "mts_rule_id": mts_rule.id, + "mto_rule_id": mto_rule.id, + "picking_type_id": mts_rule.picking_type_id.id, + "location_id": mts_rule.location_id.id, + "location_src_id": mts_rule.location_src_id.id, + "warehouse_id": wh.id + } + ) + self.stock_location = wh.lot_stock_id + self.preprod_location = wh.pbm_loc_id + self.finished_product = self.env["product.product"].create( + { + "name": "finished_product", + "type": "product", + "sale_line_warn": "no-message", + } + ) + self.component_product = self.env["product.product"].create( + { + "name": "component", + "type": "product", + "sale_line_warn": "no-message", + } + ) + self.bom = self.env["mrp.bom"].create( + { + "product_id": self.finished_product.id, + "product_tmpl_id": self.finished_product.product_tmpl_id.id, + "bom_line_ids": [ + ( + 0, + 0, + { + "product_id": self.component_product.id, + "product_qty": 2, + "product_uom_id": self.component_product.uom_id.id + } + ) + ] + } + ) + + def _create_mo(self): + manufacture = self.wh.manu_type_id + return self.env["mrp.production"].create( + { + "product_id": self.finished_product.id, + "bom_id": self.bom.id, + "product_qty": 1, + "product_uom_id": self.finished_product.uom_id.id, + "picking_type_id": self.wh.manu_type_id.id, + "location_src_id": manufacture.default_location_src_id.id, + "location_dest_id": manufacture.default_location_dest_id.id, + } + ) + + def test_stock_in_stock(self): + """quantity in WH/Stock = 2 -> take 2 in stock to preprod""" + self.env["stock.quant"]._update_available_quantity( + self.component_product, self.stock_location, 2 + ) + mo = self._create_mo() + self.assertEqual(mo.move_raw_ids.procure_method, "make_to_order") + + def test_stock_in_preprod(self): + """quantity in WH/Preprod = 2 -> take 2 in preprod""" + self.env["stock.quant"]._update_available_quantity( + self.component_product, self.preprod_location, 2 + ) + mo = self._create_mo() + self.assertEqual(mo.move_raw_ids.procure_method, "make_to_stock") + + def test_stock_in_preprod_and_in_stock(self): + """quantity in WH/PreProd = 1 -> take 1 in preprod, + and fetch the rest from stock""" + self.env["stock.quant"]._update_available_quantity( + self.component_product, self.preprod_location, 1 + ) + mo = self._create_mo() + self.assertEqual( + len( + mo.move_raw_ids.filtered( + lambda r: r.procure_method == "make_to_stock" + ) + ), + 1 + ) + self.assertEqual( + len( + mo.move_raw_ids.filtered( + lambda r: r.procure_method == "make_to_order" + ) + ), + 1 + )