From 988cd51e6027893bc00faee3f01f82d32a6e5a8c Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Mon, 6 Jan 2025 16:36:46 +0100 Subject: [PATCH 1/2] Add stock_split_picking_kit --- .../odoo/addons/stock_split_picking_kit | 1 + setup/stock_split_picking_kit/setup.py | 6 + stock_split_picking_kit/README.rst | 79 ++++ stock_split_picking_kit/__init__.py | 1 + stock_split_picking_kit/__manifest__.py | 13 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 5 + .../static/description/index.html | 425 ++++++++++++++++++ stock_split_picking_kit/tests/__init__.py | 0 .../tests/test_stock_split_picking_kit.py | 245 ++++++++++ stock_split_picking_kit/wizards/__init__.py | 1 + .../wizards/stock_split_picking.py | 89 ++++ .../wizards/stock_split_picking.xml | 16 + 13 files changed, 882 insertions(+) create mode 120000 setup/stock_split_picking_kit/odoo/addons/stock_split_picking_kit create mode 100644 setup/stock_split_picking_kit/setup.py create mode 100644 stock_split_picking_kit/README.rst create mode 100644 stock_split_picking_kit/__init__.py create mode 100644 stock_split_picking_kit/__manifest__.py create mode 100644 stock_split_picking_kit/readme/CONTRIBUTORS.rst create mode 100644 stock_split_picking_kit/readme/DESCRIPTION.rst create mode 100644 stock_split_picking_kit/static/description/index.html create mode 100644 stock_split_picking_kit/tests/__init__.py create mode 100644 stock_split_picking_kit/tests/test_stock_split_picking_kit.py create mode 100644 stock_split_picking_kit/wizards/__init__.py create mode 100644 stock_split_picking_kit/wizards/stock_split_picking.py create mode 100644 stock_split_picking_kit/wizards/stock_split_picking.xml diff --git a/setup/stock_split_picking_kit/odoo/addons/stock_split_picking_kit b/setup/stock_split_picking_kit/odoo/addons/stock_split_picking_kit new file mode 120000 index 000000000000..291487af6aec --- /dev/null +++ b/setup/stock_split_picking_kit/odoo/addons/stock_split_picking_kit @@ -0,0 +1 @@ +../../../../stock_split_picking_kit \ No newline at end of file diff --git a/setup/stock_split_picking_kit/setup.py b/setup/stock_split_picking_kit/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_split_picking_kit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_split_picking_kit/README.rst b/stock_split_picking_kit/README.rst new file mode 100644 index 000000000000..e773c0e725ea --- /dev/null +++ b/stock_split_picking_kit/README.rst @@ -0,0 +1,79 @@ +======================= +Stock Split Picking Kit +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9798e0e24a436deb9d2c10ee1e971daae16ba54f022ffc888cc67cecb33a81ac + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fstock--logistics--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/14.0/stock_split_picking_kit + :alt: OCA/stock-logistics-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-workflow-14-0/stock-logistics-workflow-14-0-stock_split_picking_kit + :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/stock-logistics-workflow&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a new splitting mode to the `stock_split_picking` module. + +The new mode `Quantity of kits` allows to split a transfer by a specified number of kits. + +**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 +~~~~~~~ + +* Camptocamp +* + +Contributors +~~~~~~~~~~~~ + +* Thierry Ducrest + +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/stock-logistics-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_split_picking_kit/__init__.py b/stock_split_picking_kit/__init__.py new file mode 100644 index 000000000000..5cb1c49143f5 --- /dev/null +++ b/stock_split_picking_kit/__init__.py @@ -0,0 +1 @@ +from . import wizards diff --git a/stock_split_picking_kit/__manifest__.py b/stock_split_picking_kit/__manifest__.py new file mode 100644 index 000000000000..03ec521f0fd9 --- /dev/null +++ b/stock_split_picking_kit/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +{ + "name": "Stock Split Picking Kit", + "summary": "Split a picking by a number of kits.", + "version": "14.0.1.0.0", + "category": "Inventory", + "author": "Camptocamp, " "Odoo Community Association (OCA),", + "license": "AGPL-3", + "website": "https://github.com/OCA/stock-logistics-workflow", + "depends": ["stock_split_picking", "mrp"], + "data": ["wizards/stock_split_picking.xml"], +} diff --git a/stock_split_picking_kit/readme/CONTRIBUTORS.rst b/stock_split_picking_kit/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..0dd376faecb9 --- /dev/null +++ b/stock_split_picking_kit/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Thierry Ducrest diff --git a/stock_split_picking_kit/readme/DESCRIPTION.rst b/stock_split_picking_kit/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..88f59733683a --- /dev/null +++ b/stock_split_picking_kit/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module adds a new splitting mode to the `stock_split_picking` module. + +The new mode `Quantity of kits` allows to split a transfer by a specified number of kits. +A new field on the wizard also allows specify a sort order for the stock moves before +splitting the transfer. diff --git a/stock_split_picking_kit/static/description/index.html b/stock_split_picking_kit/static/description/index.html new file mode 100644 index 000000000000..767ace55ba17 --- /dev/null +++ b/stock_split_picking_kit/static/description/index.html @@ -0,0 +1,425 @@ + + + + + +Stock Split Picking Kit + + + +
+

