product_lot_sequence_per_pr.../models/stock_move.py
2025-09-29 11:47:29 +07:00

71 lines
3.6 KiB
Python

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."""
res = super()._onchange_product_id()
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)
return res
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)