mcpField
E
erp-integration

erp-integration

github.com/finsilabs開発60120 回インストール6 日前に更新
リポジトリ

erp-integration について

ECプラットフォームと ERP を双方向同期するための開発者向けスキルです。プラットフォーム別の接続方法、アーキテクチャ、データマッピング、競合処理、リトライを扱います。

主な機能

  1. 01プラットフォーム別の連携方法
  2. 02イベント駆動とポーリング方式
  3. 03データマッピングと競合処理
  4. 04冪等同期とリトライ
  5. 05デッドレターキューと差分同期

活用シーン

  1. 01店舗注文の ERP 自動処理
  2. 02ERP 在庫の EC カタログへの同期
  3. 03システム間の顧客データ同期
  4. 04複数連携向けミドルウェアの設計

インストール

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

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill erp-integration

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/async-order-sync-queue-with-retries-and-/
criteria.json
task.md
evals/erp-adapter-interface-and-data-transform/
criteria.json
task.md
evals/inventory-sync-with-delta-polling-and-ca/
criteria.json
task.md

詳細

ERP 連携

概要

EC プラットフォームと ERP システム(SAP、NetSuite、Odoo、Microsoft Dynamics)を連携し、注文、在庫、顧客、商品を双方向に同期します。このスキルでは、連携アーキテクチャのパターン(イベント駆動、ポーリング、ミドルウェア)、データマッピング、競合解決、再試行戦略によるエラー処理、重複レコードを防ぐ冪等な同期について説明します。

このスキルを使用する場面

  • 注文処理を自動化するため、ストアフロントを ERP に接続する場合
  • ERP/WMS から EC カタログにリアルタイムの在庫数を同期する場合
  • ストアフロントと ERP 間で顧客マスターデータの同期を構築する場合
  • ERP からストアフロントへの商品および価格フィードを実装する場合
  • 複数の連携ポイントを処理するミドルウェア層を設計する場合

基本手順

ステップ 1:プラットフォームと連携方法を決定する
プラットフォーム連携オプション推奨される方法
Shopify注文データには Shopify Admin API と Webhook を使用標準的な ERP コネクターには Zapier(ノーコード、月額 $20)または Celigo(iPaaS)を使用します。NetSuite には公式の NetSuite Connector for Shopify アプリを使用します。カスタム要件には Shopify Webhook を使用します
WooCommerceWooCommerce REST API と WordPress フックシンプルなフローには Zapier を使用します。SAP/NetSuite には Zynk WooCommerce コネクター(£500 から)をインストールするか、WooCommerce REST API を使ってカスタム連携を構築します
BigCommerceBigCommerce API と Webhookエンタープライズ向けの ERP コネクターには Celigo または Boomi を使用します。BigCommerce の App Marketplace には、NetSuite、SAP、Microsoft Dynamics 用の構築済みコネクターがあります
カスタム / ヘッドレスAPI へのフルアクセス — ミドルウェアサービスを構築イベント駆動型の注文同期(ストアの Webhook → キュー → ERP アダプター)、ポーリングベースの在庫同期(スケジュールされたジョブ → ERP API → カタログを更新)、および同期失敗時のデッドレターキューを実装します
ステップ 2:プラットフォーム別の ERP 連携

Shopify

標準的な ERP には構築済みコネクターを使用します:

  1. NetSuite の場合:Shopify App Store から公式の NetSuite Connector for Shopify をインストールします(月額 $150~$300)。カスタムコードなしで、注文、在庫、顧客を双方向に同期します
  2. SAP の場合:Celigo's SAP + Shopify 連携テンプレートを使用するか、SAP パートナーに Shopify コネクターについて問い合わせます
  3. Odoo の場合:Odoo に Odoo Shopify Connector モジュールをインストールします(無料、コミュニティ版)。Odoo の設定で Shopify API 認証情報を設定します

Shopify Webhook を使ったカスタム ERP 連携の場合:

  1. Shopify 管理画面で、Settings → Notifications → Webhooks に移動します
  2. Create webhook をクリックし、orders/create、orders/paid、inventory_levels/update のエンドポイントを追加します
  3. ERP ミドルウェアのエンドポイントが注文データを JSON で受信し、ERP の形式に変換します
  4. Shopify への在庫同期には、ERP のポーリング後に Inventory API を使って在庫数を更新します