Stock Split Picking Kit

+ + +

Beta License: AGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

+

This module adds a new splitting mode to the stock_split_picking module.

+

The new mode Quantity of kits allows to split a transfer by a specified number of kits.

+

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

+
    +
  • Camptocamp
  • +
  • +
+
+
+

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/stock-logistics-workflow project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_split_picking_kit/tests/__init__.py b/stock_split_picking_kit/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/stock_split_picking_kit/tests/test_stock_split_picking_kit.py b/stock_split_picking_kit/tests/test_stock_split_picking_kit.py new file mode 100644 index 000000000000..a16a08d1dc66 --- /dev/null +++ b/stock_split_picking_kit/tests/test_stock_split_picking_kit.py @@ -0,0 +1,245 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo.tests.common import Form, SavepointCase + + +class TestStockSplitPickingKit(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.src_location = cls.env.ref("stock.stock_location_stock") + cls.dest_location = cls.env.ref("stock.stock_location_customers") + cls.picking_type = cls.env.ref("stock.picking_type_out") + cls.product = cls.env["product.product"].create({"name": "Test product"}) + cls.partner = cls.env["res.partner"].create({"name": "Test partner"}) + cls.product_model = cls.env["product.product"] + + # TODO: How should we handle kit with multiple level + # cls.product_garden_furniture = cls.product_model.create( + # { + # "name": "GARDEN FURNITURE", + # "type": "product", + # "sale_ok": False, + # "purchase_ok": True, + # } + # ) + # cls.tmpl_garden_furniture = cls.product_garden_furniture.product_tmpl_id + # cls.product_garden_chair = cls.product_model.create( + # { + # "name": "GARDEN CHAIR", + # "type": "product", + # "sale_ok": True, + # "purchase_ok": True, + # } + # ) + cls.product_garden_table = cls.product_model.create( + { + "name": "GARDEN TABLE", + "type": "product", + "sale_ok": True, + "purchase_ok": True, + } + ) + cls.tmpl_garden_table = cls.product_garden_table.product_tmpl_id + cls.product_garden_table_top = cls.product_model.create( + { + "name": "GARDEN TABLE TOP", + "type": "product", + "sale_ok": True, + } + ) + cls.product_garden_table_leg = cls.product_model.create( + { + "name": "GARDEN TABLE LEG", + "type": "product", + "sale_ok": False, + "purchase_ok": False, + } + ) + cls.bom_model = cls.env["mrp.bom"] + cls.bom_garden_table = cls.bom_model.create( + { + "product_tmpl_id": cls.tmpl_garden_table.id, + "product_id": cls.product_garden_table.id, + "type": "phantom", + "bom_line_ids": [ + ( + 0, + 0, + { + "product_id": cls.product_garden_table_leg.id, + "product_qty": 4.0, + }, + ), + ( + 0, + 0, + { + "product_id": cls.product_garden_table_top.id, + "product_qty": 1.0, + }, + ), + ], + } + ) + # cls.bom_garden_furniture = cls.bom_model.create( + # { + # "product_tmpl_id": cls.tmpl_garden_furniture.id, + # "product_id": cls.product_garden_furniture.id, + # "type": "phantom", + # "bom_line_ids": [ + # ( + # 0, + # 0, + # {"product_id": cls.product_garden_chair.id, "product_qty": 4.0}, + # ), + # ( + # 0, + # 0, + # {"product_id": cls.product_garden_table.id, "product_qty": 1.0}, + # ), + # ], + # } + # ) + + @classmethod + def _create_picking(cls, lines): + picking_form = Form(cls.env["stock.picking"]) + picking_form.picking_type_id = cls.picking_type + picking_form.partner_id = cls.partner + for product, qty in lines: + with picking_form.move_ids_without_package.new() as move: + move.product_id = product + move.product_uom_qty = qty + picking = picking_form.save() + picking.action_confirm() + return picking + + def _get_picking_ids_from_action(self, res, expected_quantity): + """Return the new picking found in the action returned by the wizard.""" + id_list = res["domain"][0][2] + self.assertEqual(len(id_list), expected_quantity) + return self.env["stock.picking"].browse(id_list) + + @classmethod + def _get_kit_quantity(cls, picking, bom): + """Returns the quantity of kits in a transfer.""" + filters = { + "incoming_moves": lambda m: m.location_id.usage == "supplier", + "outgoing_moves": lambda m: m.location_id.usage != "supplier", + } + kit_quantity = picking.move_lines._compute_kit_quantities( + bom.product_id, 100, bom, filters + ) + return abs(kit_quantity) + + def _check_move_lines(self, picking, move_lines): + moves = [] + for move in picking.move_lines: + moves.append((move.product_id, move.product_qty, bool(move.bom_line_id))) + self.assertEqual(set(moves), set(move_lines)) + + def test_split_picking_kit_no_split(self): + """Check number of kits is equal to the split limit. + + No split is needed. + """ + picking = self._create_picking( + [ + (self.product_garden_table, 3), + ] + ) + wizard = ( + self.env["stock.split.picking"] + .with_context(active_ids=picking.ids) + .create({"mode": "kit_quantity", "kit_split_quantity": 3}) + ) + res = wizard.action_apply() + new_picking = self._get_picking_ids_from_action(res, 0) + self.assertFalse(new_picking) + np_kq = self._get_kit_quantity(picking, self.bom_garden_table) + self.assertEqual(np_kq, 3) + + def test_split_picking_kit_single_split(self): + """Check number of kits is 4 and the split limit is 3. + + New picking is created and one kit is moved to it. + + """ + picking = self._create_picking( + [ + (self.product_garden_table, 4), + ] + ) + wizard = ( + self.env["stock.split.picking"] + .with_context(active_ids=picking.ids) + .create({"mode": "kit_quantity", "kit_split_quantity": 3}) + ) + res = wizard.action_apply() + # There is 3 kits left + np_kq = self._get_kit_quantity(picking, self.bom_garden_table) + self.assertEqual(np_kq, 3) + # And one kit in the new picking + new_picking = self._get_picking_ids_from_action(res, 1) + np_kq = self._get_kit_quantity(new_picking, self.bom_garden_table) + self.assertEqual(np_kq, 1) + + def test_split_picking_kit_with_no_kit(self): + """Check split picking only has non kit product.""" + picking = self._create_picking( + [(self.product_garden_table_top, 3), (self.product_garden_table_leg, 21)] + ) + wizard = ( + self.env["stock.split.picking"] + .with_context(active_ids=picking.ids) + .create({"mode": "kit_quantity", "kit_split_quantity": 7}) + ) + res = wizard.action_apply() + expected_lines = [ + (self.product_garden_table_top, 3, False), + (self.product_garden_table_leg, 4, False), + ] + self._check_move_lines(picking, expected_lines) + new_picking = self._get_picking_ids_from_action(res, 1) + expected_lines = [ + (self.product_garden_table_leg, 17, False), + ] + self._check_move_lines(new_picking, expected_lines) + + def test_split_picking_with_product_and_kit(self): + picking = self._create_picking( + [ + (self.product_garden_table_top, 3), + (self.product_garden_table_leg, 21), + (self.product_garden_table, 4), + ] + ) + wizard = ( + self.env["stock.split.picking"] + .with_context(active_ids=picking.ids) + .create( + { + "mode": "kit_quantity", + "kit_split_quantity": 6, + "split_kit_order_move": "id", + } + ) + ) + res = wizard.action_apply() + expected_lines = [ + (self.product_garden_table_top, 3, False), + (self.product_garden_table_leg, 3, False), + ] + self._check_move_lines(picking, expected_lines) + new_picking = self._get_picking_ids_from_action(res, 1) + expected_lines = [ + (self.product_garden_table_leg, 18, False), + (self.product_garden_table_top, 4, True), + (self.product_garden_table_leg, 16, True), + ] + self._check_move_lines(new_picking, expected_lines) diff --git a/stock_split_picking_kit/wizards/__init__.py b/stock_split_picking_kit/wizards/__init__.py new file mode 100644 index 000000000000..44b46a90c170 --- /dev/null +++ b/stock_split_picking_kit/wizards/__init__.py @@ -0,0 +1 @@ +from . import stock_split_picking diff --git a/stock_split_picking_kit/wizards/stock_split_picking.py b/stock_split_picking_kit/wizards/stock_split_picking.py new file mode 100644 index 000000000000..18235453c951 --- /dev/null +++ b/stock_split_picking_kit/wizards/stock_split_picking.py @@ -0,0 +1,89 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo import fields, models +from odoo.tools import groupby + + +class StockSplitPicking(models.TransientModel): + _inherit = "stock.split.picking" + + mode = fields.Selection( + selection_add=[("kit_quantity", "Quantity of kits")], + ondelete={"kit_quantity": "set default"}, + ) + kit_split_quantity = fields.Integer(string="Number of kits by transfer") + split_kit_order_move = fields.Char() + + def _apply_kit_quantity(self): + pickings = self.env["stock.picking"] + for picking in self.mapped("picking_ids"): + pickings |= self._split_by_kit_quantity(picking) + return self._picking_action(pickings) + + def _split_by_kit_quantity(self, picking): + filters = { + "incoming_moves": lambda m: m.location_id.usage == "supplier", + "outgoing_moves": lambda m: m.location_id.usage != "supplier", + } + move_lines = picking.move_lines + if self.split_kit_order_move: + move_lines = move_lines.sorted(self.split_kit_order_move) + moves_to_backorder = self.env["stock.move"] + new_picking = self.env["stock.picking"] + used_slots = 0 + max_slots = self.kit_split_quantity + for bom, bom_move_list in groupby( + move_lines, key=lambda move: move.bom_line_id.bom_id + ): + + moves = self.env["stock.move"].browse([move.id for move in bom_move_list]) + if used_slots >= max_slots: + # Current picking is full, everything else is moved to a new picking + moves_to_backorder |= moves + continue + + available_slots = max_slots - used_slots + if bom.type != "phantom": + # Non kit moves, their quantity is the number of slots used + for move in moves: + quantity = move.product_qty + if available_slots >= move.product_qty: + used_slots += quantity + available_slots = max_slots - used_slots + elif available_slots <= 0: + moves_to_backorder |= move + else: + new_move_vals = move._split(quantity - available_slots) + moves_to_backorder |= self.env["stock.move"].create( + new_move_vals + ) + used_slots = max_slots + else: + # Kit moves + kit_quantity = moves._compute_kit_quantities( + bom.product_id, + max(moves.mapped("product_qty")), # Just use max possible + bom, + filters, + ) + kit_quantity = abs(kit_quantity) + if kit_quantity <= available_slots: + used_slots += kit_quantity + else: + kit_to_split = kit_quantity - available_slots + new_move_vals = [] + for move in moves: + new_move_vals += move._split( + move.bom_line_id.product_qty * kit_to_split + ) + moves_to_backorder |= self.env["stock.move"].create(new_move_vals) + used_slots = max_slots + if moves_to_backorder: + new_picking = picking._create_split_backorder() + moves_to_backorder.write({"picking_id": new_picking.id}) + moves_to_backorder.mapped("move_line_ids").write( + {"picking_id": new_picking.id} + ) + + return new_picking diff --git a/stock_split_picking_kit/wizards/stock_split_picking.xml b/stock_split_picking_kit/wizards/stock_split_picking.xml new file mode 100644 index 000000000000..daa3a3824be5 --- /dev/null +++ b/stock_split_picking_kit/wizards/stock_split_picking.xml @@ -0,0 +1,16 @@ + + + + stock.split.picking.kit.form + stock.split.picking + + + + + + + + From 248a32e9443d25f5704822a05524061c0fcead28 Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Fri, 17 Jan 2025 09:19:05 +0100 Subject: [PATCH 2/2] fixup! Add stock_split_picking_kit --- stock_split_picking_kit/README.rst | 8 +++- stock_split_picking_kit/__manifest__.py | 2 +- .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 6 ++- .../static/description/index.html | 7 ++- .../tests/test_stock_split_picking_kit.py | 45 ++----------------- .../wizards/stock_split_picking.py | 17 ++++--- 7 files changed, 31 insertions(+), 55 deletions(-) diff --git a/stock_split_picking_kit/README.rst b/stock_split_picking_kit/README.rst index e773c0e725ea..7ab034a63441 100644 --- a/stock_split_picking_kit/README.rst +++ b/stock_split_picking_kit/README.rst @@ -30,7 +30,11 @@ Stock Split Picking Kit This module adds a new splitting mode to the `stock_split_picking` module. -The new mode `Quantity of kits` allows to split a transfer by a specified number of kits. +The new mode `Quantity of kits` allows splitting a transfer by a specified number of kits. +A new field on the wizard also allows specifying a sort order for the stock moves before +splitting the transfer. + +Multi level boms are not supported. **Table of contents** @@ -54,12 +58,14 @@ Authors ~~~~~~~ * Camptocamp +* BCIM * Contributors ~~~~~~~~~~~~ * Thierry Ducrest +* Jacques-Etienne Baudoux (BCIM) Maintainers ~~~~~~~~~~~ diff --git a/stock_split_picking_kit/__manifest__.py b/stock_split_picking_kit/__manifest__.py index 03ec521f0fd9..0d85fca56482 100644 --- a/stock_split_picking_kit/__manifest__.py +++ b/stock_split_picking_kit/__manifest__.py @@ -5,7 +5,7 @@ "summary": "Split a picking by a number of kits.", "version": "14.0.1.0.0", "category": "Inventory", - "author": "Camptocamp, " "Odoo Community Association (OCA),", + "author": "Camptocamp, BCIM, Odoo Community Association (OCA),", "license": "AGPL-3", "website": "https://github.com/OCA/stock-logistics-workflow", "depends": ["stock_split_picking", "mrp"], diff --git a/stock_split_picking_kit/readme/CONTRIBUTORS.rst b/stock_split_picking_kit/readme/CONTRIBUTORS.rst index 0dd376faecb9..fae6a9d370e0 100644 --- a/stock_split_picking_kit/readme/CONTRIBUTORS.rst +++ b/stock_split_picking_kit/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Thierry Ducrest +* Jacques-Etienne Baudoux (BCIM) diff --git a/stock_split_picking_kit/readme/DESCRIPTION.rst b/stock_split_picking_kit/readme/DESCRIPTION.rst index 88f59733683a..ec43a6c1dd99 100644 --- a/stock_split_picking_kit/readme/DESCRIPTION.rst +++ b/stock_split_picking_kit/readme/DESCRIPTION.rst @@ -1,5 +1,7 @@ This module adds a new splitting mode to the `stock_split_picking` module. -The new mode `Quantity of kits` allows to split a transfer by a specified number of kits. -A new field on the wizard also allows specify a sort order for the stock moves before +The new mode `Quantity of kits` allows splitting a transfer by a specified number of kits. +A new field on the wizard also allows specifying a sort order for the stock moves before splitting the transfer. + +Multi level boms are not supported. diff --git a/stock_split_picking_kit/static/description/index.html b/stock_split_picking_kit/static/description/index.html index 767ace55ba17..742a960d3d38 100644 --- a/stock_split_picking_kit/static/description/index.html +++ b/stock_split_picking_kit/static/description/index.html @@ -371,7 +371,10 @@

Stock Split Picking Kit

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

This module adds a new splitting mode to the stock_split_picking module.

-

The new mode Quantity of kits allows to split a transfer by a specified number of kits.

+

The new mode Quantity of kits allows splitting a transfer by a specified number of kits. +A new field on the wizard also allows specifying a sort order for the stock moves before +splitting the transfer.

+

Multi level boms are not supported.

Table of contents

@@ -405,6 +409,7 @@

Authors

Contributors

diff --git a/stock_split_picking_kit/tests/test_stock_split_picking_kit.py b/stock_split_picking_kit/tests/test_stock_split_picking_kit.py index a16a08d1dc66..9c276df16d3a 100644 --- a/stock_split_picking_kit/tests/test_stock_split_picking_kit.py +++ b/stock_split_picking_kit/tests/test_stock_split_picking_kit.py @@ -18,24 +18,6 @@ def setUpClass(cls): cls.partner = cls.env["res.partner"].create({"name": "Test partner"}) cls.product_model = cls.env["product.product"] - # TODO: How should we handle kit with multiple level - # cls.product_garden_furniture = cls.product_model.create( - # { - # "name": "GARDEN FURNITURE", - # "type": "product", - # "sale_ok": False, - # "purchase_ok": True, - # } - # ) - # cls.tmpl_garden_furniture = cls.product_garden_furniture.product_tmpl_id - # cls.product_garden_chair = cls.product_model.create( - # { - # "name": "GARDEN CHAIR", - # "type": "product", - # "sale_ok": True, - # "purchase_ok": True, - # } - # ) cls.product_garden_table = cls.product_model.create( { "name": "GARDEN TABLE", @@ -86,25 +68,6 @@ def setUpClass(cls): ], } ) - # cls.bom_garden_furniture = cls.bom_model.create( - # { - # "product_tmpl_id": cls.tmpl_garden_furniture.id, - # "product_id": cls.product_garden_furniture.id, - # "type": "phantom", - # "bom_line_ids": [ - # ( - # 0, - # 0, - # {"product_id": cls.product_garden_chair.id, "product_qty": 4.0}, - # ), - # ( - # 0, - # 0, - # {"product_id": cls.product_garden_table.id, "product_qty": 1.0}, - # ), - # ], - # } - # ) @classmethod def _create_picking(cls, lines): @@ -129,13 +92,13 @@ def _get_picking_ids_from_action(self, res, expected_quantity): def _get_kit_quantity(cls, picking, bom): """Returns the quantity of kits in a transfer.""" filters = { - "incoming_moves": lambda m: m.location_id.usage == "supplier", - "outgoing_moves": lambda m: m.location_id.usage != "supplier", + "incoming_moves": lambda m: True, + "outgoing_moves": lambda m: False, } kit_quantity = picking.move_lines._compute_kit_quantities( - bom.product_id, 100, bom, filters + bom.product_id, max(picking.move_lines.mapped("product_qty")), bom, filters ) - return abs(kit_quantity) + return kit_quantity def _check_move_lines(self, picking, move_lines): moves = [] diff --git a/stock_split_picking_kit/wizards/stock_split_picking.py b/stock_split_picking_kit/wizards/stock_split_picking.py index 18235453c951..a878bb22c6d3 100644 --- a/stock_split_picking_kit/wizards/stock_split_picking.py +++ b/stock_split_picking_kit/wizards/stock_split_picking.py @@ -17,16 +17,18 @@ class StockSplitPicking(models.TransientModel): def _apply_kit_quantity(self): pickings = self.env["stock.picking"] - for picking in self.mapped("picking_ids"): + for picking in self.picking_ids: pickings |= self._split_by_kit_quantity(picking) return self._picking_action(pickings) def _split_by_kit_quantity(self, picking): filters = { - "incoming_moves": lambda m: m.location_id.usage == "supplier", - "outgoing_moves": lambda m: m.location_id.usage != "supplier", + "incoming_moves": lambda m: True, + "outgoing_moves": lambda m: False, } - move_lines = picking.move_lines + move_lines = picking.move_lines.filtered( + lambda m: m.state not in ["done", "cancel"] + ) if self.split_kit_order_move: move_lines = move_lines.sorted(self.split_kit_order_move) moves_to_backorder = self.env["stock.move"] @@ -48,7 +50,7 @@ def _split_by_kit_quantity(self, picking): # Non kit moves, their quantity is the number of slots used for move in moves: quantity = move.product_qty - if available_slots >= move.product_qty: + if available_slots >= quantity: used_slots += quantity available_slots = max_slots - used_slots elif available_slots <= 0: @@ -67,7 +69,6 @@ def _split_by_kit_quantity(self, picking): bom, filters, ) - kit_quantity = abs(kit_quantity) if kit_quantity <= available_slots: used_slots += kit_quantity else: @@ -82,8 +83,6 @@ def _split_by_kit_quantity(self, picking): if moves_to_backorder: new_picking = picking._create_split_backorder() moves_to_backorder.write({"picking_id": new_picking.id}) - moves_to_backorder.mapped("move_line_ids").write( - {"picking_id": new_picking.id} - ) + moves_to_backorder.move_line_ids.write({"picking_id": new_picking.id}) return new_picking