# WaveRuleMatchService 流程图

> 对应实现：`C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy`
>
> 说明：本文是 AI 可直接读取的纯文本流程图，不使用图片。  
> 口径说明：`WES-WMS` 指当前项目实现侧，`Shopee` 指需要调用或接收交互的外部接口方。  
> 目标：把当前代码的入口、分流、外部接口调用、回滚和状态回写，整理成可检索的文本流程图。

---

## 1. 读取方式

建议按下面顺序理解本文：

1. 先看 `2. 主流程总图`
2. 再看 `3. 分支流程`
3. 最后看 `4. 方法映射表`

如果只想快速定位代码路径，直接看 `4. 方法映射表`。

---

## 2. 主流程总图

```text
入口
└─ findBestMatchedRuleByWorkStationAndOutboundType(session, warehouseCode, workStation, shipmentType)
   ├─ [1] 用户权限校验
   │   └─ hasUserShipmentTypePermission
   │      ├─ 命中禁止 → MSG_WRM_0013
   │      └─ 通过 → 继续
   │
   ├─ [2] 入参校验
   │   ├─ warehouseCode / workStation / shipmentType 任意为空 → MSG_WRM_0001
   │   └─ 通过 → 继续
   │
   ├─ [3] 工作站校验
   │   ├─ findEnabledWorkStation
   │   ├─ 工作站不存在 / 未启用 / 仓库不匹配 → MSG_WRM_0002 / 0004 / 0005 / 0006
   │   └─ 通过 → 继续
   │
   ├─ [4] 接单状态校验
   │   ├─ acceptStatus != OPEN → MSG_WRM_0015
   │   └─ 通过 → 继续
   │
   ├─ [5] 同工作站出库类型隔离
   │   ├─ hasDifferentActiveShipmentType
   │   ├─ 已存在其它 shipmentType 占用 → MSG_WRM_0014
   │   └─ 通过 → 继续
   │
   ├─ [6] Redis 锁
   │   ├─ buildRuleMatchLockKey
   │   ├─ getAndTryLock
   │   ├─ 获取失败 → MSG_WRM_0012
   │   └─ 获取成功 → 继续
   │
   ├─ [7] 查询启用规则
   │   ├─ findEnabledRules(warehouseCode)
   │   ├─ rules 为空 → MSG_WRM_0007
   │   └─ 继续
   │
   ├─ [8] 分流一：一波一单
   │   └─ assignSingleShipmentWaves
   │
   ├─ [9] 分流二：已绑定 Shopee 波次格口补缺口
   │   └─ fillShortageCells
   │
   ├─ [10] 分流三：空格口首次分配
   │   └─ assignEmptyCells
   │
   ├─ [11] 成功返回
   │   └─ ResponseMessageFactory.success()
   │
   └─ [12] finally
       └─ unlock
```

---

## 3. 分支流程

### 3.1 一波一单分流

```text
assignSingleShipmentWaves(warehouseCode, shipmentType, workstationId, rules)
├─ for each rule
│  ├─ findCells(session, workstationId)
│  ├─ matchEmptyCells(cells)
│  ├─ findSingleShipmentWaveShipments(session, warehouseCode, shipmentType, rule, emptyCells.size())
│  ├─ if 未命中 → 继续下一个 rule
│  └─ if 命中
│     ├─ 取当前空闲格口的首个 cell
│     ├─ occupySingleShipmentSortingWallCell
│     ├─ 若不是 RT 且 shipment_header_ext1.shopeeWave 为空
│     │  ├─ acquireUnusedShopeeWave
│     │  └─ requestShopeeWaveBind / bindShipmentShopeeWave
│     ├─ createInternalWave
│     ├─ addShipmentsToInternalWave
│     └─ 记录成功日志
└─ 返回 success 或 error
```

关键点：

- `RT` 不使用 Shopee 波次号。
- 非 `RT` 的一波一单，若本地没有 `shopeeWave`，会先取号再绑定。
- 一波一单是“1 单 -> 1 格口 -> 1 内部波次”的结构。

### 3.2 补缺口分流

```text
fillShortageCells(warehouseCode, shipmentType, workstationId, rules)
├─ for each rule
│  ├─ findShortageCells(session, workstationId, rule)
│  ├─ for each cell
│  │  ├─ 如果 cell 未绑定 shopeeWaveCode → 跳过
│  │  ├─ calcCellNeedQty
│  │  ├─ needQty <= 0 → 跳过
│  │  ├─ resolveShopeeWaveGroupKey
│  │  ├─ findAvailableShipments(..., shopeeWaveCode, groupKey)
│  │  ├─ assignToCell
│  │  └─ 记录补单成功或失败日志
│  └─ 继续下一个 rule
└─ 返回
```

