Skip to main content

SOP: Receiving Exceptions

Document ID: WMS-REC-002 Version: 1.0 Effective date: 04/30/2026 Owner: Warehouse Operations Manager Next review: [six months from effective date] Applies to: Counters filing receiving exceptions during an IN_PROGRESS session, and managers reviewing them at approval time


1. Purpose

This procedure governs how to record physical-vs-PO discrepancies during a receiving session — damaged units, wrong items shipped, missing items, and overages. Exceptions are the paper trail that explains a variance: when the quantityCounted doesn't match the quantityExpected, an exception is the why. Without an exception, a manager looking at the approval page (per WMS-REC-003 §4.1) has no context for the variance and the correct action is to reject the session.

Exception data flows downstream into vendor-credit accounting, the receiving variance investigation report (WMS-AUD-003), and the manager's approval decision. They are the most important paper trail in receiving.

⚠ CURRENT STATE: UI gap. As of WMS app v[current], the API and database fully support receiving exceptions (the POST /receiving/:sessionId/exception endpoint and the receiving_exceptions table). However, no floor UI surfaces this feature today — the receiving session page does not have an exception button despite mentioning one in the start-page instructions. This SOP documents the system as designed and the current-state workaround in §4.5. Until the floor UI ships, exception recording requires either (a) the workaround in §4.5 or (b) direct API call by a manager. Floor staff should file rejection-grade variances on paper and hand them to a manager at submit time.

2. Scope

In scope:

  • Filing exceptions of type DAMAGED, WRONG_ITEM, MISSING, OVERAGE against a receiving session line
  • The interaction between exceptions and the line's quantityCounted and quantityDamaged fields
  • Photo evidence (imageUrl)
  • The current-state workaround until the floor UI exists

Out of scope:

  • Counting and the lock mechanism — see WMS-REC-001
  • Manager approval / rejection / reopen flow — see WMS-REC-003
  • Damage discovered to existing inventory (after approval, on the shelf) — see WMS-INV-001 §4.3
  • Damage discovered during shipping or at pack-out — see WMS-SHIP-004
  • Customer return condition coding — see WMS-RET-002

3. Roles & permissions

RoleFile exceptionView exceptions on session
READONLY
STAFF✓ (must hold the session lock)
MANAGER
ADMIN
SUPER_ADMIN

API enforcement:

  • The POST /receiving/:sessionId/exception endpoint checks authentication only.
  • The service-layer recordException calls checkLock(line.session, userId) first — exceptions can only be filed by the user holding the session lock. If the lock is held by another user, the service throws "Session is locked by another user" (HTTP 400). See WMS-REC-001 §4.4.

4. Procedures

4.1 What an exception is, and what it does to the count

Each exception is a row in receiving_exceptions with:

FieldWhat it means
sessionIdThe receiving session this exception belongs to
lineIdThe specific ReceivingLine (SKU within the PO) the exception applies to
typeOne of DAMAGED / WRONG_ITEM / MISSING / OVERAGE (see §5.1)
quantityHow many units are affected
notesFree-text description (e.g., "Crushed corner on case 3 of 4") — required in practice, optional in schema
imageUrlURL to a photo stored in GCS (recommended for DAMAGED and WRONG_ITEM)
reportedByAuto-set to the user filing the exception
resolvedBy / resolvedAtSet later, if at all — currently unused by the approve flow

Important: only DAMAGED exceptions modify the count.

When you file an exception with type: "DAMAGED":

  • The line's quantityDamaged field is incremented by the exception quantity (atomically, in the same transaction).
  • The line's quantityCounted is not changed — counters should still scan or count the damaged units into quantityCounted. The quantityDamaged records that some of the counted units arrived in unsellable condition.
  • At approval time, the approve transaction calculates goodQuantity = quantityCounted − quantityDamaged and only stocks goodQuantity. Damaged units are recorded in the audit trail but do not enter inventory. (See WMS-REC-003 §4.2 for the full approve-time logic.)

For the other three types — WRONG_ITEM, MISSING, OVERAGE — the exception is purely a record. The counter still adjusts quantityCounted to reflect physical reality:

  • MISSING → leave quantityCounted lower than quantityExpected. The exception explains why.
  • OVERAGE → set quantityCounted higher than quantityExpected. The exception explains why.
  • WRONG_ITEM → leave the affected line at quantityCounted: 0. The wrong item is segregated physically and dealt with separately (return-to-vendor, swap, etc.) — it does not become inventory under any line of this PO.

