Workflows - Manual vs Automated
Understanding the dual operation modes of the order system - traditional manual operations and optional pipeline automation using the same validation layer.
Dual Operation Modes
Flexible order management with two approaches:
- Manual Operations - Direct API calls for full control
- Pipeline Automation - Workflow orchestration for scale
- Shared Validation - Same business rules for both modes
- Gradual Adoption - Start manual, add automation later
- Hybrid Workflows - Mix manual and automated as needed
- No Lock-in - Switch between modes freely
Philosophy: Traditional system first, optional automation layer on top.
Core Philosophy
The De. order system is built on a fundamental principle: it works perfectly as a traditional manual system, with pipeline automation as an optional enhancement.
Traditional System First
// ✅ Manual operations work standalone
POST /api/lsp/orders/tasks/create // Create task
PATCH /api/lsp/orders/:ref/stage // Update stage
PATCH /api/lsp/orders/:ref/complete // Complete with validationNo pipeline required:
- Create orders via API
- Update stages manually
- Complete when ready
- All validations enforced
- All business rules applied
Optional Automation Layer
// ✅ Pipeline enhances but doesn't replace
Pipeline templates automate:
- Child order creation
- Stage transitions
- Failure handling
- Workflow orchestrationPipeline sits on top:
- Uses same helper functions
- Same validation layer
- Same business rules
- Enhances, doesn't replace
Why This Matters
Gradual Adoption:
Week 1: Manual operations
↓ Learn the system
Week 4: Add simple automation (auto-create children)
↓ Prove value
Week 8: Add full pipeline automation
↓ Scale operationsNo Vendor Lock-in:
- System works without pipelines
- Can switch back to manual anytime
- Mix manual + automated
- Choose per operation
Manual Operations
Creating Orders
Inbound Order (External Shipment)
POST /api/lsp/orders/inbound/create
Content-Type: application/json
{
"vendor": "VENDOR-123",
"expectedDate": "2024-03-15",
"items": [
{ "sku": "ITEM-001", "quantity": 100 },
{ "sku": "ITEM-002", "quantity": 50 }
],
"flow": {
"process": "RECEIVING",
"stage": "ARRIVAL",
"status": "PENDING"
}
}
// Response:
{
"error": false,
"reference": "IN-12345",
"message": "Inbound order created"
}Internal Task (Warehouse Operation)
POST /api/lsp/orders/tasks/create
Content-Type: application/json
{
"parentOrderReference": "OUT-001",
"process": "PICKING",
"items": [
{ "sku": "ITEM-001", "location": "A-12-3", "quantity": 2 }
],
"assignee": {
"operator": "OPR-123"
}
}
// Response:
{
"error": false,
"reference": "PICK-001",
"message": "Picking task created"
}Shipping Order (Last-Mile)
POST /api/lsp/orders/shipping/create
Content-Type: application/json
{
"parentOrderReference": "OUT-001",
"carrier": "CARRIER-001",
"driver": "DRV-456",
"destination": {
"address": "123 Main St",
"city": "Boston",
"zipCode": "02101"
},
"timeSlot": {
"from": "2024-03-15T14:00:00Z",
"to": "2024-03-15T16:00:00Z"
}
}
// Response:
{
"error": false,
"reference": "SHIP-001",
"message": "Shipping order created"
}Updating Stages
Manual Stage Transition
PATCH /api/lsp/orders/PICK-001/stage
Content-Type: application/json
{
"orderType": "InternalOrder",
"newStage": "PICKING"
}
// With validation:
// - Checks current stage (ASSIGNED)
// - Validates transition (ASSIGNED → PICKING) ✅
// - Updates if valid
// Response:
{
"error": false,
"status": "ORDER::STAGE_UPDATED",
"previousStage": "ASSIGNED",
"newStage": "PICKING"
}Invalid Transition Prevented
PATCH /api/lsp/orders/PICK-001/stage
Content-Type: application/json
{
"orderType": "InternalOrder",
"newStage": "VERIFICATION" // Skipping PICKING stage
}
// Validation fails:
{
"error": true,
"status": "ORDER::INVALID_STAGE_TRANSITION",
"message": "Cannot transition from ASSIGNED to VERIFICATION",
"currentStage": "ASSIGNED",
"requestedStage": "VERIFICATION",
"validNextStages": ["TRAVELING"] // Only valid option
}Completing Orders
Complete with Validation
PATCH /api/lsp/orders/OUT-001/complete
Content-Type: application/json
{
"orderType": "OutboundOrder"
}
// Validation checks:
// 1. Are all children complete?
// 2. Are mandatory children done?
// 3. Do any children block completion?
// If all children complete:
{
"error": false,
"status": "ORDER::COMPLETED",
"message": "Order completed successfully"
}
// If children not ready:
{
"error": true,
"status": "ORDER::COMPLETION_BLOCKED",
"message": "Cannot complete: 2 child orders not in required status",
"blockers": [
{
"reference": "PICK-001",
"type": "InternalOrder",
"currentStatus": "IN_PROGRESS",
"requiredStatuses": ["COMPLETED"]
},
{
"reference": "PACK-001",
"type": "InternalOrder",
"currentStatus": "PENDING",
"requiredStatuses": ["COMPLETED"]
}
]
}Cancelling Orders
Cancel with Compensation
PATCH /api/lsp/orders/OUT-001/cancel
Content-Type: application/json
{
"orderType": "OutboundOrder",
"reason": "Customer requested cancellation"
}
// Validation checks:
// 1. Can cancel with active children?
// 2. What should happen to children?
// If allowed:
{
"error": false,
"status": "ORDER::CANCELLED",
"message": "Order cancelled successfully",
"compensation": {
"strategy": "CASCADE_CANCEL",
"childrenUpdated": 3,
"errors": []
}
}Handling Failures
Mark Failed with Compensation
PATCH /api/lsp/orders/PICK-001/fail
Content-Type: application/json
{
"orderType": "InternalOrder",
"reason": "Items not found in location",
"failureCode": "INVENTORY_MISMATCH"
}
// Executes compensation:
// 1. Mark order failed
// 2. Trigger parent compensation
// 3. Execute cleanup actions
// Response:
{
"error": false,
"status": "ORDER::FAILED",
"message": "Order marked as failed",
"compensation": {
"self": {
"strategy": "MARK_FAILED",
"compensatedOrders": ["PICK-001"]
},
"parent": {
"strategy": "ROLLBACK_SIBLINGS",
"compensatedOrders": ["PACK-001", "SHIP-001"],
"failures": []
}
}
}Manual Workflow Example
E-Commerce Fulfillment (All Manual)
// 1. Create outbound order (customer order)
const outbound = await POST('/api/lsp/orders/outbound/create', {
customer: 'CUST-123',
items: [...]
})
// OUT-001 created
// 2. Manually create picking task
const picking = await POST('/api/lsp/orders/tasks/create', {
parentOrderReference: 'OUT-001',
process: 'PICKING',
items: [...]
})
// PICK-001 created
// 3. Operator picks items
await PATCH('/api/lsp/orders/PICK-001/stage', { newStage: 'PICKING' })
// ... picking happens ...
await PATCH('/api/lsp/orders/PICK-001/complete', { orderType: 'InternalOrder' })
// 4. Manually create packing task
const packing = await POST('/api/lsp/orders/tasks/create', {
parentOrderReference: 'OUT-001',
process: 'PACKING',
items: [...]
})
// PACK-001 created
// 5. Operator packs items
await PATCH('/api/lsp/orders/PACK-001/stage', { newStage: 'PACKING' })
// ... packing happens ...
await PATCH('/api/lsp/orders/PACK-001/complete', { orderType: 'InternalOrder' })
// 6. Manually create shipping order
const shipping = await POST('/api/lsp/orders/shipping/create', {
parentOrderReference: 'OUT-001',
carrier: 'CARRIER-001',
destination: {...}
})
// SHIP-001 created
// 7. Driver delivers
await PATCH('/api/lsp/orders/SHIP-001/stage', { newStage: 'DELIVERED' })
await PATCH('/api/lsp/orders/SHIP-001/complete', { orderType: 'ShippingOrder' })
// 8. Complete parent order (with validation)
await PATCH('/api/lsp/orders/OUT-001/complete', { orderType: 'OutboundOrder' })
// ✅ All children complete → parent completesAll operations:
- ✅ Manual API calls
- ✅ Full validation at each step
- ✅ Lifecycle policies enforced
- ✅ Process-stage validation
- ✅ No automation required
Pipeline Automation
How Pipelines Work
Pipelines use the same helper functions as manual operations:
// Manual route
.patch('/:ref/complete', async (req, rep) => {
const validation = await canCompleteOrder(order, type, db, context)
// ✅ Uses helper function
})
// Pipeline automation
async function handleSuccessTransition() {
const validation = await canCompleteOrder(order, type, db, context)
// ✅ Same helper function
}Result: Consistent validation whether manual or automated.
Pipeline Template Structure
{
id: 'pipe-outbound-v1',
name: 'Standard Outbound Fulfillment',
orderType: 'OutboundOrder',
stages: [
{
name: 'Inventory Allocation',
nextStage: 'ALLOCATION',
onSuccess: {
action: 'CREATE_CHILD',
childOrderType: 'InternalOrder',
creationReason: 'PIPELINE_STAGE',
relationship: {
type: 'MANDATORY',
canBlockParent: true
}
}
},
{
name: 'Picking',
nextStage: 'PICKING',
onSuccess: {
action: 'CONTINUE'
}
},
{
name: 'Packing',
nextStage: 'PACKING',
onSuccess: {
action: 'CREATE_CHILD',
childOrderType: 'InternalOrder'
}
},
{
name: 'Shipping',
nextStage: 'SHIPPING',
onSuccess: {
action: 'CREATE_CHILD',
childOrderType: 'ShippingOrder'
},
onFailure: {
action: 'HALT',
compensation: true
}
}
]
}Automated Workflow Example
E-Commerce Fulfillment (Pipeline Automated)
// 1. Create outbound order with pipeline
const outbound = await POST('/api/lsp/orders/outbound/create', {
customer: 'CUST-123',
items: [...],
pipelineId: 'pipe-outbound-v1' // ← Attach pipeline
})
// Pipeline automatically:
// ↓
// Stage 1: Inventory Allocation
// - Validates stage transition ✅
// - Allocates inventory
// - Auto-creates PICK-001 (InternalOrder)
// - Composition metadata added
// ↓
// Stage 2: Picking
// - PICK-001 assigned to operator
// - Operator scans items
// - Auto-transitions to PICKING stage
// - Auto-completes when done
// ↓
// Stage 3: Packing
// - Auto-creates PACK-001 (InternalOrder)
// - Operator packs
// - Auto-completes when done
// ↓
// Stage 4: Shipping
// - Auto-creates SHIP-001 (ShippingOrder)
// - Driver assigned
// - Delivery tracked
// - Auto-completes on delivery
// ↓
// Parent Auto-Completion
// - All children complete
// - Validates parent completion ✅
// - Auto-completes OUT-001All automated, but:
- ✅ Same validation as manual
- ✅ Same lifecycle policies
- ✅ Same process-stage rules
- ✅ Same compensation strategies
Child Creation with Pipelines
// Pipeline creates child using same function
await createChildOrder({
parentOrder,
childOrderType: 'InternalOrder',
childOrderId: generateId(),
childOrderReference: `PICK-${generateCode()}`,
lspOrderType: 'InternalOrder',
processor: context,
// Composition metadata (tracked)
creationReason: 'PIPELINE_STAGE',
created: {
by: {
type: 'PIPELINE',
id: 'pipe-outbound-v1',
name: 'Standard Outbound Pipeline'
},
at: { timestamp: Date.now(), timezone: 'UTC' }
},
relationship: {
type: 'MANDATORY',
canBlockParent: true,
sequence: 1
},
metadata: {
trigger: 'allocation_complete',
notes: 'Auto-created picking task'
}
}, db, context)
// ✅ Same function as manual creation
// ✅ Same validation
// ✅ Same descendant trackingStage Transitions with Pipelines
// Pipeline transitions stage
const validation = isValidStageTransition(
orderType,
order.flow.process,
order.flow.stage,
nextStage
)
if (!validation.valid) {
// ❌ Invalid transition - fail pipeline
stage.status = 'FAILED'
stage.errors.push({
code: 'INVALID_STAGE_TRANSITION',
message: validation.reason
})
return
}
// ✅ Valid - update stage
await db.updateOne(
{ reference: order.reference },
{ $set: { 'flow.stage': nextStage } }
)
// Same validation as manual operationsCompensation with Pipelines
// Pipeline fails - trigger compensation
case 'HALT':
execution.status = 'HALTED'
if (execution.orderId) {
const order = await fetchOrder(orderId)
// Execute compensation (same as manual)
const compensation = await compensateOnParentFailure(
order,
orderType,
db,
context
)
// Log results
stage.metadata.compensation = compensation
}
breakHybrid Workflows
Mix Manual and Automated
Start automated, intervene manually when needed:
// 1. Pipeline creates and processes order
const outbound = await POST('/api/lsp/orders/outbound/create', {
pipelineId: 'pipe-outbound-v1'
})
// Pipeline automatically:
// - Creates PICK-001
// - Creates PACK-001
// - Creates SHIP-001
// 2. Exception occurs - package damaged
// Operator manually creates exception task
const exception = await POST('/api/lsp/orders/tasks/create', {
parentOrderReference: 'OUT-001',
process: 'EXCEPTION',
creationReason: 'MANUAL_REQUEST', // Manual, not pipeline
relationship: {
type: 'OPTIONAL',
canBlockParent: false // Don't block shipment
},
notes: 'Package damaged during packing - replace box'
})
// 3. Pipeline continues normally
// Exception task runs in parallel
// Doesn't block parent completion
// ✅ Hybrid: Automated + Manual interventionManual Override of Automation
Pipeline automation with manual control points:
// Pipeline template with manual approval
{
stages: [
{
name: 'Picking',
nextStage: 'PICKING',
onSuccess: {
action: 'WAIT_FOR_APPROVAL', // Manual checkpoint
approverRole: 'SUPERVISOR'
}
},
{
name: 'Packing',
nextStage: 'PACKING',
onSuccess: {
action: 'CONTINUE' // Auto-proceed
}
}
]
}
// Workflow:
// 1. Auto-create picking task ✅
// 2. Operator picks items ✅
// 3. Wait for supervisor approval ⏸️ (Manual)
// 4. Supervisor approves → continue
// 5. Auto-create packing task ✅
// 6. Auto-complete ✅Comparison: Manual vs Automated
| Aspect | Manual Operations | Pipeline Automation |
|---|---|---|
| Control | Full manual control | Automated workflow |
| Speed | Slower (human steps) | Faster (automated) |
| Scalability | Limited by operators | High scalability |
| Flexibility | Very flexible | Template-constrained |
| Validation | ✅ Same | ✅ Same |
| Business Rules | ✅ Same | ✅ Same |
| Error Handling | Manual intervention | Auto-compensation |
| Learning Curve | Simple API calls | Template design needed |
| Best For | Small operations, exceptions | High volume, standard workflows |
When to Use Each Mode
Use Manual Operations When:
Low Volume
- < 100 orders per day
- Simple operations
- API calls sufficient
High Variability
- Every order different
- Custom workflows
- Exception handling
Learning Phase
- New to the system
- Understanding flows
- Testing scenarios
Exceptions
- One-off situations
- Edge cases
- Manual judgment needed
Use Pipeline Automation When:
High Volume
- 1,000+ orders per day
- Repetitive workflows
- Consistency needed
Standard Processes
- Well-defined workflows
- Predictable steps
- Few exceptions
Scale Requirements
- Growing operations
- Need automation
- Resource optimization
Complex Hierarchies
- Multi-level workflows
- Many child orders
- Automatic coordination
Use Hybrid Approach When:
Most Real Operations
- Standard flow automated
- Exceptions manual
- Best of both worlds
Gradual Automation
- Start manual
- Automate common paths
- Keep manual for edge cases
Operator-Assisted
- Automation with checkpoints
- Manual approvals
- Human-in-the-loop
Migration Path
Start Manual
Week 1-4: Learn the system
// All manual operations
POST /api/lsp/orders/tasks/create
PATCH /api/lsp/orders/:ref/stage
PATCH /api/lsp/orders/:ref/completeBenefits:
- Understand order flows
- Learn validation rules
- See how hierarchy works
- No automation complexity
Add Simple Automation
Week 5-8: Automate child creation
// Pipeline just creates children
// Everything else manual
{
stages: [
{
name: 'Start',
onSuccess: {
action: 'CREATE_CHILD', // Auto-create
childOrderType: 'InternalOrder'
}
}
]
}Benefits:
- Reduce manual child creation
- Maintain manual control
- Gradual complexity
Full Automation
Week 9+: Complete workflows
// Full pipeline automation
{
stages: [
{ name: 'Allocation', onSuccess: { action: 'CREATE_CHILD' } },
{ name: 'Picking', onSuccess: { action: 'CONTINUE' } },
{ name: 'Packing', onSuccess: { action: 'CREATE_CHILD' } },
{ name: 'Shipping', onSuccess: { action: 'COMPLETE_PIPELINE' } }
]
}Benefits:
- Full automation
- Maximum efficiency
- Proven workflows
- Scale operations
Best Practices
Start Simple
✅ Begin with manual:
// Week 1: All manual
createOrder()
updateStage()
completeOrder()✅ Add automation gradually:
// Week 4: Simple pipeline
{ action: 'CREATE_CHILD' }
// Week 8: Full pipeline
{ action: 'COMPLETE_PIPELINE' }Keep Manual Option
✅ Always allow manual override:
// Even with pipelines, support:
POST /api/lsp/orders/tasks/create // Manual creation
PATCH /api/lsp/orders/:ref/stage // Manual stage update
PATCH /api/lsp/orders/:ref/cancel // Manual cancellationMonitor Both Modes
✅ Track automation vs manual:
// Composition metadata tells you
childOrders: [{
creationReason: 'PIPELINE_STAGE', // Automated
created: { by: { type: 'PIPELINE' } }
}]
vs
childOrders: [{
creationReason: 'MANUAL_REQUEST', // Manual
created: { by: { type: 'USER', id: 'OPR-123' } }
}]Design for Flexibility
✅ Don't force automation:
// Support both:
if (req.body.pipelineId) {
// Use pipeline
} else {
// Manual workflow
}Use Cases
Small 3PL (Manual)
Operation: 50 orders/day, high variability
Approach:
- All manual API calls
- Custom workflow per client
- No automation needed
- Full flexibility
Why: Volume doesn't justify automation complexity
Large Fulfillment Center (Automated)
Operation: 10,000 orders/day, standard flow
Approach:
- Pipeline automation
- Standard workflows
- Exception handling manual
- Scale with automation
Why: Volume requires automation, standard processes
Mid-Size Warehouse (Hybrid)
Operation: 500 orders/day, mostly standard
Approach:
- Pipelines for standard orders (90%)
- Manual for exceptions (10%)
- Best of both worlds
Why: Mix of volume + variability
Next Steps
Related Documentation:

