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

28 KiB
Raw Blame History

WaveRuleMatchService 流程文档

对应实现:C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy

本文档描述该服务的当前代码流程,按调用入口到出口的完整链路展开,包括主流程、分流流程、补缺口流程、回滚流程和详细的规则匹配机制。


目录

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

1. 服务概述

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

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

三种分流入口:

分支 条件 处理方式
一波一单分流 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 + 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 校验
  • 不走格口分配(不绑定格口)
  • 一单一个内部波次

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
  │     → 规则字段匹配 + 订单结构匹配 + kickOutWave 过滤
  │     → 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 订单池 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 = '')        -- 未挂起

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)
  │   → 排除 kickOutWave 过滤的已拒单
  │
  └─ 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

13.1 数据字典结构

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

字段 含义 示例
identifier 出库单字段 userDef1shipmentCategory1priority
value1 规则字段 channelIdshopIdurgentFlag
value2 操作符 IN / EQ / LE
warehouseCode 仓库级/全局 优先匹配当前仓,无配置时读取 *

13.2 支持的操作符

操作符 SQL 生成 说明
IN(默认) shipmentField IN (:values) 规则值按逗号/中文逗号拆分多选
EQ / = / EQUAL / EQUALS shipmentField = :value 等值匹配
LE / <= / LESS_OR_EQUAL ifnull(shipmentField, 0) <= :value 数值小于等于

13.3 字段白名单

防止字典配置拼出非预期 SQL,白名单校验在 toRuleFieldMapping 中执行:

出库单字段(30+): shipmentType, ticketType, pickType, sourcePlatform, sourceErp, erpOrderType, route, carrierCode, shipmentSubType, shipmentCategory1~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.4 执行流程

resolveRuleFieldMappings(warehouseCode)
  → 查 config_detail (优先 warehouseCode, 兜底 '*')
  → toRuleFieldMapping(detail)
    → 白名单校验
    → normalizeRuleOperator 规范化操作符
    → 返回 RuleFieldMapping(ruleField, shipmentField, operator)

appendRuleFieldMatching(sql, params, warehouseCode, rule)
  for each mapping:
    switch operator:
      IN  → appendIn(sql, params, shipmentField, ruleField + 'Values', splitCandidates(ruleValue))
      EQ  → appendEquals(sql, params, shipmentField, ruleField + 'Value', ruleValue)
      LE  → appendLessOrEqual(sql, params, shipmentField, ruleField + 'Value', toNonNegativeInteger(ruleValue))

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            ← 一波一单条件
  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
  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' 字段匹配字典类型

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