TailorFitWMS — 仓储管理系统

为 Cookinglife 量身打造的仓储系统:从订单接收、拣货到库存管理、面单打印以及 NetSuite ERP 同步——已连续生产运行与维护近两年。

仓储管理
PHP → Laravel 迁移
NetSuite ERP
多承运商发货

角色与范围

Cookinglife(经营厨具的电商,同时拥有线下门店)的全部仓储流程都跑在我搭建并持续演进的系统上:订单接收、拣货(单件、批量、拣货车与多层拣选)、库存与库位管理、打包、面单以及与作为 ERP 的 NetSuite 的同步。在持续维护之外,现有的 PHP 平台正逐个模块迁移到 Laravel,全程不停机。

  • 从订单接收到发货:一套系统覆盖完整履约流程,并以 NetSuite 作为订单与库存的唯一数据源。
  • 多种拣货方式(单件、批量、拣货车、多层),并通过 OnScan.js 在现场扫码。
  • 通过 Sendcloud 生成多承运商面单,并配有自建的扫描任务:NetSuite 在“packed”状态后一创建面单即被拉取。
  • 对接 Shopify 与 Magento,并通过 Alumio 完成地址与电话号码的规范化。
  • 零停机迁移到 Laravel:通过 nginx 路由逐模块推进(订单、拣货、库存、发货、NetSuite、后台),每个模块均可即时回滚。
查看线上平台

成果

  • 用一套系统覆盖完整履约流程,取代拣货、库存与发货各自为政的工具。
  • 解决了套装订单(由多个部件组成的商品)上的结构性 NetSuite 报错:改为让库存自动跨多个库位分配,而不再要求所有部件位于同一库位。
  • 用一个周期性同步任务消除了面单创建与拉取之间的竞态条件,面单不再偶发丢失。
  • 为 WMS 在非工作时间宕机准备的备用发货通道,并带有对账逻辑,避免订单在拣货队列中重复出现。
  • 已连续生产运行近两年(自 2024 年 8 月起 848 次提交)——不是一次性的全新项目,而是随业务持续生长的系统。

架构

三层结构:共享的 Core、TailorFitWMS 模块本身,以及按客户划分的 Overwrites——让企业专属的调整不会渗入内核。功能以插件模块的形式构建,可按企业开关,并提供事件驱动的扩展点。数据分别存放在 MySQL(事务数据)、MongoDB(文档)与 Redis(缓存与会话);后台任务(面单同步、NetSuite 任务)经由 Laravel Horizon 运行,每小时 100 至 500 个作业。现有 PHP 平台向 Laravel 的迁移通过 nginx location 块逐模块进行,因此每一部分都能单独上线与回滚,不影响其余部分。

模型选择

这套平台的核心不含 AI/LLM——履约逻辑(库存、拣货、发货、ERP 同步)需要的是可预测与可追溯,而不是语言模型的灵活性。这里真正的“模型选择”是架构层面的而非 AI:从现有的自研 PHP 框架转向 Laravel,以获得更大的工具生态与更好的可维护性;但采取逐模块的渐进方式而非一次性重写,以控制一个 7×24 小时驱动仓库的系统所承担的风险。

成本与延迟

  • 生产环境运行在 5 个 Docker 服务上,每个分配 8 CPU / 8GB——这一规格面向持续处理订单与 NetSuite 流量的系统,而非仅应对峰值。
  • 面单刻意设计为异步:NetSuite 在“packed”状态之后才创建面单,系统无需等待——扫描任务每 3 分钟统一拉取,而不是逐单轮询。
  • NetSuite 集成(OAuth1 TBA、SOAP + RESTlet + REST)是链路中较慢的一环;扫码、拣货等本地仓库操作与之解耦,照常运行。

经验总结

套装订单(由多个部件组成的商品)屡屡卡在 NetSuite 的 VALID_LINE_ITEM_REQD 报错上:部件库存分散在多个库位,而 NetSuite 期望每行只对应一个库位。解决方式是重写 RESTlet 集成,让库存自动跨多个库位分配。面单偶尔丢失,源于 NetSuite 创建面单与系统拉取面单之间的竞态条件;每 3 分钟运行一次的周期性扫描任务从结构上解决了它,而不是打一次性补丁。至于 WMS 自身在夜间和周末宕机的情况,另有一条外部应急通道保证仍能发货——并配有对账逻辑,防止系统恢复后这些订单在拣货队列中重复出现。