How to Design a Supply Chain Management & Order Tracking System
End-to-End Logistics Platform — Orders, Inventory, Warehousing, Shipping, Procurement & Analytics
1. Introduction & The Supply Chain Challenge
Supply chain management is the backbone of global commerce. Every product you purchase — from groceries to electronics — passes through a complex network of suppliers, manufacturers, warehouses, distribution centers, carriers, and last-mile delivery services before reaching your doorstep. A modern Supply Chain Management (SCM) and Order Tracking system must orchestrate all of these touchpoints in real-time, providing visibility, control, and optimization across the entire value chain.
The COVID-19 pandemic exposed fragilities in global supply chains that cost the world economy an estimated $4 trillion. Companies that had invested in digital supply chain infrastructure — real-time inventory visibility, multi-carrier shipping, demand sensing, and automated procurement — recovered faster and gained market share. The lesson is clear: supply chain digitization is no longer optional; it is a competitive imperative. Yet building a comprehensive SCM system is one of the most complex software engineering challenges in enterprise computing.
Consider the journey of a single customer order. When a customer places an order on an e-commerce platform, the system must validate the order, check inventory availability across multiple warehouses (potentially in different countries), determine the optimal fulfillment location based on proximity and inventory levels, allocate inventory to prevent overselling, generate a pick list for warehouse workers, coordinate packing and labeling, select the optimal carrier based on cost and delivery time, generate shipping documents (bill of lading, customs declarations for international orders), track the shipment in real-time via GPS, handle exceptions like carrier delays or failed deliveries, manage returns if the customer initiates an RMA, and update financial records for billing and cost accounting. Each of these steps involves multiple microservices, external integrations, and complex business rules.
Real-World Case Studies
Understanding how industry leaders solve supply chain challenges provides practical insights for our system design:
| Company | System | Scale | Key Innovation |
|---|---|---|---|
| Amazon | Fulfillment & Logistics | 1.6M packages/day | Anticipatory shipping, robotic fulfillment, ML-driven demand forecasting |
| Walmart | Supply Chain Platform | 10,000+ stores, 115 distribution centers | Retail Link data sharing, cross-docking, blockchain traceability |
| FedEx | Package Tracking | 15M packages/day | Real-time GPS tracking, SenseAware IoT sensors, ML-based exception prediction |
| DHL | Global Trade Management | 220+ countries | Automated customs classification, HS code lookup, duty calculation |
| Maersk & IBM | TradeLens | 150M+ shipping events | Blockchain-based provenance tracking for container shipping |
Amazon's approach to demand forecasting is particularly instructive. They operate over 400 fulfillment centers worldwide, each storing different product assortments. Their anticipatory shipping model uses machine learning to predict what customers will order and pre-positions inventory in nearby fulfillment centers before the orders are placed. This reduces delivery times from days to hours. The system processes billions of data points daily — search queries, browsing patterns, purchase history, seasonal trends, weather data, and social media signals — to generate item-level demand forecasts at each fulfillment center.
Walmart's supply chain platform is another marvel of engineering. Their Retail Link system shares real-time sales data with over 100,000 suppliers, enabling vendor-managed inventory (VMI) where suppliers automatically replenish stock based on actual consumption. Their cross-docking technique at distribution centers eliminates storage entirely: incoming shipments are immediately sorted and loaded onto outbound trucks, reducing handling costs by 50% and delivery time by 60%. Building a system that coordinates this level of real-time data sharing and logistics orchestration requires careful attention to data consistency, API design, and event-driven architectures.
Our system draws from these real-world examples to create a comprehensive SCM platform that handles the full supply chain lifecycle — from procurement and manufacturing through warehousing, order fulfillment, shipping, and returns — with real-time visibility and intelligent optimization at every stage.
2. Functional & Non-Functional Requirements
Functional Requirements
- Order Lifecycle Management: Create, validate, confirm, fulfill, ship, deliver, and cancel orders. Support partial shipments, split orders across warehouses, and backorders when inventory is unavailable.
- Inventory Tracking: Real-time inventory visibility across all warehouses and transit locations. Support lot tracking, serial number tracking, expiry date management, and quarantine status.
- Shipment Tracking: Real-time GPS-based shipment tracking with carrier integration. Webhook-based event ingestion. Predicted delivery time estimates using ML models.
- Warehouse Management: Pick list generation with wave planning, zone picking, and batch picking. Pack station workflows with label generation. Putaway suggestions based on item velocity.
- Procurement & Supplier Management: Supplier onboarding, catalog management, RFQ workflows, purchase order creation and approval, and supplier performance scoring.
- Demand Forecasting: ML-based demand prediction at the SKU-location level using historical sales, seasonality, promotions, and external signals.
- Returns Management (RMA): Return authorization, return shipping label generation, receiving inspection, disposition decisions, and refund processing.
- Multi-Carrier Shipping: Rate shopping across multiple carriers, automatic carrier selection, international shipping with customs documentation, and hazmat compliance.
- Document Management: Generate and store bills of lading, packing slips, commercial invoices, customs declarations, certificates of origin, and proof of delivery.
- Analytics & Reporting: Real-time dashboards for order status, inventory health, shipment performance, cost analysis, and supplier scorecards.
- Alerting: Proactive notifications for delivery delays, stockout risk, carrier exceptions, quality issues, and customs holds.
- Route Optimization: Last-mile delivery route optimization considering traffic, delivery windows, vehicle capacity, and driver hours-of-service regulations.
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Availability | 99.99% | Supply chain operations run 24/7; downtime causes cascading delays |
| Inventory Accuracy | 99.99% real-time consistency | Overselling erodes customer trust; underselling wastes capacity |
| Order Processing Latency | < 500ms validation, < 2s allocation | E-commerce customers expect instant confirmation |
| Shipment Tracking Updates | < 30 seconds freshness | Customers expect near real-time tracking |
| Throughput | 100K orders/hour peak, 10K shipments/hour | Black Friday / holiday season peak loads |
| Data Retention | 7 years regulatory, 90 days detailed logs | Financial and customs compliance |
| Multi-Currency & Multi-Region | 50+ currencies, 200+ countries | Global supply chain operations |
| Disaster Recovery | RPO 1 minute, RTO 5 minutes | Minimize data loss and downtime during failures |
| API Rate Limiting | 5000 req/min per tenant | Protect system from partner API abuse |
| Compliance | GDPR, SOC 2, FDA 21 CFR Part 11, Hazmat | Industry-specific regulatory requirements |
Key Design Tradeoffs
| Tradeoff | Option A | Option B | Our Choice |
|---|---|---|---|
| Inventory Allocation | Pessimistic locking | Optimistic concurrency with retries | Optimistic concurrency + reservation TTL |
| Order State Management | Single status field | Event-sourced state machine | Event-sourced for audit trail |
| Carrier Integration | Direct API calls to each carrier | Abstracted carrier adapter pattern | Adapter pattern |
| Demand Forecasting | Batch processing (daily) | Streaming ML inference (real-time) | Batch training + real-time inference |
| Document Storage | Attached to order records in DB | Object storage with DB references | Object storage + metadata in DB |
3. Capacity Estimation & Cost Modeling
Order Volume
- Peak orders: 100K orders/hour (Black Friday), average 10K orders/hour
- Daily orders: ~240K average, ~1M peak
- Annual orders: ~88 million
- Average order size: 3.2 line items per order
- Annual line items: ~280 million
Inventory
- Active SKUs: 2 million unique products
- Warehouse locations: 50 warehouses across 15 countries
- SKU-location combinations: up to 100 million
- Inventory transactions/day: ~5 million (receiving, picking, adjustments, transfers)
- Inventory snapshot storage: 100M records x 500 bytes = 50 GB current state
Shipment Tracking
- Active shipments: ~2 million at any time
- Tracking events/day: ~50 million (GPS pings, status scans, exceptions)
- Carrier webhooks/day: ~100 million events ingested
- Tracking data storage: 50M events/day x 90 days x 300 bytes = 1.35 TB (hot storage)
Compute Resources
| Service | Instances | vCPU Each | Memory Each | Total |
|---|---|---|---|---|
| Order Service | 8 | 4 | 8 GB | 32 vCPU, 64 GB |
| Inventory Service | 8 | 4 | 8 GB | 32 vCPU, 64 GB |
| Warehouse Service | 4 | 4 | 8 GB | 16 vCPU, 32 GB |
| Shipping Service | 6 | 4 | 8 GB | 24 vCPU, 48 GB |
| Tracking Ingestion | 12 | 4 | 16 GB | 48 vCPU, 192 GB |
| Forecasting Engine | 4 | 8 | 32 GB | 32 vCPU, 128 GB |
| Analytics API | 4 | 4 | 8 GB | 16 vCPU, 32 GB |
| Document Service | 2 | 2 | 4 GB | 4 vCPU, 8 GB |
Storage
| Data Type | Hot (SSD) | Warm (HDD) | Cold (Archive) |
|---|---|---|---|
| Order Data | 200 GB | 2 TB | 10 TB |
| Inventory State | 50 GB | 200 GB | 1 TB |
| Tracking Events | 1.35 TB | 12 TB | 100 TB |
| Documents | 100 GB | 1 TB | 5 TB |
| Analytics Aggregates | 50 GB | 500 GB | 2 TB |
Estimated Monthly Cost (Cloud)
| Component | Monthly Cost |
|---|---|
| Compute (VMs/Containers) | $15,000 |
| Primary Database (PostgreSQL) | $5,000 |
| Redis Cluster | $2,500 |
| Event Streaming (Kafka) | $4,000 |
| Object Storage | $3,000 |
| Time-Series DB (InfluxDB) | $1,500 |
| ML Compute (GPU instances) | $3,000 |
| CDN & Bandwidth | $2,000 |
| Monitoring & Logging | $1,500 |
| External API Costs (carriers, mapping) | $5,000 |
| Total | $42,500/month |
4. Data Model & Storage Schema
The data model for a supply chain system is inherently complex due to the many entities and relationships involved. We design the schema around aggregate roots that represent bounded contexts: Orders, Inventory, Shipments, Warehouses, Suppliers, and Purchase Orders. Each aggregate has its own transactional consistency boundary, and cross-aggregate operations use the saga pattern.
Order Aggregate
SQL
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
order_number VARCHAR(50) UNIQUE NOT NULL,
customer_id UUID NOT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'pending',
currency_code CHAR(3) NOT NULL,
subtotal DECIMAL(12,2) NOT NULL,
tax DECIMAL(12,2) NOT NULL DEFAULT 0,
shipping_cost DECIMAL(12,2) NOT NULL DEFAULT 0,
total DECIMAL(12,2) NOT NULL,
shipping_address_id UUID NOT NULL,
billing_address_id UUID NOT NULL,
source_channel VARCHAR(30) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
confirmed_at TIMESTAMPTZ,
shipped_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ,
version INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id),
sku VARCHAR(100) NOT NULL,
product_name VARCHAR(255) NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(12,2) NOT NULL,
discount DECIMAL(12,2) NOT NULL DEFAULT 0,
tax DECIMAL(12,2) NOT NULL DEFAULT 0,
line_total DECIMAL(12,2) NOT NULL,
allocated_warehouse_id UUID,
fulfillment_status VARCHAR(30) NOT NULL DEFAULT 'pending',
allocated_quantity INT NOT NULL DEFAULT 0,
picked_quantity INT NOT NULL DEFAULT 0,
shipped_quantity INT NOT NULL DEFAULT 0,
version INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE order_status_history (
id BIGSERIAL PRIMARY KEY,
order_id UUID NOT NULL REFERENCES orders(id),
old_status VARCHAR(30),
new_status VARCHAR(30) NOT NULL,
changed_by UUID,
reason TEXT,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Inventory Aggregate
SQL
CREATE TABLE inventory_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
sku VARCHAR(100) NOT NULL,
warehouse_id UUID NOT NULL,
lot_number VARCHAR(50),
serial_number VARCHAR(100),
expiry_date DATE,
quantity_on_hand INT NOT NULL DEFAULT 0 CHECK (quantity_on_hand >= 0),
quantity_reserved INT NOT NULL DEFAULT 0 CHECK (quantity_reserved >= 0),
quantity_available INT GENERATED ALWAYS AS (quantity_on_hand - quantity_reserved) STORED,
quantity_in_transit INT NOT NULL DEFAULT 0,
quantity_quarantined INT NOT NULL DEFAULT 0,
bin_location VARCHAR(50),
status VARCHAR(20) NOT NULL DEFAULT 'available',
last_counted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
version INTEGER NOT NULL DEFAULT 1,
UNIQUE(sku, warehouse_id, lot_number, serial_number)
);
CREATE TABLE inventory_transactions (
id BIGSERIAL PRIMARY KEY,
tenant_id UUID NOT NULL,
sku VARCHAR(100) NOT NULL,
warehouse_id UUID NOT NULL,
transaction_type VARCHAR(30) NOT NULL,
quantity_change INT NOT NULL,
reference_type VARCHAR(30),
reference_id UUID,
performed_by UUID NOT NULL,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE inventory_reservations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL,
order_item_id UUID NOT NULL,
sku VARCHAR(100) NOT NULL,
warehouse_id UUID NOT NULL,
reserved_quantity INT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Shipment Aggregate
SQL
CREATE TABLE shipments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
shipment_number VARCHAR(50) UNIQUE NOT NULL,
order_id UUID NOT NULL REFERENCES orders(id),
carrier_code VARCHAR(20) NOT NULL,
service_level VARCHAR(30) NOT NULL,
tracking_number VARCHAR(100),
status VARCHAR(30) NOT NULL DEFAULT 'pending',
ship_from_warehouse_id UUID NOT NULL,
ship_to_address_id UUID NOT NULL,
estimated_delivery_date DATE,
actual_delivery_date DATE,
weight_kg DECIMAL(8,3),
dimensions_json JSONB,
shipping_cost DECIMAL(12,2),
insurance_cost DECIMAL(12,2) DEFAULT 0,
customs_value DECIMAL(12,2),
is_international BOOLEAN NOT NULL DEFAULT FALSE,
hazmat_class VARCHAR(10),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE shipment_events (
id BIGSERIAL PRIMARY KEY,
shipment_id UUID NOT NULL REFERENCES shipments(id),
event_type VARCHAR(50) NOT NULL,
event_code VARCHAR(20),
description TEXT,
location_lat DECIMAL(10,7),
location_lng DECIMAL(10,7),
location_name VARCHAR(255),
carrier_timestamp TIMESTAMPTZ,
ingested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
raw_payload JSONB
);
quantity_available as a computed guard against overselling. The inventory_reservations table with a TTL-based expires_at ensures that abandoned carts do not permanently lock inventory. Expired reservations are cleaned up by a background job, releasing inventory back to the available pool.
Additional Core Tables
SQL
CREATE TABLE warehouses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
address_id UUID NOT NULL,
country_code CHAR(2) NOT NULL,
timezone VARCHAR(50) NOT NULL,
capacity_cubic_meters DECIMAL(10,2),
supports_hazmat BOOLEAN NOT NULL DEFAULT FALSE,
supports_perishable BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE suppliers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
contact_email VARCHAR(255),
contact_phone VARCHAR(50),
lead_time_days INT,
payment_terms_days INT,
rating DECIMAL(3,2),
is_preferred BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE purchase_orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
po_number VARCHAR(50) UNIQUE NOT NULL,
supplier_id UUID NOT NULL REFERENCES suppliers(id),
warehouse_id UUID NOT NULL REFERENCES warehouses(id),
status VARCHAR(30) NOT NULL DEFAULT 'draft',
total_amount DECIMAL(12,2),
currency_code CHAR(3) NOT NULL,
expected_delivery_date DATE,
approved_by UUID,
approved_at TIMESTAMPTZ,
created_by UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE purchase_order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
purchase_order_id UUID NOT NULL REFERENCES purchase_orders(id),
sku VARCHAR(100) NOT NULL,
product_name VARCHAR(255) NOT NULL,
quantity_ordered INT NOT NULL,
quantity_received INT NOT NULL DEFAULT 0,
unit_cost DECIMAL(12,2) NOT NULL,
line_total DECIMAL(12,2) NOT NULL
);
Storage Strategy
| Data Category | Primary Store | Cache Layer | Archive | Rationale |
|---|---|---|---|---|
| Orders & Items | PostgreSQL | Redis (hot orders) | S3 Parquet | ACID transactions critical for orders |
| Inventory State | PostgreSQL | Redis (real-time counts) | S3 Parquet | Strong consistency for allocation |
| Tracking Events | TimescaleDB | Redis (latest event) | S3 (after 90 days) | Time-series optimized, append-only |
| Documents | Object Storage (S3) | CDN | Cold Storage (Glacier) | BLOB data; DB stores metadata only |
| Analytics Aggregates | ClickHouse | Redis (pre-computed) | S3 Parquet | Columnar store for OLAP queries |
| Forecast Data | PostgreSQL | Redis | S3 | Structured forecast results with versioning |
5. High-Level Architecture Overview
The system follows a microservices architecture with event-driven communication between services. Each service owns its data store and exposes a well-defined API. Services communicate asynchronously via an event bus (Kafka) for most operations, with synchronous REST/gRPC calls only for operations that require immediate responses.
Service Responsibilities
| Service | Responsibility | Database | Key Integrations |
|---|---|---|---|
| Order Service | Order CRUD, validation, status transitions, saga orchestrator | PostgreSQL | Payment, Inventory, Warehouse |
| Inventory Service | Stock levels, reservations, allocations, cycle counting | PostgreSQL | Order, Warehouse, Purchase Order |
| Warehouse Service | Pick/pack/ship workflows, putaway, wave planning | PostgreSQL | Inventory, Order, Shipping |
| Shipping Service | Carrier integration, label generation, tracking ingestion, rate shopping | TimescaleDB | Carriers, GPS, Order |
| Procurement Service | Supplier management, PO workflow, receiving | PostgreSQL | Supplier APIs, ERP, Inventory |
| Returns Service | RMA authorization, return tracking, inspection, disposition | PostgreSQL | Order, Inventory, Shipping |
| Forecasting Engine | Demand prediction, reorder calculations, safety stock optimization | PostgreSQL + S3 | Historical sales, promotions, external signals |
| Analytics Engine | Aggregations, dashboards, report generation | ClickHouse | All services (via Kafka) |
6. API Design
The API layer follows RESTful conventions with resource-oriented URLs, standard HTTP methods, and consistent error responses. All endpoints require authentication (JWT bearer tokens) and authorization (role-based access control). The API versioning uses URI path prefixing (/api/v1/).
Order API
C#
[ApiController]
[Route("api/v1/orders")]
[Authorize]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly ILogger<OrdersController> _logger;
[HttpPost]
[ProducesResponseType(typeof(OrderResponse), StatusCodes.Status201Created)]
public async Task<ActionResult<OrderResponse>> CreateOrder(
[FromBody] CreateOrderRequest request)
{
var command = new CreateOrderCommand
{
TenantId = User.GetTenantId(),
CustomerId = request.CustomerId,
Items = request.Items.Select(i => new OrderItemDto
{
Sku = i.Sku,
Quantity = i.Quantity,
UnitPrice = i.UnitPrice
}).ToList(),
ShippingAddress = request.ShippingAddress,
BillingAddress = request.BillingAddress,
SourceChannel = request.SourceChannel,
Notes = request.Notes
};
var result = await _orderService.CreateOrderAsync(command);
return CreatedAtAction(nameof(GetOrder),
new { id = result.Id }, result);
}
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(OrderResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<OrderResponse>> GetOrder(Guid id)
{
var order = await _orderService.GetOrderAsync(id, User.GetTenantId());
if (order == null) return NotFound();
return Ok(order);
}
[HttpPost("{id:guid}/confirm")]
public async Task<ActionResult> ConfirmOrder(Guid id)
{
await _orderService.ConfirmOrderAsync(id, User.GetTenantId());
return Ok();
}
[HttpPost("{id:guid}/cancel")]
public async Task<ActionResult> CancelOrder(
Guid id, [FromBody] CancelOrderRequest request)
{
await _orderService.CancelOrderAsync(
id, User.GetTenantId(), request.Reason);
return Ok();
}
[HttpGet]
[ProducesResponseType(typeof(PagedResult<OrderSummary>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedResult<OrderSummary>>> ListOrders(
[FromQuery] OrderFilter filter,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var result = await _orderService.ListOrdersAsync(
User.GetTenantId(), filter, page, pageSize);
return Ok(result);
}
}
public class CreateOrderRequest
{
public Guid CustomerId { get; set; }
public List<OrderItemRequest> Items { get; set; }
public AddressRequest ShippingAddress { get; set; }
public AddressRequest BillingAddress { get; set; }
public string SourceChannel { get; set; }
public string? Notes { get; set; }
}
Inventory API
C#
[ApiController]
[Route("api/v1/inventory")]
[Authorize]
public class InventoryController : ControllerBase
{
[HttpGet("stock-levels")]
public async Task<ActionResult<List<StockLevelResponse>>>
GetStockLevels([FromQuery] StockLevelQuery query)
{
var levels = await _inventoryService.GetStockLevelsAsync(
User.GetTenantId(), query.Sku, query.WarehouseId);
return Ok(levels);
}
[HttpPost("reserve")]
public async Task<ActionResult<ReservationResponse>> ReserveInventory(
[FromBody] ReserveInventoryRequest request)
{
var reservation = await _inventoryService.ReserveAsync(new ReserveCommand
{
TenantId = User.GetTenantId(),
OrderId = request.OrderId,
OrderItemId = request.OrderItemId,
Sku = request.Sku,
WarehouseId = request.WarehouseId,
Quantity = request.Quantity,
ReservationTtlMinutes = 30
});
return Ok(reservation);
}
[HttpPost("allocate")]
public async Task<ActionResult> AllocateInventory(
[FromBody] AllocateInventoryRequest request)
{
await _inventoryService.AllocateAsync(request.ReservationId);
return Ok();
}
[HttpGet("availability/{sku}")]
public async Task<ActionResult<AvailabilityResponse>> CheckAvailability(
string sku, [FromQuery] int requestedQuantity = 1)
{
var availability = await _inventoryService
.CheckGlobalAvailabilityAsync(sku, requestedQuantity);
return Ok(availability);
}
}
Shipment Tracking API
C#
[ApiController]
[Route("api/v1/shipments")]
[Authorize]
public class ShipmentsController : ControllerBase
{
[HttpPost("rate-shop")]
public async Task<ActionResult<List<RateQuoteResponse>>>
GetRateQuotes([FromBody] RateShopRequest request)
{
var quotes = await _shippingService.GetRateQuotesAsync(
request.OriginWarehouseId,
request.DestinationAddress,
request.Packages,
request.CarrierFilter);
return Ok(quotes);
}
[HttpPost]
public async Task<ActionResult<ShipmentResponse>> CreateShipment(
[FromBody] CreateShipmentRequest request)
{
var shipment = await _shippingService.CreateShipmentAsync(
request.OrderId, request.CarrierCode, request.ServiceLevel);
return CreatedAtAction(nameof(GetShipment),
new { id = shipment.Id }, shipment);
}
[HttpGet("{id:guid}/tracking")]
public async Task<ActionResult<TrackingResponse>> GetTracking(Guid id)
{
var tracking = await _shippingService.GetTrackingAsync(id);
return Ok(tracking);
}
[HttpGet("{id:guid}/eta")]
public async Task<ActionResult<EtaResponse>> GetEta(Guid id)
{
var eta = await _shippingService.GetPredictedEtaAsync(id);
return Ok(eta);
}
}
Error Response Format
JSON
{
"error": {
"code": "INVENTORY_INSUFFICIENT",
"message": "Insufficient inventory for SKU WIDGET-001",
"details": {
"sku": "WIDGET-001",
"requested": 50,
"available": 32,
"warehouse": "WH-EAST-01"
},
"traceId": "abc-123-def-456",
"timestamp": "2026-07-10T14:32:01Z"
}
}
7. Order Lifecycle Management
The order lifecycle is the central nervous system of the supply chain platform. Every order transitions through a well-defined set of states, and each transition triggers downstream operations in other services. We model this as an event-sourced state machine, providing a complete audit trail and enabling temporal queries.
Order State Machine
Order Service Implementation
C#
public class OrderService : IOrderService
{
private readonly IOrderRepository _orderRepo;
private readonly IInventoryClient _inventoryClient;
private readonly IEventPublisher _eventPublisher;
private readonly IIdempotencyChecker _idempotency;
private readonly ILogger<OrderService> _logger;
public async Task<OrderResponse> CreateOrderAsync(CreateOrderCommand command)
{
if (await _idempotency.IsDuplicate(command.IdempotencyKey))
return await _idempotency.GetCachedResult<OrderResponse>(
command.IdempotencyKey);
var order = Order.Create(
command.TenantId,
command.CustomerId,
command.Items.Select(i =>
OrderItem.Create(i.Sku, i.Quantity, i.UnitPrice)).ToList(),
command.ShippingAddress,
command.BillingAddress,
command.SourceChannel);
await _orderRepo.SaveAsync(order);
await _eventPublisher.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
TenantId = order.TenantId,
Items = order.Items.Select(i => new OrderItemEvent
{
Sku = i.Sku,
Quantity = i.Quantity
}).ToList(),
Timestamp = DateTime.UtcNow
});
_logger.LogInformation(
"Order {OrderNumber} created for customer {CustomerId}",
order.OrderNumber, command.CustomerId);
var response = order.ToResponse();
await _idempotency.CacheResult(command.IdempotencyKey, response);
return response;
}
public async Task ConfirmOrderAsync(Guid orderId, Guid tenantId)
{
var order = await _orderRepo.GetByIdAsync(orderId, tenantId);
if (order == null) throw new OrderNotFoundException(orderId);
if (order.Status != OrderStatus.Pending)
throw new InvalidOrderStateException(
orderId, OrderStatus.Pending, order.Status);
var validation = await ValidateOrderAsync(order);
if (!validation.IsValid)
{
order.Reject(validation.Reasons);
await _orderRepo.SaveAsync(order);
return;
}
order.Confirm();
await _orderRepo.SaveAsync(order);
await _eventPublisher.PublishAsync(new OrderConfirmedEvent
{
OrderId = order.Id,
Items = order.Items.Select(i =>
new OrderItemAllocationRequest
{
Sku = i.Sku,
Quantity = i.Quantity,
PreferredWarehouseId =
order.ShippingAddress.PreferredWarehouseId
}).ToList()
});
}
public async Task HandleInventoryAllocated(InventoryAllocatedEvent evt)
{
var order = await _orderRepo.GetByIdAsync(evt.OrderId, evt.TenantId);
if (order == null) return;
order.AllocateItems(evt.Allocations);
await _orderRepo.SaveAsync(order);
if (order.AllItemsAllocated)
{
await _eventPublisher.PublishAsync(
new OrderReadyForFulfillmentEvent
{
OrderId = order.Id,
WarehouseAssignments =
order.GetWarehouseAssignments()
});
}
}
public async Task HandleShipmentCreated(ShipmentCreatedEvent evt)
{
var order = await _orderRepo.GetByIdAsync(evt.OrderId, evt.TenantId);
if (order == null) return;
order.MarkShipped(
evt.ShipmentId, evt.CarrierCode, evt.TrackingNumber);
await _orderRepo.SaveAsync(order);
await _eventPublisher.PublishAsync(new OrderShippedEvent
{
OrderId = order.Id,
TrackingNumber = evt.TrackingNumber,
EstimatedDelivery = evt.EstimatedDeliveryDate
});
}
public async Task HandleDeliveryConfirmed(DeliveryConfirmedEvent evt)
{
var order = await _orderRepo.GetByIdAsync(evt.OrderId, evt.TenantId);
if (order == null) return;
order.MarkDelivered(evt.ProofOfDeliveryUrl);
await _orderRepo.SaveAsync(order);
await _eventPublisher.PublishAsync(new OrderCompletedEvent
{
OrderId = order.Id,
DeliveredAt = evt.DeliveredAt
});
}
}
Saga Pattern for Order Fulfillment
C#
public class OrderFulfillmentSaga
{
public SagaStep[] Steps => new[]
{
new SagaStep
{
Name = "AllocateInventory",
Execute = async (context) =>
{
await _inventoryClient.AllocateAsync(
context.OrderId, context.Items);
},
Compensate = async (context) =>
{
await _inventoryClient.ReleaseReservationAsync(
context.OrderId);
}
},
new SagaStep
{
Name = "CapturePayment",
Execute = async (context) =>
{
await _paymentClient.CaptureAsync(
context.PaymentIntentId, context.TotalAmount);
},
Compensate = async (context) =>
{
await _paymentClient.VoidAsync(
context.PaymentIntentId);
}
},
new SagaStep
{
Name = "CreateFulfillmentTask",
Execute = async (context) =>
{
await _warehouseClient.CreateFulfillmentTaskAsync(
context.OrderId, context.WarehouseAssignments);
},
Compensate = async (context) =>
{
await _warehouseClient.CancelFulfillmentTaskAsync(
context.OrderId);
}
}
};
}
Partial Shipments & Backorders
When an order contains items from multiple warehouses or some items are out of stock, the system supports partial shipments. The order is split into fulfillment groups by warehouse. Items that cannot be allocated immediately are placed on backorder.
| Scenario | Behavior | Customer Notification |
|---|---|---|
| All items available in one warehouse | Single shipment, standard fulfillment | Order confirmed, shipped, delivered |
| Items split across warehouses | Multiple shipments from different warehouses | Each shipment tracked separately |
| Some items out of stock | Available items ship now; backordered items ship later | Partial shipment notification with ETA |
| Entire order out of stock | Order held, backorder created | Backorder confirmation with estimated restock date |
| Customer cancels backordered items | Backorder removed, refund for cancelled items | Refund confirmation |
8. Inventory Tracking Across Warehouses
Real-time inventory visibility is the most critical capability in a supply chain system. Every decision — order allocation, replenishment, demand planning, and customer promises — depends on accurate, up-to-date inventory data. We achieve this through a multi-layered approach: strong consistency at the database level, Redis caching for fast reads, and event-driven synchronization across warehouses.
Inventory Ledger Architecture
Inventory Allocation Algorithm
C#
public class InventoryAllocator : IInventoryAllocator
{
private readonly IInventoryRepository _inventoryRepo;
private readonly IWarehouseRepository _warehouseRepo;
private readonly IRdpClient _redis;
public async Task<List<AllocationResult>> AllocateAsync(
Guid orderId,
List<AllocationRequest> items,
AllocationStrategy strategy)
{
var results = new List<AllocationResult>();
foreach (var item in items)
{
var allocation = strategy switch
{
AllocationStrategy.ClosestWarehouse =>
await AllocateClosestAsync(orderId, item),
AllocationStrategy.CheapestShipping =>
await AllocateCheapestAsync(orderId, item),
AllocationStrategy.Balanced =>
await AllocateBalancedAsync(orderId, item),
AllocationStrategy.FIFO =>
await AllocateFifoAsync(orderId, item),
_ => throw new ArgumentException(
$"Unknown strategy: {strategy}")
};
results.Add(allocation);
}
return results;
}
private async Task<AllocationResult> AllocateClosestAsync(
Guid orderId, AllocationRequest item)
{
var customerAddress =
await GetCustomerAddressAsync(orderId);
var warehouses =
await _warehouseRepo.GetActiveWarehousesAsync();
var rankedWarehouses = warehouses
.Where(w => w.SupportsItem(item.Sku))
.OrderBy(w => w.DistanceTo(customerAddress))
.ToList();
foreach (var warehouse in rankedWarehouses)
{
var available = await GetAvailableQuantityAsync(
item.Sku, warehouse.Id);
if (available >= item.Quantity)
{
var reservation = await CreateReservationAsync(
orderId, item, warehouse.Id, item.Quantity);
return new AllocationResult
{
Sku = item.Sku,
WarehouseId = warehouse.Id,
Quantity = item.Quantity,
Status = AllocationStatus.FullyAllocated,
ReservationId = reservation.Id
};
}
if (available > 0)
{
var reservation = await CreateReservationAsync(
orderId, item, warehouse.Id, available);
item.Quantity -= available;
if (item.Quantity == 0)
return new AllocationResult
{
Sku = item.Sku,
WarehouseId = warehouse.Id,
Quantity = available,
Status = AllocationStatus.FullyAllocated,
ReservationId = reservation.Id
};
}
}
return new AllocationResult
{
Sku = item.Sku,
Status = AllocationStatus.Unavailable,
Quantity = 0,
BackorderEta =
await GetRestockEstimateAsync(item.Sku)
};
}
private async Task<int> GetAvailableQuantityAsync(
string sku, Guid warehouseId)
{
var cacheKey = $"inv:avl:{sku}:{warehouseId}";
var cached = await _redis.GetAsync<int?>(cacheKey);
if (cached.HasValue) return cached.Value;
var inventory = await _inventoryRepo
.GetAvailableQuantityAsync(sku, warehouseId);
await _redis.SetAsync(
cacheKey, inventory, TimeSpan.FromSeconds(30));
return inventory;
}
}
public enum AllocationStrategy
{
ClosestWarehouse,
CheapestShipping,
Balanced,
FIFO
}
Inventory Reservation with TTL
C#
public class InventoryReservationService
{
private readonly IInventoryRepository _repo;
private readonly IEventPublisher _events;
public async Task<ReservationResult> ReserveAsync(
ReserveCommand cmd)
{
using var transaction = await _repo.BeginTransactionAsync(
IsolationLevel.Serializable);
try
{
var inventory = await _repo.GetForUpdateAsync(
cmd.Sku, cmd.WarehouseId, transaction);
if (inventory == null)
throw new InventoryNotFoundException(
cmd.Sku, cmd.WarehouseId);
if (inventory.QuantityAvailable < cmd.Quantity)
return ReservationResult.Insufficient(
inventory.QuantityAvailable, cmd.Quantity);
inventory.QuantityReserved += cmd.Quantity;
inventory.Version++;
var reservation = new InventoryReservation
{
Id = Guid.NewGuid(),
OrderId = cmd.OrderId,
OrderItemId = cmd.OrderItemId,
Sku = cmd.Sku,
WarehouseId = cmd.WarehouseId,
ReservedQuantity = cmd.Quantity,
ExpiresAt = DateTime.UtcNow.AddMinutes(
cmd.ReservationTtlMinutes),
Status = ReservationStatus.Active
};
await _repo.CreateReservationAsync(
reservation, transaction);
await _repo.UpdateInventoryAsync(
inventory, transaction);
await transaction.CommitAsync();
ScheduleExpirationCheck(
reservation.Id, reservation.ExpiresAt);
return ReservationResult.Success(reservation);
}
catch (Exception)
{
await transaction.RollbackAsync();
throw;
}
}
public async Task ReleaseExpiredReservationsAsync()
{
var expired = await _repo
.GetExpiredReservationsAsync(DateTime.UtcNow);
foreach (var reservation in expired)
{
using var transaction =
await _repo.BeginTransactionAsync();
try
{
var inventory = await _repo.GetForUpdateAsync(
reservation.Sku,
reservation.WarehouseId,
transaction);
inventory.QuantityReserved -=
reservation.ReservedQuantity;
inventory.Version++;
reservation.Status =
ReservationStatus.Expired;
await _repo.UpdateInventoryAsync(
inventory, transaction);
await _repo.UpdateReservationAsync(
reservation, transaction);
await transaction.CommitAsync();
await _events.PublishAsync(
new ReservationExpiredEvent
{
ReservationId = reservation.Id,
OrderId = reservation.OrderId,
Sku = reservation.Sku,
Quantity = reservation.ReservedQuantity
});
}
catch (Exception)
{
await transaction.RollbackAsync();
}
}
}
}
9. Real-Time Shipment Tracking
Real-time shipment tracking is a core customer-facing feature. Customers expect to see where their package is at any moment, with accurate estimated delivery times. This requires ingesting tracking events from multiple carriers, normalizing them into a unified event model, and pushing updates to clients in real-time.
Carrier Integration Architecture
Carrier Adapter Pattern
C#
public interface ICarrierAdapter
{
string CarrierCode { get; }
Task<RateQuoteResponse> GetRatesAsync(RateRequest request);
Task<LabelResponse> CreateLabelAsync(LabelRequest request);
Task<List<TrackingEvent>> GetTrackingHistoryAsync(
string trackingNumber);
Task<CancelResponse> CancelShipmentAsync(string shipmentId);
}
public class FedExAdapter : ICarrierAdapter
{
public string CarrierCode => "FEDEX";
private readonly HttpClient _httpClient;
private readonly FedExAuthManager _authManager;
public async Task<List<TrackingEvent>> GetTrackingHistoryAsync(
string trackingNumber)
{
var token = await _authManager.GetAccessTokenAsync();
var response = await _httpClient.GetAsync(
$"https://api.fedex.com/track/v1/" +
$"trackingnumbers/{trackingNumber}",
new { Authorization = $"Bearer {token}" });
return response.Tracks
.SelectMany(t => t.Events)
.Select(e => new TrackingEvent
{
Timestamp = e.Timestamp,
Status = NormalizeStatus(e.EventType),
Description = e.EventDescription,
Location = e.ScanLocation?.City,
Coordinates = e.ScanLocation?.Coordinates != null
? new Coordinates(
e.ScanLocation.Coordinates.Latitude,
e.ScanLocation.Coordinates.Longitude)
: null
}).ToList();
}
public async Task<LabelResponse> CreateLabelAsync(
LabelRequest request)
{
var shipmentRequest = MapToFedExShipment(request);
var response = await _httpClient.PostAsync(
"https://api.fedex.com/ship/v1/shipments",
shipmentRequest);
return new LabelResponse
{
TrackingNumber = response.TrackingNumber,
LabelUrl = response.Label.Parts.First().DocumentUrl,
ShippingCost = response.TotalNetCharge.Amount,
EstimatedDelivery = response.EstimatedDeliveryTime
};
}
}
public class NormalizedTrackingEvent
{
public string CarrierCode { get; set; }
public string TrackingNumber { get; set; }
public TrackingStatus Status { get; set; }
public string Description { get; set; }
public DateTime CarrierTimestamp { get; set; }
public DateTime IngestedAt { get; set; }
public Coordinates? Location { get; set; }
public string? LocationName { get; set; }
public string? ExceptionCode { get; set; }
public string? ExceptionDescription { get; set; }
public Dictionary<string, string> Metadata { get; set; }
}
Tracking Webhook Ingestion
C#
[ApiController]
[Route("api/v1/webhooks/carriers")]
[AllowAnonymous]
public class CarrierWebhookController : ControllerBase
{
private readonly ITrackingIngestionPipeline _pipeline;
private readonly IWebhookSignatureVerifier _sigVerifier;
[HttpPost("{carrierCode}")]
public async Task<ActionResult> HandleWebhook(
string carrierCode,
[FromBody] JsonElement payload)
{
if (!_sigVerifier.Verify(
carrierCode,
Request.Headers["X-Webhook-Signature"]
.FirstOrDefault(),
payload))
{
return Unauthorized();
}
var events = carrierCode.ToUpper() switch
{
"FEDEX" => FedexWebhookParser.Parse(payload),
"UPS" => UpsWebhookParser.Parse(payload),
"DHL" => DhlWebhookParser.Parse(payload),
"USPS" => UspsWebhookParser.Parse(payload),
_ => throw new NotSupportedException(
$"Carrier {carrierCode} not supported")
};
foreach (var trackingEvent in events)
{
await _pipeline.IngestAsync(trackingEvent);
}
return Ok(new { processed = events.Count });
}
}
public class TrackingIngestionPipeline :
ITrackingIngestionPipeline
{
private readonly ITrackingEventRepository _repo;
private readonly IDeduplicationService _dedup;
private readonly IEventPublisher _events;
private readonly IEtaPredictor _etaPredictor;
public async Task IngestAsync(NormalizedTrackingEvent evt)
{
if (await _dedup.IsDuplicateAsync(
evt.CarrierCode,
evt.TrackingNumber,
evt.CarrierTimestamp))
return;
await _repo.StoreEventAsync(evt);
await UpdateShipmentStatusAsync(
evt.TrackingNumber, evt.Status);
if (evt.ExceptionCode != null)
{
await _events.PublishAsync(
new ShipmentExceptionEvent
{
TrackingNumber = evt.TrackingNumber,
ExceptionCode = evt.ExceptionCode,
Description = evt.ExceptionDescription
});
}
var newEta = await _etaPredictor.PredictAsync(
evt.TrackingNumber, evt);
if (newEta != null)
{
await _events.PublishAsync(
new EtaUpdatedEvent
{
TrackingNumber = evt.TrackingNumber,
EstimatedDelivery = newEta.DeliveryDate,
Confidence = newEta.Confidence
});
}
await _events.PublishAsync(
new TrackingUpdateEvent
{
TrackingNumber = evt.TrackingNumber,
Status = evt.Status,
Description = evt.Description,
Timestamp = evt.CarrierTimestamp,
Location = evt.LocationName
});
}
}
Tracking Status Reference
| Status | Description | Customer Message |
|---|---|---|
Pending | Label created, awaiting pickup | Your order has been packed and is awaiting carrier pickup |
Picked Up | Carrier has picked up the package | Your package has been picked up by the carrier |
InTransit | Package in transit to destination | Your package is on its way (last scan: City, State) |
OutForDelivery | On the delivery vehicle | Your package is out for delivery today |
Delivered | Successfully delivered | Your package has been delivered |
Exception | Delivery attempt failed | We encountered an issue with delivery. We will retry tomorrow. |
Returned | Package returned to sender | Your package is being returned to us |
10. Warehouse Management (Pick, Pack, Ship)
Warehouse management is the physical execution layer of the supply chain. When an order is confirmed and inventory is allocated, the warehouse service generates work instructions for warehouse workers: pick lists, packing instructions, and shipping labels. Efficient warehouse operations directly impact order fulfillment speed and accuracy.
Warehouse Workflow
Pick List Generation
C#
public class WavePlanner
{
public async Task<Wave> CreateWaveAsync(
WaveConfiguration config)
{
var pendingOrders = await _orderRepo
.GetOrdersForFulfillmentAsync(
config.WarehouseId,
maxOrders: config.MaxOrdersPerWave,
priorityThreshold: config.PriorityThreshold);
var ordersByZone = pendingOrders
.GroupBy(o => GetPickZone(o.Items))
.OrderBy(g => g.Key)
.ToList();
var wave = new Wave
{
Id = Guid.NewGuid(),
WarehouseId = config.WarehouseId,
OrderCount = pendingOrders.Count,
CreatedAt = DateTime.UtcNow
};
foreach (var zoneGroup in ordersByZone)
{
var pickList = GeneratePickList(
zoneGroup.Key, zoneGroup.ToList());
wave.PickLists.Add(pickList);
pickList.OptimizedPath =
OptimizePickPath(pickList.Items);
}
var availablePickers = await _warehouseRepo
.GetAvailablePickersAsync(config.WarehouseId);
AssignPickers(wave.PickLists, availablePickers);
await _waveRepo.SaveAsync(wave);
return wave;
}
private List<PickListItem> GeneratePickList(
PickZone zone, List<Order> orders)
{
return orders
.SelectMany(o => o.Items
.Where(i =>
GetBinLocation(i.Sku).Zone == zone))
.GroupBy(i => new
{
i.Sku,
BinLocation = GetBinLocation(i.Sku)
})
.Select(g => new PickListItem
{
Sku = g.Key.Sku,
BinLocation = g.Key.BinLocation.Code,
TotalQuantity = g.Sum(i =>
i.Quantity - i.PickedQuantity),
OrderIds = g.Select(i => i.OrderId)
.Distinct().ToList(),
IsPickAndPack = g.Count() == 1
}).ToList();
}
private List<Point> OptimizePickPath(
List<PickListItem> items)
{
var points = items
.Select(i => i.BinLocation.ToPoint()).ToList();
var optimizedPath = new List<Point>();
var remaining = new HashSet<Point>(points);
var current = points.First();
while (remaining.Any())
{
var nearest = remaining
.OrderBy(p => Distance(current, p))
.First();
optimizedPath.Add(nearest);
remaining.Remove(nearest);
current = nearest;
}
return optimizedPath;
}
}
Pack Station Workflow
C#
public class PackStationService
{
public async Task<PackingInstructions>
GetPackingInstructionsAsync(Guid orderId)
{
var order = await _orderRepo.GetByIdAsync(orderId);
var items = await _inventoryClient
.GetPickedItemsAsync(orderId);
return new PackingInstructions
{
OrderId = orderId,
Items = items.Select(i => new PackItem
{
Sku = i.Sku,
ProductName = i.ProductName,
Quantity = i.QuantityPicked,
ImageUrl = i.ProductImageUrl,
IsFragile = i.IsFragile,
IsHazmat = i.IsHazmat,
RequiresColdPack = i.RequiresColdPack
}).ToList(),
RecommendedBoxSize =
CalculateOptimalBox(items),
SpecialInstructions =
GenerateSpecialInstructions(items),
PackingSlip =
await GeneratePackingSlipAsync(order),
ShippingLabel = await _shippingService
.GetLabelAsync(orderId)
};
}
public async Task ConfirmPackAsync(
PackConfirmation confirmation)
{
var scanResults = new List<ScanResult>();
foreach (var barcode in confirmation.ScannedBarcodes)
{
var expected = confirmation.ExpectedItems
.FirstOrDefault(i => i.Barcode == barcode);
scanResults.Add(new ScanResult
{
Barcode = barcode,
IsValid = expected != null,
Sku = expected?.Sku
});
}
var missedItems = confirmation.ExpectedItems
.Where(e => !confirmation.ScannedBarcodes
.Contains(e.Barcode))
.ToList();
if (missedItems.Any())
throw new PackVerificationException(
$"Missing items: {string.Join(", ",
missedItems.Select(i => i.Sku))}");
var expectedWeight = confirmation.ExpectedItems
.Sum(i => i.Weight * i.Quantity);
var variance =
Math.Abs(confirmation.ActualWeight - expectedWeight)
/ expectedWeight;
if (variance > 0.15)
throw new WeightVerificationException(
expectedWeight, confirmation.ActualWeight);
await _shippingService.CreateShipmentFromPackAsync(
confirmation.OrderId,
confirmation.BoxDimensions,
confirmation.ActualWeight);
}
}
11. Demand Forecasting with Machine Learning
Demand forecasting is the intelligence layer of the supply chain. Accurate forecasts enable proactive inventory positioning, reduce stockouts, minimize overstock, and optimize procurement cycles. Our system uses a combination of statistical methods and machine learning models to generate item-level demand forecasts at each warehouse location.
Forecasting Architecture
Forecasting Engine Implementation
C#
public class DemandForecastingEngine
{
private readonly IFeatureStore _featureStore;
private readonly IModelRegistry _modelRegistry;
private readonly IForecastRepository _forecastRepo;
public async Task<ForecastResult> GenerateForecastAsync(
ForecastRequest request)
{
var features = await _featureStore.GetFeaturesAsync(
request.Sku,
request.WarehouseId,
request.HorizonDays);
var model = await _modelRegistry.GetBestModelAsync(
request.Sku, request.WarehouseId);
var predictions = model switch
{
XGBoostModel xgb =>
await xgb.PredictAsync(features),
LSTMModel lstm =>
await lstm.PredictAsync(features),
ExponentialSmoothing ets =>
await ets.PredictAsync(features),
EnsembleModel ens =>
await ens.PredictAsync(features),
_ => throw new NotSupportedException()
};
var safetyStock = CalculateSafetyStock(
predictions.MeanDemand,
predictions.StandardDeviation,
request.LeadTimeDays,
request.ServiceLevelTarget);
var reorderPoint =
(predictions.MeanDemand * request.LeadTimeDays)
+ safetyStock;
var result = new ForecastResult
{
Sku = request.Sku,
WarehouseId = request.WarehouseId,
ModelUsed = model.ModelName,
ModelAccuracy = model.LastAccuracy,
DailyForecasts = predictions.DailyValues,
TotalDemandForecast = predictions.Sum,
SafetyStock = safetyStock,
ReorderPoint = reorderPoint,
ConfidenceInterval = new ConfidenceInterval
{
Lower = predictions.P10,
Upper = predictions.P90
},
GeneratedAt = DateTime.UtcNow
};
await _forecastRepo.SaveAsync(result);
return result;
}
private int CalculateSafetyStock(
double meanDemand,
double stdDevDemand,
int leadTimeDays,
double serviceLevel)
{
var zScore = serviceLevel switch
{
0.90 => 1.28,
0.95 => 1.65,
0.99 => 2.33,
_ => 1.65
};
var sigmaLT =
stdDevDemand * Math.Sqrt(leadTimeDays);
return (int)Math.Ceiling(zScore * sigmaLT);
}
}
public class ModelRetrainingPipeline
{
public async Task RetrainModelsAsync()
{
var skuLocations = await _forecastRepo
.GetSkuLocationsWithHistoryAsync(minDays: 90);
foreach (var (sku, warehouseId) in skuLocations)
{
var historicalData = await _featureStore
.GetHistoricalFeaturesAsync(
sku, warehouseId, months: 12);
var trainTestSplit =
SplitData(historicalData, testRatio: 0.2);
var models = new List<TrainedModel>
{
await TrainXGBoostAsync(trainTestSplit),
await TrainLSTMAsync(trainTestSplit),
await TrainETSAsync(trainTestSplit)
};
var bestModel = models
.OrderBy(m =>
m.MeanAbsolutePercentageError)
.First();
await _modelRegistry.RegisterModelAsync(
sku, warehouseId, bestModel);
_logger.LogInformation(
"Retrained models for {Sku} at " +
"{Warehouse}. Best: {Model} " +
"(MAPE: {MAPE:P2})",
sku, warehouseId,
bestModel.ModelName,
bestModel.MeanAbsolutePercentageError);
}
}
}
Forecast Accuracy Metrics
| Metric | Formula | Target | Current |
|---|---|---|---|
| MAPE | mean(|actual - forecast| / actual) | < 20% | 17.3% |
| Bias | mean(forecast - actual) / mean(actual) | -5% to +5% | +2.1% |
| SKU-Location Accuracy | 1 - MAPE | > 80% | 82.7% |
| Stockout Rate | stockout_days / total_days | < 2% | 1.4% |
| Overstock Rate | overstock_cost / total_inv_cost | < 10% | 8.2% |
12. Procurement Automation & Supplier Management
Procurement is the upstream supply chain — acquiring the raw materials and finished goods that flow through the rest of the system. An automated procurement system reduces manual effort, improves supplier relationships, optimizes purchasing costs, and ensures timely replenishment.
Supplier Management
C#
public class SupplierPerformanceScorer
{
public async Task<SupplierScorecard> CalculateScoreAsync(
Guid supplierId, DateRange period)
{
var orders = await _poRepo.GetCompletedOrdersAsync(
supplierId, period);
var metrics = new SupplierMetrics
{
OnTimeDeliveryRate = orders.Count(o =>
o.ActualDeliveryDate <= o.ExpectedDeliveryDate)
/ (double)orders.Count,
QualityAcceptanceRate = orders
.Sum(o => o.ItemsAccepted) /
(double)orders.Sum(o => o.ItemsReceived),
AverageLeadTimeDays = orders
.Average(o =>
(o.ActualDeliveryDate - o.OrderDate).Days),
LeadTimeVariability = CalculateStdDev(
orders.Select(o =>
(o.ActualDeliveryDate - o.OrderDate).Days)),
PriceCompetitiveness =
await CalculatePriceIndexAsync(
supplierId, orders),
ResponsivenessScore =
await CalculateResponseTimeAsync(
supplierId, period),
TotalOrderValue = orders.Sum(o => o.TotalAmount),
OrderCount = orders.Count,
DefectRate = 1.0 - (orders.Sum(o =>
o.ItemsAccepted) /
(double)Math.Max(1,
orders.Sum(o => o.ItemsReceived)))
};
metrics.CompositeScore =
(metrics.OnTimeDeliveryRate * 30) +
(metrics.QualityAcceptanceRate * 25) +
(Math.Max(0, 1 - metrics.DefectRate) * 20) +
(metrics.PriceCompetitiveness * 15) +
(metrics.ResponsivenessScore * 10);
return new SupplierScorecard
{
SupplierId = supplierId,
Period = period,
Metrics = metrics,
Rating = metrics.CompositeScore switch
{
>= 90 => SupplierRating.Excellent,
>= 75 => SupplierRating.Good,
>= 60 => SupplierRating.Average,
>= 40 => SupplierRating.Below,
_ => SupplierRating.Poor
}
};
}
}
Automated Replenishment Trigger
C#
public class ReplenishmentEngine
{
private readonly IInventoryRepository _inventoryRepo;
private readonly IForecastRepository _forecastRepo;
private readonly ISupplierRepository _supplierRepo;
private readonly IPurchaseOrderService _poService;
public async Task CheckAndGenerateReplenishmentAsync()
{
var lowStockItems = await _inventoryRepo
.GetItemsBelowReorderPointAsync();
var itemsBySupplier = lowStockItems
.GroupBy(i => _supplierRepo
.GetPreferredSupplier(i.Sku).Id);
foreach (var (supplierId, items) in itemsBySupplier)
{
var supplier =
await _supplierRepo.GetByIdAsync(supplierId);
var openPos = await _poService
.GetOpenPurchaseOrdersAsync(supplierId);
foreach (var item in items)
{
var existingCoverage = openPos
.Where(po => po.Items
.Any(i => i.Sku == item.Sku))
.Sum(po => po.Items
.First(i => i.Sku == item.Sku)
.QuantityOrdered);
var deficit = item.QuantityOnHand
+ existingCoverage - item.ReorderPoint;
if (deficit >= 0) continue;
var orderQuantity =
CalculateEconomicOrderQuantity(
item.Sku,
Math.Abs(deficit),
supplier.LeadTimeDays);
await _poService
.AddToExistingOrNewPurchaseOrderAsync(
supplierId,
item.Sku,
orderQuantity);
}
}
}
private int CalculateEconomicOrderQuantity(
string sku, int demand, int leadTimeDays)
{
var annualDemand =
demand * 365.0 / leadTimeDays;
var orderingCost = 50.0;
var unitCost = GetUnitCost(sku);
var holdingCostRate = 0.25;
var holdingCost = unitCost * holdingCostRate;
var eoq = Math.Sqrt(
2 * annualDemand * orderingCost / holdingCost);
return (int)Math.Ceiling(
Math.Max(eoq, demand * 1.2));
}
}
13. Purchase Order Workflow
Purchase orders follow a structured approval workflow with role-based access control. The workflow ensures that large purchases receive appropriate approval while routine replenishment proceeds quickly. We implement this as a configurable state machine that supports different approval thresholds per organization.
PO State Machine
PO Service Implementation
C#
public class PurchaseOrderService : IPurchaseOrderService
{
private readonly IPurchaseOrderRepository _poRepo;
private readonly IApprovalWorkflowEngine _approvalEngine;
private readonly IEventPublisher _events;
public async Task<PurchaseOrderResponse>
CreatePurchaseOrderAsync(CreatePoCommand command)
{
var supplier = await _supplierRepo.GetByIdAsync(
command.SupplierId);
var po = PurchaseOrder.Create(
command.TenantId,
command.SupplierId,
command.WarehouseId,
command.Items.Select(i => PoItem.Create(
i.Sku,
i.ProductName,
i.Quantity,
supplier.GetPrice(i.Sku, i.Quantity)
)).ToList());
po.SetExpectedDeliveryDate(
CalculateExpectedDelivery(
supplier, command.ExpectedDeliveryDate));
await _poRepo.SaveAsync(po);
if (po.TotalAmount <= supplier.AutoApprovalThreshold)
{
await ApprovePurchaseOrderAsync(
po.Id, null,
"Auto-approved: below threshold");
}
return po.ToResponse();
}
public async Task ApprovePurchaseOrderAsync(
Guid poId, Guid? approvedBy, string notes)
{
var po = await _poRepo.GetByIdAsync(poId);
if (po == null)
throw new PurchaseOrderNotFoundException(poId);
if (approvedBy != null)
{
var approver = await _userRepo.GetByIdAsync(
approvedBy);
if (!await _approvalEngine.CanApproveAsync(
approver, po.TotalAmount))
{
throw new InsufficientApprovalAuthorityException(
approver.Id, po.TotalAmount);
}
}
po.Approve(approvedBy, notes);
await _poRepo.SaveAsync(po);
await _events.PublishAsync(
new PurchaseOrderApprovedEvent
{
PurchaseOrderId = po.Id,
SupplierId = po.SupplierId,
TotalAmount = po.TotalAmount,
Items = po.Items.Select(i => new PoItemEvent
{
Sku = i.Sku,
Quantity = i.QuantityOrdered
}).ToList()
});
}
public async Task ReceiveItemsAsync(
Guid poId,
List<ReceiveItemRequest> receivedItems,
Guid receivedBy)
{
var po = await _poRepo.GetByIdAsync(poId);
foreach (var received in receivedItems)
{
var poItem = po.Items
.First(i => i.Sku == received.Sku);
var inspectionResult =
await PerformInspectionAsync(
received.Sku,
received.Quantity,
received.Condition);
poItem.Receive(
received.Quantity,
inspectionResult.Accepted,
inspectionResult.Rejected,
inspectionResult.Reason);
if (inspectionResult.Accepted > 0)
{
await _inventoryClient.ReceiveStockAsync(
received.Sku,
po.WarehouseId,
inspectionResult.Accepted,
received.LotNumber,
received.ExpiryDate,
poId);
}
}
po.CheckCompletion();
await _poRepo.Save(po);
if (po.Status ==
PurchaseOrderStatus.Received)
{
await _events.PublishAsync(
new PurchaseOrderFullyReceivedEvent
{
PurchaseOrderId = po.Id,
WarehouseId = po.WarehouseId,
ReceivedAt = DateTime.UtcNow
});
}
}
}
14. Receiving, Putaway & Cycle Counting
Receiving is the process of accepting inbound shipments at a warehouse, verifying their contents against purchase orders, performing quality inspection, and placing items into storage locations. Cycle counting is the ongoing process of verifying inventory accuracy through systematic partial counts.
Putaway Strategy
C#
public class PutawayEngine
{
public async Task<PutawayAssignment> AssignBinAsync(
string sku, int quantity, Guid warehouseId)
{
var item = await _itemRepo.GetBySkuAsync(sku);
var warehouse =
await _warehouseRepo.GetByIdAsync(warehouseId);
var zones = await _zoneRepo.GetZonesAsync(warehouseId);
var targetZone = SelectZone(item, zones);
var availableBins = await _binRepo
.GetAvailableBinsAsync(targetZone.Id,
item.Requirements);
var bestBin = availableBins
.OrderBy(b => b.CurrentUtilization
/ b.MaxCapacity)
.ThenBy(b => b.DistanceFromDock)
.FirstOrDefault();
if (bestBin == null)
throw new NoAvailableBinException(
targetZone.Id, item.Requirements);
return new PutawayAssignment
{
Sku = sku,
Quantity = quantity,
BinLocation = bestBin,
Zone = targetZone,
Instructions = GeneratePutawayInstructions(
item, bestBin)
};
}
private WarehouseZone SelectZone(
Item item,
List<WarehouseZone> zones)
{
return item switch
{
{ IsPerishable: true } =>
zones.First(z =>
z.Type == ZoneType.ColdStorage),
{ IsHazmat: true } =>
zones.First(z =>
z.Type == ZoneType.Hazmat),
{ IsHighVelocity: true } =>
zones.First(z =>
z.Type == ZoneType.PickFace),
{ IsOversized: true } =>
zones.First(z =>
z.Type == ZoneType.BulkStorage),
_ =>
zones.First(z =>
z.Type == ZoneType.General)
};
}
}
public class CycleCountService
{
public async Task<CycleCountTask>
GenerateCountTaskAsync(
Guid warehouseId,
CycleCountStrategy strategy)
{
var locations = strategy switch
{
CycleCountStrategy.ABC =>
await GetABCClassifiedLocationsAsync(
warehouseId),
CycleCountStrategy.HighValue =>
await GetHighValueLocationsAsync(
warehouseId),
CycleCountStrategy.HighDiscrepancy =>
await GetHighDiscrepancyLocationsAsync(
warehouseId),
CycleCountStrategy.Random =>
await GetRandomLocationsAsync(
warehouseId, count: 50),
_ => throw new ArgumentException(
$"Unknown strategy: {strategy}")
};
return new CycleCountTask
{
WarehouseId = warehouseId,
Strategy = strategy,
Locations = locations.Select(l =>
new CountLocation
{
BinCode = l.Code,
ExpectedSku = l.CurrentSku,
ExpectedQuantity = l.CurrentQuantity,
Zone = l.Zone
}).ToList(),
AssignedTo =
await GetAvailableCounterAsync(warehouseId)
};
}
public async Task<CountResult>
ProcessCountResultAsync(
Guid taskId,
List<CountConfirmation> confirmations)
{
var discrepancies = new List<Discrepancy>();
var task = await _taskRepo.GetByIdAsync(taskId);
foreach (var confirmation in confirmations)
{
var expected = task.Locations
.First(l =>
l.BinCode == confirmation.BinCode);
if (confirmation.CountedQuantity !=
expected.ExpectedQuantity)
{
var discrepancy = new Discrepancy
{
BinCode = confirmation.BinCode,
Sku = expected.ExpectedSku,
ExpectedQuantity =
expected.ExpectedQuantity,
ActualQuantity =
confirmation.CountedQuantity,
Variance =
confirmation.CountedQuantity
- expected.ExpectedQuantity
};
discrepancies.Add(discrepancy);
if (Math.Abs(discrepancy.Variance) <= 5)
{
await _inventoryClient.AdjustQuantityAsync(
expected.ExpectedSku,
task.WarehouseId,
confirmation.BinCode,
discrepancy.Variance,
"Cycle count adjustment");
}
else
{
await _events.PublishAsync(
new InventoryDiscrepancyEvent
{
BinCode = confirmation.BinCode,
Sku = expected.ExpectedSku,
Variance = discrepancy.Variance,
Severity =
Math.Abs(discrepancy.Variance) > 20
? DiscrepancySeverity.Critical
: DiscrepancySeverity.Warning
});
}
}
}
return new CountResult
{
TaskId = taskId,
TotalLocations = confirmations.Count,
Discrepancies = discrepancies,
AccuracyRate = 1.0 - (discrepancies.Count
/ (double)confirmations.Count)
};
}
}
ABC Classification for Cycle Counting
| Class | % of SKUs | % of Revenue | Count Frequency | Tolerance |
|---|---|---|---|---|
| A (High Value) | 20% | 80% | Weekly | 0 units |
| B (Medium Value) | 30% | 15% | Monthly | 1 unit |
| C (Low Value) | 50% | 5% | Quarterly | 2 units |
15. Reorder Point Calculation & Safety Stock Optimization
Reorder point (ROP) and safety stock calculations are the mathematical foundation of inventory management. Getting these right means never running out of stock while also not over-investing in inventory. Our system uses dynamic calculations that adapt to changing demand patterns and lead times.
Reorder Point Formula
Safety Stock = Z x sigma_d x sqrt(Lead Time)
Where: Z = Z-score for desired service level, sigma_d = standard deviation of daily demand, Lead Time = supplier lead time in days
C#
public class InventoryOptimizer
{
private readonly IInventoryRepository _inventoryRepo;
private readonly IForecastRepository _forecastRepo;
private readonly IPurchaseOrderRepository _poRepo;
public async Task<InventoryRecommendation>
CalculateOptimalLevelsAsync(
string sku, Guid warehouseId)
{
var forecast = await _forecastRepo
.GetLatestForecastAsync(sku, warehouseId);
var historicalStats =
await GetHistoricalDemandStatsAsync(
sku, warehouseId, months: 12);
var supplier = await GetPreferredSupplierAsync(sku);
var leadTimeStats =
await GetLeadTimeStatsAsync(
supplier.Id, sku, months: 6);
var serviceLevels = new[] { 0.90, 0.95, 0.99 };
var recommendations = serviceLevels.Select(sl =>
{
var zScore = GetZScore(sl);
var safetyStock = (int)Math.Ceiling(
zScore
* historicalStats.StdDevDailyDemand
* Math.Sqrt(
leadTimeStats.MeanLeadTimeDays));
var rop = (int)Math.Ceiling(
historicalStats.MeanDailyDemand
* leadTimeStats.MeanLeadTimeDays)
+ safetyStock;
var maxStock = rop + (int)Math.Ceiling(
historicalStats.MeanDailyDemand
* leadTimeStats.MeanLeadTimeDays);
var annualHoldingCost = maxStock
* GetUnitCost(sku) * 0.25;
var annualOrderCost =
(historicalStats.MeanDailyDemand * 365
/ Math.Max(1,
maxStock - safetyStock)) * 50;
var stockoutCost = (1 - sl)
* historicalStats.MeanDailyDemand
* 365 * GetUnitCost(sku) * 0.5;
return new ServiceLevelRecommendation
{
ServiceLevel = sl,
SafetyStock = safetyStock,
ReorderPoint = rop,
MaxInventoryLevel = maxStock,
EstimatedAnnualCost =
annualHoldingCost
+ annualOrderCost
+ stockoutCost,
ProbabilityOfStockout = 1 - sl
};
}).ToList();
var optimal = recommendations
.OrderBy(r =>
r.EstimatedAnnualCost).First();
return new InventoryRecommendation
{
Sku = sku,
WarehouseId = warehouseId,
CurrentStock = await GetCurrentStockAsync(
sku, warehouseId),
Recommendations = recommendations,
Optimal = optimal,
ReorderStatus =
await GetReorderStatusAsync(
sku, warehouseId,
optimal.ReorderPoint)
};
}
private double GetZScore(double serviceLevel) =>
serviceLevel switch
{
>= 0.99 => 2.326,
>= 0.975 => 1.96,
>= 0.95 => 1.645,
>= 0.90 => 1.282,
>= 0.85 => 1.036,
_ => 1.645
};
public async Task<DynamicReorderCheck>
CheckDynamicReorderPointsAsync()
{
var allSkuLocations = await _inventoryRepo
.GetAllSkuLocationsAsync();
var alerts = new List<ReorderAlert>();
foreach (var (sku, warehouseId) in allSkuLocations)
{
var rec = await CalculateOptimalLevelsAsync(
sku, warehouseId);
var currentStock = rec.CurrentStock;
if (currentStock <= rec.Optimal.ReorderPoint)
{
alerts.Add(new ReorderAlert
{
Sku = sku,
WarehouseId = warehouseId,
CurrentStock = currentStock,
ReorderPoint =
rec.Optimal.ReorderPoint,
RecommendedOrderQty =
rec.Optimal.MaxInventoryLevel
- currentStock,
Urgency = currentStock == 0
? AlertUrgency.Critical
: currentStock <
rec.Optimal.SafetyStock
? AlertUrgency.High
: AlertUrgency.Normal
});
}
}
return new DynamicReorderCheck
{
CheckedAt = DateTime.UtcNow,
TotalSkuLocations = allSkuLocations.Count,
Alerts = alerts,
CriticalAlerts = alerts.Count(a =>
a.Urgency == AlertUrgency.Critical)
};
}
}
Safety Stock Optimization Scenarios
| Scenario | Service Level | Safety Stock | Annual Cost | Stockout Risk |
|---|---|---|---|---|
| Cost Optimized | 90% | 42 units | $12,500 | 10% of days |
| Balanced (Recommended) | 95% | 56 units | $14,200 | 5% of days |
| Service Optimized | 99% | 82 units | $18,900 | 1% of days |
| Critical Item | 99.9% | 112 units | $25,400 | 0.1% of days |
16. Route Optimization & Last-Mile Delivery
Last-mile delivery is the most expensive segment of the supply chain, accounting for up to 53% of total shipping costs. Route optimization reduces fuel consumption, driver hours, and delivery times while improving customer satisfaction through accurate delivery windows.
Route Optimization Architecture
VRP Solver Implementation
C#
public class RouteOptimizer
{
private readonly IMapService _mapService;
private readonly ITrafficService _trafficService;
public async Task<OptimizedRoutes> OptimizeRoutesAsync(
RouteOptimizationRequest request)
{
var deliveries = request.Deliveries;
var vehicles = request.Vehicles;
var allLocations = new List<Location>
{ request.Depot };
allLocations.AddRange(
deliveries.Select(d => d.DeliveryAddress));
var matrix = await _mapService
.GetDistanceMatrixAsync(allLocations);
var initialSolution = SolveWithNearestNeighbor(
deliveries, vehicles, matrix);
var optimized = ImproveWithLocalSearch(
initialSolution, matrix,
request.Constraints);
var final = RefineWithSimulatedAnnealing(
optimized, matrix,
request.Constraints,
temperature: 1000,
coolingRate: 0.995,
iterations: 10000);
var feasible = ApplyTimeWindowConstraints(
final, deliveries, matrix);
var detailedRoutes = new List<DetailedRoute>();
foreach (var route in feasible.Routes)
{
var detailed = await GenerateDetailedRouteAsync(
route, request.Depot, matrix);
detailedRoutes.Add(detailed);
}
return new OptimizedRoutes
{
Routes = detailedRoutes,
TotalDistance = feasible.TotalDistance,
TotalTime = feasible.TotalTime,
TotalCost = CalculateCost(feasible, vehicles),
UtilizationRate =
feasible.TotalCapacityUsed
/ vehicles.Sum(v => v.MaxCapacity),
SavingsVsUnoptimized = CalculateSavings(
initialSolution, feasible)
};
}
private VehicleRouteSolution SolveWithNearestNeighbor(
List<Delivery> deliveries,
List<Vehicle> vehicles,
DistanceMatrix matrix)
{
var unassigned = new HashSet<int>(
Enumerable.Range(0, deliveries.Count));
var routes = new List<Route>();
foreach (var vehicle in
vehicles.Where(v => v.IsAvailable))
{
var route = new Route
{ VehicleId = vehicle.Id };
var currentLoad = 0;
var currentLocation = 0;
while (unassigned.Any())
{
var nearest = unassigned
.OrderBy(i =>
matrix[currentLocation, i + 1])
.ThenBy(i =>
deliveries[i]
.DeliveryWindow.Start)
.First();
var delivery = deliveries[nearest];
var newLoad = currentLoad
+ delivery.Weight;
if (newLoad > vehicle.MaxCapacity)
break;
route.Stops.Add(new Stop
{
DeliveryIndex = nearest,
ArrivalTime = CalculateArrivalTime(
route, currentLocation,
nearest + 1, matrix),
DepartureTime =
CalculateDepartureTime(
route, nearest, delivery),
LoadAfterStop = newLoad
});
currentLoad = newLoad;
currentLocation = nearest + 1;
unassigned.Remove(nearest);
}
if (route.Stops.Any())
{
route.ReturnTime = CalculateReturnTime(
route, currentLocation, matrix);
routes.Add(route);
}
}
return new VehicleRouteSolution
{
Routes = routes,
UnassignedDeliveries = unassigned
.Select(i => deliveries[i]).ToList(),
TotalDistance = routes.Sum(r =>
CalculateRouteDistance(r, matrix)),
TotalCapacityUsed = routes.Sum(r =>
r.Stops.Last().LoadAfterStop)
};
}
}
Last-Mile Delivery Constraints
| Constraint | Rule | Penalty if Violated |
|---|---|---|
| Vehicle Capacity (Weight) | Total weight <= vehicle max | Route rejected |
| Vehicle Capacity (Volume) | Total volume <= vehicle limit | Route rejected |
| Delivery Windows | Arrival within customer window | Soft: $25 penalty |
| Driver Hours of Service | Max 11 hours driving | Hard: route rejected |
| Break Requirements | 30-min break after 8 hours | Hard: inserted into route |
| Vehicle Range | Total distance <= fuel range | Hard: route rejected |
17. Returns Management (RMA)
Return management is a critical but often overlooked part of the supply chain. E-commerce return rates average 20-30%, and managing returns efficiently — from authorization through inspection, disposition, and refund — is essential for customer satisfaction and cost control.
RMA Workflow
RMA Service Implementation
C#
public class ReturnsService
{
public async Task<RmaResponse> CreateReturnAsync(
CreateReturnCommand command)
{
var order = await _orderRepo.GetByIdAsync(
command.OrderId);
var returnWindow =
await GetReturnWindowAsync(order);
if (DateTime.UtcNow >
order.DeliveredAt.AddDays(
returnWindow.Days))
throw new ReturnWindowExpiredException(
order.Id, returnWindow);
var returnEligibility =
CheckReturnEligibility(
order, command.Items);
var rma = ReturnAuthorization.Create(
command.OrderId,
command.CustomerId,
command.Items
.Where(i =>
returnEligibility[i.Sku].Eligible)
.Select(i => new ReturnItem
{
Sku = i.Sku,
Quantity = i.Quantity,
Reason = i.Reason,
ReasonCode = i.ReasonCode
}).ToList(),
CalculateRefundAmount(
order, command.Items));
var returnLabel = await _shippingService
.CreateReturnLabelAsync(
rma.Id,
order.ShippingAddress,
await GetNearestReturnCenterAsync(
order.ShippingAddress));
rma.ReturnLabelUrl = returnLabel.LabelUrl;
rma.ReturnTrackingNumber =
returnLabel.TrackingNumber;
await _rmaRepo.SaveAsync(rma);
return rma.ToResponse();
}
public async Task<InspectionResult>
ProcessReturnAsync(
Guid rmaId,
List<InspectionItem> inspections)
{
var rma = await _rmaRepo.GetByIdAsync(rmaId);
foreach (var inspection in inspections)
{
var rmaItem = rma.Items
.First(i => i.Sku == inspection.Sku);
var disposition =
DetermineDisposition(inspection);
rmaItem.Disposition = disposition;
rmaItem.InspectedBy =
inspection.InspectedBy;
rmaItem.InspectedAt = DateTime.UtcNow;
switch (disposition)
{
case Disposition.Restock:
await _inventoryClient
.ReceiveStockAsync(
rmaItem.Sku,
rma.WarehouseId,
inspection.Quantity,
lotNumber: null,
expiryDate: null,
rmaId);
break;
case Disposition.Refurbish:
await _warehouseClient
.CreateRefurbishTaskAsync(
rmaItem.Sku,
inspection.Quantity);
break;
case Disposition.Dispose:
await _warehouseClient
.CreateDisposalTaskAsync(
rmaItem.Sku,
inspection.Quantity);
break;
case Disposition.Donate:
await _warehouseClient
.CreateDonationTaskAsync(
rmaItem.Sku,
inspection.Quantity);
break;
}
}
var refundAmount =
CalculateRefundBasedOnDisposition(
rma, inspections);
await _paymentClient.ProcessRefundAsync(
rma.OrderId, refundAmount, rma.Id);
rma.RefundAmount = refundAmount;
rma.Status = RmaStatus.Completed;
await _rmaRepo.SaveAsync(rma);
return new InspectionResult
{
RmaId = rmaId,
Dispositions = inspections.ToDictionary(
i => i.Sku,
i => DetermineDisposition(i)),
RefundAmount = refundAmount
};
}
private Disposition DetermineDisposition(
InspectionItem item)
{
if (item.Condition == ItemCondition.Unused
&& item.HasOriginalPackaging)
return Disposition.Restock;
if (item.Condition ==
ItemCondition.MinorDefect
&& item.Repairable)
return Disposition.Refurbish;
if (item.Condition == ItemCondition.Damaged
&& item.DisposalCost < item.Value)
return Disposition.Dispose;
if (item.Condition == ItemCondition.Damaged
&& item.DisposalCost >= item.Value)
return Disposition.Donate;
return Disposition.Dispose;
}
}
Return Reason Analysis
| Return Reason | % of Returns | Avg Refund | Disposition | Prevention Strategy |
|---|---|---|---|---|
| Wrong size/fit | 35% | 100% | Restock (80%) | Improve size charts, AR try-on |
| Not as described | 22% | 100% | Restock (90%) | Better product photos |
| Quality issue | 18% | 100% | Refurbish (60%) | Supplier quality improvement |
| Changed mind | 12% | 100% | Restock (95%) | Extended decision period |
| Damaged in transit | 8% | 100% | Dispose (70%) | Better packaging |
| Defective product | 5% | 100% | Refurbish (50%) | Pre-ship QC testing |
18. Multi-Carrier Shipping & Rate Shopping
Using multiple carriers gives the supply chain flexibility, cost optimization, and resilience. Rate shopping — comparing shipping rates across carriers in real-time for each shipment — can reduce shipping costs by 15-25%. Our system integrates with 20+ carriers and uses intelligent carrier selection algorithms.
Rate Shopping Engine
C#
public class RateShoppingEngine
{
private readonly IEnumerable<ICarrierAdapter> _carriers;
private readonly ICarrierPerformanceStore _perfStore;
private readonly ILogger<RateShoppingEngine> _logger;
public async Task<List<RateQuoteResponse>>
GetRateQuotesAsync(RateShopRequest request)
{
var tasks = _carriers
.Where(c => c.IsServiceable(
request.DestinationCountry))
.Select(async carrier =>
{
try
{
return await carrier.GetRatesAsync(
new RateRequest
{
Origin = request.OriginAddress,
Destination =
request.DestinationAddress,
Packages = request.Packages,
ShipmentDate = request.ShipmentDate,
DeclaredValue = request.DeclaredValue,
IsHazmat = request.ContainsHazmat,
IsFragile = request.ContainsFragile
});
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"Failed to get rates from {Carrier}",
carrier.CarrierCode);
return (RateQuoteResponse?)null;
}
});
var results = await Task.WhenAll(tasks);
var validQuotes = results
.Where(q => q != null).ToList();
foreach (var quote in validQuotes)
{
var perf = await _perfStore.GetPerformanceAsync(
quote.CarrierCode,
request.DestinationZip);
quote.OnTimeRate =
perf?.OnTimeDeliveryRate ?? 0.95;
quote.DamageRate =
perf?.DamageRate ?? 0.01;
quote.CustomerRating =
perf?.CustomerSatisfaction ?? 4.0;
}
return validQuotes;
}
public CarrierSelection SelectOptimalCarrier(
List<RateQuoteResponse> quotes,
CarrierSelectionPolicy policy)
{
return policy.Mode switch
{
SelectionMode.LowestCost =>
quotes.OrderBy(q => q.TotalCost).First(),
SelectionMode.FastestDelivery =>
quotes.OrderBy(q => q.DaysInTransit)
.ThenBy(q => q.TotalCost).First(),
SelectionMode.BestValue =>
quotes.OrderBy(q =>
q.TotalCost
* (1 - q.OnTimeRate * 0.3))
.ThenBy(q => q.DaysInTransit)
.First(),
SelectionMode.MostReliable =>
quotes.OrderByDescending(q => q.OnTimeRate)
.ThenBy(q => q.TotalCost).First(),
SelectionMode.CarrierPreference =>
quotes.Where(q => policy.PreferredCarriers
.Contains(q.CarrierCode))
.OrderBy(q => q.TotalCost)
.FirstOrDefault()
?? quotes.OrderBy(q => q.TotalCost)
.First(),
SelectionMode.CostWithServiceFloor =>
quotes.Where(q =>
q.OnTimeRate >= policy.MinOnTimeRate)
.OrderBy(q => q.TotalCost).First(),
_ => quotes.OrderBy(q => q.TotalCost).First()
};
}
}
public class CarrierSelectionPolicy
{
public SelectionMode Mode { get; set; }
public decimal MaxBudget { get; set; }
public double MinOnTimeRate { get; set; } = 0.95;
public List<string> PreferredCarriers { get; set; }
public List<string> BlockedCarriers { get; set; }
public Dictionary<string, decimal>
CarrierSurcharges { get; set; }
}
public enum SelectionMode
{
LowestCost,
FastestDelivery,
BestValue,
MostReliable,
CarrierPreference,
CostWithServiceFloor
}
Carrier Comparison Matrix
| Carrier | Domestic Rate (lb) | International Rate (lb) | Avg Transit | On-Time % | API Quality |
|---|---|---|---|---|---|
| FedEx Express | $8.50 | $25.00 | 1-3 days | 97.2% | Excellent |
| UPS Ground | $6.20 | $22.00 | 2-5 days | 96.8% | Excellent |
| USPS Priority | $5.80 | $18.00 | 2-3 days | 94.5% | Good |
| DHL Express | $9.10 | $20.00 | 1-4 days | 95.1% | Excellent |
| FedEx Ground | $5.50 | N/A | 3-7 days | 96.1% | Excellent |
| Regional Carriers | $4.20 | N/A | 1-3 days | 93.0% | Moderate |
19. Customs, International Shipping & Incoterms
International shipping introduces customs clearance, duties and taxes, trade compliance, and documentation requirements that can delay or block shipments if not handled correctly. Our system automates customs classification, duty calculation, and document generation to ensure smooth cross-border trade.
HS Code Classification
C#
public class CustomsClassificationService
{
private readonly IHsCodeRepository _hsCodeRepo;
private readonly IMlClassifier _mlClassifier;
private readonly ITradeComplianceService _compliance;
public async Task<ClassificationResult>
ClassifyProductAsync(
ProductClassificationRequest request)
{
var ruleBased = await _hsCodeRepo
.FindByKeywordsAsync(
request.ProductName, request.Category);
if (ruleBased.Count == 1)
{
return new ClassificationResult
{
HsCode = ruleBased[0].HsCode,
Description = ruleBased[0].Description,
DutyRate = await GetDutyRateAsync(
ruleBased[0].HsCode,
request.DestinationCountry),
Confidence = 0.95,
Method =
ClassificationMethod.RuleBased
};
}
var mlResult = await _mlClassifier
.ClassifyAsync(new
{
request.ProductName,
request.Description,
request.Category,
request.PrimaryMaterial,
request.WeightKg,
request.Dimensions
});
var complianceCheck = await _compliance
.CheckExportControlAsync(
mlResult.HsCode,
request.DestinationCountry);
if (complianceCheck.IsRestricted)
throw new TradeComplianceException(
$"Product restricted for export " +
$"to {request.DestinationCountry}: " +
complianceCheck.Reason);
var dutyRate = await GetDutyRateAsync(
mlResult.HsCode,
request.DestinationCountry);
return new ClassificationResult
{
HsCode = mlResult.HsCode,
Description = mlResult.Description,
DutyRate = dutyRate,
ImportVAT = await GetImportVatAsync(
mlResult.HsCode,
request.DestinationCountry),
Confidence = mlResult.Confidence,
Method =
ClassificationMethod.MLClassifier,
RequiresLicense =
complianceCheck.RequiresLicense,
RestrictedCountries =
complianceCheck.RestrictedCountries
};
}
private async Task<DutyRate> GetDutyRateAsync(
string hsCode, string destinationCountry)
{
var rate = await _hsCodeRepo.GetDutyRateAsync(
hsCode, destinationCountry);
var ftaRate = await GetFtaRateAsync(
hsCode, destinationCountry);
if (ftaRate != null
&& ftaRate.Rate < rate.StandardRate)
{
return new DutyRate
{
Rate = ftaRate.Rate,
Agreement = ftaRate.FtaName,
RequiresCertificate =
ftaRate.RequiresOriginCertificate
};
}
return rate;
}
}
Incoterms Reference
| Incoterm | Seller Risk Ends | Buyer Risk Begins | Seller Pays Transport | Common Use |
|---|---|---|---|---|
| EXW (Ex Works) | Seller's premises | Seller's premises | No | Domestic, buyer arranges all |
| FOB (Free on Board) | Port of loading | Port of loading | To port | Sea freight, bulk cargo |
| CIF (Cost, Insurance, Freight) | Port of loading | Port of destination | To destination port | International sea freight |
| DDP (Delivered Duty Paid) | Buyer's premises | Buyer's premises | Full door-to-door | E-commerce, simplest for buyer |
| DAP (Delivered at Place) | Named destination | Named destination | To destination (no duty) | Large equipment, B2B |
International Shipment Processing
C#
public class InternationalShippingProcessor
{
public async Task<InternationalShipment>
ProcessInternationalShipmentAsync(Guid shipmentId)
{
var shipment = await _shipmentRepo
.GetByIdAsync(shipmentId);
var items = await _shipmentRepo
.GetItemsAsync(shipmentId);
var classifications =
new List<ClassificationResult>();
foreach (var item in items)
{
var classification = await _customsService
.ClassifyProductAsync(
new ProductClassificationRequest
{
ProductName = item.ProductName,
Description = item.Description,
Category = item.Category,
PrimaryMaterial = item.Material,
WeightKg = item.WeightKg
});
classifications.Add(classification);
}
var duties = await CalculateDutiesAsync(
classifications,
shipment.DestinationCountry);
var declaration = new CustomsDeclaration
{
ShipmentId = shipmentId,
Exporter = await GetExporterInfoAsync(
shipment.WarehouseId),
Importer = await GetImporterInfoAsync(
shipment.DestinationAddress),
Items = classifications
.Select((c, i) => new DeclarationItem
{
HsCode = c.HsCode,
Description = c.Description,
Quantity = items[i].Quantity,
UnitValue = items[i].UnitValue,
CountryOfOrigin =
items[i].CountryOfOrigin,
NetWeight = items[i].WeightKg
}).ToList(),
TotalValue = items.Sum(i =>
i.Quantity * i.UnitValue),
CurrencyCode = shipment.CurrencyCode,
Incoterms = shipment.Incoterms
};
var commercialInvoice = await _documentService
.GenerateCommercialInvoiceAsync(declaration);
var packingList = await _documentService
.GeneratePackingListAsync(
shipmentId, items);
await _complianceService
.ValidateExportAsync(declaration);
return new InternationalShipment
{
ShipmentId = shipmentId,
Declaration = declaration,
CommercialInvoice = commercialInvoice,
PackingList = packingList,
Duties = duties,
TotalLandedCost = CalculateLandedCost(
items, duties, shipment)
};
}
}
20. Document Management (BOL, Packing Slips, Customs Declarations)
Supply chain operations generate a massive volume of documents: bills of lading (BOL), packing slips, commercial invoices, customs declarations, certificates of origin, proof of delivery (POD), and more. Proper document management ensures compliance, enables auditing, and provides a paper trail for dispute resolution.
Document Generation Service
C#
public class DocumentGenerationService
{
private readonly IObjectStorage _storage;
private readonly IPdfGenerator _pdfGenerator;
private readonly IDocumentRepository _docRepo;
public async Task<BillOfLading>
GenerateBillOfLadingAsync(Guid shipmentId)
{
var shipment = await _shipmentRepo
.GetByIdAsync(shipmentId);
var items = await _shipmentRepo
.GetItemsAsync(shipmentId);
var warehouse = await _warehouseRepo
.GetByIdAsync(shipment.ShipFromWarehouseId);
var bol = new BillOfLading
{
ShipmentId = shipmentId,
BolNumber = GenerateBolNumber(),
Shipper = new Party
{
Name = warehouse.Name,
Address = warehouse.Address,
Contact = warehouse.ContactPhone,
TaxId = warehouse.TaxId
},
Consignee = new Party
{
Name = shipment.DestinationAddress.Name,
Address = shipment.DestinationAddress,
Contact = shipment.DestinationAddress.Phone
},
Carrier = new CarrierInfo
{
Name = shipment.CarrierCode,
SCAC = GetScacCode(shipment.CarrierCode),
ProNumber = shipment.TrackingNumber
},
Pieces = items.Select(i => new BOLPiece
{
Description = i.ProductName,
Quantity = i.Quantity,
Weight = i.WeightKg,
Dimensions = i.Dimensions,
HsCode = i.HsCode,
DeclaredValue = i.DeclaredValue,
HazmatClass = i.HazmatClass
}).ToList(),
FreightTerms = shipment.FreightTerms,
SpecialInstructions =
await GetSpecialInstructionsAsync(shipment),
CreatedAt = DateTime.UtcNow
};
var pdfBytes = await _pdfGenerator
.GenerateBolAsync(bol);
var storagePath =
$"documents/bol/{bol.BolNumber}.pdf";
await _storage.UploadAsync(
storagePath, pdfBytes,
"application/pdf");
bol.DocumentUrl = storagePath;
await _docRepo.SaveBolAsync(bol);
return bol;
}
public async Task<PackingSlip>
GeneratePackingSlipAsync(Guid orderId)
{
var order = await _orderRepo.GetByIdAsync(orderId);
var items = await _inventoryClient
.GetPickedItemsAsync(orderId);
var slip = new PackingSlip
{
OrderId = orderId,
OrderNumber = order.OrderNumber,
CustomerName =
order.ShippingAddress.Name,
ShipToAddress = order.ShippingAddress,
Items = items.Select(i =>
new PackingSlipItem
{
Sku = i.Sku,
ProductName = i.ProductName,
Quantity = i.QuantityPicked,
LotNumber = i.LotNumber,
ExpiryDate = i.ExpiryDate,
Barcode = i.Barcode
}).ToList(),
TotalItems = items.Sum(i => i.QuantityPicked),
TotalWeight = items.Sum(i =>
i.WeightKg * i.QuantityPicked),
GeneratedAt = DateTime.UtcNow
};
var pdfBytes = await _pdfGenerator
.GeneratePackingSlipAsync(slip);
var storagePath =
$"documents/packing-slips/{orderId}.pdf";
await _storage.UploadAsync(
storagePath, pdfBytes,
"application/pdf");
slip.DocumentUrl = storagePath;
return slip;
}
public async Task<CustomsDeclaration>
GenerateCustomsDeclarationAsync(
Guid shipmentId)
{
var shipment = await _shipmentRepo
.GetByIdAsync(shipmentId);
var items = await _shipmentRepo
.GetItemsAsync(shipmentId);
var declaration = new CustomsDeclaration
{
ShipmentId = shipmentId,
DeclarationNumber = GenerateDeclarationNumber(),
Exporter = await GetExporterInfoAsync(
shipment.WarehouseId),
Importer = await GetImporterInfoAsync(
shipment.DestinationAddress),
CountryOfExport =
shipment.OriginCountry,
CountryOfImport =
shipment.DestinationCountry,
Items = items.Select(i =>
new DeclarationItem
{
HsCode = i.HsCode,
Description = i.Description,
Quantity = i.Quantity,
UnitValue = i.UnitValue,
TotalValue =
i.Quantity * i.UnitValue,
CountryOfOrigin =
i.CountryOfOrigin,
NetWeight = i.WeightKg,
GrossWeight = i.GrossWeightKg
}).ToList(),
TotalDeclaredValue = items.Sum(i =>
i.Quantity * i.UnitValue),
CurrencyCode = shipment.CurrencyCode,
Incoterms = shipment.Incoterms,
PurposeOfShipment = "Commercial",
LicenseNumber =
await GetLicenseNumberIfNeededAsync(items)
};
await _docRepo.SaveDeclarationAsync(
declaration);
return declaration;
}
}
Document Types and Retention
| Document | Generated When | Retention Period | Regulatory Requirement |
|---|---|---|---|
| Bill of Lading (BOL) | Carrier pickup | 7 years | FMC, freight auditing |
| Packing Slip | Pack station confirmation | 3 years | Customer service, disputes |
| Commercial Invoice | International shipment | 7 years | Customs, IRS, import audit |
| Customs Declaration | International shipment | 5 years | CBP, customs audit |
| Certificate of Origin | FTA shipments | 5 years | FTA compliance |
| Proof of Delivery (POD) | Delivery confirmed | 3 years | Dispute resolution |
| Hazmat Declaration | Hazmat shipment | 2 years | DOT, IATA |
| FDA Documentation | Food/pharma shipment | 6 years | FDA 21 CFR Part 11 |
21. Alerting, Anomaly Detection & Proactive Notifications
Proactive alerting transforms a supply chain system from reactive to predictive. Instead of waiting for customers to report problems, the system detects anomalies early and triggers notifications before issues escalate. Our alerting system monitors inventory levels, shipment progress, carrier performance, and operational metrics in real-time.
Alert Rule Engine
C#
public class AlertRuleEngine
{
private readonly IAlertRuleRepository _ruleRepo;
private readonly IAlertNotificationService _notifService;
private readonly IEventPublisher _events;
public async Task EvaluateAlertsAsync(
SupplyChainEvent evt)
{
var rules = await _ruleRepo
.GetActiveRulesAsync(evt.TenantId);
foreach (var rule in rules)
{
if (!rule.MatchesEventType(evt.EventType))
continue;
var shouldAlert = await EvaluateConditionAsync(
rule, evt);
if (shouldAlert)
{
var alert = new Alert
{
Id = Guid.NewGuid(),
RuleId = rule.Id,
TenantId = evt.TenantId,
Severity = rule.Severity,
Title = rule.GenerateTitle(evt),
Message = rule.GenerateMessage(evt),
EntityType = evt.EntityType,
EntityId = evt.EntityId,
Metadata = evt.Metadata,
CreatedAt = DateTime.UtcNow
};
await _notifService.SendAlertAsync(
alert, rule.Recipients);
await _events.PublishAsync(
new AlertTriggeredEvent
{
AlertId = alert.Id,
RuleId = rule.Id,
Severity = rule.Severity
});
}
}
}
private async Task<bool> EvaluateConditionAsync(
AlertRule rule, SupplyChainEvent evt)
{
return rule.ConditionType switch
{
ConditionType.StockoutRisk =>
await CheckStockoutRisk(evt),
ConditionType.DeliveryDelay =>
await CheckDeliveryDelay(evt),
ConditionType.CarrierException =>
await CheckCarrierException(evt),
ConditionType.CustomsHold =>
await CheckCustomsHold(evt),
ConditionType.ThresholdBreach =>
CheckThresholdBreach(rule, evt),
ConditionType.PatternAnomaly =>
await CheckPatternAnomaly(rule, evt),
_ => false
};
}
private async Task<bool> CheckStockoutRisk(
SupplyChainEvent evt)
{
var sku = evt.Metadata["sku"].ToString();
var warehouseId = Guid.Parse(
evt.Metadata["warehouseId"].ToString());
var inventory = await _inventoryService
.GetCurrentLevelsAsync(sku, warehouseId);
var forecast = await _forecastService
.GetDemandForecastAsync(sku, warehouseId);
var daysOfSupply =
inventory.QuantityAvailable
/ Math.Max(1, forecast.MeanDailyDemand);
return daysOfSupply <
rule.DaysOfSupplyThreshold;
}
private async Task<bool> CheckDeliveryDelay(
SupplyChainEvent evt)
{
var shipmentId = Guid.Parse(
evt.EntityId.ToString());
var shipment = await _shipmentRepo
.GetByIdAsync(shipmentId);
if (shipment.EstimatedDeliveryDate == null)
return false;
var delayDays = (DateTime.UtcNow.Date -
shipment.EstimatedDeliveryDate.Value).Days;
return delayDays >= rule.DelayDaysThreshold;
}
private async Task<bool> CheckCarrierException(
SupplyChainEvent evt)
{
var exceptionCode = evt.Metadata["exceptionCode"]
.ToString();
return rule.MonitoredExceptionCodes
.Contains(exceptionCode);
}
private async Task<bool> CheckPatternAnomaly(
AlertRule rule, SupplyChainEvent evt)
{
var metric = await _metricsStore
.GetRecentMetricAsync(
rule.MetricName,
rule.AnomalyLookbackMinutes);
var mean = metric.Values.Average();
var stdDev = CalculateStdDev(metric.Values);
var currentValue = metric.Values.Last();
var zScore = Math.Abs(
(currentValue - mean) / stdDev);
return zScore > rule.AnomalyZScoreThreshold;
}
}
public class AlertEscalationPolicy
{
public List<EscalationLevel> Levels { get; set; }
public async Task EscalateAsync(
Alert alert, int attemptCount)
{
var level = Levels
.OrderBy(l => l.DelayMinutes)
.SkipWhile(l =>
l.DelayMinutes <= attemptCount * 5)
.FirstOrDefault();
if (level == null)
{
// All levels exhausted, notify management
await NotifyManagementAsync(alert);
return;
}
foreach (var recipient in level.Recipients)
{
await SendEscalationAsync(
alert, recipient, level.Method);
}
}
}
Alert Types and Thresholds
| Alert Type | Default Threshold | Severity | Escalation |
|---|---|---|---|
| Stockout Risk | < 3 days of supply | High | Notify procurement + warehouse |
| Stockout Imminent | < 1 day of supply | Critical | Escalate to supply chain director |
| Delivery Delay | > 2 days past ETA | Medium | Notify customer service |
| Severe Delay | > 5 days past ETA | High | Notify operations manager |
| Carrier Exception | Any exception event | Medium | Notify shipping coordinator |
| Customs Hold | Hold status detected | High | Notify compliance team |
| Inventory Discrepancy | > 5% variance | High | Notify warehouse manager |
| Supplier Delay Risk | PO delivery > 3 days late | Medium | Notify procurement |
| High Return Rate | > 15% return rate (7-day) | Medium | Notify quality team |
| Anomaly Detection | 3+ sigma deviation | Varies | Notify ops team |
22. Analytics Dashboard & Business Intelligence
The analytics dashboard provides supply chain managers with real-time visibility into every aspect of their operations: order fulfillment rates, inventory health, shipment performance, cost trends, supplier scorecards, and demand forecasts. Built on a columnar OLAP database (ClickHouse), the analytics engine delivers sub-second query performance over billions of records.
Analytics Data Pipeline
Key Dashboard Metrics
C#
public class AnalyticsService : IAnalyticsService
{
private readonly IClickHouseClient _clickhouse;
private readonly IRedisClient _redis;
public async Task<DashboardData>
GetDashboardAsync(
Guid tenantId, DateRange period)
{
var cacheKey =
$"dash:{tenantId}:{period.GetHashCode()}";
var cached = await _redis.GetAsync
<DashboardData>(cacheKey);
if (cached != null) return cached;
var dashboard = new DashboardData
{
OrderMetrics = await GetOrderMetricsAsync(
tenantId, period),
InventoryMetrics = await GetInventoryMetricsAsync(
tenantId, period),
ShippingMetrics = await GetShippingMetricsAsync(
tenantId, period),
FinancialMetrics = await GetFinancialMetricsAsync(
tenantId, period),
SupplierMetrics = await GetSupplierMetricsAsync(
tenantId, period),
TrendData = await GetTrendDataAsync(
tenantId, period),
GeneratedAt = DateTime.UtcNow
};
await _redis.SetAsync(
cacheKey, dashboard,
TimeSpan.FromMinutes(5));
return dashboard;
}
private async Task<OrderMetrics> GetOrderMetricsAsync(
Guid tenantId, DateRange period)
{
return new OrderMetrics
{
TotalOrders = await _clickhouse.ExecuteScalarAsync<long>(@"
SELECT count() FROM orders
WHERE tenant_id = {tenantId}
AND created_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
OrdersByStatus = await _clickhouse.QueryAsync<
(string status, int count)>(@"
SELECT status, count() as cnt
FROM orders
WHERE tenant_id = {tenantId}
AND created_at BETWEEN {start} AND {end}
GROUP BY status",
tenantId, period.Start, period.End),
AverageOrderValue = await _clickhouse
.ExecuteScalarAsync<decimal>(@"
SELECT avg(total) FROM orders
WHERE tenant_id = {tenantId}
AND created_at BETWEEN {start} AND {end}
AND status != 'cancelled'",
tenantId, period.Start, period.End),
FulfillmentRate = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT
countIf(status = 'delivered') * 1.0
/ count()
FROM orders
WHERE tenant_id = {tenantId}
AND created_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
AverageFulfillmentTime = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT avg(
dateDiff('hour',
confirmed_at, delivered_at))
FROM orders
WHERE tenant_id = {tenantId}
AND delivered_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End)
};
}
private async Task<InventoryMetrics>
GetInventoryMetricsAsync(
Guid tenantId, DateRange period)
{
return new InventoryMetrics
{
TotalSkus = await _clickhouse
.ExecuteScalarAsync<int>(@"
SELECT uniq(sku) FROM inventory_items
WHERE tenant_id = {tenantId}",
tenantId),
InventoryTurnoverRate = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT
sum(quantity_change) * 1.0
/ avg(quantity_on_hand)
FROM inventory_transactions it
JOIN inventory_items ii
ON it.sku = ii.sku
WHERE ii.tenant_id = {tenantId}
AND it.created_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
StockoutRate = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT
countIf(quantity_on_hand = 0) * 1.0
/ count()
FROM inventory_items
WHERE tenant_id = {tenantId}",
tenantId),
InventoryAccuracy = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT
1 - abs(sum(variance))
/ sum(expected_quantity)
FROM cycle_count_results
WHERE tenant_id = {tenantId}
AND counted_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
SlowMovingInventory = await _clickhouse
.QueryAsync<(string sku,
int daysSinceLastMovement,
int quantityOnHand)>(@"
SELECT sku,
dateDiff('day',
max(created_at), now()) as
days_since_move,
max(quantity_on_hand) as qty
FROM inventory_transactions it
JOIN inventory_items ii
ON it.sku = ii.sku
WHERE ii.tenant_id = {tenantId}
GROUP BY sku
HAVING days_since_move > 90
ORDER BY qty DESC
LIMIT 50",
tenantId)
};
}
private async Task<ShippingMetrics>
GetShippingMetricsAsync(
Guid tenantId, DateRange period)
{
return new ShippingMetrics
{
OnTimeDeliveryRate = await _clickhouse
.ExecuteScalarAsync<double>(@"
SELECT
countIf(
actual_delivery_date <=
estimated_delivery_date) * 1.0
/ count()
FROM shipments
WHERE tenant_id = {tenantId}
AND actual_delivery_date
BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
AverageShippingCost = await _clickhouse
.ExecuteScalarAsync<decimal>(@"
SELECT avg(shipping_cost)
FROM shipments
WHERE tenant_id = {tenantId}
AND created_at BETWEEN {start} AND {end}",
tenantId, period.Start, period.End),
CostPerOrder = await _clickhouse
.ExecuteScalarAsync<decimal>(@"
SELECT
sum(s.shipping_cost)
/ count(DISTINCT s.order_id)
FROM shipments s
WHERE s.tenant_id = {tenantId}
AND s.created_at BETWEEN
{start} AND {end}",
tenantId, period.Start, period.End),
CarrierPerformance = await _clickhouse
.QueryAsync<(string carrier,
double onTimeRate,
decimal avgCost)>(@"
SELECT
carrier_code,
countIf(
actual_delivery_date <=
estimated_delivery_date)
* 1.0 / count() as on_time,
avg(shipping_cost) as avg_cost
FROM shipments
WHERE tenant_id = {tenantId}
AND actual_delivery_date
BETWEEN {start} AND {end}
GROUP BY carrier_code
ORDER BY on_time DESC",
tenantId, period.Start, period.End)
};
}
}
Dashboard KPI Summary
| KPI | Description | Target | Refresh Rate |
|---|---|---|---|
| Order Fulfillment Rate | % of orders delivered on time | > 98% | Real-time |
| Inventory Turnover | How many times inventory sells per period | 8-12x/year | Daily |
| Stockout Rate | % of SKUs with zero inventory | < 2% | Real-time |
| Average Order Value | Mean order total | Trending up | Hourly |
| Cost Per Shipment | Average shipping cost per order | Trending down | Daily |
| Inventory Accuracy | Physical count vs system count | > 99.9% | Per cycle count |
| Supplier On-Time Rate | % of POs delivered on time | > 95% | Weekly |
| Return Rate | % of orders returned | < 10% | Daily |
| Days of Supply | Average days of inventory remaining | 30-45 days | Daily |
| Gross Margin Return on Investment | GMROI = Gross Margin / Average Inventory Cost | > 3.0 | Monthly |
23. Blockchain for Provenance & Audit Trails
Blockchain technology offers an immutable, transparent ledger for tracking product provenance across the supply chain. While not all supply chain operations benefit from blockchain, specific use cases — food safety tracing, pharmaceutical authenticity, luxury goods verification, and ethical sourcing certification — gain significant value from distributed ledger technology. Our system provides an optional blockchain integration layer that can be activated for supply chains requiring provable traceability.
Blockchain Integration Architecture
Blockchain Provenance Service
C#
public class BlockchainProvenanceService
{
private readonly IEventRepository _eventRepo;
private readonly IBlockchainClient _blockchain;
private readonly IHashChainService _hashChain;
public async Task<ProvenanceRecord>
RecordProvenanceEventAsync(
ProvenanceEvent evt)
{
// Store event off-chain for fast queries
var storedEvent = await _eventRepo.StoreAsync(evt);
// Create hash of event data
var eventDataHash = ComputeHash(evt);
// Link to previous event hash (hash chain)
var previousHash = await _hashChain
.GetPreviousHashAsync(
evt.ProductId, evt.SequenceNumber);
var chainedHash = ComputeHash(
$"{eventDataHash}:{previousHash}");
// Submit to blockchain
var txHash = await _blockchain
.SubmitTransactionAsync(new BlockchainTx
{
ContractName = "ProvenanceLedger",
Method = "RecordEvent",
Args = new object[]
{
evt.ProductId,
evt.EventType.ToString(),
chainedHash,
evt.Timestamp.ToUnixTimeSeconds(),
evt.ActorId,
evt.LocationCode
}
});
return new ProvenanceRecord
{
EventId = storedEvent.Id,
ProductId = evt.ProductId,
SequenceNumber = evt.SequenceNumber,
EventType = evt.EventType,
OffChainHash = eventDataHash,
ChainedHash = chainedHash,
BlockchainTxHash = txHash,
Timestamp = evt.Timestamp,
Verified = true
};
}
public async Task<ProvenanceChain>
GetFullProvenanceAsync(string productId)
{
var events = await _eventRepo
.GetAllEventsAsync(productId);
var chain = new ProvenanceChain
{
ProductId = productId,
Events = new List<ProvenanceRecord>(),
IsValid = true
};
string previousHash = "";
foreach (var evt in events)
{
var record = await VerifyEventAsync(
evt, previousHash);
if (!record.Verified)
{
chain.IsValid = false;
chain.TamperDetectedAt = evt.Id;
break;
}
chain.Events.Add(record);
previousHash = record.ChainedHash;
}
return chain;
}
private async Task<ProvenanceRecord>
VerifyEventAsync(
StoredProvenanceEvent evt,
string previousHash)
{
var recomputedHash = ComputeHash(
$"{evt.DataHash}:{previousHash}");
var blockchainVerified =
await _blockchain.VerifyTransactionAsync(
evt.BlockchainTxHash);
return new ProvenanceRecord
{
EventId = evt.Id,
ProductId = evt.ProductId,
EventType = evt.EventType,
OffChainHash = evt.DataHash,
ChainedHash = recomputedHash,
BlockchainTxHash = evt.BlockchainTxHash,
Timestamp = evt.Timestamp,
Verified = recomputedHash == evt.ChainedHash
&& blockchainVerified
};
}
}
public enum ProvenanceEventType
{
RawMaterialSourced,
Manufactured,
QualityInspected,
Packaged,
Shipped,
CustomsCleared,
ReceivedAtWarehouse,
Stored,
Picked,
ShippedToCustomer,
Delivered
}
When to Use Blockchain vs Traditional Database
| Use Case | Blockchain Needed? | Why |
|---|---|---|
| Internal warehouse operations | No | Single organization controls data; traditional DB is faster and cheaper |
| Multi-party supply chain (food safety) | Yes | Multiple untrusting parties need shared immutable record |
| Pharmaceutical traceability | Yes | FDA DSCSA requires verifiable chain of custody |
| Luxury goods authentication | Yes | Consumers and resellers need proof of authenticity |
| Internal shipment tracking | No | Carrier data is trusted; no multi-party dispute resolution needed |
| Conflict minerals sourcing | Yes | Regulatory requirement for verifiable origin documentation |
| Organic/fair-trade certification | Optional | Consumer trust benefit; traditional audit trail may suffice |
24. Monitoring, Security & Compliance
A supply chain system handles sensitive financial data, personal customer information, and regulated goods (hazmat, pharmaceuticals, food). Security and compliance are not optional features — they are foundational requirements. This section covers the monitoring, security controls, and compliance frameworks that ensure the system operates safely and legally.
Monitoring Stack
Security Controls
C#
public class SupplyChainSecurityService
{
// Multi-tenant data isolation
public async Task<T> ExecuteWithTenantIsolation<T>(
Guid tenantId,
Func<Task<T>> operation)
{
using var scope = _tenantContext
.CreateScope(tenantId);
// Verify tenant context is set
var currentTenant = _tenantContext.CurrentTenant;
if (currentTenant?.Id != tenantId)
throw new UnauthorizedAccessException(
"Tenant mismatch");
return await operation();
}
// RBAC permission check
public async Task<bool> HasPermissionAsync(
Guid userId,
string resource,
string action)
{
var user = await _userRepo.GetByIdAsync(userId);
var permissions = await _permissionRepo
.GetUserPermissionsAsync(userId);
// Check direct permissions
if (permissions.Any(p =>
p.Resource == resource
&& p.Action == action))
return true;
// Check role-based permissions
foreach (var role in user.Roles)
{
var rolePerms = await _permissionRepo
.GetRolePermissionsAsync(role.Id);
if (rolePerms.Any(p =>
p.Resource == resource
&& p.Action == action))
return true;
}
// Check scope-based permissions
// (e.g., warehouse-specific access)
return await CheckScopedPermissionAsync(
userId, resource, action);
}
// Audit logging for compliance
public async Task LogAuditEventAsync(
AuditEvent evt)
{
var logEntry = new AuditLogEntry
{
Id = Guid.NewGuid(),
Timestamp = DateTime.UtcNow,
UserId = evt.UserId,
TenantId = evt.TenantId,
Action = evt.Action,
ResourceType = evt.ResourceType,
ResourceId = evt.ResourceId,
OldValue = evt.OldValue,
NewValue = evt.NewValue,
IpAddress = evt.IpAddress,
UserAgent = evt.UserAgent,
CorrelationId = evt.CorrelationId
};
// Write to append-only audit log
await _auditLogRepo.AppendAsync(logEntry);
// Also publish to SIEM for real-time monitoring
await _siemClient.SendEventAsync(logEntry);
// Alert on sensitive operations
if (evt.IsSensitiveOperation)
{
await _alertService.RaiseSecurityAlertAsync(
$"Sensitive operation: {evt.Action} " +
$"on {evt.ResourceType}/{evt.ResourceId}",
evt.UserId);
}
}
}
// API Key management for partner integrations
public class ApiKeyService
{
public async Task<ApiKey> GenerateApiKeyAsync(
Guid partnerId,
ApiKeyScope scope,
TimeSpan validity)
{
var key = new ApiKey
{
Id = Guid.NewGuid(),
PartnerId = partnerId,
KeyHash = HashApiKey(
GenerateSecureKey()),
KeyPrefix =
GenerateSecureKey().Substring(0, 8),
Scopes = scope,
ExpiresAt = DateTime.UtcNow.Add(validity),
RateLimit = scope switch
{
ApiKeyScope.Readonly => 1000,
ApiKeyScope.Standard => 5000,
ApiKeyScope.Admin => 10000,
_ => 1000
},
CreatedAt = DateTime.UtcNow
};
await _apiKeyRepo.SaveAsync(key);
return key;
}
}
Compliance Frameworks
| Framework | Applicability | Key Requirements | Our Controls |
|---|---|---|---|
| GDPR | EU customer data | Data minimization, right to erasure, consent management | PII encryption, data retention policies, erasure API |
| SOC 2 Type II | SaaS platform | Access controls, monitoring, incident response | RBAC, audit logging, automated alerts, pen testing |
| FDA 21 CFR Part 11 | Pharmaceutical supply chain | Electronic signatures, audit trails, data integrity | E-signatures, immutable audit logs, version control |
| Hazmat Regulations (DOT/IATA) | Hazardous materials shipping | Proper classification, packaging, labeling, documentation | Hazmat classifier, automatic documentation, carrier validation |
| C-TPAT | US customs trade partnership | Supply chain security, risk assessment | Supplier vetting, shipment screening, access controls |
| ISO 28000 | Supply chain security management | Security management system, risk assessment | Threat modeling, security audits, incident response plan |
Monitoring Dashboard Metrics
| Metric | Alert Threshold | Dashboard Panel |
|---|---|---|
| API Error Rate | > 1% (5min window) | Service health overview |
| API Latency (p99) | > 2s (5min window) | Latency heatmap by endpoint |
| Database Connection Pool | > 80% utilized | Database health panel |
| Kafka Consumer Lag | > 10,000 messages | Event processing pipeline |
| Redis Memory Usage | > 75% of max | Cache hit/miss ratio |
| Disk Usage | > 85% | Storage utilization panel |
| Failed Login Attempts | > 5 in 5 minutes | Security events panel |
| Inventory Sync Lag | > 60 seconds | Inventory freshness panel |
| Carrier API Response Time | > 10 seconds | External integration health |
| Forecast Model Accuracy | MAPE > 25% | ML model performance |
25. Testing Strategy
A supply chain system requires a comprehensive testing strategy that covers unit tests for business logic, integration tests for service interactions, contract tests for API compatibility, performance tests for scale validation, and chaos engineering tests for resilience verification. The cost of bugs in a supply chain system is enormous — an inventory allocation error can result in thousands of oversold orders, and a shipping calculation bug can cost millions in incorrect carrier charges.
Testing Pyramid
Unit Tests for Business Logic
C#
public class InventoryAllocatorTests
{
[Fact]
public async Task Should_Allocate_From_Closest_Warehouse()
{
// Arrange
var allocator = new InventoryAllocator(
MockRepository.Create<IInventoryRepository>(),
MockRepository.Create<IWarehouseRepository>(),
MockRepository.Create<IRdpClient>());
var warehouses = new List<Warehouse>
{
CreateWarehouse("WH-1",
distance: 100, available: 50),
CreateWarehouse("WH-2",
distance: 50, available: 100),
CreateWarehouse("WH-3",
distance: 200, available: 200)
};
// Act
var result = await allocator.AllocateAsync(
Guid.NewGuid(),
new List<AllocationRequest>
{
new() { Sku = "SKU-001", Quantity = 30 }
},
AllocationStrategy.ClosestWarehouse);
// Assert - should allocate from WH-2 (closest)
Assert.Equal("WH-2",
result.First().WarehouseId.ToString());
Assert.Equal(30, result.First().Quantity);
}
[Fact]
public async Task Should_Split_Allocation_Across_Warehouses()
{
// Arrange - WH-1 has 20, need 50
var warehouses = new List<Warehouse>
{
CreateWarehouse("WH-1",
distance: 50, available: 20),
CreateWarehouse("WH-2",
distance: 100, available: 40)
};
// Act
var result = await allocator.AllocateAsync(
Guid.NewGuid(),
new List<AllocationRequest>
{
new() { Sku = "SKU-001", Quantity = 50 }
},
AllocationStrategy.ClosestWarehouse);
// Assert - should split across WH-1 and WH-2
Assert.Equal(2, result.Count);
Assert.Contains(result,
r => r.Quantity == 20);
Assert.Contains(result,
r => r.Quantity == 30);
}
[Fact]
public async Task Should_Return_Unavailable_When_Insufficient()
{
// Arrange - total available is 10, need 50
var warehouses = new List<Warehouse>
{
CreateWarehouse("WH-1",
distance: 50, available: 10),
CreateWarehouse("WH-2",
distance: 100, available: 0)
};
// Act
var result = await allocator.AllocateAsync(
Guid.NewGuid(),
new List<AllocationRequest>
{
new() { Sku = "SKU-001", Quantity = 50 }
},
AllocationStrategy.ClosestWarehouse);
// Assert
Assert.Contains(result,
r => r.Status ==
AllocationStatus.Unavailable);
}
}
public class SafetyStockCalculatorTests
{
[Theory]
[InlineData(0.90, 1.28)]
[InlineData(0.95, 1.65)]
[InlineData(0.99, 2.33)]
public void Should_Return_Correct_ZScore(
double serviceLevel, double expectedZ)
{
var calculator = new InventoryOptimizer(
null, null, null);
var result = calculator.GetZScoreForTest(
serviceLevel);
Assert.Equal(expectedZ, result, 2);
}
[Fact]
public void Should_Calculate_SafetyStock_Correctly()
{
// Mean daily demand: 100, StdDev: 20
// Lead time: 7 days, Service level: 95%
// Safety stock = 1.65 * 20 * sqrt(7)
// = 1.65 * 20 * 2.646
// = 87.3 -> 88 units
var result = InventoryOptimizer
.CalculateSafetyStockForTest(
meanDemand: 100,
stdDevDemand: 20,
leadTimeDays: 7,
serviceLevel: 0.95);
Assert.Equal(88, result);
}
}
public class OrderStateMachineTests
{
[Fact]
public void Should_Transition_From_Pending_To_Confirmed()
{
var order = Order.Create(
tenantId: Guid.NewGuid(),
customerId: Guid.NewGuid(),
items: new List<OrderItem>
{
OrderItem.Create("SKU-001", 10, 29.99m)
},
shippingAddress: CreateAddress(),
billingAddress: CreateAddress(),
sourceChannel: "web");
order.Confirm();
Assert.Equal(OrderStatus.Confirmed, order.Status);
Assert.NotNull(order.ConfirmedAt);
}
[Fact]
public void Should_Throw_On_Invalid_Transition()
{
var order = CreateOrderWithStatus(
OrderStatus.Shipped);
Assert.Throws<InvalidOrderStateException>(
() => order.Confirm());
}
}
Integration Tests
C#
public class OrderFulfillmentIntegrationTests :
IClassFixture<TestContainersFixture>
{
private readonly TestContainersFixture _fixture;
private readonly HttpClient _client;
public OrderFulfillmentIntegrationTests(
TestContainersFixture fixture)
{
_fixture = fixture;
_client = fixture.CreateHttpClient();
}
[Fact]
public async Task Should_Fullfill_Order_End_To_End()
{
// 1. Create order
var createResponse = await _client.PostAsJsonAsync(
"/api/v1/orders",
new CreateOrderRequest
{
CustomerId = Guid.NewGuid(),
Items = new List<OrderItemRequest>
{
new() { Sku = "TEST-SKU-001",
Quantity = 5, UnitPrice = 29.99m }
},
ShippingAddress = CreateTestAddress(),
BillingAddress = CreateTestAddress(),
SourceChannel = "integration-test"
});
createResponse.EnsureSuccessStatusCode();
var order = await createResponse.Content
.ReadFromJsonAsync<OrderResponse>();
// 2. Confirm order
var confirmResponse = await _client.PostAsync(
$"/api/v1/orders/{order.Id}/confirm", null);
confirmResponse.EnsureSuccessStatusCode();
// 3. Wait for allocation
await Task.Delay(TimeSpan.FromSeconds(5));
// 4. Verify inventory allocated
var orderAfterAlloc = await _client
.GetFromJsonAsync<OrderResponse>(
$"/api/v1/orders/{order.Id}");
Assert.Equal("Allocated",
orderAfterAlloc.Status);
// 5. Verify shipment created
var shipmentResponse = await _client
.GetFromJsonAsync<List<ShipmentResponse>>(
$"/api/v1/shipments?orderId={order.Id}");
Assert.NotEmpty(shipmentResponse);
}
}
Test Coverage Targets
| Test Type | Coverage Target | Run Frequency | Execution Time |
|---|---|---|---|
| Unit Tests | > 85% line coverage | Every commit | < 5 minutes |
| Integration Tests | All API endpoints | Every PR | < 15 minutes |
| Contract Tests | All external integrations | Daily | < 10 minutes |
| Performance Tests | Critical paths (order, inventory) | Weekly | < 30 minutes |
| Chaos Tests | Failure injection scenarios | Monthly | < 60 minutes |
| E2E Tests | Complete fulfillment workflow | Nightly | < 45 minutes |
26. Interview Q&A Deep Dive
Supply chain system design questions appear frequently in senior-level system design interviews at companies like Amazon, Shopify, Flexport, and Uber Freight. Below are the most common questions with detailed answers that demonstrate deep understanding of the domain.
Question 1: How do you handle inventory overselling during flash sales?
Question 2: How would you design the real-time tracking update pipeline?
NormalizedTrackingEvent model. Carrier adapters handle format differences — FedEx uses XML, UPS uses JSON, DHL uses SOAP. (3) A deduplication layer using a composite key of (carrier_code, tracking_number, carrier_timestamp) with a 7-day TTL bloom filter. (4) An enrichment layer that adds predicted ETA using our ML model, maps carrier status codes to our unified status enum, and resolves GPS coordinates to human-readable locations. (5) A real-time push layer using Redis Pub/Sub fan-out to WebSocket servers. Clients subscribe to tracking number channels and receive updates within 1-2 seconds of ingestion. The entire pipeline from webhook receipt to client notification takes under 5 seconds at p99.
Question 3: How do you ensure inventory accuracy across 50 warehouses?
Question 4: How do you handle carrier API failures and degraded performance?
Question 5: Explain the Saga pattern used for order fulfillment. What happens if a step fails midway?
FulfillmentTaskFailedEvent, which triggers payment void (compensate step 2) and inventory release (compensate step 1). The saga state machine tracks which steps have completed and which compensations have executed. A dead letter queue captures events that fail to process after 3 retry attempts. A reconciliation job runs every 15 minutes to detect sagas stuck in intermediate states and either retries or completes compensation. The key design principle is that every step must be idempotent — if a step's event is processed twice (due to at-least-once delivery), the step should produce the same result. We achieve this by storing idempotency keys in the database and checking before executing.
Question 6: How would you design the demand forecasting system?
Question 7: How do you handle international shipping complexities?
Question 8: How do you optimize last-mile delivery routes?
Pre-Interview Checklist
- Understand the full order lifecycle from placement through delivery and returns
- Know inventory allocation strategies and the thundering herd problem
- Design a saga pattern for distributed transaction coordination
- Explain carrier integration architecture with adapter pattern
- Discuss real-time tracking pipeline with event-driven architecture
- Understand reorder point and safety stock calculations
- Know the VRP problem and practical optimization approaches
- Explain multi-carrier rate shopping with policy-based selection
- Discuss international shipping: HS codes, duties, Incoterms
- Understand demand forecasting approaches and ensemble methods
- Know RMA workflow and disposition decision logic
- Explain document management and retention requirements
- Discuss monitoring, security, and compliance frameworks
- Understand warehouse operations: wave planning, pick optimization
- Know cycle counting strategies and ABC classification