Pick & Pack Module
Enterprise WMS-style picking and packing with optimal route calculation and real-time progress tracking.
Overview
The Pick & Pack module automates the picking and packing workflow, generating optimized pick lists based on zone coordinates and routing algorithms. It integrates seamlessly with inventory management and provides real-time tracking of picking progress.
Features
Core Capabilities
- Automated Pick List Generation: Create pick lists from orders/shipments
- Optimal Route Calculation: Uses routing engine for zone-ordered picking
- Real-Time Progress Tracking: Live updates of picking status
- Pack Session Management: Track packing operations
- Inventory Integration: Automatic stock movement creation
- Multi-Item Support: Handle multiple items per pick list
- Assignment Management: Assign pick lists to workers
Architecture
graph TB
A[Orders/Shipments] --> B[Create Pick List]
B --> C[Routing Engine]
C --> D[Optimal Zone Sequence]
D --> E[Pick List Items]
E --> F[Worker Picks Items]
F --> G[Inventory Outbound]
F --> H[Pack Session]
H --> I[Completed]
style B fill:#3b82f6
style C fill:#10b981
style F fill:#f59e0bDatabase Schema
pick_lists Table
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PRIMARY KEY | Unique pick list identifier |
tenant_id | uuid | NOT NULL, FK → tenants | Tenant isolation |
created_by | uuid | NOT NULL, FK → profiles | Creator user |
assigned_to | uuid | FK → profiles, NULL | Assigned worker |
status | text | NOT NULL, CHECK | Status: 'pending', 'picking', 'completed' |
created_at | timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
updated_at | timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
idx_pick_lists_tenantontenant_ididx_pick_lists_statusonstatusidx_pick_lists_assignedonassigned_to
pick_list_items Table
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PRIMARY KEY | Unique item identifier |
tenant_id | uuid | NOT NULL, FK → tenants | Tenant isolation |
pick_list_id | uuid | NOT NULL, FK → pick_lists | Parent pick list |
item_id | uuid | NOT NULL, FK → inventory_items | Inventory item |
qty_required | integer | NOT NULL, > 0 | Required quantity |
qty_picked | integer | NOT NULL, >= 0, DEFAULT 0 | Picked quantity |
status | text | NOT NULL, CHECK | Status: 'pending', 'picked' |
zone_id | uuid | FK → zones, NULL | Item zone location |
sequence | integer | NOT NULL | Pick order (from routing) |
created_at | timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
idx_pick_list_items_pick_listonpick_list_ididx_pick_list_items_itemonitem_ididx_pick_list_items_sequenceon(pick_list_id, sequence)
pack_sessions Table
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PRIMARY KEY | Unique session identifier |
tenant_id | uuid | NOT NULL, FK → tenants | Tenant isolation |
pick_list_id | uuid | NOT NULL, FK → pick_lists | Related pick list |
packed_by | uuid | NOT NULL, FK → profiles | Packer user |
packed_at | timestamptz | NOT NULL, DEFAULT now() | Pack timestamp |
Indexes:
idx_pack_sessions_pick_listonpick_list_ididx_pack_sessions_tenantontenant_id
API Functions
Create Pick List
import { createPickList } from '@/lib/pickpack';
const { data, error } = await createPickList({
items: [
{ item_id: 'item-1-uuid', qty_required: 5 },
{ item_id: 'item-2-uuid', qty_required: 10 },
{ item_id: 'item-3-uuid', qty_required: 3 },
],
created_by: 'user-uuid',
assigned_to: 'worker-uuid', // Optional
tenant_id: 'tenant-uuid', // Optional, auto-set from context
});Parameters:
items(array, required): Array of{ item_id, qty_required, zone_id? }created_by(uuid, required): Creator user IDassigned_to(uuid, optional): Assigned worker IDtenant_id(uuid, optional): Tenant ID (auto-set from auth context)
Returns: { data: PickList | null, error: any }
Behavior:
- Fetches zone information for each item
- Calculates optimal zone sequence using routing engine
- Creates pick list with items ordered by optimal route
- Sets sequence numbers based on route order
Get Pick List with Items
import { getPickListWithItems } from '@/lib/pickpack';
const { data, error } = await getPickListWithItems('pick-list-uuid');
// Returns: PickListWithItems (includes items array)Update Pick Item Status
import { updatePickItemStatus } from '@/lib/pickpack';
// Mark item as picked
const { data, error } = await updatePickItemStatus(
'pick-list-item-uuid',
'picked',
5 // qty_picked
);Parameters:
pickListItemId(string, required): Pick list item IDstatus(string, required): 'pending' or 'picked'qtyPicked(number, required): Quantity picked
Effect:
- Updates item status and quantity
- Creates outbound movement in inventory
- Updates pick list status if all items picked
Complete Pick List
import { completePickList } from '@/lib/pickpack';
const { data, error } = await completePickList('pick-list-uuid');Effect:
- Sets pick list status to 'completed'
- Validates all items are picked
- Updates timestamps
Create Pack Session
import { createPackSession } from '@/lib/pickpack';
const { data, error } = await createPackSession({
pick_list_id: 'pick-list-uuid',
packed_by: 'user-uuid',
});Returns: { data: PackSession | null, error: any }
Routing Integration
The Pick & Pack module uses the routing engine to calculate optimal pick routes:
sequenceDiagram
participant PL[Pick List Creation]
participant RE[Routing Engine]
participant DB[Database]
PL->>DB: Get item zones
DB->>PL: Return zone IDs
PL->>RE: findOptimalPath(zoneIds)
RE->>DB: Get zone coordinates
DB->>RE: Return coordinates
RE->>RE: Calculate optimal sequence
RE->>PL: Return zone order
PL->>DB: Create items with sequenceRoute Calculation:
- Extract unique zone IDs from items
- Call
findOptimalPath()from routing engine - Use returned zone order to sequence items
- Assign sequence numbers to pick list items
Data Flow
sequenceDiagram
participant Order
participant PickList
participant Routing
participant Worker
participant Inventory
participant Pack
Order->>PickList: Create pick list
PickList->>Routing: Calculate route
Routing->>PickList: Return sequence
PickList->>Worker: Assign pick list
Worker->>PickList: Pick items
PickList->>Inventory: Create outbound
Worker->>Pack: Create pack session
Pack->>PickList: Mark completedPermissions
| Role | Permissions |
|---|---|
| Admin | Full access: create, assign, complete pick lists |
| Inventory Worker | Create pick lists, pick items, create pack sessions |
| Driver | No access to pick & pack |
| Tenant Admin | Full access within tenant |
Integration Points
Inventory Integration
When items are picked, outbound movements are automatically created:
// Automatic when item status changes to 'picked'
// Creates: stock_movement with type='outbound'Routing Integration
Uses routing engine for optimal zone sequencing:
import { findOptimalPath } from '@/lib/routingEngine';
const zoneIds = items.map(item => item.zone_id).filter(Boolean);
const { data: optimalPath } = await findOptimalPath(zoneIds);
// Use optimalPath.zone_order for item sequencingWarehouse Map Integration
Pick lists are visualized on the warehouse floor plan:
// Floor plan shows:
// - Pick list zones
// - Optimal route path
// - Current picking progressBest Practices
1. Use Optimal Routing
Always let the system calculate optimal routes:
// ✅ GOOD - System calculates route
await createPickList({ items: [...] });
// ❌ BAD - Manual sequencing
// Don't manually set sequence numbers2. Assign to Workers
Assign pick lists to specific workers for accountability:
await createPickList({
items: [...],
assigned_to: 'worker-uuid',
});3. Track Progress
Regularly check pick list status:
const { data: pickList } = await getPickListWithItems(pickListId);
const progress = pickList.items.filter(i => i.status === 'picked').length / pickList.items.length;4. Complete Pack Sessions
Always create pack sessions when packing is complete:
await createPackSession({
pick_list_id: pickListId,
packed_by: userId,
});Troubleshooting
Issue: Items Not in Optimal Order
Symptom: Pick list items not ordered by zone proximity.
Solution:
- Verify zones have coordinates (x, y, z)
- Check routing engine is working
- Re-run
computeAllDistances()script
Issue: Outbound Movement Not Created
Symptom: Picking item doesn't create inventory movement.
Solution:
- Verify item status is set to 'picked'
- Check inventory module is integrated
- Verify user has inventory permissions
Issue: Pick List Stuck in "Picking" Status
Symptom: Pick list doesn't complete even when all items picked.
Solution:
- Verify all items have status 'picked'
- Check
qty_pickedmatchesqty_required - Manually call
completePickList()if needed
Related Documentation
- Inventory Module - Provides items for picking
- Routing Engine - Calculates optimal routes
- Warehouse Map Module - Visualizes pick routes
- Multi-Tenant Architecture - Tenant isolation