# -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. from flectra.addons.stock.tests.common import TestStockCommon from flectra.exceptions import UserError from flectra import api, registry from flectra.tests.common import TransactionCase class TestPickShip(TestStockCommon): def create_pick_ship(self): picking_client = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) dest = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 10, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_client.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'state': 'waiting', 'procure_method': 'make_to_order', }) picking_pick = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 10, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_pick.id, 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'move_dest_ids': [(4, dest.id)], 'state': 'confirmed', }) return picking_pick, picking_client def create_pick_pack_ship(self): picking_ship = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) ship = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_ship.id, 'location_id': self.output_location, 'location_dest_id': self.customer_location, }) picking_pack = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) pack = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_pack.id, 'location_id': self.pack_location, 'location_dest_id': self.output_location, 'move_dest_ids': [(4, ship.id)], }) picking_pick = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_pick.id, 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'move_dest_ids': [(4, pack.id)], 'state': 'confirmed', }) return picking_pick, picking_pack, picking_ship def test_mto_moves(self): """ 10 in stock, do pick->ship and check ship is assigned when pick is done, then backorder of ship """ picking_pick, picking_client = self.create_pick_ship() location = self.env['stock.location'].browse(self.stock_location) # make some stock self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.do_transfer() self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned') # Now partially transfer the ship picking_client.move_lines[0].move_line_ids[0].qty_done = 5 picking_client.do_transfer() # no new in order to create backorder backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_client.id)]) self.assertEqual(backorder.state, 'assigned', 'Backorder should be started') def test_mto_moves_transfer(self): """ 10 in stock, 5 in pack. Make sure it does not assign the 5 pieces in pack """ picking_pick, picking_client = self.create_pick_ship() stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0) pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 5.0) self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 1.0) self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0) (picking_pick + picking_client).action_assign() move_pick = picking_pick.move_lines move_cust = picking_client.move_lines self.assertEqual(move_pick.state, 'assigned') self.assertEqual(picking_pick.state, 'assigned') self.assertEqual(move_cust.state, 'waiting') self.assertEqual(picking_client.state, 'waiting', 'The picking should not assign what it does not have') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 5.0) move_pick.move_line_ids[0].qty_done = 10.0 picking_pick.do_transfer() self.assertEqual(move_pick.state, 'done') self.assertEqual(picking_pick.state, 'done') self.assertEqual(move_cust.state, 'assigned') self.assertEqual(picking_client.state, 'assigned', 'The picking should not assign what it does not have') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 5.0) self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 0.0) self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0) def test_mto_moves_return(self): picking_pick, picking_client = self.create_pick_ship() stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.do_transfer() self.assertEqual(picking_pick.state, 'done') self.assertEqual(picking_client.state, 'assigned') # return a part of what we've done stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 2.0 # Return only 2 stock_return_picking_action = stock_return_picking.create_returns() return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_pick.move_lines[0].move_line_ids[0].qty_done = 2.0 return_pick.do_transfer() # the client picking should not be assigned anymore, as we returned partially what we took self.assertEqual(picking_client.state, 'confirmed') def test_mto_moves_return_return(self): picking_pick, picking_client = self.create_pick_ship() stock_location = self.env['stock.location'].browse(self.stock_location) lot = self.env['stock.production.lot'].create({ 'product_id': self.productA.id, 'name': '123456789' }) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0, lot_id=lot) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.button_validate() self.assertEqual(picking_pick.state, 'done') self.assertEqual(picking_client.state, 'assigned') # return this picking stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 10.0 stock_return_picking_action = stock_return_picking.create_returns() return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_pick.move_lines[0].move_line_ids[0].write({ 'qty_done': 10.0, 'lot_id': lot.id, }) return_pick.button_validate() # return this return of this picking stock_return_picking = self.env['stock.return.picking']\ .with_context(active_id=return_pick.id)\ .create({}) stock_return_picking.product_return_moves.quantity = 10.0 stock_return_picking_action = stock_return_picking.create_returns() return_return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_return_pick.move_lines[0].move_line_ids[0].write({ 'qty_done': 10.0, 'lot_id': lot.id, }) return_return_pick.button_validate() # test computation of traceability vals = { 'line_id': 1, 'model_name': 'stock.move.line', 'level': 11, 'parent_quant': False, } lines = self.env['stock.traceability.report'].get_lines( model_id=return_return_pick.move_line_ids[0].id, stream='upstream', **vals ) self.assertEqual( [l.get('res_id') for l in lines], [return_return_pick.id, return_pick.id, picking_pick.id], "Upstream computation from return of return worked" ) lines = self.env['stock.traceability.report'].get_lines( model_id=picking_pick.move_line_ids[0].id, stream='downstream', **vals ) self.assertEqual( [l.get('res_id') for l in lines], [picking_pick.id, return_pick.id, return_return_pick.id], "Downstream computation from original picking worked" ) def test_mto_resupply_cancel_ship(self): """ This test simulates a pick pack ship with a resupply route set. Pick and pack are validated, ship is cancelled. This test ensure that new picking are not created from the cancelled ship after the scheduler task. The supply route is only set in order to make the scheduler run without mistakes (no next activity). """ picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship() stock_location = self.env['stock.location'].browse(self.stock_location) warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) warehouse_1.write({'delivery_steps': 'pick_pack_ship'}) warehouse_2 = self.env['stock.warehouse'].create({ 'name': 'Small Warehouse', 'code': 'SWH' }) warehouse_1.write({ 'default_resupply_wh_id': warehouse_2.id, 'resupply_wh_ids': [(6, 0, [warehouse_2.id])] }) resupply_route = self.env['stock.location.route'].search([('supplier_wh_id', '=', warehouse_2.id), ('supplied_wh_id', '=', warehouse_1.id)]) self.assertTrue(resupply_route) self.productA.write({'route_ids': [(4, resupply_route.id), (4, self.env.ref('stock.route_warehouse0_mto').id)]}) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.action_done() picking_pack.action_assign() picking_pack.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pack.action_done() picking_ship.action_cancel() picking_ship.move_lines.write({'procure_method': 'make_to_order'}) self.env['procurement.group'].run_scheduler() next_activity = self.env['mail.activity'].search([('res_model', '=', 'product.template'), ('res_id', '=', self.productA.product_tmpl_id.id)]) self.assertEqual(picking_ship.state, 'cancel') self.assertFalse(next_activity, 'If a next activity has been created if means that scheduler failed\ and the end of this test do not have sense.') self.assertEqual(len(picking_ship.move_lines.mapped('move_orig_ids')), 0, 'Scheduler should not create picking pack and pick since ship has been manually cancelled.') def test_no_backorder_1(self): """ Check the behavior of doing less than asked in the picking pick and chosing not to create a backorder. In this behavior, the second picking should obviously only be able to reserve what was brought, but its initial demand should stay the same and the system will ask the user will have to consider again if he wants to create a backorder or not. """ picking_pick, picking_client = self.create_pick_ship() location = self.env['stock.location'].browse(self.stock_location) # make some stock self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0 # create a backorder picking_pick.do_transfer() picking_pick_backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pick.id)]) self.assertEqual(picking_pick_backorder.state, 'assigned') self.assertEqual(picking_pick_backorder.move_line_ids.product_qty, 5.0) self.assertEqual(picking_client.state, 'assigned') # cancel the backorder picking_pick_backorder.action_cancel() self.assertEqual(picking_client.state, 'assigned') def test_edit_done_chained_move(self): """ Let’s say two moves are chained: the first is done and the second is assigned. Editing the move line of the first move should impact the reservation of the second one. """ picking_pick, picking_client = self.create_pick_ship() location = self.env['stock.location'].browse(self.stock_location) # make some stock self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.action_done() self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done') self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned') self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move') self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move') self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move') picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0 self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done') self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be partially available') self.assertEqual(picking_pick.move_lines.quantity_done, 5.0, 'Wrong quantity_done for pick move') self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move') self.assertEqual(picking_client.move_lines.reserved_availability, 5.0, 'Wrong quantity already reserved for client move') # Check if run action_assign does not crash picking_client.action_assign() def test_edit_done_chained_move_with_lot(self): """ Let’s say two moves are chained: the first is done and the second is assigned. Editing the lot on the move line of the first move should impact the reservation of the second one. """ self.productA.tracking = 'lot' lot1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': self.productA.id, }) lot2 = self.env['stock.production.lot'].create({ 'name': 'lot2', 'product_id': self.productA.id, }) picking_pick, picking_client = self.create_pick_ship() location = self.env['stock.location'].browse(self.stock_location) # make some stock self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].write({ 'qty_done': 10.0, 'lot_id': lot1.id, }) picking_pick.action_done() self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done') self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned') self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move') self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move') self.assertEqual(picking_client.move_lines.move_line_ids.lot_id, lot1, 'Wrong lot for client move line') self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move') picking_pick.move_lines[0].move_line_ids[0].lot_id = lot2.id self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done') self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be partially available') self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move') self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move') self.assertEqual(picking_client.move_lines.move_line_ids.lot_id, lot2, 'Wrong lot for client move line') self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move') # Check if run action_assign does not crash picking_client.action_assign() def test_chained_move_with_uom(self): """ Create pick ship with a different uom than the once used for quant. Check that reserved quantity and flow work correctly. """ picking_client = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) dest = self.MoveObj.create({ 'name': self.gB.name, 'product_id': self.gB.id, 'product_uom_qty': 5, 'product_uom': self.uom_kg.id, 'picking_id': picking_client.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'state': 'waiting', }) picking_pick = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.gB.name, 'product_id': self.gB.id, 'product_uom_qty': 5, 'product_uom': self.uom_kg.id, 'picking_id': picking_pick.id, 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'move_dest_ids': [(4, dest.id)], 'state': 'confirmed', }) location = self.env['stock.location'].browse(self.stock_location) pack_location = self.env['stock.location'].browse(self.pack_location) # make some stock self.env['stock.quant']._update_available_quantity(self.gB, location, 10000.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0 picking_pick.action_done() self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, location), 5000.0) self.assertEqual(self.env['stock.quant']._gather(self.gB, pack_location).quantity, 5000.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0) self.assertEqual(picking_client.state, 'assigned') self.assertEqual(picking_client.move_lines.reserved_availability, 5.0) def test_pick_ship_return(self): """ Create pick and ship. Bring it ot the customer and then return it to stock. This test check the state and the quantity after each move in order to ensure that it is correct. """ picking_pick, picking_ship = self.create_pick_ship() stock_location = self.env['stock.location'].browse(self.stock_location) pack_location = self.env['stock.location'].browse(self.pack_location) customer_location = self.env['stock.location'].browse(self.customer_location) self.productA.tracking = 'lot' lot = self.env['stock.production.lot'].create({ 'product_id': self.productA.id, 'name': '123456789' }) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0, lot_id=lot) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_pick.action_done() self.assertEqual(picking_pick.state, 'done') self.assertEqual(picking_ship.state, 'assigned') picking_ship.action_assign() picking_ship.move_lines[0].move_line_ids[0].qty_done = 10.0 picking_ship.action_done() customer_quantity = self.env['stock.quant']._get_available_quantity(self.productA, customer_location, lot_id=lot) self.assertEqual(customer_quantity, 10, 'It should be one product in customer') """ First we create the return picking for pick pinking. Since we do not have created the return between customer and output. This return should not be available and should only have picking pick as origin move. """ stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 10.0 stock_return_picking_action = stock_return_picking.create_returns() return_pick_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) self.assertEqual(return_pick_picking.state, 'waiting') stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_ship.ids, active_id=picking_ship.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 10.0 stock_return_picking_action = stock_return_picking.create_returns() return_ship_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) self.assertEqual(return_ship_picking.state, 'assigned', 'Return ship picking should automatically be assigned') """ We created the return for ship picking. The origin/destination link between return moves should have been created during return creation. """ self.assertTrue(return_ship_picking.move_lines in return_pick_picking.move_lines.mapped('move_orig_ids'), 'The pick return picking\'s moves should have the ship return picking\'s moves as origin') self.assertTrue(return_pick_picking.move_lines in return_ship_picking.move_lines.mapped('move_dest_ids'), 'The ship return picking\'s moves should have the pick return picking\'s moves as destination') return_ship_picking.move_lines[0].move_line_ids[0].write({ 'qty_done': 10.0, 'lot_id': lot.id, }) return_ship_picking.action_done() self.assertEqual(return_ship_picking.state, 'done') self.assertEqual(return_pick_picking.state, 'assigned') customer_quantity = self.env['stock.quant']._get_available_quantity(self.productA, customer_location, lot_id=lot) self.assertEqual(customer_quantity, 0, 'It should be one product in customer') pack_quantity = self.env['stock.quant']._get_available_quantity(self.productA, pack_location, lot_id=lot) self.assertEqual(pack_quantity, 0, 'It should be one product in pack location but is reserved') # Should use previous move lot. return_pick_picking.move_lines[0].move_line_ids[0].qty_done = 10.0 return_pick_picking.action_done() self.assertEqual(return_pick_picking.state, 'done') stock_quantity = self.env['stock.quant']._get_available_quantity(self.productA, stock_location, lot_id=lot) self.assertEqual(stock_quantity, 10, 'The product is not back in stock') def test_pick_pack_ship_return(self): """ This test do a pick pack ship delivery to customer and then return it to stock. Once everything is done, this test will check if all the link orgini/destination between moves are correct. """ picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship() stock_location = self.env['stock.location'].browse(self.stock_location) self.productA.tracking = 'serial' lot = self.env['stock.production.lot'].create({ 'product_id': self.productA.id, 'name': '123456789' }) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot) picking_pick.action_assign() picking_pick.move_lines[0].move_line_ids[0].qty_done = 1.0 picking_pick.action_done() picking_pack.action_assign() picking_pack.move_lines[0].move_line_ids[0].qty_done = 1.0 picking_pack.action_done() picking_ship.action_assign() picking_ship.move_lines[0].move_line_ids[0].qty_done = 1.0 picking_ship.action_done() stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_ship.ids, active_id=picking_ship.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.create_returns() return_ship_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_ship_picking.move_lines[0].move_line_ids[0].write({ 'qty_done': 1.0, 'lot_id': lot.id, }) return_ship_picking.action_done() stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_pack.ids, active_id=picking_pack.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.create_returns() return_pack_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_pack_picking.move_lines[0].move_line_ids[0].qty_done = 1.0 return_pack_picking.action_done() stock_return_picking = self.env['stock.return.picking']\ .with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\ .create({}) stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.create_returns() return_pick_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_pick_picking.move_lines[0].move_line_ids[0].qty_done = 1.0 return_pick_picking.action_done() # Now that everything is returned we will check if the return moves are correctly linked between them. # +--------------------------------------------------------------------------------------------------------+ # | -- picking_pick(1) --> -- picking_pack(2) --> -- picking_ship(3) --> # | Stock Pack Output Customer # | <--- return pick(6) -- <--- return pack(5) -- <--- return ship(4) -- # +--------------------------------------------------------------------------------------------------------+ # Recaps of final link (MO = move_orig_ids, MD = move_dest_ids) # picking_pick(1) : MO = (), MD = (2,6) # picking_pack(2) : MO = (1), MD = (3,5) # picking ship(3) : MO = (2), MD = (4) # return ship(4) : MO = (3), MD = (5) # return pack(5) : MO = (2, 4), MD = (6) # return pick(6) : MO = (1, 5), MD = () self.assertEqual(len(picking_pick.move_lines.move_orig_ids), 0, 'Picking pick should not have origin moves') self.assertEqual(set(picking_pick.move_lines.move_dest_ids.ids), set((picking_pack.move_lines | return_pick_picking.move_lines).ids)) self.assertEqual(set(picking_pack.move_lines.move_orig_ids.ids), set(picking_pick.move_lines.ids)) self.assertEqual(set(picking_pack.move_lines.move_dest_ids.ids), set((picking_ship.move_lines | return_pack_picking.move_lines).ids)) self.assertEqual(set(picking_ship.move_lines.move_orig_ids.ids), set(picking_pack.move_lines.ids)) self.assertEqual(set(picking_ship.move_lines.move_dest_ids.ids), set(return_ship_picking.move_lines.ids)) self.assertEqual(set(return_ship_picking.move_lines.move_orig_ids.ids), set(picking_ship.move_lines.ids)) self.assertEqual(set(return_ship_picking.move_lines.move_dest_ids.ids), set(return_pack_picking.move_lines.ids)) self.assertEqual(set(return_pack_picking.move_lines.move_orig_ids.ids), set((picking_pack.move_lines | return_ship_picking.move_lines).ids)) self.assertEqual(set(return_pack_picking.move_lines.move_dest_ids.ids), set(return_pick_picking.move_lines.ids)) self.assertEqual(set(return_pick_picking.move_lines.move_orig_ids.ids), set((picking_pick.move_lines | return_pack_picking.move_lines).ids)) self.assertEqual(len(return_pick_picking.move_lines.move_dest_ids), 0) def test_put_in_pack(self): """ In a pick pack ship scenario, create two packs in pick and check that they are correctly recognised and handled by the pack and ship picking. Along this test, we'll use action_toggle_processed to process a pack from the entire_package_ids one2many and we'll directly fill the move lines, the latter is the behavior when the user did not enable the display of entire packs on the picking type. """ picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship() ship_move = self.env['stock.move'].create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 4, 'product_uom': self.productB.uom_id.id, 'picking_id': picking_ship.id, 'location_id': self.output_location, 'location_dest_id': self.customer_location, }) pack_move = self.env['stock.move'].create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 4, 'product_uom': self.productB.uom_id.id, 'picking_id': picking_pack.id, 'location_id': self.pack_location, 'location_dest_id': self.output_location, 'move_dest_ids': [(4, ship_move.id)], }) self.env['stock.move'].create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 4, 'product_uom': self.productB.uom_id.id, 'picking_id': picking_pick.id, 'location_id': self.stock_location, 'location_dest_id': self.pack_location, 'move_dest_ids': [(4, pack_move.id)], 'state': 'confirmed', }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0) self.env['stock.quant']._update_available_quantity(self.productB, stock_location, 4.0) picking_pick.action_assign() picking_pick.move_line_ids.filtered(lambda ml: ml.product_id == self.productA).qty_done = 1.0 picking_pick.move_line_ids.filtered(lambda ml: ml.product_id == self.productB).qty_done = 2.0 first_pack = picking_pick.put_in_pack() picking_pick.move_line_ids.filtered(lambda ml: ml.product_id == self.productB and ml.qty_done == 0.0).qty_done = 2.0 second_pack = picking_pick.put_in_pack() picking_pick.button_validate() self.assertEqual(len(first_pack.quant_ids), 2) self.assertEqual(len(second_pack.quant_ids), 1) picking_pack.action_assign() self.assertEqual(len(picking_pack.entire_package_ids), 2) output_sub_location = self.env['stock.location'].create({'name': 'sub1', 'location_id': self.output_location}) picking_pack.entire_package_ids[0].with_context( {'picking_id': picking_pack.id, 'destination_location': output_sub_location.id}).action_toggle_processed() picking_pack.entire_package_ids[1].with_context( {'picking_id': picking_pack.id, 'destination_location': output_sub_location.id}).action_toggle_processed() self.assertEqual(picking_pack.move_line_ids[:1].location_dest_id, output_sub_location) picking_pack.button_validate() self.assertEqual(first_pack.location_id, output_sub_location) self.assertEqual(second_pack.location_id, output_sub_location) self.assertEqual(len(picking_pack.entire_package_ids), 2) picking_ship.action_assign() self.assertEqual(picking_ship.move_line_ids[:1].location_id, output_sub_location) for move_line in picking_ship.move_line_ids: move_line.qty_done = move_line.product_uom_qty picking_ship.button_validate() quants_of_products_ab = self.env['stock.quant'].search([('product_id', 'in', (self.productA.id, self.productB.id))]) quants_of_product_a = quants_of_products_ab.filtered(lambda q: q.product_id == self.productA) quants_of_product_b = quants_of_products_ab.filtered(lambda q: q.product_id == self.productB) self.assertEqual(quants_of_products_ab.mapped('location_id.id'), [self.customer_location]) self.assertEqual(len(quants_of_product_a), 1) self.assertEqual(len(quants_of_product_b), 2) def test_merge_move_mto_mts(self): """ Create 2 moves of the same product in the same picking with one in 'MTO' and the other one in 'MTS'. The moves shouldn't be merged """ picking_pick, picking_client = self.create_pick_ship() self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 3, 'product_uom': self.productA.uom_id.id, 'picking_id': picking_client.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'origin': 'MPS', 'procure_method': 'make_to_stock', }) picking_client.action_confirm() self.assertEqual(len(picking_client.move_lines), 2, 'Moves should not be merged') def test_mto_cancel_move_line(self): """ Create a pick ship situation. Then process the pick picking with a backorder. Then try to unlink the move line created on the ship and check if the picking and move state are updated. Then validate the backorder and unlink the ship move lines in order to check again if the picking and state are updated. """ picking_pick, picking_client = self.create_pick_ship() location = self.env['stock.location'].browse(self.stock_location) # make some stock self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.move_lines.quantity_done = 5.0 backorder_wizard_values = picking_pick.button_validate() backorder_wizard = self.env[(backorder_wizard_values.get('res_model'))].browse(backorder_wizard_values.get('res_id')) backorder_wizard.process() self.assertTrue(picking_client.move_line_ids, 'A move line should be created.') self.assertEqual(picking_client.move_line_ids.product_uom_qty, 5, 'The move line should have 5 unit reserved.') # Directly delete the move lines on the picking. (Use show detail operation on picking type) # Should do the same behavior than unreserve picking_client.move_line_ids.unlink() self.assertEqual(picking_client.move_lines.state, 'waiting', 'The move state should be waiting since nothing is reserved and another origin move still in progess.') self.assertEqual(picking_client.state, 'waiting', 'The picking state should not be ready anymore.') picking_client.action_assign() back_order = self.env['stock.picking'].search([('backorder_id', '=', picking_pick.id)]) back_order.move_lines.quantity_done = 5 back_order.button_validate() self.assertEqual(picking_client.move_lines.reserved_availability, 10, 'The total quantity should be reserved since everything is available.') picking_client.move_line_ids.unlink() self.assertEqual(picking_client.move_lines.state, 'confirmed', 'The move should be confirmed since all the origin moves are processed.') self.assertEqual(picking_client.state, 'confirmed', 'The picking should be confirmed since all the moves are confirmed.') def test_unreserve(self): picking_pick, picking_client = self.create_pick_ship() self.assertEqual(picking_pick.state, 'confirmed') picking_pick.do_unreserve() self.assertEqual(picking_pick.state, 'confirmed') location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0) picking_pick.action_assign() self.assertEqual(picking_pick.state, 'assigned') picking_pick.do_unreserve() self.assertEqual(picking_pick.state, 'confirmed') self.assertEqual(picking_client.state, 'waiting') picking_client.do_unreserve() self.assertEqual(picking_client.state, 'waiting') class TestSinglePicking(TestStockCommon): def test_backorder_1(self): """ Check the good behavior of creating a backorder for an available stock move. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2) # assign delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) # valid with backorder creation delivery_order.move_lines[0].move_line_ids[0].qty_done = 1 delivery_order.do_transfer() self.assertNotEqual(delivery_order.date_done, False) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)]) self.assertEqual(backorder.state, 'assigned') def test_backorder_2(self): """ Check the good behavior of creating a backorder for a partially available stock move. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1) # assign to partially available delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) # valid with backorder creation delivery_order.move_lines[0].move_line_ids[0].qty_done = 1 delivery_order.do_transfer() self.assertNotEqual(delivery_order.date_done, False) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)]) self.assertEqual(backorder.state, 'confirmed') def test_backorder_3(self): """ Check the good behavior of creating a backorder for an available move on a picking with two available moves. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productB.id, 'product_uom_qty': 2, 'product_uom': self.productB.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2) # assign to partially available delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') delivery_order.move_lines[0].move_line_ids[0].qty_done = 2 delivery_order.do_transfer() backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)]) self.assertEqual(backorder.state, 'confirmed') def test_extra_move_1(self): """ Check the good behavior of creating an extra move in a delivery order. This usecase simulates the delivery of 2 item while the initial stock move had to move 1 and there's only 1 in stock. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0) # assign to available delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) # valid with backorder creation delivery_order.move_lines[0].move_line_ids[0].qty_done = 2 self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) delivery_order.with_context(debug=True).do_transfer() self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location, allow_negative=True), -1.0) self.assertEqual(move1.product_qty, 2.0) self.assertEqual(move1.quantity_done, 2.0) self.assertEqual(move1.reserved_availability, 0.0) self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 2.0) self.assertEqual(move1.state, 'done') def test_extra_move_2(self): """ Check the good behavior of creating an extra move in a delivery order. This usecase simulates the delivery of 3 item while the initial stock move had to move 1 and there's only 1 in stock. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0) # assign to available delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) # valid with backorder creation delivery_order.move_lines[0].move_line_ids[0].qty_done = 3 self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) delivery_order.do_transfer() self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location, allow_negative=True), -2.0) self.assertEqual(move1.product_qty, 3.0) self.assertEqual(move1.quantity_done, 3.0) self.assertEqual(move1.reserved_availability, 0.0) self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 3.0) self.assertEqual(move1.state, 'done') def test_extra_move_3(self): """ Check the good behavior of creating an extra move in a receipt. This usecase simulates the receipt of 2 item while the initial stock move had to move 1. """ receipt = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_in, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, }) stock_location = self.env['stock.location'].browse(self.stock_location) # assign to available receipt.action_confirm() receipt.action_assign() self.assertEqual(receipt.state, 'assigned') # valid with backorder creation receipt.move_lines[0].move_line_ids[0].qty_done = 2 receipt.with_context(debug=True).do_transfer() self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 2.0) self.assertEqual(move1.product_qty, 2.0) self.assertEqual(move1.quantity_done, 2.0) self.assertEqual(move1.reserved_availability, 0.0) self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 2.0) self.assertEqual(move1.state, 'done') def test_extra_move_4(self): """ Create a picking with similar moves (created after confirmation). Action done should propagate all the extra quantity and only merge extra moves in their original moves. """ delivery = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 5, 'quantity_done': 10, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 5) delivery.action_confirm() delivery.action_assign() delivery.write({ 'move_lines': [(0, 0, { 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 0, 'quantity_done': 10, 'state': 'assigned', 'product_uom': self.productA.uom_id.id, 'picking_id': delivery.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, })] }) delivery.action_done() self.assertEqual(len(delivery.move_lines), 2, 'Move should not be merged together') for move in delivery.move_lines: self.assertEqual(move.quantity_done, move.product_uom_qty, 'Initial demand should be equals to quantity done') def test_extra_move_5(self): """ Create a picking a move that is problematic with rounding (5.95 - 5.5 = 0.4500000000000002). Ensure that initial demand is corrct afer action_done and backoder are not created. """ delivery = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 5.5, 'quantity_done': 5.95, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 5.5) delivery.action_confirm() delivery.action_assign() delivery.action_done() self.assertEqual(delivery.move_lines.product_uom_qty, 5.95, 'Move initial demand should be 5.95') back_order = self.env['stock.picking'].search([('backorder_id', '=', delivery.id)]) self.assertFalse(back_order, 'There should be no back order') def test_recheck_availability_1(self): """ Check the good behavior of check availability. I create a DO for 2 unit with only one in stock. After the first check availability, I should have 1 reserved product with one move line. After adding a second unit in stock and recheck availability. The DO should have 2 reserved unit, be in available state and have only one move line. """ self.env['stock.quant']._update_available_quantity(self.productA, self.env['stock.location'].browse(self.stock_location), 1.0) delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() # Check State self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'partially_available') # Check reserved quantity self.assertEqual(move1.reserved_availability, 1.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.product_qty, 1) inventory = self.env['stock.inventory'].create({ 'name': 'remove product1', 'filter': 'product', 'location_id': self.stock_location, 'product_id': self.productA.id, }) inventory.action_start() inventory.line_ids.product_qty = 2 inventory.action_done() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'assigned') # Check reserved quantity self.assertEqual(move1.reserved_availability, 2.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.product_qty, 2) def test_recheck_availability_2(self): """ Same check than test_recheck_availability_1 but with lot this time. If the new product has the same lot that already reserved one, the move lines reserved quantity should be updated. Otherwise a new move lines with the new lot should be added. """ self.productA.tracking = 'lot' lot1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': self.productA.id, }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1) delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() # Check State self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'partially_available') # Check reserved quantity self.assertEqual(move1.reserved_availability, 1.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.product_qty, 1) inventory = self.env['stock.inventory'].create({ 'name': 'remove product1', 'filter': 'product', 'location_id': self.stock_location, 'product_id': self.productA.id, }) inventory.action_start() inventory.line_ids.prod_lot_id = lot1 inventory.line_ids.product_qty = 2 inventory.action_done() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'assigned') # Check reserved quantity self.assertEqual(move1.reserved_availability, 2.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.lot_id.id, lot1.id) self.assertEqual(move1.move_line_ids.product_qty, 2) def test_recheck_availability_3(self): """ Same check than test_recheck_availability_2 but with different lots. """ self.productA.tracking = 'lot' lot1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': self.productA.id, }) lot2 = self.env['stock.production.lot'].create({ 'name': 'lot2', 'product_id': self.productA.id, }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1) delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() # Check State self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'partially_available') # Check reserved quantity self.assertEqual(move1.reserved_availability, 1.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.product_qty, 1) inventory = self.env['stock.inventory'].create({ 'name': 'remove product1', 'filter': 'product', 'location_id': self.stock_location, 'product_id': self.productA.id, }) inventory.action_start() self.env['stock.inventory.line'].create({ 'inventory_id': inventory.id, 'location_id': inventory.location_id.id, 'partner_id': inventory.partner_id.id, 'prod_lot_id': lot2.id, 'product_id': self.productA.id, 'product_qty': 1, }) inventory.action_done() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'assigned') # Check reserved quantity self.assertEqual(move1.reserved_availability, 2.0) self.assertEqual(len(move1.move_line_ids), 2) self.assertEqual(move1.move_line_ids[0].lot_id.id, lot1.id) self.assertEqual(move1.move_line_ids[1].lot_id.id, lot2.id) def test_recheck_availability_4(self): """ Same check than test_recheck_availability_2 but with serial number this time. Serial number reservation should always create a new move line. """ self.productA.tracking = 'serial' serial1 = self.env['stock.production.lot'].create({ 'name': 'serial1', 'product_id': self.productA.id, }) serial2 = self.env['stock.production.lot'].create({ 'name': 'serial2', 'product_id': self.productA.id, }) stock_location = self.env['stock.location'].browse(self.stock_location) self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=serial1) delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() # Check State self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'partially_available') # Check reserved quantity self.assertEqual(move1.reserved_availability, 1.0) self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(move1.move_line_ids.product_qty, 1) inventory = self.env['stock.inventory'].create({ 'name': 'remove product1', 'filter': 'product', 'location_id': self.stock_location, 'product_id': self.productA.id, }) inventory.action_start() self.env['stock.inventory.line'].create({ 'inventory_id': inventory.id, 'location_id': inventory.location_id.id, 'partner_id': inventory.partner_id.id, 'prod_lot_id': serial2.id, 'product_id': self.productA.id, 'product_qty': 1, }) inventory.action_done() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') self.assertEqual(move1.state, 'assigned') # Check reserved quantity self.assertEqual(move1.reserved_availability, 2.0) self.assertEqual(len(move1.move_line_ids), 2) self.assertEqual(move1.move_line_ids[0].lot_id.id, serial1.id) self.assertEqual(move1.move_line_ids[1].lot_id.id, serial2.id) def test_add_move_when_picking_is_available_1(self): """ Check that any move added in a picking once it's assigned is directly considered as assigned and bypass the reservation. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) # make some stock pack_location = self.env['stock.location'].browse(self.pack_location) self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2) # assign delivery_order.action_confirm() delivery_order.action_assign() self.assertEqual(delivery_order.state, 'assigned') # add a move move2 = self.MoveObj\ .with_context(default_picking_id=delivery_order.id)\ .create({ 'name': self.productA.name, 'product_id': self.productB.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) self.assertEqual(move2.state, 'assigned') self.assertEqual(delivery_order.state, 'assigned') def test_use_create_lot_use_existing_lot_1(self): """ Check the behavior of a picking when `use_create_lot` and `use_existing_lot` are set to False and there's a move for a tracked product. """ self.env['stock.picking.type']\ .browse(self.picking_type_out)\ .write({ 'use_create_lots': False, 'use_existing_lots': False, }) self.productA.tracking = 'lot' delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'picking_type_id': self.picking_type_out, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.force_assign() delivery_order.move_lines.quantity_done = 2 # do not set a lot_id or lot_name, it should work delivery_order.action_done() def test_use_create_lot_use_existing_lot_2(self): """ Check the behavior of a picking when `use_create_lot` and `use_existing_lot` are set to True and there's a move for a tracked product. """ self.env['stock.picking.type']\ .browse(self.picking_type_out)\ .write({ 'use_create_lots': True, 'use_existing_lots': True, }) self.productA.tracking = 'lot' delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'picking_type_id': self.picking_type_out, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.force_assign() delivery_order.move_lines.quantity_done = 2 move_line = delivery_order.move_lines.move_line_ids # not lot_name set, should raise with self.assertRaises(UserError): delivery_order.action_done() # enter a new lot name, should work move_line.lot_name = 'newlot' delivery_order.action_done() def test_use_create_lot_use_existing_lot_3(self): """ Check the behavior of a picking when `use_create_lot` is set to True and `use_existing_lot` is set to False and there's a move for a tracked product. """ self.env['stock.picking.type']\ .browse(self.picking_type_out)\ .write({ 'use_create_lots': True, 'use_existing_lots': False, }) self.productA.tracking = 'lot' delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'picking_type_id': self.picking_type_out, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.force_assign() delivery_order.move_lines.quantity_done = 2 move_line = delivery_order.move_lines.move_line_ids # not lot_name set, should raise with self.assertRaises(UserError): delivery_order.action_done() # enter a new lot name, should work move_line.lot_name = 'newlot' delivery_order.action_done() def test_use_create_lot_use_existing_lot_4(self): """ Check the behavior of a picking when `use_create_lot` is set to False and `use_existing_lot` is set to True and there's a move for a tracked product. """ self.env['stock.picking.type']\ .browse(self.picking_type_out)\ .write({ 'use_create_lots': False, 'use_existing_lots': True, }) self.productA.tracking = 'lot' delivery_order = self.env['stock.picking'].create({ 'location_id': self.pack_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 2, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'picking_type_id': self.picking_type_out, 'location_id': self.pack_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.force_assign() delivery_order.move_lines.quantity_done = 2 move_line = delivery_order.move_lines.move_line_ids # not lot_name set, should raise with self.assertRaises(UserError): delivery_order.action_done() # creating a lot from the view should raise with self.assertRaises(UserError): self.env['stock.production.lot']\ .with_context(active_picking_id=delivery_order.id)\ .create({ 'name': 'lot1', 'product_id': self.productA.id, }) # enter an existing lot_id, should work lot1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': self.productA.id, }) move_line.lot_id = lot1 delivery_order.action_done() def test_merge_moves_1(self): receipt = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_in, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 3, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 5, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 1, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, }) self.MoveObj.create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 5, 'product_uom': self.productB.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, }) receipt.action_confirm() self.assertEqual(len(receipt.move_lines), 2, 'Moves were not merged') self.assertEqual(receipt.move_lines.filtered(lambda m: m.product_id == self.productA).product_uom_qty, 9, 'Merged quantity is not correct') self.assertEqual(receipt.move_lines.filtered(lambda m: m.product_id == self.productB).product_uom_qty, 5, 'Merge should not impact product B reserved quantity') def test_merge_moves_2(self): receipt = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_in, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 3, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'origin': 'MPS' }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 5, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'origin': 'PO0001' }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 3, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'origin': 'MPS' }) receipt.action_confirm() self.assertEqual(len(receipt.move_lines), 1, 'Moves were not merged') self.assertEqual(receipt.move_lines.origin.count('MPS'), 1, 'Origin not merged together or duplicated') self.assertEqual(receipt.move_lines.origin.count('PO0001'), 1, 'Origin not merged together or duplicated') def test_merge_moves_3(self): """ Create 2 moves without initial_demand and already a quantity done. Check that we still have only 2 moves after validation. """ receipt = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_in, }) move_1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 0, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'origin': 'MPS' }) move_2 = self.MoveObj.create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 0, 'product_uom': self.productB.uom_id.id, 'picking_id': receipt.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'origin': 'PO0001' }) move_1.quantity_done = 5 move_2.quantity_done = 5 receipt.button_validate() self.assertEqual(len(receipt.move_lines), 2, 'Moves were not merged') def test_merge_chained_moves(self): """ Imagine multiple step delivery. Two different receipt picking for the same product should only generate 1 picking from input to QC and another from QC to stock. The link at the end should follow this scheme. Move receipt 1 \ Move Input-> QC - Move QC -> Stock Move receipt 2 / """ branch_id = self.env.ref('base_branch_company.data_branch_1') warehouse = self.env['stock.warehouse'].create({ 'name': 'TEST WAREHOUSE', 'branch_id': branch_id.id, 'code': 'TEST1', 'reception_steps': 'three_steps', }) receipt1 = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': warehouse.wh_input_stock_loc_id.id, 'partner_id': self.partner_delta_id, 'picking_type_id': warehouse.in_type_id.id, 'branch_id': branch_id.id }) move_receipt_1 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 5, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt1.id, 'location_id': self.supplier_location, 'location_dest_id': warehouse.wh_input_stock_loc_id.id, }) receipt2 = self.env['stock.picking'].create({ 'location_id': self.supplier_location, 'location_dest_id': warehouse.wh_input_stock_loc_id.id, 'partner_id': self.partner_delta_id, 'picking_type_id': warehouse.in_type_id.id, }) move_receipt_2 = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 3, 'product_uom': self.productA.uom_id.id, 'picking_id': receipt2.id, 'location_id': self.supplier_location, 'location_dest_id': warehouse.wh_input_stock_loc_id.id, }) receipt1.action_confirm() receipt2.action_confirm() # Check following move has been created and grouped in one picking. self.assertTrue(move_receipt_1.move_dest_ids, 'No move created from push rules') self.assertTrue(move_receipt_2.move_dest_ids, 'No move created from push rules') self.assertEqual(move_receipt_1.move_dest_ids.picking_id, move_receipt_2.move_dest_ids.picking_id, 'Destination moves should be in the same picking') # Check link for input move are correct. input_move = move_receipt_2.move_dest_ids self.assertEqual(len(input_move.move_dest_ids), 1) self.assertEqual(set(input_move.move_orig_ids.ids), set((move_receipt_2 | move_receipt_1).ids), 'Move from input to QC should be merged and have the two receipt moves as origin.') self.assertEqual(move_receipt_1.move_dest_ids, input_move) self.assertEqual(move_receipt_2.move_dest_ids, input_move) # Check link for quality check move are also correct. qc_move = input_move.move_dest_ids self.assertEqual(len(qc_move), 1) self.assertTrue(qc_move.move_orig_ids == input_move, 'Move between QC and stock should only have the input move as origin') def test_empty_moves_validation_1(self): """ Use button validate on a picking that contains only moves without initial demand and without quantity done should be impossible and raise a usererror. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 0, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) self.MoveObj.create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 0, 'product_uom': self.productB.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() with self.assertRaises(UserError): delivery_order.button_validate() def test_empty_moves_validation_2(self): """ Use button validate on a picking that contains only moves without initial demand but at least one with a quantity done should process the move with quantity done and cancel the other. """ delivery_order = self.env['stock.picking'].create({ 'location_id': self.stock_location, 'location_dest_id': self.customer_location, 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_out, }) move_a = self.MoveObj.create({ 'name': self.productA.name, 'product_id': self.productA.id, 'product_uom_qty': 0, 'product_uom': self.productA.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) move_b = self.MoveObj.create({ 'name': self.productB.name, 'product_id': self.productB.id, 'product_uom_qty': 0, 'product_uom': self.productB.uom_id.id, 'picking_id': delivery_order.id, 'location_id': self.stock_location, 'location_dest_id': self.customer_location, }) delivery_order.action_confirm() delivery_order.action_assign() move_a.quantity_done = 1 delivery_order.button_validate() self.assertEqual(move_a.state, 'done') self.assertEqual(move_b.state, 'cancel') back_order = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)]) self.assertFalse(back_order, 'There should be no back order') class TestStockUOM(TestStockCommon): def setUp(self): super(TestStockUOM, self).setUp() dp = self.env.ref('product.decimal_product_uom') dp.digits = 7 # Trick: invoke the method 'precision_get' with the current environment. # This fills in the cache of the method with the right value. If we # don't do that, the registry will access the corresponding precision # with a new cursor (LazyCursor), and get a different value! self.assertEqual(dp.precision_get(dp.name), 7) def test_pickings_transfer_with_different_uom_and_back_orders(self): """ Picking transfer with diffrent unit of meassure. """ # weight category categ_test = self.env['product.uom.categ'].create({'name': 'Bigger than tons'}) T_LBS = self.env['product.uom'].create({ 'name': 'T-LBS', 'category_id': categ_test.id, 'uom_type': 'reference', 'rounding': 0.01 }) T_GT = self.env['product.uom'].create({ 'name': 'T-GT', 'category_id': categ_test.id, 'uom_type': 'bigger', 'rounding': 0.0000001, 'factor_inv': 2240.00, }) T_TEST = self.env['product.product'].create({ 'name': 'T_TEST', 'type': 'product', 'uom_id': T_LBS.id, 'uom_po_id': T_LBS.id, 'tracking': 'lot', }) picking_in = self.env['stock.picking'].create({ 'partner_id': self.partner_delta_id, 'picking_type_id': self.picking_type_in, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location }) move = self.env['stock.move'].create({ 'name': 'First move with 60 GT', 'product_id': T_TEST.id, 'product_uom_qty': 60, 'product_uom': T_GT.id, 'picking_id': picking_in.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location }) picking_in.action_confirm() self.assertEqual(move.product_uom_qty, 60.00, 'Wrong T_GT quantity') self.assertEqual(move.product_qty, 134400.00, 'Wrong T_LBS quantity') lot = self.env['stock.production.lot'].create({'name': 'Lot TEST', 'product_id': T_TEST.id}) self.env['stock.move.line'].create({ 'move_id': move.id, 'product_id': T_TEST.id, 'product_uom_id': T_LBS.id, 'location_id': self.supplier_location, 'location_dest_id': self.stock_location, 'qty_done': 42760.00, 'lot_id': lot.id, }) picking_in.action_done() back_order_in = self.env['stock.picking'].search([('backorder_id', '=', picking_in.id)]) self.assertEqual(len(back_order_in), 1.00, 'There should be one back order created') self.assertEqual(back_order_in.move_lines.product_qty, 91640.00, 'There should be one back order created') class TestRoutes(TransactionCase): def test_pick_ship_1(self): """ Enable the pick ship route, force a procurement group on the pick. When a second move is added, make sure the `partner_id` and `origin` fields are erased. """ product1 = self.env['product.product'].create({ 'name': 'product a', 'type': 'product', 'categ_id': self.env.ref('product.product_category_all').id, }) uom_unit = self.env.ref('product.product_uom_unit') wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) # create and get back the pick ship route wh.write({'delivery_steps': 'pick_ship'}) pick_ship_route = wh.route_ids.filtered(lambda r: 'Pick + Ship' in r.name) # create a procurement group and set in on the pick procurement rule procurement_group0 = self.env['procurement.group'].create({}) pick_rule = pick_ship_route.pull_ids.filtered(lambda rule: 'Stock -> Output' in rule.name) push_rule = pick_ship_route.pull_ids - pick_rule pick_rule.write({ 'group_propagation_option': 'fixed', 'group_id': procurement_group0.id, }) stock_location = pick_rule.location_src_id ship_location = pick_rule.location_id customer_location = push_rule.location_id partners = self.env['res.partner'].search([], limit=2) partner0 = partners[0] partner1 = partners[1] procurement_group1 = self.env['procurement.group'].create({'partner_id': partner0.id}) procurement_group2 = self.env['procurement.group'].create({'partner_id': partner1.id}) move1 = self.env['stock.move'].create({ 'name': 'first out move', 'procure_method': 'make_to_order', 'location_id': ship_location.id, 'location_dest_id': customer_location.id, 'product_id': product1.id, 'product_uom': uom_unit.id, 'product_uom_qty': 1.0, 'warehouse_id': wh.id, 'group_id': procurement_group1.id, 'origin': 'origin1', }) move2 = self.env['stock.move'].create({ 'name': 'second out move', 'procure_method': 'make_to_order', 'location_id': ship_location.id, 'location_dest_id': customer_location.id, 'product_id': product1.id, 'product_uom': uom_unit.id, 'product_uom_qty': 1.0, 'warehouse_id': wh.id, 'group_id': procurement_group2.id, 'origin': 'origin2', }) # first out move, the "pick" picking should have a partner and an origin move1._action_confirm() picking_pick = move1.move_orig_ids.picking_id self.assertEqual(picking_pick.partner_id.id, procurement_group1.partner_id.id) self.assertEqual(picking_pick.origin, move1.group_id.name) # second out move, the "pick" picking should have lost its partner and origin move2._action_confirm() self.assertEqual(picking_pick.partner_id.id, False) self.assertEqual(picking_pick.origin, False)