关于 multi-warehouse
multi-warehouse 是一项面向运营多个仓库、门店或 3PL 的商家的库存管理技能,介绍如何分配订单并在地点间调拨库存。它按 Shopify、WooCommerce、BigCommerce 和自定义店面说明配置方式及拆分履约。
主要功能
- 01比较平台的多地点库存方案
- 02配置按位置分配库存与订单
- 03创建地点间的库存调拨
- 04处理拆分发货并平衡库存
适用场景
- 01按距离或成本将订单路由到仓库
- 02在多个地点间重新分配库存
- 03将订单拆分到多个仓库履约
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill multi-warehouse安装后在 Claude Code 中输入/multi-warehouse确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/proximity-based-allocation-algorithm/criteria.jsontask.mdevals/split-fulfillment-records-and-customer-n/criteria.jsontask.mdevals/transfer-order-lifecycle-management/criteria.jsontask.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 原生支持多地点库存。
设置地点:
- 前往 Settings → Locations → Add location
- 添加每个仓库、门店或履行中心
- 输入地址(用于计算运费)
- 将产品分配到各个地点 — 前往产品页面,点击某个变体,并设置该地点的库存数量
配置履行路由:
- 前往 Settings → Shipping and delivery → Shipping
- 在 Fulfillment from 下,为各个地点设置优先顺序
- Shopify 会将订单路由到优先列表中第一个备齐所有商品的地点
- 如需更复杂的路由(基于距离、成本优化),请从 App Store 安装 ShipHero 或 ShipBob
在地点之间创建调拨订单:
- 前往 Inventory → Transfers → Create transfer
- 选择 Origin location(库存来源地点)
- 选择 Destination location(库存目的地点)
- 添加需要调拨的产品和数量
- 货物到达后,前往 Transfers → [Transfer] → Accept items — Shopify 会自动增加目的地点的库存,并减少来源地点的库存
第三方物流集成:
- 对于 ShipBob:从 App Store 安装 ShipBob;ShipBob 会从 Shopify 获取订单,并自动将订单路由到最近的履行中心
- 对于 Flexport:安装 Flexport 应用;产品和库存会双向同步
WooCommerce
WooCommerce 需要安装插件才能管理多地点库存。
选项 A:ATUM Multi-Inventory(推荐)
- 安装 ATUM Inventory Management for WooCommerce(免费核心版)和 ATUM Multi-Inventory 扩展(付费)
- 在 ATUM 中,前往 Multi-Inventory → Locations → Add Location
- 对于每个产品,前往 ATUM Multi-Inventory 选项卡,按地点分配库存数量
- 配置库存选择规则(基于区域、基于优先级或优先选择有货地点)
选项 B:WooCommerce Multi-Location Inventory by Iconic
- 安装该插件
- 前往 WooCommerce → Multi-Location Inventory → Locations,添加仓库
- 编辑每个产品并设置各地点的库存数量
- 在插件设置中配置履约路由
调拨订单:
- ATUM Multi-Inventory 包含调拨订单功能——前往 ATUM → Inventory Transfers → New Transfer
- 设置来源地点、目标地点、产品和数量
- 标记为已接收,即可自动更新库存
BigCommerce
BigCommerce 可通过免费应用支持多地点库存。
启用多地点库存:
- 前往 Apps → Search "Multi-Location Inventory"
- 安装并启动应用
- 添加地点:前往 Locations → Add location,填写名称和地址
- 按产品和地点分配库存
配置履约路由:
- 在 Multi-Location Inventory 应用中,前往 Routing Rules
- 设置规则:从最近地点发货、从成本最低的地点发货、从库存最多的地点发货等
- 当订单无法由单个地点履约时,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