feat(jobs): step details quick-look modal for backend managers

Click a step's name in the embedded job-form list → opens a read-only
modal with everything a manager wants in one scroll: equipment,
schedule, master collect-measurements banner, operator instructions
(rich-text from recipe_node.description), measurement prompts list,
and values recorded so far.

Implementation: separate read-only form view bound to the embedded
field via context={'form_view_ref': '...'}. The standalone editable
form view stays registered for the Job Steps menu, so direct
navigation still loads the editable variant.

Three new computed/related fields on fp.job.step:
- quick_look_instructions (Html, related from recipe_node_id.description)
- quick_look_prompt_ids (filtered+sorted recipe_node.input_ids, step_input only)
- quick_look_recorded_value_ids (search across moves: input_value rows
  whose move.from_step_id == self.id)

Plus a small action_open_full_form method that escapes from the modal
to the editable form when the manager actually needs to edit.

Edge cases:
- No recipe_node_id → instructions panel shows empty-state hint
- collect_measurements=False → amber banner: "Master switch off — no
  values will be collected at runtime"
- Multiple moves on same step → values list shows all, newest first

Spec: docs/superpowers/specs/2026-04-30-step-details-modal-design.md.
Verified on entech: step "11. Hard Anodize Type III" populates with
516 chars instructions + 7 prompts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-29 23:35:08 -04:00
parent 875548c547
commit 555dd5421f
5 changed files with 241 additions and 2 deletions

View File

@@ -872,3 +872,64 @@ class FpJobStep(models.Model):
step.duration_running_minutes = closed + running
else:
step.duration_running_minutes = step.duration_actual or 0.0
# ------------------------------------------------------------------
# Sub 12d — Step Details Quick-Look modal
# ------------------------------------------------------------------
# Three computed/related fields that power the read-only manager
# quick-look modal. The modal is bound via context= on the parent
# job form's <field name="step_ids"/> — no TransientModel needed.
quick_look_instructions = fields.Html(
string='Operator Instructions',
related='recipe_node_id.description',
readonly=True,
)
quick_look_collect_master = fields.Boolean(
string='Collect Measurements',
related='recipe_node_id.collect_measurements',
readonly=True,
)
quick_look_prompt_ids = fields.Many2many(
'fusion.plating.process.node.input',
string='Prompts',
compute='_compute_quick_look_prompt_ids',
)
quick_look_recorded_value_ids = fields.Many2many(
'fp.job.step.move.input.value',
string='Recorded Values',
compute='_compute_quick_look_recorded_value_ids',
)
@api.depends('recipe_node_id', 'recipe_node_id.input_ids')
def _compute_quick_look_prompt_ids(self):
for step in self:
node = step.recipe_node_id
if node:
step.quick_look_prompt_ids = node.input_ids.filtered(
lambda i: (i.kind or 'step_input') == 'step_input'
).sorted('sequence')
else:
step.quick_look_prompt_ids = False
def _compute_quick_look_recorded_value_ids(self):
Value = self.env['fp.job.step.move.input.value']
for step in self:
if not step.id:
step.quick_look_recorded_value_ids = False
continue
step.quick_look_recorded_value_ids = Value.search([
('move_id.from_step_id', '=', step.id),
], order='create_date desc')
def action_open_full_form(self):
"""From the quick-look modal, escape to the full editable form."""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'fp.job.step',
'res_id': self.id,
'view_mode': 'form',
'target': 'current',
'name': self.name,
}