wms-testing-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WMS Testing Patterns

WMS测试模式

<default_to_action> When testing Warehouse Management Systems:
  1. VALIDATE inventory accuracy (receipt, putaway, cycle count, adjustments)
  2. TEST pick/pack/ship workflows end-to-end (wave release -> shipment confirm)
  3. VERIFY EDI message processing (856 ASN, 940 Order, 945 Confirmation, 943/944 Stock)
  4. ASSERT RF/barcode scanning flows (scan -> validate -> update -> confirm)
  5. EXERCISE allocation and replenishment logic (FIFO, FEFO, lot control)
  6. TEST WMS-ERP integration (inventory sync, order status, goods receipt)
  7. VALIDATE wave management (wave planning, release, short-pick handling)
Quick Pattern Selection:
  • Inventory discrepancies -> Cycle count and adjustment tests
  • Order fulfillment issues -> Pick/pack/ship workflow tests
  • EDI failures -> Message format and acknowledgment tests
  • Scanning problems -> RF device simulation tests
  • Allocation errors -> Lot control and FIFO/FEFO tests
  • Integration gaps -> WMS-ERP sync boundary tests
Critical Success Factors:
  • WMS accuracy directly impacts customer experience and financial reporting
  • Always test both normal and exception flows (short picks, damaged goods, returns)
  • EDI testing must cover both syntax validation AND business rule validation </default_to_action>
<default_to_action> 测试仓库管理系统时:
  1. 验证库存准确性(收货、上架、周期盘点、调整)
  2. 端到端测试拣选/包装/发货流程(波次释放 -> 发货确认)
  3. 验证EDI消息处理(856 ASN、940订单、945确认、943/944库存)
  4. 断言RF/条码扫描流程(扫描 -> 验证 -> 更新 -> 确认)
  5. 演练分配和补货逻辑(FIFO、FEFO、批次控制)
  6. 测试WMS-ERP集成(库存同步、订单状态、收货)
  7. 验证波次管理(波次规划、释放、短拣处理)
快速模式选择:
  • 库存差异 -> 周期盘点和调整测试
  • 订单履约问题 -> 拣选/包装/发货流程测试
  • EDI故障 -> 消息格式和确认测试
  • 扫描问题 -> RF设备模拟测试
  • 分配错误 -> 批次控制和FIFO/FEFO测试
  • 集成缺口 -> WMS-ERP同步边界测试
关键成功因素:
  • WMS准确性直接影响客户体验和财务报告
  • 务必测试正常流程和异常流程(短拣、损坏商品、退货)
  • EDI测试必须涵盖语法验证和业务规则验证 </default_to_action>

Quick Reference Card

快速参考卡片

When to Use

适用场景

  • Testing WMS platforms (Blue Yonder/JDA, Manhattan Associates, SAP EWM, Oracle WMS)
  • Validating inventory transaction accuracy (receipts, picks, adjustments)
  • Testing EDI document exchange (X12 856/940/945/943/944, EDIFACT DESADV/ORDERS)
  • Verifying RF/mobile scanning workflows
  • Testing wave management and allocation logic
  • Validating WMS-to-ERP integration (SAP MM/WM, Oracle)
  • 测试WMS平台(Blue Yonder/JDA、Manhattan Associates、SAP EWM、Oracle WMS)
  • 验证库存交易准确性(收货、拣选、调整)
  • 测试EDI文档交换(X12 856/940/945/943/944、EDIFACT DESADV/ORDERS)
  • 验证RF/移动扫描工作流程
  • 测试波次管理和分配逻辑
  • 验证WMS到ERP的集成(SAP MM/WM、Oracle)

Testing Levels

测试层级

LevelPurposeDependenciesSpeed
Unit LogicAllocation/replenishment rulesNoneFast
API ContractWMS REST/SOAP endpoint contractsAPI stubsFast
EDI ValidationDocument syntax + business rulesEDI parserMedium
IntegrationWMS-ERP inventory syncFull stackSlower
E2E WorkflowComplete order fulfillment cycleWMS + ERP + TMSSlow
层级目的依赖项执行速度
单元逻辑分配/补货规则快速
API契约WMS REST/SOAP端点契约API桩快速
EDI验证文档语法 + 业务规则EDI解析器中等
集成测试WMS-ERP库存同步全栈环境较慢
端到端流程完整订单履周期WMS + ERP + TMS缓慢

Critical Test Scenarios

关键测试场景

ScenarioMust TestExample
ReceivingInbound accuracyPO receipt with over/under delivery tolerance
PutawayLocation assignmentDirected putaway by product attributes
PickingWave/batch pickMulti-order wave with priority allocation
PackingPack verificationCartonization and weight/dim validation
ShippingCarrier assignmentRate shopping and label generation
Cycle CountInventory accuracyBlind count vs. guided count reconciliation
ReturnsReverse logisticsRMA receipt, inspection, disposition
EDI 856ASN generationShip-confirm triggers correct ASN to customer
EDI 940Warehouse orderInbound order creates correct work orders
Short PickException handlingPartial allocation with backorder creation
场景必须测试项示例
收货入库准确性含超收/欠收容差的PO收货
上架位置分配按商品属性定向上架
拣选波次/批次拣选含优先级分配的多订单波次
包装包装验证装箱和重量/尺寸验证
发货承运人分配费率比价和标签生成
周期盘点库存准确性盲盘与引导盘对账
退货逆向物流RMA收货、检验、处置
EDI 856ASN生成发货确认触发生成正确的ASN给客户
EDI 940仓库订单入库订单创建正确的工作订单
短拣异常处理部分分配并创建欠交订单

Tools

工具

  • WMS Platforms: Blue Yonder WMS, Manhattan WMOS, SAP EWM, Oracle WMS Cloud
  • EDI Testing: Bots EDI, SPS Commerce Test, Cleo Clarify
  • RF Simulation: Custom RF emulators, Zebra SDK test harnesses
  • Integration: SAP PI/PO, MuleSoft, Dell Boomi
  • Monitoring: Splunk, WMS dashboards, EDI tracking portals
  • WMS平台:Blue Yonder WMS、Manhattan WMOS、SAP EWM、Oracle WMS Cloud
  • EDI测试:Bots EDI、SPS Commerce Test、Cleo Clarify
  • RF模拟:自定义RF模拟器、Zebra SDK测试工具
  • 集成工具:SAP PI/PO、MuleSoft、Dell Boomi
  • 监控工具:Splunk、WMS仪表板、EDI跟踪门户

Agent Coordination

Agent协调

  • qe-middleware-validator
    : WMS-ERP integration flows and message transformation validation
  • qe-contract-validator
    : EDI document contracts and WMS API contracts
  • qe-sap-idoc-tester
    : WMS-SAP IDoc validation (WMMBID01, SHPCON)
  • qe-odata-contract-tester
    : SAP EWM OData service testing

  • qe-middleware-validator
    :WMS-ERP集成流程和消息转换验证
  • qe-contract-validator
    :EDI文档契约和WMS API契约
  • qe-sap-idoc-tester
    :WMS-SAP IDoc验证(WMMBID01、SHPCON)
  • qe-odata-contract-tester
    :SAP EWM OData服务测试

Inventory Transaction Testing

库存交易测试

Receipt and Putaway

收货与上架

javascript
describe('Inbound Receipt - PO with Lot Control', () => {
  it('receives goods against PO with lot tracking', async () => {
    const receipt = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 100,
      lot: 'LOT-2026-001',
      expiryDate: '2027-06-30',
      uom: 'EA'
    });

    expect(receipt.status).toBe('RECEIVED');
    expect(receipt.quantityReceived).toBe(100);
    expect(receipt.lot).toBe('LOT-2026-001');

    // Verify putaway task was created
    const putawayTask = await wms.getTask(receipt.putawayTaskId);
    expect(putawayTask.type).toBe('DIRECTED_PUTAWAY');
    expect(putawayTask.suggestedLocation).toMatch(/^BIN-/);
  });

  it('rejects over-receipt beyond tolerance', async () => {
    // PO line is for 100 EA, tolerance is 10%
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 115, // 15% over, exceeds 10% tolerance
      lot: 'LOT-2026-002'
    });

    expect(result.status).toBe('REJECTED');
    expect(result.reason).toContain('Over-receipt tolerance exceeded');
    expect(result.maxAllowed).toBe(110);
  });

  it('accepts under-receipt and creates remaining open quantity', async () => {
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 80,
      lot: 'LOT-2026-003'
    });

    expect(result.status).toBe('PARTIAL_RECEIPT');
    expect(result.quantityReceived).toBe(80);
    expect(result.remainingOpen).toBe(20);
  });
});

