Skip to content

Inventory / Stock Management Module

Complete enterprise-grade inventory tracking and stock movement management with real-time updates and complete audit trail.

Overview

The Inventory module provides comprehensive stock management capabilities including master data management, real-time quantity tracking, automated stock movements, and complete audit trails. All operations are tenant-isolated and integrated with other modules.

Features

Core Capabilities

  • Master Data Management: SKU, name, description, zone location
  • Real-Time Tracking: Live quantity updates with automatic synchronization
  • Stock Movements: Complete audit trail (inbound, outbound, adjustment, transfer)
  • Low Stock Alerts: Automated warnings when quantity falls below minimum
  • Zone Organization: Zone-based inventory location tracking
  • Barcode Integration: Quick item lookup via barcode scanning
  • Movement History: Complete audit trail with user tracking
  • Analytics: Stock movement reports and trends

Architecture

mermaid
graph LR
    A[Inventory Items] --> B[Stock Movements]
    A --> C[Zones]
    B --> D[Users]
    B --> E[Audit Trail]
    A --> F[Pick & Pack]
    A --> G[Slotting AI]
    
    style A fill:#3b82f6
    style B fill:#10b981
    style C fill:#f59e0b

Database Schema

inventory_items Table

ColumnTypeConstraintsDescription
iduuidPRIMARY KEYUnique item identifier
tenant_iduuidNOT NULL, FK → tenantsTenant isolation
skutextNOT NULL, UNIQUEStock Keeping Unit (unique)
nametextNOT NULLItem name
descriptiontextNULLOptional description
zone_iduuidFK → zones, NULLZone location
quantityintegerNOT NULL, >= 0Current stock quantity
min_quantityintegerNOT NULL, >= 0Minimum stock level
unittextNOT NULL, DEFAULT 'pcs'Unit of measurement
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • idx_inventory_items_sku on sku
  • idx_inventory_items_zone on zone_id
  • idx_inventory_items_tenant on tenant_id
  • idx_inventory_items_low_stock on (zone_id, quantity, min_quantity) WHERE quantity < min_quantity

stock_movements Table

ColumnTypeConstraintsDescription
iduuidPRIMARY KEYUnique movement identifier
tenant_iduuidNOT NULL, FK → tenantsTenant isolation
item_iduuidNOT NULL, FK → inventory_itemsRelated item
typetextNOT NULL, CHECKMovement type: 'inbound', 'outbound', 'adjustment', 'transfer'
qtyintegerNOT NULL, != 0Movement quantity (positive or negative)
from_zone_iduuidFK → zones, NULLSource zone (for transfer)
to_zone_iduuidFK → zones, NULLDestination zone (for transfer/inbound)
user_iduuidNOT NULL, FK → profilesUser who created movement
reasontextNULLOptional reason for movement
created_attimestamptzNOT NULL, DEFAULT now()Movement timestamp

Indexes:

  • idx_stock_movements_item on item_id
  • idx_stock_movements_type on type
  • idx_stock_movements_tenant on tenant_id
  • idx_stock_movements_created on created_at

API Functions

Create Item

typescript
import { createItem } from '@/lib/inventory';

const { data, error } = await createItem({
  sku: 'ITEM-001',
  name: 'Sample Product',
  description: 'Product description',
  zone_id: 'zone-uuid',
  min_quantity: 10,
  unit: 'pcs',
  tenant_id: 'tenant-uuid', // Optional, auto-set from context
});

Parameters:

  • sku (string, required): Unique Stock Keeping Unit
  • name (string, required): Item name
  • description (string, optional): Item description
  • zone_id (uuid, optional): Initial zone location
  • min_quantity (number, optional, default: 0): Minimum stock level
  • unit (string, optional, default: 'pcs'): Unit of measurement
  • tenant_id (uuid, optional): Tenant ID (auto-set from auth context)

Returns: { data: InventoryItem | null, error: any }

Adjust Stock

typescript
import { adjustStock } from '@/lib/inventory';

// Increase stock by 10
const { data, error } = await adjustStock(
  'item-uuid',
  10,
  'Manual adjustment - stock correction'
);

// Decrease stock by 5
const { data, error } = await adjustStock(
  'item-uuid',
  -5,
  'Stock correction - found discrepancy'
);

