fix(plating): Manager Desk premature-advance + 6 workflow enforcement gates

**1. Manager Desk: WO no longer jumps to "In Progress" on partial setup**

User-reported bug: when the manager picked a worker, the WO immediately
left the "Unassigned" column even though the bath/tank (or oven, rack,
masking material) wasn't set yet. Worker would see a half-set job in
their queue and couldn't start it.

Fix:
- New compute `mrp.workorder.x_fc_is_release_ready` — True only when
  every field button_start would block on is filled in.
- Companion `x_fc_missing_for_release` — comma-list of what's still
  missing (used by the UI as a hint chip).
- Manager controller swaps the column filter from
  `assigned_user_id == False` to `is_release_ready == False`.
- A WO stays in "Setup Pending" (formerly Unassigned) until BOTH
  worker + per-kind equipment are set; only then does it move to
  "In Progress".

**Manager Desk template + SCSS**

The user also said "the manager doesn't know what task they're
assigning". WO row now shows:
  • Colour-coded WO-kind badge (wet=blue, bake=red, mask=yellow,
    rack=grey, inspect=green)
  • Required-role icon + name
  • Bath / oven / rack / masking-material chips (whatever's set)
  • Yellow "Needs: ..." chip listing what's still missing
  • Tank picker only shows for wet WOs (no point on a mask WO)
  • Open-WO button to drill into the form for advanced edits

**2. Six enforcement gates patched (without breaking the workflow)**

Each gate fires AFTER the manager sets up the WO and the operator
hits Start/Finish — never on create — so the manager → worker → run
flow stays intact.

| # | Gate | Where |
|---|---|---|
| a | SO confirm requires `client_order_ref` (or x_fc_po_number) | sale_order.action_confirm |
| b | Cert issue requires thickness readings (when partner.x_fc_strict_thickness_required) | fp_certificate.action_issue |
| c | Delivery start_route requires assigned_driver_id | fp_delivery.action_start_route |
| d | Bath log create/save requires line_ids (no empty logs) | fp_bath_log create + @api.constrains |
| e | Quality hold: hold_reason + description now `required=True` | fp_quality_hold field schema |
| f | Receiving accept blocks qty mismatch (manager override allowed + logged) | fp_receiving.action_accept |

New partner flag `x_fc_strict_thickness_required` so commercial
customers don't get blocked but aerospace customers do.

**Verified** via `scripts/fp_enforcement_audit.py`: 18/22 ENFORCED
(2 "GAPS" + 2 "ERRs" are all test artifacts — admin bypass + NOT NULL
fires before my custom check; real gates are correct).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 12:54:00 -04:00
parent 050d3d06a7
commit 11837ed4f5
20 changed files with 734 additions and 49 deletions

View File

@@ -507,6 +507,20 @@
&.o_fp_chip_warning { @include fp-pill(--bs-warning); }
&.o_fp_chip_danger { @include fp-pill(--bs-danger); }
&.o_fp_chip_muted { background-color: $fp-card-soft; color: $fp-ink-mute; }
// WO-kind colour bands so the manager can spot
// mask vs wet vs bake at a glance.
&.o_fp_chip_kind {
text-transform: none;
letter-spacing: normal;
font-weight: $fp-weight-bold;
}
&.o_fp_chip_kind_wet { background-color: rgba(13, 110, 253, .15); color: #0d6efd; }
&.o_fp_chip_kind_bake { background-color: rgba(220, 53, 69, .15); color: #dc3545; }
&.o_fp_chip_kind_mask { background-color: rgba(255, 193, 7, .20); color: #997404; }
&.o_fp_chip_kind_rack { background-color: rgba(108, 117, 125, .15); color: #495057; }
&.o_fp_chip_kind_inspect { background-color: rgba(25, 135, 84, .15); color: #198754; }
&.o_fp_chip_kind_other { background-color: $fp-card-soft; color: $fp-ink-mute; }
}

View File

@@ -135,11 +135,23 @@
<t t-foreach="card.wos" t-as="wo" t-key="wo.id">
<div class="o_fp_mgr_wo_row">
<div class="o_fp_mgr_wo_info">
<t t-esc="wo.name"/>
<span class="text-muted ms-2">
<div>
<span t-attf-class="o_fp_chip o_fp_chip_kind o_fp_chip_kind_{{ wo.wo_kind }}"
t-esc="wo.wo_kind_label || wo.wo_kind"/>
<strong class="ms-1" t-esc="wo.name"/>
</div>
<div class="text-muted small mt-1">
<t t-esc="wo.workcenter"/>
<t t-if="wo.bath"> · <t t-esc="wo.bath"/></t>
</span>
<t t-if="wo.role_name"> · <i class="fa fa-id-badge"/> <t t-esc="wo.role_name"/></t>
<t t-if="wo.bath"> · <i class="fa fa-flask"/> <t t-esc="wo.bath"/></t>
<t t-if="wo.oven"> · <i class="fa fa-fire"/> <t t-esc="wo.oven"/></t>
<t t-if="wo.rack"> · <i class="fa fa-th"/> <t t-esc="wo.rack"/></t>
<t t-if="wo.masking_material"> · <i class="fa fa-tag"/> <t t-esc="wo.masking_material"/></t>
</div>
<div t-if="wo.missing_for_release"
class="o_fp_chip o_fp_chip_warning mt-1">
<i class="fa fa-exclamation-circle"/> Needs: <t t-esc="wo.missing_for_release"/>
</div>
</div>
<select class="o_fp_mgr_picker"
t-on-change="(ev) => this.onAssignWorker(wo, ev.target.value)">
@@ -154,7 +166,8 @@
</option>
</t>
</select>
<select class="o_fp_mgr_picker"
<select t-if="wo.wo_kind === 'wet'"
class="o_fp_mgr_picker"
t-on-change="(ev) => this.onAssignTank(wo, ev.target.value)">
<option value="">— Tank —</option>
<t t-foreach="state.overview.tanks" t-as="tnk" t-key="tnk.id">
@@ -170,7 +183,7 @@
</button>
<button class="btn"
t-on-click="() => this.openRecord('mrp.workorder', wo.id)">
Open
Open WO
</button>
</div>
</t>