describe('Directed Putaway', () => {
  it('assigns location based on product attributes', async () => {
    // Hazmat product should go to hazmat zone
    const putaway = await wms.executePutaway({
      item: 'MAT-HAZ-001',
      attributes: { hazmat: true, temperature: 'ambient' },
      quantity: 50,
      lot: 'LOT-HAZ-001'
    });

    expect(putaway.location).toMatch(/^HAZ-/); // Hazmat zone prefix
    expect(putaway.status).toBe('PUTAWAY_COMPLETE');
  });

  it('falls back to overflow when primary zone is full', async () => {
    // Fill the primary zone first
    await wms.fillZone('ZONE-A', 100); // 100% capacity

    const putaway = await wms.executePutaway({
      item: 'MAT-200',
      quantity: 10,
      preferredZone: 'ZONE-A'
    });

    expect(putaway.location).toMatch(/^OVF-/); // Overflow zone
    expect(putaway.overflowReason).toBe('PRIMARY_ZONE_FULL');
  });
});
javascript
describe('Inbound Receipt - PO with Lot Control', () => {
  it('receives goods against PO with lot tracking', async () => {
    const receipt = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 100,
      lot: 'LOT-2026-001',
      expiryDate: '2027-06-30',
      uom: 'EA'
    });

    expect(receipt.status).toBe('RECEIVED');
    expect(receipt.quantityReceived).toBe(100);
    expect(receipt.lot).toBe('LOT-2026-001');

    // Verify putaway task was created
    const putawayTask = await wms.getTask(receipt.putawayTaskId);
    expect(putawayTask.type).toBe('DIRECTED_PUTAWAY');
    expect(putawayTask.suggestedLocation).toMatch(/^BIN-/);
  });

  it('rejects over-receipt beyond tolerance', async () => {
    // PO line is for 100 EA, tolerance is 10%
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 115, // 15% over, exceeds 10% tolerance
      lot: 'LOT-2026-002'
    });

    expect(result.status).toBe('REJECTED');
    expect(result.reason).toContain('Over-receipt tolerance exceeded');
    expect(result.maxAllowed).toBe(110);
  });

  it('accepts under-receipt and creates remaining open quantity', async () => {
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 80,
      lot: 'LOT-2026-003'
    });

    expect(result.status).toBe('PARTIAL_RECEIPT');
    expect(result.quantityReceived).toBe(80);
    expect(result.remainingOpen).toBe(20);
  });
});

describe('Directed Putaway', () => {
  it('assigns location based on product attributes', async () => {
    // Hazmat product should go to hazmat zone
    const putaway = await wms.executePutaway({
      item: 'MAT-HAZ-001',
      attributes: { hazmat: true, temperature: 'ambient' },
      quantity: 50,
      lot: 'LOT-HAZ-001'
    });

    expect(putaway.location).toMatch(/^HAZ-/); // Hazmat zone prefix
    expect(putaway.status).toBe('PUTAWAY_COMPLETE');
  });

  it('falls back to overflow when primary zone is full', async () => {
    // Fill the primary zone first
    await wms.fillZone('ZONE-A', 100); // 100% capacity

    const putaway = await wms.executePutaway({
      item: 'MAT-200',
      quantity: 10,
      preferredZone: 'ZONE-A'
    });

    expect(putaway.location).toMatch(/^OVF-/); // Overflow zone
    expect(putaway.overflowReason).toBe('PRIMARY_ZONE_FULL');
  });
});

Cycle Count and Adjustments

周期盘点与调整

javascript
describe('Cycle Count - Blind Count', () => {
  it('auto-approves variance within threshold', async () => {
    // System shows 100 EA, physical count is 98
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-01',
      item: 'MAT-100',
      countedQuantity: 98,
      countType: 'BLIND', // Counter does not see system quantity
      varianceThreshold: 5 // percent
    });

    expect(count.systemQuantity).toBe(100);
    expect(count.variance).toBe(-2);
    expect(count.variancePercent).toBeCloseTo(2.0);
    expect(count.autoApproved).toBe(true);
    expect(count.adjustmentPosted).toBe(true);
  });

  it('requires supervisor approval when variance exceeds threshold', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-02',
      item: 'MAT-200',
      countedQuantity: 80,
      countType: 'BLIND',
      varianceThreshold: 5 // 20% variance exceeds 5% threshold
    });

    expect(count.variancePercent).toBeCloseTo(20.0);
    expect(count.autoApproved).toBe(false);
    expect(count.adjustmentPosted).toBe(false);
    expect(count.status).toBe('PENDING_SUPERVISOR_APPROVAL');
  });

  it('triggers recount when variance is critical', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-03',
      item: 'MAT-300',
      countedQuantity: 0, // System shows 50
      countType: 'BLIND',
      varianceThreshold: 5,
      criticalThreshold: 50
    });

    expect(count.variancePercent).toBe(100);
    expect(count.status).toBe('RECOUNT_REQUIRED');
    expect(count.recountAssigned).toBe(true);
    expect(count.recountAssignee).not.toBe(count.originalCounter);
  });
});

javascript
describe('Cycle Count - Blind Count', () => {
  it('auto-approves variance within threshold', async () => {
    // System shows 100 EA, physical count is 98
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-01',
      item: 'MAT-100',
      countedQuantity: 98,
      countType: 'BLIND', // Counter does not see system quantity
      varianceThreshold: 5 // percent
    });

    expect(count.systemQuantity).toBe(100);
    expect(count.variance).toBe(-2);
    expect(count.variancePercent).toBeCloseTo(2.0);
    expect(count.autoApproved).toBe(true);
    expect(count.adjustmentPosted).toBe(true);
  });

  it('requires supervisor approval when variance exceeds threshold', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-02',
      item: 'MAT-200',
      countedQuantity: 80,
      countType: 'BLIND',
      varianceThreshold: 5 // 20% variance exceeds 5% threshold
    });

    expect(count.variancePercent).toBeCloseTo(20.0);
    expect(count.autoApproved).toBe(false);
    expect(count.adjustmentPosted).toBe(false);
    expect(count.status).toBe('PENDING_SUPERVISOR_APPROVAL');
  });

  it('triggers recount when variance is critical', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-03',
      item: 'MAT-300',
      countedQuantity: 0, // System shows 50
      countType: 'BLIND',
      varianceThreshold: 5,
      criticalThreshold: 50
    });

    expect(count.variancePercent).toBe(100);
    expect(count.status).toBe('RECOUNT_REQUIRED');
    expect(count.recountAssigned).toBe(true);
    expect(count.recountAssignee).not.toBe(count.originalCounter);
  });
});

Pick/Pack/Ship Workflow Testing

拣选/包装/发货流程测试

Wave Management

波次管理

