Low Level Design: Supply Chain Management Service

What Is a Supply Chain Management Service?

A supply chain management (SCM) service coordinates the flow of goods from suppliers to warehouses: maintaining a supplier catalog, creating and tracking purchase orders, recording shipments, processing goods receipts, updating inventory, and alerting on exceptions. It is a strong low level design interview topic because it involves multi-step workflows, state machines, and event-driven integration.

Functional Requirements

  • Manage a supplier catalog with contact information, lead times, and item offerings.
  • Create and manage purchase orders (POs) sent to suppliers.
  • Track shipments associated with POs from dispatch to delivery.
  • Record goods receipt: confirm items received, note discrepancies.
  • Update inventory levels on receipt.
  • Alert on late shipments, quantity mismatches, and quality rejections.

Non-Functional Requirements

  • PO and shipment state transitions must be auditable and irreversible (append-only history).
  • Alert delivery must be reliable (at-least-once with deduplication).
  • Inventory updates must be atomic with goods receipt recording.

Core Entities

Supplier
  id              UUID PK
  name            VARCHAR(128)
  contact_email   VARCHAR(256)
  lead_time_days  INT
  is_active       BOOLEAN
  created_at      TIMESTAMP

SupplierItem
  id              UUID PK
  supplier_id     UUID FK -> Supplier
  sku             VARCHAR(64)
  unit_cost       DECIMAL(12,2)
  currency        CHAR(3)
  moq             INT             -- minimum order quantity

PurchaseOrder
  id              UUID PK
  po_number       VARCHAR(32) UNIQUE
  supplier_id     UUID FK -> Supplier
  status          ENUM('DRAFT','SENT','ACKNOWLEDGED','PARTIALLY_SHIPPED','SHIPPED','RECEIVED','CLOSED','CANCELLED')
  expected_date   DATE
  created_at      TIMESTAMP
  updated_at      TIMESTAMP

POLineItem
  id              UUID PK
  po_id           UUID FK -> PurchaseOrder
  sku             VARCHAR(64)
  quantity_ordered INT
  unit_cost       DECIMAL(12,2)
  quantity_received INT DEFAULT 0

Shipment
  id              UUID PK
  po_id           UUID FK -> PurchaseOrder
  tracking_number VARCHAR(128)
  carrier         VARCHAR(64)
  status          ENUM('DISPATCHED','IN_TRANSIT','OUT_FOR_DELIVERY','DELIVERED','EXCEPTION')
  dispatched_at   TIMESTAMP
  delivered_at    TIMESTAMP NULL

ShipmentItem
  shipment_id     UUID FK -> Shipment
  sku             VARCHAR(64)
  quantity        INT

GoodsReceipt
  id              UUID PK
  shipment_id     UUID FK -> Shipment
  received_by     VARCHAR(64)
  received_at     TIMESTAMP
  notes           TEXT

GoodsReceiptLine
  id              UUID PK
  receipt_id      UUID FK -> GoodsReceipt
  sku             VARCHAR(64)
  quantity_expected INT
  quantity_received INT
  quantity_rejected INT DEFAULT 0
  rejection_reason VARCHAR(256) NULL

Purchase Order State Machine

Valid transitions:

DRAFT -> SENT
SENT -> ACKNOWLEDGED | CANCELLED
ACKNOWLEDGED -> PARTIALLY_SHIPPED | SHIPPED | CANCELLED
PARTIALLY_SHIPPED -> SHIPPED
SHIPPED -> RECEIVED
RECEIVED -> CLOSED

Persist every transition to a POStatusHistory table (po_id, from_status, to_status, changed_by, changed_at) for a full audit trail. Reject invalid transitions at the service layer before touching the PO row.

Shipment Tracking

Carrier webhooks push status updates (IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION). The SCM service exposes a webhook endpoint that:

  1. Verifies carrier HMAC signature.
  2. Upserts shipment status.
  3. If DELIVERED, triggers the goods receipt workflow notification.
  4. If EXCEPTION (e.g., customs hold, lost parcel), creates an Alert record and notifies the procurement team.

For carriers without webhook support, a polling job calls the carrier API every N minutes and applies the same upsert logic.

Goods Receipt Flow

  1. Warehouse staff open a GoodsReceipt against a Shipment.
  2. For each ShipmentItem, enter quantity_received and quantity_rejected.
  3. On confirmation, begin a transaction: a. Insert GoodsReceipt and GoodsReceiptLine rows. b. Update POLineItem.quantity_received for each SKU. c. Increment inventory (call Inventory Service or update local stock table). d. If all line items are fully received, advance PO status to RECEIVED. e. If quantity discrepancy exists (received != expected), insert a Discrepancy Alert. f. Commit.

Inventory Update

