123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- # -*- coding: utf-8 -*-
- # Part of Odoo. See LICENSE file for full copyright and licensing details.
- from odoo.exceptions import UserError, ValidationError
- from odoo.tests.common import Form, TransactionCase
- class StockGenerate(TransactionCase):
- @classmethod
- def setUpClass(cls):
- super(StockGenerate, cls).setUpClass()
- Product = cls.env['product.product']
- cls.product_serial = Product.create({
- 'name': 'Tracked by SN',
- 'type': 'product',
- 'tracking': 'serial',
- })
- cls.uom_unit = cls.env.ref('uom.product_uom_unit')
- cls.warehouse = cls.env['stock.warehouse'].create({
- 'name': 'Base Warehouse',
- 'reception_steps': 'one_step',
- 'delivery_steps': 'ship_only',
- 'code': 'BWH'
- })
- cls.location = cls.env['stock.location'].create({
- 'name': 'Room A',
- 'location_id': cls.warehouse.lot_stock_id.id,
- })
- cls.location_dest = cls.env['stock.location'].create({
- 'name': 'Room B',
- 'location_id': cls.warehouse.lot_stock_id.id,
- })
- cls.Wizard = cls.env['stock.assign.serial']
- def get_new_move(self, nbre_of_lines):
- move_lines_val = []
- for i in range(nbre_of_lines):
- move_lines_val.append({
- 'product_id': self.product_serial.id,
- 'product_uom_id': self.uom_unit.id,
- 'reserved_uom_qty': 1,
- 'location_id': self.location.id,
- 'location_dest_id': self.location_dest.id
- })
- return self.env['stock.move'].create({
- 'name': 'Move Test',
- 'product_id': self.product_serial.id,
- 'product_uom': self.uom_unit.id,
- 'location_id': self.location.id,
- 'location_dest_id': self.location_dest.id,
- 'move_line_ids': [(0, 0, line_vals) for line_vals in move_lines_val]
- })
- def test_generate_01_sn(self):
- """ Creates a move with 5 move lines, then asks for generates 5 Serial
- Numbers. Checks move has 5 new move lines with each a SN, and the 5
- original move lines are still unchanged.
- """
- nbre_of_lines = 5
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='001',
- default_next_serial_count=nbre_of_lines,
- ))
- wiz = form_wizard.save()
- self.assertEqual(len(move.move_line_ids), nbre_of_lines)
- wiz.generate_serial_numbers()
- # Checks new move lines have the right SN
- generated_numbers = ['001', '002', '003', '004', '005']
- self.assertEqual(len(move.move_line_ids), nbre_of_lines + len(generated_numbers))
- for move_line in move.move_line_nosuggest_ids:
- # For a product tracked by SN, the `qty_done` is set on 1 when
- # `lot_name` is set.
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(move_line.lot_name, generated_numbers.pop(0))
- # Checks pre-generated move lines didn't change
- for move_line in (move.move_line_ids - move.move_line_nosuggest_ids):
- self.assertEqual(move_line.qty_done, 0)
- self.assertEqual(move_line.lot_name, False)
- def test_generate_02_prefix_suffix(self):
- """ Generates some Serial Numbers and checks the prefix and/or suffix
- are correctly used.
- """
- nbre_of_lines = 10
- # Case #1: Prefix, no suffix
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='bilou-87',
- default_next_serial_count=nbre_of_lines,
- ))
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Checks all move lines have the right SN
- generated_numbers = [
- 'bilou-87', 'bilou-88', 'bilou-89', 'bilou-90', 'bilou-91',
- 'bilou-92', 'bilou-93', 'bilou-94', 'bilou-95', 'bilou-96'
- ]
- for move_line in move.move_line_nosuggest_ids:
- # For a product tracked by SN, the `qty_done` is set on 1 when
- # `lot_name` is set.
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(
- move_line.lot_name,
- generated_numbers.pop(0)
- )
- # Case #2: No prefix, suffix
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='005-ccc',
- default_next_serial_count=nbre_of_lines,
- ))
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Checks all move lines have the right SN
- generated_numbers = [
- '005-ccc', '006-ccc', '007-ccc', '008-ccc', '009-ccc',
- '010-ccc', '011-ccc', '012-ccc', '013-ccc', '014-ccc'
- ]
- for move_line in move.move_line_nosuggest_ids:
- # For a product tracked by SN, the `qty_done` is set on 1 when
- # `lot_name` is set.
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(
- move_line.lot_name,
- generated_numbers.pop(0)
- )
- # Case #3: Prefix + suffix
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='alpha-012-345-beta',
- default_next_serial_count=nbre_of_lines,
- ))
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Checks all move lines have the right SN
- generated_numbers = [
- 'alpha-012-345-beta', 'alpha-012-346-beta', 'alpha-012-347-beta',
- 'alpha-012-348-beta', 'alpha-012-349-beta', 'alpha-012-350-beta',
- 'alpha-012-351-beta', 'alpha-012-352-beta', 'alpha-012-353-beta',
- 'alpha-012-354-beta'
- ]
- for move_line in move.move_line_nosuggest_ids:
- # For a product tracked by SN, the `qty_done` is set on 1 when
- # `lot_name` is set.
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(
- move_line.lot_name,
- generated_numbers.pop(0)
- )
- # Case #4: Prefix + suffix, identical number pattern
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='BAV023B00001S00001',
- default_next_serial_count=nbre_of_lines,
- ))
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Checks all move lines have the right SN
- generated_numbers = [
- 'BAV023B00001S00001', 'BAV023B00001S00002', 'BAV023B00001S00003',
- 'BAV023B00001S00004', 'BAV023B00001S00005', 'BAV023B00001S00006',
- 'BAV023B00001S00007', 'BAV023B00001S00008', 'BAV023B00001S00009',
- 'BAV023B00001S00010'
- ]
- for move_line in move.move_line_nosuggest_ids:
- # For a product tracked by SN, the `qty_done` is set on 1 when
- # `lot_name` is set.
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(
- move_line.lot_name,
- generated_numbers.pop(0)
- )
- def test_generate_03_raise_exception(self):
- """ Tries to generate some SN but with invalid initial number.
- """
- move = self.get_new_move(3)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='code-xxx',
- ))
- form_wizard.next_serial_count = 0
- # Must raise an exception because `next_serial_count` must be greater than 0.
- with self.assertRaises(ValidationError):
- form_wizard.save()
- form_wizard.next_serial_count = 3
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- self.assertEqual(move.move_line_nosuggest_ids.mapped('lot_name'), ["code-xxx0", "code-xxx1", "code-xxx2"])
- def test_generate_04_generate_in_multiple_time(self):
- """ Generates a Serial Number for each move lines (except the last one)
- but with multiple assignments, and checks the generated Serial Numbers
- are what we expect.
- """
- nbre_of_lines = 10
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- ))
- # First assignment
- form_wizard.next_serial_count = 3
- form_wizard.next_serial_number = '001'
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Second assignment
- form_wizard.next_serial_count = 2
- form_wizard.next_serial_number = 'bilou-64'
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Third assignment
- form_wizard.next_serial_count = 4
- form_wizard.next_serial_number = 'ro-1337-bot'
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- # Checks all move lines have the right SN
- generated_numbers = [
- # Correspond to the first assignment
- '001', '002', '003',
- # Correspond to the second assignment
- 'bilou-64', 'bilou-65',
- # Correspond to the third assignment
- 'ro-1337-bot', 'ro-1338-bot', 'ro-1339-bot', 'ro-1340-bot',
- ]
- self.assertEqual(len(move.move_line_ids), nbre_of_lines + len(generated_numbers))
- self.assertEqual(len(move.move_line_nosuggest_ids), len(generated_numbers))
- for move_line in move.move_line_nosuggest_ids:
- self.assertEqual(move_line.qty_done, 1)
- self.assertEqual(move_line.lot_name, generated_numbers.pop(0))
- for move_line in (move.move_line_ids - move.move_line_nosuggest_ids):
- self.assertEqual(move_line.qty_done, 0)
- self.assertEqual(move_line.lot_name, False)
- def test_generate_with_putaway(self):
- """ Checks the `location_dest_id` of generated move lines is correclty
- set in fonction of defined putaway rules.
- """
- nbre_of_lines = 4
- shelf_location = self.env['stock.location'].create({
- 'name': 'shelf1',
- 'usage': 'internal',
- 'location_id': self.location_dest.id,
- })
- # Checks a first time without putaway...
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- ))
- form_wizard.next_serial_count = nbre_of_lines
- form_wizard.next_serial_number = '001'
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- for move_line in move.move_line_nosuggest_ids:
- self.assertEqual(move_line.qty_done, 1)
- # The location dest must be the default one.
- self.assertEqual(move_line.location_dest_id.id, self.location_dest.id)
- # We need to activate multi-locations to use putaway rules.
- grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
- self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
- # Creates a putaway rule
- putaway_product = self.env['stock.putaway.rule'].create({
- 'product_id': self.product_serial.id,
- 'location_in_id': self.location_dest.id,
- 'location_out_id': shelf_location.id,
- })
- # Checks now with putaway...
- move = self.get_new_move(nbre_of_lines)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- ))
- form_wizard.next_serial_count = nbre_of_lines
- form_wizard.next_serial_number = '001'
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- for move_line in move.move_line_nosuggest_ids:
- self.assertEqual(move_line.qty_done, 1)
- # The location dest must be now the one from the putaway.
- self.assertEqual(move_line.location_dest_id.id, shelf_location.id)
- def test_set_multiple_lot_name_01(self):
- """ Sets five SN in one time in stock move view form, then checks move
- has five new move lines with the right `lot_name`.
- """
- nbre_of_lines = 10
- picking_type = self.env['stock.picking.type'].search([
- ('use_create_lots', '=', True),
- ('warehouse_id', '=', self.warehouse.id)
- ])
- move = self.get_new_move(nbre_of_lines)
- move.picking_type_id = picking_type
- # We must begin with a move with 10 move lines.
- self.assertEqual(len(move.move_line_ids), nbre_of_lines)
- value_list = [
- 'abc-235',
- 'abc-237',
- 'abc-238',
- 'abc-282',
- 'abc-301',
- ]
- values = '\n'.join(value_list)
- move_form = Form(move, view='stock.view_stock_move_nosuggest_operations')
- with move_form.move_line_nosuggest_ids.new() as line:
- line.lot_name = values
- move = move_form.save()
- # After we set multiple SN, we must have now 15 move lines.
- self.assertEqual(len(move.move_line_ids), nbre_of_lines + len(value_list))
- # Then we look each SN name is correct.
- for move_line in move.move_line_nosuggest_ids:
- self.assertEqual(move_line.lot_name, value_list.pop(0))
- for move_line in (move.move_line_ids - move.move_line_nosuggest_ids):
- self.assertEqual(move_line.lot_name, False)
- def test_set_multiple_lot_name_02_empty_values(self):
- """ Sets multiple values with some empty lines in one time, then checks
- we haven't create useless move line and all move line's `lot_name` have
- been correctly set.
- """
- nbre_of_lines = 5
- picking_type = self.env['stock.picking.type'].search([
- ('use_create_lots', '=', True),
- ('warehouse_id', '=', self.warehouse.id)
- ])
- move = self.get_new_move(nbre_of_lines)
- move.picking_type_id = picking_type
- # We must begin with a move with five move lines.
- self.assertEqual(len(move.move_line_ids), nbre_of_lines)
- value_list = [
- '',
- 'abc-235',
- '',
- 'abc-237',
- '',
- '',
- 'abc-238',
- 'abc-282',
- 'abc-301',
- '',
- ]
- values = '\n'.join(value_list)
- # Checks we have more values than move lines.
- self.assertTrue(len(move.move_line_ids) < len(value_list))
- move_form = Form(move, view='stock.view_stock_move_nosuggest_operations')
- with move_form.move_line_nosuggest_ids.new() as line:
- line.lot_name = values
- move = move_form.save()
- filtered_value_list = list(filter(lambda line: len(line), value_list))
- # After we set multiple SN, we must have a line for each value.
- self.assertEqual(len(move.move_line_ids), nbre_of_lines + len(filtered_value_list))
- # Then we look each SN name is correct.
- for move_line in move.move_line_nosuggest_ids:
- self.assertEqual(move_line.lot_name, filtered_value_list.pop(0))
- for move_line in (move.move_line_ids - move.move_line_nosuggest_ids):
- self.assertEqual(move_line.lot_name, False)
- def test_generate_with_putaway_02(self):
- """
- Suppose a tracked-by-USN product P
- Sub locations in WH/Stock + Storage Category
- The Storage Category adds a capacity constraint (max 1 x P / Location)
- - Plan a receipt with 2 x P
- - Receive 4 x P
- -> The test ensures that the destination locations are correct
- """
- stock_location = self.warehouse.lot_stock_id
- self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_storage_categories').id)]})
- self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_stock_multi_locations').id)]})
- # max 1 x product_serial
- stor_category = self.env['stock.storage.category'].create({
- 'name': 'Super Storage Category',
- 'product_capacity_ids': [(0, 0, {
- 'product_id': self.product_serial.id,
- 'quantity': 1,
- })]
- })
- # 5 sub locations with the storage category
- # (the last one should never be used)
- sub_loc_01, sub_loc_02, sub_loc_03, sub_loc_04, dummy = self.env['stock.location'].create([{
- 'name': 'Sub Location %s' % i,
- 'usage': 'internal',
- 'location_id': stock_location.id,
- 'storage_category_id': stor_category.id,
- } for i in [1, 2, 3, 4, 5]])
- self.env['stock.putaway.rule'].create({
- 'location_in_id': stock_location.id,
- 'location_out_id': stock_location.id,
- 'product_id': self.product_serial.id,
- 'storage_category_id': stor_category.id,
- })
- # Receive 1 x P
- receipt_picking = self.env['stock.picking'].create({
- 'picking_type_id': self.warehouse.in_type_id.id,
- 'location_id': self.env.ref('stock.stock_location_suppliers').id,
- 'location_dest_id': stock_location.id,
- })
- move = self.env['stock.move'].create({
- 'name': self.product_serial.name,
- 'product_id': self.product_serial.id,
- 'product_uom': self.product_serial.uom_id.id,
- 'product_uom_qty': 2.0,
- 'picking_id': receipt_picking.id,
- 'location_id': receipt_picking.location_id.id,
- 'location_dest_id': receipt_picking.location_dest_id.id,
- })
- receipt_picking.action_confirm()
- self.assertEqual(move.move_line_ids[0].location_dest_id, sub_loc_01)
- self.assertEqual(move.move_line_ids[1].location_dest_id, sub_loc_02)
- form_wizard = Form(self.env['stock.assign.serial'].with_context(
- default_move_id=move.id,
- default_next_serial_number='001',
- default_next_serial_count=4,
- ))
- wiz = form_wizard.save()
- wiz.generate_serial_numbers()
- self.assertRecordValues(move.move_line_ids, [
- {'qty_done': 1, 'lot_name': '001', 'location_dest_id': sub_loc_01.id},
- {'qty_done': 1, 'lot_name': '002', 'location_dest_id': sub_loc_02.id},
- {'qty_done': 1, 'lot_name': '003', 'location_dest_id': sub_loc_03.id},
- {'qty_done': 1, 'lot_name': '004', 'location_dest_id': sub_loc_04.id},
- ])
|