分仓(多仓库):从「全球一盘货」到「就近发货」
为什么需要分仓?
当你的茶叶卖到欧洲、北美和国内,如果全部从云南仓发货,海外用户要等半个月,运费也高。如果能在当地设仓,从本地发货,时效和成本都会好很多。分仓就是把「一个店铺、多个发货地」做进系统:用户选仓、商品按仓展示、邮费从仓算起、订单带上发货仓,前后端一气呵成。
本文简要介绍 Teanary 里的分仓设计:产品逻辑、技术实现和你可以怎么用。
产品侧:用户看到什么
-
右上角选仓
和语言、货币并列,多了一个「分仓」下拉。用户选「默认仓库」「欧洲仓」「美国仓」等,当前会话就固定在这个仓,列表、购物车、结账都只针对该仓。 -
商品按仓可见
商品和仓库是多对多关联(一个商品可挂多个仓)。前台只展示当前所选仓库下的商品;首页精选、分类列表、搜索、推荐、随机商品都只出当前仓,避免用户加了一车「欧洲仓」商品却从「国内仓」结账的混乱。 -
购物车与结账
购物车展示时只显示当前仓的商品;去结账时再次校验,只允许当前仓商品进入结算。切换仓库会清空购物车,避免跨仓混单。 -
邮费从仓算
运费计算的起点是当前选中仓库的发货地(国家/省/城市等),终点是用户填的收货地址。后续若要按「国内/国际」「同城免邮」等做差异化,都可以基于「发货仓 + 收货地址」扩展。 -
订单带上发货仓
下单时把当前warehouse_id写入订单;个人中心订单列表、订单详情里会显示「发货仓库」,方便用户和运营核对。
技术侧:怎么实现的
-
数据模型
warehouses:仓库主表(名称、编码、国家/省/市/地址、电话、是否默认、排序等)。product_warehouse:商品与仓库多对多;一个商品可挂在多个仓。orders.warehouse_id:订单所属发货仓,可为空以兼容老单。
-
当前仓的传递
和语言、货币类似:中间件在请求里解析/设置session('warehouse_id'),默认仓由WarehouseService::getDefaultWarehouse()决定;切换仓的接口更新 session,并清空cart_id避免跨仓购物车。 -
商品作用域
Product::scopeForWarehouse($query, $warehouseId):当$warehouseId非空时,whereHas('warehouses', ...)只保留在该仓下的商品。列表、精选、推荐、随机、搜索、购物车过滤、结账校验都统一用「当前 session 仓 + 该 scope 或等价过滤」。 -
邮费计算
运费计算器接口增加可选参数「发货仓」;ShippingService从 session 取当前仓,把仓库对象传给计算器。计算器内部仍按「目的国/区域 + 重量等」算费,起点信息可用于后续做同城/国内/国际区分。 -
缓存与后台
仓库列表由WarehouseService提供,带缓存;后台有仓库 CRUD、商品表单里多选「可发货仓库」。商品保存时warehouses()->sync($warehouseIds),保证前台按仓过滤时数据一致。
小结
分仓把「多发货地」做成一级能力:选仓 → 看当前仓商品 → 购物车/结账仅当前仓 → 邮费从该仓算 → 订单记录发货仓,逻辑清晰、前后端统一。若你后面要做「库存分仓」「就近推荐仓库」或更复杂的运费策略,当前这套仓库与订单、商品的关联也可以直接作为基础扩展。