106 lines
4.2 KiB
Python
106 lines
4.2 KiB
Python
from odoo import api, models
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StockLot(models.Model):
|
|
_inherit = 'stock.lot'
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
"""
|
|
Optimized batch lot creation with efficient sequence allocation.
|
|
For large batches, allocates all sequence numbers in a single database query.
|
|
"""
|
|
# Group lots by product for batch processing
|
|
lots_by_product = {}
|
|
for vals in vals_list:
|
|
if not vals.get('name') and vals.get('product_id'):
|
|
product_id = vals['product_id']
|
|
if product_id not in lots_by_product:
|
|
lots_by_product[product_id] = []
|
|
lots_by_product[product_id].append(vals)
|
|
|
|
# Process each product group
|
|
for product_id, product_vals_list in lots_by_product.items():
|
|
product = self.env['product.product'].browse(product_id)
|
|
seq = getattr(product.product_tmpl_id, 'lot_sequence_id', False)
|
|
|
|
if seq and len(product_vals_list) > 10:
|
|
# Use optimized batch allocation for large quantities
|
|
lot_names = self._allocate_sequence_batch(seq, len(product_vals_list))
|
|
for vals, name in zip(product_vals_list, lot_names):
|
|
vals['name'] = name
|
|
_logger.info(f"Batch allocated {len(lot_names)} lot names for product {product.display_name}")
|
|
else:
|
|
# Standard allocation for small quantities
|
|
for vals in product_vals_list:
|
|
if seq:
|
|
vals['name'] = seq.next_by_id()
|
|
else:
|
|
# Fallback to global sequence if no product sequence
|
|
vals['name'] = self.env['ir.sequence'].next_by_code('stock.lot.serial')
|
|
|
|
return super().create(vals_list)
|
|
|
|
def _allocate_sequence_batch(self, sequence, count):
|
|
"""
|
|
Allocate multiple sequence numbers in a single database operation.
|
|
This is significantly faster than calling next_by_id() in a loop for large quantities.
|
|
Properly handles date format codes like %(y)s, %(month)s, %(day)s, etc.
|
|
"""
|
|
if count <= 0:
|
|
return []
|
|
|
|
# Use PostgreSQL's generate_series to allocate multiple sequence values at once
|
|
self.env.cr.execute("""
|
|
SELECT nextval(%s) FROM generate_series(1, %s)
|
|
""", (f"ir_sequence_{sequence.id:03d}", count))
|
|
|
|
sequence_numbers = [row[0] for row in self.env.cr.fetchall()]
|
|
|
|
# Get the interpolation context for date formatting (same as ir.sequence)
|
|
from datetime import datetime
|
|
now = datetime.now()
|
|
|
|
# Build the interpolation dictionary (same format as Odoo's ir.sequence)
|
|
interpolation_dict = {
|
|
'year': now.strftime('%Y'),
|
|
'y': now.strftime('%y'),
|
|
'month': now.strftime('%m'),
|
|
'day': now.strftime('%d'),
|
|
'doy': now.strftime('%j'),
|
|
'woy': now.strftime('%W'),
|
|
'weekday': now.strftime('%w'),
|
|
'h24': now.strftime('%H'),
|
|
'h12': now.strftime('%I'),
|
|
'min': now.strftime('%M'),
|
|
'sec': now.strftime('%S'),
|
|
}
|
|
|
|
# Format prefix and suffix with date codes
|
|
try:
|
|
prefix = (sequence.prefix or '') % interpolation_dict if sequence.prefix else ''
|
|
except (KeyError, ValueError):
|
|
# If formatting fails, use prefix as-is
|
|
prefix = sequence.prefix or ''
|
|
|
|
try:
|
|
suffix = (sequence.suffix or '') % interpolation_dict if sequence.suffix else ''
|
|
except (KeyError, ValueError):
|
|
# If formatting fails, use suffix as-is
|
|
suffix = sequence.suffix or ''
|
|
|
|
# Format the sequence numbers according to the sequence configuration
|
|
lot_names = []
|
|
for seq_num in sequence_numbers:
|
|
lot_name = '{}{:0{}d}{}'.format(
|
|
prefix,
|
|
seq_num,
|
|
sequence.padding,
|
|
suffix
|
|
)
|
|
lot_names.append(lot_name)
|
|
|
|
return lot_names |