feat: Add subcontract lot generation wizard and UI buttons to stock pickings and moves, including validation for cancelled manufacturing orders.

This commit is contained in:
Suherdy Yacob 2026-01-28 13:45:34 +07:00
parent 56cd97ef27
commit de4f502d01
6 changed files with 149 additions and 0 deletions

View File

@ -5,6 +5,7 @@ This Odoo 19 module provides automated lot/serial number generation specifically
## Features
- **Auto-Generate Lots**: Automatically generates lot/serial numbers for subcontract receipt moves based on the product's configured sequence.
- **Validation Check**: Prevents lot generation if the linked Subcontract Manufacturing Order is cancelled, alerting the user to potential data inconsistency.
- **Wizard Support**: Includes a "Generate Subcontract Lots" wizard for manual control and batch generation.
- **Validation Hook**: Automatically triggers lot generation when validating a subcontracting receipt (Stock Picking).
- **Native Compatibility**: Uses Odoo 19's standard `lot_sequence_id` on the product template, ensuring no conflict with standard features.

View File

@ -1,4 +1,6 @@
from odoo import api, models, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
@ -11,9 +13,24 @@ class StockMove(models.Model):
"""
Auto-generate lot numbers for subcontracting moves.
This method handles the automatic lot generation for subcontracted products.
It also validates that the linked Manufacturing Order is not cancelled.
"""
self.ensure_one()
if getattr(self, 'is_subcontract', False):
# Check for cancelled subcontract MOs
# We use getattr/safe access or rely on the fact that this module depends on mrp_subcontracting
productions = self._get_subcontract_production()
cancelled_productions = productions.filtered(lambda p: p.state == 'cancel')
if cancelled_productions:
raise UserError(_(
"Cannot generate lots for product %s.\n"
"The linked Subcontracting Manufacturing Order (e.g., %s) is cancelled.\n"
"Lots generated would not be correctly linked to the receipt quantity.\n"
"Please check the Manufacturing Order status."
) % (self.product_id.display_name, cancelled_productions[0].name))
if not self.product_id.tracking in ['lot', 'serial']:
return []

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_subcontract_lot_generator,subcontract.lot.generator,model_subcontract_lot_generator,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_subcontract_lot_generator subcontract.lot.generator model_subcontract_lot_generator base.group_user 1 1 1 1

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add menu item to stock move -->
<record id="view_stock_move_form_inherit_lot_generation" model="ir.ui.view">
<field name="name">stock.move.form.inherit.lot.generation</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="action_generate_lots_for_move"
type="object"
string="Generate Lots"
class="btn-secondary"
icon="fa-plus"
invisible="not is_subcontract"
help="Generate lot/serial numbers for this subcontract move"/>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add auto-generate lots button to subcontracting receipt forms -->
<record id="view_picking_form_inherit_subcontract_lots" model="ir.ui.view">
<field name="name">stock.picking.form.inherit.subcontract.lots</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<!-- Add invisible fields for computed values -->
<xpath expr="//form" position="inside">
<field name="has_subcontract_moves" invisible="1"/>
</xpath>
<!-- Add the auto-generate button in the header for subcontract receipts -->
<xpath expr="//header" position="inside">
<button name="action_auto_generate_lots_subcontract"
type="object"
string="Auto Generate Lots"
class="btn-secondary"
icon="fa-plus"
invisible="picking_type_code != 'incoming' or not has_subcontract_moves"
help="Automatically generate lot/serial numbers for subcontracted products"/>
<button name="action_open_subcontract_lot_wizard"
type="object"
string="Advanced Lot Generation"
class="btn-secondary"
icon="fa-cogs"
invisible="picking_type_code != 'incoming' or not has_subcontract_moves"
help="Advanced lot generation with custom options"/>
</xpath>
</field>
</record>
<!-- Enhance the move lines view to show auto-generate option -->
<record id="view_stock_move_line_detailed_operation_inherit" model="ir.ui.view">
<field name="name">stock.move.line.detailed.operation.inherit.subcontract</field>
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_stock_move_line_detailed_operation_tree"/>
<field name="arch" type="xml">
<!-- Add a button in the move line tree for individual lot generation -->
<xpath expr="//field[@name='lot_name']" position="after">
<button name="action_open_lot_generator"
type="object"
icon="fa-plus"
class="btn-link"
context="{'default_product_id': product_id, 'default_move_id': move_id}"
invisible="not product_id"
help="Generate lot/serial numbers"/>
</xpath>
</field>
</record>
<!-- Add a smart button to show generated lots for subcontract moves -->
<record id="view_picking_form_subcontract_lots_smart_button" model="ir.ui.view">
<field name="name">stock.picking.form.subcontract.lots.smart.button</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button type="object"
name="action_view_generated_lots"
class="oe_stat_button"
icon="fa-barcode"
invisible="picking_type_code != 'incoming' or not has_subcontract_moves">
<field name="subcontract_lot_count" widget="statinfo" string="Generated Lots"/>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Subcontract Lot Generator Wizard Form -->
<record id="view_subcontract_lot_generator_form" model="ir.ui.view">
<field name="name">subcontract.lot.generator.form</field>
<field name="model">subcontract.lot.generator</field>
<field name="arch" type="xml">
<form string="Generate Lots for Subcontract">
<group>
<group>
<field name="picking_id" readonly="1"/>
<field name="move_id" readonly="1"/>
<field name="product_id" readonly="1"/>
<field name="tracking" invisible="1"/>
</group>
<group>
<field name="quantity"/>
<field name="lot_count" readonly="tracking == 'serial'"/>
<field name="use_sequence"/>
</group>
</group>
<footer>
<button name="action_generate_lots" type="object" string="Generate Lots" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Action for the wizard -->
<record id="action_subcontract_lot_generator" model="ir.actions.act_window">
<field name="name">Generate Subcontract Lots</field>
<field name="res_model">subcontract.lot.generator</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{
'default_picking_id': active_id,
}</field>
</record>
</odoo>