from odoo import api, models class StockMove(models.Model): _inherit = 'stock.move' @api.onchange('product_id') def _onchange_product_id(self): """Seed the next_serial field on stock.move when product changes, if product has a sequence.""" # Set next_serial based on product's lot sequence if available if self.product_id and getattr(self.product_id.product_tmpl_id, 'lot_sequence_id', False): self.next_serial = getattr(self.product_id.product_tmpl_id, 'next_serial', False) else: self.next_serial = False def _create_lot_ids_from_move_line_vals(self, vals_list, product_id, company_id=False): """ Normalize incoming lot names during 'Generate Serials/Lots' or 'Import Serials/Lots'. - If user leaves '0' or empty as lot name, create lots without a name to let stock.lot.create() generate names from the product's per-product sequence (handled by our stock.lot override). - Otherwise, fallback to the standard behavior for explicit names. """ Lot = self.env['stock.lot'] # First handle entries that should be auto-generated (empty or '0') remaining_vals = [] for vals in vals_list: lot_name = (vals.get('lot_name') or '').strip() if not lot_name or lot_name == '0': lot_vals = { 'product_id': product_id, } if company_id: lot_vals['company_id'] = company_id # omit 'name' to trigger sequence in stock.lot.create() override lot = Lot.create([lot_vals])[0] vals['lot_id'] = lot.id vals['lot_name'] = False else: remaining_vals.append(vals) # Delegate remaining with explicit names to the standard implementation if remaining_vals: return super()._create_lot_ids_from_move_line_vals(remaining_vals, product_id, company_id) return None @api.model def action_generate_lot_line_vals(self, context, mode, first_lot, count, lot_text): """ If the 'Generate Serials/Lots' action is invoked with an empty or '0' base, generate names using the per-product sequence instead of stock.lot.generate_lot_names('0', n), which would yield 0,1,2... """ if mode == 'generate': product_id = context.get('default_product_id') if product_id: product = self.env['product.product'].browse(product_id) tmpl = product.product_tmpl_id if (not first_lot or first_lot == '0') and getattr(tmpl, 'lot_sequence_id', False): seq = tmpl.lot_sequence_id # Generate count names directly from the sequence generated_names = [seq.next_by_id() for _ in range(count or 0)] # Reuse parent implementation for the rest of the processing (locations, uom, etc.) # by passing a non-zero base and then overriding the names in the returned list. fake_first = 'SEQDUMMY-1' vals_list = super().action_generate_lot_line_vals(context, mode, fake_first, count, lot_text) # Overwrite the lot_name with sequence-based names; keep all other computed values (uom, putaway). for vals, name in zip(vals_list, generated_names): vals['lot_name'] = name return vals_list # Fallback to standard behavior return super().action_generate_lot_line_vals(context, mode, first_lot, count, lot_text)