功能模块设计
3.1 模块总览
WMS 功能模块 ├── 基础数据管理 → wms-core ├── 入库管理 → wms-inbound ├── 出库管理 → wms-outbound ├── 库存管理 → wms-inventory ├── 盘点管理 → wms-count └── 报表看板 → wms-report
3.2 入库管理流程
3.2.1 整体状态机
![]()
ASN 创建 → 已通知 → 收货中 → 收货完成 → 上架中 → 上架完成 ↓ ↓ 已取消 异常
3.2.2 详细流程
![]()
Step 1: ASN 创建 │ 上游(采购系统/ERP)推送 ASN 数据 │ 包含:ASN 单号、供应商、预计到货时间、商品明细 │ 状态:已通知 │ Step 2: 收货 │ 仓库管理员扫描 ASN 单号 │ 逐 SKU 扫描条码 / 输入实收数量 │ 系统校验:实收 vs 预计 │ ├── 一致 → 收货完成 │ ├── 少于 → 标记差异,部分收货 │ └── 多于 → 标记异常(无单收货审批) │ Step 3: 上架 │ 系统根据上架策略推荐库位 │ 管理员扫描库位码确认上架 │ 库存数量 +N,库位状态更新 │ 状态:上架完成
3.2.3 上架策略伪代码
public List<Location> recommendLocations(Sku sku, int qty) { // 1. 查询该 SKU 已有库存的库位 List<Location> existingLocations = inventoryMapper .findLocationsBySkuId(sku.getId()); if (!existingLocations.isEmpty()) { return existingLocations.stream() .filter(loc -> loc.getAvailableCapacity() >= qty) .sorted(Comparator.comparingInt(Location::getAvailableCapacity)) .limit(3) .collect(Collectors.toList()); } // 2. 按 ABC 分类 + 容量筛选空库位 String abcClass = sku.getAbcClass(); // A / B / C return locationMapper.findAvailableByAbcClass(abcClass, qty, 3); }
3.3 出库管理流程
3.3.1 整体状态机
![]()
订单接收 → 波次生成 → 分配库存 → 拣货中 → 拣货完成 → 复核中 → 复核完成 → 已发货 ↓ ↓ ↓ ↓ 已取消 缺货异常 缺货标记 差异退回
3.3.2 详细流程
![]()
Step 1: 订单接收 │ OMS 推送出库订单 │ 含:订单号、客户、商品明细、期望发货时间、承运商 │ Step 2: 波次生成 │ 根据波次策略将多个订单合并为波次 │ 波次 = 拣货任务的基本单位 │ Step 3: 库存分配 │ 按 FIFO / 批次优先规则分配库存 │ 锁定库存(预占,防止被其他订单抢走) │ 生成拣货任务清单 │ Step 4: 拣货 │ 拣货员 PDA 接收任务 │ 系统按库位排序优化拣货路径 │ 扫描库位 → 扫描商品 → 确认数量 │ Step 5: 复核 │ 扫描订单号 → 逐件扫描商品条码 │ 重量校验:实际重量 vs 理论重量 │ 差异处理 │ Step 6: 打包发货 │ 打印面单、装箱清单 │ 称重、测量体积 │ 交接承运商 → 扣减库存
3.3.3 拣货路径优化
![]()
仓库库位布局示例(数字为库位编号): ┌──────────────────────────────┐ │ 01 02 03 04 │ 21 22 23 │ ← A 区(快周转) │ 05 06 07 08 │ 24 25 26 │ │─────────────────────────────│ │ 09 10 11 12 │ 27 28 29 │ ← B 区(中周转) │ 13 14 15 16 │ 30 31 32 │ │─────────────────────────────│ │ 17 18 19 20 │ 33 34 35 │ ← C 区(慢周转) └──────────────────────────────┘ ↑ 复核区 / 发货区 优化后路径:从靠近发货区的库位开始, 由近到远排列拣货任务 示例:需要拣 03, 18, 25 三个库位 原始顺序:03 → 18 → 25 (S型路径,效率低) 优化顺序:25 → 03 → 18 (由近及远)
3.3.4 播种式 vs 摘果式拣货
| 方式 | 描述 | 适用场景 |
|---|
| 摘果式 | 一个拣货员完成一个订单的所有商品 | 订单少、单品多 |
| 播种式 | 先汇总多个订单的总需求量,统一拣货后再按订单分播 | 订单多、SKU 重合度高 |
3.4 库存管理
3.4.1 库存维度模型
库存 = f(仓库, SKU, 库位, 批次, 状态, 货主) 查询示例: "查询仓库1中 SKU-1001 在所有库位的可用库存" → SUM(quantity) WHERE warehouse_id=1 AND sku_id=1001 AND status='AVAILABLE'
3.4.2 库存状态
| 状态 | 含义 | 是否可分配 |
|---|
| AVAILABLE | 可用 | 是 |
| LOCKED | 锁定(已被订单预占) | 否 |
| FROZEN | 冻结(质检中) | 否 |
| DAMAGED | 残次 | 否 |
| EXPIRED | 过期 | 否 |
3.4.3 库存扣减时序
![]()
出库订单分配库存的时序(防止并发超扣): ┌────────────────┐ │ 开启事务 (BEGIN) │ └───────┬────────┘ ▼ ┌───────────────────────────────────┐ │ SELECT * FROM inventory │ │ WHERE sku_id = ? │ │ AND status = 'AVAILABLE' │ │ AND quantity > 0 │ │ ORDER BY production_date ASC │ ← FIFO: 先过期先出 │ FOR UPDATE │ ← 行锁 └───────┬───────────────────────────┘ ▼ ┌────────────────────────┐ │ 计算扣减量,逐行扣减 │ │ 若 quantity >= need: │ │ 扣 need,完成 │ │ 若 quantity < need: │ │ 全扣,继续下一行 │ └───────┬────────────────┘ ▼ ┌────────────────────┐ │ 更新库存行 │ │ 插入库存变更流水 │ └───────┬────────────┘ ▼ ┌──────────────────┐ │ 提交事务 (COMMIT) │ └──────────────────┘
3.4.4 安全库存预警
-- 低库存预警:当前可用库存 < 安全库存阈值 SELECT s.sku_code, s.sku_name, s.safety_stock, COALESCE(SUM(i.quantity), 0) AS current_stock FROM sku s LEFT JOIN inventory i ON s.id = i.sku_id AND i.status = 'AVAILABLE' GROUP BY s.id HAVING current_stock < s.safety_stock;
3.5 盘点管理
3.5.1 盘点类型
| 类型 | 说明 | 频率 |
|---|
| 动态盘点 | 每日出入库结束后对变动库位复盘 | 每日 |
| 循环盘点 | 按库区轮转,每天盘一个区域 | 按计划 |
| 全面盘点 | 全仓库所有库位盘点 | 月度/季度 |
| 随机抽盘 | 随机抽取部分 SKU 或库位盘点 | 按需 |
3.5.2 盘点流程
![]()
Step 1: 创建盘点计划 │ 选择盘点类型、范围(仓库/库区/SKU) │ 选择盘点方式:明盘(显示系统库存)/ 盲盘(不显示) │ Step 2: 生成盘点任务 │ 按库位/库区拆分任务 │ 分配盘点人员 │ 库位冻结(可选,盘点期间禁止出入库操作) │ Step 3: 执行盘点 │ 盘点员扫描库位码 │ 逐 SKU 扫描条码,录入实际数量 │ 系统对比账面数量 │ Step 4: 差异处理 │ 无差异 → 盘点完成 │ 有差异 → 复盘(二次盘点) │ 复盘仍有差异 → 生成差异报告 → 主管审核 │ Step 5: 库存调整 │ 审核通过后调整库存 │ 生成盘盈/盘亏单据 │ 记录操作日志
3.5.3 盘点锁定策略
![]()
盘点锁库策略(针对部分盘点): ┌──────────────────────────────────────┐ │ 临时表:count_lock │ │ ┌──────────┬──────────┬────────────┐ │ │ │ location │ lock_type │ lock_time │ │ │ ├──────────┼──────────┼────────────┤ │ │ │ A-01-01 │ COUNTING │ 2026-07-30 │ │ │ │ A-01-02 │ COUNTING │ 2026-07-30 │ │ │ └──────────┴──────────┴────────────┘ │ │ │ │ 出入库时检查:若库位在 count_lock 中 │ │ 且 lock_type=COUNTING,则拒绝操作 │ └──────────────────────────────────────┘
3.6 库位管理
3.6.1 库位编码规则
![]()
推荐编码格式:仓库-库区-排-列-层 示例:WH01-A-03-05-02 WH01 仓库编号 A 库区(A区快周转 / B区中周转 / C区慢周转) 03 第 3 排货架 05 第 5 列 02 第 2 层
3.6.2 库位属性
| 属性 | 说明 |
|---|
| 库位编码 | 唯一标识 |
| 库位类型 | 存储位 / 拣货位 / 暂存位 / 退货位 / 残次位 |
| 容量 | 最大存放数量 / 最大体积 / 最大重量 |
| 状态 | 空闲 / 占用 / 冻结 / 禁用 |
| ABC 分类 | 对应的周转率分类 |
3.7 事件驱动解耦
![]()
业务模块之间通过 Spring Event / RabbitMQ 解耦:
入库完成事件 ├── → 库存模块:更新库存 ├── → 报表模块:更新入库统计 └── → 通知模块:发送入库通知 出库完成事件 ├── → 库存模块:扣减库存 ├── → 报表模块:更新出库统计 └── → OMS:回传发货状态 盘点调整事件 ├── → 库存模块:调整库存 └── → 审计模块:记录调整日志
// 事件定义 public class InboundCompletedEvent extends ApplicationEvent { private final Long asnId; private final List<InboundDetail> details; // ... } // 事件发布 @Transactional public void completeInbound(Long asnId) { // 业务处理... eventPublisher.publishEvent(new InboundCompletedEvent(this, asnId, details)); } // 事件监听 @Component public class InventoryEventListener { @EventListener @Transactional public void handleInboundCompleted(InboundCompletedEvent event) { // 更新库存 } }