stock_picking_batch/tests/test_wave_picking.py

507 lines
22 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import UserError
from odoo.tests import Form
from odoo.tests.common import TransactionCase
class TestBatchPicking(TransactionCase):
@classmethod
def setUpClass(cls):
""" Create 3 standard pickings and reserve them to have some move lines.
The setup data looks like this:
Picking1 Picking2 Picking3
ProductA ProductA ProductB
Lot1: 5 units Lot4: 5 units SN6 : 1 unit
Lot2: 5 units SN7 : 1 unit
Lot3: 5 units SN8 : 1 unit
ProductB SN9 : 1 unit
SN1 : 1 unit SN10: 1 unit
SN2 : 1 unit
SN3 : 1 unit
SN4 : 1 unit
SN5 : 1 unit
The picking_internal is the same as Picking1 move-wise, only using a different picking_type so it doesn't get auto-batched with the other pickings.
"""
super().setUpClass()
cls.stock_location = cls.env.ref('stock.stock_location_stock')
cls.customer_location = cls.env.ref('stock.stock_location_customers')
cls.picking_type_out = cls.env['ir.model.data']._xmlid_to_res_id('stock.picking_type_out')
cls.picking_type_in = cls.env['ir.model.data']._xmlid_to_res_id('stock.picking_type_in')
cls.picking_type_internal = cls.env['ir.model.data']._xmlid_to_res_id('stock.picking_type_internal')
cls.user_demo = cls.env['res.users'].search([('login', '=', 'demo')])
cls.productA = cls.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'lot',
'categ_id': cls.env.ref('product.product_category_all').id,
})
cls.lots_p_a = cls.env['stock.lot'].create([{
'name': 'lot_product_a_' + str(i + 1),
'product_id': cls.productA.id,
'company_id': cls.env.company.id,
} for i in range(4)])
cls.productB = cls.env['product.product'].create({
'name': 'Product B',
'type': 'product',
'tracking': 'serial',
'categ_id': cls.env.ref('product.product_category_all').id,
})
cls.lots_p_b = cls.env['stock.lot'].create([{
'name': 'lot_product_a_' + str(i + 1),
'product_id': cls.productB.id,
'company_id': cls.env.company.id,
} for i in range(10)])
Quant = cls.env['stock.quant']
for lot in cls.lots_p_a:
Quant._update_available_quantity(cls.productA, cls.stock_location, 5.0, lot_id=lot)
for lot in cls.lots_p_b:
Quant._update_available_quantity(cls.productB, cls.stock_location, 1.0, lot_id=lot)
cls.picking_client_1 = cls.env['stock.picking'].create({
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
'picking_type_id': cls.picking_type_out,
'company_id': cls.env.company.id,
'state': 'draft',
})
cls.env['stock.move'].create({
'name': cls.productA.name,
'product_id': cls.productA.id,
'product_uom_qty': 15,
'product_uom': cls.productA.uom_id.id,
'picking_id': cls.picking_client_1.id,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
})
cls.env['stock.move'].create({
'name': cls.productB.name,
'product_id': cls.productB.id,
'product_uom_qty': 5,
'product_uom': cls.productB.uom_id.id,
'picking_id': cls.picking_client_1.id,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
})
cls.picking_client_2 = cls.env['stock.picking'].create({
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
'picking_type_id': cls.picking_type_out,
'company_id': cls.env.company.id,
'state': 'draft',
})
cls.env['stock.move'].create({
'name': cls.productA.name,
'product_id': cls.productA.id,
'product_uom_qty': 5,
'product_uom': cls.productA.uom_id.id,
'picking_id': cls.picking_client_2.id,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
})
cls.picking_client_3 = cls.env['stock.picking'].create({
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
'picking_type_id': cls.picking_type_out,
'company_id': cls.env.company.id,
'state': 'draft',
})
cls.env['stock.move'].create({
'name': cls.productB.name,
'product_id': cls.productB.id,
'product_uom_qty': 5,
'product_uom': cls.productB.uom_id.id,
'picking_id': cls.picking_client_3.id,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
})
cls.picking_internal = cls.env['stock.picking'].create({
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
'picking_type_id': cls.picking_type_internal,
'company_id': cls.env.company.id,
'state': 'draft',
})
cls.env['stock.move'].create({
'name': cls.productA.name,
'product_id': cls.productA.id,
'product_uom_qty': 15,
'product_uom': cls.productA.uom_id.id,
'picking_id': cls.picking_internal.id,
'location_id': cls.customer_location.id,
'location_dest_id': cls.stock_location.id,
})
cls.env['stock.move'].create({
'name': cls.productB.name,
'product_id': cls.productB.id,
'product_uom_qty': 5,
'product_uom': cls.productB.uom_id.id,
'picking_id': cls.picking_internal.id,
'location_id': cls.customer_location.id,
'location_dest_id': cls.stock_location.id,
})
cls.all_pickings = cls.picking_client_1 | cls.picking_client_2 | cls.picking_client_3
cls.all_pickings.action_confirm()
cls.picking_internal.action_confirm()
def test_creation_from_lines(self):
""" Select all the move_lines and create a wave from them """
all_lines = self.all_pickings.move_line_ids
res_dict = all_lines.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': all_lines.ids}
self.assertEqual(res_dict.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.user_id = self.user_demo
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertTrue(wave)
self.assertEqual(wave.picking_ids, self.all_pickings)
self.assertEqual(wave.move_line_ids, all_lines)
self.assertEqual(wave.user_id, self.user_demo)
def test_creation_from_pickings(self):
""" Select all the picking_ids and create a wave from them """
action = self.env['ir.actions.actions']._for_xml_id('stock_picking_batch.stock_add_to_wave_action_stock_picking')
action['context'] = {'active_model': 'stock.picking', 'active_ids': self.all_pickings.ids}
self.assertEqual(action.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[action['res_model']].with_context(action['context']))
wizard_form.mode = 'new'
wizard = wizard_form.save()
res = wizard.attach_pickings()
self.assertEqual(set(res['context']['picking_to_wave']), set(self.all_pickings.ids))
def test_add_to_existing_wave_from_lines(self):
res_dict = self.picking_client_1.move_line_ids.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': self.picking_client_1.move_line_ids.ids}
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.user_id = self.user_demo
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
res_dict = self.picking_client_2.move_line_ids.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': self.picking_client_2.move_line_ids.ids}
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'existing'
wizard_form.wave_id = wave
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertEqual(len(wave), 1)
self.assertEqual(wave.picking_ids, self.picking_client_1 | self.picking_client_2)
def test_add_to_existing_wave_from_pickings(self):
res_dict = self.picking_client_1.move_line_ids.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': self.picking_client_1.move_line_ids.ids}
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.user_id = self.user_demo
action = wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
action = self.env['ir.actions.actions']._for_xml_id('stock_picking_batch.stock_add_to_wave_action_stock_picking')
action['context'] = {'active_model': 'stock.picking', 'active_ids': self.all_pickings.ids}
self.assertEqual(action.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[action['res_model']].with_context(action['context']))
wizard_form.mode = 'existing'
wizard_form.wave_id = wave
wizard = wizard_form.save()
res = wizard.attach_pickings()
self.assertEqual(set(res['context']['picking_to_wave']), set(self.all_pickings.ids))
self.assertEqual(res['context']['active_wave_id'], wave.id)
def test_wave_split_picking(self):
lines = self.picking_client_1.move_ids.filtered(lambda m: m.product_id == self.productB).move_line_ids
move = lines.move_id
self.assertEqual(len(move), 1)
all_db_pickings = self.env['stock.picking'].search([])
res_dict = lines.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': lines.ids}
self.assertEqual(res_dict.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertTrue(wave)
# Original picking lost a stock move
self.assertTrue(move.picking_id)
self.assertFalse(move.picking_id == self.picking_client_1)
self.assertTrue(self.picking_client_1.move_ids)
self.assertTrue(move.picking_id.batch_id == wave)
self.assertTrue(lines.batch_id == wave)
new_all_db_picking = self.env['stock.picking'].search([])
self.assertEqual(len(all_db_pickings) + 1, len(new_all_db_picking))
def test_wave_split_move(self):
lines = self.picking_internal.move_ids.filtered(lambda m: m.product_id == self.productB).move_line_ids[0:2]
move = lines.move_id
all_db_pickings = self.env['stock.picking'].search([])
res_dict = lines.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': lines.ids}
self.assertEqual(res_dict.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertTrue(wave)
self.assertTrue(wave.picking_type_id)
# Original picking lost a stock move
self.assertTrue(move.picking_id)
self.assertTrue(move.picking_id == self.picking_internal)
self.assertFalse(lines.move_id == move)
self.assertFalse(move.picking_id.batch_id)
new_move = lines.move_id
self.assertTrue(new_move.picking_id.batch_id == wave)
self.assertTrue(lines.batch_id == wave)
new_all_db_picking = self.env['stock.picking'].search([])
self.assertEqual(len(all_db_pickings) + 1, len(new_all_db_picking))
def test_wave_split_move_uom(self):
self.uom_dozen = self.env.ref('uom.product_uom_dozen')
sns = self.env['stock.lot'].create([{
'name': 'sn-' + str(i),
'product_id': self.productB.id,
'company_id': self.env.company.id
} for i in range(12)])
for i in range(12):
self.env['stock.quant']._update_available_quantity(self.productB, self.stock_location, 1.0, lot_id=sns[i])
dozen_move = self.env['stock.move'].create({
'name': self.productB.name,
'product_id': self.productB.id,
'product_uom_qty': 1,
'product_uom': self.uom_dozen.id,
'picking_id': self.picking_client_1.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
dozen_move._action_confirm()
dozen_move._action_assign()
self.assertEqual(len(dozen_move.move_line_ids), 12)
self.assertEqual(dozen_move.move_line_ids.product_uom_id, self.env.ref('uom.product_uom_unit'))
lines = dozen_move.move_line_ids[0:5]
res_dict = lines.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': lines.ids}
self.assertEqual(res_dict.get('res_model'), 'stock.add.to.wave')
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard_form.save().attach_pickings()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertFalse(lines.move_id == dozen_move)
self.assertEqual(lines.batch_id, wave)
self.assertEqual(dozen_move.product_uom_qty, 0.58)
def test_wave_mutliple_move_lines(self):
self.productA = self.env['product.product'].create({
'name': 'Product Test A',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
picking = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
'picking_type_id': self.picking_type_out,
'company_id': self.env.company.id,
})
ml1 = self.env['stock.move.line'].create({
'product_id': self.productA.id,
'quantity': 5,
'product_uom_id': self.productA.uom_id.id,
'picking_id': picking.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
self.env['stock.move.line'].create({
'product_id': self.productA.id,
'quantity': 5,
'product_uom_id': self.productA.uom_id.id,
'picking_id': picking.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
ml2 = self.env['stock.move.line'].create({
'product_id': self.productA.id,
'quantity': 5,
'product_uom_id': self.productA.uom_id.id,
'picking_id': picking.id,
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
})
ml1._add_to_wave()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertFalse(picking.batch_id)
self.assertEqual(ml1.picking_id.batch_id.id, wave.id)
self.assertEqual(ml1.picking_id.move_ids.quantity, 5)
self.assertEqual(ml1.picking_id.move_ids.product_uom_qty, 5)
self.assertEqual(ml2.picking_id.id, picking.id)
self.assertEqual(ml2.picking_id.move_ids.quantity, 10)
self.assertEqual(ml2.picking_id.move_ids.product_uom_qty, 0)
def test_wave_trigger_errors(self):
with self.assertRaises(UserError):
lines = self.picking_client_1.move_line_ids
res_dict = lines.action_open_add_to_wave()
wizard_form = Form(self.env[res_dict['res_model']])
wizard_form.mode = 'new'
wizard = wizard_form.save()
wizard.attach_pickings()
with self.assertRaises(UserError):
self.picking_client_1.company_id = self.env.company
self.picking_client_2.company_id = self.env['res.company'].create({'name': 'Company 2'})
lines = (self.picking_client_1 | self.picking_client_2).move_line_ids
res_dict = lines.action_open_add_to_wave()
res_dict['context'] = {'active_model': 'stock.move.line', 'active_ids': lines.ids}
wizard_form = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
wizard_form.mode = 'new'
wizard = wizard_form.save()
wizard.attach_pickings()
def test_not_assign_to_wave(self):
""" Picking
- Move line A 5 from Container to Cust -> Going to a wave picking
- Move line A 5 from Container to Cust -> Validate
---------------------------------------------
Create
- Move A 5 from Container to Cust
Check it creates a new picking and it's not assign to the wave
"""
location = self.env['stock.location'].create({
'name': 'Container',
'location_id': self.stock_location.id
})
self.env['stock.quant']._update_available_quantity(self.productA, location, 5.0, lot_id=self.lots_p_a[0])
self.env['stock.quant']._update_available_quantity(self.productA, location, 5.0, lot_id=self.lots_p_a[1])
picking_1 = self.env['stock.picking'].create({
'location_id': location.id,
'location_dest_id': self.customer_location.id,
'picking_type_id': self.picking_type_out,
'company_id': self.env.company.id,
'state': 'draft',
})
self.env['stock.move'].create({
'name': 'Test Wave',
'product_id': self.productA.id,
'product_uom_qty': 10,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_1.id,
'picking_type_id': self.picking_type_out,
'location_id': location.id,
'location_dest_id': self.customer_location.id,
})
picking_1.action_confirm()
picking_1.action_assign()
self.assertEqual(len(picking_1.move_line_ids), 2)
move_line_to_wave = picking_1.move_line_ids[0]
move_line_to_wave._add_to_wave()
picking_1.move_line_ids.quantity = 5
picking_1.move_ids.picked = True
picking_1._action_done()
new_move = self.env['stock.move'].create({
'name': 'Test Wave',
'product_id': self.productA.id,
'product_uom_qty': 5,
'product_uom': self.productA.uom_id.id,
'picking_type_id': self.picking_type_out,
'location_id': location.id,
'location_dest_id': self.customer_location.id,
})
new_move._action_confirm()
self.assertTrue(new_move.picking_id)
self.assertTrue(new_move.picking_id.id not in [picking_1.id, move_line_to_wave.picking_id.id])
def test_operation_type_in_wave(self):
"""
Check that the operation type of the picking is set correclty in the wave.
"""
warehouse = self.env['stock.warehouse'].search([], limit=1)
warehouse.reception_steps = 'three_steps'
self.productA = self.env['product.product'].create({
'name': 'Product Test A',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.productB = self.env['product.product'].create({
'name': 'Product Test B',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
})
picking = self.env['stock.picking'].create({
'location_id': self.customer_location.id,
'location_dest_id': self.stock_location.id,
'picking_type_id': self.picking_type_in,
'company_id': self.env.company.id,
'state': 'draft',
})
self.env['stock.move'].create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': picking.id,
'location_id': self.customer_location.id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
})
self.env['stock.move'].create({
'name': self.productB.name,
'product_id': self.productB.id,
'product_uom_qty': 5,
'product_uom': self.productB.uom_id.id,
'picking_id': picking.id,
'location_id': self.customer_location.id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
})
picking.action_confirm()
picking.move_ids.move_line_ids.write({'quantity': 1})
picking.move_ids.picked = True
res_dict = picking.button_validate()
self.env[res_dict['res_model']].with_context(res_dict['context']).process()
move_line = self.env["stock.move.line"].search([('product_id', '=', self.productA.id), ('location_id', '=', warehouse.wh_input_stock_loc_id.id)])
move_line._add_to_wave()
wave = self.env['stock.picking.batch'].search([
('is_wave', '=', True)
])
self.assertEqual(wave.picking_type_id, move_line.picking_type_id)