feat(jobs): Sub 14 polish — workflow state form layout + Simple Editor field

Two follow-ups on the workflow state work:

1) Form layout
   The "How triggers combine" help text was crammed into a 2-column
   group, taking ~25% of the available width. Pulled it out of the
   group and rendered as a full-width <div class="alert alert-info">
   below the trigger fields. Same fix applied to Notes — uses a
   <separator> + bare <field> for full sheet width.

2) Simple Recipe Editor support
   The trigger field was only exposed in the Tree Editor. Added it
   to the Simple Editor's inline library form too:

   * fp.step.template.triggers_workflow_state_id (new Many2one) —
     per-template default, snapshot-copied to recipe nodes when
     dropped into a recipe (added to _SNAPSHOT_FIELDS).
   * /fp/simple_recipe/workflow_states/list — new endpoint to feed
     the dropdown. Soft-fails when fusion_plating_jobs isn't
     installed (returns []).
   * Library editor JS — _fpEnsureWorkflowStatesLoaded helper
     caches the catalog on first open (create + edit paths both
     warm it). Save vals carry the trigger id.
   * Library editor XML — dropdown rendered after the flag
     checkboxes. Hidden when the catalog is empty so the form
     doesn't show a useless "— None —" pick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-04 00:04:59 -04:00
parent 28bf6b5071
commit e54ffe7309
7 changed files with 157 additions and 19 deletions

View File

@@ -50,6 +50,10 @@ export class FpSimpleRecipeEditor extends Component {
libraryEditor: null,
libraryEditorBusy: false,
tankSearchResults: [],
// Sub 14 — workflow-state catalog cache for the inline
// library form's "Triggers Workflow State" dropdown. Lazy-
// loaded the first time the user opens the library editor.
workflowStates: [],
});
this._recipeId = null;
@@ -231,7 +235,8 @@ export class FpSimpleRecipeEditor extends Component {
* mirrors the shape returned by `/fp/simple_recipe/library/load` so
* the same template renders both create + edit.
*/
onOpenLibraryCreate() {
async onOpenLibraryCreate() {
await this._fpEnsureWorkflowStatesLoaded();
this.state.libraryEditor = {
id: null, // null = create
name: "",
@@ -242,6 +247,8 @@ export class FpSimpleRecipeEditor extends Component {
requires_signoff: false,
requires_predecessor_done: false,
parallel_start: false, // Sub 13 — per-step opt-out
triggers_workflow_state_id: false, // Sub 14 — workflow trigger
triggers_workflow_state_name: "",
requires_rack_assignment: false,
requires_transition_form: false,
tank_ids: [],
@@ -250,8 +257,28 @@ export class FpSimpleRecipeEditor extends Component {
this.state.tankSearchResults = [];
}
/**
* Sub 14 — fetch the workflow-state catalog once per editor session,
* cache on this.state.workflowStates. Used by both create + edit
* flows to populate the "Triggers Workflow State" dropdown.
*/
async _fpEnsureWorkflowStatesLoaded() {
if (this.state.workflowStates && this.state.workflowStates.length) {
return;
}
try {
const data = await rpc(
"/fp/simple_recipe/workflow_states/list", {}
);
this.state.workflowStates = data.workflow_states || [];
} catch (err) {
this.state.workflowStates = [];
}
}
async onOpenLibraryEdit(templateId) {
this.state.libraryEditorBusy = true;
await this._fpEnsureWorkflowStatesLoaded();
const data = await rpc("/fp/simple_recipe/library/load", {
template_id: templateId,
});
@@ -291,6 +318,8 @@ export class FpSimpleRecipeEditor extends Component {
requires_signoff: !!ed.requires_signoff,
requires_predecessor_done: !!ed.requires_predecessor_done,
parallel_start: !!ed.parallel_start,
// Sub 14 — workflow trigger (Many2one int or false)
triggers_workflow_state_id: ed.triggers_workflow_state_id || false,
requires_rack_assignment: !!ed.requires_rack_assignment,
requires_transition_form: !!ed.requires_transition_form,
tank_ids: (ed.tank_ids || []).map((t) => t.id),

View File

@@ -435,6 +435,33 @@
</label>
</div>
<!-- Sub 14 — workflow milestone trigger dropdown.
Hidden when no states exist (e.g. catalog
not seeded yet). -->
<div class="o_fp_le_field"
t-if="state.workflowStates and state.workflowStates.length">
<label class="form-label">Triggers Workflow State</label>
<select class="form-select"
t-on-change="(ev) => { state.libraryEditor.triggers_workflow_state_id = ev.target.value ? parseInt(ev.target.value, 10) : false; }">
<option value=""
t-att-selected="!state.libraryEditor.triggers_workflow_state_id">
— None (use default-kind matching) —
</option>
<t t-foreach="state.workflowStates" t-as="ws" t-key="ws.id">
<option t-att-value="ws.id"
t-att-selected="state.libraryEditor.triggers_workflow_state_id === ws.id"
t-esc="ws.name"/>
</t>
</select>
<small class="text-muted d-block mt-1">
When a recipe step generated from this template
finishes (or is skipped/cancelled), the parent
job advances to the chosen state on its status
bar. Leave blank to fall back to default-kind
matching configured on the workflow state catalog.
</small>
</div>
<!-- ============== PROMPTS ============== -->
<div class="o_fp_le_prompts">
<div class="o_fp_le_prompts_header">