javascript
describe('Wave Planning and Release', () => {
  it('creates wave from eligible orders with batch-pick strategy', async () => {
    // Create 5 orders for wave planning
    const orders = await Promise.all(
      Array.from({ length: 5 }, (_, i) => wms.createOrder({
        orderId: `SO-100${i}`,
        lines: [
          { item: 'MAT-100', qty: 10 },
          { item: 'MAT-200', qty: 5 }
        ],
        priority: i === 0 ? 'RUSH' : 'STANDARD',
        shipBy: '2026-02-05'
      }))
    );

    const wave = await wms.planWave({
      strategy: 'BATCH_PICK',
      maxOrders: 10,
      cutoffTime: '2026-02-05T14:00:00Z'
    });

    expect(wave.orderCount).toBe(5);
    expect(wave.pickTasks).toBeGreaterThan(0);
    expect(wave.status).toBe('PLANNED');

    // Rush order should be first in pick sequence
    const firstTask = wave.pickTasks[0];
    expect(firstTask.orderId).toBe('SO-1000');
    expect(firstTask.priority).toBe('RUSH');
  });

  it('handles short-pick by creating backorder', async () => {
    // Only 5 EA available, order needs 10
    await wms.setInventory('BIN-B-01-01', 'MAT-500', 5);

    const order = await wms.createOrder({
      orderId: 'SO-SHORT',
      lines: [{ item: 'MAT-500', qty: 10 }]
    });

    const wave = await wms.planWave({ strategy: 'SINGLE_ORDER' });
    await wms.releaseWave(wave.id);

    // Attempt to pick
    const pickResult = await wms.confirmPick({
      taskId: wave.pickTasks[0].id,
      pickedQty: 5 // Only 5 available
    });

    expect(pickResult.status).toBe('SHORT_PICK');
    expect(pickResult.pickedQuantity).toBe(5);
    expect(pickResult.shortQuantity).toBe(5);
    expect(pickResult.backorderCreated).toBe(true);
    expect(pickResult.backorderId).toBeDefined();
  });
});
javascript
describe('Wave Planning and Release', () => {
  it('creates wave from eligible orders with batch-pick strategy', async () => {
    // Create 5 orders for wave planning
    const orders = await Promise.all(
      Array.from({ length: 5 }, (_, i) => wms.createOrder({
        orderId: `SO-100${i}`,
        lines: [
          { item: 'MAT-100', qty: 10 },
          { item: 'MAT-200', qty: 5 }
        ],
        priority: i === 0 ? 'RUSH' : 'STANDARD',
        shipBy: '2026-02-05'
      }))
    );

    const wave = await wms.planWave({
      strategy: 'BATCH_PICK',
      maxOrders: 10,
      cutoffTime: '2026-02-05T14:00:00Z'
    });

    expect(wave.orderCount).toBe(5);
    expect(wave.pickTasks).toBeGreaterThan(0);
    expect(wave.status).toBe('PLANNED');

    // Rush order should be first in pick sequence
    const firstTask = wave.pickTasks[0];
    expect(firstTask.orderId).toBe('SO-1000');
    expect(firstTask.priority).toBe('RUSH');
  });

  it('handles short-pick by creating backorder', async () => {
    // Only 5 EA available, order needs 10
    await wms.setInventory('BIN-B-01-01', 'MAT-500', 5);

    const order = await wms.createOrder({
      orderId: 'SO-SHORT',
      lines: [{ item: 'MAT-500', qty: 10 }]
    });

    const wave = await wms.planWave({ strategy: 'SINGLE_ORDER' });
    await wms.releaseWave(wave.id);

    // Attempt to pick
    const pickResult = await wms.confirmPick({
      taskId: wave.pickTasks[0].id,
      pickedQty: 5 // Only 5 available
    });

    expect(pickResult.status).toBe('SHORT_PICK');
    expect(pickResult.pickedQuantity).toBe(5);
    expect(pickResult.shortQuantity).toBe(5);
    expect(pickResult.backorderCreated).toBe(true);
    expect(pickResult.backorderId).toBeDefined();
  });
});

Allocation Logic

分配逻辑

javascript
describe('FIFO Allocation', () => {
  it('allocates oldest lot first', async () => {
    // Set up two lots with different receipt dates
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-OLD', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-NEW', receiptDate: '2026-02-01' });

    const allocation = await wms.allocate({
      item: 'MAT-FIFO',
      quantity: 30,
      method: 'FIFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-OLD');
    expect(allocation.quantity).toBe(30);
  });

  it('splits allocation across lots when oldest is insufficient', async () => {
    await wms.receiveInventory('MAT-SPLIT', 20, { lot: 'LOT-A', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-SPLIT', 50, { lot: 'LOT-B', receiptDate: '2026-01-15' });

    const allocation = await wms.allocate({
      item: 'MAT-SPLIT',
      quantity: 40,
      method: 'FIFO'
    });

    expect(allocation.splits).toHaveLength(2);
    expect(allocation.splits[0]).toEqual({ lot: 'LOT-A', quantity: 20 });
    expect(allocation.splits[1]).toEqual({ lot: 'LOT-B', quantity: 20 });
  });
});

describe('FEFO Allocation (First Expired, First Out)', () => {
  it('allocates lot with earliest expiry first', async () => {
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-LATE', expiry: '2027-12-31' });
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-SOON', expiry: '2026-06-30' });

    const allocation = await wms.allocate({
      item: 'MAT-FEFO',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-EXP-SOON');
  });

  it('excludes expired lots from allocation', async () => {
    await wms.receiveInventory('MAT-EXPIRED', 100, { lot: 'LOT-PAST', expiry: '2025-12-31' });
    await wms.receiveInventory('MAT-EXPIRED', 50, { lot: 'LOT-VALID', expiry: '2027-12-31' });

    const allocation = await wms.allocate({
      item: 'MAT-EXPIRED',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-VALID');
    expect(allocation.excludedLots).toContain('LOT-PAST');
    expect(allocation.excludeReason).toBe('EXPIRED');
  });
});
javascript
describe('FIFO Allocation', () => {
  it('allocates oldest lot first', async () => {
    // Set up two lots with different receipt dates
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-OLD', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-NEW', receiptDate: '2026-02-01' });

    const allocation = await wms.allocate({
      item: 'MAT-FIFO',
      quantity: 30,
      method: 'FIFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-OLD');
    expect(allocation.quantity).toBe(30);
  });

  it('splits allocation across lots when oldest is insufficient', async () => {
    await wms.receiveInventory('MAT-SPLIT', 20, { lot: 'LOT-A', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-SPLIT', 50, { lot: 'LOT-B', receiptDate: '2026-01-15' });

    const allocation = await wms.allocate({
      item: 'MAT-SPLIT',
      quantity: 40,
      method: 'FIFO'
    });

    expect(allocation.splits).toHaveLength(2);
    expect(allocation.splits[0]).toEqual({ lot: 'LOT-A', quantity: 20 });
    expect(allocation.splits[1]).toEqual({ lot: 'LOT-B', quantity: 20 });
  });
});

describe('FEFO Allocation (First Expired, First Out)', () => {
  it('allocates lot with earliest expiry first', async () => {
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-LATE', expiry: '2027-12-31' });
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-SOON', expiry: '2026-06-30' });

    const allocation = await wms.allocate({
      item: 'MAT-FEFO',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-EXP-SOON');
  });

  it('excludes expired lots from allocation', async () => {
    await wms.receiveInventory('MAT-EXPIRED', 100, { lot: 'LOT-PAST', expiry: '2025-12-31' });
    await wms.receiveInventory('MAT-EXPIRED', 50, { lot: 'LOT-VALID', expiry: '2027-12-31' });

    const allocation = await wms.allocate({
      item: 'MAT-EXPIRED',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-VALID');
    expect(allocation.excludedLots).toContain('LOT-PAST');
    expect(allocation.excludeReason).toBe('EXPIRED');
  });
});

Packing and Shipping

包装与发货

javascript
describe('Pack Verification', () => {
  it('verifies pack contents match pick list', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-001',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 },
        { barcode: 'MAT-200-LOT002', qty: 5 }
      ]
    });

    expect(packResult.status).toBe('VERIFIED');
    expect(packResult.allItemsScanned).toBe(true);
    expect(packResult.weightValidation).toBe('WITHIN_TOLERANCE');
  });

  it('rejects pack with missing items', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-002',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 }
        // Missing MAT-200
      ]
    });

    expect(packResult.status).toBe('INCOMPLETE');
    expect(packResult.missingItems).toContainEqual({ item: 'MAT-200', expectedQty: 5 });
  });
});

describe('Shipping and Carrier Assignment', () => {
  it('rate-shops across carriers and selects cheapest', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1001',
      cartons: ['CTN-001'],
      carrierSelection: 'RATE_SHOP',
      eligibleCarriers: ['UPS', 'FEDEX', 'USPS']
    });

    expect(shipment.status).toBe('SHIPPED');
    expect(shipment.carrier).toBeDefined();
    expect(shipment.trackingNumber).toBeDefined();
    expect(shipment.rateShopResults).toHaveLength(3);
    expect(shipment.selectedRate).toBeLessThanOrEqual(
      Math.min(...shipment.rateShopResults.map(r => r.rate))
    );
  });

  it('generates shipping label and triggers EDI 856', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1002',
      cartons: ['CTN-003'],
      carrier: 'UPS',
      service: 'GROUND'
    });

    expect(shipment.label).toBeDefined();
    expect(shipment.label.format).toBe('ZPL');
    expect(shipment.edi856Generated).toBe(true);
    expect(shipment.edi856.transactionSetId).toMatch(/^856/);
  });
});

