diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index f3d0ba9ffce..24fc4d79e5c 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -182,15 +182,14 @@ class StockMove(models.Model): if self.product_id: self.description_picking = self.product_id._get_description(self.picking_type_id) - @api.depends('has_tracking', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'picking_type_id.show_reserved', 'picking_type_id.show_operations') + @api.depends('has_tracking', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'state') def _compute_display_assign_serial(self): for move in self: move.display_assign_serial = ( move.has_tracking == 'serial' and move.state in ('partially_available', 'assigned', 'confirmed') and move.picking_type_id.use_create_lots and - not move.picking_type_id.use_existing_lots and - not move.picking_type_id.show_reserved + not move.picking_type_id.use_existing_lots ) @api.depends('picking_id.is_locked') @@ -641,23 +640,14 @@ class StockMove(models.Model): suffix = splitted[1] initial_number = int(initial_number) - move_lines_commands = [] - location_dest = self.location_dest_id._get_putaway_strategy(self.product_id) or self.location_dest_id + lot_names = [] for i in range(0, next_serial_count): - lot_name = '%s%s%s' % ( + lot_names.append('%s%s%s' % ( prefix, str(initial_number + i).zfill(padding), suffix - ) - move_lines_commands.append((0, 0, { - 'lot_name': lot_name, - 'qty_done': 1, - 'product_id': self.product_id.id, - 'product_uom_id': self.product_id.uom_id.id, - 'location_id': self.location_id.id, - 'location_dest_id': location_dest.id, - 'picking_id': self.picking_id.id, - })) + )) + move_lines_commands = self._generate_serial_move_line_commands(lot_names) self.write({'move_line_ids': move_lines_commands}) return True @@ -816,31 +806,34 @@ class StockMove(models.Model): self.name = product.partner_ref self.product_uom = product.uom_id.id - @api.onchange('move_line_nosuggest_ids') - def onchange_move_line_nosuggest_ids(self): + @api.onchange('move_line_ids', 'move_line_nosuggest_ids') + def onchange_move_line_ids(self): + if not self.picking_type_id.use_create_lots: + # This onchange manages the creation of multiple lot name. We don't + # need that if the picking type disallows the creation of new lots. + return + breaking_char = '\n' - move_lines_to_create = [] - for move_line in self.move_line_nosuggest_ids: + if self.picking_type_id.show_reserved: + move_lines = self.move_line_ids + else: + move_lines = self.move_line_nosuggest_ids + + for move_line in move_lines: # Look if the `lot_name` contains multiple values. if breaking_char in (move_line.lot_name or ''): - splitted_lines = move_line.lot_name.split(breaking_char) - splitted_lines = list(filter(lambda line: line, splitted_lines)) - move_line.lot_name = splitted_lines[0] - # For each SN line, set move ine data... - for line in splitted_lines[1:]: - move_line_data = { - 'lot_name': line, - 'qty_done': 1, - 'product_id': move_line.product_id.id, - 'product_uom_id': move_line.product_id.uom_id.id, - 'location_id': move_line.location_id.id, - 'location_dest_id': move_line.location_dest_id.id, - 'owner_id': move_line.owner_id or False, - 'package_id': move_line.package_id or False, - } - move_lines_to_create += [(0, 0, move_line_data)] - # ... then create these move lines. - self.update({'move_line_nosuggest_ids': move_lines_to_create}) + split_lines = move_line.lot_name.split(breaking_char) + split_lines = list(filter(None, split_lines)) + move_line.lot_name = split_lines[0] + move_lines_commands = self._generate_serial_move_line_commands( + split_lines[1:], + origin_move_line=move_line, + ) + if self.picking_type_id.show_reserved: + self.update({'move_line_ids': move_lines_commands}) + else: + self.update({'move_line_nosuggest_ids': move_lines_commands}) + break @api.onchange('product_uom') def onchange_product_uom(self): @@ -904,6 +897,64 @@ class StockMove(models.Model): def _assign_picking_post_process(self, new=False): pass + def _generate_serial_move_line_commands(self, lot_names, origin_move_line=None): + """Return a list of commands to update the move lines (write on + existing ones or create new ones). + Called when user want to create and assign multiple serial numbers in + one time (using the button/wizard or copy-paste a list in the field). + + :param lot_names: A list containing all serial number to assign. + :type lot_names: list + :param origin_move_line: A move line to duplicate the value from, default to None + :type origin_move_line: record of :class:`stock.move.line` + :return: A list of commands to create/update :class:`stock.move.line` + :rtype: list + """ + self.ensure_one() + + # Select the right move lines depending of the picking type configuration. + move_lines = self.env['stock.move.line'] + if self.picking_type_id.show_reserved: + move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + else: + move_lines = self.move_line_nosuggest_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + + if origin_move_line: + location_dest = origin_move_line.location_dest_id + else: + location_dest = self.location_dest_id._get_putaway_strategy(self.product_id) + move_line_vals = { + 'location_dest_id': location_dest.id or self.location_dest_id.id, + 'location_id': self.location_id.id, + 'product_id': self.product_id.id, + 'product_uom_id': self.product_id.uom_id.id, + 'qty_done': 1, + } + if origin_move_line: + # `owner_id` and `package_id` are taken only in the case we create + # new move lines from an existing move line. Also, updates the + # `qty_done` because it could be usefull for products tracked by lot. + move_line_vals.update({ + 'owner_id': origin_move_line.owner_id.id, + 'package_id': origin_move_line.package_id.id, + 'qty_done': origin_move_line.qty_done or 1, + }) + + move_lines_commands = [] + for lot_name in lot_names: + # We write the lot name on an existing move line (if we have still one)... + if move_lines: + move_lines_commands.append((1, move_lines[0].id, { + 'lot_name': lot_name, + 'qty_done': 1, + })) + move_lines = move_lines[1:] + # ... or create a new move line with the serial name. + else: + move_line_cmd = dict(move_line_vals, lot_name=lot_name) + move_lines_commands.append((0, 0, move_line_cmd)) + return move_lines_commands + def _get_new_picking_values(self): """ return create values for new picking that will be linked with group of moves in self. diff --git a/addons/stock/tests/test_generate_serial_numbers.py b/addons/stock/tests/test_generate_serial_numbers.py index ab534983696..1c3e79454f1 100644 --- a/addons/stock/tests/test_generate_serial_numbers.py +++ b/addons/stock/tests/test_generate_serial_numbers.py @@ -17,7 +17,7 @@ class StockGenerate(SavepointCase): }) cls.uom_unit = cls.env.ref('uom.product_uom_unit') - warehouse = cls.env['stock.warehouse'].create({ + cls.warehouse = cls.env['stock.warehouse'].create({ 'name': 'Base Warehouse', 'reception_steps': 'one_step', 'delivery_steps': 'ship_only', @@ -25,11 +25,11 @@ class StockGenerate(SavepointCase): }) cls.location = cls.env['stock.location'].create({ 'name': 'Room A', - 'location_id': warehouse.lot_stock_id.id, + 'location_id': cls.warehouse.lot_stock_id.id, }) cls.location_dest = cls.env['stock.location'].create({ 'name': 'Room B', - 'location_id': warehouse.lot_stock_id.id, + 'location_id': cls.warehouse.lot_stock_id.id, }) cls.Wizard = cls.env['stock.assign.serial'] @@ -275,7 +275,12 @@ class StockGenerate(SavepointCase): 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) @@ -307,7 +312,12 @@ class StockGenerate(SavepointCase): 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) diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 97bb1e2d8fe..4aa69439b00 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -228,7 +228,7 @@ - + @@ -257,7 +257,10 @@ 'default_product_id': parent.product_id, }" /> - + @@ -300,20 +303,6 @@ - - stock.move.line.operations.tree - stock.move.line - 1000 - primary - - - - text - Write your SN/LN one by one or copy paste a list. - - - - stock.move.form stock.move