124 lines
6.0 KiB
Markdown
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.
|