当前位置: 首页 > news >正文

SpringBoot+Vue3 仓储管理系统(WMS)设计:商品·SKU·出入库·移库·盘点全流程拆解

SpringBoot+Vue3 仓储管理系统(WMS)设计:商品·SKU·出入库·移库·盘点全流程拆解

🌐演示地址:http://ruoyioffice.com | 📦源码1·GitHub:ruoyi-office | 📦源码2·GitCode:ruoyi-office | 📦源码3·Gitee:ruoyi-office | 💬微信:17156169080(备注「RuoYi Office」)

进销存里的库存只回答"还剩多少",而仓储管理系统(WMS,Warehouse Management System)要回答更细的问题:哪个 SKU 在哪个仓库、出入库怎么走单、跨仓移库怎么记、盘点对不上账怎么调。WMS 是物流仓储的执行层,并发尤其严苛——多个出库单可能同时扣同一个 SKU 的库存。RuoYi Office 的仓储模块(yudao-module-wms)以"商品-SKU"主数据 + "SKU × 仓库"库存余额为核心,用入库单、出库单、移库单、盘点单四类单据驱动库存,单据走草稿→完成→作废,完成时才真正写库存;库存变更用悲观锁SELECT ... FOR UPDATE批量锁定后在内存计算校验,从根上杜绝并发超卖,每一次变动都落wms_inventory_history流水可追溯。

▲ 全景图:商品-SKU 主数据 + 仓库档案 → 入库/出库/移库/盘点四类单据 → "SKU×仓库"库存余额(悲观锁变更)→wms_inventory_history流水贯穿全程

引言:仓储管理(WMS)到底难在哪?

“仓库管理不就是记一下进出吗?和进销存有啥区别?”——这是个常见误解。WMS 的复杂度来自"颗粒度更细"和"并发更猛":

库存颗粒度到 SKU:同一个商品有不同规格(颜色、尺码、批次),WMS 的库存维度是"SKU × 仓库",而不是粗粒度的"商品"。一件衬衫,红色 L 码和蓝色 M 码是两个 SKU、两条库存。

单据有完整生命周期:仓库单据不是"建好就生效",而是草稿(待作业)→ 完成(已作业)→ 作废。只有"完成"时才真正写库存,草稿阶段可以反复改、可以删,作废后不影响库存。

并发是常态,超卖是灾难:WMS 往往对接电商订单、产线领料,同一个 SKU 可能被多个出库单同时扣减。如果库存扣减没做好并发控制,超卖几乎必然发生。

盘点要防"边盘边变":盘点时录入"实盘数量",但如果这期间别人又出入了库,账面就变了,直接覆盖会把别人的变动冲掉。盘点必须校验"账面快照有没有被改过"。

本文以 RuoYi Office 的yudao-module-wms模块为例,基于真实源码,完整拆解商品-SKU 主数据、四类单据、库存悲观锁变更、盘点快照校验、库存流水的设计与实现。


一、业务设计:以"SKU×仓库"库存为核心的仓储模型

1.1 核心抽象:库存维度是 SKU × 仓库

结论先行:WMS 的库存余额表wms_inventory(sku_id, warehouse_id)为维度,记录每个 SKU 在每个仓库的当前数量。商品(wms_item)是聚合概念,真正有库存、能出入库的是 SKU(wms_item_sku)——它带条码、规格、尺寸重量、成本价/销售价。这种"商品-SKU 两层"的主数据结构,是 WMS 能精细管理多规格商品的基础。

1.2 四类单据:入库、出库、移库、盘点

仓库的全部作业都收敛到四类单据,每类对应一种库存变更模式:

入库单 wms_receipt_order:库存 +(采购到货、退货入库、其它入库) 出库单 wms_shipment_order:库存 −(销售出库、领料出库、其它出库) 移库单 wms_movement_order:A 仓 − + B 仓 +(仓间转移,总量不变) 盘点单 wms_check_order:按实盘数量调平账面(盘盈 + / 盘亏 −)

每类单据都有主表 + 明细(detail),明细记录"哪个 SKU、哪个仓库、多少数量、单价"。

1.3 单据生命周期:草稿 → 完成 → 作废

