From 763fb6d16940592ed9576f77666af495959dc6c7 Mon Sep 17 00:00:00 2001 From: Mmequignon Date: Thu, 7 May 2026 15:14:56 +0200 Subject: [PATCH 1/6] shopfloor: move_line: Replace assert with exception Backported from 18.0 --- shopfloor/exceptions.py | 4 ++++ shopfloor/models/stock_move_line.py | 9 +++++++-- shopfloor/services/zone_picking.py | 18 ++++++++---------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/shopfloor/exceptions.py b/shopfloor/exceptions.py index 14bb2ecbf90..5f25441d240 100644 --- a/shopfloor/exceptions.py +++ b/shopfloor/exceptions.py @@ -4,3 +4,7 @@ class ConcurentWorkOnTransfer(Exception): """Some user already processed some transfers.""" + + +class CannotProcessMoreThanPlanned(Exception): + """Cannot process more units than the quantity""" diff --git a/shopfloor/models/stock_move_line.py b/shopfloor/models/stock_move_line.py index 452fe771d2c..71d7c831add 100644 --- a/shopfloor/models/stock_move_line.py +++ b/shopfloor/models/stock_move_line.py @@ -7,6 +7,8 @@ from odoo.exceptions import UserError from odoo.tools.float_utils import float_compare, float_is_zero +from ..exceptions import CannotProcessMoreThanPlanned + _logger = logging.getLogger(__name__) @@ -43,8 +45,11 @@ def _split_partial_quantity(self): ) qty_lesser = compare == -1 qty_greater = compare == 1 - assert not qty_greater, "Quantity done cannot exceed quantity to do" - if qty_lesser: + if qty_greater: + raise CannotProcessMoreThanPlanned( + "Quantity done cannot exceed quantity to do" + ) + elif qty_lesser: remaining = self.reserved_uom_qty - self.qty_done new_line = self.copy({"reserved_uom_qty": remaining, "qty_done": 0}) # if we didn't bypass reservation update, the quant reservation diff --git a/shopfloor/services/zone_picking.py b/shopfloor/services/zone_picking.py index ff6512cfe2e..bba01b659b4 100644 --- a/shopfloor/services/zone_picking.py +++ b/shopfloor/services/zone_picking.py @@ -11,7 +11,7 @@ from odoo.addons.base_rest.components.service import to_bool, to_int from odoo.addons.component.core import Component -from ..exceptions import ConcurentWorkOnTransfer +from ..exceptions import CannotProcessMoreThanPlanned, ConcurentWorkOnTransfer from ..utils import to_float @@ -1033,15 +1033,6 @@ def _set_destination_package(self, move_line, quantity, package): return (package_changed, response) # the quantity done is set to the passed quantity # but if we move a partial qty, we need to split the move line - compare = self._move_line_compare_qty(move_line, quantity) - qty_greater = compare == 1 - if qty_greater: - response = self._response_for_set_line_destination( - move_line, - message=self.msg_store.unable_to_pick_more(move_line.reserved_uom_qty), - qty_done=quantity, - ) - return (package_changed, response) stock = self._actions_for("stock") stock._lock_lines(move_line) try: @@ -1058,6 +1049,13 @@ def _set_destination_package(self, move_line, quantity, package): qty_done=quantity, ) return (package_changed, response) + except CannotProcessMoreThanPlanned: + response = self._response_for_set_line_destination( + move_line, + message=self.msg_store.unable_to_pick_more(move_line.quantity), + qty_done=quantity, + ) + return (package_changed, response) package_changed = True # Zero check # Only apply zero check if the product is of type "product". From 53c1c1067b821fbeadfe0ef196d05966126b04bf Mon Sep 17 00:00:00 2001 From: Mmequignon Date: Mon, 20 Apr 2026 10:23:01 +0200 Subject: [PATCH 2/6] shopfloor: extract move fix package level behavior Backported from 18.0 --- shopfloor/models/stock_move.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/shopfloor/models/stock_move.py b/shopfloor/models/stock_move.py index 4dfdd22dfb3..f52dd02ea7a 100644 --- a/shopfloor/models/stock_move.py +++ b/shopfloor/models/stock_move.py @@ -56,6 +56,17 @@ def split_unavailable_qty(self): partial_move.split_other_move_lines(partial_move.move_line_ids) return partial_moves + def _last_move_from_package_level(self): + """Returns True if self is the last move in the related package level""" + if self.package_level_id.move_ids - self: + # More moves in package level than self + return False + move_lines = self.move_line_ids + if move_lines.package_level_id.move_line_ids - move_lines: + # More lines in package level than there's lines with package level + return False + return True + def _extract_in_split_order(self, default=None, backorder=False): """Extract moves in a new picking @@ -83,10 +94,21 @@ def _extract_in_split_order(self, default=None, backorder=False): message = (_("The split order {} has been created.")).format(link) picking.message_post(body=message) self.picking_id = new_picking.id - self.package_level_id.picking_id = new_picking.id self.move_line_ids.picking_id = new_picking.id - self.move_line_ids.package_level_id.picking_id = new_picking.id - self._action_assign() + # When package content is fully extracted to a new picking, also move + # the package. Otherwise the package stays in current picking. + if self._last_move_from_package_level(): + self.package_level_id.picking_id = new_picking.id + self.move_line_ids.package_level_id.picking_id = new_picking.id + else: + self.package_level_id = False + self.move_line_ids.package_level_id = False + self.package_level_id.picking_id = False + for line in self.move_line_ids: + # We drop result package only if the whole package was + # supposed to be moved. + if line.package_id == line.result_package_id: + line.result_package_id = False return new_picking def extract_and_action_done(self): From 4f5078b81134f2c7c80e16d8cfdd0ee2f9a15973 Mon Sep 17 00:00:00 2001 From: Mmequignon Date: Mon, 20 Apr 2026 10:25:17 +0200 Subject: [PATCH 3/6] shopfloor: extract move add more tests Backported from 18.0 --- shopfloor/tests/__init__.py | 1 + shopfloor/tests/model_common.py | 111 ++++++++++++++++++++++++++ shopfloor/tests/test_stock_move.py | 91 ++++++++++++++++++++++ shopfloor/tests/test_stock_split.py | 117 +--------------------------- 4 files changed, 207 insertions(+), 113 deletions(-) create mode 100644 shopfloor/tests/model_common.py create mode 100644 shopfloor/tests/test_stock_move.py diff --git a/shopfloor/tests/__init__.py b/shopfloor/tests/__init__.py index 2c3ee22e892..61a133369e3 100644 --- a/shopfloor/tests/__init__.py +++ b/shopfloor/tests/__init__.py @@ -83,6 +83,7 @@ from . import test_misc from . import test_move_action_assign from . import test_scan_anything +from . import test_stock_move from . import test_stock_split from . import test_picking_form from . import test_user diff --git a/shopfloor/tests/model_common.py b/shopfloor/tests/model_common.py new file mode 100644 index 00000000000..366835b8b68 --- /dev/null +++ b/shopfloor/tests/model_common.py @@ -0,0 +1,111 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo.tests import Form, tagged +from odoo.tests.common import TransactionCase + + +@tagged("-at_install", "post_install") +class ModelCommon(TransactionCase): + """Common class to test model code""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.setUpClassData() + + @classmethod + def setUpClassData(cls): + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.customer_location = cls.env.ref("stock.stock_location_customers") + cls.pack_location = cls.warehouse.wh_pack_stock_loc_id + cls.ship_location = cls.warehouse.wh_output_stock_loc_id + cls.stock_location = cls.env.ref("stock.stock_location_stock") + # Create products + cls.product_a = ( + cls.env["product.product"] + .sudo() + .create( + { + "name": "Product A", + "is_storable": True, + "default_code": "A", + "barcode": "A", + "weight": 2, + } + ) + ) + cls.product_a_packaging = ( + cls.env["product.packaging"] + .sudo() + .create( + { + "name": "Box", + "product_id": cls.product_a.id, + "barcode": "ProductABox", + } + ) + ) + cls.product_b = ( + cls.env["product.product"] + .sudo() + .create( + { + "name": "Product B", + "is_storable": True, + "default_code": "B", + "barcode": "B", + "weight": 2, + } + ) + ) + cls.product_b_packaging = ( + cls.env["product.packaging"] + .sudo() + .create( + { + "name": "Box", + "product_id": cls.product_b.id, + "barcode": "ProductBBox", + } + ) + ) + # Put product_a quantities in different packages to get several move lines + cls.package_1 = cls.env["stock.quant.package"].create({"name": "PACKAGE_1"}) + cls.package_2 = cls.env["stock.quant.package"].create({"name": "PACKAGE_2"}) + cls.package_3 = cls.env["stock.quant.package"].create({"name": "PACKAGE_3"}) + cls.package_4 = cls.env["stock.quant.package"].create({"name": "PACKAGE_4"}) + + @classmethod + def _update_qty_in_location( + cls, location, product, quantity, package=None, lot=None + ): + quants = cls.env["stock.quant"]._gather( + product, location, lot_id=lot, package_id=package, strict=True + ) + # this method adds the quantity to the current quantity, so remove it + quantity -= sum(quants.mapped("quantity")) + if not quantity: + return + cls.env["stock.quant"]._update_available_quantity( + product, location, quantity=quantity, package_id=package, lot_id=lot + ) + + @classmethod + def _create_picking(cls, picking_type=None, lines=None, confirm=True, **kw): + picking_form = Form(cls.env["stock.picking"]) + picking_form.picking_type_id = picking_type or cls.picking_type + picking_form.partner_id = cls.customer + if lines is None: + lines = [(cls.product_a, 10), (cls.product_b, 10)] + for product, qty in lines: + with picking_form.move_ids_without_package.new() as move: + move.product_id = product + move.product_uom_qty = qty + for k, v in kw.items(): + setattr(picking_form, k, v) + picking = picking_form.save() + if confirm: + picking.action_confirm() + return picking diff --git a/shopfloor/tests/test_stock_move.py b/shopfloor/tests/test_stock_move.py new file mode 100644 index 00000000000..44cb6627aa1 --- /dev/null +++ b/shopfloor/tests/test_stock_move.py @@ -0,0 +1,91 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from .model_common import ModelCommon + + +class TestStockMove(ModelCommon): + @classmethod + def setUpClassData(cls): + res = super().setUpClassData() + cls.picking_type = cls.env.ref("stock.picking_type_internal") + cls.customer = cls.env["res.partner"].create({"name": "Bob"}) + return res + + def test_extract_in_split_order_partial(self): + package = self.package_1 + # multiple moves in the same package, one move split, package stays + self._update_qty_in_location( + self.stock_location, self.product_a, 10.0, package=package + ) + self._update_qty_in_location( + self.stock_location, self.product_b, 10.0, package=package + ) + picking = self._create_picking(confirm=True) + move1 = picking.move_ids[0] + move2 = picking.move_ids[1] + move_line1 = move1.move_line_ids + move_line2 = move2.move_line_ids + package_level = picking.package_level_ids + self.assertEqual(len(package_level), 1) + self.assertEqual(package_level, move_line1.package_level_id) + self.assertEqual(package_level, move_line2.package_level_id) + split_picking = move2._extract_in_split_order() + # extracting move2 is taking half the content of the package. + # Package level remains on picking. + self.assertEqual(picking.package_level_ids, package_level) + self.assertEqual(move_line1.package_level_id, package_level) + self.assertEqual(move_line1.result_package_id, package) + # move2 has been extracted in a new picking, + # which shouldn't have a package level + self.assertFalse(split_picking.package_level_ids) + self.assertFalse(move_line2.package_level_id) + self.assertFalse(move_line2.result_package_id) + + def test_extract_in_split_order_full(self): + package = self.package_1 + # multiple moves in the same package, one move split, package stays + self._update_qty_in_location( + self.stock_location, + self.product_a, + 10.0, + ) + self._update_qty_in_location( + self.stock_location, self.product_b, 10.0, package=package + ) + picking = self._create_picking(confirm=True) + move1 = picking.move_ids[0] + move2 = picking.move_ids[1] # productb + move_line1 = move1.move_line_ids + move_line2 = move2.move_line_ids + package_level = picking.package_level_ids + self.assertEqual(len(package_level), 1) + self.assertFalse(move_line1.package_level_id) + self.assertEqual(package_level, move_line2.package_level_id) + split_picking = move2._extract_in_split_order() + # extracting move2 is taking the full package. + # Package level is moved in the new picking + self.assertFalse(picking.package_level_ids) + self.assertFalse(move_line1.package_level_id) + self.assertFalse(move_line1.result_package_id) + # move2 has been extracted in a new picking, and the package with it + self.assertEqual(split_picking.package_level_ids, package_level) + self.assertEqual(move_line2.package_level_id, package_level) + self.assertEqual(move_line2.result_package_id, package) + + def test_last_move_from_package_level(self): + package = self.package_1 + # multiple moves in the same package, one move split, package stays + self._update_qty_in_location( + self.stock_location, self.product_a, 10.0, package=package + ) + self._update_qty_in_location( + self.stock_location, self.product_b, 10.0, package=package + ) + picking = self._create_picking(confirm=True) + move1 = picking.move_ids[0] + move2 = picking.move_ids[1] + # Both move1 and move2 are in the package level, so either of them are the last + self.assertFalse(move1._last_move_from_package_level()) + self.assertFalse(move2._last_move_from_package_level()) + # But together, they represent the last moves of the package level + self.assertTrue((move1 | move2)._last_move_from_package_level()) diff --git a/shopfloor/tests/test_stock_split.py b/shopfloor/tests/test_stock_split.py index ceab8957793..5409ac64253 100644 --- a/shopfloor/tests/test_stock_split.py +++ b/shopfloor/tests/test_stock_split.py @@ -1,126 +1,17 @@ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.tests import tagged -from odoo.tests.common import TransactionCase +from .model_common import ModelCommon # pylint: disable=missing-return -@tagged("post_install", "-at_install") -class TestStockSplit(TransactionCase): +class TestStockSplit(ModelCommon): @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.warehouse.delivery_steps = "pick_pack_ship" - cls.customer_location = cls.env.ref("stock.stock_location_customers") - cls.pack_location = cls.warehouse.wh_pack_stock_loc_id - cls.ship_location = cls.warehouse.wh_output_stock_loc_id - cls.stock_location = cls.env.ref("stock.stock_location_stock") - # Create products - cls.product_a = ( - cls.env["product.product"] - .sudo() - .create( - { - "name": "Product A", - "type": "product", - "default_code": "A", - "barcode": "A", - "weight": 2, - } - ) - ) - cls.product_a_packaging = ( - cls.env["product.packaging"] - .sudo() - .create( - { - "name": "Box", - "product_id": cls.product_a.id, - "barcode": "ProductABox", - } - ) - ) - cls.product_b = ( - cls.env["product.product"] - .sudo() - .create( - { - "name": "Product B", - "type": "product", - "default_code": "B", - "barcode": "B", - "weight": 2, - } - ) - ) - cls.product_b_packaging = ( - cls.env["product.packaging"] - .sudo() - .create( - { - "name": "Box", - "product_id": cls.product_b.id, - "barcode": "ProductBBox", - } - ) - ) - # Put product_a quantities in different packages to get several move lines - cls.package_1 = cls.env["stock.quant.package"].create({"name": "PACKAGE_1"}) - cls.package_2 = cls.env["stock.quant.package"].create({"name": "PACKAGE_2"}) - cls.package_3 = cls.env["stock.quant.package"].create({"name": "PACKAGE_3"}) - cls.package_4 = cls.env["stock.quant.package"].create({"name": "PACKAGE_4"}) + def setUpClassData(cls): + super().setUpClassData() cls._update_qty_in_location( cls.stock_location, cls.product_a, 6, package=cls.package_1 ) - cls._update_qty_in_location( - cls.stock_location, cls.product_a, 4, package=cls.package_2 - ) - cls._update_qty_in_location( - cls.stock_location, cls.product_a, 5, package=cls.package_3 - ) - # Put product_b quantities in stock - cls._update_qty_in_location(cls.stock_location, cls.product_b, 10) - # Create the pick/pack/ship transfer - cls.ship_move_a = cls.env["stock.move"].create( - { - "name": cls.product_a.display_name, - "product_id": cls.product_a.id, - "product_uom_qty": 15.0, - "product_uom": cls.product_a.uom_id.id, - "location_id": cls.ship_location.id, - "location_dest_id": cls.customer_location.id, - "warehouse_id": cls.warehouse.id, - "picking_type_id": cls.warehouse.out_type_id.id, - "procure_method": "make_to_order", - "state": "draft", - } - ) - cls.ship_move_b = cls.env["stock.move"].create( - { - "name": cls.product_b.display_name, - "product_id": cls.product_b.id, - "product_uom_qty": 4, - "product_uom": cls.product_b.uom_id.id, - "location_id": cls.ship_location.id, - "location_dest_id": cls.customer_location.id, - "warehouse_id": cls.warehouse.id, - "picking_type_id": cls.warehouse.out_type_id.id, - "procure_method": "make_to_order", - "state": "draft", - } - ) - (cls.ship_move_a | cls.ship_move_b)._assign_picking() - (cls.ship_move_a | cls.ship_move_b)._action_confirm(merge=False) - cls.pack_move_a = cls.ship_move_a.move_orig_ids[0] - cls.pick_move_a = cls.pack_move_a.move_orig_ids[0] - cls.pack_move_b = cls.ship_move_b.move_orig_ids[0] - cls.pick_move_b = cls.pack_move_b.move_orig_ids[0] - cls.picking = cls.pick_move_a.picking_id - cls.packing = cls.pack_move_a.picking_id - cls.picking.action_assign() @classmethod def _update_qty_in_location( From 40d3ed9a9cf44859278acc854ecdae46c7729b31 Mon Sep 17 00:00:00 2001 From: Mmequignon Date: Mon, 20 Apr 2026 13:57:27 +0200 Subject: [PATCH 4/6] shopfloor: fix package level schema Backported from 18.0 --- shopfloor/actions/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shopfloor/actions/data.py b/shopfloor/actions/data.py index 47e10453ba4..79d7a6f8e63 100644 --- a/shopfloor/actions/data.py +++ b/shopfloor/actions/data.py @@ -271,7 +271,7 @@ def _package_level_parser(self): "location_id:location_src", lambda rec, fname: self.location( fields.first(rec.move_line_ids).location_id - or fields.first(rec.move_lines).location_id + or fields.first(rec.move_ids).location_id or rec.picking_id.location_id ), ), From 4c4a439d0ae5bbaca36025344a5cb20d5f267b10 Mon Sep 17 00:00:00 2001 From: Nicolas Delbovier Date: Thu, 7 May 2026 15:58:07 +0200 Subject: [PATCH 5/6] [FIX] shopfloor: resolve v16/v18 model discrepancy in common tests --- shopfloor/tests/model_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shopfloor/tests/model_common.py b/shopfloor/tests/model_common.py index 366835b8b68..f10810ea1fd 100644 --- a/shopfloor/tests/model_common.py +++ b/shopfloor/tests/model_common.py @@ -29,7 +29,7 @@ def setUpClassData(cls): .create( { "name": "Product A", - "is_storable": True, + "type": "product", "default_code": "A", "barcode": "A", "weight": 2, @@ -53,7 +53,7 @@ def setUpClassData(cls): .create( { "name": "Product B", - "is_storable": True, + "type": "product", "default_code": "B", "barcode": "B", "weight": 2, From 71c999ec009d80d25a3053dbea0dbe25c552cf78 Mon Sep 17 00:00:00 2001 From: Nicolas Delbovier Date: Thu, 7 May 2026 17:11:26 +0200 Subject: [PATCH 6/6] [WIP] shopfloor: try to fix unit test --- shopfloor/models/stock_move.py | 9 +- shopfloor/tests/test_stock_split.py | 122 +++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/shopfloor/models/stock_move.py b/shopfloor/models/stock_move.py index f52dd02ea7a..ea5e5abce1d 100644 --- a/shopfloor/models/stock_move.py +++ b/shopfloor/models/stock_move.py @@ -101,10 +101,11 @@ def _extract_in_split_order(self, default=None, backorder=False): self.package_level_id.picking_id = new_picking.id self.move_line_ids.package_level_id.picking_id = new_picking.id else: - self.package_level_id = False - self.move_line_ids.package_level_id = False - self.package_level_id.picking_id = False - for line in self.move_line_ids: + lines = self.move_line_ids + lines.write({"package_level_id": False}) + self.write({"package_level_id": False}) + + for line in lines: # We drop result package only if the whole package was # supposed to be moved. if line.package_id == line.result_package_id: diff --git a/shopfloor/tests/test_stock_split.py b/shopfloor/tests/test_stock_split.py index 5409ac64253..f9b9c54d13b 100644 --- a/shopfloor/tests/test_stock_split.py +++ b/shopfloor/tests/test_stock_split.py @@ -1,5 +1,7 @@ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import Command + from .model_common import ModelCommon # pylint: disable=missing-return @@ -12,19 +14,114 @@ def setUpClassData(cls): cls._update_qty_in_location( cls.stock_location, cls.product_a, 6, package=cls.package_1 ) - - @classmethod - def _update_qty_in_location( - cls, location, product, quantity, package=None, lot=None - ): - quants = cls.env["stock.quant"]._gather( - product, location, lot_id=lot, package_id=package, strict=True + cls._update_qty_in_location( + cls.stock_location, cls.product_a, 4, package=cls.package_2 + ) + cls._update_qty_in_location( + cls.stock_location, cls.product_a, 5, package=cls.package_3 + ) + # Put product_b quantities in stock + cls._update_qty_in_location(cls.stock_location, cls.product_b, 10) + # Create the pick/pack/ship transfer + cls.ship_move_a = cls.env["stock.move"].create( + { + "name": cls.product_a.display_name, + "product_id": cls.product_a.id, + "product_uom_qty": 15.0, + "product_uom": cls.product_a.uom_id.id, + "location_id": cls.ship_location.id, + "location_dest_id": cls.customer_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.out_type_id.id, + "procure_method": "make_to_order", + "state": "waiting", + } + ) + cls.pack_move_a = cls.env["stock.move"].create( + { + "name": cls.product_a.display_name, + "product_id": cls.product_a.id, + "product_uom_qty": 15.0, + "product_uom": cls.product_a.uom_id.id, + "location_id": cls.pack_location.id, + "location_dest_id": cls.ship_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.pack_type_id.id, + "procure_method": "make_to_order", + "state": "waiting", + "move_dest_ids": [Command.link(cls.ship_move_a.id)], + } ) - # this method adds the quantity to the current quantity, so remove it - quantity -= sum(quants.mapped("quantity")) - cls.env["stock.quant"]._update_available_quantity( - product, location, quantity, package_id=package, lot_id=lot + cls.pick_move_a = cls.env["stock.move"].create( + { + "name": cls.product_a.display_name, + "product_id": cls.product_a.id, + "product_uom_qty": 15.0, + "product_uom": cls.product_a.uom_id.id, + "location_id": cls.stock_location.id, + "location_dest_id": cls.pack_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.pick_type_id.id, + "procure_method": "make_to_stock", + "state": "confirmed", + "move_dest_ids": [Command.link(cls.pack_move_a.id)], + } + ) + cls.ship_move_b = cls.env["stock.move"].create( + { + "name": cls.product_b.display_name, + "product_id": cls.product_b.id, + "product_uom_qty": 4, + "product_uom": cls.product_b.uom_id.id, + "location_id": cls.ship_location.id, + "location_dest_id": cls.customer_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.out_type_id.id, + "procure_method": "make_to_order", + "state": "waiting", + } + ) + cls.pack_move_b = cls.env["stock.move"].create( + { + "name": cls.product_b.display_name, + "product_id": cls.product_b.id, + "product_uom_qty": 4.0, + "product_uom": cls.product_b.uom_id.id, + "location_id": cls.pack_location.id, + "location_dest_id": cls.ship_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.pack_type_id.id, + "procure_method": "make_to_order", + "state": "waiting", + "move_dest_ids": [Command.link(cls.ship_move_b.id)], + } + ) + cls.pick_move_b = cls.env["stock.move"].create( + { + "name": cls.product_b.display_name, + "product_id": cls.product_b.id, + "product_uom_qty": 4.0, + "product_uom": cls.product_b.uom_id.id, + "location_id": cls.stock_location.id, + "location_dest_id": cls.pack_location.id, + "warehouse_id": cls.warehouse.id, + "picking_type_id": cls.warehouse.pick_type_id.id, + "procure_method": "make_to_stock", + "state": "confirmed", + "move_dest_ids": [Command.link(cls.pack_move_b.id)], + } ) + ( + cls.ship_move_a + | cls.ship_move_b + | cls.pack_move_a + | cls.pack_move_b + | cls.pick_move_a + | cls.pick_move_b + )._assign_picking() + cls.picking = cls.pick_move_a.picking_id + cls.packing = cls.pack_move_a.picking_id + cls.picking.action_assign() def test_split_pickings_from_source_location(self): dest_location = self.pick_move_a.location_dest_id.sudo().copy( @@ -50,7 +147,8 @@ def test_split_pickings_from_source_location(self): self.assertEqual(len(self.pack_move_a.move_line_ids), 3) self.assertEqual(len(self.packing.package_level_ids), 3) self.assertEqual(len(move_lines_to_process), 1) - new_packing = move_lines_to_process._split_pickings_from_source_location() + move_lines_to_process._extract_in_split_order() + new_packing = self.packing.backorder_ids self.assertEqual(len(self.packing.package_level_ids), 2) self.assertEqual(len(new_packing.package_level_ids), 1) self.assertEqual(len(new_packing.move_line_ids), 1)