javascript
describe('Pack Verification', () => {
  it('verifies pack contents match pick list', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-001',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 },
        { barcode: 'MAT-200-LOT002', qty: 5 }
      ]
    });

    expect(packResult.status).toBe('VERIFIED');
    expect(packResult.allItemsScanned).toBe(true);
    expect(packResult.weightValidation).toBe('WITHIN_TOLERANCE');
  });

  it('rejects pack with missing items', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-002',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 }
        // Missing MAT-200
      ]
    });

    expect(packResult.status).toBe('INCOMPLETE');
    expect(packResult.missingItems).toContainEqual({ item: 'MAT-200', expectedQty: 5 });
  });
});

describe('Shipping and Carrier Assignment', () => {
  it('rate-shops across carriers and selects cheapest', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1001',
      cartons: ['CTN-001'],
      carrierSelection: 'RATE_SHOP',
      eligibleCarriers: ['UPS', 'FEDEX', 'USPS']
    });

    expect(shipment.status).toBe('SHIPPED');
    expect(shipment.carrier).toBeDefined();
    expect(shipment.trackingNumber).toBeDefined();
    expect(shipment.rateShopResults).toHaveLength(3);
    expect(shipment.selectedRate).toBeLessThanOrEqual(
      Math.min(...shipment.rateShopResults.map(r => r.rate))
    );
  });

  it('generates shipping label and triggers EDI 856', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1002',
      cartons: ['CTN-003'],
      carrier: 'UPS',
      service: 'GROUND'
    });

    expect(shipment.label).toBeDefined();
    expect(shipment.label.format).toBe('ZPL');
    expect(shipment.edi856Generated).toBe(true);
    expect(shipment.edi856.transactionSetId).toMatch(/^856/);
  });
});

EDI Document Testing

EDI文档测试

EDI 856 - Advanced Ship Notice

EDI 856 - 高级发货通知

javascript
describe('EDI 856 ASN Generation', () => {
  it('generates valid 856 with correct hierarchical levels', async () => {
    const asn = await edi.generate856({
      shipmentId: 'SHP-001',
      carrier: { scac: 'UPSN', proNumber: 'PRO-12345' },
      orders: [{
        orderId: 'SO-1001',
        items: [
          { sku: 'MAT-100', qty: 10, lot: 'LOT-001', upc: '012345678901' }
        ]
      }]
    });

    // Validate hierarchical structure: Shipment -> Order -> Item
    expect(asn.segments.filter(s => s.id === 'HL')).toHaveLength(3);
    expect(asn.getSegment('BSN', 'BSN01')).toBe('00'); // Original
    expect(asn.getSegment('TD5', 'TD504')).toBe('UPSN');
    expect(asn.getSegment('SN1', 'SN102')).toBe('10');

    // Validate segment count and control numbers
    expect(asn.getSegment('CTT', 'CTT01')).toBe('1'); // 1 transaction
    expect(asn.getSegment('SE', 'SE01')).toBeDefined(); // Segment count
  });

  it('validates 997 functional acknowledgment for sent 856', async () => {
    const asn = await edi.send856('SHP-001');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.transactionSetId).toBe('997');
    expect(ack.acknowledgmentCode).toBe('A'); // Accepted
    expect(ack.referencedTransactionSet).toBe('856');
  });

  it('handles 997 rejection and triggers resend', async () => {
    const asn = await edi.send856('SHP-INVALID');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.acknowledgmentCode).toBe('R'); // Rejected
    expect(ack.errors).toContainEqual(
      expect.objectContaining({ segmentId: 'SN1', errorCode: '7' })
    );

    // Verify resend was triggered
    const resendLog = await edi.getResendLog('SHP-INVALID');
    expect(resendLog.attempts).toBeGreaterThanOrEqual(1);
  });
});
javascript
describe('EDI 856 ASN Generation', () => {
  it('generates valid 856 with correct hierarchical levels', async () => {
    const asn = await edi.generate856({
      shipmentId: 'SHP-001',
      carrier: { scac: 'UPSN', proNumber: 'PRO-12345' },
      orders: [{
        orderId: 'SO-1001',
        items: [
          { sku: 'MAT-100', qty: 10, lot: 'LOT-001', upc: '012345678901' }
        ]
      }]
    });

    // Validate hierarchical structure: Shipment -> Order -> Item
    expect(asn.segments.filter(s => s.id === 'HL')).toHaveLength(3);
    expect(asn.getSegment('BSN', 'BSN01')).toBe('00'); // Original
    expect(asn.getSegment('TD5', 'TD504')).toBe('UPSN');
    expect(asn.getSegment('SN1', 'SN102')).toBe('10');

    // Validate segment count and control numbers
    expect(asn.getSegment('CTT', 'CTT01')).toBe('1'); // 1 transaction
    expect(asn.getSegment('SE', 'SE01')).toBeDefined(); // Segment count
  });

  it('validates 997 functional acknowledgment for sent 856', async () => {
    const asn = await edi.send856('SHP-001');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.transactionSetId).toBe('997');
    expect(ack.acknowledgmentCode).toBe('A'); // Accepted
    expect(ack.referencedTransactionSet).toBe('856');
  });

  it('handles 997 rejection and triggers resend', async () => {
    const asn = await edi.send856('SHP-INVALID');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.acknowledgmentCode).toBe('R'); // Rejected
    expect(ack.errors).toContainEqual(
      expect.objectContaining({ segmentId: 'SN1', errorCode: '7' })
    );

    // Verify resend was triggered
    const resendLog = await edi.getResendLog('SHP-INVALID');
    expect(resendLog.attempts).toBeGreaterThanOrEqual(1);
  });
});

EDI 940 - Warehouse Shipping Order

EDI 940 - 仓库发货订单

javascript
describe('EDI 940 Inbound Processing', () => {
  it('creates WMS order from valid 940 document', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(true);
    expect(result.wmsOrderId).toBeDefined();
    expect(result.lineCount).toBe(5);
    expect(result.allocationTriggered).toBe(true);
    expect(result.priorityMapped).toBe('STANDARD');
  });

  it('rejects 940 with unknown SKU and generates 997 rejection', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-invalid-sku.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.errors).toContainEqual(
      expect.objectContaining({ segment: 'LIN', error: 'Unknown SKU: INVALID-SKU' })
    );
    expect(result.ack997.code).toBe('R');
  });

  it('detects duplicate 940 by BSN reference number', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');

    // Process first time - success
    await edi.process940(edi940);

    // Process duplicate - should detect
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.duplicate).toBe(true);
    expect(result.originalOrderId).toBeDefined();
  });
});
javascript
describe('EDI 940 Inbound Processing', () => {
  it('creates WMS order from valid 940 document', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(true);
    expect(result.wmsOrderId).toBeDefined();
    expect(result.lineCount).toBe(5);
    expect(result.allocationTriggered).toBe(true);
    expect(result.priorityMapped).toBe('STANDARD');
  });

  it('rejects 940 with unknown SKU and generates 997 rejection', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-invalid-sku.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.errors).toContainEqual(
      expect.objectContaining({ segment: 'LIN', error: 'Unknown SKU: INVALID-SKU' })
    );
    expect(result.ack997.code).toBe('R');
  });

  it('detects duplicate 940 by BSN reference number', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');

    // Process first time - success
    await edi.process940(edi940);

    // Process duplicate - should detect
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.duplicate).toBe(true);
    expect(result.originalOrderId).toBeDefined();
  });
});