仓库单据不是"建好即生效",而是有明确的状态机WmsOrderStatusEnum

状态枚举含义是否影响库存
草稿PREPARE待作业,可改可删
完成FINISHED已作业,写入库存
作废CANCELED已取消

关键设计:只有"完成(complete)"动作才真正写库存。草稿阶段单据可以反复编辑、删除;完成后库存生效;作废只能在草稿态进行。状态流转用updateByIdAndStatus(id, 旧状态, 新状态)乐观校验,防止并发把同一张单据完成两次。


二、系统设计:模块组成与核心决策

2.1 模块组成

WMS 模块由"主数据 + 库存 + 四类单据"组成:

子模块数据表功能
仓库wms_warehouse仓库档案(编号/名称/排序)
往来单位wms_merchant供应商/客户等往来单位
商品 / SKUwms_item/wms_item_sku商品聚合 / 可入出库的规格单元
商品分类 / 品牌wms_item_category/wms_item_brand商品维度配置
库存余额wms_inventorySKU×仓库当前数量(真相源)
库存流水wms_inventory_history每次变动一条流水,可追溯
入库单wms_receipt_order(+detail)完成后库存 +
出库单wms_shipment_order(+detail)完成后库存 −
移库单wms_movement_order(+detail)A 仓 − / B 仓 +,总量不变
盘点单wms_check_order(+detail)按实盘数量调平账面

2.2 核心设计决策

决策点方案理由
库存维度(sku_id, warehouse_id)精细到 SKU、支持多仓
库存写入时机仅"完成"动作写库存草稿可改可删,作业才生效
并发控制悲观锁SELECT ... FOR UPDATE批量锁定多单同扣 SKU 不超卖
库存计算锁定后内存批量计算+校验,再批量更新一次锁、一致更新
库存行缺失不存在则创建,唯一索引冲突回查高并发下补行安全
盘点防覆盖校验账面快照未被改过边盘边变不冲掉别人变动
全程留痕wms_inventory_history流水每次变动可追溯

三、PC 端功能实现

3.1 库存查询

库存页按"SKU × 仓库"展示当前数量,是仓库的实时全局视图。

▲ 库存列表:每行是一个"SKU × 仓库"的当前余额,数量由四类单据完成时变更,可下钻到库存流水查看每一次出入

设计要点

  • SKU 级精度:同一商品不同规格分别记库存,库存维度是 SKU 而非商品。
  • 多仓并行:同一 SKU 在不同仓库各有一行库存,支持跨仓查询与移库。
  • 只读不可手改:库存数量不能直接编辑,只能通过四类单据变更。

3.2 入库单列表

入库单是"货进仓"的单据,草稿态可编辑,完成时给对应仓库加库存。

▲ 入库单列表:状态分草稿/完成/作废,只有点"完成"才真正写库存并落入库流水;草稿态可改可删

设计要点

  • 完成才生效:草稿态不动库存,"完成"动作才把明细数量加进库存。
  • 金额自动汇总:总数量、总金额由明细行自动汇总(明细可直接填行金额或按单价×数量算)。
  • 类型可配:入库类型(采购入库/退货入库/其它入库)用字典配置。

3.3 盘点单列表

盘点单用于"账实对账",录入实盘数量后按盈亏调平账面库存。

▲ 盘点单列表:盘点录入实盘数量,完成时校验账面快照未被改动,再按盈亏生成调整流水

设计要点

  • 账面快照:盘点明细记录盘点时的账面数量,完成时校验"账面没被别人改过"。
  • 盈亏自动算:实盘与账面的差即盈亏,生成对应的库存调整与流水。
  • 无盈亏不写:实盘等于账面时不更新库存、不生成流水,避免无效记录。

四、后端核心实现

4.1 单据完成才写库存:以入库单为例

入库单完成时分两步:先用updateByIdAndStatus把状态从"草稿"乐观更新为"完成"(防并发重复完成),再调用库存服务写入库存。入库在库存变更模型里用正数数量:

