44 KiB
WaveRuleMatchService 流程文档
对应实现:
C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy本文档描述该服务的当前代码流程,按调用入口到出口的完整链路展开,包括主流程、分流流程、补缺口流程、回滚流程和详细的规则匹配机制。
需求标准来源:
C:\work\gitlab\shopee\ttx-project-doc\集波\SHOPEE_010_出库集波V1.2.docx,当前同步到 V1.4 口径:wave_rule.waveType与shipment_header.orderStructure包含关系、同一groupKey集波约束、字段匹配字典规则、Max SKU Pieces Per Order/Mix Mode Max SKU Pieces Filter标准要求、multi_attr_list全包含匹配要求。
目录
- 服务概述
- 入口与总流程
- 前置校验与并发控制
- 一波一单分流流程
- 推拣货任务当前口径
- 标准集波主流程
- 格口分配策略
- Shopee 波次绑定与 ESS 校验
- 内部波次创建与运行
- 回滚机制
- 首次集波内补缺口流程
- Flow Pick 换箱换波流程
- 字段匹配规则(WAVE_RULE_FIELD_MATCHING)
- 方法调用关系图
- 标准需求与当前实现对照
1. 服务概述
WaveRuleMatchService 是 Shopee 出库集波的核心服务,职责:
- 接收「工作站 + 出库类型」请求
- 查询启用的波次规则(
wave_rule),按优先级排序 - 查找工作站下可用的分播墙格口(
wcs_sorting_wall_cell) - 从订单池(
shipment_header)中匹配符合条件的出库单 - 完成 Shopee 波次号绑定、ESS 合波校验、内部波次创建与运行
- 首次集波成功返回
ResponseMessageFactory.success(),不返回「格口 + 命中规则 + 波次」明细
当前匹单核心口径:
| 维度 | 当前规则 |
|---|---|
| 规则优先级 | wave_rule.wavePriority 数字越大越优先;高优先级规则订单数不足 maxShipments 时仍优先消费当前可匹配单据 |
| 抓单数量 | 只使用 wave_rule.maxShipments;wave_master.maxShipments 不参与当前逻辑 |
| 订单结构 | 出库单头 shipment_header.orderStructure 记录数字编码:1=SSSQ、2=SSAQ、3=MSAQ |
| 规则类型匹配 | wave_rule.waveType 通过 resolveMatchedOrderStructures 转换为可抓的 orderStructure 集合 |
| 字段匹配 | 通过 WAVE_RULE_FIELD_MATCHING 字典追加 wave_rule 字段与 shipment_header 字段的 IN/EQ/LE 条件 |
| 同波约束 | 首次抓单会先按最早候选单解析 groupKey;补缺口时按当前 shopee_wave.groupKey 过滤,保证同一 Shopee 波次只集相同 groupKey |
| V1.4 新增 SKU 件数过滤 | Max SKU Pieces Per Order、Mix Mode Max SKU Pieces Filter 是 docx 标准要求;当前 WaveRule 域和匹单 SQL 未见对应落地字段,列为待实现 |
三种分流入口:
| 分支 | 条件 | 处理方式 |
|---|---|---|
| 一波一单分流 | pickType=1、XSCK+ticketType=1、ticketType=5、ticketType=6 |
一单一个内部波次;RT 不使用 Shopee 波次号 |
| 已绑波次格口补缺口 | 空闲格口已绑定 shopeeWaveCode 且 currentWaveRule 匹配当前规则 |
按 low_threshold/maxShipments 计算缺口后补单 |
| 空格口首次分配 | 其他普通批量候选单 | 按规则优先级逐规则匹配,先抢占格口,再绑定 Shopee 波次,再创建并运行内部波次 |
PDF 补充:WMS 出库 vendor 交互范围
来源:WMS出库流程.pdf。该 PDF 主要是 vendor 视角交互时序图,可抽取文字较少;以下只同步与集波/拣货任务边界相关的口径。名词口径按当前项目统一:vendor 是本项目 / WES-WMS 实现侧,Shopee 是需要调用或接收交互的外部接口方。
| PDF 流程 | 创建源 | PDF 说明 | 与当前 WaveRuleMatchService 的关系 |
|---|---|---|---|
销售出库自动化 SubPickingTask |
Shopee | RunWave 生成的拣货任务占到自动化区时,拆分 normal 拣货任务和自动化拣货任务;自动化部分下发到 vendor,本项目侧执行 WMS 创建的任务,不能混合拣货 |
与当前内部波次创建、运行和 Shopee picking task 调用方向相关;自动化任务下发属于当前项目 vendor 侧链路,本文不标记为本服务已完全落地 |
销售出库自动化 Order |
vendor | 订单预命中自动化区时,由本项目 vendor 侧接收/组织任务,并按 PDF 口径合并多个订单后创建并执行混合拣货任务 | 属当前项目 vendor 侧创建任务链路,不是当前服务的首次集波直接创建内部波次逻辑 |
| RTS 需求池模式 | vendor | RTS 需求到自动化区后,由本项目 vendor 侧按需求创建并执行拣货任务 | 属当前项目 vendor 侧需求池链路;当前服务只按 ticketType=3 参与集波匹单/接口类型分流 |
| MTO 需求池模式 | vendor | MTO 需求到自动化区后,由本项目 vendor 侧按需求创建并执行拣货任务 | 属当前项目 vendor 侧需求池链路;当前服务只按 ticketType=4/5 参与集波匹单/接口类型分流 |
| MTO 非需求池模式 | Shopee | 库存占用到自动化库位后拆分 PickingTask,任务下发到 vendor,本项目侧不能混合拣货 |
与 Shopee 外部创建任务方向相关;任务拆分和下发细节不在当前服务内闭环 |
PDF 名词口径:波次ID 约等于 WMS 拣货任务;PickingTask 通常对应多个业务订单,可由多个拣货员执行,当前项目 vendor 侧不需要感知多拣货员层;SubPickingTask 用于任务过大或跨区拆分,本次交互中一个销售出库波次 ID 对应一个 SubPickingTask。
2. 入口与总流程
外部入口
POST wms/automation/waveRuleMatch/findBestMatchedRule
→ WaveRuleMatchController.findBestMatchedRule(map)
→ WaveRuleMatchService.findBestMatchedRuleByWorkStationAndOutboundType(warehouseCode, workStation, shipmentType)
调用链总图
findBestMatchedRuleByWorkStationAndOutboundType(String, String, String)
│
├─ 参数校验(warehouseCode / workStation / shipmentType 非空)
├─ findEnabledWorkStation(warehouseCode, workStation)
│ └─ 支持工作站编码 或 数字 ID 查询
│
└─ findBestMatchedRuleByWorkStationAndOutboundType(Long, String) ← 统一入口
│
├─ [1] 工作站校验:存在 / 启用 ENABLE / 仓库匹配
├─ [2] hasDifferentActiveShipmentType
│ → 已占用 USED 格口关联 Shopee 波次反查当前工作站作业出库类型
│ → 有其它出库类型时记录系统处理日志并返回 MSG_WRM_0014
│
├─ [3] buildRuleMatchLockKey → Redis 锁(仓库+工作站+出库类型)
│ 失败 → MSG_WRM_0012 "工作站正在集波中"
│
├─ [4] findEnabledRules(warehouseCode)
│ → wave_rule WHERE warehouseCode=? AND status=ENABLE ORDER BY wavePriority DESC
│ 无规则 → MSG_WRM_0007
│
├─ [5] assignSingleShipmentWaves(先尝试一波一单候选)
│ → pickType=1 / ticketType=1,5,6
│
├─ [6] fillShortageCells(已绑定 Shopee 波次格口补缺口)
│ → 空闲 + shopeeWaveCode 有值 + currentWaveRule 匹配
│
├─ [7] assignEmptyCells(空格口首次分配)
│ → 普通批量候选单进入空格口分配
│
└─ [8] 成功返回 ResponseMessageFactory.success()
3. 前置校验与并发控制
3.1 工作站校验
findEnabledWorkStation(warehouseCode, workStation)
- 入参
workStation兼容 编码(String)和 数字 ID(自动识别) - 校验:
status=ENABLE、warehouseCode匹配 - 失败返回对应
MSG_WRM_0004/0005/0006
3.2 Redis 锁
buildRuleMatchLockKey → "wave_rule_match:{warehouseCode}:{workstationId}:{shipmentType}"
RedissonLockService.getAndTryLock(lockKey)
- 锁粒度:仓库 + 工作站 + 出库类型
- 获取失败时不阻塞等待,直接返回
MSG_WRM_0012(前端可提示用户稍后重试) - 在 finally 块中
WmsRedissionLockService.unlock(lock)
3.3 同工作站出库类型隔离
hasDifferentActiveShipmentType(warehouseCode, workstationId, shipmentType)
→ findActiveWorkstationShipmentTypes(warehouseCode, workstationId)
- 查询当前工作站已占用的
USED格口。 - 通过格口
shopeeWaveCode关联shipment_header.shopeeWave,获取当前工作站正在作业的shipmentType。 - 若存在非本次请求的出库类型,入口直接拒绝本次集波。
- 拒绝时记录系统处理日志,并返回
MSG_WRM_0014。 - 该校验位于 Redis 锁之前,避免无效请求占用集波锁。
3.4 工作站出库类型权限
标准要求:登录工作站时按用户权限控制可选择的出库类型,配置后工作站只能下拉选择该用户有权限的单据类型。
当前实现/待实现:WaveRuleMatchService 已做同一工作站不同出库类型隔离;用户与出库类型的权限绑定仍是 TODO,不能在本文档中描述为已落地。
4. 一波一单分流流程
触发条件
pickType=1、XSCK+ticketType=1、ticketType=5、ticketType=6
方法
assignSingleShipmentWaves(warehouseCode, shipmentType, workstationId, rules)
流程图
for each WaveRule:
findSingleShipmentWaveShipments(warehouseCode, shipmentType, rule)
→ buildAvailableShipmentSql
→ appendSingleWaveFilter
→ appendWaveTypeOrderStructureFilter
→ appendRuleFieldMatching
→ LIMIT 1(只取 1 单)
if shipment found:
createInternalWave(warehouseCode, rule) → 创建内部波次
addShipmentsToInternalWave([shipmentId], waveId)
→ ShipmentHeaderService.addMultipleToWave + WaveService.run
return success
无任何规则命中 → MSG_WRM_0010
特点
- RT 不使用 Shopee 波次号(
shopeeWaveCode返回空字符串"") - 当前一波一单分流不走 ESS 校验
- 不走格口分配(不绑定格口)
- 一单一个内部波次
- 同样受 waveType/orderStructure 包含关系约束,避免一波一单候选绕过规则订单结构过滤
5. 推拣货任务当前口径
触发条件
pickType=2(出库单由上游推入时设置,同时预写入 shopeeWave)当前作为待恢复的独立分流口径。
方法
当前代码中未保留 assignPickingTaskSingleWaves 和 findPickingTaskShipments 方法。
流程图
当前已落地:
appendBatchWaveFilter 排除 pickType=2,避免推拣货任务进入普通批量集波。
待恢复:
上游推单写入 pickType=2 + shopeeWave 后,
WES 独立分流入口按一单一波一槽口处理。
特点
| 特性 | 说明 |
|---|---|
| 波次号来源 | 上游预写入 shipment_header.shopeeWave,WES 不走取号逻辑 |
| 当前状态 | 普通批量集波已过滤 pickType=2 |
| 待实现 | 独立分流入口、一单一波一槽口、内部波次运行 |
| 补单 | 后续仍按“不走补单逻辑”处理 |
| 取号 | 标准要求推拣货任务使用上游同步的 Shopee 波次号,WES 不走取号逻辑 |
6. 标准集波主流程
6.1 主循环
for (WaveRule rule : rules) {
List<WcsSortingWallCell> cells = findCells(workstationId)
// 三层级分组
List<List<WcsSortingWallCell>> cellGroups = [
sameCurRuleCells, // currentWaveRule == rule.code
ruleCells, // currentWaveRule 为空 && waveRule 匹配
emptyCells // 两者均为空
]
for (group : cellGroups) {
assignCellNumbers(warehouseCode, shipmentType, workstationId, rule, group)
// 命中即返回,不继续尝试下一组格口
}
}
6.2 assignCellNumbers 核心流程
assignCellNumbers(warehouseCode, shipmentType, workstationId, rule, cells):
│
├─ 1. findSingleShipmentWaveShipments → 查一波一单候选
│ → pickType=1 / XSCK+ticketType=1 / ticketType=5 / RT
│ 有 → singleShipmentWave=true, 取 1 单
│ 无 → 继续
│
├─ 2. findAvailableShipments → 查批量集波候选
│ → 排除 pickType=1,2 + 排除一波一单 ticketType
│ → waveType/orderStructure 匹配 + 规则字段匹配 + kickOutWave 过滤
│ → 首次抓单按最早候选单确定 groupKey,并只抓相同 groupKey
│ → LIMIT = maxShipments(默认 30)
│
├─ 3. 仍无候选 → MSG_WRM_0010
│
├─ 4. logPriorityRuleConsumption — 记录优先级消费日志
│
├─ 5. createInternalWave(warehouseCode, rule) → 创建内部波次
│ 失败 → MSG_WRM_0009
│
└─ 6. for each cell in cells:
│
├─ 取出 cell.shopeeWaveCode
│
├─ [一波一单] singleShipmentWave=true && cell 已有波次号 → continue(不补入已用格口)
│
├─ [已有波次] cell 已有波次号 → skipSingleRpln 检查
│ → 单品单件 且 未低于 low_threshold → continue
│
├─ [无波次号] → acquireUnusedShopeeWave → 取新号
│ 失败 → rollbackInternalWaveIfEmpty + MSG_WRM_0009
│
├─ [补充新单] 已有波次号 + 非单品单件/低于阈值
│ → findAvailableShipments(..., shopeeWaveCode) 过滤 kickOutWave
│
├─ assignToCell:
│ ├─ pickCellShipmentIds → 按格口缺口截断(单品单件=maxShipments, 其他=差额)
│ ├─ ensureCellReadyForWave → 先抢占格口 USED
│ ├─ bindShipmentShopeeWave → Shopee 波次绑定接口;成功整批写 shopeeWave,失败整批写 kickOutWave
│ ├─ addShipmentsToInternalWave → 加入波次 + 运行
│ └─ 成功后消费当前候选池
│
└─ 一个格口消费一批后 break(不再占用其他格口)
6.3 waveType 与 orderStructure 匹配
标准文档 V1.4 要求:匹单时除 WAVE_RULE_FIELD_MATCHING 字段匹配外,还要先按 wave_rule.waveType 与 shipment_header.orderStructure 的包含关系过滤候选出库单。
当前实现方法:
appendWaveTypeOrderStructureFilter(sql, params, rule)
→ resolveMatchedOrderStructures(rule)
→ SQL: and orderStructure in (:matchedOrderStructures)
匹配关系如下:
wave_rule.waveType |
规则含义 | 可抓 shipment_header.orderStructure |
出库单结构含义 |
|---|---|---|---|
1 |
SSSQ | 1 |
SSSQ,单 SKU 单件 |
5 |
MSSQ | 1 |
SSSQ,单 SKU 单件 |
4 |
SSAQ | 1, 2 |
SSSQ / SSAQ |
2 |
SSSQ(Same SKU same Qty) | 3 |
MSAQ |
3 |
MSAQ | 1, 2, 3 |
SSSQ / SSAQ / MSAQ |
说明:
shipment_header.orderStructure由ShipmentHeaderService#calcOrderStructure写入,当前编码为1=SSSQ、2=SSAQ、3=MSAQ。wave_rule.waveType是规则侧单选类型,不直接等值匹配订单结构,而是通过上表转换成可包含的订单结构集合。appendWaveTypeOrderStructureFilter同时用于普通批量候选和一波一单候选,保证入口一致。- 未识别的
waveType不追加订单结构条件;此场景通常表示规则数据异常,应通过规则主数据维护修正。 - docx 中同时出现
SSSQ与SSSQ(Same SKU same Qty)两个名称,其中编码2映射到MSAQ;命名含义按 docx 待确认,当前实现按编码表执行。
6.4 订单池 SQL 基础条件
SELECT id FROM shipment_header
WHERE warehouseCode = :warehouseCode
AND shipmentType = :shipmentType
AND leadingSts = 100 -- 订单池状态
AND trailingSts = 100 -- 订单池状态
AND processType = 'NORMAL' -- 正常处理流程
AND ifnull(waveId, 0) = 0 -- 未加入任何波次
AND (lockCode IS NULL OR trim(lockCode) = '') -- 未被锁定
AND (cancelTime IS NULL OR cancelTime = '') -- 未取消
AND (holdTime IS NULL OR holdTime = '') -- 未挂起
6.5 groupKey 集波约束
标准文档要求:匹单时除规则字段匹配外,还需要根据 shipment_header.groupKey 特征值一致才能集在一起。
当前实现:
| 场景 | 方法 | 规则 |
|---|---|---|
| 首次空格口集波 | findAvailableShipments(..., groupKey='') |
先用已拼好的候选 SQL 查最早候选单的 groupKey,再追加 appendGroupKeyFilter 限制同一特征值 |
| 已绑波次格口补缺口 | resolveShopeeWaveGroupKey + findAvailableShipments(..., shopeeWaveCode, groupKey) |
从 shopee_wave.groupKey 解析当前波次特征值,只补相同 groupKey 的单 |
空 groupKey |
appendGroupKeyFilter |
空值只匹配空值,避免空值与非空值串波 |
这意味着同一 Shopee 波次内不会混入不同 groupKey 的订单;如果候选池存在多个 groupKey,当前按抓单优先级排序后的首个候选单决定本轮特征值。
7. 格口分配策略
7.1 三层级匹配
| 层级 | 方法 | 条件 | 优先级 |
|---|---|---|---|
| 1 | matchCurRuleCells |
currentWaveRule == rule.code |
最高 |
| 2 | matchRuleCells |
currentWaveRule 为空 且 waveRule 逗号分隔匹配 rule.code |
中 |
| 3 | matchEmptyCells |
currentWaveRule 和 waveRule 均为空 |
兜底 |
标准要求:分播墙明细界面中 wcs_sorting_wall_cell.waveRule 取值来自 wave_rule,支持多选,多个规则中间用逗号隔开。当前 matchRuleCells 通过逗号拆分匹配当前规则编码。
7.2 格口抢占
occupySortingWallCell(warehouseCode, cell, rule, shopeeWaveCode)
UPDATE wcs_sorting_wall_cell
SET useStatus = 'USED',
shopeeWaveCode = ?,
currentWaveRule = ?,
lastUpdatedBy = ?
WHERE id = ?
AND warehouseCode = ?
AND status = 'ENABLE'
AND useStatus = 'IDLE'
- 使用乐观锁(
useStatus='IDLE'条件),同一格口只能被一个请求抢占成功 - 已有相同 Shopee 波次和规则的 USED 格口通过
ensureCellReadyForWave复用(计划任务补单场景)
7.3 格口查询
findCells(workstationId)
→ 找出工作站下所有分播墙 -> 各墙下启用格口 -> 过滤 IDLE 状态格口
8. Shopee 波次绑定与 ESS 校验
8.1 波次号抢占
acquireUnusedShopeeWave(warehouseCode, waveType)
- 查询
shopee_wave表:warehouseCode+waveType+status=100(UNUSED),按 id 升序取前 20 - 逐条尝试乐观更新:
SET status=200(USING) WHERE id=? AND status=100 - 更新成功的第一条即抢占成功,标记
processStamp='CLAIMED_UNUSED' - 全部失败返回 null(号段耗尽)
8.2 Shopee 波次绑定
bindShipmentShopeeWave(warehouseCode, shipmentIds, shopeeWaveCode)
流程:
1. buildShopeeWaveBindLockKey
→ 同一 Shopee 波次号绑定串行,避免多个入口同时使用同一号段。
2. isShopeeWaveCodeAvailable
→ 获取锁后重新检查 shopee_wave 是否仍可用。
3. requestShopeeWaveBind
→ 预留真实上游绑定接口。
→ 当前口径只有整批成功或整批失败,不处理部分成功。
4. 失败单据 → markKickOutWave(shipmentIds, shopeeWaveCode)
→ UPDATE shipment_header SET kickOutWave = CONCAT(已有的, ',', 新波次号)
→ 逗号分隔,同波次号去重
5. 成功单据 → UPDATE shipment_header SET shopeeWave = ? WHERE id IN (...)
→ 同步更新 shopee_wave.status/pickType/groupKey
6. 返回 ResponseMessageFactory.success()
8.3 接口对接状态
requestShopeeWaveBind 当前预留真实接口调用位置:
接口成功 → 本批出库单全部成功绑定 shopeeWave
接口失败 → 本批出库单全部写入 kickOutWave
正式接口契约确认后,只需补齐 requestShopeeWaveBind 的真实调用;当前不再保留柔性拒单解析方法。
标准要求补充:销售 create_picking_task、RTS-MTO 下发/变更等上游链路需要带入或校验 Shopee 波次号;当前服务侧保留统一合波校验入口,真实 create_picking_task / RTS-MTO 接口契约仍待接入验证。
9. 内部波次创建与运行
9.1 createInternalWave
createInternalWave(warehouseCode, rule)
resolveMasterCode(warehouseCode)— 从系统参数读取波次主表编码(WCS/EQUIPMENT_MANUFACTURER)waveSvc.createInternalWave(session, warehouseCode, masterCode, ruleCode)— 创建内部波次- 返回 waveId(Long)
- 失败返回 null
9.2 addShipmentsToInternalWave
addShipmentsToInternalWave(shipmentIds, waveId)
shipmentHeaderService.addMultipleToWave(session, shipmentIds, waveId)— 加入波次waveSvc.run(session, waveId)— 异步提交wms.wave队列消息- 任一步失败返回错误
9.3 波次运行结果
WaveService#run 只提交异步队列消息并返回提交结果,WaveRuleMatchService 不解析同步库存不足踢单明细。
库存不足踢单由 wms-wave 回池链路处理:回池时清空出库单头 shopeeWave/waveRule,不记录 kickOutWave;上游撤单接口仍需在回池链路接入。
标准要求:库存分配优先按效期/先到期先出与 FIFO,其次按工作站点位与库存点位半径最小,同半径内低层货位优先;库存不足单据需踢单并通知上游撤单。当前本文只记录集波服务入口与回池清理口径,库存最优分配和上游撤单通知不应描述为 WaveRuleMatchService 已完成。
10. 回滚机制
回滚场景与对应方法
| 场景 | 回滚方法 | 操作 |
|---|---|---|
| Shopee 波次绑定失败 | rollbackShipmentShopeeWave |
回退 shipment_header.shopeeWave = null(仅当 waveId=0 时) |
| 内部波次空无单据 | rollbackInternalWaveIfEmpty |
UPDATE wave SET status=999(标记异常,不删除数据) |
| 格口抢占失败 | rollbackWaveShipments + 逐单 rollbackShipmentShopeeWave |
从内部波次剔除 + 回退 Shopee 波次号 |
| ESS 拒绝 | markKickOutWave |
写入 kickOutWave,同波次后续补单过滤 |
| 库存不足踢单 | ShipmentAllocationService.clearShopeeWaveBindingAfterKickOut |
清空 shopeeWave/waveRule,不写 kickOutWave |
| 内部波次加单失败 | removeShipmentsFromWave |
调用 ShipmentHeaderService.batchRemoveFromWave |
| 新 Shopee 波次未使用 | rollbackClaimedShopeeWaveIfUnused |
恢复 shopee_wave.status=100(仅当无任何引用时) |
| 旧波次无未完成任务 | markShopeeWaveCompletedIfNoOpenTask |
shopee_wave.status=300(COMPLETED) |
回滚原则
- 不删除数据:波次标记 999 而非 DELETE,Shopee 波次号恢复 100 而非删除
- 不取消出库单:
handleFlowPickChangeWaveFailure明确注释"不取消出库单" - 先绑后解:绑定顺序为 Shopee 波次号 → 格口 → 内部波次,回滚按反序执行
11. 首次集波内补缺口流程
11.1 入口
当前状态:pollReplenishment/pollReplenishmentOnce 已删除。补缺口能力在首次集波入口中通过 fillShortageCells 执行。
已具备的依赖方法:
fillShortageCells(...):入口内逐规则处理已绑定 Shopee 波次的可补格口。findShortageCells(Long workStationId, WaveRule rule):SQL 查询空闲、已绑定 Shopee 波次、currentWaveRule匹配当前规则的格口。calcCellNeedQty(...):计算单品单件或非单品单件缺口。assignToCell(...):先抢占格口,再请求 Shopee 波次绑定接口,再创建并运行内部波次。
入口不返回分配明细;成功、失败和跳过原因通过系统处理日志记录。
11.2 流程
findBestMatchedRuleByWorkStationAndOutboundType(...)
│
└─ fillShortageCells(warehouseCode, shipmentType, workstationId, rules)
│
└─ for each rule:
findShortageCells(workstationId, rule)
→ wcs_sorting_wall_cell
→ useStatus = IDLE
→ shopeeWaveCode 非空
→ currentWaveRule = rule.code
│
└─ for each cell:
calcCellNeedQty
findAvailableShipments(..., shopeeWaveCode, groupKey)
assignToCell
11.3 fillShortageCells 逻辑
fillShortageCells(warehouseCode, shipmentType, workstationId, rules):
│
├─ 参数校验(warehouseCode / shipmentType / workstationId / rules)
│
├─ for each rule:
│ ├─ findShortageCells → 查当前规则可补格口
│ └─ for each cell:
│ ├─ calcCellNeedQty → 计算缺口
│ │ ├─ 单品单件: 未拣货数量 < low_threshold → need = maxShipments
│ │ ├─ 非单品单件: need = maxShipments - 当前 Shopee 波次已绑单量
│ │ └─ need <= 0 → 跳过
│
├─ resolveShopeeWaveGroupKey → 同一 Shopee 波次只补相同 groupKey 单据
│
├─ findAvailableShipments(..., shopeeWaveCode, groupKey)
│ → waveType/orderStructure 匹配
│ → WAVE_RULE_FIELD_MATCHING 字段匹配
│ → 排除 kickOutWave 过滤的已拒单
│ → 只补同一 groupKey 单据
│
└─ assignToCell
├─ ensureCellReadyForWave → 先抢占格口
├─ bindShipmentShopeeWave → Shopee 波次绑定接口
├─ createInternalWave
└─ addShipmentsToInternalWave → WaveService#run
11.4 缺口计算
calcCellNeedQty(warehouseCode, rule, cell, shopeeWaveCode)
| 规则类型 | 触发条件 | 补单数量 |
|---|---|---|
| 单品单件 | < low_threshold |
maxShipments(一次性补满) |
| 非单品单件 | 始终尝试 | maxShipments - 当前波次已绑单量 |
| 新格口/无波次号 | — | 返回 null(不截断) |
12. Flow Pick 换箱换波流程
12.1 触发
当前 Flow Pick 换箱换波接口为占位流程,需结合真实触发入口恢复;原计划任务补单入口已删除。
isFlowPickCellFull:单品单件 + countShopeeShipments >= maxShipments
标准要求:Flow Pick 包含 SSSQ、MSSQ 规则;RTS/MTO 下发单据因按行拆单,也按 Flow Pick 处理。当前服务中的满槽换箱判断以单品单件规则为主,RTS/MTO Flow Pick 细分触发口径按 docx 待确认。
12.2 流程
changeFlowPickWave(workstation, cell, rule):
│
├─ 1. findFlowPickRemainingShipments
│ → 当前工作站 + 旧波次 + 规则下 未完成拣货任务的订单
│ 无 → markShopeeWaveCompletedIfNoOpenTask + 返回
│
├─ 2. requestFlowPickSealBox(封箱)
│ → essFlowPickService.sealBox(动态调用)
│ 未接入 → 记录日志,返回 true(继续本地换波)
│ 失败 → 终止
│
├─ 3. resolveShopeeWaveType(从剩余订单推断波次类型)
│
├─ 4. acquireUnusedShopeeWave(取新号段)
│ 失败 → 返回错误
│
├─ 5. requestFlowPickChangeBox(换箱)
│ → essFlowPickService.changeBoxWave(动态调用)
│ 未接入 → 记录日志,返回 true(继续本地回写)
│ 失败 → rollbackClaimedShopeeWaveIfUnused + handleFlowPickChangeWaveFailure + 返回错误
│
├─ 6. updateFlowPickShopeeWave(回写新波次号)
│ → UPDATE shipment_header SET shopeeWave = 新号
│ → UPDATE wcs_work_station_pick_task_header SET shopeeWave = 新号
│ → UPDATE wcs_sorting_wall_cell SET shopeeWaveCode = 新号
│
└─ 7. markShopeeWaveCompletedIfNoOpenTask(旧波次标记完成)
12.3 当前状态
| 步骤 | 实现状态 | 说明 |
|---|---|---|
| 剩余订单查询 | ✅ 已实现 | findFlowPickRemainingShipments |
| 封箱接口 | ⚠️ 动态调用占位 | essFlowPickService.sealBox(未接入时本地跳过) |
| 新波次取号 | ✅ 已实现 | acquireUnusedShopeeWave |
| 换箱接口 | ⚠️ 动态调用占位 | essFlowPickService.changeBoxWave(未接入时本地跳过) |
| 字段回写 | ✅ 已实现 | updateFlowPickShopeeWave |
| 旧波次完成 | ✅ 已实现 | markShopeeWaveCompletedIfNoOpenTask |
| 失败回滚 | ⚠️ 仅日志 | handleFlowPickChangeWaveFailure 仅记录日志,未实现 AGV 回库/订单池恢复 |
13. 字段匹配规则(WAVE_RULE_FIELD_MATCHING)
注意:
wave_rule.waveType与shipment_header.orderStructure的包含关系不是通过WAVE_RULE_FIELD_MATCHING配置,而是由appendWaveTypeOrderStructureFilter + resolveMatchedOrderStructures固定维护。WAVE_RULE_FIELD_MATCHING只负责其它规则属性与出库单字段的匹配。
13.1 数据字典结构
通过数据字典表 config_detail + config_value 配置,recordType = 'WAVE_RULE_FIELD_MATCHING'。
| 字段 | 含义 | 示例 |
|---|---|---|
identifier |
出库单字段 | userDef1、shipmentCategory2、priority |
value1 |
规则字段 | channelId、shopId、urgentFlag |
value2 |
操作符 | IN / EQ / LE |
value3 |
Sales Outbound Order 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=2 |
value4 |
Sales Outbound Task 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=1 |
value5 |
Move Transfer Demand 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=4 |
value6 |
Move Transfer Task 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=5 |
value7 |
RTS Demand 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=3 |
value8 |
RT Order 是否参与匹配,Y 才生效 |
对应 shipment_header.ticketType=6 |
warehouseCode |
仓库级/全局 | 优先匹配当前仓,无配置时读取 * |
匹单字段需要同时满足字段映射和
ticketType标记。某字段在对应订单类型列未标记Y时,该字段不参与该订单类型匹单;只有标记Y的订单类型才按value2操作符生成字段条件。
13.2 支持的操作符
| 操作符 | SQL 生成 | 说明 |
|---|---|---|
IN(默认) |
ticketType 未标记 Y 或 shipmentField IN (:values) |
规则值按逗号/中文逗号拆分多选 |
EQ / = / EQUAL / EQUALS |
ticketType 未标记 Y 或 shipmentField = :value |
等值匹配 |
LE / <= / LESS_OR_EQUAL |
ticketType 未标记 Y 或 ifnull(shipmentField, 0) <= :value |
数值小于等于 |
13.3 ticketType 字段适用关系
WAVE_RULE_FIELD_MATCHING 每一行字段配置都可以按订单类型单独控制是否参与匹单。
| 字典列 | 订单类型 | ticketType | 是否参与 |
|---|---|---|---|
value3 |
Sales Outbound Order | 2 |
填 Y 参与,否则跳过该字段 |
value4 |
Sales Outbound Task | 1 |
填 Y 参与,否则跳过该字段 |
value5 |
Move Transfer Demand | 4 |
填 Y 参与,否则跳过该字段 |
value6 |
Move Transfer Task | 5 |
填 Y 参与,否则跳过该字段 |
value7 |
RTS Demand | 3 |
填 Y 参与,否则跳过该字段 |
value8 |
RT Order | 6 |
填 Y 参与,否则跳过该字段 |
示例:channelId 这一行如果只在 value3/value4 填 Y,则只有 ticketType=2/1 的单据会校验 channelId;其它 ticketType 单据不会因为 channelId 不匹配被过滤。
13.4 字段白名单
防止字典配置拼出非预期 SQL,白名单校验在 toRuleFieldMapping 中执行:
出库单字段(30+):
shipmentType, pickType, sourcePlatform, sourceErp, erpOrderType, route, carrierCode, shipmentSubType, shipmentCategory2~8, shipToCountry/State/City/District/Town(Code), hostCompanyCode, storeCode, shipObjType, requestedDeliveryType, priority, userDef1~8
规则字段(16):
channelId, fulfillmentChainId, orderSize, skuSizeType, categoryLevel1Id, shopGroup, shopId, urgentFlag, outerPackagingType, outerPackagingId, deliveryRegion, fragile, liquid, highValue, battery, danger
13.5 V1.4 新增/待确认匹单字段
| 标准字段/规则 | 标准要求 | 当前实现/待实现 |
|---|---|---|
Max SKU Pieces Per Order |
单订单任一 SKU 件数大于规则阈值时,该订单排除出当前 wave rule,转给低优先级的大件数规则 | 当前 WaveRule 域与 appendRuleFieldMatching 未见对应字段/SQL,待实现 |
Mix Mode Max SKU Pieces Filter |
仅 Mix-mode/MSAQ 规则生效,排除单 SKU 件数过高的混合订单,避免混合波次槽位过度丰富 | 当前未见对应字段/SQL,待实现 |
| SKU 主档字段匹配 | docx 要求 API 列为 SKU 的规则字段需与单据所有明细 SKU 主档字段匹配,全部匹配才能进该规则 | 当前字段匹配 SQL 只处理单头/扩展字段;明细 SKU 全量匹配待实现或待接入 |
multi_attr_list |
docx 要求多值字段用逗号存储时必须全包含;按示例,规则 A,B,C 可匹配单据 A,B,规则 B,C 不匹配单据 A,B |
当前 channelId/fulfillmentChainId 逗号字段按任一值命中;全包含方向按 docx 待确认,当前待调整 |
13.6 执行流程
resolveRuleFieldMappings(warehouseCode)
→ 查 config_detail (优先 warehouseCode, 兜底 '*')
→ toRuleFieldMapping(detail)
→ 白名单校验
→ normalizeRuleOperator 规范化操作符
→ resolveRuleFieldTicketTypes(detail) 解析 value3~value8 中标记 Y 的 ticketType
→ 返回 RuleFieldMapping(ruleField, shipmentField, operator, ticketTypes)
appendRuleFieldMatching(sql, params, warehouseCode, rule)
for each mapping:
→ mapping.ticketTypes 为空时跳过该字段
switch operator:
IN → appendTicketTypeScopedIn
EQ → appendTicketTypeScopedEquals
LE → appendTicketTypeScopedLessOrEqual
SQL 形态:
and (
ifnull(ticketType, 0) not in (:当前字段适用ticketTypes)
or 字段条件成立
)
14. 方法调用关系图
14.1 首次集波
findBestMatchedRuleByWorkStationAndOutboundType(String, String, String) [入口-工作站编码]
└─ findEnabledWorkStation
└─ findBestMatchedRuleByWorkStationAndOutboundType(Long, String) [统一入口]
├─ findEnabledRules
├─ assignSingleShipmentWaves [一波一单分流]
│ ├─ findSingleShipmentWaveShipments
│ ├─ createInternalWave
│ └─ addShipmentsToInternalWave
├─ fillShortageCells [已绑定 Shopee 波次格口补缺口]
│ ├─ findShortageCells
│ ├─ calcCellNeedQty
│ ├─ resolveShopeeWaveGroupKey
│ ├─ findAvailableShipments(..., shopeeWaveCode, groupKey)
│ └─ assignToCell
└─ (标准集波) for each rule: [标准集波]
├─ findCells → matchCurRuleCells / matchRuleCells / matchEmptyCells
└─ assignCellNumbers
├─ findSingleShipmentWaveShipments
├─ findAvailableShipments
├─ createInternalWave
├─ acquireUnusedShopeeWave
└─ for each cell:
├─ skipSingleRpln (单品单件)
├─ assignToCell
│ ├─ pickCellShipmentIds
│ ├─ calcCellNeedQty
│ ├─ ensureCellReadyForWave → occupySortingWallCell
│ ├─ bindShipmentShopeeWave
│ │ ├─ requestShopeeWaveBind
│ │ ├─ markKickOutWave (整批失败)
│ │ └─ updateShopeeWaveBindingFields (整批成功)
│ ├─ addShipmentsToInternalWave → run
│ └─ 成功后消费当前候选池
└─ (回滚)
├─ rollbackInternalWaveIfEmpty
├─ rollbackWaveShipments → removeShipmentsFromWave
└─ rollbackShipmentShopeeWave
14.2 首次集波内补缺口
fillShortageCells
└─ for each rule:
├─ findShortageCells
└─ for each cell:
├─ calcCellNeedQty
├─ resolveShopeeWaveGroupKey
├─ findAvailableShipments(..., shopeeWaveCode, groupKey)
└─ assignToCell
14.3 查询方法
订单查询:
buildAvailableShipmentSql ← 基础 SQL(异常过滤)
buildAvailableShipmentParams ← 基础参数
appendBatchWaveFilter ← 排除一波一单
appendSingleWaveFilter ← 一波一单条件
appendWaveTypeOrderStructureFilter ← wave_rule.waveType → shipment_header.orderStructure 包含关系
└─ resolveMatchedOrderStructures
appendRuleFieldMatching ← WAVE_RULE_FIELD_MATCHING 字典匹配
├─ appendIn / appendEquals / appendLessOrEqual
└─ resolveRuleFieldMappings → toRuleFieldMapping
appendKickOutFilter ← 排除已拒单
appendShipmentPriorityOrder ← 排序
格口查询:
findCells(workstationId)
findCells(workstationId, rule)
matchCurRuleCells / matchRuleCells / matchEmptyCells
findActiveWorkstationShipmentTypes(warehouseCode, workStationId)
规则查询:
findEnabledRules(warehouseCode)
工作站查询:
findEnabledWorkStation(warehouseCode, workStation)
波次查询:
countShopeeShipments(warehouseCode, shopeeWaveCode)
resolveShopeeWaveGroupKey(warehouseCode, shopeeWaveCode)
resolveShopeeWaveType(warehouseCode, shipmentIds)
工具方法:
toLongValue / toNonNegativeInteger
ruleStringValue / ruleIntegerValue
splitCandidates / matchesCellWaveRule
buildRuleMatchLockKey
isSingleRule
resolveMasterCode / resolveShipmentLimit / resolveLowThreshold / resolveMatchedOrderStructures
normalizeRuleOperator
附录:关键常量
| 常量 | 值 | 用途 |
|---|---|---|
SHIPMENT_IN_POOL |
100 |
出库单订单池状态 |
SHIPMENT_PROCESS_NORMAL |
'NORMAL' |
正常处理流程标识 |
DEFAULT_SHIPMENT_LIMIT |
30 |
规则未配 maxShipments 的兜底上限 |
PICK_TYPE_PICKING_BY_ORDER |
1 |
按单拣选标识 |
PICK_TYPE_PICKING_TASK |
2 |
推拣货任务标识 |
TICKET_TYPE_RT |
6 |
RT ticketType,一波一单且不使用 Shopee 波次号 |
TICKET_TYPE_XSCK_TASK |
1 |
销售出库任务 |
TICKET_TYPE_MTO_TASK |
5 |
MTO 任务 |
WAVE_RULE_FIELD_MATCHING |
'WAVE_RULE_FIELD_MATCHING' |
字段匹配字典类型 |
15. 标准需求与当前实现对照
15.1 标准文档 V1.4 已同步口径
| 标准需求点 | 当前文档/代码口径 | 实现状态 |
|---|---|---|
shipment_header.orderStructure 自动分析 |
ShipmentHeaderService#calcOrderStructure 写入 1=SSSQ、2=SSAQ、3=MSAQ |
已落地 |
wave_rule.waveType 与 shipment_header.orderStructure 包含关系 |
appendWaveTypeOrderStructureFilter + resolveMatchedOrderStructures |
已落地 |
同一波次 groupKey 一致 |
findAvailableShipments 首次解析候选 groupKey;补缺口读取 shopee_wave.groupKey |
已落地 |
| 上墙优先级 | findEnabledRules 按 wavePriority desc, id asc;高优先级不足 maxShipments 仍优先消费 |
已落地 |
| 抓单优先级 | priority desc、cutOffTime asc、purchaseTime asc、orderTime asc、id asc |
已落地 |
wave_rule.maxShipments |
resolveShipmentLimit 使用规则级 maxShipments,默认 30 |
已落地 |
wcs_sorting_wall_cell.waveRule 多选 |
逗号分隔匹配当前规则编码 | 已落地 |
WAVE_RULE_FIELD_MATCHING |
appendRuleFieldMatching 按字典追加 IN/EQ/LE 条件 |
已落地 |
kickOutWave 过滤 |
appendKickOutFilter 对同一 Shopee 波次过滤已拒单 |
已落地 |
Max SKU Pieces Per Order |
标准字段已同步到文档,当前未见字段/SQL | 待实现 |
Mix Mode Max SKU Pieces Filter |
标准字段已同步到文档,当前未见字段/SQL | 待实现 |
multi_attr_list 全包含 |
当前逗号字段按任一值命中;docx 示例方向已记录 | 待调整,按 docx 待确认 |
wave_rule 字段命名规范 |
WES 小驼峰 + base 逻辑创建自定义字段 / 时间 / 用户等字段 | 标准要求已在 docx 强调;当前主数据字段命名仍以代码与迁移为准 |
shipment_header.pickType 手工拣选切换 |
仅订单池 100 状态可更新为 1/0 |
当前服务侧仅有按单拣选过滤,界面/接口切换待接入 |
ticketType 映射 |
XSCK=1/2、MTO=5/4、RTS=3、RT=6 | 当前文档已在 13.3 详细列出,按此口径匹配 |
15.2 waveType/orderStructure 标准映射
| 标准 waveType | 编码 | 当前可抓 orderStructure | 编码 |
|---|---|---|---|
| SSSQ | 1 |
SSSQ | 1 |
| MSSQ | 5 |
SSSQ | 1 |
| SSAQ | 4 |
SSSQ / SSAQ | 1 / 2 |
| SSSQ(Same SKU same Qty) | 2 |
MSAQ | 3 |
| MSAQ | 3 |
SSSQ / SSAQ / MSAQ | 1 / 2 / 3 |
15.3 标准需求中仍为占位或待恢复的点
| 标准需求点 | 当前实现状态 | 后续落点 |
|---|---|---|
| Shopee 合波校验真实接口 | requestShopeeWaveBind 为预留入口,当前只保留整批成功/失败契约 |
接口契约明确后补真实调用 |
| create_picking_task / RTS-MTO 上游校验 | 标准要求与 Shopee 波次获取、合波校验衔接;当前仅有服务侧统一预留入口 | 真实接口契约确认后接入 |
| 库存不足踢单通知上游撤单 | 当前由回池链路清空 shopeeWave/waveRule,上游撤单接口仍待接入 |
wms-wave 回池链路 |
| 计划任务轮询补单 | 原 pollReplenishment/pollReplenishmentOnce 已删除;当前补缺口在首次集波入口 fillShortageCells 执行 |
如需后台轮询,需恢复独立计划任务入口 |
| 推拣货任务一单一波一槽口 | 普通批量已过滤 pickType=2,独立分流入口当前未保留 |
待恢复 pickType=2 + shopeeWave 专用分流 |
| Flow Pick 封箱/换箱接口 | 本地流程已有占位方法,真实 ESS/AGV 接口未接入 | requestFlowPickSealBox / requestFlowPickChangeBox / handleFlowPickChangeWaveFailure |
| 工作站用户权限控制出库类型 | 当前只校验工作站启用、仓库和不同出库类型隔离 | 用户权限与出库类型绑定仍待实现 |
| 库存最优分配策略 | docx 要求效期/FIFO、工作站/库存半径、低层货位优先 | 属库存分配链路,本文不标记为 WaveRuleMatchService 已落地 |
附录:TODO 清单(代码内标注)
| 位置 | TODO | 优先级 |
|---|---|---|
findBestMatchedRuleByWorkStationAndOutboundType |
用户权限与出库类型绑定 | P1 |
resolveMasterCode 前注释 |
库存最优分配规则对齐 | P2 |
acquireUnusedShopeeWave 前注释 |
Shopee 波次号段申请接口 & 用完续号 | P1 |
requestFlowPickSealBox Javadoc |
Flow Pick 封箱接口正式对接 | P1 |
requestFlowPickChangeBox Javadoc |
Flow Pick 换箱接口正式对接 | P1 |
requestShopeeWaveBind Javadoc |
Shopee 波次绑定接口正式契约对接 | P0 |
handleFlowPickChangeWaveFailure Javadoc |
换波失败完整回滚(订单池恢复/取消任务/AGV 回库) | P1 |