Parameters:

  • itemId (string, required): Inventory item ID
  • adjustmentQty (number, required): Adjustment quantity (positive or negative)
  • reason (string, optional): Reason for adjustment

Returns: { data: StockMovement | null, error: any }

Note: Only admins can perform adjustments. Regular users should use inbound/outbound movements.

Inbound Movement

typescript
import { inbound } from '@/lib/inventory';

const { data, error } = await inbound({
  item_id: 'item-uuid',
  qty: 50,
  to_zone_id: 'zone-uuid',
  reason: 'Received from supplier',
});

Parameters:

  • item_id (string, required): Inventory item ID
  • qty (number, required, > 0): Inbound quantity
  • to_zone_id (uuid, optional): Destination zone
  • reason (string, optional): Reason for inbound

Returns: { data: StockMovement | null, error: any }

Effect: Increases item quantity by qty.

Outbound Movement

typescript
import { outbound } from '@/lib/inventory';

const { data, error } = await outbound({
  item_id: 'item-uuid',
  qty: 20,
  from_zone_id: 'zone-uuid',
  reason: 'Shipped to customer',
});

Parameters:

  • item_id (string, required): Inventory item ID
  • qty (number, required, > 0): Outbound quantity
  • from_zone_id (uuid, optional): Source zone
  • reason (string, optional): Reason for outbound

Returns: { data: StockMovement | null, error: any }

Effect: Decreases item quantity by qty.

Validation: Fails if quantity would go below 0.

Transfer Movement

typescript
import { transfer } from '@/lib/inventory';

const { data, error } = await transfer({
  item_id: 'item-uuid',
  qty: 15,
  from_zone_id: 'zone-a-uuid',
  to_zone_id: 'zone-b-uuid',
  reason: 'Reorganization - moving to faster zone',
});

Parameters:

  • item_id (string, required): Inventory item ID
  • qty (number, required, > 0): Transfer quantity
  • from_zone_id (uuid, required): Source zone
  • to_zone_id (uuid, required): Destination zone
  • reason (string, optional): Reason for transfer

Returns: { data: StockMovement | null, error: any }

Effect: Moves quantity from one zone to another. Total quantity unchanged.

Get Item with History

typescript
import { getItemWithMovementHistory } from '@/lib/inventory';

const { data, error } = await getItemWithMovementHistory('item-uuid');
// Returns: { item: InventoryItem, movements: StockMovement[] }

Returns: Complete item data with all stock movements in chronological order.

Get Low Stock Items

typescript
import { getLowStockItems } from '@/lib/inventory';

const { data, error } = await getLowStockItems();
// Returns: InventoryItem[] where quantity < min_quantity

Returns: Array of items where quantity < min_quantity.

Data Flow

mermaid
sequenceDiagram
    participant User
    participant UI
    participant API
    participant Database
    participant RLS
    
    User->>UI: Create Stock Movement
    UI->>API: inbound/outbound/transfer()
    API->>Database: Insert stock_movement
    Database->>RLS: Check tenant access
    RLS->>Database: Authorize
    Database->>Database: Update inventory_items.quantity
    Database->>API: Return movement record
    API->>UI: Update UI
    UI->>User: Show confirmation

Permissions

RolePermissions
AdminFull access: create items, all movement types, adjustments
Inventory WorkerInbound, outbound, transfer (no adjustments, no item creation)
DriverNo access to inventory
Tenant AdminFull access within tenant

Integration Points

Pick & Pack Integration

When items are picked in Pick & Pack module, outbound movements are automatically created:

typescript
// Automatic integration
// When pick list item is marked as "picked"
// → Creates outbound movement automatically

Slotting AI Integration

Slotting AI analyzes inventory velocity and turnover to recommend optimal zone placement:

typescript
// Slotting AI reads:
// - inventory_items (current locations)
// - stock_movements (movement frequency)
// → Generates zone recommendations

Warehouse Map Integration

Inventory items are displayed on the warehouse floor plan with zone locations:

typescript
// Floor plan shows:
// - Zone locations
// - Item counts per zone
// - Low stock indicators

Inventory Map Rendering

