Chuyển đến nội dung chính

Lesson 14: Supplier Network & Routing Engine — Multi-supplier, Quality Control & Fallback

Supplier network architecture, routing engine (geo-based, cost-based, quality-based), multi-supplier orchestration, quality control & scoring, fallback strategy, supplier onboarding, SLA monitoring, capacity planning.

🏗️ Architecture — Lesson 14 Lesson 14: Supplier Network & Routing Engine — Multi-supplier, Quality Control & Fallback

Fashion Design & Print-on-Demand System Architecture — From Domain Analysis to Production

Part 4: Order Processing & Fulfillment

xdev.asia

1. Supplier Network Architecture

Global Supplier Network
┌─────────────────────────────────────────────────────┐
│                   Routing Engine                     │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │   Geo    │ │   Cost   │ │ Quality  │            │
│  │ Router   │ │ Router   │ │ Router   │            │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘            │
│       └─────────────┼───────────┘                    │
│                     ▼                                │
│            ┌────────────────┐                        │
│            │  Score + Rank  │                        │
│            └───────┬────────┘                        │
└────────────────────┼────────────────────────────────┘
                     │
    ┌────────────────┼────────────────────────┐
    ▼                ▼                ▼       ▼
┌────────┐    ┌────────┐    ┌────────┐  ┌────────┐
│US East │    │US West │    │EU      │  │APAC    │
│Supplier│    │Supplier│    │Supplier│  │Supplier│
│        │    │        │    │        │  │        │
│DTG,DTF │    │DTG,Sub │    │DTG,DTF │  │DTG,Sub │
│T,Hoodie│    │T,AOP   │    │T,Hoodie│  │T,Mug   │
└────────┘    └────────┘    └────────┘  └────────┘

2. Supplier Routing Engine

interface SupplierRouter {
  selectBestSupplier(
    order: Order,
    items: OrderItem[],
    shippingAddress: Address,
  ): Promise<SupplierSelection>;
}

interface SupplierSelection {
  supplierId: string;
  score: number;                    // 0-100 composite score
  factors: RoutingFactor[];
  estimatedCost: Money;
  estimatedProductionDays: number;
  estimatedShippingDays: number;
  fallbackSuppliers: string[];      // Backup options
}

interface Supplier {
  id: string;
  name: string;
  region: string[];                 // ['us-east', 'us-west']
  
  // Capabilities
  supportedProducts: string[];      // ['tshirt', 'hoodie', 'mug']
  supportedMethods: PrintMethod[];  // ['dtg', 'dtf', 'sublimation']
  
  // Performance metrics
  qualityScore: number;             // 0-100 (based on QC results)
  onTimeRate: number;               // 0-1.0
  defectRate: number;               // 0-1.0 (lower = better)
  avgProductionDays: number;
  
  // Capacity
  dailyCapacity: number;            // Max items per day
  currentUtilization: number;       // 0-1.0
  
  // Pricing
  costMatrix: CostMatrix;
  
  // API integration
  apiConfig: SupplierAPIConfig;
  
  // SLA
  sla: {
    maxProductionDays: number;
    maxDefectRate: number;
    guaranteedCapacity: number;
  };
  
  status: 'active' | 'paused' | 'onboarding' | 'suspended';
}

async function selectBestSupplier(
  items: OrderItem[],
  shippingAddress: Address,
  preferences: RoutingPreferences,
): Promise<SupplierSelection> {
  // 1. Filter eligible suppliers
  const eligible = suppliers.filter(s => 
    s.status === 'active' &&
    items.every(item => 
      s.supportedProducts.includes(item.productType) &&
      s.supportedMethods.includes(item.printMethod)
    ) &&
    s.currentUtilization < 0.95  // Not at capacity
  );

  // 2. Score each supplier
  const scored = eligible.map(supplier => {
    const geoScore = calculateGeoScore(supplier, shippingAddress);
    const costScore = calculateCostScore(supplier, items);
    const qualityScore = supplier.qualityScore;
    const capacityScore = (1 - supplier.currentUtilization) * 100;
    const reliabilityScore = supplier.onTimeRate * 100;

    // Weighted composite score
    const weights = preferences.optimize === 'cost'
      ? { geo: 0.15, cost: 0.40, quality: 0.20, capacity: 0.10, reliability: 0.15 }
      : preferences.optimize === 'speed'
      ? { geo: 0.35, cost: 0.10, quality: 0.20, capacity: 0.20, reliability: 0.15 }
      : { geo: 0.20, cost: 0.20, quality: 0.25, capacity: 0.15, reliability: 0.20 }; // balanced

    const totalScore = 
      geoScore * weights.geo +
      costScore * weights.cost +
      qualityScore * weights.quality +
      capacityScore * weights.capacity +
      reliabilityScore * weights.reliability;

    return {
      supplier,
      score: totalScore,
      factors: [
        { name: 'Geo proximity', score: geoScore },
        { name: 'Cost', score: costScore },
        { name: 'Quality', score: qualityScore },
        { name: 'Capacity', score: capacityScore },
        { name: 'Reliability', score: reliabilityScore },
      ],
    };
  });

  // 3. Sort by score (highest first)
  scored.sort((a, b) => b.score - a.score);

  const best = scored[0];
  return {
    supplierId: best.supplier.id,
    score: best.score,
    factors: best.factors,
    estimatedCost: calculateSupplierCost(best.supplier, items),
    estimatedProductionDays: best.supplier.avgProductionDays,
    estimatedShippingDays: estimateShipping(best.supplier, shippingAddress),
    fallbackSuppliers: scored.slice(1, 4).map(s => s.supplier.id),
  };
}

3. Quality Control & Scoring