EDI 945 - Warehouse Shipping Advice

EDI 945 - 仓库发货通知

javascript
describe('EDI 945 Outbound Generation', () => {
  it('generates 945 on shipment confirmation', async () => {
    await wms.shipConfirm({ orderId: 'SO-1005', carrier: 'FEDEX' });

    const edi945 = await edi.getLatest945('SO-1005');

    expect(edi945.transactionSetId).toBe('945');
    expect(edi945.getSegment('W06', 'W0601')).toBe('N'); // Normal shipment
    expect(edi945.getSegment('W12', 'W1202')).toBeDefined(); // Quantity shipped
    expect(edi945.getSegment('N1', 'N102')).toBeDefined(); // Ship-to name
  });
});

javascript
describe('EDI 945 Outbound Generation', () => {
  it('generates 945 on shipment confirmation', async () => {
    await wms.shipConfirm({ orderId: 'SO-1005', carrier: 'FEDEX' });

    const edi945 = await edi.getLatest945('SO-1005');

    expect(edi945.transactionSetId).toBe('945');
    expect(edi945.getSegment('W06', 'W0601')).toBe('N'); // Normal shipment
    expect(edi945.getSegment('W12', 'W1202')).toBeDefined(); // Quantity shipped
    expect(edi945.getSegment('N1', 'N102')).toBeDefined(); // Ship-to name
  });
});

RF/Barcode Scanning Flow Testing

RF/条码扫描流程测试

javascript
describe('RF Directed Pick Workflow', () => {
  let rfSession;

  beforeEach(async () => {
    rfSession = await rfSimulator.createSession({
      device: 'Zebra-MC9300',
      mode: 'directed-pick'
    });
  });

  it('completes directed pick with valid scans', async () => {
    // Step 1: Login
    const login = await rfSession.scan('badge', 'USER-PICKER-01');
    expect(login.screen).toBe('MAIN_MENU');

    // Step 2: Select pick task
    const taskSelect = await rfSession.selectMenu('PICK');
    expect(taskSelect.screen).toBe('PICK_TASK_ASSIGNED');
    expect(taskSelect.taskId).toBeDefined();

    // Step 3: Scan source location
    const locationScan = await rfSession.scan('location', 'BIN-A-01-01');
    expect(locationScan.screen).toBe('SCAN_ITEM');
    expect(locationScan.expectedItem).toBe('MAT-100');

    // Step 4: Scan item barcode
    const itemScan = await rfSession.scan('item', 'MAT-100-LOT001');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');
    expect(itemScan.maxQty).toBe(10);

    // Step 5: Enter quantity
    const qtyConfirm = await rfSession.enterQuantity(10);
    expect(qtyConfirm.screen).toBe('SCAN_DESTINATION');

    // Step 6: Scan staging lane
    const destScan = await rfSession.scan('location', 'STAGE-LANE-01');
    expect(destScan.screen).toBe('PICK_COMPLETE');
    expect(destScan.taskStatus).toBe('COMPLETED');
  });

  it('rejects wrong item scan with mismatch alert', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');

    const wrongItem = await rfSession.scan('item', 'MAT-999-WRONG');

    expect(wrongItem.screen).toBe('ITEM_MISMATCH');
    expect(wrongItem.alert).toBe('MISMATCH');
    expect(wrongItem.expectedItem).toBe('MAT-100');
    expect(wrongItem.scannedItem).toBe('MAT-999');
  });

  it('warns on over-pick quantity', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');
    await rfSession.scan('item', 'MAT-100-LOT001');

    const overPick = await rfSession.enterQuantity(999);

    expect(overPick.screen).toBe('OVER_PICK_WARNING');
    expect(overPick.maxAllowed).toBe(10);
    expect(overPick.requiresSupervisor).toBe(true);
  });

  it('handles wrong location scan gracefully', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');

    const wrongLocation = await rfSession.scan('location', 'BIN-Z-99-99');

    expect(wrongLocation.screen).toBe('WRONG_LOCATION');
    expect(wrongLocation.expectedLocation).toBe('BIN-A-01-01');
    expect(wrongLocation.action).toBe('RESCAN');
  });
});

describe('RF Receiving Workflow', () => {
  it('receives goods by scanning PO and items', async () => {
    const rfSession = await rfSimulator.createSession({ mode: 'receiving' });

    await rfSession.scan('badge', 'USER-RECEIVER-01');
    const poScan = await rfSession.scan('document', 'PO-4500001234');
    expect(poScan.screen).toBe('SCAN_ITEM');
    expect(poScan.expectedItems).toHaveLength(3);

    const itemScan = await rfSession.scan('item', 'MAT-100');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');

    const receipt = await rfSession.enterQuantity(100);
    expect(receipt.screen).toBe('SCAN_LOT');

    const lotScan = await rfSession.scan('lot', 'LOT-2026-050');
    expect(lotScan.screen).toBe('RECEIPT_CONFIRMED');
    expect(lotScan.quantityReceived).toBe(100);
  });
});

javascript
describe('RF Directed Pick Workflow', () => {
  let rfSession;

  beforeEach(async () => {
    rfSession = await rfSimulator.createSession({
      device: 'Zebra-MC9300',
      mode: 'directed-pick'
    });
  });

  it('completes directed pick with valid scans', async () => {
    // Step 1: Login
    const login = await rfSession.scan('badge', 'USER-PICKER-01');
    expect(login.screen).toBe('MAIN_MENU');

    // Step 2: Select pick task
    const taskSelect = await rfSession.selectMenu('PICK');
    expect(taskSelect.screen).toBe('PICK_TASK_ASSIGNED');
    expect(taskSelect.taskId).toBeDefined();

    // Step 3: Scan source location
    const locationScan = await rfSession.scan('location', 'BIN-A-01-01');
    expect(locationScan.screen).toBe('SCAN_ITEM');
    expect(locationScan.expectedItem).toBe('MAT-100');

    // Step 4: Scan item barcode
    const itemScan = await rfSession.scan('item', 'MAT-100-LOT001');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');
    expect(itemScan.maxQty).toBe(10);

    // Step 5: Enter quantity
    const qtyConfirm = await rfSession.enterQuantity(10);
    expect(qtyConfirm.screen).toBe('SCAN_DESTINATION');

    // Step 6: Scan staging lane
    const destScan = await rfSession.scan('location', 'STAGE-LANE-01');
    expect(destScan.screen).toBe('PICK_COMPLETE');
    expect(destScan.taskStatus).toBe('COMPLETED');
  });

  it('rejects wrong item scan with mismatch alert', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');

    const wrongItem = await rfSession.scan('item', 'MAT-999-WRONG');

    expect(wrongItem.screen).toBe('ITEM_MISMATCH');
    expect(wrongItem.alert).toBe('MISMATCH');
    expect(wrongItem.expectedItem).toBe('MAT-100');
    expect(wrongItem.scannedItem).toBe('MAT-999');
  });

  it('warns on over-pick quantity', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');
    await rfSession.scan('item', 'MAT-100-LOT001');

    const overPick = await rfSession.enterQuantity(999);

    expect(overPick.screen).toBe('OVER_PICK_WARNING');
    expect(overPick.maxAllowed).toBe(10);
    expect(overPick.requiresSupervisor).toBe(true);
  });

  it('handles wrong location scan gracefully', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');

    const wrongLocation = await rfSession.scan('location', 'BIN-Z-99-99');

    expect(wrongLocation.screen).toBe('WRONG_LOCATION');
    expect(wrongLocation.expectedLocation).toBe('BIN-A-01-01');
    expect(wrongLocation.action).toBe('RESCAN');
  });
});

