# 出库集波规则匹配 API 文档

> 对应实现：`WaveRuleMatchController#findBestMatchedRule` → `WaveRuleMatchService#findBestMatchedRuleByWorkStationAndOutboundType`

---

## 概述

工作站集波的核心入口。用户在 WES 工作站界面选择出库类型后，前端调用本接口触发自动集波流程：

1. 根据**工作站 + 出库类型**找出当前可用的分播墙格口
2. 按规则优先级查找订单池中符合条件的出库单
3. 完成 Shopee 波次绑定、ESS 校验、内部波次创建与运行
4. 返回「格口 + 命中规则 + 波次」的分配结果

---

## 请求

### URL

```
POST wms/automation/waveRuleMatch/findBestMatchedRule
```

### 请求体 (JSON)

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `warehouseCode` | String | 是 | 仓库编码 |
| `workStation` | String | 是 | 工作站编码（或工作站数字 ID） |
| `shipmentType` | String | 是 | 出库类型，如 `XSCK`（销售出库）、`MTO`、`RTS`、`RT` |

示例：

```json
{
  "warehouseCode": "WH01",
  "workStation": "WS001",
  "shipmentType": "XSCK"
}
```

### 出库类型说明

| 出库类型 | 含义 | 处理方式 |
|----------|------|----------|
| `XSCK` | 销售出库 | 标准集波（按 ticketType 分流：1=销售出库任务 一波一单，其他批量） |
| `MTO` | MTO 出库 | 标准集波（按 ticketType 分流：5=MTO任务 一波一单，其他批量） |
| `RTS` | RTS 出库 | 标准批量集波 |
| `RT` | RT 出库 | 一波一单，不使用 Shopee 波次号，走库内流程 |

---

## 响应

### 成功响应

```json
{
  "success": true,
  "data": [
    {
      "shipmentId": 12345,
      "waveId": 67890,
      "shopeeWaveCode": "SW20260520001",
      "waveRule": "RULE_XSCK_SSSQ",
      "sortingWallId": 1001,
      "sortingWallCode": "WALL_A01",
      "cellId": 2001,
      "cellCode": "A01-01"
    }
  ]
}
```

| 响应字段 | 类型 | 说明 |
|----------|------|------|
| `shipmentId` | Long | 已集波出库单 ID |
| `waveId` | Long | 内部波次 ID |
| `shopeeWaveCode` | String | Shopee 波次号（RT 类型返回空字符串 `""`） |
| `waveRule` | String | 命中的波次规则编码 |
| `sortingWallId` | Long | 分播墙 ID |
| `sortingWallCode` | String | 分播墙编码 |
| `cellId` | Long | 格口 ID |
| `cellCode` | String | 格口编码 |

> 特殊：RT 和推拣货任务返回简化结构 `{shipmentId, waveId, shopeeWaveCode, waveRule, shipmentType}`。

### 错误响应

```json
{
  "success": false,
  "messageCode": "MSG_WRM_0001",
  "message": "参数不完整：warehouseCode、workStation、shipmentType 不能为空"
}
```

### 错误码

| 消息码 | 说明 | 触发条件 |
|--------|------|----------|
| `MSG_WRM_0001` | 参数不完整 | `warehouseCode`/`workStation`/`shipmentType` 任意为空 |
| `MSG_WRM_0002` | 工作站不可用 | 工作站不存在、仓库不匹配或未启用 |
| `MSG_WRM_0003` | 参数不完整 | `workstationId`/`shipmentType` 为空（内部入口） |
| `MSG_WRM_0004` | 工作站[{0}]不存在 | 工作站 ID 无效 |
| `MSG_WRM_0005` | 工作站[{0}]未启用 | 工作站状态非 ENABLE |
| `MSG_WRM_0006` | 工作站[{0}]仓库编码为空 | 工作站未关联仓库 |
| `MSG_WRM_0007` | 未找到可用波次规则 | 仓库下无启用规则 |
| `MSG_WRM_0008` | 无可用格口 | 工作站下所有格口已被占用 |
| `MSG_WRM_0009` | 规则匹配到但无格口可用 | 当前规则下无空闲格口承接 |
| `MSG_WRM_0010` | 当前规则下无可匹配出库单 | 订单池无符合条件的出库单 |
| `MSG_WRM_0011` | 规则或工作站为空 | 内部校验 |
| `MSG_WRM_0012` | 工作站正在集波中 | Redis 锁获取失败，同一工作站同一出库类型正在集波 |