// Track supplier quality over time
interface QualityControlService {
  recordInspection(inspection: QCInspection): Promise<void>;
  getSupplierScore(supplierId: string): Promise<QualityScore>;
  flagSupplier(supplierId: string, reason: string): Promise<void>;
}

interface QCInspection {
  orderId: string;
  supplierId: string;
  inspectedAt: Date;
  
  // Inspection results
  printQuality: QualityRating;         // Color accuracy, sharpness
  alignmentAccuracy: QualityRating;    // Design placement
  garmentQuality: QualityRating;       // Fabric, stitching
  packagingQuality: QualityRating;
  
  // Defects found
  defects: Defect[];
  
  // Overall
  passed: boolean;
  notes: string;
  
  // Customer feedback (post-delivery)
  customerRating?: number;             // 1-5 stars
  customerFeedback?: string;
}

type QualityRating = 1 | 2 | 3 | 4 | 5;

interface Defect {
  type: DefectType;
  severity: 'minor' | 'major' | 'critical';
  description: string;
  imageUrl?: string;                   // Photo of defect
}

type DefectType =
  | 'color_shift'              // Colors don't match design
  | 'misalignment'             // Design not centered
  | 'smearing'                 // Ink smear
  | 'fading'                   // Premature fading
  | 'cracking'                 // Print cracking
  | 'ghosting'                 // Double image
  | 'wrong_product'            // Wrong garment/size/color
  | 'stain'                    // Stain on garment
  | 'packaging_damage';        // Damaged in packaging

// Update supplier quality score (rolling average)
async function updateSupplierScore(supplierId: string): Promise<number> {
  const recentInspections = await getInspections(supplierId, {
    limit: 100,
    period: '90d',
  });

  const passRate = recentInspections.filter(i => i.passed).length / recentInspections.length;
  const avgQuality = average(recentInspections.map(i => 
    (i.printQuality + i.alignmentAccuracy + i.garmentQuality + i.packagingQuality) / 4
  ));
  const defectRate = recentInspections.reduce(
    (sum, i) => sum + i.defects.length, 0
  ) / recentInspections.length;

  const score = (passRate * 40) + (avgQuality / 5 * 40) + ((1 - Math.min(defectRate, 1)) * 20);

  // Auto-suspend if score drops below threshold
  if (score < 50) {
    await flagSupplier(supplierId, `Quality score dropped to ${score.toFixed(1)}`);
  }

  return score;
}

4. Fallback & Retry Strategy

// When supplier fails, auto-reroute to fallback
async function handleSupplierFailure(
  subOrder: SubOrder,
  failureReason: string,
): Promise<SubOrder> {
  const selection = await router.selectBestSupplier(
    subOrder.items,
    subOrder.shippingAddress,
    { exclude: [subOrder.supplierId] },  // Exclude failed supplier
  );

  if (!selection) {
    // No alternative supplier available
    await alertService.critical({
      type: 'no_supplier_available',
      orderId: subOrder.parentOrderId,
      reason: failureReason,
    });
    throw new NoSupplierAvailableError();
  }

  // Re-route to fallback supplier
  const updatedSubOrder = {
    ...subOrder,
    supplierId: selection.supplierId,
    status: OrderStatus.SUBMITTED_TO_SUPPLIER,
    rerouteHistory: [
      ...(subOrder.rerouteHistory || []),
      {
        fromSupplier: subOrder.supplierId,
        toSupplier: selection.supplierId,
        reason: failureReason,
        timestamp: new Date(),
      },
    ],
  };

  // Submit to new supplier
  await supplierService.submitOrder(updatedSubOrder);

  // Notify customer about delay
  await notificationService.sendDelayNotification(subOrder.parentOrderId, {
    reason: 'Production issue — order rerouted to alternative facility',
    newEstimatedDelivery: calculateNewETA(selection),
  });

  return updatedSubOrder;
}

5. Supplier API Integration

// Abstract supplier API — mỗi supplier có API khác nhau
interface SupplierAPI {
  submitOrder(order: SupplierOrderRequest): Promise<SupplierOrderResponse>;
  getOrderStatus(supplierOrderId: string): Promise<SupplierOrderStatus>;
  cancelOrder(supplierOrderId: string): Promise<void>;
  getCatalog(): Promise<SupplierProduct[]>;
  getShippingRates(params: ShippingRateParams): Promise<ShippingRate[]>;
}

// Printful API adapter
class PrintfulAdapter implements SupplierAPI {
  async submitOrder(order: SupplierOrderRequest): Promise<SupplierOrderResponse> {
    const printfulOrder = {
      recipient: {
        name: order.shippingAddress.name,
        address1: order.shippingAddress.line1,
        city: order.shippingAddress.city,
        state_code: order.shippingAddress.state,
        country_code: order.shippingAddress.country,
        zip: order.shippingAddress.postalCode,
      },
      items: order.items.map(item => ({
        sync_variant_id: item.supplierVariantId,
        quantity: item.quantity,
        files: [{
          type: 'default',
          url: item.printFileUrl,      // Our CDN URL for print file
        }],
      })),
    };

    const response = await this.client.post('/orders', printfulOrder);
    
    return {
      supplierOrderId: response.data.result.id.toString(),
      status: 'submitted',
      estimatedShipDate: response.data.result.estimated_fulfillment,
    };
  }
}

6. Summary

ComponentStrategyKey Metric
Routing EngineWeighted multi-factor scoringGeo + Cost + Quality + Capacity
Quality ControlRolling 90-day inspection scoringPass rate, defect rate, customer rating
FallbackAuto-reroute to ranking alternativesReroute time, success rate
Capacity PlanningReal-time utilization monitoringUtilization %, daily capacity
Supplier APIAdapter pattern per supplierAPI compatibility, sync reliability