describe('RF Receiving Workflow', () => {
  it('receives goods by scanning PO and items', async () => {
    const rfSession = await rfSimulator.createSession({ mode: 'receiving' });

    await rfSession.scan('badge', 'USER-RECEIVER-01');
    const poScan = await rfSession.scan('document', 'PO-4500001234');
    expect(poScan.screen).toBe('SCAN_ITEM');
    expect(poScan.expectedItems).toHaveLength(3);

    const itemScan = await rfSession.scan('item', 'MAT-100');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');

    const receipt = await rfSession.enterQuantity(100);
    expect(receipt.screen).toBe('SCAN_LOT');

    const lotScan = await rfSession.scan('lot', 'LOT-2026-050');
    expect(lotScan.screen).toBe('RECEIPT_CONFIRMED');
    expect(lotScan.quantityReceived).toBe(100);
  });
});

WMS-ERP Integration Testing

WMS-ERP集成测试

SAP EWM to S/4HANA Sync

SAP EWM到S/4HANA同步

javascript
describe('WMS-ERP Goods Receipt Sync', () => {
  it('syncs goods receipt from EWM to S/4HANA within SLA', async () => {
    const startTime = Date.now();

    // Confirm inbound delivery in EWM
    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-001',
      items: [{ material: 'MAT-100', quantity: 100, batch: 'BATCH-001' }]
    });

    // Wait for sync to S/4HANA
    const materialDoc = await s4hana.waitForMaterialDocument({
      reference: 'IBD-001',
      timeout: 30000
    });

    const elapsed = Date.now() - startTime;

    expect(materialDoc.created).toBe(true);
    expect(materialDoc.type).toBe('WE'); // Goods receipt
    expect(materialDoc.quantity).toBe(100);
    expect(materialDoc.batch).toBe('BATCH-001');
    expect(materialDoc.plant).toBe('1000');
    expect(elapsed).toBeLessThan(30000); // 30s SLA
  });

  it('validates inventory quantities match between EWM and S/4HANA', async () => {
    const ewmStock = await ewm.getStock('MAT-100', 'WH-01');
    const s4Stock = await s4hana.getStock('MAT-100', '1000', '0001');

    expect(ewmStock.available).toBe(s4Stock.unrestricted);
    expect(ewmStock.allocated).toBe(s4Stock.reserved);
    expect(ewmStock.blocked).toBe(s4Stock.qualityInspection);
  });

  it('handles sync failure with IDoc error and retry', async () => {
    // Simulate S/4HANA unavailability
    await s4hana.simulateDowntime(10000);

    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-FAIL',
      items: [{ material: 'MAT-200', quantity: 50 }]
    });

    // Check IDoc status
    const idoc = await sap.getIdoc({ reference: 'IBD-FAIL', type: 'WMMBID01' });
    expect(idoc.status).toBe('03'); // Error status

    // Restore S/4HANA and verify auto-retry
    await s4hana.restoreService();
    const retried = await sap.waitForIdocStatus(idoc.id, '53', { timeout: 60000 });
    expect(retried.status).toBe('53'); // Successfully processed
  });
});
javascript
describe('WMS-ERP Goods Receipt Sync', () => {
  it('syncs goods receipt from EWM to S/4HANA within SLA', async () => {
    const startTime = Date.now();

    // Confirm inbound delivery in EWM
    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-001',
      items: [{ material: 'MAT-100', quantity: 100, batch: 'BATCH-001' }]
    });

    // Wait for sync to S/4HANA
    const materialDoc = await s4hana.waitForMaterialDocument({
      reference: 'IBD-001',
      timeout: 30000
    });

    const elapsed = Date.now() - startTime;

    expect(materialDoc.created).toBe(true);
    expect(materialDoc.type).toBe('WE'); // Goods receipt
    expect(materialDoc.quantity).toBe(100);
    expect(materialDoc.batch).toBe('BATCH-001');
    expect(materialDoc.plant).toBe('1000');
    expect(elapsed).toBeLessThan(30000); // 30s SLA
  });

  it('validates inventory quantities match between EWM and S/4HANA', async () => {
    const ewmStock = await ewm.getStock('MAT-100', 'WH-01');
    const s4Stock = await s4hana.getStock('MAT-100', '1000', '0001');

    expect(ewmStock.available).toBe(s4Stock.unrestricted);
    expect(ewmStock.allocated).toBe(s4Stock.reserved);
    expect(ewmStock.blocked).toBe(s4Stock.qualityInspection);
  });

  it('handles sync failure with IDoc error and retry', async () => {
    // Simulate S/4HANA unavailability
    await s4hana.simulateDowntime(10000);

    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-FAIL',
      items: [{ material: 'MAT-200', quantity: 50 }]
    });

    // Check IDoc status
    const idoc = await sap.getIdoc({ reference: 'IBD-FAIL', type: 'WMMBID01' });
    expect(idoc.status).toBe('03'); // Error status

    // Restore S/4HANA and verify auto-retry
    await s4hana.restoreService();
    const retried = await sap.waitForIdocStatus(idoc.id, '53', { timeout: 60000 });
    expect(retried.status).toBe('53'); // Successfully processed
  });
});

Manhattan WMOS to Oracle EBS Sync

Manhattan WMOS到Oracle EBS同步

javascript
describe('Manhattan WMOS to Oracle EBS', () => {
  it('posts inventory adjustment to Oracle via REST API', async () => {
    const adjustment = await wmos.postAdjustment({
      item: 'ITEM-500',
      location: 'LOC-A-01',
      adjustmentQty: -5,
      reason: 'DAMAGED'
    });

    const oracleTransaction = await oracle.waitForTransaction({
      reference: adjustment.transactionId,
      timeout: 15000
    });

    expect(oracleTransaction.transactionType).toBe('ADJUSTMENT');
    expect(oracleTransaction.quantity).toBe(-5);
    expect(oracleTransaction.reasonCode).toBe('DAMAGED');
    expect(oracleTransaction.accountPosted).toBe(true);
  });
});

javascript
describe('Manhattan WMOS to Oracle EBS', () => {
  it('posts inventory adjustment to Oracle via REST API', async () => {
    const adjustment = await wmos.postAdjustment({
      item: 'ITEM-500',
      location: 'LOC-A-01',
      adjustmentQty: -5,
      reason: 'DAMAGED'
    });

    const oracleTransaction = await oracle.waitForTransaction({
      reference: adjustment.transactionId,
      timeout: 15000
    });

    expect(oracleTransaction.transactionType).toBe('ADJUSTMENT');
    expect(oracleTransaction.quantity).toBe(-5);
    expect(oracleTransaction.reasonCode).toBe('DAMAGED');
    expect(oracleTransaction.accountPosted).toBe(true);
  });
});

Returns and Reverse Logistics

退货与逆向物流

javascript
describe('Returns Processing', () => {
  it('processes RMA receipt with inspection and disposition', async () => {
    const rma = await wms.createRMA({
      rmaNumber: 'RMA-001',
      originalOrder: 'SO-1001',
      items: [{ sku: 'MAT-100', qty: 2, reason: 'DEFECTIVE' }]
    });

    // Receive return
    const receipt = await wms.receiveReturn({
      rmaId: rma.id,
      scannedItems: [{ barcode: 'MAT-100', qty: 2 }]
    });

    expect(receipt.status).toBe('RECEIVED_PENDING_INSPECTION');

    // Inspect returned items
    const inspection = await wms.inspectReturn({
      rmaId: rma.id,
      results: [
        { item: 'MAT-100', unit: 1, condition: 'RESTOCK', grade: 'A' },
        { item: 'MAT-100', unit: 2, condition: 'SCRAP', grade: 'F' }
      ]
    });

    expect(inspection.disposition.restock).toBe(1);
    expect(inspection.disposition.scrap).toBe(1);
    expect(inspection.restockLocation).toBeDefined();
  });
});

