# Copyright 2018 Tecnativa - Carlos Dauden from odoo.exceptions import UserError from odoo.tests.common import TransactionCase class TestBatchPicking(TransactionCase): def setUp(self): super(TestBatchPicking, self).setUp() self.user_demo = self.env.ref('base.user_demo') self.stock_loc = self.browse_ref('stock.stock_location_stock') self.customer_loc = self.browse_ref('stock.stock_location_customers') self.batch_model = self.env['stock.batch.picking'] # Delete (in transaction) all batches for simplify tests. self.batch_model.search([]).unlink() self.picking_model = self.env['stock.picking'] self.product6 = self.env.ref('product.product_product_6') self.product7 = self.env.ref('product.product_product_7') self.product9 = self.env.ref('product.product_product_9') self.product10 = self.env.ref('product.product_product_10') self.picking = self.create_simple_picking([ self.product6.id, self.product7.id, ]) self.picking.action_confirm() self.picking2 = self.create_simple_picking([ self.product9.id, self.product10.id, ]) self.picking2.action_confirm() self.batch = self.batch_model.create({ 'picker_id': self.env.uid, 'picking_ids': [ (4, self.picking.id), (4, self.picking2.id), ] }) def create_simple_picking(self, product_ids, batch_id=False): # The 'planned_picking' context key ensures that the picking # will be created in the 'draft' state (no autoconfirm) return self.picking_model.with_context(planned_picking=True).create({ 'picking_type_id': self.ref('stock.picking_type_out'), 'location_id': self.stock_loc.id, 'location_dest_id': self.customer_loc.id, 'batch_picking_id': batch_id, 'move_lines': [ (0, 0, { 'name': 'Test move', 'product_id': product_id, 'product_uom': self.ref('product.product_uom_unit'), 'product_uom_qty': 1, 'location_id': self.stock_loc.id, 'location_dest_id': self.customer_loc.id }) for product_id in product_ids ] }) def test_assign__no_picking(self): batch = self.batch_model.create({}) with self.assertRaises(UserError): batch.action_assign() # Even with multiple batches batches = batch | self.batch_model.create({}) with self.assertRaises(UserError): batches.action_assign() def test_assign(self): self.assertEqual('draft', self.batch.state) self.assertEqual('confirmed', self.picking.state) self.assertEqual('confirmed', self.picking2.state) self.assertEqual(0, len(self.batch.move_line_ids)) self.assertEqual(4, len(self.batch.move_lines)) self.batch.action_assign() self.assertEqual('assigned', self.batch.state) self.assertEqual('assigned', self.picking.state) self.assertEqual('assigned', self.picking2.state) self.assertEqual(4, len(self.batch.move_line_ids)) self.assertEqual(4, len(self.batch.move_lines)) def test_assign_with_cancel(self): self.picking2.action_cancel() self.assertEqual('draft', self.batch.state) self.assertEqual('confirmed', self.picking.state) self.assertEqual('cancel', self.picking2.state) self.batch.action_assign() self.assertEqual('assigned', self.batch.state) self.assertEqual('assigned', self.picking.state) self.assertEqual('cancel', self.picking2.state) def test_action_transfer(self): self.batch.action_assign() self.assertEqual('assigned', self.batch.state) self.assertEqual('assigned', self.picking.state) self.assertEqual('assigned', self.picking2.state) self.assertEqual(4, len(self.batch.move_line_ids)) self.assertEqual( {(0, 1)}, {(op.qty_done, op.ordered_qty) for op in self.batch.move_line_ids} ) self.batch.action_transfer() self.assertEqual('done', self.batch.state) self.assertEqual('done', self.picking.state) self.assertEqual('done', self.picking2.state) self.assertEqual(4, len(self.batch.move_line_ids)) self.assertEqual( {(1, 1)}, {(op.qty_done, op.ordered_qty) for op in self.batch.move_line_ids} ) def test_action_transfer__unavailable(self): picking3 = self.create_simple_picking([ self.ref('product.product_product_8') ]) self.batch = self.batch_model.create({ 'picker_id': self.env.uid, 'picking_ids': [ (4, self.picking.id), (4, picking3.id), ] }) self.assertEqual('draft', picking3.state) self.assertEqual('confirmed', self.picking.state) self.batch.action_transfer() self.assertEqual('confirmed', picking3.state) self.assertEqual('done', self.picking.state) def test_cancel(self): self.assertEqual('draft', self.batch.state) self.assertEqual('confirmed', self.picking.state) self.assertEqual('confirmed', self.picking2.state) self.batch.action_cancel() self.assertEqual('cancel', self.batch.state) self.assertEqual('cancel', self.picking.state) self.assertEqual('cancel', self.picking2.state) def test_cancel_multi(self): picking3 = self.create_simple_picking([ self.ref('product.product_product_8') ]) batch2 = self.batch_model.create({ 'picker_id': self.env.uid, 'picking_ids': [ (4, picking3.id), ] }) batches = self.batch | batch2 batches.action_cancel() self.assertEqual('cancel', self.batch.state) self.assertEqual('cancel', self.picking.state) self.assertEqual('cancel', self.picking2.state) self.assertEqual('cancel', batch2.state) self.assertEqual('cancel', picking3.state) def test_cancel__no_pickings(self): batch = self.batch_model.create({}) self.assertEqual('draft', batch.state) batch.action_cancel() self.assertEqual('cancel', batch.state) def test_all_cancel_or_done__on_cancel(self): self.picking.force_transfer() self.picking2.action_cancel() self.assertEqual('done', self.picking.state) self.assertEqual('cancel', self.picking2.state) self.assertEqual('done', self.batch.state) def test_all_cancel_or_done__on_done(self): self.picking2.action_cancel() self.picking.force_transfer() self.assertEqual('done', self.picking.state) self.assertEqual('cancel', self.picking2.state) self.assertEqual('done', self.batch.state) def test_stock_picking_copy(self): picking = self.batch.picking_ids[0] self.assertEqual(self.batch, picking.batch_picking_id) copy = picking.copy() self.assertFalse(copy.batch_picking_id) def test_create_wizard(self): wizard = self.env['stock.batch.picking.creator'].create({ 'name': 'Unittest wizard', }) # Pickings already in batch. with self.assertRaises(UserError): wizard.with_context(active_ids=[self.picking.id])\ .action_create_batch() # Creating and selecting (too) another picking picking3 = self.create_simple_picking([ self.ref('product.product_product_8'), ]) picking3.action_confirm() self.assertEqual(0, self.batch_model.search_count( [('name', '=', 'Unittest wizard')] )) wizard.with_context(active_ids=[self.picking.id, picking3.id])\ .action_create_batch() batch = self.batch_model.search( [('name', '=', 'Unittest wizard')] ) self.assertEqual(1, len(batch)) # Only picking3 because self.picking is already in another batch. self.assertEqual(picking3, batch.picking_ids) self.assertEqual(batch, picking3.batch_picking_id) def test_wizard_picker_id(self): wh_main = self.browse_ref("stock.warehouse0") wizard_model = self.env['stock.batch.picking.creator'] wizard = wizard_model.create({ 'name': 'Unittest wizard', }) self.assertFalse(wizard.picker_id) wh_main.default_picker_id = self.env.user wizard = wizard_model.create({ 'name': 'Unittest wizard', }) self.assertEqual(self.env.user, wizard.picker_id) other_wh = self.env['stock.warehouse'].create({ 'name': 'Unittest other warehouse', 'code': 'UWH', }) wizard = wizard_model.with_context(warehouse_id=other_wh.id).create({ 'name': 'Unittest wizard', }) self.assertFalse(wizard.picker_id) user2 = self.env['res.users'].create({ 'name': 'Unittest user', 'login': 'unittest_user' }) other_wh.default_picker_id = user2 wizard = wizard_model.with_context(warehouse_id=other_wh.id).create({ 'name': 'Unittest wizard', }) self.assertEqual(user2, wizard.picker_id) def test_backorder(self): # Change move lines quantities for product 6 and 7 for move in self.batch.move_lines: if move.product_id == self.product6: move.product_uom_qty = 5 elif move.product_id == self.product7: move.product_uom_qty = 2 self.batch.action_assign() # Mark product 6 as partially processed and 7 and 9 as fully processed. for operation in self.batch.move_line_ids: if operation.product_id == self.product6: operation.qty_done = 3 elif operation.product_id == self.product7: operation.qty_done = 2 elif operation.product_id == self.product9: operation.qty_done = 1 self.batch.action_transfer() self.assertEqual('done', self.picking.state) self.assertEqual('done', self.picking2.state) # A backorder has been created for product with # 5 - 3 = 2 qty to process. backorder = self.picking_model.search([ ('backorder_id', '=', self.picking.id) ]) self.assertEqual(1, len(backorder)) self.assertEqual('assigned', backorder.state) self.assertEqual(1, len(backorder.move_lines)) self.assertEqual(self.product6, backorder.move_lines[0].product_id) self.assertEqual(2, backorder.move_lines[0].product_uom_qty) self.assertEqual(1, len(backorder.move_line_ids)) self.assertEqual(2, backorder.move_line_ids[0].product_uom_qty) self.assertEqual(0, backorder.move_line_ids[0].qty_done) backorder2 = self.picking_model.search([ ('backorder_id', '=', self.picking2.id) ]) self.assertEqual(1, len(backorder2)) self.assertEqual('assigned', backorder2.state) self.assertEqual(1, len(backorder2.move_lines)) self.assertEqual(self.product10, backorder2.move_lines.product_id) self.assertEqual(1, backorder2.move_lines.product_uom_qty) self.assertEqual(1, len(backorder2.move_line_ids)) self.assertEqual(1, backorder2.move_line_ids.product_uom_qty) self.assertEqual(0, backorder2.move_line_ids.qty_done) def test_assign_draft_pick(self): picking3 = self.create_simple_picking([ self.ref('product.product_product_11'), ], batch_id=self.batch.id) self.assertEqual('draft', picking3.state) self.batch.action_transfer() self.assertEqual('done', self.batch.state) self.assertEqual('done', self.picking.state) self.assertEqual('done', self.picking2.state) self.assertEqual('done', picking3.state) def test_package(self): warehouse = self.browse_ref('stock.warehouse0') warehouse.delivery_steps = 'pick_ship' group = self.env['procurement.group'].create({ 'name': 'Test', 'move_type': 'direct', }) values = { 'company_id': warehouse.company_id, 'group_id': group, 'date_planned': '2018-11-13 12:12:59', 'warehouse_id': warehouse, } group.run( product_id=self.env.ref('product.product_product_11'), product_qty=1, product_uom=self.env.ref('product.product_uom_unit'), location_id=self.customer_loc, name='test', origin='TEST', values=values) pickings = self.picking_model.search([ ('group_id', '=', group.id) ]) self.assertEqual(2, len(pickings)) picking = pickings.filtered(lambda p: p.state == 'confirmed') picking.action_assign() picking.move_line_ids[0].qty_done = 1 package = picking.put_in_pack() picking.action_done() other_picking = pickings.filtered(lambda p: p.id != picking.id) self.assertEqual('assigned', other_picking.state) self.assertEqual( package, other_picking.move_line_ids.package_id, ) # We add the 'package' picking in batch other_picking.batch_picking_id = self.batch self.batch.action_assign() self.batch.action_transfer() self.assertEqual('done', self.batch.state) self.assertEqual('done', self.picking.state) self.assertEqual('done', self.picking2.state) self.assertEqual('done', other_picking.state) self.assertEqual(self.customer_loc, package.location_id) def test_remove_undone(self): self.picking2.action_cancel() picking3 = self.create_simple_picking([ self.ref('product.product_product_11') ], batch_id=self.batch.id) picking3.force_transfer() picking4 = self.create_simple_picking([ self.ref('product.product_product_11') ], batch_id=self.batch.id) self.assertEqual('confirmed', self.picking.state) self.assertEqual('cancel', self.picking2.state) self.assertEqual('done', picking3.state) self.assertEqual('draft', picking4.state) self.assertEqual('draft', self.batch.state) self.batch.remove_undone_pickings() self.assertEqual('done', self.batch.state) self.assertEqual(2, len(self.batch.picking_ids)) self.assertEqual(self.picking2 | picking3, self.batch.picking_ids) def test_partial_done(self): # If user filled some qty_done manually in operations tab, # we want only these qties to be processed. # So picking with no qties processed are release and backorder are # created for picking partially processed. self.batch.action_assign() self.assertEqual('assigned', self.picking.state) self.assertEqual('assigned', self.picking2.state) self.picking.move_line_ids[0].qty_done = 1 self.batch.action_transfer() self.assertEqual(self.picking, self.batch.picking_ids) self.assertEqual('done', self.picking.state) # Second picking is released (and still confirmed) self.assertEqual('assigned', self.picking2.state) self.assertFalse(self.picking2.batch_picking_id) picking_backorder = self.picking_model.search([ ('backorder_id', '=', self.picking.id) ]) self.assertEqual(1, len(picking_backorder.move_lines)) def test_wizard_batch_grouped_by_field(self): Wiz = self.env['stock.batch.picking.creator'] self.picking.origin = 'A' self.picking2.origin = 'B' pickings = self.picking + self.picking2 wiz = Wiz.with_context(active_ids=pickings.ids).create({ 'name': 'Unittest wizard', }) # Read values from config parameters, before first execution there # are no values self.assertFalse(wiz.batch_by_group) self.assertFalse(wiz.group_field_ids) # Add fields no to do one batch picking per grouped picking # create_date field origin_field = self.env.ref('stock.field_stock_picking_origin') wiz.batch_by_group = True wiz.group_field_ids = [(0, 0, { 'sequence': 1, 'field_id': origin_field.id, })] # Raise error if any picking already is in other batch picking with self.assertRaises(UserError): wiz.action_create_batch() # Two picking has distinct origin so two batch pickings must be created pickings.write({'batch_picking_id': False}) res = wiz.action_create_batch() self.assertTrue(res['domain']) # Two picking has same origin so only one batch picking must be created pickings.write({'batch_picking_id': False}) self.picking2.origin = 'A' res = wiz.action_create_batch() self.assertTrue(res['res_id']) # Test if group field create_date has been stored into config # parameters self.assertEqual(origin_field, wiz.load_store_fields())