役割と範囲
Cookinglife(実店舗も展開するキッチン用品の EC)は、私が構築しその後も開発を続けているシステム上で倉庫業務のすべてを回しています。受注、ピッキング(シングル・バッチ・トロリー・多階層)、在庫とロケーション管理、梱包、配送ラベル、そして ERP としての NetSuite との同期まで。継続的な保守と並行して、既存の PHP プラットフォームをモジュール単位で、無停止のまま Laravel へ移行しています。
- 受注から出荷まで:フルフィルメント全体を1つのシステムで扱い、受注と在庫の正となるデータは NetSuite に置く構成。
- 複数のピッキング方式(シングル、バッチ、トロリー、多階層)と、OnScan.js による現場でのバーコードスキャン。
- Sendcloud 経由のマルチキャリア配送ラベル。NetSuite が「packed」ステータス後にラベルを作成した時点で取得する専用のスイープジョブ付き。
- Shopify・Magento との連携に加え、Alumio による住所と電話番号の正規化。
- 無停止での Laravel 移行:nginx のルーティングを使い、モジュール単位(受注、ピッキング、在庫、配送、NetSuite、管理画面)で切り替え、モジュールごとに即時ロールバック可能。
成果
- ピッキング・在庫・配送を別々のツールで回すのではなく、フルフィルメント全体を1つのシステムに集約。
- キット商品(複数パーツで構成される商品)で発生していた NetSuite の構造的なエラーを解消。すべてを1ロケーションに置くことを前提とせず、在庫を複数ロケーションへ自動的に引き当てる方式に変更しました。
- 配送ラベルの作成と取得の間にあった競合状態を、定期実行の同期ジョブで解消。ラベルが時折失われる事象がなくなりました。
- 営業時間外に WMS が停止した場合の予備の出荷経路。復旧後に同じ受注がピッキングキューへ二重に現れないよう、突合ロジックを併設。
- 約2年にわたる継続稼働(2024年8月以降 848 コミット)——新規構築の一発物ではなく、事業とともに成長し続けるシステムです。
アーキテクチャ
構成は3層:共有の Core、TailorFitWMS モジュール本体、そして顧客ごとの Overwrites——企業固有の調整がコアに入り込まない設計です。機能はプラグインモジュールとして構築され、企業ごとに有効・無効を切り替えられ、イベント駆動の拡張ポイントを備えます。データは MySQL(トランザクションデータ)、MongoDB(ドキュメント)、Redis(キャッシュとセッション)に配置。バックグラウンド処理(ラベル同期、NetSuite タスク)は Laravel Horizon 上で、毎時 100〜500 ジョブを処理します。既存 PHP プラットフォームから Laravel への移行は nginx の location ブロックを用いてモジュール単位で進めており、各部分を他に影響させずに個別リリース・個別ロールバックできます。
モデル選定
このプラットフォームの中核に AI/LLM は使っていません。フルフィルメントのロジック(在庫、ピッキング、配送、ERP 連携)に必要なのは言語モデルの柔軟さではなく、予測可能性と追跡可能性です。ここでの本質的な「モデル選定」は AI ではなくアーキテクチャの選択でした。既存の独自 PHP フレームワークから Laravel へ移り、より広いツールのエコシステムと保守性を得る。ただし一括書き換えではなく段階的にモジュール単位で進め、24時間365日倉庫を動かすシステムのリスクを抑えています。
コストとレイテンシ
- 本番環境は Docker サービス5つで稼働し、それぞれに 8 CPU / 8GB を割り当て——ピーク時だけでなく、受注と NetSuite のトラフィックを常時処理するシステムに見合ったサイジングです。
- 配送ラベルは意図的に非同期です。NetSuite は「packed」ステータスの後にはじめてラベルを作成するため、システムが待つ必要はありません——受注ごとにポーリングするのではなく、スイープジョブが3分おきにまとめて取得します。
- NetSuite 連携(OAuth1 TBA、SOAP + RESTlet + REST)はチェーンの中で相対的に遅い部分ですが、スキャンやピッキングといった現場の操作はそれとは切り離して動き続けます。
学んだこと
キット商品(複数パーツで構成される商品)は NetSuite の VALID_LINE_ITEM_REQD エラーで繰り返し止まっていました。パーツの在庫が複数ロケーションに分散している一方、NetSuite は明細1行につき1ロケーションを前提としていたためです。RESTlet 連携を書き換え、在庫を複数ロケーションへ自動で引き当てることで解決しました。配送ラベルが時折失われていたのは、NetSuite がラベルを作成する瞬間とシステムが取得する瞬間の競合状態が原因で、3分おきの定期スイープジョブにより、一時的なパッチではなく構造的に解決しました。さらに、WMS 自体が停止する夜間や週末に備えて外部の緊急経路を用意し、出荷を継続できるようにしています——システム復旧後に同じ受注がピッキングキューへ二重に現れないよう、突合ロジックを組み込んでいます。