diff --git a/stock_picking_zone/demo/stock_location_demo.xml b/stock_picking_zone/demo/stock_location_demo.xml index 2d247286ec62..d721e0a8f957 100644 --- a/stock_picking_zone/demo/stock_location_demo.xml +++ b/stock_picking_zone/demo/stock_location_demo.xml @@ -22,7 +22,7 @@ Handover + eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/> diff --git a/stock_picking_zone/models/stock_move.py b/stock_picking_zone/models/stock_move.py index 1917db76a1c9..d3ec93b6bd25 100644 --- a/stock_picking_zone/models/stock_move.py +++ b/stock_picking_zone/models/stock_move.py @@ -62,12 +62,12 @@ def _insert_middle_moves(self): self.write({ 'move_dest_ids': [(3, dest_move.id), (4, middle_move.id)], }) - middle_move._action_confirm() + middle_move._action_confirm(merge=False) def _prepare_middle_move_values(self, destination): return { 'picking_id': False, - 'location_id': self.location_dest_id.id, + 'location_id': self.location_id.id, 'location_dest_id': destination.id, 'state': 'waiting', 'picking_type_id': self.picking_id.picking_type_id.id, diff --git a/stock_picking_zone/tests/test_picking_zone.py b/stock_picking_zone/tests/test_picking_zone.py index 8a2d17018275..7a3e23484d8c 100644 --- a/stock_picking_zone/tests/test_picking_zone.py +++ b/stock_picking_zone/tests/test_picking_zone.py @@ -21,26 +21,33 @@ def setUpClass(cls): 'name': 'Highbay', 'location_id': cls.wh.lot_stock_id.id, }) + cls.location_shelf_1 = cls.env['stock.location'].create({ + 'name': 'Shelf 1', + 'location_id': cls.wh.lot_stock_id.id, + }) cls.location_hb_1 = cls.env['stock.location'].create({ - 'name': 'Highbay Shelve 1', + 'name': 'Highbay Shelf 1', 'location_id': cls.location_hb.id, }) cls.location_hb_1_1 = cls.env['stock.location'].create({ - 'name': 'Highbay Shelve 1 Bin 1', + 'name': 'Highbay Shelf 1 Bin 1', 'location_id': cls.location_hb_1.id, }) cls.location_hb_1_2 = cls.env['stock.location'].create({ - 'name': 'Highbay Shelve 1 Bin 2', + 'name': 'Highbay Shelf 1 Bin 2', 'location_id': cls.location_hb_1.id, }) cls.location_handover = cls.env['stock.location'].create({ 'name': 'Handover', - 'location_id': cls.wh.view_location_id.id, + 'location_id': cls.wh.lot_stock_id.id, }) - cls.product_a = cls.env['product.product'].create({ - 'name': 'Product A', 'type': 'product', + cls.product1 = cls.env['product.product'].create({ + 'name': 'Product 1', 'type': 'product', + }) + cls.product2 = cls.env['product.product'].create({ + 'name': 'Product 2', 'type': 'product', }) picking_type_sequence = cls.env['ir.sequence'].create({ @@ -60,24 +67,18 @@ def setUpClass(cls): 'sequence_id': picking_type_sequence.id, }) - def _create_pick_ship(self, wh): + def _create_pick_ship(self, wh, products=[]): + """Create pick+ship pickings + + Products must be a list of tuples (product, quantity). + One stock move will be create for each tuple. + """ customer_picking = self.env['stock.picking'].create({ 'location_id': wh.wh_output_stock_loc_id.id, 'location_dest_id': self.customer_loc.id, 'partner_id': self.partner_delta.id, 'picking_type_id': wh.out_type_id.id, }) - dest = self.env['stock.move'].create({ - 'name': self.product_a.name, - 'product_id': self.product_a.id, - 'product_uom_qty': 10, - 'product_uom': self.product_a.uom_id.id, - 'picking_id': customer_picking.id, - 'location_id': wh.wh_output_stock_loc_id.id, - 'location_dest_id': self.customer_loc.id, - 'state': 'waiting', - 'procure_method': 'make_to_order', - }) pick_picking = self.env['stock.picking'].create({ 'location_id': wh.lot_stock_id.id, @@ -86,17 +87,30 @@ def _create_pick_ship(self, wh): 'picking_type_id': wh.pick_type_id.id, }) - self.env['stock.move'].create({ - 'name': self.product_a.name, - 'product_id': self.product_a.id, - 'product_uom_qty': 10, - 'product_uom': self.product_a.uom_id.id, - 'picking_id': pick_picking.id, - 'location_id': wh.lot_stock_id.id, - 'location_dest_id': wh.wh_output_stock_loc_id.id, - 'move_dest_ids': [(4, dest.id)], - 'state': 'confirmed', - }) + for product, qty in products: + dest = self.env['stock.move'].create({ + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': qty, + 'product_uom': product.uom_id.id, + 'picking_id': customer_picking.id, + 'location_id': wh.wh_output_stock_loc_id.id, + 'location_dest_id': self.customer_loc.id, + 'state': 'waiting', + 'procure_method': 'make_to_order', + }) + + self.env['stock.move'].create({ + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': qty, + 'product_uom': product.uom_id.id, + 'picking_id': pick_picking.id, + 'location_id': wh.lot_stock_id.id, + 'location_dest_id': wh.wh_output_stock_loc_id.id, + 'move_dest_ids': [(4, dest.id)], + 'state': 'confirmed', + }) return pick_picking, customer_picking def _update_product_qty_in_location(self, location, product, quantity): @@ -104,9 +118,41 @@ def _update_product_qty_in_location(self, location, product, quantity): product, location, quantity ) - def test_change_location_to_zone(self): + def assert_src_stock(self, record): + self.assertEqual(record.location_id, self.wh.lot_stock_id) + + def assert_src_handover(self, record): + self.assertEqual(record.location_id, self.location_handover) - pick_picking, customer_picking = self._create_pick_ship(self.wh) + def assert_dest_handover(self, record): + self.assertEqual(record.location_dest_id, self.location_handover) + + def assert_src_shelf1(self, record): + self.assertEqual(record.location_id, self.location_shelf_1) + + def assert_dest_shelf1(self, record): + self.assertEqual(record.location_dest_id, self.location_shelf_1) + + def assert_src_highbay_1_2(self, record): + self.assertEqual(record.location_id, self.location_hb_1_2) + + def assert_dest_highbay_1_2(self, record): + self.assertEqual(record.location_destid, self.location_hb_1_2) + + def assert_src_output(self, record): + self.assertEqual(record.location_id, self.wh.wh_output_stock_loc_id) + + def assert_dest_output(self, record): + self.assertEqual(record.location_dest_id, + self.wh.wh_output_stock_loc_id) + + def assert_dest_customer(self, record): + self.assertEqual(record.location_dest_id, self.customer_loc) + + def test_change_location_to_zone(self): + pick_picking, customer_picking = self._create_pick_ship( + self.wh, [(self.product1, 10)] + ) move_a = pick_picking.move_lines move_b = customer_picking.move_lines @@ -117,30 +163,167 @@ def test_change_location_to_zone(self): ml = move_a.move_line_ids self.assertEqual(len(ml), 1) - self.assertEqual(ml.location_id, self.location_hb_1_2) - self.assertEqual(ml.location_dest_id, self.location_handover) + self.assert_src_highbay_1_2(ml) + self.assert_dest_handover(ml) self.assertEqual(ml.picking_id.picking_type_id, self.pick_type_zone) - self.assertEqual(move_a.location_id, self.wh.lot_stock_id) - self.assertEqual(move_a.location_dest_id, self.location_handover) - # the move stays B stays on the same source location (sticky) - self.assertEqual(move_b.location_id, self.wh.wh_output_stock_loc_id) - self.assertEqual(move_b.location_dest_id, self.customer_loc) + self.assert_src_stock(move_a) + self.assert_dest_handover(move_a) + # the move stays B stays on the same source location + self.assert_src_output(move_b) + self.assert_dest_customer(move_b) move_middle = move_a.move_dest_ids - self.assertEqual(move_middle.location_id, move_a.location_dest_id) - self.assertEqual(move_middle.location_dest_id, move_b.location_id) + # the middle move stays in the same source location than the original + # move: the move line will be in the sub-locations (handover) - self.assertEqual( - move_a.picking_id.location_dest_id, - self.location_handover - ) - self.assertEqual( - move_middle.picking_id.location_id, - self.location_handover - ) + self.assert_src_stock(move_middle) + # Output + self.assert_dest_output(move_middle) + + self.assert_src_stock(move_a.picking_id) + self.assert_dest_handover(move_a.picking_id) self.assertEqual(move_a.state, 'assigned') self.assertEqual(move_middle.state, 'waiting') self.assertEqual(move_b.state, 'waiting') + + # we deliver move A to check that our middle move line properly takes + # goods from the handover + move_a.move_line_ids.qty_done = move_a.move_line_ids.product_uom_qty + move_a._action_done() + self.assertEqual(move_a.state, 'done') + self.assertEqual(move_middle.state, 'assigned') + self.assertEqual(move_b.state, 'waiting') + + move_line_middle = move_middle.move_line_ids + self.assertEqual(len(move_line_middle), 1) + self.assert_src_handover(move_line_middle) + self.assert_dest_output(move_line_middle) + + def test_several_move_lines(self): + pick_picking, customer_picking = self._create_pick_ship( + self.wh, [(self.product1, 10), (self.product2, 10)] + ) + product1 = self.product1 + product2 = self.product2 + # in Highbay → should generate a new operation in Highbay picking type + self._update_product_qty_in_location( + self.location_hb_1_2, self.product1, 20 + ) + # another product in a shelf, no additional operation for this one + self._update_product_qty_in_location( + self.location_shelf_1, self.product2, 20 + ) + pick_moves = pick_picking.move_lines + move_a_p1 = pick_moves.filtered(lambda r: r.product_id == product1) + move_a_p2 = pick_moves.filtered(lambda r: r.product_id == product2) + cust_moves = customer_picking.move_lines + move_b_p1 = cust_moves.filtered(lambda r: r.product_id == product1) + move_b_p2 = cust_moves.filtered(lambda r: r.product_id == product2) + + pick_picking.action_assign() + + # At this point, we should have 3 stock.picking: + # + # +-------------------------------------------------------------------+ + # | HO/xxxx Assigned | + # | Stock → Stock/Handover | + # | Product1 Highbay/Bay1/Bin1 → Stock/Handover (available) move_a_p1 | + # +-------------------------------------------------------------------+ + # + # +------------------------------------------------------------------+ + # | PICK/xxxx Waiting | + # | Stock → Output | + # | Product1 Stock/Handover → Output (waiting) move_middle (added) | + # | Product2 Stock/Shelf1 → Output (available) move_a_p2 | + # +------------------------------------------------------------------+ + # + # +------------------------------------------------+ + # | OUT/xxxx Waiting | + # | Output → Customer | + # | Product1 Output → Customer (waiting) move_b_p1 | + # | Product2 Output → Customer (waiting) move_b_p2 | + # +------------------------------------------------+ + + move_middle = move_a_p1.move_dest_ids + self.assertEqual(len(move_middle), 1) + + self.assertFalse(move_a_p1.move_orig_ids) + self.assertEqual(move_middle.move_dest_ids, move_b_p1) + self.assertFalse(move_a_p2.move_orig_ids) + self.assertEqual(move_a_p2.move_dest_ids, move_b_p2) + + ml = move_a_p1.move_line_ids + self.assertEqual(len(ml), 1) + self.assert_src_highbay_1_2(ml) + self.assert_dest_handover(ml) + # this is a new HO picking + self.assertEqual(ml.picking_id.picking_type_id, self.pick_type_zone) + self.assertEqual(ml.state, 'assigned') + + # this one stays in the PICK/ + ml = move_a_p2.move_line_ids + self.assertEqual(len(ml), 1) + self.assert_src_shelf1(ml) + self.assert_dest_output(ml) + self.assertEqual(ml.picking_id.picking_type_id, self.wh.pick_type_id) + self.assertEqual(ml.state, 'assigned') + + # the move stays B stays on the same source location + self.assert_src_output(move_b_p1) + self.assert_dest_customer(move_b_p1) + self.assertEqual(move_b_p1.state, 'waiting') + self.assert_src_output(move_b_p2) + self.assert_dest_customer(move_b_p2) + self.assertEqual(move_b_p2.state, 'waiting') + + # Check middle move + # Stock + self.assert_src_stock(move_middle) + # Output + self.assert_dest_output(move_middle) + self.assert_src_stock(move_middle.picking_id) + # we deliver move A to check that our middle move line properly takes + # goods from the handover + qty = move_a_p1.move_line_ids.product_uom_qty + move_a_p1.move_line_ids.qty_done = qty + move_a_p1.picking_id.action_done() + + self.assertEqual(move_a_p1.state, 'done') + self.assertEqual(move_a_p2.state, 'assigned') + self.assertEqual(move_middle.state, 'assigned') + self.assertEqual(move_b_p1.state, 'waiting') + self.assertEqual(move_b_p2.state, 'waiting') + + move_line_middle = move_middle.move_line_ids + self.assertEqual(len(move_line_middle), 1) + # Handover + self.assert_src_handover(move_line_middle) + # Output + self.assert_dest_output(move_line_middle) + + qty = move_middle.move_line_ids.product_uom_qty + move_middle.move_line_ids.qty_done = qty + qty = move_a_p2.move_line_ids.product_uom_qty + move_a_p2.move_line_ids.qty_done = qty + pick_picking.action_done() + + self.assertEqual(move_a_p1.state, 'done') + self.assertEqual(move_a_p2.state, 'done') + self.assertEqual(move_middle.state, 'done') + self.assertEqual(move_b_p1.state, 'assigned') + self.assertEqual(move_b_p2.state, 'assigned') + + qty = move_b_p1.move_line_ids.product_uom_qty + move_b_p1.move_line_ids.qty_done = qty + qty = move_b_p2.move_line_ids.product_uom_qty + move_b_p2.move_line_ids.qty_done = qty + customer_picking.action_done() + + self.assertEqual(move_a_p1.state, 'done') + self.assertEqual(move_a_p2.state, 'done') + self.assertEqual(move_middle.state, 'done') + self.assertEqual(move_b_p1.state, 'done') + self.assertEqual(move_b_p2.state, 'done')