feat(step-library): full plating workflow coverage + per-recipe configurability + audit
Implements 2026-04-29-step-library-audit-design.md. Bumps fusion_plating to 19.0.18.7.0, fusion_plating_jobs to 19.0.8.12.0, fusion_plating_reports to 19.0.10.2.0. LIBRARY EXPANSION - 8 new Step Kinds: Receiving, Electroclean, Strike, Salt Spray, Adhesion Test, Hardness Test, Packaging, Tank Replenishment - 4 new input types: photo, multi_point_thickness, bath_chemistry_panel, ph - DEFAULT_INPUTS_BY_KIND rewritten to seed audit-grade prompts on every kind (bath IDs, photos, multi-point thickness, signatures, etc.) - + Common Audit Fields one-click button on the library template form - Default Operator Instructions relabel + alert callout PER-RECIPE CONFIGURABILITY - collect (Boolean) per recipe-step input prompt — opt out without delete - collect_measurements (Boolean) master switch on recipe step — when off, wizard skips entirely - template_input_id (Many2one) traceability link from recipe to library - Recipe-step backend form view exposes the new fields with handle drag, toggle, target range, and library-source column RUNTIME WIRING - Step input wizard filters node.input_ids to step_input AND collect=True; short-circuits on collect_measurements=False - New input types: photo (image widget + ir.attachment), multi-point thickness (5 readings + auto avg, skips empty cells), bath chemistry panel (pH/conc/temp/bath bundle), pH (0-14 numeric) - Composite values JSON-serialized into value_text; photo via attachment CoC REPORT - Filters captured prompts to collect=True only - Renders new input types with appropriate format MIGRATION (post-migrate.py for 19.0.18.7.0) - Backfills collect=True on recipe-step inputs - Backfills collect_measurements=True on recipe steps - Re-runs action_seed_default_inputs on every existing template (idempotent, preserves user edits) - Backfills template_input_id by name-matching against source library template (handles JSONB vs varchar name columns) SEED DATA - 8 example templates (one per new kind) in fp_step_template_data.xml with noupdate=1 BATTLE TEST - bt_step_library_audit.py: 29 assertions all PASS on entech OWL EDITOR EXTENSION DEFERRED - The simple recipe editor's per-step Instructions/Measurements expansions were not implemented in this pass; users configure via the backend recipe-step form. Track follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,7 @@ class FpAddFromSoWizard(models.TransientModel):
|
||||
'quantity': int(src.product_uom_qty) or 1,
|
||||
'unit_price': src.price_unit or 0.0,
|
||||
'part_deadline': src.x_fc_part_deadline,
|
||||
'part_deadline_offset_days': src.x_fc_part_deadline_offset_days,
|
||||
'rush_order': src.x_fc_rush_order,
|
||||
'wo_group_tag': src.x_fc_wo_group_tag or False,
|
||||
'line_description': src.name,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
@@ -222,8 +224,27 @@ class FpDirectOrderLine(models.Model):
|
||||
|
||||
# ---- Scheduling / fulfilment ----
|
||||
part_deadline = fields.Date(
|
||||
string='Part Deadline',
|
||||
help='Per-line deadline. Defaults to SO customer deadline if blank.',
|
||||
string='Part Deadline Override',
|
||||
help='Absolute-date manual override. Beats days-offset and the '
|
||||
'part default lead time. Leave blank to fall through.',
|
||||
)
|
||||
part_deadline_offset_days = fields.Integer(
|
||||
string='Days Offset',
|
||||
help='Manual override expressed as "+N days from the order\'s '
|
||||
'customer deadline". Ignored if Part Deadline Override is set.',
|
||||
)
|
||||
effective_part_deadline = fields.Date(
|
||||
string='Effective Deadline',
|
||||
compute='_compute_effective_part_deadline',
|
||||
store=True,
|
||||
help='Resolution: explicit override → days offset → part default '
|
||||
'lead time → order customer deadline.',
|
||||
)
|
||||
effective_internal_deadline = fields.Date(
|
||||
string='Shop Target',
|
||||
compute='_compute_effective_internal_deadline',
|
||||
store=True,
|
||||
help='Effective customer deadline minus the order\'s shop buffer.',
|
||||
)
|
||||
rush_order = fields.Boolean(string='Rush')
|
||||
wo_group_tag = fields.Char(
|
||||
@@ -323,6 +344,68 @@ class FpDirectOrderLine(models.Model):
|
||||
for rec in self:
|
||||
rec.serial_count = len(rec.serial_ids)
|
||||
|
||||
@api.depends(
|
||||
'part_deadline',
|
||||
'part_deadline_offset_days',
|
||||
'part_catalog_id',
|
||||
'part_catalog_id.x_fc_default_lead_time_days',
|
||||
'wizard_id.customer_deadline',
|
||||
'wizard_id.planned_start_date',
|
||||
)
|
||||
def _compute_effective_part_deadline(self):
|
||||
"""Mirror of SaleOrderLine._compute_effective_part_deadline."""
|
||||
for line in self:
|
||||
wiz = line.wizard_id
|
||||
commit = wiz.customer_deadline if wiz else False
|
||||
start = (
|
||||
wiz.planned_start_date if wiz else False
|
||||
) or fields.Date.context_today(line)
|
||||
|
||||
if line.part_deadline:
|
||||
line.effective_part_deadline = line.part_deadline
|
||||
continue
|
||||
if line.part_deadline_offset_days and commit:
|
||||
line.effective_part_deadline = (
|
||||
commit + timedelta(days=line.part_deadline_offset_days)
|
||||
)
|
||||
continue
|
||||
part_lead = (
|
||||
line.part_catalog_id
|
||||
and line.part_catalog_id.x_fc_default_lead_time_days
|
||||
)
|
||||
if part_lead:
|
||||
line.effective_part_deadline = (
|
||||
start + timedelta(days=part_lead)
|
||||
)
|
||||
continue
|
||||
if commit:
|
||||
line.effective_part_deadline = commit
|
||||
continue
|
||||
line.effective_part_deadline = start
|
||||
|
||||
@api.depends(
|
||||
'effective_part_deadline',
|
||||
'wizard_id.customer_deadline',
|
||||
'wizard_id.internal_deadline',
|
||||
)
|
||||
def _compute_effective_internal_deadline(self):
|
||||
for line in self:
|
||||
eff = line.effective_part_deadline
|
||||
if not eff:
|
||||
line.effective_internal_deadline = False
|
||||
continue
|
||||
wiz = line.wizard_id
|
||||
commit = wiz.customer_deadline if wiz else False
|
||||
internal = wiz.internal_deadline if wiz else False
|
||||
if commit and internal and commit >= internal:
|
||||
buffer_days = (commit - internal).days
|
||||
target = eff - timedelta(days=buffer_days)
|
||||
line.effective_internal_deadline = (
|
||||
target if target <= eff else eff
|
||||
)
|
||||
else:
|
||||
line.effective_internal_deadline = eff
|
||||
|
||||
@api.depends('serial_ids')
|
||||
def _compute_primary_serial(self):
|
||||
for rec in self:
|
||||
|
||||
@@ -557,6 +557,7 @@ class FpDirectOrderWizard(models.Model):
|
||||
'x_fc_coating_config_id': line.coating_config_id.id,
|
||||
'x_fc_treatment_ids': [(6, 0, line.treatment_ids.ids)],
|
||||
'x_fc_part_deadline': line.part_deadline,
|
||||
'x_fc_part_deadline_offset_days': line.part_deadline_offset_days,
|
||||
'x_fc_rush_order': line.rush_order,
|
||||
'x_fc_wo_group_tag': line.wo_group_tag or False,
|
||||
'x_fc_part_wo_description': line.part_wo_description or False,
|
||||
|
||||
@@ -165,10 +165,6 @@
|
||||
widget="boolean_toggle"
|
||||
invisible="not process_variant_id"
|
||||
optional="hide"/>
|
||||
<button name="action_customize_process" type="object"
|
||||
string="Customize" icon="fa-pencil-square-o"
|
||||
class="btn-link"
|
||||
invisible="not process_variant_id"/>
|
||||
<field name="effective_process_id"
|
||||
string="Effective Process"
|
||||
readonly="1"
|
||||
@@ -178,19 +174,24 @@
|
||||
readonly="1"
|
||||
optional="hide"/>
|
||||
<field name="thickness_id"
|
||||
options="{'no_create': True}"
|
||||
options="{'no_quick_create': True}"
|
||||
context="{'default_coating_config_id': coating_config_id}"
|
||||
domain="[('coating_config_id', '=', coating_config_id)]"
|
||||
invisible="not coating_config_id"
|
||||
optional="show"/>
|
||||
<field name="serial_ids"
|
||||
widget="many2many_tags"
|
||||
options="{'no_quick_create': False, 'color_field': 'state_color'}"
|
||||
domain="[('part_id', '=', part_catalog_id)]"
|
||||
optional="show"/>
|
||||
<field name="serial_count"
|
||||
string="# SN"
|
||||
string="Serial Count"
|
||||
optional="hide"/>
|
||||
<button name="action_open_serial_bulk_add" type="object"
|
||||
string="Bulk Add Serials" icon="fa-list-ol"
|
||||
class="btn-link"/>
|
||||
title="Bulk add serials"
|
||||
icon="fa-list-ol"
|
||||
class="btn-link"
|
||||
invisible="not part_catalog_id or serial_count > 0"/>
|
||||
<field name="job_number" optional="hide"/>
|
||||
<field name="treatment_ids"
|
||||
widget="many2many_tags"
|
||||
@@ -207,7 +208,20 @@
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
sum="Total"/>
|
||||
<field name="part_deadline"/>
|
||||
<field name="effective_part_deadline"
|
||||
string="Effective Deadline"
|
||||
optional="show"
|
||||
readonly="1"/>
|
||||
<field name="part_deadline"
|
||||
string="Part Deadline Override"
|
||||
optional="hide"/>
|
||||
<field name="part_deadline_offset_days"
|
||||
string="Days Offset"
|
||||
optional="hide"/>
|
||||
<field name="effective_internal_deadline"
|
||||
string="Shop Target"
|
||||
optional="hide"
|
||||
readonly="1"/>
|
||||
<field name="wo_group_tag" optional="show"/>
|
||||
<field name="rush_order" optional="hide"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
|
||||
Reference in New Issue
Block a user