Warehouse Management System

Event-sourced warehouse management with zones, slots, transport units, floor plan editing, and immutable placement ledger built on Nextcloud Tables.

Last updated: 2025-02-18

Warehouse Management System (WHMS)

The WHMS module provides event-sourced warehouse management with an immutable placement ledger. Current state is always derived from the event history — never stored directly.

Interactive 3D Warehouse

Explore the warehouse in 3D — orbit, zoom, and click on any slot to inspect its contents, zone utilization, and recent activity.

certexi.com/app/whms/3d/editor
Loading interactive demo...

3D warehouse visualization — 3 zones, 24 slots, 13 occupied.

LiDAR Scan Visualization

LiDAR sensors provide millimeter-precision spatial mapping. Certexi processes point clouds in real time to verify slot occupancy.

certexi.com/app/whms/lidar
Loading interactive demo...

Simulated LiDAR scan — 400 classified points across walls, racks, pallets, and floor surfaces.

Architecture

Loading diagram…

Design Principles

Immutable Event Ledger

Events are append-only — no updates, no deletes. Every placement, removal, and verification is recorded as a new row. Current state is derived by replaying events.

Warehouse-Local Coordinates

The system uses floor-plan-relative (x, y) coordinates rather than GPS. This works indoors, offline, and survives floor plan updates since the stable slot_id is the primary reference.

Evidence-First Design

Every placement requires evidence:

  • Scan proof — Barcode/QR of slot + asset
  • Photo proof — Timestamped photo at placement
  • CCTV link — Optional clip from nearby camera
  • Scale data — Optional weight verification

Event Types

TypeDescription
PLACEDAsset placed in a slot
REMOVEDAsset removed from a slot
VERIFIEDSupervisor verified a placement
DISPUTEDSupervisor disputed a placement

Data Model

Slots (Master Data)

Physical storage locations with polygon geofences:

FieldTypeDescription
slot_idtextUnique identifier (e.g., "A-01-01")
zoneselectionZone category (A, B, C, etc.)
zone_typeselectionstorage, staging, receiving, shipping, cold, hazmat
floor_plan_polygonJSONArray of points defining the geofence
constraintsJSONmax_weight, max_height, allowed_types

Assets

Items that can be placed in slots:

FieldTypeDescription
asset_idtextUnique identifier (e.g., "PLT-2024-00001")
typeselectionpallet, container, box, drum, equipment
weight_kgnumberWeight in kilograms
dimensionsJSON in cm

Placement Events (Immutable)

🚨

Append-Only

The placement events table is an immutable ledger. Rows must never be updated or deleted. All state changes are recorded as new events.

Each event records: event_type, slot_id, asset_barcode, operator, timestamp, photo_url, scale_weight_kg, evidence_hash (SHA-256), and optional cctv_clip_url, work_order_ref, and verification_notes.

Deriving Current State

Since the ledger is immutable, current state is computed:

async function getSlotOccupancy(slotId: number) {
  const events = await queryEvents({
    slot_id: slotId,
    orderBy: 'timestamp',
    order: 'DESC',
  });

  const lastPlacement = events.find(
    (e) => e.event_type === 'PLACED' || e.event_type === 'REMOVED'
  );

  if (!lastPlacement || lastPlacement.event_type === 'REMOVED') {
    return { isOccupied: false, assetBarcode: null };
  }

  const isVerified = events.some(
    (e) => e.event_type === 'VERIFIED' && e.timestamp > lastPlacement.timestamp
  );

  return {
    isOccupied: true,
    assetBarcode: lastPlacement.asset_barcode,
    isVerified,
  };
}

API Endpoints

Events

  • POST /api/whms/events — Create a placement event
  • GET /api/whms/events?slot_id=X&limit=50 — Query event history

Slots

  • GET /api/whms/slots — List all slots
  • GET /api/whms/slots/:id — Get slot with computed occupancy status
  • POST /api/whms/slots — Create a slot
  • PUT /api/whms/slots/:id — Update slot metadata

Assets

  • GET /api/whms/assets — List all assets
  • GET /api/whms/assets/:barcode/location — Get asset's current location

Dashboard

  • GET /api/whms/dashboard/utilization — Zone utilization percentages

Floor Plan Editor

The layout editor uses Konva.js for polygon drawing:

  1. Upload a floor plan image
  2. Draw polygons to define slot boundaries
  3. Assign slot IDs, zones, and constraints
  4. Zone color coding for visual clarity

Component Inventory

The WHMS module is implemented across the following components. Use these paths when importing or extending behavior.

Layout & map

ComponentPathDescription
FloorPlanCanvas@/components/whms/layout-editor/floor-plan-canvasKonva-based floor plan with polygon slots
GeoSlotEditor@/components/whms/layout-editor/geo-slot-editorEdit slot polygons and metadata
SlotSidebar@/components/whms/layout-editor/slot-sidebarSlot properties and zone assignment
WhmsGeofenceMap@/components/whms/WhmsGeofenceMapLeaflet map with zone/slot layers
MinecraftGrid3D@/components/whms/MinecraftGrid3D3D grid view of zones and slots
IsometricZoneViewer@/components/whms/IsometricZoneViewerIsometric zone visualization
BirdEyeView@/components/whms/BirdEyeViewTop-down zone overview
LidarZoneViewer@/components/whms/LidarZoneViewerLidar-style zone display

Scanner & placement

ComponentPathDescription
PlacementFlow@/components/whms/scanner/placement-flowScan slot → scan asset → photo → confirm
RemovalFlow@/components/whms/scanner/removal-flowScan slot → confirm removal
ZoneScanner@/components/whms/ZoneScannerZone/slot barcode scanning
AssetDropPopup@/components/whms/AssetDropPopupIn-place drop confirmation

