mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
The former implementation was to take as much as possible of the largest packaging, to the smallest packacking, to have less to move. Then, only, removal order (fifo, ...) was applied for equal quantities. It is more important to respect removal order than limiting the operations, so remove this "optimization".
652 lines
24 KiB
Python
652 lines
24 KiB
Python
# Copyright 2019 Camptocamp (https://www.camptocamp.com)
|
|
|
|
from odoo import exceptions, fields
|
|
from odoo.tests import common
|
|
|
|
|
|
class TestReserveRule(common.SavepointCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.partner_delta = cls.env.ref("base.res_partner_4")
|
|
cls.wh = cls.env["stock.warehouse"].create(
|
|
{
|
|
"name": "Base Warehouse",
|
|
"reception_steps": "one_step",
|
|
"delivery_steps": "pick_ship",
|
|
"code": "WHTEST",
|
|
}
|
|
)
|
|
cls.rule = cls.env.ref("stock_reserve_rule.stock_reserve_rule_1_demo")
|
|
cls.rule.active = True
|
|
|
|
cls.customer_loc = cls.env.ref("stock.stock_location_customers")
|
|
|
|
cls.loc_zone1 = cls.env["stock.location"].create(
|
|
{"name": "Zone1", "location_id": cls.wh.lot_stock_id.id}
|
|
)
|
|
cls.loc_zone1_bin1 = cls.env["stock.location"].create(
|
|
{"name": "Zone1 Bin1", "location_id": cls.loc_zone1.id}
|
|
)
|
|
cls.loc_zone1_bin2 = cls.env["stock.location"].create(
|
|
{"name": "Zone1 Bin2", "location_id": cls.loc_zone1.id}
|
|
)
|
|
cls.loc_zone2 = cls.env["stock.location"].create(
|
|
{"name": "Zone2", "location_id": cls.wh.lot_stock_id.id}
|
|
)
|
|
cls.loc_zone2_bin1 = cls.env["stock.location"].create(
|
|
{"name": "Zone2 Bin1", "location_id": cls.loc_zone2.id}
|
|
)
|
|
cls.loc_zone2_bin2 = cls.env["stock.location"].create(
|
|
{"name": "Zone2 Bin2", "location_id": cls.loc_zone2.id}
|
|
)
|
|
cls.loc_zone3 = cls.env["stock.location"].create(
|
|
{"name": "Zone3", "location_id": cls.wh.lot_stock_id.id}
|
|
)
|
|
cls.loc_zone3_bin1 = cls.env["stock.location"].create(
|
|
{"name": "Zone3 Bin1", "location_id": cls.loc_zone3.id}
|
|
)
|
|
cls.loc_zone3_bin2 = cls.env["stock.location"].create(
|
|
{"name": "Zone3 Bin2", "location_id": cls.loc_zone3.id}
|
|
)
|
|
|
|
cls.product1 = cls.env["product.product"].create(
|
|
{"name": "Product 1", "type": "product"}
|
|
)
|
|
cls.product2 = cls.env["product.product"].create(
|
|
{"name": "Product 2", "type": "product"}
|
|
)
|
|
|
|
cls.unit = cls.env["product.packaging.type"].create(
|
|
{"name": "Unit", "code": "UNIT", "sequence": 0}
|
|
)
|
|
cls.retail_box = cls.env["product.packaging.type"].create(
|
|
{"name": "Retail Box", "code": "PACK", "sequence": 3}
|
|
)
|
|
cls.transport_box = cls.env["product.packaging.type"].create(
|
|
{"name": "Transport Box", "code": "CASE", "sequence": 4}
|
|
)
|
|
cls.pallet = cls.env["product.packaging.type"].create(
|
|
{"name": "Pallet", "code": "PALLET", "sequence": 5}
|
|
)
|
|
|
|
def _create_picking(self, wh, products=None):
|
|
"""Create picking
|
|
|
|
Products must be a list of tuples (product, quantity).
|
|
One stock move will be created for each tuple.
|
|
"""
|
|
if products is None:
|
|
products = []
|
|
|
|
picking = self.env["stock.picking"].create(
|
|
{
|
|
"location_id": wh.lot_stock_id.id,
|
|
"location_dest_id": wh.wh_output_stock_loc_id.id,
|
|
"partner_id": self.partner_delta.id,
|
|
"picking_type_id": wh.pick_type_id.id,
|
|
}
|
|
)
|
|
|
|
for product, qty in products:
|
|
self.env["stock.move"].create(
|
|
{
|
|
"name": product.name,
|
|
"product_id": product.id,
|
|
"product_uom_qty": qty,
|
|
"product_uom": product.uom_id.id,
|
|
"picking_id": picking.id,
|
|
"location_id": wh.lot_stock_id.id,
|
|
"location_dest_id": wh.wh_output_stock_loc_id.id,
|
|
"state": "confirmed",
|
|
}
|
|
)
|
|
return picking
|
|
|
|
def _update_qty_in_location(self, location, product, quantity, in_date=None):
|
|
self.env["stock.quant"]._update_available_quantity(
|
|
product, location, quantity, in_date=in_date
|
|
)
|
|
|
|
def _create_rule(self, rule_values, removal_values):
|
|
rule_config = {
|
|
"name": "Test Rule",
|
|
"location_id": self.wh.lot_stock_id.id,
|
|
"rule_removal_ids": [(0, 0, values) for values in removal_values],
|
|
}
|
|
rule_config.update(rule_values)
|
|
self.env["stock.reserve.rule"].create(rule_config)
|
|
# workaround for https://github.com/odoo/odoo/pull/41900
|
|
self.env["stock.reserve.rule"].invalidate_cache()
|
|
|
|
def _setup_packagings(self, product, packagings):
|
|
"""Create packagings on a product
|
|
packagings is a list [(name, qty, packaging_type)]
|
|
"""
|
|
self.env["product.packaging"].create(
|
|
[
|
|
{
|
|
"name": name,
|
|
"qty": qty,
|
|
"product_id": product.id,
|
|
"packaging_type_id": packaging_type.id,
|
|
}
|
|
for name, qty, packaging_type in packagings
|
|
]
|
|
)
|
|
|
|
def test_removal_rule_location_child_of_rule_location(self):
|
|
# removal rule location is a child
|
|
self._create_rule({}, [{"location_id": self.loc_zone1.id}])
|
|
# removal rule location is not a child
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
self._create_rule(
|
|
{}, [{"location_id": self.env.ref("stock.stock_location_locations").id}]
|
|
)
|
|
|
|
def test_rule_take_all_in_2(self):
|
|
all_locs = (
|
|
self.loc_zone1_bin1,
|
|
self.loc_zone1_bin2,
|
|
self.loc_zone2_bin1,
|
|
self.loc_zone2_bin2,
|
|
self.loc_zone3_bin1,
|
|
self.loc_zone3_bin2,
|
|
)
|
|
for loc in all_locs:
|
|
self._update_qty_in_location(loc, self.product1, 100)
|
|
|
|
picking = self._create_picking(self.wh, [(self.product1, 200)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{"location_id": self.loc_zone1.id, "sequence": 2},
|
|
{"location_id": self.loc_zone2.id, "sequence": 1},
|
|
{"location_id": self.loc_zone3.id, "sequence": 3},
|
|
],
|
|
)
|
|
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone2_bin2.id, "product_qty": 100},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_take_all_in_2_and_3(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 150)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{"location_id": self.loc_zone1.id, "sequence": 3},
|
|
{"location_id": self.loc_zone2.id, "sequence": 1},
|
|
{"location_id": self.loc_zone3.id, "sequence": 2},
|
|
],
|
|
)
|
|
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 50},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_remaining(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 400)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{"location_id": self.loc_zone1.id, "sequence": 3},
|
|
{"location_id": self.loc_zone2.id, "sequence": 1},
|
|
{"location_id": self.loc_zone3.id, "sequence": 2},
|
|
],
|
|
)
|
|
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone1_bin1.id, "product_qty": 100},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "partially_available")
|
|
self.assertEqual(move.reserved_availability, 300.0)
|
|
|
|
def test_rule_domain(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 200)])
|
|
|
|
domain = [("product_id", "!=", self.product1.id)]
|
|
self._create_rule(
|
|
{"rule_domain": domain, "sequence": 1},
|
|
[
|
|
# this rule should be excluded by the domain
|
|
{"location_id": self.loc_zone1.id, "sequence": 1}
|
|
],
|
|
)
|
|
self._create_rule(
|
|
{"sequence": 2},
|
|
[
|
|
{"location_id": self.loc_zone2.id, "sequence": 1},
|
|
{"location_id": self.loc_zone3.id, "sequence": 2},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 100},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_picking_type(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 200)])
|
|
|
|
self._create_rule(
|
|
# different picking, should be excluded
|
|
{"picking_type_ids": [(6, 0, self.wh.int_type_id.ids)], "sequence": 1},
|
|
[{"location_id": self.loc_zone1.id, "sequence": 1}],
|
|
)
|
|
self._create_rule(
|
|
# same picking type as the move
|
|
{"picking_type_ids": [(6, 0, self.wh.pick_type_id.ids)], "sequence": 2},
|
|
[
|
|
{"location_id": self.loc_zone2.id, "sequence": 1},
|
|
{"location_id": self.loc_zone3.id, "sequence": 2},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 100},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_quant_domain(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 200)])
|
|
|
|
domain = [("quantity", ">", 200)]
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
# This rule is not excluded by the domain,
|
|
# but the quant will be as the quantity is less than 200.
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"quant_domain": domain,
|
|
},
|
|
{"location_id": self.loc_zone2.id, "sequence": 2},
|
|
{"location_id": self.loc_zone3.id, "sequence": 3},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 100},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 100},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_empty_bin(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 300)
|
|
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 150)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 50)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 250)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
# This rule should be excluded for zone1 / bin1 because the
|
|
# bin would not be empty, but applied on zone1 / bin2.
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "empty_bin",
|
|
},
|
|
# this rule should be applied because we will empty the bin
|
|
{
|
|
"location_id": self.loc_zone2.id,
|
|
"sequence": 2,
|
|
"removal_strategy": "empty_bin",
|
|
},
|
|
{"location_id": self.loc_zone3.id, "sequence": 3},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone1_bin2.id, "product_qty": 150.0},
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 50.0},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 50.0},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_empty_bin_partial(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 50)
|
|
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 50)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 50)
|
|
picking = self._create_picking(self.wh, [(self.product1, 80)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "empty_bin",
|
|
},
|
|
{"location_id": self.loc_zone2.id, "sequence": 2},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
|
|
# We expect to take 50 in zone1/bin1 as it will empty a bin,
|
|
# but zone1/bin2 must not be used as it would not empty it.
|
|
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone1_bin1.id, "product_qty": 50.0},
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 30.0},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_empty_bin_fifo(self):
|
|
self._update_qty_in_location(
|
|
self.loc_zone1_bin1,
|
|
self.product1,
|
|
30,
|
|
in_date=fields.Datetime.to_datetime("2021-01-04 12:00:00"),
|
|
)
|
|
self._update_qty_in_location(
|
|
self.loc_zone1_bin2,
|
|
self.product1,
|
|
60,
|
|
in_date=fields.Datetime.to_datetime("2021-01-02 12:00:00"),
|
|
)
|
|
self._update_qty_in_location(
|
|
self.loc_zone2_bin1,
|
|
self.product1,
|
|
50,
|
|
in_date=fields.Datetime.to_datetime("2021-01-05 12:00:00"),
|
|
)
|
|
picking = self._create_picking(self.wh, [(self.product1, 80)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "empty_bin",
|
|
},
|
|
{"location_id": self.loc_zone2.id, "sequence": 2},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
|
|
# We expect to take 60 in zone1/bin2 as it will empty a bin and
|
|
# respecting fifo, the 60 of zone2 should be taken before the 30 of
|
|
# zone1. Then, as zone1/bin1 would not be empty, it is discarded. The
|
|
# remaining is taken in zone2 which has no rule.
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone1_bin2.id, "product_qty": 60.0},
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 20.0},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_packaging(self):
|
|
self._setup_packagings(
|
|
self.product1,
|
|
[("Pallet", 500, self.pallet), ("Retail Box", 50, self.retail_box)],
|
|
)
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 40)
|
|
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 510)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 60)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 590)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
# due to this rule and the packaging size of 500, we will
|
|
# not use zone1/bin1, but zone1/bin2 will be used.
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "packaging",
|
|
},
|
|
# zone2/bin2 will match the second packaging size of 50
|
|
{
|
|
"location_id": self.loc_zone2.id,
|
|
"sequence": 2,
|
|
"removal_strategy": "packaging",
|
|
},
|
|
# the rest should be taken here
|
|
{"location_id": self.loc_zone3.id, "sequence": 3},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone1_bin2.id, "product_qty": 500.0},
|
|
{"location_id": self.loc_zone2_bin1.id, "product_qty": 50.0},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 40.0},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_packaging_fifo(self):
|
|
self._setup_packagings(
|
|
self.product1,
|
|
[("Pallet", 500, self.pallet), ("Retail Box", 50, self.retail_box)],
|
|
)
|
|
self._update_qty_in_location(
|
|
self.loc_zone1_bin1,
|
|
self.product1,
|
|
500,
|
|
in_date=fields.Datetime.to_datetime("2021-01-04 12:00:00"),
|
|
)
|
|
self._update_qty_in_location(
|
|
self.loc_zone1_bin2,
|
|
self.product1,
|
|
500,
|
|
in_date=fields.Datetime.to_datetime("2021-01-02 12:00:00"),
|
|
)
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "packaging",
|
|
},
|
|
],
|
|
)
|
|
|
|
# take in bin2 to respect fifo
|
|
picking = self._create_picking(self.wh, [(self.product1, 50)])
|
|
picking.action_assign()
|
|
self.assertRecordValues(
|
|
picking.move_lines.move_line_ids,
|
|
[{"location_id": self.loc_zone1_bin2.id, "product_qty": 50.0}],
|
|
)
|
|
picking2 = self._create_picking(self.wh, [(self.product1, 50)])
|
|
picking2.action_assign()
|
|
self.assertRecordValues(
|
|
picking2.move_lines.move_line_ids,
|
|
[{"location_id": self.loc_zone1_bin2.id, "product_qty": 50.0}],
|
|
)
|
|
|
|
def test_rule_packaging_0_packaging(self):
|
|
# a packaging mistakenly created with a 0 qty should be ignored,
|
|
# not make the reservation fail
|
|
self._setup_packagings(
|
|
self.product1,
|
|
[
|
|
("Pallet", 500, self.pallet),
|
|
("Retail Box", 50, self.retail_box),
|
|
("DivisionByZero", 0, self.unit),
|
|
],
|
|
)
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 40)
|
|
picking = self._create_picking(self.wh, [(self.product1, 590)])
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "packaging",
|
|
}
|
|
],
|
|
)
|
|
# Here, it will try to reserve a pallet of 500, then an outer box of
|
|
# 50, then should ignore the one with 0 not to fail because of division
|
|
# by zero
|
|
picking.action_assign()
|
|
|
|
def test_rule_packaging_type(self):
|
|
# only take one kind of packaging
|
|
self._setup_packagings(
|
|
self.product1,
|
|
[
|
|
("Pallet", 500, self.pallet),
|
|
("Transport Box", 50, self.transport_box),
|
|
("Retail Box", 10, self.retail_box),
|
|
("Unit", 1, self.unit),
|
|
],
|
|
)
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 40)
|
|
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 600)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 30)
|
|
self._update_qty_in_location(self.loc_zone2_bin2, self.product1, 500)
|
|
self._update_qty_in_location(self.loc_zone3_bin1, self.product1, 500)
|
|
picking = self._create_picking(self.wh, [(self.product1, 560)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
# we'll take one pallet (500) from zone1/bin2, but as we filter
|
|
# on pallets only, we won't take the 600 out of it (if the rule
|
|
# had no type, we would have taken 100 of transport boxes).
|
|
{
|
|
"location_id": self.loc_zone1.id,
|
|
"sequence": 1,
|
|
"removal_strategy": "packaging",
|
|
"packaging_type_ids": [(6, 0, self.pallet.ids)],
|
|
},
|
|
# zone2/bin2 will match the second packaging size of 50,
|
|
# but won't take 60 because it doesn't take retail boxes
|
|
{
|
|
"location_id": self.loc_zone2.id,
|
|
"sequence": 2,
|
|
"removal_strategy": "packaging",
|
|
"packaging_type_ids": [(6, 0, self.transport_box.ids)],
|
|
},
|
|
# the rest should be taken here
|
|
{"location_id": self.loc_zone3.id, "sequence": 3},
|
|
],
|
|
)
|
|
picking.action_assign()
|
|
move = picking.move_lines
|
|
ml = move.move_line_ids
|
|
self.assertRecordValues(
|
|
ml,
|
|
[
|
|
{"location_id": self.loc_zone1_bin2.id, "product_qty": 500.0},
|
|
{"location_id": self.loc_zone2_bin2.id, "product_qty": 50.0},
|
|
{"location_id": self.loc_zone3_bin1.id, "product_qty": 10.0},
|
|
],
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|
|
|
|
def test_rule_excluded_not_child_location(self):
|
|
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 100)
|
|
self._update_qty_in_location(self.loc_zone2_bin1, self.product1, 100)
|
|
picking = self._create_picking(self.wh, [(self.product1, 80)])
|
|
|
|
self._create_rule(
|
|
{},
|
|
[
|
|
{"location_id": self.loc_zone1.id, "sequence": 1},
|
|
{"location_id": self.loc_zone2.id, "sequence": 2},
|
|
],
|
|
)
|
|
move = picking.move_lines
|
|
|
|
move.location_id = self.loc_zone2
|
|
picking.action_assign()
|
|
ml = move.move_line_ids
|
|
|
|
# As the source location of the stock.move is loc_zone2, we should
|
|
# never take any quantity in zone1.
|
|
|
|
self.assertRecordValues(
|
|
ml, [{"location_id": self.loc_zone2_bin1.id, "product_qty": 80.0}]
|
|
)
|
|
self.assertEqual(move.state, "assigned")
|