from odoo import api, fields, models, _ from odoo.exceptions import UserError class SubcontractLotGenerator(models.TransientModel): _name = 'subcontract.lot.generator' _description = 'Subcontract Lot Generator Wizard' picking_id = fields.Many2one('stock.picking', string='Picking', required=True) move_id = fields.Many2one('stock.move', string='Move', required=True) product_id = fields.Many2one('product.product', string='Product', required=True) quantity = fields.Float('Quantity', required=True, default=1.0) lot_count = fields.Integer('Number of Lots', default=1, help='Number of lots to generate') tracking = fields.Selection(related='product_id.tracking') use_sequence = fields.Boolean('Use Product Sequence', default=True) @api.onchange('product_id') def _onchange_product_id(self): if self.product_id: if self.product_id.tracking == 'serial': self.lot_count = int(self.quantity) else: self.lot_count = 1 @api.onchange('quantity', 'tracking') def _onchange_quantity(self): if self.tracking == 'serial': self.lot_count = int(self.quantity) def action_generate_lots(self): """Generate lots based on wizard configuration.""" self.ensure_one() if not self.product_id.tracking in ['lot', 'serial']: raise UserError(_('Product must have lot or serial tracking enabled.')) if self.lot_count <= 0: raise UserError(_('Number of lots must be greater than 0.')) # Get the sequence tmpl = self.product_id.product_tmpl_id lot_sequence = getattr(tmpl, 'lot_sequence_id', False) if not lot_sequence and self.use_sequence: raise UserError(_('No lot sequence configured for product %s') % self.product_id.display_name) # Generate lot names if self.use_sequence and lot_sequence: # Use standard Odoo sequence generation # Removed custom batch allocation and unsafe prefix overrides lot_names = [lot_sequence.next_by_id() for _ in range(self.lot_count)] else: # Generate simple sequential names fallback lot_names = [f"LOT-{i+1:04d}" for i in range(self.lot_count)] # Create the lots Lot = self.env['stock.lot'] lot_vals_list = [] for lot_name in lot_names: lot_vals = { 'name': lot_name, 'product_id': self.product_id.id, 'company_id': self.picking_id.company_id.id, } lot_vals_list.append(lot_vals) lots = Lot.create(lot_vals_list) # Calculate quantity in Product's Base UOM # self.quantity is in self.move_id.product_uom (or whatever was passed) base_uom = self.product_id.uom_id move_uom = self.move_id.product_uom total_base_qty = self.quantity if move_uom and base_uom and move_uom != base_uom: total_base_qty = move_uom._compute_quantity(self.quantity, base_uom) if self.tracking == 'serial': # One move line per lot for serial tracking, usually 1 unit of base UOM per serial qty_per_lot = 1.0 else: # Distribute quantity across lots for lot tracking qty_per_lot = total_base_qty / self.lot_count for lot in lots: vals = { 'move_id': self.move_id.id, 'product_id': self.product_id.id, 'lot_id': lot.id, 'quantity': qty_per_lot, # Odoo 19 uses 'quantity' instead of product_uom_qty/qty_done in stock.move.line for initial demand? # Let's check standard stock.move.line fields. Usually it's quantity (done) or reserved_uom_qty. # In Odoo 19 stock.move.line has 'quantity'. 'product_uom_id': base_uom.id, 'location_id': self.move_id.location_id.id, 'location_dest_id': self.move_id.location_dest_id.id, 'picking_id': self.picking_id.id, } self.env['stock.move.line'].create(vals) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': _('Generated %d lots for %s') % (len(lots), self.product_id.display_name), 'type': 'success', 'sticky': False, } }