6.0 KiB
6.0 KiB
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_statusmapping (already mapsclosed → 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
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_receivedtest_start_allows_when_receivedtest_start_skips_contract_reviewtest_start_bypass_via_contexttest_finish_blocks_when_not_receivedtest_finish_allows_when_receivedtest_finish_skips_contract_reviewtest_finish_bypass_via_context
Manual verification on entech post-deploy:
- Open SO-30041 (currently
not_received) → fp.job → trybutton_starton first non-CR step → UserError raised. - Close the receiving record (counted → staged → closed) → SO flips to
received. - Re-try
button_start→ succeeds. - Repeat the start/finish flow with
fp_skip_receiving_gate=Truefrom 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_progressonnot_receivedSOs 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.