mcpField
Q
qe-wms-testing-patterns

qe-wms-testing-patterns

github.com/proffesor-for-testingテスト・品質51983 回インストール今日更新
リポジトリ

qe-wms-testing-patterns について

qe-wms-testing-patternsは、倉庫管理システム(WMS)のテストパターン集です。テスターが在庫、出荷業務、EDI、システム連携を検証するためのシナリオや例外ケース、エージェント連携の手順をまとめています。

主な機能

  1. 01入荷、格納、棚卸し、在庫調整の検証
  2. 02波動ピッキング、梱包、出荷のテスト
  3. 03EDI文書と業務ルールの検証
  4. 04RFおよびバーコードスキャンの確認
  5. 05引当、補充、WMS-ERP同期の検証

活用シーン

  1. 01在庫差異と調整ロジックの調査
  2. 02波動リリースから出荷までの注文処理をテスト
  3. 03EDI文書と確認メッセージの検証
  4. 04スキャン障害や連携時の例外をシミュレート

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/proffesor-for-testing/agentic-qe --skill qe-wms-testing-patterns

インストール後、Claude Code で/qe-wms-testing-patternsを入力して読み込まれたか確認します。

詳細

WMS テストパターン

<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>

クイックリファレンスカード

使用する場面
  • 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)の検証
テストレベル
レベル目的依存関係速度
ユニットロジック引当/補充ルールなし高速
API コントラクトWMS REST/SOAP エンドポイントのコントラクトAPI スタブ高速
EDI 検証ドキュメント構文+ビジネスルールEDI パーサー中速
統合WMS と ERP の在庫同期フルスタック低速
E2E ワークフロー注文処理サイクル全体WMS + ERP + TMS低速
重要なテストシナリオ
シナリオ必須テスト項目例
入荷入荷精度過剰/不足納品の許容範囲を含む発注書の入荷
格納保管場所の割り当て商品属性に基づく指示格納
ピッキングウェーブ/バッチピッキング優先順位に基づく引当を含む複数注文のウェーブ
梱包梱包検証カートン化と重量/寸法の検証
出荷配送業者の割り当て運賃比較とラベル生成
循環棚卸在庫精度ブラインドカウントとガイド付きカウントの照合
返品返品物流RMA の入荷、検査、処置
EDI 856ASN 生成出荷確定によって顧客向けの正しい ASN が生成されること
EDI 940倉庫注文入荷注文によって正しい作業指示が作成されること
ショートピック例外処理バックオーダー作成を伴う部分引当
ツール
  • 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 トラッキングポータル
エージェントの連携
  • 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 サービスのテスト

在庫トランザクションのテスト

入荷と格納
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');
  });
});
循環棚卸と調整
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);
  });
});

ピッキング/梱包/出荷ワークフローのテスト

ウェーブ管理
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();
  });
});
引当ロジック
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');
  });
});
梱包と出荷
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 ドキュメントのテスト

EDI 856 - 事前出荷通知
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 - 倉庫出荷指示
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 - 倉庫出荷通知
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/バーコードスキャンフローのテスト

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 統合テスト

SAP EWM から S/4HANA への同期
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 から Oracle EBS への同期
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);
  });
});

返品とリバースロジスティクス

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();
  });
});

補充テスト

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');
  });
});

ベストプラクティス

実施すること
  • 完全なライフサイクルをテストします:入荷 -> 棚入れ -> 引き当て -> ピッキング -> 梱包 -> 出荷 -> EDI
  • 各状態遷移時に在庫の正確性(手持ち、引き当て済み、輸送中)を検証します
  • WMS のすべての引き当て方法(FIFO、FEFO、LIFO、ゾーン優先度)をテストします
  • トランザクションの途中で発生する RF デバイスの障害(バッテリー切れ、WiFi 切断、スキャンタイムアウト)をシミュレートします
  • すべての送信ドキュメントについて EDI 確認応答(997)を検証します
  • ピッキング不足、過剰ピッキング、誤ピッキングのシナリオを明示的にテストします
  • WMS-ERP の同期遅延が SLA のしきい値内であることを検証します
  • 複数のしきい値(自動承認、スーパーバイザー確認、再カウント)で棚卸差異をテストします
避けること
  • 許容範囲を超える/下回るシナリオをテストせず、正常な入荷フローだけをテストすること
  • RF のエラーフロー(誤スキャン、タイムアウト、接続喪失)を省略すること
  • EDI のセグメントレベルの検証を無視すること(ドキュメントレベルの合格を鵜呑みにすること)
  • 期限切れロットのシナリオをテストせずに引き当てロジックをテストすること
  • WMS-ERP の同期が瞬時に行われると想定すること。必ずタイミングのアサーションを使ってテストしてください
  • 優先度が混在する注文でのウェーブ計画をテストせずにデプロイすること
  • 梱包検証でのカートン化と重量検証を無視すること

エージェント支援による WMS テスト

// 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");

エージェント連携のヒント

主なエージェント
  • qe-middleware-validator:WMS-ERP 統合フローのテストとメッセージ変換の検証
  • qe-contract-validator:EDI ドキュメントのコントラクトテストと WMS API コントラクト
支援エージェント
  • qe-message-broker-tester:WMS が非同期処理にメッセージキューを使用する場合
  • qe-odata-contract-tester:WMS が OData サービス(SAP EWM)を公開する場合
  • qe-sap-idoc-tester:WMS-SAP 統合で IDoc(WMMBID01、SHPCON)を使用する場合
連携パターン
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
メモリ名前空間
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
エージェント群の連携
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
});
QCSD 統合
  • アイデア創出:フラグ HAS_WMS によって、WMS 固有の品質基準(在庫精度、EDI 準拠)が適用されます
  • 要件の詳細化:SFDIPOT に倉庫業務の製品要因(スループット、精度、遅延)が含まれます
  • 開発:カバレッジ分析に EDI ドキュメントの解析パスと引き当てアルゴリズムが含まれます
  • 検証:パイプラインゲートに WMS 統合のヘルスチェックと EDI 確認応答の検証が含まれます

関連スキル


忘れないでください

倉庫管理システムは、物理世界とデジタル世界をつなぐ橋渡し役です。在庫に誤りがあると、顧客には誤った出荷が届き、財務レポートも不正確になり、補充にも失敗します。在庫のすべての状態遷移、すべての EDI ドキュメントフィールド、すべての RF スキャン例外をテストしてください。在庫差異が 2%なら小さく見えるかもしれませんが、50 の倉庫にある 100,000 SKU 全体で積み重なると、大きな問題になります。

エージェントを使用する場合: エージェントは EDI ドキュメント構造をセグメント単位で検証し、WMS-ERP の同期が SLA のしきい値内であることをテストし、大規模な RF スキャンワークフローをシミュレートします。手動テストでは網羅できない、引き当て方法、ロット管理のシナリオ、EDI エラー条件の組み合わせ爆発をカバーするために、エージェントを活用してください。

コメント