Zones & slots

ComponentPathDescription
ZoneDetailModal@/components/whms/ZoneDetailModalZone info, slots, CCTV, analytics
ZoneDrilldownPanel@/components/whms/ZoneDrilldownPanelSliding panel with zone details
ZoneCCTVTab@/components/whms/ZoneCCTVTabCCTV feeds for a zone
ZoneAnalyticsPanel@/components/whms/ZoneAnalyticsPanelZone metrics and charts
SlotsTab@/components/whms/SlotsTabList/grid of slots with occupancy
CCTVTab@/components/whms/CCTVTabCamera list and status

Dashboard & utilization

ComponentPathDescription
UtilizationWidget@/components/whms/dashboard/utilization-widgetZone capacity and usage
LiveOperationsFeed@/components/whms/LiveOperationsFeedReal-time placement/removal feed
RecordsTab@/components/whms/RecordsTabTable records linked to WHMS

Gamification (optional)

ComponentPathDescription
MissionsTab@/components/whms/MissionsTabMissions and transport units on map
MissionCard / MissionMarker@/components/whms/Mission UI and map markers
QuestsTab / QuestCard / QuestCreator@/components/whms/Quests and challenges
GamificationHeader@/components/whms/GamificationHeaderXP, level, streak
AdminGameSetup@/components/whms/admin/AdminGameSetupConfigure games and leaderboards

3D & panels

ComponentPathDescription
DroppableSlot3D / DraggableAsset@/components/whms/Drag-and-drop in 3D view
AssetBankDrawer / RecordPalette@/components/whms/Asset/record picker for placement
FloatingPanelManager / FloatingWindow@/components/whms/panels/Floating inventory and panels
PanelInventory / InventoryGrid@/components/whms/panels/Inventory grid in panels

UI patterns (sandboxes)

The following sandboxes illustrate the UI building blocks used across WHMS screens. Real components use the same primitives (Card, Badge, Button, Progress, Tabs) plus map/3D and API data.

Slot status card

A single slot’s occupancy and verification status in list or grid views:

Slot status card
<Card className="max-w-xs">
  <CardHeader className="pb-2 flex flex-row items-center justify-between">
    <CardTitle className="text-sm font-medium">A-01-03</CardTitle>
    <Badge variant="secondary">Staging</Badge>
  </CardHeader>
  <CardContent className="space-y-2">
    <p className="text-xs text-muted-foreground">Occupied</p>
    <p className="text-sm font-mono">PLT-2024-00042</p>
    <div className="flex gap-2">
      <Badge variant="outline">Verified</Badge>
      <Button size="sm" variant="ghost">View</Button>
    </div>
  </CardContent>
</Card>

Zone utilization row

One row of the utilization widget (zone name + capacity bar):

Zone utilization row
<div className="space-y-3 max-w-sm">
  <div className="flex justify-between text-sm">
    <span>Zone A — Storage</span>
    <span className="text-muted-foreground">72%</span>
  </div>
  <Progress value={72} className="h-2" />
  <div className="flex justify-between text-sm">
    <span>Zone B — Staging</span>
    <span className="text-muted-foreground">24 / 32 slots</span>
  </div>
  <Progress value={75} className="h-2" />
</div>

Placement flow step

A single step in the placement wizard (scan → photo → confirm):

Placement step
<Card className="max-w-sm">
  <CardHeader>
    <CardTitle className="text-base">Step 2: Photo</CardTitle>
    <CardDescription>Capture proof of placement at slot</CardDescription>
  </CardHeader>
  <CardContent className="space-y-4">
    <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
      Camera preview or placeholder
    </div>
    <div className="flex gap-2">
      <Button className="flex-1">Capture</Button>
      <Button variant="outline">Skip</Button>
    </div>
  </CardContent>
</Card>

Zone tabs (detail view)

Tabs used in zone detail (Slots, CCTV, Analytics, Incidents):

Zone detail tabs
<Tabs defaultValue="slots" className="max-w-md">
  <TabsList className="grid w-full grid-cols-4">
    <TabsTrigger value="slots">Slots</TabsTrigger>
    <TabsTrigger value="cctv">CCTV</TabsTrigger>
    <TabsTrigger value="analytics">Analytics</TabsTrigger>
    <TabsTrigger value="incidents">Incidents</TabsTrigger>
  </TabsList>
  <TabsContent value="slots" className="p-4 border rounded-b-md">
    <p className="text-sm text-muted-foreground">Slot list with occupancy and actions.</p>
  </TabsContent>
  <TabsContent value="cctv" className="p-4 border rounded-b-md">
    <p className="text-sm text-muted-foreground">Camera feeds for this zone.</p>
  </TabsContent>
  <TabsContent value="analytics" className="p-4 border rounded-b-md">
    <p className="text-sm text-muted-foreground">Charts and utilization.</p>
  </TabsContent>
  <TabsContent value="incidents" className="p-4 border rounded-b-md">
    <p className="text-sm text-muted-foreground">Incidents and disputes.</p>
  </TabsContent>
</Tabs>

Operations feed item

One line in the live operations feed (placement/removal events):

Operations feed item
<div className="flex items-center gap-3 rounded-lg border p-3 max-w-md">
  <Badge>PLACED</Badge>
  <div className="flex-1 min-w-0">
    <p className="text-sm font-medium truncate">A-02-01 ← PLT-2024-00011</p>
    <p className="text-xs text-muted-foreground">Op. García · 2 min ago</p>
  </div>
  <Button size="sm" variant="ghost">Details</Button>
</div>