時裝設計與按需印刷系統架構-從領域分析到生產
第 4 部分:訂單處理與履行
亞洲開發網
1.POD訂單生命週期
POD Order Flow (không có inventory — produce on demand)
Customer POD Platform Supplier/Printer
│ │ │
│ Place Order │ │
├───────────────────────────▶│ │
│ │ Validate + Payment │
│ │──────────┐ │
│ │ │ │
│ │◀─────────┘ │
│ │ │
│ │ Prepare print files │
│ │──────────┐ │
│ │ │ (render hi-res, │
│ │◀─────────┘ color convert) │
│ │ │
│ │ Submit to Supplier │
│ ├──────────────────────────────▶│
│ │ │
│ │ Printing... │
│ │ │ │
│ │ Status: In Production │
│ │◀──────────────────────────────┤
│ "Your order is being │ │
│ printed!" │ Quality Check │
│◀───────────────────────────┤ │ │
│ │ Status: Shipped + Tracking │
│ │◀──────────────────────────────┤
│ Tracking notification │ │
│◀───────────────────────────┤ │
│ │ │
│ Delivered │ │
│◀───────────────────────────┤ │
2. 訂單狀態機
// Order states specific to POD workflow
enum OrderStatus {
// Checkout phase
PENDING_PAYMENT = 'pending_payment',
PAYMENT_FAILED = 'payment_failed',
PAID = 'paid',
// Processing phase
PROCESSING = 'processing', // Preparing print files
FILE_READY = 'file_ready', // Print-ready files generated
// Production phase (at supplier)
SUBMITTED_TO_SUPPLIER = 'submitted_to_supplier',
IN_PRODUCTION = 'in_production', // Printing/pressing
QUALITY_CHECK = 'quality_check', // QC at supplier
PRODUCTION_FAILED = 'production_failed', // Print defect
// Shipping phase
PACKED = 'packed',
SHIPPED = 'shipped',
IN_TRANSIT = 'in_transit',
OUT_FOR_DELIVERY = 'out_for_delivery',
DELIVERED = 'delivered',
// Exception states
CANCELLED = 'cancelled',
REFUNDED = 'refunded',
RETURN_REQUESTED = 'return_requested',
RETURNED = 'returned',
}
// Valid state transitions
const ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.PAYMENT_FAILED, OrderStatus.CANCELLED],
[OrderStatus.PAYMENT_FAILED]: [OrderStatus.PENDING_PAYMENT, OrderStatus.CANCELLED],
[OrderStatus.PAID]: [OrderStatus.PROCESSING, OrderStatus.CANCELLED],
[OrderStatus.PROCESSING]: [OrderStatus.FILE_READY, OrderStatus.CANCELLED],
[OrderStatus.FILE_READY]: [OrderStatus.SUBMITTED_TO_SUPPLIER],
[OrderStatus.SUBMITTED_TO_SUPPLIER]: [OrderStatus.IN_PRODUCTION, OrderStatus.PRODUCTION_FAILED],
[OrderStatus.IN_PRODUCTION]: [OrderStatus.QUALITY_CHECK, OrderStatus.PRODUCTION_FAILED],
[OrderStatus.QUALITY_CHECK]: [OrderStatus.PACKED, OrderStatus.PRODUCTION_FAILED],
[OrderStatus.PRODUCTION_FAILED]: [OrderStatus.SUBMITTED_TO_SUPPLIER, OrderStatus.REFUNDED], // Retry or refund
[OrderStatus.PACKED]: [OrderStatus.SHIPPED],
[OrderStatus.SHIPPED]: [OrderStatus.IN_TRANSIT],
[OrderStatus.IN_TRANSIT]: [OrderStatus.OUT_FOR_DELIVERY, OrderStatus.DELIVERED],
[OrderStatus.OUT_FOR_DELIVERY]: [OrderStatus.DELIVERED],
[OrderStatus.DELIVERED]: [OrderStatus.RETURN_REQUESTED],
[OrderStatus.RETURN_REQUESTED]: [OrderStatus.RETURNED, OrderStatus.DELIVERED],
[OrderStatus.RETURNED]: [OrderStatus.REFUNDED],
[OrderStatus.CANCELLED]: [OrderStatus.REFUNDED],
[OrderStatus.REFUNDED]: [],
};
class OrderStateMachine {
transition(order: Order, newStatus: OrderStatus): Order {
const allowedTransitions = ORDER_TRANSITIONS[order.status];
if (!allowedTransitions.includes(newStatus)) {
throw new InvalidStateTransitionError(
`Cannot transition from ${order.status} to ${newStatus}`
);
}
return {
...order,
status: newStatus,
statusHistory: [
...order.statusHistory,
{ status: newStatus, timestamp: new Date(), actor: 'system' },
],
};
}
}
3. Saga模式-訂單編排
// Orchestration Saga cho order processing
// Mỗi step có compensation (rollback) nếu step sau fail
interface OrderSaga {
steps: SagaStep[];
execute(order: Order): Promise<SagaResult>;
}
interface SagaStep {
name: string;
execute: (context: SagaContext) => Promise<void>;
compensate: (context: SagaContext) => Promise<void>; // Rollback
}
const orderSagaSteps: SagaStep[] = [
{
name: 'validate_order',
execute: async (ctx) => {
await validateOrderItems(ctx.order);
await checkSupplierAvailability(ctx.order);
},
compensate: async (ctx) => {
// Nothing to compensate
},
},
{
name: 'process_payment',
execute: async (ctx) => {
ctx.paymentResult = await paymentService.charge(ctx.order);
},
compensate: async (ctx) => {
// Refund payment
await paymentService.refund(ctx.paymentResult.chargeId, ctx.order.total);
},
},
{
name: 'generate_print_files',
execute: async (ctx) => {
ctx.printFiles = await printFileService.generate(ctx.order);
},
compensate: async (ctx) => {
await printFileService.cleanup(ctx.printFiles);
},
},
{
name: 'submit_to_supplier',
execute: async (ctx) => {
ctx.supplierOrder = await supplierService.submitOrder(
ctx.order,
ctx.printFiles,
);
},
compensate: async (ctx) => {
await supplierService.cancelOrder(ctx.supplierOrder.id);
},
},
{
name: 'notify_customer',
execute: async (ctx) => {
await notificationService.sendOrderConfirmation(ctx.order);
},
compensate: async (ctx) => {
await notificationService.sendOrderCancellation(ctx.order);
},
},
];
// Saga executor with automatic compensation on failure
class SagaExecutor {
async execute(steps: SagaStep[], context: SagaContext): Promise<SagaResult> {
const completedSteps: SagaStep[] = [];
for (const step of steps) {
try {
await step.execute(context);
completedSteps.push(step);
} catch (error) {
// Compensate all completed steps in reverse order
for (const completedStep of completedSteps.reverse()) {
try {
await completedStep.compensate(context);
} catch (compensateError) {
// Log compensation failure — needs manual intervention
await alertService.criticalAlert({
type: 'saga_compensation_failed',
step: completedStep.name,
orderId: context.order.id,
error: compensateError.message,
});
}
}
return { success: false, failedStep: step.name, error: error.message };
}
}
return { success: true };
}
}
4. 拆分訂單(多供應商)
// Khi 1 order có items từ nhiều suppliers → split thành sub-orders
interface OrderSplitter {
split(order: Order): SubOrder[];
}
interface SubOrder {
id: string;
parentOrderId: string;
supplierId: string;
items: OrderItem[];
status: OrderStatus;
// Supplier-specific
supplierOrderId?: string; // ID từ supplier API
shippingMethod: string;
trackingNumber?: string;
}
function splitOrderBySupplier(order: Order): SubOrder[] {
// Group items by optimal supplier
const supplierGroups = new Map<string, OrderItem[]>();
for (const item of order.items) {
const bestSupplier = selectBestSupplier(item, order.shippingAddress);
const group = supplierGroups.get(bestSupplier.id) || [];
group.push(item);
supplierGroups.set(bestSupplier.id, group);
}
return Array.from(supplierGroups.entries()).map(([supplierId, items]) => ({
id: generateSubOrderId(),
parentOrderId: order.id,
supplierId,
items,
status: OrderStatus.PROCESSING,
shippingMethod: selectShippingMethod(supplierId, order.shippingAddress),
}));
}
// Parent order status = worst child status
function aggregateOrderStatus(subOrders: SubOrder[]): OrderStatus {
const priorities = [
OrderStatus.PRODUCTION_FAILED, // Highest priority (worst)
OrderStatus.PROCESSING,
OrderStatus.FILE_READY,
OrderStatus.SUBMITTED_TO_SUPPLIER,
OrderStatus.IN_PRODUCTION,
OrderStatus.QUALITY_CHECK,
OrderStatus.PACKED,
OrderStatus.SHIPPED,
OrderStatus.IN_TRANSIT,
OrderStatus.DELIVERED, // Lowest priority (best)
];
let worstIndex = priorities.length - 1;
for (const sub of subOrders) {
const idx = priorities.indexOf(sub.status);
if (idx >= 0 && idx < worstIndex) {
worstIndex = idx;
}
}
return priorities[worstIndex];
}
5. 訂單事件溯源
// Mọi thay đổi order được lưu dưới dạng events
interface OrderEvent {
eventId: string;
orderId: string;
eventType: string;
data: Record<string, unknown>;
timestamp: Date;
actor: string; // 'system', 'customer', 'admin', 'supplier'
version: number; // For optimistic concurrency
}
type OrderEventType =
| 'OrderCreated'
| 'PaymentReceived'
| 'PrintFileGenerated'
| 'SubmittedToSupplier'
| 'ProductionStarted'
| 'QualityCheckPassed'
| 'QualityCheckFailed'
| 'Shipped'
| 'TrackingUpdated'
| 'Delivered'
| 'CancelRequested'
| 'Cancelled'
| 'RefundIssued'
| 'ReturnRequested';
// Rebuild order state from events
function rehydrateOrder(events: OrderEvent[]): Order {
let order: Partial = {};
for (const event of events) {
switch (event.eventType) {
case 'OrderCreated':
order = { ...event.data, status: OrderStatus.PENDING_PAYMENT };
break;
case 'PaymentReceived':
order.status = OrderStatus.PAID;
order.paymentId = event.data.paymentId as string;
break;
case 'Shipped':
order.status = OrderStatus.SHIPPED;
order.trackingNumber = event.data.trackingNumber as string;
break;
// ... handle each event type
}
}
return order as Order;
}
六、總結
| 圖案 | 目的 | POD 上下文 |
| 狀態機 | 強制執行有效的訂單轉換 | 超過 15 個特定於 POD 生產流程的狀態 |
| 傳奇模式 | 分散式事務協調 | 付款 → 列印文件 → 供應商 → 通知回滾 |
| 分割訂單 | 多供應商履行 | 1 個客戶訂單 → N 個供應商子訂單 |
| 事件溯源 | 完整的審計追踪 | 從事件歷史記錄中重建訂單狀態 |
| 冪等性 | 處理重複的 webhook/重試 | 每個操作的冪等鍵 |