javascript
describe('Returns Processing', () => {
  it('processes RMA receipt with inspection and disposition', async () => {
    const rma = await wms.createRMA({
      rmaNumber: 'RMA-001',
      originalOrder: 'SO-1001',
      items: [{ sku: 'MAT-100', qty: 2, reason: 'DEFECTIVE' }]
    });

    // Receive return
    const receipt = await wms.receiveReturn({
      rmaId: rma.id,
      scannedItems: [{ barcode: 'MAT-100', qty: 2 }]
    });

    expect(receipt.status).toBe('RECEIVED_PENDING_INSPECTION');

    // Inspect returned items
    const inspection = await wms.inspectReturn({
      rmaId: rma.id,
      results: [
        { item: 'MAT-100', unit: 1, condition: 'RESTOCK', grade: 'A' },
        { item: 'MAT-100', unit: 2, condition: 'SCRAP', grade: 'F' }
      ]
    });

    expect(inspection.disposition.restock).toBe(1);
    expect(inspection.disposition.scrap).toBe(1);
    expect(inspection.restockLocation).toBeDefined();
  });
});

Replenishment Testing

补货测试

javascript
describe('Replenishment Triggers', () => {
  it('triggers min/max replenishment when pick face drops below min', async () => {
    // Set pick face to minimum threshold
    await wms.setInventory('PICK-A-01', 'MAT-100', 5); // Min = 10, Max = 50

    const replenishment = await wms.checkReplenishment('PICK-A-01', 'MAT-100');

    expect(replenishment.triggered).toBe(true);
    expect(replenishment.type).toBe('MIN_MAX');
    expect(replenishment.replenishQty).toBe(45); // Fill to max (50 - 5)
    expect(replenishment.sourceLocation).toMatch(/^BULK-/);
  });

  it('triggers demand-based replenishment from wave allocation', async () => {
    // Wave needs 100 EA, pick face only has 20
    await wms.setInventory('PICK-B-01', 'MAT-200', 20);

    const wave = await wms.planWave({
      strategy: 'SINGLE_ORDER',
      orders: [{ orderId: 'SO-REPLEN', lines: [{ item: 'MAT-200', qty: 100 }] }]
    });

    const replenishment = await wms.getReplenishmentTasks(wave.id);

    expect(replenishment).toHaveLength(1);
    expect(replenishment[0].type).toBe('DEMAND');
    expect(replenishment[0].quantity).toBe(80); // 100 needed - 20 available
    expect(replenishment[0].priority).toBe('URGENT');
  });
});

javascript
describe('Replenishment Triggers', () => {
  it('triggers min/max replenishment when pick face drops below min', async () => {
    // Set pick face to minimum threshold
    await wms.setInventory('PICK-A-01', 'MAT-100', 5); // Min = 10, Max = 50

    const replenishment = await wms.checkReplenishment('PICK-A-01', 'MAT-100');

    expect(replenishment.triggered).toBe(true);
    expect(replenishment.type).toBe('MIN_MAX');
    expect(replenishment.replenishQty).toBe(45); // Fill to max (50 - 5)
    expect(replenishment.sourceLocation).toMatch(/^BULK-/);
  });

  it('triggers demand-based replenishment from wave allocation', async () => {
    // Wave needs 100 EA, pick face only has 20
    await wms.setInventory('PICK-B-01', 'MAT-200', 20);

    const wave = await wms.planWave({
      strategy: 'SINGLE_ORDER',
      orders: [{ orderId: 'SO-REPLEN', lines: [{ item: 'MAT-200', qty: 100 }] }]
    });

    const replenishment = await wms.getReplenishmentTasks(wave.id);

    expect(replenishment).toHaveLength(1);
    expect(replenishment[0].type).toBe('DEMAND');
    expect(replenishment[0].quantity).toBe(80); // 100 needed - 20 available
    expect(replenishment[0].priority).toBe('URGENT');
  });
});

Best Practices

最佳实践

Do This

建议做法

  • Test the complete lifecycle: receive -> putaway -> allocate -> pick -> pack -> ship -> EDI
  • Validate inventory accuracy at every state transition (on-hand, allocated, in-transit)
  • Test all allocation methods for your WMS (FIFO, FEFO, LIFO, zone priority)
  • Simulate RF device failures mid-transaction (battery death, WiFi drop, scan timeout)
  • Validate EDI acknowledgments (997) for every outbound document
  • Test short-pick, over-pick, and wrong-pick scenarios explicitly
  • Verify WMS-ERP sync latency against SLA thresholds
  • Test cycle count variance at multiple thresholds (auto-approve, supervisor, recount)
  • 测试完整生命周期:收货 -> 上架 -> 分配 -> 拣选 -> 包装 -> 发货 -> EDI
  • 在每个状态转换时验证库存准确性(在手、已分配、在途)
  • 测试WMS的所有分配方式(FIFO、FEFO、LIFO、区域优先级)
  • 模拟RF设备交易中途故障(电池耗尽、WiFi断开、扫描超时)
  • 验证每个出站文档的EDI确认(997)
  • 明确测试短拣、超拣和错拣场景
  • 验证WMS-ERP同步延迟符合SLA阈值
  • 在多个阈值下测试周期盘点差异(自动批准、主管审批、重新盘点)

Avoid This

避免做法

  • Testing only happy-path receiving without over/under tolerance scenarios
  • Skipping RF error flows (wrong scan, timeout, connectivity loss)
  • Ignoring EDI segment-level validation (trusting document-level pass)
  • Testing allocation logic without expired lot scenarios
  • Assuming WMS-ERP sync is instantaneous; always test with timing assertions
  • Deploying without testing wave planning with mixed priority orders
  • Ignoring cartonization and weight validation in pack verification

  • 仅测试正常收货流程,忽略超收/欠收容差场景
  • 跳过RF错误流程(错误扫描、超时、连接中断)
  • 忽略EDI段级验证(仅信任文档级通过)
  • 测试分配逻辑时忽略过期批次场景
  • 假设WMS-ERP同步是即时的;务必添加时间断言
  • 未测试混合优先级订单的波次规划就部署
  • 在包装验证中忽略装箱和重量验证

Agent-Assisted WMS Testing

Agent辅助WMS测试

typescript
// Validate EDI document contracts
await Task("EDI Contract Validation", {
  documents: ['856-ASN', '940-Order', '945-Advice', '943-StockTransfer'],
  validateSegments: true,
  checkAcknowledgments: true,
  testErrorScenarios: ['invalid-sku', 'duplicate-bsn', 'missing-required']
}, "qe-contract-validator");

// Test WMS-ERP integration flows
await Task("WMS-ERP Integration Test", {
  source: 'SAP-EWM',
  target: 'SAP-S4HANA',
  flows: ['goods-receipt', 'goods-issue', 'stock-transfer', 'inventory-adjustment'],
  slaThreshold: 30000,
  validateIdocs: true
}, "qe-middleware-validator");

// Validate SAP IDoc structures
await Task("IDoc Structure Validation", {
  idocTypes: ['WMMBID01', 'SHPCON', 'MBGMCR01'],
  validateSegments: true,
  testMandatoryFields: true,
  crossReferenceWithEWM: true
}, "qe-sap-idoc-tester");

// Test EWM OData services
await Task("EWM OData Service Testing", {
  services: [
    '/sap/opu/odata/sap/API_WAREHOUSE_ORDER',
    '/sap/opu/odata/sap/API_PHYSICAL_INVENTORY_DOC'
  ],
  testCrud: true,
  validateNavigationProperties: true,
  testFilterAndPagination: true
}, "qe-odata-contract-tester");

typescript
// Validate EDI document contracts
await Task("EDI Contract Validation", {
  documents: ['856-ASN', '940-Order', '945-Advice', '943-StockTransfer'],
  validateSegments: true,
  checkAcknowledgments: true,
  testErrorScenarios: ['invalid-sku', 'duplicate-bsn', 'missing-required']
}, "qe-contract-validator");

