fix(operator-wizard): surface office-authored instructions to operators

Critical UX gap discovered in production-environment battle test: when
operator hits "Mark Done" and the input wizard fires, they only saw the
measurement prompts list. The rich-text instructions written by the
office (recipe_node.description) never reached the operator at the
exact moment they need them.

Fixed: wizard model gains instructions (Html, computed from
step.recipe_node_id.description) + has_instructions flag.
Form view renders the instructions in a prominent blue alert at the
top of the wizard, above the Measurements list. Hidden when blank
so operators on instruction-less steps don't see noise.

Also: extend default_kind Selection on fusion.plating.process.node to
match fp.step.template — both models now have the same 24 kinds. Without
this, recipe authors could pick a kind in the library template form
that the recipe-node Selection rejected with a ValueError.

Battle test artifact:
- Recipe "Hard Anodize Type III + Dye + Seal" (id=1863) — 23 steps,
  105 measurement prompts, rich-text operator instructions per step
- SO S00278 for ABC Manufactoring confirmed → fp.job 1236 / WH/JOB/00337
  with all 23 steps materialized, 105 prompts visible to operators
- Wizard test: step "11. Hard Anodize Type III" → 516 chars of
  instructions render + 7 input prompts in the form

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-29 23:05:21 -04:00
parent ec0a07fbe9
commit 875548c547
5 changed files with 403 additions and 11 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
'version': '19.0.8.12.0',
'version': '19.0.8.13.0',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',

View File

@@ -59,11 +59,31 @@ class FpJobStepInputWizard(models.TransientModel):
job_id = fields.Many2one(
related='step_id.job_id', string='Job', store=False, readonly=True,
)
# Sub 12d — surface the office-authored instructions to the operator
# at the exact moment they're recording values. Sourced from the
# recipe node's description (rich-text); empty when the recipe
# author left it blank.
instructions = fields.Html(
string='Operator Instructions',
compute='_compute_instructions',
readonly=True,
)
has_instructions = fields.Boolean(
compute='_compute_instructions',
)
line_ids = fields.One2many(
'fp.job.step.input.wizard.line', 'wizard_id',
string='Inputs',
)
@api.depends('step_id', 'step_id.recipe_node_id', 'step_id.recipe_node_id.description')
def _compute_instructions(self):
for rec in self:
node = rec.step_id.recipe_node_id if rec.step_id else False
html = (node and node.description) or ''
rec.instructions = html
rec.has_instructions = bool(html and html.strip())
@api.model
def default_get(self, fields_list):
defaults = super().default_get(fields_list)

View File

@@ -11,6 +11,17 @@
<field name="step_id" readonly="1"/>
<field name="job_id" readonly="1"/>
</group>
<field name="has_instructions" invisible="1"/>
<div class="alert alert-info"
role="alert"
invisible="not has_instructions"
style="margin-bottom: 12px;">
<h4 style="margin-top: 0;">
<i class="fa fa-info-circle"/>
Instructions for this step
</h4>
<field name="instructions" nolabel="1" readonly="1"/>
</div>
<separator string="Measurements"/>
<p class="text-muted" invisible="line_ids">
Click <strong>Add a line</strong> to record one or