Files
ttx-project-doc/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md
T
2026-07-10 14:44:54 +08:00

44 KiB
Raw Blame History

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.waveTypeshipment_header.orderStructure 包含关系、同一 groupKey 集波约束、字段匹配字典规则、Max SKU Pieces Per Order / Mix Mode Max SKU Pieces Filter 标准要求、multi_attr_list 全包含匹配要求。


目录

  1. 服务概述
  2. 入口与总流程
  3. 前置校验与并发控制
  4. 一波一单分流流程
  5. 推拣货任务当前口径
  6. 标准集波主流程
  7. 格口分配策略
  8. Shopee 波次绑定与 ESS 校验
  9. 内部波次创建与运行
  10. 回滚机制
  11. 首次集波内补缺口流程
  12. Flow Pick 换箱换波流程
  13. 字段匹配规则(WAVE_RULE_FIELD_MATCHING
  14. 方法调用关系图
  15. 标准需求与当前实现对照

1. 服务概述

WaveRuleMatchService 是 Shopee 出库集波的核心服务,职责:

  • 接收「工作站 + 出库类型」请求
  • 查询启用的波次规则(wave_rule),按优先级排序
  • 查找工作站下可用的分播墙格口(wcs_sorting_wall_cell
  • 从订单池(shipment_header)中匹配符合条件的出库单
  • 完成 Shopee 波次号绑定、ESS 合波校验、内部波次创建与运行
  • 首次集波成功返回 ResponseMessageFactory.success(),不返回「格口 + 命中规则 + 波次」明细

当前匹单核心口径:

维度 当前规则
规则优先级 wave_rule.wavePriority 数字越大越优先;高优先级规则订单数不足 maxShipments 时仍优先消费当前可匹配单据
抓单数量 只使用 wave_rule.maxShipmentswave_master.maxShipments 不参与当前逻辑
订单结构 出库单头 shipment_header.orderStructure 记录数字编码:1=SSSQ2=SSAQ3=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 OrderMix Mode Max SKU Pieces Filter 是 docx 标准要求;当前 WaveRule 域和匹单 SQL 未见对应落地字段,列为待实现

三种分流入口:

分支 条件 处理方式
一波一单分流 pickType=1XSCK+ticketType=1ticketType=5ticketType=6 一单一个内部波次;RT 不使用 Shopee 波次号
已绑波次格口补缺口 空闲格口已绑定 shopeeWaveCodecurrentWaveRule 匹配当前规则 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=ENABLEwarehouseCode 匹配
  • 失败返回对应 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=1XSCK+ticketType=1ticketType=5ticketType=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)当前作为待恢复的独立分流口径。

方法

当前代码中未保留 assignPickingTaskSingleWavesfindPickingTaskShipments 方法。

流程图

当前已落地:
  appendBatchWaveFilter 排除 pickType=2,避免推拣货任务进入普通批量集波。

待恢复:
  上游推单写入 pickType=2 + shopeeWave 后,
  WES 独立分流入口按一单一波一槽口处理。

特点

特性 说明
波次号来源 上游预写入 shipment_header.shopeeWaveWES 不走取号逻辑
当前状态 普通批量集波已过滤 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.waveTypeshipment_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.orderStructureShipmentHeaderService#calcOrderStructure 写入,当前编码为 1=SSSQ2=SSAQ3=MSAQ
  • wave_rule.waveType 是规则侧单选类型,不直接等值匹配订单结构,而是通过上表转换成可包含的订单结构集合。
  • appendWaveTypeOrderStructureFilter 同时用于普通批量候选和一波一单候选,保证入口一致。
  • 未识别的 waveType 不追加订单结构条件;此场景通常表示规则数据异常,应通过规则主数据维护修正。
  • docx 中同时出现 SSSQSSSQ(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 currentWaveRulewaveRule 均为空 兜底

标准要求:分播墙明细界面中 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)
  1. 查询 shopee_wave 表:warehouseCode + waveType + status=100(UNUSED),按 id 升序取前 20
  2. 逐条尝试乐观更新:SET status=200(USING) WHERE id=? AND status=100
  3. 更新成功的第一条即抢占成功,标记 processStamp='CLAIMED_UNUSED'
  4. 全部失败返回 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)
  1. resolveMasterCode(warehouseCode) — 从系统参数读取波次主表编码(WCS/EQUIPMENT_MANUFACTURER
  2. waveSvc.createInternalWave(session, warehouseCode, masterCode, ruleCode) — 创建内部波次
  3. 返回 waveIdLong
  4. 失败返回 null

9.2 addShipmentsToInternalWave

addShipmentsToInternalWave(shipmentIds, waveId)
  1. shipmentHeaderService.addMultipleToWave(session, shipmentIds, waveId) — 加入波次
  2. waveSvc.run(session, waveId) — 异步提交 wms.wave 队列消息
  3. 任一步失败返回错误

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 而非 DELETEShopee 波次号恢复 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.waveTypeshipment_header.orderStructure 的包含关系不是通过 WAVE_RULE_FIELD_MATCHING 配置,而是由 appendWaveTypeOrderStructureFilter + resolveMatchedOrderStructures 固定维护。WAVE_RULE_FIELD_MATCHING 只负责其它规则属性与出库单字段的匹配。

13.1 数据字典结构

通过数据字典表 config_detail + config_value 配置,recordType = 'WAVE_RULE_FIELD_MATCHING'

字段 含义 示例
identifier 出库单字段 userDef1shipmentCategory2priority
value1 规则字段 channelIdshopIdurgentFlag
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/value4Y,则只有 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=SSSQ2=SSAQ3=MSAQ 已落地
wave_rule.waveTypeshipment_header.orderStructure 包含关系 appendWaveTypeOrderStructureFilter + resolveMatchedOrderStructures 已落地
同一波次 groupKey 一致 findAvailableShipments 首次解析候选 groupKey;补缺口读取 shopee_wave.groupKey 已落地
上墙优先级 findEnabledRuleswavePriority desc, id asc;高优先级不足 maxShipments 仍优先消费 已落地
抓单优先级 priority desccutOffTime ascpurchaseTime ascorderTime ascid 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