Inventory update is part of the same database transaction as goods receipt when both services share a DB. If inventory is a separate service, use an outbox pattern: write an InventoryCreditEvent row in the same transaction, then a relay process publishes it to the message bus. The inventory service consumes it and applies the credit idempotently using the GoodsReceiptLine ID as the idempotency key.

Exception Alerting

Alert triggers:

  • Late shipment: scheduled job checks POs where expected_date < TODAY AND status NOT IN ('RECEIVED','CLOSED','CANCELLED').
  • Quantity mismatch: detected during goods receipt (quantity_received != quantity_expected).
  • Quality rejection: quantity_rejected > 0 on any receipt line.
  • Carrier exception: EXCEPTION status from carrier webhook.

Each alert is inserted into an Alert table and published to a notification topic. The Alert table acts as the outbox for deduplication: insert with a UNIQUE key on (alert_type, reference_id) to avoid duplicate notifications from repeated job runs.

API Design

POST /suppliers                           -- create supplier
POST /suppliers/{id}/items                -- add item offering
POST /purchase-orders                     -- create PO
POST /purchase-orders/{id}/send           -- transition DRAFT -> SENT
POST /purchase-orders/{id}/acknowledge    -- SENT -> ACKNOWLEDGED
GET  /purchase-orders/{id}                -- PO detail with line items
POST /shipments                           -- create shipment against PO
POST /shipments/{id}/status               -- carrier webhook or manual update
POST /shipments/{id}/receive              -- create goods receipt
GET  /purchase-orders/{id}/history        -- audit trail

Interview Tips

  • Model the PO lifecycle as an explicit state machine and defend each valid transition.
  • Emphasize that inventory credit and goods receipt must be atomic — the outbox pattern is the clean answer when services are separate.
  • Show the Alert deduplication strategy; interviewers will probe for double-alert scenarios.
  • Discuss the webhook vs. polling trade-off for carrier tracking and when you would use each.

Frequently Asked Questions

What is a supply chain management service in system design?

A supply chain management service coordinates the flow of goods from suppliers through warehouses to end customers. In system design terms it is a collection of bounded contexts: procurement (purchase orders, supplier contracts), inventory (stock levels per location), fulfillment (pick, pack, ship), and logistics (carrier integration, tracking). Each context owns its data and exposes events that downstream contexts consume. The architecture favors event-driven patterns because supply chain operations are long-running and involve external parties; a purchase order placed today may not be received for weeks, so synchronous RPC across the full flow is impractical.

How does a purchase order state machine work?

A purchase order (PO) moves through a well-defined set of states: Draft, Submitted, Acknowledged, Partially Received, Fully Received, Invoiced, and Closed (or Cancelled at any pre-receipt stage). Each transition is triggered by an event: a buyer submits the draft, the supplier sends an acknowledgment EDI message, warehouse staff record a goods receipt, and finance records invoice payment. The state machine is enforced at the service layer; invalid transitions return errors rather than silently corrupting state. All transitions are persisted as immutable events in an append-only store, giving a full audit trail. Timeout monitors trigger alerts or automated follow-ups when a PO remains in Submitted without acknowledgment beyond the SLA window.

How do you track shipments across multiple carriers?

Carrier integration is abstracted behind a unified tracking adapter interface. Each carrier (UPS, FedEx, DHL, regional last-mile providers) has a concrete adapter that polls or receives webhooks from the carrier’s API and maps carrier-specific status codes to a canonical event schema (e.g., IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION). A tracking aggregator service subscribes to all adapter outputs, deduplicates events by tracking number and timestamp, and publishes normalized events to an internal bus. Downstream services (customer notifications, analytics, SLA monitoring) consume from the bus without knowing which carrier handled the shipment. Polling intervals are backed off exponentially once a shipment reaches a terminal state to reduce unnecessary API calls.

How do you handle inventory discrepancies between systems?

Discrepancies arise when the warehouse management system (WMS), the order management system (OMS), and the ERP disagree on stock levels due to delayed syncs, failed messages, or manual adjustments. The canonical resolution pattern is to designate one system as the source of truth for each data attribute and have others reconcile toward it. Periodic reconciliation jobs compare counts across systems and emit discrepancy events that trigger investigation workflows. Physical cycle counts feed corrections back as signed adjustment transactions rather than absolute overwrites, preserving the audit trail. For real-time decisions (can this order be fulfilled?), the OMS reserves inventory via soft allocation; the WMS confirms or rejects the reservation when it processes the pick task, and the OMS rolls back if rejected. Alerting on discrepancy magnitude and frequency helps surface systemic integration bugs early.

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Shopify Interview Guide

See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering

See also: Databricks Interview Guide 2026: Spark Internals, Delta Lake, and Lakehouse Architecture

Scroll to Top