@Override@Transactional(rollbackFor=Exception.class)publicvoidcompleteReceiptOrder(Longid){// 1.1 校验存在且为草稿;1.2 校验明细存在WmsReceiptOrderDOorder=validateReceiptOrderPrepare(id);List<WmsReceiptOrderDetailDO>details=receiptOrderDetailService.validateReceiptOrderDetailListExists(id);// 2. 乐观更新状态:草稿 → 完成(影响 0 行说明已被并发完成)if(receiptOrderMapper.updateByIdAndStatus(id,WmsOrderStatusEnum.PREPARE.getStatus(),newWmsReceiptOrderDO().setStatus(WmsOrderStatusEnum.FINISHED.getStatus()))==0){throwexception(RECEIPT_ORDER_STATUS_NOT_PREPARE);}// 3. 写入库存(正数 = 入库)createInventory(order,details);}privatevoidcreateInventory(WmsReceiptOrderDOorder,List<WmsReceiptOrderDetailDO>details){List<WmsInventoryChangeReqDTO.Item>items=convertList(details,d->BeanUtils.toBean(d,WmsInventoryChangeReqDTO.Item.class));inventoryService.changeInventory(newWmsInventoryChangeReqDTO().setOrderId(order.getId()).setOrderNo(order.getNo()).setOrderType(WmsOrderTypeEnum.RECEIPT.getType()).setItems(items));}

出库单、移库单完全同构,只是数量正负不同(出库传负数、移库一出一进)。

4.2 库存变更的并发核心:悲观锁批量锁定 + 内存计算

这是 WMS 最关键的技术点。与 ERP 的"条件 UPDATE"不同,WMS 走悲观锁路线——先把本次涉及的所有库存行用SELECT ... FOR UPDATE批量锁住,再在内存里逐条计算、校验充足性,全部通过后批量更新。一次加锁、一致更新,避免多单并发交错扣减:

privateMap<Item,Tuple>changeInventoryList(List<WmsInventoryChangeReqDTO.Item>items){// 1.1 创建或定位本次涉及的库存行List<WmsInventoryDO>inventories=getOrCreateInventoryList(items);// 1.2 悲观锁:按 ID 批量 SELECT ... FOR UPDATE,锁住这些库存行inventories=inventoryMapper.selectListByIdsForUpdate(convertSet(inventories,WmsInventoryDO::getId));// 2.1 在内存里逐条计算变更后数量,并校验库存充足Map<Item,Tuple>resultMap=newIdentityHashMap<>(items.size());for(Itemitem:items){WmsInventoryDOinventory=findInventory(inventories,item);BigDecimalbefore=inventory.getQuantity();BigDecimalafter=before.add(item.getQuantity());// 出库时 quantity 为负if(after.compareTo(BigDecimal.ZERO)<0){throwbuildInventoryQuantityNotEnoughException(item,before);// 库存不足}inventory.setQuantity(after);resultMap.put(item,newTuple(before,after));}// 2.2 全部校验通过后,批量更新库存数量(已加锁,安全)inventoryMapper.updateBatch(convertList(inventories,inv->newWmsInventoryDO().setId(inv.getId()).setQuantity(inv.getQuantity())));returnresultMap;}

为什么用悲观锁而不是条件 UPDATE?因为一次出库往往涉及多个 SKU,且移库还要同时改两个仓库行。把相关库存行一次性锁住、在内存里整体计算校验,能保证"要么整单成功、要么整单失败"的一致性,比逐行条件更新更适合多明细场景。

4.3 库存行缺失:创建 + 唯一索引冲突回查

库存行可能还不存在(某 SKU 第一次入某仓)。系统先批量查已有行,对缺失的执行创建;高并发下两个线程可能同时创建同一行,靠数据库唯一索引拦截,捕获DuplicateKeyException后回查已建行:

privateList<WmsInventoryDO>createMissingInventoryList(List<WmsInventoryDO>missing){List<WmsInventoryDO>created=newArrayList<>(missing.size());for(WmsInventoryDOm:missing){WmsInventoryDOinventory=newWmsInventoryDO().setSkuId(m.getSkuId()).setWarehouseId(m.getWarehouseId()).setQuantity(BigDecimal.ZERO);try{inventoryMapper.insert(inventory);}catch(DuplicateKeyExceptionex){// 并发下别人已创建:按唯一键回查已有库存行inventory=inventoryMapper.selectBySkuIdAndWarehouseId(m.getSkuId(),m.getWarehouseId());if(inventory==null){throwex;}}created.add(inventory);}returncreated;}

4.4 盘点防覆盖:账面快照校验

盘点最怕"边盘边变"——录入实盘时别人又出入了库。系统在盘点完成时用SELECT ... FOR UPDATE锁住库存行,并校验"当前账面数量是否仍等于盘点时记录的快照",不一致就报错,避免把别人的变动冲掉:

privateWmsInventoryDOgetOrCreateCheckInventory(WmsInventoryCheckReqDTO.Itemitem){if(item.getInventoryId()==null){returncreateCheckInventory(item);}// 锁定库存行WmsInventoryDOinventory=inventoryMapper.selectByIdForUpdate(item.getInventoryId());// 校验:库存行还在、SKU/仓库一致、且账面数量未被改动(与盘点快照一致)if(inventory==null||!isSameInventory(inventory,item)||inventory.getQuantity().compareTo(item.getQuantity())!=0){throwexception(CHECK_ORDER_INVENTORY_CHANGED);// 盘点期间库存已变化}returninventory;}

盘点只在"实盘 ≠ 账面"时才更新库存并写流水(盈亏调整),相等则跳过——避免无效记录。

4.5 库存流水:每次变动都留痕

无论入库、出库、移库还是盘点,库存变更后都生成wms_inventory_history流水,记录变更前后数量、单价、关联单据:

privateWmsInventoryHistoryDObuildInventoryHistory(WmsInventoryChangeReqDTOreqDTO,Itemitem,Tupleresult){returnnewWmsInventoryHistoryDO().setWarehouseId(item.getWarehouseId()).setSkuId(item.getSkuId()).setQuantity(item.getQuantity())// 本次变动量(正入负出).setBeforeQuantity(result.get(0)).setAfterQuantity(result.get(1))// 变更前/后.setPrice(item.getPrice()).setTotalPrice(item.getTotalPrice()).setOrderId(reqDTO.getOrderId()).setOrderNo(reqDTO.getOrderNo()).setOrderType(reqDTO.getOrderType());// 关联单据类型}

五、RuoYi Office 的创新设计

5.1 商品-SKU 两层主数据,库存精细到规格

WMS 把"商品"和"SKU"分成两层:商品是聚合,SKU 才是带条码、尺寸、重量、成本价的最小库存单元。库存维度是"SKU × 仓库",因此能精细管理多规格、多批次商品——这是 WMS 区别于粗粒度库存系统的根本。

5.2 单据生命周期清晰,"完成"才写库存

四类单据都走"草稿→完成→作废"。草稿态随便改、随便删,"完成"动作才把库存写进去。这种延迟生效的设计让作业有缓冲、可纠错,也让库存变更点高度集中、易于审计。

5.3 悲观锁批量锁定,多明细整单一致

库存变更先SELECT ... FOR UPDATE批量锁住相关库存行,再在内存整体计算校验、批量更新。对"一单多 SKU、移库改两仓"的复杂场景,能保证整单原子一致,从根上防并发超卖。

5.4 库存行补建的并发安全

某 SKU 第一次入某仓时库存行还不存在,系统按需创建;并发创建冲突时靠唯一索引拦截 + 回查,保证"库存行不会重复、也不会丢",这是高并发补行的经典处理。

5.5 盘点账面快照校验,杜绝边盘边变

盘点完成时锁定库存行并校验"账面是否仍等于盘点时的快照",不一致直接报错。这避免了"盘点录入慢、期间别人出入库,结果实盘一提交把别人的变动冲掉"的数据事故。


六、数据结构

6.1 表结构:wms_inventory(库存余额,真相源)

字段类型说明
idbigint主键
sku_idbigint商品 SKU 编号
warehouse_idbigint仓库编号
quantitydecimal当前库存数量

6.2 表结构:wms_inventory_history(库存流水)

字段类型说明
idbigint主键
sku_id/warehouse_idbigintSKU / 仓库
quantitydecimal本次变动量(正入负出)
before_quantity/after_quantitydecimal变更前 / 变更后数量
price/total_pricedecimal单价 / 金额
order_id/order_no/order_typebigint/varchar/int关联单据 ID / 单号 / 类型

6.3 表结构:wms_item_sku(商品 SKU,节选)

字段类型说明
id/item_idbigint主键 / 所属商品
name/code/bar_codevarchar规格名 / 规格编号 / 条码
length/width/heightdecimal长 / 宽 / 高(cm)
gross_weight/net_weightdecimal毛重 / 净重(kg)
cost_price/selling_pricedecimal成本价 / 销售价

6.4 表结构:wms_receipt_order(入库单,节选)

字段类型说明
id/nobigint/varchar主键 / 入库单号
type/statusint入库类型 / 状态(草稿/完成/作废)
warehouse_id/merchant_idbigint仓库 / 往来单位
total_quantity/total_pricedecimal总数量 / 总金额

6.5 设计要点

  • 库存维度唯一wms_inventory(sku_id, warehouse_id)建唯一索引,是补行冲突回查与悲观锁的基础。
  • 多租户:所有表基于 Yudao 多租户体系隔离数据。
  • 金额数量BigDecimal:库存数量、单价、金额统一精度。
  • 单据状态用字典:入库类型、单据状态用字典维护,前端渲染彩色标签。

七、技术亮点总结

设计要点实现方式价值
SKU 级库存(sku_id, warehouse_id)维度精细管多规格多仓
完成才写库存complete动作触发changeInventory草稿可纠错、变更点集中
状态乐观校验updateByIdAndStatus(草稿→完成)防并发重复完成
悲观锁变更SELECT ... FOR UPDATE批量锁定多明细整单一致防超卖
内存批量计算锁后内存校验充足再批量更新一次锁、一致更新
库存行补建创建 + 唯一索引冲突回查高并发补行安全
盘点快照校验账面未变才允许调整杜绝边盘边变覆盖
库存流水留痕wms_inventory_historybefore/after每次变动可追溯
移库总量守恒一出一进同事务仓间转移不丢量

八、快速体验

在线演示:http://ruoyioffice.com/web/(账号admin/ 密码admin123

操作路径:WMS 仓储 → 主数据(仓库 / 商品 / SKU)→ 入库单 / 出库单 / 移库单 / 盘点单 → 库存查询 / 库存流水

推荐体验流程

  1. 建主数据:维护仓库、商品及其 SKU(规格、条码、成本价)。
  2. 入库:新建入库单,录 SKU 和数量,先存草稿再点"完成",回到库存查询观察数量增加。
  3. 出库:新建出库单完成,观察库存减少;对库存不足的 SKU 出大额,观察"库存不足"拦截。
  4. 移库:新建移库单从 A 仓移到 B 仓,观察两仓库存一减一增、总量不变。
  5. 盘点:新建盘点单录入实盘数量并完成,观察盈亏调整与流水;尝试模拟"盘点期间库存变化",观察快照校验拦截。
  6. 查流水:进入库存流水,逐笔核对每次变动的方向、前后数量与关联单据。

源码仓库

平台地址
GitHubhttps://github.com/yuqing2026/ruoyi-office
GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office
Giteehttps://gitee.com/yqzy1688/ruoyi-office

结语

仓储管理的本质,是在更细的颗粒度(SKU×仓库)和更猛的并发下,把库存管得既准又稳。RuoYi Office 的答案是:用"商品-SKU"两层主数据精细到规格,用四类单据 + "完成才写库存"的生命周期让作业可纠错,用悲观锁批量锁定 + 内存整体计算保证多明细整单一致,用盘点快照校验杜绝边盘边变,用库存流水把每一次变动留痕。

值得一提的是,RuoYi Office 里 WMS 与 ERP 进销存采用了两种不同的库存并发策略——ERP 用"条件 UPDATE(乐观)“,WMS 用"SELECT FOR UPDATE(悲观)”。这两种方案的取舍,我们在本周的《库存扣减的并发难题》里做了系统对比。

如果你正在设计 WMS 或库存系统,欢迎参考源码实现,也欢迎在评论区聊聊:你们仓库的盘点,是怎么防止"边盘边变"对不上账的?


常见问题(FAQ)

RuoYi Office 的 WMS 是开源免费的吗?

是。仓储管理模块(yudao-module-wms)基于 RuoYi-Vue-Pro / Yudao 架构,后端 Spring Boot 3.5 + 前端 Vue3,开源可商用、无 license 限制,本地约 10 分钟即可启动体验。

WMS 和进销存的库存有什么区别?

进销存(ERP)库存维度是"产品 × 仓库",偏经营视角;WMS 库存维度是"SKU × 仓库",精细到规格/批次,偏仓储作业视角,并发更严苛。RuoYi Office 同时提供这两个模块,可按需选用。

WMS 怎么防止并发出库超卖?

WMS 用悲观锁:库存变更时先SELECT ... FOR UPDATE批量锁住相关库存行,再在内存里整体计算、校验充足、批量更新,保证多明细整单一致,从根上防超卖。详见本周文章《库存扣减的并发难题》。

单据建好就会扣库存吗?

不会。四类单据都走"草稿→完成→作废",草稿态可反复编辑、删除,只有点"完成"才真正写库存,作废只能在草稿态进行。库存变更点高度集中,便于审计。

盘点时别人改了库存会不会冲突?

会被拦截。盘点完成时锁定库存行并校验"账面是否仍等于盘点时的快照",不一致直接报"盘点期间库存已变化",避免把别人的变动覆盖掉。


💡想要体验 RuoYi Office 的强大功能?

🌐在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)

📦源码仓库:GitHub | GitCode | Gitee

💬技术咨询:添加微信17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!

http://www.jsqmd.com/news/1097492/

相关文章:

  • STM32新手避坑指南:用江科大模板+MPU6050 DMP库,5分钟搞定欧拉角读取
  • 零风险Cookie导出神器:Get cookies.txt LOCALLY完全本地化方案深度解析 [特殊字符]
  • 3分钟搞定:Postman便携版,让API测试摆脱安装束缚
  • 踩遍布局所有弯路,我整理这份Flex全套实战笔记
  • JMeter+Ant+Jenkins自动化测试流水线搭建与实战指南
  • 每周AI新动态:GLM 5.2、gpt-oss与Qwen-AgentWorld发布
  • 红外热成像仪详细功能解析,测温成像测距一机搞定
  • 如何快速上手openYuanrong agent runtime?5分钟入门教程
  • 公文管理别再用 Word 传来传去:套红模板、发文自动拆收文、归档台账的闭环设计
  • BK 2713 功率放大器介绍:为什么它适合驱动水声换能器和容性负载?
  • 现代工业传动系统中盖茨皮带的适配方案
  • 如何在Photoshop中直接使用AI绘图?SD-PPP插件终极指南
  • SQL注入攻击原理与防范:从数据混淆到参数化查询实战
  • 深入解析Grafana k6性能测试中的Stage负载模型设计与实战应用
  • OpenCV 核心算法大全、解决问题 + 落地应用完整详解
  • Codex++ 安装与 Codex 环境配置指南
  • 免费解锁iPhone 6s-X激活锁:applera1n完整指南与安全操作
  • 10个openeuler/ssh-utils使用技巧,让远程运维更高效
  • DCMTK医疗影像处理开源工具包:5大核心模块深度解析与实战应用
  • sysmaster特权容器部署教程:突破传统容器限制的终极方案
  • 3个技巧快速掌握KMS_VL_ALL_AIO:Windows和Office智能激活完全指南
  • CVE-2025-31161漏洞解析与Python验证工具开发实战
  • ShaderGlass:如何在Windows桌面上为任何应用添加1200+实时GPU特效?
  • 不安装AI Agent也能使用SKILL的一个案例
  • 梦笔记20260629
  • 2026 海外移动广告归因工具横向对比|适配日本・北美・南美专属场景
  • 华为USG5500防火墙新手避坑指南:从Trust、DMZ到Untrust,一次搞懂安全域与策略配置
  • libXSched核心技术揭秘:10个关键API接口详解
  • OpenBoardView:解决专业PCB分析的5大痛点与完整工作流指南
  • 文件上传漏洞攻防解析:从Webshell上传到服务器沦陷的实战指南