mcpField
M
multi-warehouse

multi-warehouse

github.com/finsilabs其他60106 次安装6 天前更新
代码仓库

关于 multi-warehouse

multi-warehouse 是一项面向运营多个仓库、门店或 3PL 的商家的库存管理技能,介绍如何分配订单并在地点间调拨库存。它按 Shopify、WooCommerce、BigCommerce 和自定义店面说明配置方式及拆分履约。

主要功能

  1. 01比较平台的多地点库存方案
  2. 02配置按位置分配库存与订单
  3. 03创建地点间的库存调拨
  4. 04处理拆分发货并平衡库存

适用场景

  1. 01按距离或成本将订单路由到仓库
  2. 02在多个地点间重新分配库存
  3. 03将订单拆分到多个仓库履约

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill multi-warehouse

安装后在 Claude Code 中输入/multi-warehouse确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/proximity-based-allocation-algorithm/
criteria.json
task.md
evals/split-fulfillment-records-and-customer-n/
criteria.json
task.md
evals/transfer-order-lifecycle-management/
criteria.json
task.md

详细介绍

多仓库库存

概述

多仓库库存让你能够在多个实体地点(仓库、门店、第三方物流服务商)存放产品,并将每笔订单分配给最适合履行订单的地点。Shopify 和 BigCommerce 内置了多地点库存功能。WooCommerce 则需要安装插件。只有当你的路由逻辑(基于区域、成本优化、拆分履行)超出平台工具的支持范围时,才需要构建自定义分配引擎。

何时使用此技能

  • 商家运营多个仓库、门店或第三方物流服务商时
  • 运费占比高,且需要从最近的仓库路由订单时
  • 各地点之间的库存不平衡,需要通过调拨订单重新分配库存时
  • 有时必须将订单拆分到两个仓库履行,因为没有任何单一地点备齐所有商品时

核心说明

第 1 步:确定平台并选择合适的工具
平台内置多地点库存推荐扩展
Shopify支持 — 最多 1,000 个地点(Basic:4 个,Shopify plan:5 个,Advanced:8 个)使用 ShipBob 或 Flexport 路由第三方物流履行;使用 ShipHero 配置高级路由规则
WooCommerce不支持 — 需要安装插件ATUM Multi-Inventory(附加组件)或 Iconic 的 WooCommerce Multi-Location Inventory
BigCommerce支持 — Multi-Location Inventory 应用(免费)使用 ShipStation 配置高级路由;使用 Whiplash 或 ShipBob 集成第三方物流
自定义 / Headless构建优先考虑距离的路由分配引擎当平台工具无法满足路由和拆分履行需求时必须自建

第 2 步:平台专属设置

Shopify

Shopify 原生支持多地点库存。

设置地点:

  1. 前往 Settings → Locations → Add location
  2. 添加每个仓库、门店或履行中心
  3. 输入地址(用于计算运费)
  4. 将产品分配到各个地点 — 前往产品页面,点击某个变体,并设置该地点的库存数量

配置履行路由:

  1. 前往 Settings → Shipping and delivery → Shipping
  2. 在 Fulfillment from 下,为各个地点设置优先顺序
  3. Shopify 会将订单路由到优先列表中第一个备齐所有商品的地点
  4. 如需更复杂的路由(基于距离、成本优化),请从 App Store 安装 ShipHero 或 ShipBob

在地点之间创建调拨订单:

  1. 前往 Inventory → Transfers → Create transfer
  2. 选择 Origin location(库存来源地点)
  3. 选择 Destination location(库存目的地点)
  4. 添加需要调拨的产品和数量
  5. 货物到达后,前往 Transfers → [Transfer] → Accept items — Shopify 会自动增加目的地点的库存,并减少来源地点的库存

第三方物流集成:

  • 对于 ShipBob:从 App Store 安装 ShipBob;ShipBob 会从 Shopify 获取订单,并自动将订单路由到最近的履行中心
  • 对于 Flexport:安装 Flexport 应用;产品和库存会双向同步