4.2 When to file each type

TypeUse whenPhoto?
DAMAGEDPhysical damage on arrival — crushed cases, leaking liquid, torn pouches, contamination, water-damaged cartons. Apply per-unit, not per-case (10 damaged units in a 24-unit case = quantity 10, not 1).Required. Photograph every flat of damaged stock with the PO reference visible.
WRONG_ITEMVendor sent the wrong SKU. Either (a) a SKU not on this PO at all, or (b) a SKU on the PO but in the wrong variant (size, flavor, color). The scan flow during count returns NOT_ON_PO for case (a) — that's the trigger to file this exception.Strongly recommended. Photograph the unit barcode and the case label together.
MISSINGA SKU the PO expected was not in the shipment at all, or arrived short of the expected quantity. Distinguish from a counting error — recount before filing this.Optional. Photograph the empty case slot or pallet area if it tells the story.
OVERAGEThe vendor shipped more than the PO expected. Occasionally legitimate (vendor gift), occasionally a paperwork mismatch. Always file the exception so the manager has context for the variance.Optional.

Two types in the database do not flow through the API: QUALITY_ISSUE and OTHER. The schema enum (ReceivingExceptionType) lists them, but neither the route's TypeScript nor the service-layer ExceptionInput type accepts them. They are currently unreachable. If you encounter a quality issue (manufacturer defect, expired-on-arrival lot-tracked product, suspected counterfeit), file it as DAMAGED with explicit notes — "QUALITY ISSUE: lot 4471 expired May 2025, arrived July 2026" — and tag the manager directly.

4.3 Filing an exception via the (target) UI flow

This is the procedure as the system is designed. The UI button does not exist today; see §4.5 for the current workaround.

Use when: You're counting a session, you encounter a discrepancy that warrants an exception, and the floor UI for exceptions has shipped.

Prerequisites:

  • Session is IN_PROGRESS.
  • You hold the session lock (see WMS-REC-001 §4.4).
  • For DAMAGED and WRONG_ITEM: a photo has been captured.

Steps (target):

  1. From the session page, tap the line that has the exception.
  2. In the line detail panel, tap Report Exception.
  3. Select the type from DAMAGED / WRONG_ITEM / MISSING / OVERAGE.
  4. Enter the quantity affected.
  5. Enter notes describing the issue (e.g., "Crushed: 8 of 24 in case 3, foam liner failed").
  6. Attach the photo (upload to GCS, returns the imageUrl).
  7. Submit.

Result (target):

  • A receiving_exceptions row is created with reportedBy: <you> and the timestamp.
  • For DAMAGED only, the line's quantityDamaged is incremented in the same transaction.
  • An audit_logs row is written with action: EXCEPTION_RECORDED and the full input payload.

Common errors (target):

HTTPAPI messageWhat it means
400"Line not found"The line ID is stale or wrong. Refresh the session.
400"Session is locked by another user"You're not the lock holder. See WMS-REC-001 §4.4.
401"Unauthorized"Session expired. Log in again.

4.4 Reviewing exceptions at approval time

Exceptions filed against a session are visible to the approving manager at /receiving/approve/:sessionId:

  • The Damaged Items Reported banner (red) appears if any line has quantityDamaged > 0. It displays {n} items with damage ({totalDamaged} units).
  • The Variances Detected banner (yellow) appears if any line has quantityCounted ≠ quantityExpected. It displays {n} items short and/or {n} items over.
  • The Items with Variance section lists the affected lines with their expected, counted, and damaged numbers.

The current approve UI does not show the per-exception detail (notes, photo, type breakdown). Managers see the aggregate damage and variance, not the per-exception explanation. This is a real limitation — see §6.

When reviewing per WMS-REC-003 §4.1, a manager evaluating a variance line should ask: "Is there an exception filed that explains this variance, and is the explanation acceptable?" Today, answering that requires querying receiving_exceptions directly (or asking the counter verbally).

4.5 Current-state floor workaround

Use when: You're a counter, you encounter a damaged / wrong / missing / overage situation, and §4.3 doesn't work because the UI button doesn't exist yet.

