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

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_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

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.