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:
@@ -383,7 +383,10 @@ export class FpSimpleRecipeEditor extends Component {
|
||||
name: name.trim(),
|
||||
});
|
||||
if (!data.ok) {
|
||||
alert(data.error || "Could not create Step Kind.");
|
||||
// 2026-05-20 — backend forbids non-managers from
|
||||
// creating kinds. Surface the explanatory message
|
||||
// instead of a generic error code.
|
||||
alert(data.message || data.error || "Could not create Step Kind.");
|
||||
return;
|
||||
}
|
||||
// Drop the cached list so the next ensure() refetches it.
|
||||
@@ -697,11 +700,18 @@ export class FpSimpleRecipeEditor extends Component {
|
||||
// Sub 14 — make sure the workflow-state catalog is cached so
|
||||
// the dropdown in the inline form has options to render.
|
||||
await this._fpEnsureWorkflowStatesLoaded();
|
||||
// 2026-05-20 — Step Type dropdown is now driven by the
|
||||
// fp.step.kind catalog (curated to 12 active kinds). Cache the
|
||||
// list before opening the panel so the select renders with
|
||||
// options instead of being empty.
|
||||
await this._fpEnsureKindOptionsLoaded();
|
||||
this.state.editingStepId = stepId;
|
||||
this.state.editName = step.name || "";
|
||||
this.state.editInstructions = this._htmlToText(step.description || "");
|
||||
// Settings the user can now change WITHOUT delete + re-add.
|
||||
this.state.editDefaultKind = step.default_kind || "";
|
||||
// Default to 'other' when no kind is set — kind_id is required
|
||||
// on the model so we never want a blank value to round-trip.
|
||||
this.state.editDefaultKind = step.default_kind || "other";
|
||||
this.state.editTriggersWorkflowStateId =
|
||||
step.triggers_workflow_state_id || false;
|
||||
this.state.editParallelStart = !!step.parallel_start;
|
||||
|
||||
@@ -157,34 +157,40 @@
|
||||
below it lets them override per-step. -->
|
||||
<div class="o_fp_edit_row" style="display: flex; gap: 16px; flex-wrap: wrap;">
|
||||
<div class="o_fp_edit_field" style="flex: 1; min-width: 240px;">
|
||||
<label>Step Type (Default Kind)</label>
|
||||
<label>Step Type (Default Kind) *</label>
|
||||
<!-- 2026-05-20: hard-coded option list
|
||||
retired. The dropdown now drives
|
||||
off `state.kindOptions` (fp.step.kind
|
||||
records with active=True), which is
|
||||
the curated catalog (Other,
|
||||
Receiving, Contract Review, Racking,
|
||||
Masking, Wet Process, Plating, Bake,
|
||||
Inspection, Final Inspection,
|
||||
Shipping). New kinds need a manager
|
||||
+ code work to wire downstream gates;
|
||||
see kinds_create lockdown. -->
|
||||
<select class="form-select"
|
||||
t-on-change="(ev) => { state.editDefaultKind = ev.target.value; }">
|
||||
<option value="" t-att-selected="!state.editDefaultKind">— Generic —</option>
|
||||
<option value="receiving" t-att-selected="state.editDefaultKind === 'receiving'">Receiving / Incoming Inspection</option>
|
||||
<option value="contract_review" t-att-selected="state.editDefaultKind === 'contract_review'">Contract Review (QA-005)</option>
|
||||
<option value="racking" t-att-selected="state.editDefaultKind === 'racking'">Racking</option>
|
||||
<option value="mask" t-att-selected="state.editDefaultKind === 'mask'">Masking</option>
|
||||
<option value="cleaning" t-att-selected="state.editDefaultKind === 'cleaning'">Cleaning</option>
|
||||
<option value="electroclean" t-att-selected="state.editDefaultKind === 'electroclean'">Electroclean</option>
|
||||
<option value="etch" t-att-selected="state.editDefaultKind === 'etch'">Etch / Activation</option>
|
||||
<option value="rinse" t-att-selected="state.editDefaultKind === 'rinse'">Rinse</option>
|
||||
<option value="strike" t-att-selected="state.editDefaultKind === 'strike'">Strike</option>
|
||||
<option value="plate" t-att-selected="state.editDefaultKind === 'plate'">Plating</option>
|
||||
<option value="replenishment" t-att-selected="state.editDefaultKind === 'replenishment'">Tank Replenishment</option>
|
||||
<option value="wbf_test" t-att-selected="state.editDefaultKind === 'wbf_test'">Water Break Free Test</option>
|
||||
<option value="dry" t-att-selected="state.editDefaultKind === 'dry'">Drying</option>
|
||||
<option value="bake" t-att-selected="state.editDefaultKind === 'bake'">Bake</option>
|
||||
<option value="demask" t-att-selected="state.editDefaultKind === 'demask'">De-Masking</option>
|
||||
<option value="derack" t-att-selected="state.editDefaultKind === 'derack'">De-Racking</option>
|
||||
<option value="inspect" t-att-selected="state.editDefaultKind === 'inspect'">Inspection</option>
|
||||
<option value="final_inspect" t-att-selected="state.editDefaultKind === 'final_inspect'">Final Inspection</option>
|
||||
<option value="ship" t-att-selected="state.editDefaultKind === 'ship'">Shipping</option>
|
||||
<t t-foreach="state.kindOptions || []" t-as="k" t-key="k.id">
|
||||
<option t-att-value="k.code"
|
||||
t-att-selected="k.code === state.editDefaultKind">
|
||||
<t t-esc="k.name"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<p class="o_fp_edit_hint">
|
||||
Drives workflow milestone triggers (e.g. <code>final_inspect</code> fires
|
||||
the Inspected status) and routing (e.g. <code>contract_review</code> opens
|
||||
QA-005 instead of the input wizard).
|
||||
Required. Drives operator routing
|
||||
(<code>contract_review</code> opens
|
||||
QA-005, <code>racking</code> opens
|
||||
rack picker, <code>bake</code> ties
|
||||
to bake-window state machine),
|
||||
customer-portal milestones
|
||||
(<code>receiving</code> / <code>plate</code>
|
||||
/ <code>final_inspect</code> /
|
||||
<code>ship</code>), and tablet UI
|
||||
(icon, station-type filter). Pick
|
||||
<strong>Other</strong> only when the
|
||||
step has no special behaviour.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -500,13 +506,19 @@
|
||||
<select class="form-select"
|
||||
t-on-change="(ev) => this.onKindChange(ev)"
|
||||
t-att-value="state.libraryEditor.default_kind">
|
||||
<option value="">Generic — no automatic behaviour</option>
|
||||
<t t-foreach="state.kindOptions || []" t-as="k" t-key="k.id">
|
||||
<option t-att-value="k.code" t-att-selected="k.code === state.libraryEditor.default_kind">
|
||||
<t t-esc="k.name"/>
|
||||
</option>
|
||||
</t>
|
||||
<option value="__new__">+ Add a new kind…</option>
|
||||
<!-- Manager-only inline create. The
|
||||
backend kinds_create endpoint
|
||||
also gates on this group, so
|
||||
hiding here is just to avoid
|
||||
showing a button that
|
||||
immediately errors. -->
|
||||
<option value="__new__"
|
||||
t-if="state.recipe and state.recipe.user_is_manager">+ Add a new kind…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="o_fp_le_field">
|
||||
|
||||
Reference in New Issue
Block a user