feat(fusion_plating): Express masking reference images → mask step + workstation viewer

Order-entry shortcut: when masking is toggled ON for an Express order line,
an amber "MASK" button appears to attach reference image(s)/PDF(s). The files
ride the existing _fp_apply_express_overrides_to_job path onto the job's
masking step, so the operator sees exactly what to mask — no recipe edit or
custom prompt needed.

- configurator: masking_attachment_ids on the wizard line + SO line;
  action_upload_masking_ref; override branch writes refs onto mask steps;
  amber multi-file MASK button (express_action_btns) shown when masking is on.
- jobs: x_fc_masking_attachment_ids on fp.job.step (per-step) + computed
  rollup on fp.job; office "Masking Refs" form page (readonly preview).
- shopfloor: workspace step payload carries masking_refs (sudo'd attachment
  read, rule 13m); operator sees thumbnail/PDF tiles on the mask step that
  open in Odoo's full-screen FileViewer (zoom + swipe).

Verified end-to-end on entech: SO-line refs land on the mask step + job
rollup (WO-30091); payload mask_refs shape correct (is_image, /web/image).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-03 15:12:18 -04:00
parent b52b8758a1
commit 235c8fba39
16 changed files with 331 additions and 2 deletions

View File

@@ -350,6 +350,14 @@ class SaleOrderLine(models.Model):
'steps run, with this text shown on the operator tablet under '
'fp.job.step.instructions.',
)
x_fc_masking_attachment_ids = fields.Many2many(
'ir.attachment',
'sale_order_line_masking_att_rel', 'line_id', 'attachment_id',
string='Masking Reference(s)',
help='Masking reference image(s)/PDF(s) captured at Express order '
'entry; applied to the job\'s masking step at job creation so '
'the operator sees what to mask.',
)
x_fc_revision_snapshot = fields.Char(
string='Revision (snapshot)',
copy=False,
@@ -840,6 +848,19 @@ class SaleOrderLine(models.Model):
})
if nodes:
msgs.append(_('Masking + de-masking steps opted out (per SO line)'))
elif self.x_fc_masking_attachment_ids:
# Masking ON + Express reference file(s) attached → surface them on
# the mask step so the operator sees what to mask. Lands on the
# second call (after steps exist), same as bake below.
mask_steps = job.step_ids.filtered(
lambda s: s.recipe_node_id.default_kind == 'mask'
)
if mask_steps:
mask_steps.sudo().write({
'x_fc_masking_attachment_ids': [(6, 0, self.x_fc_masking_attachment_ids.ids)],
})
msgs.append(_('Masking reference(s) attached to the mask step: %d file(s)')
% len(self.x_fc_masking_attachment_ids))
# 2. Bake — empty = opt out; non-empty = keep + write step.instructions
bake_text = (self.x_fc_bake_instructions or '').strip()