SpringBoot+Vue3 仓储管理系统(WMS)设计:商品·SKU·出入库·移库·盘点全流程拆解
发布时间:2026/6/30 17:59:53
分类:文化教育
浏览:1234
设计:商品·SKU·出入库·移库·盘点全流程拆解)
SpringBootVue3 仓储管理系统WMS设计商品·SKU·出入库·移库·盘点全流程拆解演示地址http://ruoyioffice.com | 源码1·GitHubruoyi-office | 源码2·GitCoderuoyi-office | 源码3·Giteeruoyi-office | 微信17156169080备注「RuoYi Office」进销存里的库存只回答还剩多少而仓储管理系统WMSWarehouse 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是聚合概念真正有库存、能出入库的是 SKUwms_item_sku——它带条码、规格、尺寸重量、成本价/销售价。这种商品-SKU 两层的主数据结构是 WMS 能精细管理多规格商品的基础。1.2 四类单据入库、出库、移库、盘点仓库的全部作业都收敛到四类单据每类对应一种库存变更模式入库单 wms_receipt_order库存 采购到货、退货入库、其它入库 出库单 wms_shipment_order库存 −销售出库、领料出库、其它出库 移库单 wms_movement_orderA 仓 − 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把状态从草稿乐观更新为完成防并发重复完成再调用库存服务写入库存。入库在库存变更模型里用正数数量OverrideTransactional(rollbackForException.class)publicvoidcompleteReceiptOrder(Longid){// 1.1 校验存在且为草稿1.2 校验明细存在WmsReceiptOrderDOordervalidateReceiptOrderPrepare(id);ListWmsReceiptOrderDetailDOdetailsreceiptOrderDetailService.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,ListWmsReceiptOrderDetailDOdetails){ListWmsInventoryChangeReqDTO.ItemitemsconvertList(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批量锁住再在内存里逐条计算、校验充足性全部通过后批量更新。一次加锁、一致更新避免多单并发交错扣减privateMapItem,TuplechangeInventoryList(ListWmsInventoryChangeReqDTO.Itemitems){// 1.1 创建或定位本次涉及的库存行ListWmsInventoryDOinventoriesgetOrCreateInventoryList(items);// 1.2 悲观锁按 ID 批量 SELECT ... FOR UPDATE锁住这些库存行inventoriesinventoryMapper.selectListByIdsForUpdate(convertSet(inventories,WmsInventoryDO::getId));// 2.1 在内存里逐条计算变更后数量并校验库存充足MapItem,TupleresultMapnewIdentityHashMap(items.size());for(Itemitem:items){WmsInventoryDOinventoryfindInventory(inventories,item);BigDecimalbeforeinventory.getQuantity();BigDecimalafterbefore.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后回查已建行privateListWmsInventoryDOcreateMissingInventoryList(ListWmsInventoryDOmissing){ListWmsInventoryDOcreatednewArrayList(missing.size());for(WmsInventoryDOm:missing){WmsInventoryDOinventorynewWmsInventoryDO().setSkuId(m.getSkuId()).setWarehouseId(m.getWarehouseId()).setQuantity(BigDecimal.ZERO);try{inventoryMapper.insert(inventory);}catch(DuplicateKeyExceptionex){// 并发下别人已创建按唯一键回查已有库存行inventoryinventoryMapper.selectBySkuIdAndWarehouseId(m.getSkuId(),m.getWarehouseId());if(inventorynull){throwex;}}created.add(inventory);}returncreated;}4.4 盘点防覆盖账面快照校验盘点最怕边盘边变——录入实盘时别人又出入了库。系统在盘点完成时用SELECT ... FOR UPDATE锁住库存行并校验当前账面数量是否仍等于盘点时记录的快照不一致就报错避免把别人的变动冲掉privateWmsInventoryDOgetOrCreateCheckInventory(WmsInventoryCheckReqDTO.Itemitem){if(item.getInventoryId()null){returncreateCheckInventory(item);}// 锁定库存行WmsInventoryDOinventoryinventoryMapper.selectByIdForUpdate(item.getInventoryId());// 校验库存行还在、SKU/仓库一致、且账面数量未被改动与盘点快照一致if(inventorynull||!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长 / 宽 / 高cmgross_weight/net_weightdecimal毛重 / 净重kgcost_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→ 入库单 / 出库单 / 移库单 / 盘点单 → 库存查询 / 库存流水推荐体验流程建主数据维护仓库、商品及其 SKU规格、条码、成本价。入库新建入库单录 SKU 和数量先存草稿再点完成回到库存查询观察数量增加。出库新建出库单完成观察库存减少对库存不足的 SKU 出大额观察库存不足拦截。移库新建移库单从 A 仓移到 B 仓观察两仓库存一减一增、总量不变。盘点新建盘点单录入实盘数量并完成观察盈亏调整与流水尝试模拟盘点期间库存变化观察快照校验拦截。查流水进入库存流水逐笔核对每次变动的方向、前后数量与关联单据。源码仓库平台地址GitHubhttps://github.com/yuqing2026/ruoyi-officeGitCodehttps://gitcode.com/zhouzhongyan/ruoyi-officeGiteehttps://gitee.com/yqzy1688/ruoyi-office结语仓储管理的本质是在更细的颗粒度SKU×仓库和更猛的并发下把库存管得既准又稳。RuoYi Office 的答案是用商品-SKU两层主数据精细到规格用四类单据 完成才写库存的生命周期让作业可纠错用悲观锁批量锁定 内存整体计算保证多明细整单一致用盘点快照校验杜绝边盘边变用库存流水把每一次变动留痕。值得一提的是RuoYi Office 里 WMS 与 ERP 进销存采用了两种不同的库存并发策略——ERP 用条件 UPDATE乐观“WMS 用SELECT FOR UPDATE悲观”。这两种方案的取舍我们在本周的《库存扣减的并发难题》里做了系统对比。如果你正在设计 WMS 或库存系统欢迎参考源码实现也欢迎在评论区聊聊你们仓库的盘点是怎么防止边盘边变对不上账的常见问题FAQRuoYi 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 支持一下