Comprehensive documentation of inventory control including stock levels, batch/serial tracking, goods receipt, stock movements, and inventory adjustments.
The stock_levels table tracks multiple quantity states for accurate inventory visibility.
-- Available quantity calculation SELECT item_id, warehouse_id, quantity_on_hand, quantity_reserved, quantity_on_order, quantity_in_transit, (quantity_on_hand - quantity_reserved) AS available_qty, (quantity_on_hand - quantity_reserved + quantity_on_order + quantity_in_transit) AS projected_qty FROM inventory.stock_levels WHERE item_id = :item_id;
Available: On Hand minus Reserved - what can be picked right now.
Projected: Available plus On Order plus In Transit - expected future availability.
Goods received from vendor
Items issued to production/dept
Move between warehouses
Quantity corrections
Return to vendor
Written off items
| Movement Type | Effect on Stock | Reference Document |
|---|---|---|
| RECEIPT | + quantity_on_hand, - quantity_in_transit | GRN, ASN |
| ISSUE | - quantity_on_hand, - quantity_reserved | Issue Slip, Work Order |
| TRANSFER | - from_warehouse, + to_warehouse | Transfer Order |
| ADJUSTMENT | +/- quantity_on_hand (variance) | Stock Adjustment |
System generates GRN number, links to PO and ASN (if exists), captures receipt metadata.
INSERT INTO inventory.grns ( grn_number, po_id, vendor_id, warehouse_id, receipt_date, received_by, status ) VALUES ( generate_grn_number(), :po_id, :vendor_id, :warehouse_id, CURRENT_DATE, :user_id, 'DRAFT' );
For each PO line, record received quantity, batch/serial info, and storage location.
If item requires QC, create inspection record. Accepted/rejected quantities determined.
Finalize GRN, create stock movement records, update all quantity fields.
-- Update stock levels on GRN completion UPDATE inventory.stock_levels SET quantity_on_hand = quantity_on_hand + :accepted_qty, quantity_on_order = quantity_on_order - :ordered_qty, quantity_in_transit = quantity_in_transit - :shipped_qty, last_movement_date = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE item_id = :item_id AND warehouse_id = :warehouse_id;
Track items by lot/batch number. Useful for:
Track individual items by serial number. Useful for:
-- FEFO: First Expiry, First Out - for items with expiry tracking SELECT sb.batch_number, sb.quantity, sb.expiry_date FROM inventory.stock_batches sb WHERE sb.item_id = :item_id AND sb.warehouse_id = :warehouse_id AND sb.status = 'AVAILABLE' AND sb.quantity > 0 AND sb.expiry_date > CURRENT_DATE -- Not expired ORDER BY sb.expiry_date ASC, -- Earliest expiry first sb.created_at ASC; -- Then FIFO within same expiry
A scheduled job checks stock_batches daily for items expiring within a configurable window (default 30 days). Notifications are sent to warehouse managers and purchase teams.
| Adjustment Type | Description | Approval Required |
|---|---|---|
| PHYSICAL_COUNT | Reconciliation after inventory count | Yes, if variance > threshold |
| DAMAGE | Items damaged in storage | Yes |
| EXPIRY | Expired items written off | Manager approval |
| CORRECTION | Error correction | Yes, with documentation |
| WRITE_OFF | Full inventory write-off | Finance + Management |
-- Stock adjustment item with auto-calculated variance CREATE TABLE inventory.stock_adjustment_items ( ... system_qty DECIMAL(18, 4) NOT NULL, actual_qty DECIMAL(18, 4) NOT NULL, variance_qty DECIMAL(18, 4) GENERATED ALWAYS AS (actual_qty - system_qty) STORED, ... );
-- Items below reorder level SELECT i.item_code, i.item_name, sl.warehouse_id, w.warehouse_name, sl.quantity_on_hand, sl.quantity_reserved, (sl.quantity_on_hand - sl.quantity_reserved) AS available, sl.quantity_on_order, i.reorder_level, i.reorder_qty, i.lead_time_days, CASE WHEN (sl.quantity_on_hand - sl.quantity_reserved + sl.quantity_on_order) <= i.reorder_level THEN 'REORDER_NOW' WHEN (sl.quantity_on_hand - sl.quantity_reserved) <= i.min_stock THEN 'CRITICAL' ELSE 'OK' END AS stock_status FROM inventory.items i JOIN inventory.stock_levels sl ON i.id = sl.item_id JOIN inventory.warehouses w ON sl.warehouse_id = w.id WHERE i.is_active = true AND i.is_stockable = true AND (sl.quantity_on_hand - sl.quantity_reserved + sl.quantity_on_order) <= i.reorder_level ORDER BY stock_status DESC, available ASC;
When configured, the system can automatically generate Purchase Requisitions for items below reorder level. The PR includes the reorder_qty and preferred vendors from vendor-item mappings.
Instead of annual full counts, perform regular cycle counts on high-value/high-movement items. Use stock_adjustments to record and approve variances.
For large warehouses, leverage storage_locations with aisle/rack/shelf/bin hierarchy. This enables efficient picking paths and capacity planning.
Set up alerts for items expiring within lead_time_days. This provides time to use or return items before they expire and become waste.
Every stock change should create a stock_movements record with proper reference. This is essential for traceability and audit compliance.