关键点：

- 只处理当前工作站里“已经绑定 Shopee 波次”的格口。
- 补单时要保持同一 `groupKey`。
- 补缺口不重新取新 Shopee 波次号。

### 3.3 空格口首次分配

```text
assignEmptyCells(warehouseCode, shipmentType, workstationId, rules)
├─ for each rule
│  ├─ findCells(session, workstationId)
│  ├─ assignCellNumbers(warehouseCode, shipmentType, workstationId, rule, cells)
│  ├─ 若本轮分配成功 → break
│  └─ 否则继续下一个 rule
└─ 返回 success
```

`assignCellNumbers` 的核心逻辑：

```text
assignCellNumbers(...)
├─ for each cell
│  ├─ 读取 cell.shopeeWaveCode
│  ├─ 如果 cell 没有 shopeeWaveCode
│  │  ├─ findAvailableShipments(session, warehouseCode, shipmentType, rule)
│  │  ├─ acquireUnusedShopeeWave
│  │  └─ 得到新 shopeeWaveCode
│  ├─ 如果 cell 已有 shopeeWaveCode
│  │  ├─ calcCellNeedQty
│  │  ├─ needQty <= 0 → 跳过
│  │  ├─ resolveShopeeWaveGroupKey
│  │  └─ findAvailableShipments(..., shopeeWaveCode, groupKey)
│  └─ assignToCell
└─ 返回 success / error
```

---

## 4. 关键子流程

### 4.1 assignToCell

```text
assignToCell(warehouseCode, rule, cell, shipmentIds, shopeeWaveCode)
├─ pickCellShipmentIds
├─ ensureCellReadyForWave
│  ├─ 已有同 wave 同 rule 的 USED 格口 → 直接复用
│  └─ 否则走 occupySortingWallCell
├─ bindShipmentShopeeWave
│  ├─ requestShopeeWaveBind
│  ├─ 失败 → markKickOutWave
│  └─ 成功 → updateShipmentShopeeWaveBinding / updateShopeeWaveBindingFields
├─ createInternalWave
├─ addShipmentsToInternalWave
│  ├─ shipmentHeaderService.addMultipleToWave
│  └─ waveSvc.run
└─ 清空当前候选池，返回 success
```

### 4.2 bindShipmentShopeeWave

```text
bindShipmentShopeeWave(warehouseCode, shipmentIds, shopeeWaveCode, cell)
├─ 生成 bind 锁 key
├─ 获取 Shopee 波次绑定锁
├─ isShopeeWaveCodeAvailable
├─ requestShopeeWaveBind
│  ├─ findShopeeWaveBindShipments
│  ├─ 检查 ERP 单号完整性
│  ├─ resolveShopeeAppendOrderInterfaceType
│  ├─ validateShopeeBindPickingTaskArgs
│  └─ requestShopeeBindPickingTask
├─ 若失败
│  ├─ markKickOutWave
│  └─ 返回 error
└─ 若成功
   ├─ updateShipmentShopeeWaveBinding
   ├─ updateShopeeWaveBindingFields
   └─ 返回 success
```

重要说明：

- `requestShopeeWaveBind` 是当前代码里的“绑定前检查 + Shopee 调用”闭环。
- `requestShopeeCreatePickingTask`、`requestShopeeAppendOrder` 这两个方法在类里存在，但**当前主执行路径没有直接走它们**；当前实际落点是 `requestShopeeBindPickingTask`。
- 这意味着图里要把“辅助方法”和“当前实际执行路径”分开看。

### 4.3 createInternalWave / addShipmentsToInternalWave

```text
createInternalWave(session, warehouseCode, rule)
├─ resolveMasterCode
├─ waveSvc.createInternalWave
└─ 返回 waveId

addShipmentsToInternalWave(shipmentIds, waveId)
├─ shipmentHeaderService.addMultipleToWave
├─ waveSvc.run
└─ run 失败时 cancelInternalWave
```

### 4.4 回滚与释放

```text
回滚点
├─ 格口抢占失败
│  └─ 直接返回 false / error，继续尝试下一个格口或规则
├─ Shopee 波次绑定失败
│  └─ markKickOutWave
├─ 内部波次创建失败
│  └─ releaseSingleShipmentSortingWallCell / 返回 error
├─ 内部波次运行失败
│  └─ cancelInternalWave
└─ finally
   └─ unlock
```

---

## 5. 方法映射表

