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

> 对应实现：`WaveRuleMatchController#findBestMatchedRule` → `WaveRuleMatchService#findBestMatchedRuleByWorkStationAndOutboundType`
>
> 需求标准来源：`C:\work\gitlab\shopee\ttx-project-doc\集波\SHOPEE_010_出库集波V1.2.docx`（V1.4，2026-06-04 逻辑修改 / 匹单字段逻辑更新）。

---

## 概述

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

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

---

## 请求

### 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 波次号，走库内流程 |

### 标准要求补充

- `wave_rule.wavePriority` 先决定上墙优先级，`maxShipments` 决定抓单数量；即使高优规则订单不足，也要优先匹高优规则的单。
- 同一规则内抓单优先级按 `shipment_header.priority` 降序、`cutOffTime` 升序、`purchaseTime` 升序、`orderTime` 升序。
- `shipment_header.orderStructure` 若为空，出库单下发后需自动分析并写回：`SSSQ=1`、`SSAQ=2`、`MSAQ=3`。
- `wave_rule.waveType` 与 `shipment_header.orderStructure` 按 docx 表格做包含关系匹配，表中命名/编码有歧义处保留原文并按 docx 待确认。
- 匹单还要求 `shipment_header.groupKey` 一致才能集在一起。
- `multi_attr_list` 采用全包含匹配：规则值 `A,B,C` 对单据 `A,B` 可匹配；规则值 `B,C` 对单据 `A,B` 不匹配。
- `wcs_sorting_wall_cell.waveRule` 支持多选，多个规则逗号分隔。
- 工作站选择出库类型受用户权限控制，同一工作站不混做不同出库类型。
- 仅抓正常单据，取消/挂起/异常单不抓。
- `shipment_header.pickType=1/0` 仅允许在订单池 `100` 状态切换。
- `XSCK`、`MTO`、`RTS`、`RT` 的 `ticketType` 映射按 docx 口径分别为：销售任务 `1` / 销售订单 `2`，MTO 任务 `5` / MTO 订单 `4`，RTS `3`，RT `6`。
- 推拣货任务标准要求为：上游同步 wave 号段，WES 不走取号逻辑，1 拣货单 1 波次 1 槽口，不走补单。
- Flow Pick 标准要求覆盖 `SSSQ/MSSQ` 以及 RTS/MTO 下发单据；满槽换箱换波走 Flow Pick 换箱接口。
- 集波后需按单据类型取得 `shopee_wave` 并请求上游校验：销售 create_picking_task 走 3.2.7，RTS/MTO 走 3.3.6；失败订单后续通过补单/踢单处理。

---

## 响应

### 成功响应

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

> 标准要求：返回格口 + 命中规则 + 波次分配明细。
>
> 当前实现：成功时通常仅返回 `ResponseMessageFactory.success()`，不保证返回上述明细；若调用链未产出分配结果，请以 success 为准。

| 响应字段 | 类型 | 说明 |
|----------|------|------|
| `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 锁获取失败，同一工作站同一出库类型正在集波 |
| `MSG_WRM_0014` | 工作站已有其它出库类型作业 | 同一工作站已存在其它 `shipmentType` 的占用格口/波次 |

---

## 处理流程

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

---

## 核心规则

### 规则优先级

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

### V1.4 标准要求与当前实现

| 标准要求 | 当前实现/待实现 |
|----------|-----------------|
| 修改历史 V1.4：匹单字段逻辑更新 | 本文按 docx V1.4 同步 `waveType/orderStructure`、`groupKey`、规则字段匹配和新增字段说明 |
| `wave_rule` 字段按 WES 小驼峰与 base 字段命名规范维护 | 当前规则域已包含 `waveType`、`wavePriority`、`maxShipments` 及多项规则属性；主数据字段命名以代码和迁移为准 |
| 新增/强调 `Max SKU Pieces Per Order` | 标准要求：单订单任一 SKU 件数超过阈值时排除本规则；当前 `WaveRule`/匹单 SQL 未见对应规则字段，待实现 |
| 新增/强调 `Mix Mode Max SKU Pieces Filter` | 标准要求：仅 Mix-mode/MSAQ 规则使用，用于排除单 SKU 件数过高的混合订单；当前未见对应匹单实现，待实现 |
| 出库单 `orderStructure` 为空时自动分析 | 当前由订单明细分析后写入 `shipment_header.orderStructure`：`SSSQ=1`、`SSAQ=2`、`MSAQ=3` |
| 工作站出库类型权限控制 | 标准要求：登录工作站按用户权限下拉可选出库类型；当前服务已做同工作站不同出库类型隔离，用户权限绑定仍为待实现 |
| Shopee 波次号获取与上游合波校验 | 当前 `acquireUnusedShopeeWave` 抢占 `shopee_wave` 未使用号段；`requestShopeeWaveBind` 是上游校验预留入口，create_picking_task / RTS-MTO 真实接口契约仍待接入 |
| 库存最优分配与库存不足踢单 | 标准要求：效期/FIFO、工作站与库存点位半径优先、同半径低层货位优先；当前集波入口只触发 `WaveService#run`，库存不足踢单回池与上游撤单通知仍需在库存/回池链路接入 |

### 格口分配优先级

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

> 标准要求：`wcs_sorting_wall_cell.waveRule` 支持分播墙明细界面多选，一个格口可绑定多个 `wave_rule`，多个规则用逗号分隔存储；当前匹配按逗号拆分后判断是否包含当前规则编码。

### 一波一单处理

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

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

### 订单结构与 Flow Pick

| 项 | 标准要求 | 当前实现/说明 |
|----|----------|---------------|
| `waveType/orderStructure` 包含关系 | 按 docx 表：`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)` | 当前通过 `appendWaveTypeOrderStructureFilter` 固定过滤。`SSSQ(Same SKU same Qty)` 与 `SSSQ` 命名相近，按 docx 待确认 |
| `groupKey` | 同一 Shopee 波次只能集相同 `shipment_header.groupKey` 特征值订单 | 当前首次抓单按首个候选单确定 `groupKey`，补单按 `shopee_wave.groupKey` 过滤 |
| Flow Pick | 标准定义：SSSQ/MSSQ 为 Flow Pick，RTS/MTO 下发单据也按 Flow Pick 处理 | 当前已有 Flow Pick 换箱换波本地流程与接口占位，真实 ESS/AGV 接口待接入 |
| 推拣货任务 | 上游推送 Shopee 波次号，WES 不走取号逻辑；1 拣货任务 / 1 波次 / 1 槽口；不走补单 | 当前普通批量已过滤 `pickType=2`，专用分流入口待恢复 |

### 字段匹配

通过数据字典 `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`、`outerPackagingType`、`deliveryRegion`、`fragile`、`liquid`、`highValue`、`battery`、`danger` 等

补充口径：

- 标准要求：只对 `wave_rule` 中对应订单类型列标记 `Y` 的字段做匹配；SKU 来源字段需与单据所有明细 SKU 主档字段全部匹配。
- 标准要求：`multi_attr_list` 多值字段匹配时按 docx 示例为“规则值全包含单据值”，例如规则 `A,B,C` 可匹配单据 `A,B`，规则 `B,C` 不匹配单据 `A,B`；该包含方向按 docx 待确认。
- 当前实现：逗号多值字段目前按任一值命中生成条件，`multi_attr_list` 全包含匹配仍待调整。

### 并发控制

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` 等；V1.4 标准新增 `Max SKU Pieces Per Order`、`Mix Mode Max SKU Pieces Filter` 待实现 |
| `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` 过滤生效