WooCommerce

WooCommerce 需要安装插件才能管理多地点库存。

选项 A:ATUM Multi-Inventory(推荐)

  1. 安装 ATUM Inventory Management for WooCommerce(免费核心版)和 ATUM Multi-Inventory 扩展(付费)
  2. 在 ATUM 中,前往 Multi-Inventory → Locations → Add Location
  3. 对于每个产品,前往 ATUM Multi-Inventory 选项卡,按地点分配库存数量
  4. 配置库存选择规则(基于区域、基于优先级或优先选择有货地点)

选项 B:WooCommerce Multi-Location Inventory by Iconic

  1. 安装该插件
  2. 前往 WooCommerce → Multi-Location Inventory → Locations,添加仓库
  3. 编辑每个产品并设置各地点的库存数量
  4. 在插件设置中配置履约路由

调拨订单:

  • ATUM Multi-Inventory 包含调拨订单功能——前往 ATUM → Inventory Transfers → New Transfer
  • 设置来源地点、目标地点、产品和数量
  • 标记为已接收,即可自动更新库存

BigCommerce

BigCommerce 可通过免费应用支持多地点库存。

启用多地点库存:

  1. 前往 Apps → Search "Multi-Location Inventory"
  2. 安装并启动应用
  3. 添加地点:前往 Locations → Add location,填写名称和地址
  4. 按产品和地点分配库存

配置履约路由:

  1. 在 Multi-Location Inventory 应用中,前往 Routing Rules
  2. 设置规则:从最近地点发货、从成本最低的地点发货、从库存最多的地点发货等
  3. 当订单无法由单个地点履约时,BigCommerce 会自动拆分发货

集成 3PL:

  • 从 BigCommerce App Marketplace 安装 ShipStation
  • ShipStation 会连接到你的 3PL 服务商,并根据你的规则进行路由

Custom / Headless

对于无头店面,构建一个分配引擎,为每笔订单选择最佳地点:

// lib/allocation.ts
import { haversineDistance } from './geo';

interface OrderItem { variantId: string; quantity: number; productName: string; }
interface AllocationResult {
  type: 'single' | 'split';
  fulfillments: { locationId: string; items: OrderItem[] }[];
}

export async function allocateOrder(orderItems: OrderItem[], shippingAddress: Address): Promise<AllocationResult> {
  const locations = await db.locations.findMany({ where: { active: true } });

  // Fetch inventory availability for all required variants at all locations
  const inventory = await db.inventoryLevels.findMany({
    where: { variantId: { in: orderItems.map(i => i.variantId) } },
  });
  const invMap = buildInventoryMap(inventory); // locationId -> variantId -> available

  // Score locations by distance to shipping address (proximity-first routing)
  const scored = locations
    .map(loc => ({
      ...loc,
      distanceKm: haversineDistance({ lat: loc.lat, lng: loc.lng }, { lat: shippingAddress.lat, lng: shippingAddress.lng }),
    }))
    .sort((a, b) => a.distanceKm - b.distanceKm);

  // Prefer single-location fulfillment to avoid split shipments
  for (const location of scored) {
    const canFulfillAll = orderItems.every(item => (invMap[location.id]?.[item.variantId] ?? 0) >= item.quantity);
    if (canFulfillAll) return { type: 'single', fulfillments: [{ locationId: location.id, items: orderItems }] };
  }

  // Fall back to split fulfillment — greedy assignment to nearest location with stock
  const remaining = [...orderItems];
  const fulfillments: AllocationResult['fulfillments'] = [];

  for (const location of scored) {
    const canFulfill = remaining.filter(item => (invMap[location.id]?.[item.variantId] ?? 0) >= item.quantity);
    if (canFulfill.length > 0) {
      fulfillments.push({ locationId: location.id, items: canFulfill });
      canFulfill.forEach(item => remaining.splice(remaining.findIndex(r => r.variantId === item.variantId), 1));
    }
    if (remaining.length === 0) break;
  }

  if (remaining.length > 0) throw new Error('Cannot fulfill order — insufficient stock across all locations');
  return { type: 'split', fulfillments };
}