---

## 处理流程

```
POST /findBestMatchedRule
│
├─ 1. 参数校验 (warehouseCode / workStation / shipmentType)
│
├─ 2. 工作站校验 (存在 / 启用 / 仓库匹配)
│
├─ 3. Redis 锁 (仓库+工作站+出库类型, 防重复集波)
│
├─ 4. 查询启用波次规则 (按 wavePriority desc)
│
├─ [分支 A] 一波一单
│   └─ assignSingleShipmentWaves
│       按单拣选、销售任务、MTO 任务、RT 一单一波；RT 不使用 Shopee 波次号
│
├─ [分支 B] 已绑定 Shopee 波次格口补缺口
│   └─ fillShortageCells
│       仅处理空闲、已绑定 shopeeWaveCode、currentWaveRule 匹配当前规则的格口
│
└─ [分支 C] 空格口首次分配 (逐规则按优先级循环)
    │
    ├─ 5. 查可用格口 (三层级):
    │   ├─ sameCurRuleCells — currentWaveRule 同当前规则
    │   ├─ ruleCells — waveRule 配置匹配当前规则
    │   └─ emptyCells — 完全空闲格口
    │
    ├─ 6. 按规则查询出库单:
    │   ├─ 一波一单候选项 (pickType=1 / ticketType 任务 / RT)
    │   └─ 批量集波候选项 (排除一波一单)
    │
    ├─ 7. 创建内部波次 (WaveService#createInternalWave)
    │
    ├─ 8. 格口循环处理:
    │   ├─ 已有波次号 → 补单缺口判断
    │   └─ 无波次号 → 抢占 Shopee 波次号 (acquireUnusedShopeeWave)
    │
    ├─ 9. assignToCell:
│   ├─ pickCellShipmentIds — 按格口缺口截断候选单
    │   ├─ ensureCellReadyForWave — 先抢占格口 (USED)
    │   ├─ bindShipmentShopeeWave — ESS 绑定校验
    │   │   ├─ 成功 → 写入 shipment_header.shopeeWave，并更新 shopee_wave pickType/groupKey
    │   │   └─ 失败 → 写入 kickOutWave, 同波次后续过滤
│   └─ addShipmentsToInternalWave → WaveService#run
│
    └─ 10. 成功返回 ResponseMessageFactory.success()，不返回格口分配明细
```

---

## 核心规则

### 规则优先级

1. **上墙优先级** — `wave_rule.wavePriority`（数字越大越优先）
2. **抓单优先级**（同一规则内）：
   - `priority` 数字越大越优先
   - 同优先级按 `cutOffTime` 越早越优先
   - 同截单时间按 `purchaseTime` 越早越优先
   - 同购买时间按 `orderTime` 越早越优先
   - 同时间按 `id` 升序兜底

### 格口分配优先级

| 优先级 | 格口类型 | 条件 |
|--------|----------|------|
| 1（最高） | `sameCurRuleCells` | `currentWaveRule` 与当前规则相同 |
| 2 | `ruleCells` | `currentWaveRule` 为空且 `waveRule` 匹配当前规则 |
| 3（兜底） | `emptyCells` | `currentWaveRule` 和 `waveRule` 均为空 |

### 一波一单处理

以下单据类型按一波一单处理（每次集波只取 1 单，创建独立内部波次）：

| 类型 | 标识 | 说明 |
|------|------|------|
| 按单拣选 | `pickType=1` | 按单拣选专用 |
| 销售出库任务 | `shipmentType=XSCK` + `ticketType=1` | 一波一单 |
| MTO 任务 | `ticketType=5` | 一波一单 |
| RT | `ticketType=6` | 不使用 Shopee 波次号 |

### 字段匹配

通过数据字典 `WAVE_RULE_FIELD_MATCHING` 配置出库单字段与规则字段的匹配关系：