The warehouse visualization layer displays inventory items, stock movements, and driver cargo on the interactive floorplan map.

SKU Marker Placement

SKU markers are positioned on the warehouse map using a grid-based system:

  • Grid Size: 20 world units per cell
  • Placement Algorithm: Items are grouped by zone and distributed across grid cells within each zone
  • Position Calculation: gridX = Math.floor((zoneCenterX - offsetX) / gridSize)
mermaid
graph TB
    A[Inventory Items] -->|Group by Zone| B[Grid Cells]
    B -->|Calculate Position| C[SKU Markers]
    D[Stock Movements] -->|Animate| E[Movement Icons]
    F[Shipments] -->|Attach to Driver| G[Cargo Icons]
    C -->|Click| H[Detail Panel]
    G -->|Hover| I[Tooltip]

Color Coding System

SKU markers use color coding to indicate stock levels:

  • Red: Low stock (quantity < min_quantity)
  • Amber: Medium stock (min_quantity <= quantity < min_quantity * 2)
  • Green: High stock (quantity >= min_quantity * 2)

Movement Animation Behavior

Stock movements are animated on the map:

  • Inbound Movements: Icon animates from warehouse edge to target zone (3 second duration)
  • Outbound Movements: Icon animates from source zone to warehouse edge
  • Animation State: Stored in memory, cleaned up after completion
  • Recent Movements Only: Only movements from last 5 minutes are displayed

Driver Cargo Visualization

When a driver has an assigned transport:

  • Cargo Icon: Parcel/box icon positioned 15px behind driver
  • Synchronization: Cargo position updates with driver movement using CSS transforms
  • Tooltip on Hover: Displays Transport ID, SKU items count, and destination zone
  • Visual Distinction: Subtle glow outline to distinguish from driver marker

Quick Setup Steps

  1. Enable "Inventory Scene" toggle in admin controls
  2. Enable "Show SKUs" toggle to display inventory markers
  3. Enable "Show Driver Cargo" toggle to display cargo icons
  4. Enable "Show Inventory Movement" toggle to animate stock movements
  5. Click SKU markers to view detailed inventory information
  6. Hover over cargo icons to see transport details

Performance Considerations

  • Cached Data Only: All rendering uses cached state (no DB calls during render loops)
  • Grid Cell Caching: Grid cell positions cached for 5 seconds
  • Throttled Updates: SKU marker updates throttled to prevent excessive recalculations
  • Off-Screen Culling: Markers outside viewport are not rendered
  • Debounced Tooltips: Hover tooltips debounced to 300ms delay

Best Practices

1. Always Use Movements

Never update quantity directly. Always use movement functions:

typescript
// ❌ BAD
await supabase
  .from('inventory_items')
  .update({ quantity: newQuantity })
  .eq('id', itemId);

// ✅ GOOD
await inbound({ item_id: itemId, qty: difference });

2. Provide Reasons

Always include reasons for movements, especially adjustments:

typescript
await adjustStock(itemId, 10, 'Stock count correction - found 10 extra units');

3. Check Low Stock

Regularly check for low stock items:

typescript
const lowStockItems = await getLowStockItems();
if (lowStockItems.length > 0) {
  // Send alerts, trigger reordering, etc.
}

4. Use Zone Organization

Assign items to zones for better organization:

typescript
await createItem({
  sku: 'ITEM-001',
  name: 'Product',
  zone_id: 'fast-moving-zone-uuid', // Assign to appropriate zone
});

Troubleshooting

Issue: Quantity Mismatch

Symptom: Item quantity doesn't match sum of movements.

Solution:

  1. Check all movements for the item
  2. Verify no direct quantity updates
  3. Use adjustment movement to correct

Issue: Low Stock Not Alerting

Symptom: Items below min_quantity not showing alerts.

Solution:

  1. Verify min_quantity is set correctly
  2. Check index exists: idx_inventory_items_low_stock
  3. Refresh UI cache

Issue: Movement Fails with Negative Quantity

Symptom: Outbound movement fails when quantity would go negative.

Solution: This is expected behavior. Check:

  1. Current quantity is sufficient
  2. Consider using adjustment if correction needed
  3. Verify item_id is correct

Released under Commercial License