Files
ttx-project-doc/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md
T

37 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 集波约束、字段匹配字典规则。


目录

  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

三种分流入口:

分支 条件 处理方式
一波一单分流 pickType=1XSCK+ticketType=1ticketType=5ticketType=6 一单一个内部波次;RT 不使用 Shopee 波次号
已绑波次格口补缺口 空闲格口已绑定 shopeeWaveCodecurrentWaveRule 匹配当前规则 low_threshold/maxShipments 计算缺口后补单
空格口首次分配 其他普通批量候选单 按规则优先级逐规则匹配,先抢占格口,再绑定 Shopee 波次,再创建并运行内部波次

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 锁之前,避免无效请求占用集波锁。

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
待实现 独立分流入口、一单一波一槽口、内部波次运行
补单 后续仍按“不走补单逻辑”处理

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 不追加订单结构条件;此场景通常表示规则数据异常,应通过规则主数据维护修正。

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 均为空 兜底

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 的真实调用;当前不再保留柔性拒单解析方法。


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;上游撤单接口仍需在回池链路接入。


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

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, outerPackaging, outerPackagingId, deliveryRegion, fragile, liquid, highValue, battery, danger

13.5 执行流程

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 已落地
WAVE_RULE_FIELD_MATCHING appendRuleFieldMatching 按字典追加 IN/EQ/LE 条件 已落地
kickOutWave 过滤 appendKickOutFilter 对同一 Shopee 波次过滤已拒单 已落地

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 为预留入口,当前只保留整批成功/失败契约 接口契约明确后补真实调用
库存不足踢单通知上游撤单 当前由回池链路清空 shopeeWave/waveRule,上游撤单接口仍待接入 wms-wave 回池链路
计划任务轮询补单 pollReplenishment/pollReplenishmentOnce 已删除;当前补缺口在首次集波入口 fillShortageCells 执行 如需后台轮询,需恢复独立计划任务入口
推拣货任务一单一波一槽口 普通批量已过滤 pickType=2,独立分流入口当前未保留 待恢复 pickType=2 + shopeeWave 专用分流
Flow Pick 封箱/换箱接口 本地流程已有占位方法,真实 ESS/AGV 接口未接入 requestFlowPickSealBox / requestFlowPickChangeBox / handleFlowPickChangeWaveFailure

附录: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