WooCommerce

シンプルで処理量の少ない ERP 同期には Zapier を使用します:

  1. zapier.com で WooCommerce と ERP(NetSuite、Odoo、Sage)を接続します
  2. Zap を作成します。トリガー = New Order in WooCommerce、アクション = Create Sales Order in NetSuite
  3. Zapier のフィールドマッパーで、WooCommerce の注文フィールドを ERP の必須フィールドにマッピングします
  4. 無料プランでは Zapier が 15 分ごとに WooCommerce をポーリングします。より頻繁なポーリングには Starter(月額 $19.99)にアップグレードします

処理量が多い場合、またはカスタム ERP 連携の場合:

  1. WooCommerce のイベントをミドルウェアに送信するため、WP Webhooks(無料、wordpress.org)をインストールします
  2. ミドルウェアで注文を取得するには、OAuth 1.0a を使って WooCommerce REST API(/wp-json/wc/v3/orders)を使用します
  3. ERP から WooCommerce への在庫同期には、WooCommerce Products API(PUT /wp-json/wc/v3/products/{id})を使って stock_quantity を更新します

カスタム/ヘッドレス

統合アーキテクチャ — ERP に適したパターンを選択してください:

Event-Driven (recommended for real-time order sync):
  Storefront → Webhook/Event → Message Queue (SQS/BullMQ) → ERP Adapter

Polling (for ERPs without webhooks, like legacy SAP installations):
  Scheduler → Poll ERP API → Transform → Update Storefront

Middleware Platform (for complex multi-system environments):
  Storefront ↔ Celigo / MuleSoft / Workato ↔ ERP

冪等性を備えた注文同期サービス:

// lib/erp/order-sync.ts
export async function syncOrder(orderId: string): Promise<void> {
  const order = await db.orders.getWithItems(orderId);

  // Check if already synced
  const existingSync = await db.syncLog.findByOrderId(orderId);
  if (existingSync?.status === 'synced') return;

  // Check ERP for existing record (guards against retries after partial failure)
  const existing = await erpAdapter.findOrderByExternalReference(order.orderNumber);
  if (existing) {
    await db.syncLog.upsert({ orderId, externalId: existing.erpOrderId, status: 'synced' });
    return;
  }

  try {
    const erpOrder = mapOrderToERP(order);
    const { erpOrderId } = await erpAdapter.createSalesOrder(erpOrder);
    await db.syncLog.upsert({ orderId, externalId: erpOrderId, status: 'synced', syncedAt: new Date() });
    await db.orders.updateMetadata(orderId, { erpOrderId });
  } catch (error) {
    await db.syncLog.upsert({ orderId, status: 'failed', lastError: error.message });
    throw error; // Let the retry mechanism handle it
  }
}

function mapOrderToERP(order: Order): ERPSalesOrder {
  return {
    externalReference: order.orderNumber,
    orderDate: order.createdAt.toISOString().split('T')[0],
    customer: {
      externalId: order.customer?.erpCustomerId || null,
      email: order.email,
      name: `${order.shippingAddress.firstName} ${order.shippingAddress.lastName}`,
    },
    shippingAddress: {
      line1: order.shippingAddress.street1,
      city: order.shippingAddress.city,
      state: order.shippingAddress.state,
      postalCode: order.shippingAddress.postalCode,
      country: order.shippingAddress.country,
    },
    lineItems: order.lineItems.map(item => ({
      sku: item.sku,
      quantity: item.quantity,
      unitPrice: item.unitPrice / 100,   // Convert cents to dollars for ERP
      taxAmount: item.taxAmount / 100,
    })),
    orderTotal: order.totalPrice / 100,
    currency: order.currency,
  };
}

指数バックオフによる再試行を行う、信頼性の高い注文同期のための BullMQ キュー:

import { Queue, Worker, QueueEvents } from 'bullmq';

