feat(step-kinds): curate to 11 + mandatory + admin-only creation

Operator-reported foot-gun: Step Kind dropdown had 24 options, most
of which were visual-only (cleaning, electroclean, etch, rinse,
strike, dry, wbf_test, hardness_test, adhesion_test, salt_spray,
packaging, etc.) and didn't drive any gate or milestone. Picking the
wrong one meant nothing happened; picking Generic (left default)
meant nothing happened. Authors couldn't tell which choice mattered.

Curation: 24 → 11 active kinds. Each remaining kind has a concrete
downstream behaviour (gate, portal milestone, hardware tie-in, or
"explicitly no behaviour" for Other):

  other            Other (catch-all, default — no special behaviour)
  receiving        Received portal milestone
  contract_review  QA-005 form gate + button_finish lock
  racking          Rack-assignment dialog + button_finish lock
  mask             Visual mask kind (covers Masking + De-Masking)
  wet_process      Visual wet kind (NEW, covers cleaning, rinse,
                   etch, strike, dry, electroclean, wbf_test)
  plate            Plated portal milestone (last plate step closes)
  bake             Bake-window state machine + Baked milestone
  inspect          Intermediate inspection milestone
  final_inspect    Inspected (terminal) portal milestone
  ship             Shipped milestone (back-compat; delivery-state
                   driven is preferred)

Retired kinds (active=False, hidden from dropdown): cleaning,
electroclean, etch, rinse, strike, dry, wbf_test, demask, derack,
replenishment, hardness_test, adhesion_test, salt_spray, packaging,
gating. Kept in DB for audit / history but not selectable.

Mandatory enforcement:
- fp.step.kind_id on fusion.plating.process.node and fp.step.template
  is now required=True with ondelete='restrict' and a default that
  resolves to the 'other' kind. Existing NULL rows are backfilled by
  the pre-migrate before the NOT NULL constraint hits the schema.
- Dropdown no longer offers a blank / "Generic" option. New steps
  land on 'other' instead of NULL.

Admin-only catalog:
- /fp/simple_recipe/kinds/create endpoint now refuses requests from
  non-managers (group_fusion_plating_manager). Returns a clear
  message explaining why ("each kind drives gates / milestones /
  routing — pick Other if none fits, or ask a manager to wire up a
  new kind").
- "+ Add a new kind…" sentinel option in the library form is hidden
  unless state.recipe.user_is_manager. Backend gate is the authority;
  the UI hide is just to stop showing a button that will error.
- The Step Type dropdown in the inline step-edit panel switched from
  a 24-line hard-coded XML option list to a t-foreach over
  state.kindOptions (the same kinds/list endpoint payload). One
  source of truth — retire / add a kind in the catalog and every
  picker reflects the change.

Migration impact (entech): 5 templates + 579 nodes backfilled via
name-match heuristic. 15 kinds flipped to active=False. Distribution
of the 579 backfilled nodes:
  racking 105, other 97, bake 91, wet_process 90, mask 74,
  inspect 44, plate 32, final_inspect 25, receiving 10,
  contract_review 9, ship 2.

Drive-by:
- Migration uses _ensure_kind() that also registers ir.model.data
  for the new xmlids so the subsequent data XML load doesn't create
  duplicate kind records.
- Stored related default_kind on fusion.plating.process.node /
  fp.step.template is written alongside kind_id in every SQL UPDATE
  so legacy `node.default_kind == 'foo'` comparisons stay accurate
  (the ORM doesn't recompute stored related fields after direct
  SQL writes).

Module: fusion_plating 19.0.20.5.0 → 19.0.20.6.0.
15 existing tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-20 08:08:31 -04:00
parent 7c31269691
commit ac1db177e1
9 changed files with 439 additions and 40 deletions

View File

@@ -493,9 +493,23 @@ class FpProcessNode(models.Model):
help='Sub 12b — opens the transition form before Mark Done.',
)
# Sub 14b — User-extensible Step Kinds (was Selection of 24).
# 2026-05-20: required + ondelete='restrict' — kind drives gates,
# workflow milestones, and operator routing. Optional was a foot-gun
# (operators silently picked Generic / nothing). Pre-migrate
# 19.0.20.6.0 backfills every existing row before this NOT NULL
# constraint hits the schema.
kind_id = fields.Many2one(
'fp.step.kind', string='Step Kind', ondelete='set null', index=True,
help='Pick from the catalog or create a new kind.',
'fp.step.kind', string='Step Kind',
ondelete='restrict', index=True,
required=True,
default=lambda self: self.env['fp.step.kind'].search(
[('code', '=', 'other')], limit=1,
).id or False,
help='Drives operator routing (auto-open Contract Review form / '
'Rack assignment dialog / Bake window), customer-portal '
'milestones (Received / Plated / Inspected / Shipped), and '
'tablet UI (icon, station filter). Pick "Other" only when '
'the step has no special behaviour.',
)
# Back-compat: code-string accessor that all legacy
# `node.default_kind == "cleaning"` comparisons keep using.

View File

@@ -89,11 +89,18 @@ class FpStepTemplate(models.Model):
help='Opens the transition form before Mark Done (Sub 12b).')
# Sub 14b — User-extensible Step Kinds (was Selection of 24).
# 2026-05-20: required — same rationale as on fusion.plating.process.node
# (kind drives every downstream gate / milestone / routing decision).
kind_id = fields.Many2one(
'fp.step.kind', string='Step Kind', ondelete='restrict',
index=True, tracking=True,
help='Pick from the catalog or create a new kind. Drives sane-'
'default input seeding.',
required=True,
default=lambda self: self.env['fp.step.kind'].search(
[('code', '=', 'other')], limit=1,
).id or False,
help='Drives sane-default input seeding plus downstream gates / '
'milestones / routing when authors instantiate the template. '
'Pick "Other" only when the step has no special behaviour.',
)
# Back-compat shim — every legacy `tpl.default_kind == "cleaning"`
# call site keeps working without a refactor. Stored=True so existing