| 方法 | 作用 | 在流程图中的位置 |
|---|---|---|
| `findBestMatchedRuleByWorkStationAndOutboundType` | 主入口 | 主流程总图 |
| `hasUserShipmentTypePermission` | 用户出库类型权限 | 主流程总图 [1] |
| `findEnabledWorkStation` | 工作站校验 | 主流程总图 [3] |
| `hasDifferentActiveShipmentType` | 同工作站出库类型隔离 | 主流程总图 [5] |
| `findEnabledRules` | 查启用规则 | 主流程总图 [7] |
| `assignSingleShipmentWaves` | 一波一单分流 | 3.1 |
| `fillShortageCells` | 补缺口分流 | 3.2 |
| `assignEmptyCells` | 空格口首次分配 | 3.3 |
| `assignCellNumbers` | 规则驱动的格口分配 | 3.3 |
| `assignToCell` | 单个格口承接一批单据 | 4.1 |
| `bindShipmentShopeeWave` | Shopee 波次绑定 | 4.2 |
| `requestShopeeWaveBind` | 绑定前检查 + Shopee 调用 | 4.2 |
| `createInternalWave` | 创建内部波次 | 4.3 |
| `addShipmentsToInternalWave` | 订单入波次并运行 | 4.3 |
| `cancelInternalWave` | 运行失败回滚 | 4.4 |

---

## 6. 外部接口关系

| 接口编号 | `ShopeeOutboundApiService` 方法 | 方向 | Shopee 外部接口 |
|---|---|---|---|
| 3.1.5 | `requestSalesStartPickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | sales start_picking_task |
| 3.1.6 | `requestSalesBindPickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | sales bind_picking_task |
| 3.1.7 | `requestSalesSyncPickingDetail` | 当前项目/WES-WMS -> Shopee 外部接口 | sales sync_picking_detail |
| 3.1.8 | `requestSalesChangePickingDevice` | 当前项目/WES-WMS -> Shopee 外部接口 | sales change_picking_device |
| 3.1.9 | `requestSalesCompletePickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | sales complete_picking_task |
| 3.1.10 | `requestSalesSearchPickingProcessGuide` | 当前项目/WES-WMS -> Shopee 外部接口 | sales search_picking_process_guide |
| 3.2.5 | `requestSalesCacheTaskNumber` | 当前项目/WES-WMS -> Shopee 外部接口 | sales cache_task_number |
| 3.2.7 | `requestSalesCreatePickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | sales create_picking_task |
| 3.2.8 | `requestSalesAppendOrder` | 当前项目/WES-WMS -> Shopee 外部接口 | sales append_order |
| 3.2.9 | `requestSalesChangeDevice` | 当前项目/WES-WMS -> Shopee 外部接口 | sales change_device |
| 3.2.10 | `requestSalesVendorCompleteTask` | 当前项目/WES-WMS -> Shopee 外部接口 | sales vendor_complete_task |
| 3.3.7 | `requestRtsmtoBindPickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | rtsmto bind_picking_task |
| 3.3.9 | `requestRtsmtoSyncPickingDetail` | 当前项目/WES-WMS -> Shopee 外部接口 | rtsmto sync_picking_detail |
| 3.3.10 | `requestRtsmtoCompleteTask` | 当前项目/WES-WMS -> Shopee 外部接口 | rtsmto complete_task |
| 3.3.11 | `requestRtsmtoChangePickingDevice` | 当前项目/WES-WMS -> Shopee 外部接口 | rtsmto change_picking_device |
| 3.4.5 | `requestMtoStartPickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | mto start_picking_task |
| 3.4.6 | `requestMtoBindPickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | mto bind_picking_task |
| 3.4.7 | `requestMtoSyncPickingDetail` | 当前项目/WES-WMS -> Shopee 外部接口 | mto sync_picking_detail |
| 3.4.8 | `requestMtoCompletePickingTask` | 当前项目/WES-WMS -> Shopee 外部接口 | mto complete_picking_task |
| `waveSvc.createInternalWave` | - | 当前项目内部 | 创建内部波次 |
| `waveSvc.run` | - | 当前项目内部 | 触发内部波次运行，异步提交 |

---

## 7. 当前实现摘要

从代码看，`WaveRuleMatchService` 的职责可以概括成：

1. 校验当前工作站和出库类型是否允许进入集波
2. 按规则优先级找单
3. 把命中的单据分到合适的格口
4. 需要时申请 Shopee 波次号并调用 Shopee 外部接口
5. 创建内部波次并触发运行
6. 失败时做格口释放、踢单标记或波次取消

如果只记一句话：

> 这个服务是“集波入口 + 格口分配 + Shopee 绑定 + 内部波次运行”的串联中枢，不是纯规则查询服务。