| 操作符 | 含义 | 示例 |
|--------|------|------|
| `IN` | 多值包含 | `userDef1 IN (channelId 多选值)` |
| `=` / `EQ` / `EQUAL` / `EQUALS` | 等值匹配 | `userDef7 = shopId` |
| `LE` / `<=` / `LESS_OR_EQUAL` | 小于等于 | `priority <= urgentFlag` |

**字段白名单**（防 SQL 拼接注入）：

- **出库单字段**（30+）：`shipmentType`、`ticketType`、`pickType`、`sourcePlatform`、`sourceErp`、`route`、`carrierCode`、`shipToCountry`、`userDef1-8` 等
- **规则字段**（16）：`channelId`、`fulfillmentChainId`、`orderSize`、`skuSizeType`、`categoryLevel1Id`、`shopGroup`、`shopId`、`urgentFlag`、`outerPackaging`、`deliveryRegion`、`fragile`、`liquid`、`highValue`、`battery`、`danger` 等

### 并发控制

Redis 分布式锁，锁 Key：`wave_rule_match:{warehouseCode}:{workstationId}:{shipmentType}`

同一工作站 + 同一出库类型同时只有一个请求能进入集波流程。

### 异常过滤

出库单必须同时满足以下条件才能进入抓单池：

- `leadingSts = 100`（订单池状态）
- `trailingSts = 100`（订单池状态）
- `processType = 'NORMAL'`（正常处理流程）
- `waveId is null or waveId = 0`（未加入任何波次）
- `lockCode is null or lockCode = ''`（未被锁定）
- `cancelTime is null or cancelTime = ''`（未取消）
- `holdTime is null or holdTime = ''`（未挂起）

---

## 补单机制（当前代码口径）

当前代码未保留后台计划任务 `pollReplenishment/pollReplenishmentOnce`。补缺口能力收口在首次集波入口中的 `fillShortageCells`，用于处理当前工作站已绑定 Shopee 波次且规则一致的空闲格口。

- **触发方式**：调用首次集波入口时顺带执行
- **扫描范围**：当前工作站 + 已绑定 Shopee 波次 + `currentWaveRule` 匹配的空闲格口
- **单品单件**：低于 `WAVE/low_threshold` 时触发，一次性按 `maxShipments` 抓单
- **非单品单件**：缺口 = `maxShipments - 当前波次已绑单量`
- **Flow Pick 满槽**：达到 `maxShipments` 时触发换箱换波
- **后台轮询**：如需恢复，需按最终调度策略重新接入计划任务入口

---

## 相关表

| 表名 | 说明 | 关键字段 |
|------|------|----------|
| `wave_rule` | 波次规则 | `code`, `waveType`, `wavePriority`, `maxShipments`, `status`, `channelId`, `shopId` 等 |
| `shopee_wave` | Shopee 波次号段 | `code`, `waveType`, `warehouseCode`, `pickType`, `groupKey`, `flow_pick`, `status` (100=未使用, 200=使用中, 900=使用完成) |
| `shipment_header` | 出库单头 | `shopeeWave`, `kickOutWave`, `ticketType`, `pickType`, `groupKey`, `orderStructure`, `waveId` |
| `wcs_sorting_wall_cell` | 分播墙格口 | `waveRule`, `currentWaveRule`, `shopeeWaveCode`, `useStatus` |
| `wcs_work_station_pick_task_header` | 拣货任务头 | `shopeeWave`, `waveRule`, `shipmentId` |
| `wave` | 内部波次 | `waveRule` |

---

## 测试与验证

推荐验证顺序：

1. **编译**：按需执行 `:wms-wave:compileGroovy`，本轮文档同步未执行
2. **静态检查**：确认无新增测试类和旧测试类引用残留
3. **手工验证**：使用实际 wave_rule 和出库单数据调用本接口，验证系统处理日志、格口占用和订单波次绑定
4. **ESS 联调**：验证 `bindShipmentShopeeWave` 中 ESS 绑定接口成功/失败结果
5. **补单验证**：确认 `fillShortageCells` 能补满已绑定 Shopee 波次格口且 `kickOutWave` 过滤生效