const orderSyncQueue = new Queue('order-sync', {
  connection: { host: process.env.REDIS_HOST, port: 6379 },
  defaultJobOptions: {
    attempts: 5,
    backoff: { type: 'exponential', delay: 5000 }, // 5s, 10s, 20s, 40s, 80s
    removeOnComplete: { count: 1000 },
    removeOnFail: { count: 5000 },
  },
});

// Producer: enqueue when order is placed
export async function onOrderPlaced(orderId: string) {
  await orderSyncQueue.add('sync-order', { orderId }, {
    jobId: `order-sync-${orderId}`, // Prevents duplicate queue entries
  });
}

// Consumer: process sync
new Worker('order-sync', async (job) => {
  await syncOrder(job.data.orderId);
}, { connection: { host: process.env.REDIS_HOST, port: 6379 }, concurrency: 5 });

在庫同期(ポーリング方式、ERP からストアフロントへの同期):

// lib/erp/inventory-sync.ts — run via scheduled job every 5 minutes
export async function syncInventoryLevels(): Promise<void> {
  const lastSyncAt = await redis.get('erp:inventory:last_sync');
  let page = 1, hasMore = true;

  while (hasMore) {
    const { items, hasMore: more } = await erpAdapter.getInventoryLevels({
      page, pageSize: 500,
      modifiedSince: lastSyncAt ? new Date(lastSyncAt) : undefined,
    });

    for (const item of items) {
      // Available = On Hand - Reserved - Safety Stock
      const available = Math.max(0, item.onHandQuantity - item.reservedQuantity - (item.safetyStock || 0));
      const current = await db.inventory.getQuantityBySku(item.sku);
      if (current === available) continue; // Skip unchanged

      await db.inventory.updateBySku(item.sku, { quantity: available, lastSyncedAt: new Date() });
      // Update Redis cache for real-time product page availability
      const productId = await db.inventory.getProductIdBySku(item.sku);
      if (productId) await redis.setex(`inventory:${productId}`, 3600, String(available));
    }

    hasMore = more;
    page++;
  }

  await redis.set('erp:inventory:last_sync', new Date().toISOString());
}

ベストプラクティス

  • すべての同期処理を冪等にする — 作成前に外部参照(注文番号、SKU)を使って既存の ERP レコードを確認します。これにより、再試行による重複を防げます。
  • 注文同期にはメッセージキューを使用する — チェックアウト中に ERP を同期的に呼び出してはいけません。キューに登録し、再試行を伴う非同期処理を行います。
  • デッドレターキューを実装する — すべての再試行が尽きたら、失敗したジョブを手動確認用の DLQ に移動します。メッセージを黙って破棄してはいけません。
  • 全件同期ではなく差分同期を使用する — 最後の同期タイムスタンプ以降に変更されたレコードを ERP に問い合わせます。全件同期は、数千件を超えるとスケールしません。
  • ERP レコード ID をローカルレコードに保存する — 注文の同期後、ERP の注文 ID をレコードに保存し、相互参照や今後のステータス照会に利用します。
  • 利用可能な在庫を正しく計算する — available = onHand - reserved - safetyStock とし、ゼロ未満にならないようにします。ERP の手持ち在庫数量をそのまま使用してはいけません。

よくある落とし穴

問題解決策
再試行ロジックにより ERP に注文が重複して登録されるEC の注文番号を外部参照として使用し、作成前に存在を確認します。多くの ERP は外部 ID による重複チェックに対応しています。
同期後に在庫数量がマイナスになる利用可能な数量がゼロ未満にならないようにします。Math.max(0, onHand - reserved - safetyStock)
ERP のレート制限により同期が失敗する操作ごとにレートリミッター(トークンバケット)を実装し、大量のデータには個別のアイテム呼び出しではなく、ERP の一括処理/フィード API を使用します。
価格同期によってプロモーション価格が上書きされるERP から取得する基本価格と、コマースプラットフォームで管理するプロモーション価格を分けます。ERP 同期で有効なプロモーションを上書きしてはいけません。
初回の大量データ読み込みがタイムアウトする初回同期をチェックポイント付きのバッチに分割し、失敗後に再開できるようにします。同時実行数を制限して並列処理します。

関連スキル

  • @webhook-architecture
  • @marketplace-connectors
  • @monitoring-alerting-commerce
  • @product-information-management

コメント