// Test WMS-ERP integration flows
await Task("WMS-ERP Integration Test", {
  source: 'SAP-EWM',
  target: 'SAP-S4HANA',
  flows: ['goods-receipt', 'goods-issue', 'stock-transfer', 'inventory-adjustment'],
  slaThreshold: 30000,
  validateIdocs: true
}, "qe-middleware-validator");

// Validate SAP IDoc structures
await Task("IDoc Structure Validation", {
  idocTypes: ['WMMBID01', 'SHPCON', 'MBGMCR01'],
  validateSegments: true,
  testMandatoryFields: true,
  crossReferenceWithEWM: true
}, "qe-sap-idoc-tester");

// Test EWM OData services
await Task("EWM OData Service Testing", {
  services: [
    '/sap/opu/odata/sap/API_WAREHOUSE_ORDER',
    '/sap/opu/odata/sap/API_PHYSICAL_INVENTORY_DOC'
  ],
  testCrud: true,
  validateNavigationProperties: true,
  testFilterAndPagination: true
}, "qe-odata-contract-tester");

Agent Coordination Hints

Agent协调提示

Primary Agents

主要Agent

  • qe-middleware-validator: WMS-ERP integration flow testing and message transformation validation
  • qe-contract-validator: EDI document contract testing and WMS API contracts
  • qe-middleware-validator:WMS-ERP集成流程测试和消息转换验证
  • qe-contract-validator:EDI文档契约测试和WMS API契约

Supporting Agents

支持Agent

  • qe-message-broker-tester: When WMS uses message queues for async processing
  • qe-odata-contract-tester: When WMS exposes OData services (SAP EWM)
  • qe-sap-idoc-tester: When WMS-SAP integration uses IDocs (WMMBID01, SHPCON)
  • qe-message-broker-tester:当WMS使用消息队列进行异步处理时
  • qe-odata-contract-tester:当WMS暴露OData服务时(SAP EWM)
  • qe-sap-idoc-tester:当WMS-SAP集成使用IDocs时(WMMBID01、SHPCON)

Coordination Pattern

协调模式

1. qe-contract-validator -> Validate EDI document schemas and segments
2. qe-middleware-validator -> Test WMS message flows and transformations
3. qe-message-broker-tester -> Verify async queue processing and DLQ
4. qe-odata-contract-tester -> Test WMS OData APIs (if SAP EWM)
5. qe-sap-idoc-tester -> Validate IDoc structure and content
1. qe-contract-validator -> 验证EDI文档模式和段
2. qe-middleware-validator -> 测试WMS消息流和转换
3. qe-message-broker-tester -> 验证异步队列处理和死信队列
4. qe-odata-contract-tester -> 测试WMS OData API(如果是SAP EWM)
5. qe-sap-idoc-tester -> 验证IDoc结构和内容

Memory Namespace

内存命名空间

aqe/wms-testing/
  inventory/         - Receipt, putaway, adjustment test results
  fulfillment/       - Pick/pack/ship workflow results
  edi/               - EDI document validation results
  rf-scanning/       - RF workflow simulation results
  allocation/        - FIFO/FEFO/lot control test results
  integration/       - WMS-ERP sync test results
  returns/           - RMA and reverse logistics results
aqe/wms-testing/
  inventory/         - 收货、上架、调整测试结果
  fulfillment/       - 拣选/包装/发货流程结果
  edi/               - EDI文档验证结果
  rf-scanning/       - RF工作流模拟结果
  allocation/        - FIFO/FEFO/批次控制测试结果
  integration/       - WMS-ERP同步测试结果
  returns/           - RMA和逆向物流结果

Fleet Coordination

集群协调

typescript
const wmsFleet = await FleetManager.coordinate({
  strategy: 'wms-testing',
  agents: [
    'qe-contract-validator',     // EDI contracts and API contracts
    'qe-middleware-validator',    // WMS-ERP message flows
    'qe-sap-idoc-tester',        // IDoc validation
    'qe-odata-contract-tester'   // SAP EWM OData testing
  ],
  topology: 'mesh'
});

await wmsFleet.execute({
  workflows: [
    { name: 'inbound', stages: ['receive', 'putaway', 'inventory-sync'] },
    { name: 'outbound', stages: ['wave', 'pick', 'pack', 'ship', 'edi-856'] },
    { name: 'returns', stages: ['rma-receipt', 'inspection', 'disposition'] }
  ],
  testEdi: true,
  testRfScanning: true,
  testAllocation: true
});
typescript
const wmsFleet = await FleetManager.coordinate({
  strategy: 'wms-testing',
  agents: [
    'qe-contract-validator',     // EDI契约和API契约
    'qe-middleware-validator',    // WMS-ERP消息流
    'qe-sap-idoc-tester',        // IDoc验证
    'qe-odata-contract-tester'   // SAP EWM OData测试
  ],
  topology: 'mesh'
});

await wmsFleet.execute({
  workflows: [
    { name: 'inbound', stages: ['receive', 'putaway', 'inventory-sync'] },
    { name: 'outbound', stages: ['wave', 'pick', 'pack', 'ship', 'edi-856'] },
    { name: 'returns', stages: ['rma-receipt', 'inspection', 'disposition'] }
  ],
  testEdi: true,
  testRfScanning: true,
  testAllocation: true
});

QCSD Integration

QCSD集成

  • Ideation: Flag
    HAS_WMS
    triggers WMS-specific quality criteria (inventory accuracy, EDI compliance)
  • Refinement: SFDIPOT includes warehouse operations product factors (throughput, accuracy, latency)
  • Development: Coverage analysis includes EDI document parsing paths and allocation algorithms
  • Verification: Pipeline gates include WMS integration health checks and EDI acknowledgment validation

  • 构思阶段:标记
    HAS_WMS
    触发WMS特定质量标准(库存准确性、EDI合规性)
  • 细化阶段:SFDIPOT包含仓库运营产品因素(吞吐量、准确性、延迟)
  • 开发阶段:覆盖率分析包括EDI文档解析路径和分配算法
  • 验证阶段:流水线关卡包括WMS集成健康检查和EDI确认验证

Related Skills

相关技能

  • api-testing-patterns - REST/GraphQL API testing fundamentals
  • contract-testing - Consumer-driven contract testing with Pact
  • enterprise-integration-testing - Orchestrating all enterprise agents
  • middleware-testing-patterns - ESB and message broker testing
  • observability-testing-patterns - Monitoring and alerting validation

  • api-testing-patterns - REST/GraphQL API测试基础
  • contract-testing - 使用Pact的消费者驱动契约测试
  • enterprise-integration-testing - 编排所有企业Agent
  • middleware-testing-patterns - ESB和消息代理测试
  • observability-testing-patterns - 监控和告警验证

Remember

注意事项

Warehouse Management Systems are the physical-digital bridge. When inventory is wrong, customers get wrong shipments, financial reports are inaccurate, and replenishment fails. Test every inventory state transition, every EDI document field, and every RF scanning exception. A 2% inventory variance might seem small until it compounds across 100,000 SKUs in 50 warehouses.
With Agents: Agents validate EDI document structures segment-by-segment, test WMS-ERP sync within SLA thresholds, and simulate RF scanning workflows at scale. Use agents to cover the combinatorial explosion of allocation methods, lot control scenarios, and EDI error conditions that manual testing cannot reach.
仓库管理系统是物理世界与数字世界的桥梁。当库存数据错误时,客户会收到错误的货物,财务报告不准确,补货也会失败。测试每一个库存状态转换、每一个EDI文档字段和每一个RF扫描异常。2%的库存差异看似微小,但在50个仓库的10万个SKU中,这个差异会被放大。
借助Agent: Agent可以逐段验证EDI文档结构,在SLA阈值内测试WMS-ERP同步,并大规模模拟RF扫描工作流。使用Agent可以覆盖手动测试无法触及的分配方式、批次控制场景和EDI错误条件的组合爆炸。",