The mechanism the floor has today is quantityCounted itself plus a paper trail. Each scenario:

  1. Damaged units arrive

    • Count the damaged units into quantityCounted anyway (scan them or +1 them) so the totals match what physically arrived.
    • Photograph the damaged stock with the PO reference visible.
    • On a paper exception slip (template per §5.3), record: PO ref, SKU, quantity damaged, brief description, your name, time. Attach the photo or note the photo's filename.
    • At submit time, hand the slip(s) to the assigned approver in person or post them to #warehouse-approvals along with the session link before tapping Submit.
    • The approver then reviews the slip alongside the variance banner per WMS-REC-003 §4.1 and makes the approve/reject call. If the explanation is sufficient, they approve, and the damaged units enter inventory anyway (as AVAILABLE, not DAMAGED) — the manager then immediately follows the unit-level damage procedure in WMS-INV-001 §4.3 to mark them DAMAGED post-approval. This is the workaround for the missing approve-time damage handling.
  2. Wrong item delivered

    • Do not scan the wrong item into quantityCounted for any line on this PO. Leave quantityCounted at zero for the line the PO expected. Segregate the wrong item physically in the QUARANTINE zone with a WRONG ITEM — PO {ref} tag.
    • Photograph the wrong item next to the case label.
    • On a paper slip: PO ref, expected SKU, what actually arrived, quantity, your name, time. Hand to manager + buyer.
  3. Missing items

    • Recount twice to rule out counting error.
    • Leave quantityCounted at the actual physical count (lower than quantityExpected).
    • On a paper slip: PO ref, SKU, expected vs. found, your name, time. Hand to manager + buyer.
  4. Overage

    • Count the overage units into quantityCounted so the totals match what physically arrived. The session will show a positive variance.
    • On a paper slip: PO ref, SKU, expected vs. counted, your name, time. Hand to manager.

In all four cases, leave a clear paper trail. The receiving variance investigation report (WMS-AUD-003) reconstructs trends from receiving_exceptions rows; until the UI exists, those rows aren't getting created and the variance investigations are blind to type. The paper slips bridge the gap.

⚠ The workaround is friction, not a fix. Tracking exceptions on paper is an interim measure. Until the UI ships, manager approval decisions are partially "trust the counter's verbal explanation," which weakens the audit trail and makes vendor-credit conversations harder. Prioritize the floor UI — see §8.

5. Reference

5.1 Exception types — full enum vs. usable

TypeIn schemaAccepted by APIUsed by UI today
DAMAGED— (workaround in §4.5)
WRONG_ITEM
MISSING
OVERAGE
QUALITY_ISSUE
OTHER

QUALITY_ISSUE and OTHER exist in the database enum but are unreachable through the application code. Treat them as reserved for future use. Anything that would semantically be a QUALITY_ISSUE today goes through DAMAGED with explicit notes per §4.2.

5.2 Field mechanics

  • quantityCounted reflects what was physically observed in the shipment, regardless of condition. A damaged unit still scans; you count it.
  • quantityDamaged is the subset of quantityCounted that arrived in unsellable condition. Only DAMAGED exceptions increment this; only the difference (quantityCounted − quantityDamaged) becomes inventory at approval.
  • quantityExpected is fixed at session-start time from the PO data and never changes.
  • variance = quantityCounted − quantityExpected. A negative variance is a shortage; a positive one is an overage. Damage is not part of variance — a 100-counted, 20-damaged line against a PO of 100 has variance 0 (counted matches expected) but goodQuantity of 80.

5.3 Paper exception slip template (current-state workaround)

Until the floor UI ships, use this format:

RECEIVING EXCEPTION — PAPER SLIP
PO REFERENCE: _______________________
SKU: _______________________
EXCEPTION TYPE: ☐ Damaged ☐ Wrong ☐ Missing ☐ Overage
QUANTITY: ______
NOTES: _______________________________________
_______________________________________
PHOTO FILENAME: _______________________
COUNTER: _______________________
TIME: _______________________

Slips go to the assigned approver before they tap Approve on the session.

  • WMS-REC-001 — Counting (the session in which exceptions are filed)
  • WMS-REC-003 — Approval (where exceptions inform the approve/reject decision)
  • WMS-INV-001 §4.3 — Marking inventory damaged (the post-approval damage step in the §4.5 workaround)
  • WMS-INV-005 — Expired & recalled product handling (for lot-traced quality issues)
  • WMS-AUD-003 — Receiving variance investigation (consumer of receiving_exceptions data)
  • WMS-AUD-004 — System outage procedures (paper-tally fallback shares a format with §5.3)

6. Audit & compliance

Each recordException call writes an immutable row to:

  • receiving_exceptions — the structured exception record
  • audit_logsaction: EXCEPTION_RECORDED, entityType: ReceivingSession, with the full input payload as JSON

