subcontract_generate_lot/models/stock_picking.py
2026-01-28 13:25:23 +07:00

186 lines
7.4 KiB
Python

from odoo import api, fields, models
import logging
_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
subcontract_lot_count = fields.Integer(
'Subcontract Lots Count',
compute='_compute_subcontract_lot_count'
)
has_subcontract_moves = fields.Boolean(
'Has Subcontract Moves',
compute='_compute_has_subcontract_moves'
)
@api.depends('move_ids')
def _compute_has_subcontract_moves(self):
"""Compute if this picking has any subcontract moves."""
for picking in self:
# Check for 'is_subcontract' field safely using getattr/mapped in case other modules affect it
subcontract_moves = picking.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))
picking.has_subcontract_moves = bool(subcontract_moves)
@api.depends('move_ids.move_line_ids.lot_id')
def _compute_subcontract_lot_count(self):
"""Compute the number of lots generated for subcontract moves."""
for picking in self:
if picking.picking_type_code == 'incoming':
subcontract_moves = picking.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))
lot_ids = subcontract_moves.move_line_ids.mapped('lot_id')
picking.subcontract_lot_count = len(lot_ids)
else:
picking.subcontract_lot_count = 0
def action_view_generated_lots(self):
"""Action to view all lots generated for subcontract moves in this picking."""
self.ensure_one()
subcontract_moves = self.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))
lot_ids = subcontract_moves.move_line_ids.mapped('lot_id').ids
if not lot_ids:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'No Lots Found',
'message': 'No lots have been generated for subcontract moves yet.',
'type': 'info',
'sticky': False,
}
}
return {
'name': f'Generated Lots ({len(lot_ids)})',
'type': 'ir.actions.act_window',
'res_model': 'stock.lot',
'view_mode': 'list,form',
'views': [(False, 'list'), (False, 'form')],
'domain': [('id', 'in', lot_ids)],
'context': {
'create': False,
'edit': False,
},
'target': 'current',
}
def action_auto_generate_lots_subcontract(self):
"""
Action to auto-generate lot numbers for subcontracting moves.
Similar to the "+" icon functionality in MO forms.
"""
generated_count = 0
generated_lots = []
for picking in self:
if picking.picking_type_code == 'incoming':
subcontract_moves = picking.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))
if not subcontract_moves:
continue
for move in subcontract_moves:
if move.product_id.tracking in ['lot', 'serial']:
try:
lots = move._auto_generate_lots_for_subcontract()
if lots:
generated_count += len(lots)
generated_lots.extend([lot.name for lot in lots])
except Exception as e:
_logger.error(f"Error generating lots for move {move.id}: {e}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Generation Error',
'message': f'Error generating lots: {str(e)}',
'type': 'danger',
'sticky': True,
}
}
if generated_count > 0:
message = f'Generated {generated_count} lots: {", ".join(generated_lots[:5])}'
if len(generated_lots) > 5:
message += f' and {len(generated_lots) - 5} more...'
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Success',
'message': message,
'type': 'success',
'sticky': False,
}
}
else:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'No Lots Generated',
'message': 'No lots were generated. Check that products have lot/serial tracking and custom sequences configured.',
'type': 'info',
'sticky': False,
}
}
def _auto_assign_lots_on_subcontract_receipt(self):
"""
Automatically assign lot numbers when validating subcontract receipts.
This is called during the validation process.
"""
for picking in self:
if picking.picking_type_code == 'incoming':
subcontract_moves = picking.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))
for move in subcontract_moves:
if move.product_id.tracking in ['lot', 'serial'] and move.state not in ['done', 'cancel']:
move._auto_generate_lots_for_subcontract()
def action_open_subcontract_lot_wizard(self):
"""Open the subcontract lot generator wizard."""
self.ensure_one()
# Find the first subcontract move for default values
subcontract_move = self.move_ids.filtered(lambda m: getattr(m, 'is_subcontract', False))[:1]
if not subcontract_move:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'No Subcontract Moves',
'message': 'This picking does not contain any subcontract moves.',
'type': 'warning',
'sticky': False,
}
}
context = {
'default_picking_id': self.id,
}
if subcontract_move:
context.update({
'default_move_id': subcontract_move.id,
'default_product_id': subcontract_move.product_id.id,
'default_quantity': subcontract_move.product_uom_qty,
})
return {
'name': 'Generate Subcontract Lots',
'type': 'ir.actions.act_window',
'res_model': 'subcontract.lot.generator',
'view_mode': 'form',
'target': 'new',
'context': context,
}
def button_validate(self):
"""Override to auto-generate lots for subcontract moves before validation."""
# Auto-generate lots for subcontract moves if needed
self._auto_assign_lots_on_subcontract_receipt()
return super().button_validate()