// Transfer order management
export async function receiveTransferOrder(transferOrderId: string, receivedItems: { variantId: string; quantity: number }[]) {
  const transfer = await db.transferOrders.findUnique({ where: { id: transferOrderId }, include: { items: true } });

  await db.$transaction([
    // Decrease on_hand + reserved at source
    ...receivedItems.map(item => db.inventoryLevels.update({
      where: { variantId_locationId: { variantId: item.variantId, locationId: transfer.fromLocationId } },
      data: { onHand: { decrement: item.quantity }, reserved: { decrement: item.quantity } },
    })),
    // Increase on_hand at destination
    ...receivedItems.map(item => db.inventoryLevels.upsert({
      where: { variantId_locationId: { variantId: item.variantId, locationId: transfer.toLocationId } },
      create: { variantId: item.variantId, locationId: transfer.toLocationId, onHand: item.quantity, reserved: 0 },
      update: { onHand: { increment: item.quantity } },
    })),
    db.transferOrders.update({ where: { id: transferOrderId }, data: { status: 'received' } }),
  ]);
}

第 3 步:配置拆分发货通知

当订单由两个地点分别发货时,必须在客户确认订单前告知他们。

Shopify: 需要拆分发货时,Shopify 的原生结账流程会在配送选项中显示“从多个地点发货”。可在 Settings → Checkout → Checkout language 中自定义提示信息。

WooCommerce: ATUM 检测到需要拆分发货时,在购物车或结账页面显示通知。ATUM Multi-Inventory 包含可配置的拆分发货提示信息。

在订单确认邮件中: 列出所有履约组及其预计发货日期。“您的订单将分 2 次送达”,并附上商品明细,有助于减少支持工单。


第 4 步:平衡各地点的库存

长期库存失衡(一处仓库库存过多,另一处缺货)会增加调拨成本和拆分履约的情况。每周进行一次分析:

失衡迹象:

  • 某个地点的同一 SKU 持续为零库存,而另一地点库存过剩
  • 同一 SKU 超过 20% 的订单需要拆分履约
  • 同一产品、同一方向的调拨请求每周重复出现

措施: 在下一轮补货周期前,从库存过剩的地点创建调拨订单,将库存调往库存不足的地点。

Shopify Stocky: 提供“库存分布”报告,可识别各地点之间库存失衡的 SKU。

最佳实践

  • 优先选择单地点履约 — 拆分发货会增加成本并造成混乱;先穷尽单地点的方案,再考虑拆分
  • 购买应用前,先使用平台原生的多地点功能 — Shopify 和 BigCommerce 可以很好地处理常见情况;只有当路由逻辑比平台支持的更复杂时,才添加应用
  • 将调拨订单视为在途库存 — 调拨发货时,减少发货地点的可用库存;货物到达并完成清点后,再增加目的地库存
  • 在结账时告知客户拆分发货,不要等到购买后 — 付款后才得知这一情况会让客户觉得受到了欺骗
  • 每周进行库存平衡分析 — 花 15 分钟查看分布报告,可避免长期失衡不断加剧

常见问题

问题解决方案
拆分履约产生两笔运费按订单合并运费;承担第二次发货的费用,或在结账时通知客户——绝不要在不告知客户的情况下收取两次运费
调拨的收货数量与发货数量不符支持部分收货——按商品记录实际收货数量,并单独处理差异(如运输途中损坏)
各地点的库存被重复计算每条库存记录都对应一个地点;报告总库存时,务必明确汇总各地点的库存
履约过程中地点离线将该地点标记为非活跃状态;重新为分配到该地点但尚未履约的订单进行分配
客户对多个跟踪号感到困惑每次履约分别发送跟踪邮件,并明确注明:“这是您订单中的第 1 个包裹,共 2 个”

相关技能

  • @inventory-tracking
  • @low-stock-alerts
  • @catalog-import-export

评论

支持 Markdown