Both are insert-only.

Until the floor UI ships, the audit trail is incomplete. Variances explained verbally and approved on faith do not produce receiving_exceptions rows, which means:

  • The receiving variance investigation report (WMS-AUD-003) is blind to type — it sees the variance but can't categorize it.
  • Vendor credit recovery is harder — there's no system-of-record query that returns "all DAMAGED exceptions filed against vendor X in Q3."
  • Per WMS-000 §6 quarterly governance, the count of unexplained-but-approved variances should trend toward zero. Currently, by definition, all variances are unexplained-by-system (only paper).

Manager weekly review (current-state):

  • Sweep paper slips received in the past 7 days. File them in a binder by vendor.
  • Cross-reference with the receiving_sessions table for sessions approved with quantityCounted ≠ quantityExpected. Any approved session with no slip = unexplained approval. Investigate.

Manager weekly review (target-state, after UI ships):

  • Pull all receiving_exceptions rows for the past 7 days, grouped by type and vendor.
  • Per-type pattern recognition: a SKU appearing repeatedly under DAMAGED for one vendor → packaging conversation with the buyer. A SKU repeatedly under WRONG_ITEM → vendor accuracy concern.

7. Troubleshooting

SymptomCauseResolution
There is no exception button on the session pageThe UI feature is unbuiltUse §4.5 workaround. Slip-and-Slack until the UI ships.
Manager rejected my session for "no exception filed" but I had one written downPaper slip didn't reach themRetry: Slack the slip details with the session link to the assigned approver, then ask them to reopen per WMS-REC-003 §4.4. Update §5.3 slip routing if this happens repeatedly.
quantityDamaged shows zero on the approval page even though I reported damageThe current workaround can't write quantityDamaged — only the API can, and only via recordException with type DAMAGEDManager must approve based on the paper slip + your verbal explanation, then immediately mark the units DAMAGED per WMS-INV-001 §4.3 from the receiving location.
The session start page mentions an "exception button" that I can't findStale instructional text in pages/receiving/start.tsx:341Disregard. The UI feature is unbuilt. File a doc-correction ticket.
"Session is locked by another user" (HTTP 400) on /exception (manager testing the API directly)The session lock is held by the counterCoordinate. Counter must release the lock or the manager waits the 5-minute timeout. See WMS-REC-001 §4.4.
"Line not found" (HTTP 400)Wrong lineIdRefresh the session and re-fetch line IDs.

8. Escalation

  • Vendor sent the wrong SKU in volume (more than half the PO is wrong product): Stop counting. Photograph everything. Hold the dock. Notify the buyer and the warehouse manager immediately.
  • Damaged shipment exceeds 10% of total received units: Flag at submit time so the approver knows to involve the buyer for vendor credit before approving.
  • UI gap is causing audit-trail problems: Engineering priority. The exception-filing UI is the highest-leverage missing feature in receiving — until it ships, every receiving session that has any variance is a partial-information approval. Loop in the warehouse operations manager to advocate.
  • Paper slips lost or not making it to managers: WMS-AUD-002 §4 — the same audit chain weakness as for un-photographed unit-level damage. Establish a single drop point (a clipboard at the receiving desk) until the UI ships.

9. Revision history

VersionDateAuthorChanges
1.0[DATE][NAME]Initial release. Documents the receiving exception system as designed (API + database) with explicit current-state callout that the floor UI does not exist as of release date. Documents that only DAMAGED exceptions modify the count via auto-increment of quantityDamaged; the other three types (WRONG_ITEM, MISSING, OVERAGE) are pure records with no count side-effect. Documents the schema-vs-API gap on QUALITY_ISSUE and OTHER (in DB enum but unreachable via code; counsel to file as DAMAGED with notes). Provides §4.5 paper-slip workaround for current-state operations and §5.3 slip template. Documents the approve-time damage workaround pattern (approve, then mark damaged via WMS-INV-001 §4.3). Cross-references WMS-REC-001 (lock requirement), WMS-REC-003 (approval review), WMS-INV-001 (post-approval damage), WMS-AUD-003 (variance reporting). Field mechanics, error strings, and approve-page banner text pulled from apps/api/src/routes/receiving.routes.ts, packages/domain/src/services/receiving.service.ts, apps/web/src/pages/receiving/approve.tsx, and packages/db/prisma/schema/receiving.prisma.