Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-05-18-receiving-gate-on-step-transitions-design.md
gsinghpal 091f98e1f9 changes
2026-05-18 22:33:23 -04:00

124 lines
6.0 KiB
Markdown

# Receiving Gate on Step Start / Finish
**Date:** 2026-05-18
**Status:** Approved for implementation
**Author:** Brainstorming session (gsinghpal)
**Triggering observation:** WO-30040 closed with `qty_received` blank and chatter warnings on Post-plate Inspection / Final Inspection ("Step started before parts were received"). The existing soft chatter warning is not strong enough — operators ignore it and the job still completes.
## Goal
Block step transitions (start AND finish) on any non-Contract-Review step until the SO's receiving record is closed. Future-proof for custom steps added later. Allow manager bypass via the existing `fp_skip_*` context-flag pattern.
## Decisions reached
| # | Decision | Rationale |
|---|---|---|
| D1 | Scope: all step kinds EXCEPT Contract Review | CR is paperwork — doesn't need parts on the floor. Every other step (including future custom steps) involves physical work. |
| D2 | Timing: both `button_start` AND `button_finish` | Strongest. Operator can't begin OR complete physical work without receiving closed. Catches both "started too early" and "started before parts arrived, completed before they did". |
| D3 | Threshold: `sale_order.x_fc_receiving_status == 'received'` | Post-Sub-8 (and the 2026-05-18 cleanup), `received` is the terminal receiving state. `not_received` and `partial` block. |
| D4 | Manager bypass: `fp_skip_receiving_gate=True` context flag | Matches existing `fp_skip_*` pattern (qty_reconcile, qc_gate, step_gate, bake_gate). Auditor trail via chatter on the state transition. |
| D5 | Implementation: single helper called from both buttons | Mirrors existing `_fp_check_contract_review_complete` pattern. DRY — same code tested once. |
## Out of scope
- Receiving model's state machine (already correct post-Sub-8).
- The `_update_so_receiving_status` mapping (already maps `closed → received`).
- Other gates (qty_reconcile, qc_gate, bake_gate) — untouched.
- Schema changes — pure behavior change.
## Architecture
```
fp.job.step.button_start fp.job.step.button_finish
1. Sequential-order gate (existing) 1. _fp_check_contract_review_complete (existing)
2. _fp_check_receiving_gate() ← NEW 2. _fp_check_receiving_gate() ← NEW
3. Contract Review auto-open (existing) 3. super().button_finish() + downstream (existing)
4. Racking auto-open (existing)
5. Standard path + serial promote (existing)
[old soft chatter warning removed]
```
## Helper method
```python
def _fp_check_receiving_gate(self):
"""Block step transitions until parts are physically received.
Applied to every step EXCEPT Contract Review. Fires from both
button_start and button_finish. Manager bypass via context flag
`fp_skip_receiving_gate=True`.
"""
if self.env.context.get('fp_skip_receiving_gate'):
return
for step in self:
if step._fp_is_contract_review_step():
continue
so = step.job_id.sale_order_id
if not so:
continue # internal rework — gate doesn't apply
if 'x_fc_receiving_status' not in so._fields:
continue # defensive: configurator not installed
if so.x_fc_receiving_status != 'received':
label = dict(
so._fields['x_fc_receiving_status'].selection
).get(so.x_fc_receiving_status, so.x_fc_receiving_status or 'unknown')
raise UserError(_(
'Step "%(step)s" cannot proceed — parts not received yet '
'(SO %(so)s receiving status: %(status)s).\n\n'
'Close the receiving record (Sales > %(so)s > Receiving) '
'before starting or finishing work on this step. A '
'manager can bypass this gate for documented exceptions.'
) % {
'step': step.name,
'so': so.name or '?',
'status': label,
})
```
## Module changes
| Module | Bump | Files |
|---|---|---|
| `fusion_plating_jobs` | 19.0.10.12.0 → 19.0.10.13.0 | `models/fp_job_step.py` (helper + 2 callers + remove soft warning); `tests/test_fp_job_milestone_cascade.py` (new TestReceivingGate class) |
## Edge cases
| Case | Behavior |
|---|---|
| Step on job with no SO link (internal rework) | Gate doesn't fire — `continue`. |
| Configurator module not installed (`x_fc_receiving_status` field absent) | Gate doesn't fire — `continue`. |
| Contract Review step on `not_received` SO | Gate exempt; step proceeds (paperwork). |
| Step on `partial` SO | Blocks — `partial` is not `received`. Operator waits for all boxes to land. |
| Manager bypass via context | All gates skipped uniformly. Audit trail preserved via state-transition tracking. |
## Test plan
8 unit tests in new `TestReceivingGate` class in `test_fp_job_milestone_cascade.py`:
- `test_start_blocks_when_not_received`
- `test_start_allows_when_received`
- `test_start_skips_contract_review`
- `test_start_bypass_via_context`
- `test_finish_blocks_when_not_received`
- `test_finish_allows_when_received`
- `test_finish_skips_contract_review`
- `test_finish_bypass_via_context`
**Manual verification on entech post-deploy:**
1. Open SO-30041 (currently `not_received`) → fp.job → try `button_start` on first non-CR step → UserError raised.
2. Close the receiving record (counted → staged → closed) → SO flips to `received`.
3. Re-try `button_start` → succeeds.
4. Repeat the start/finish flow with `fp_skip_receiving_gate=True` from a shell to verify bypass.
## Backwards compatibility
- The old soft chatter warning at fp_job_step.py:894-907 is removed. The information is no longer useful — it was a soft warning for a behavior we're now hard-blocking. The job's chatter still tracks the state transition via Odoo's tracking.
- Jobs already in `in_progress` on `not_received` SOs at deploy time: any future button_finish will block. Manager must either close receiving OR use bypass.
- No DB migration needed.
## Deployment
- Single-module deploy to entech LXC 111 (`fusion_plating_jobs`).
- No restart of dependent modules required.
- Verify with manual flow above.