diff --git a/SHOPEE_011_AGV出库分拣_开发计划.md b/SHOPEE_011_AGV出库分拣_开发计划.md
index ca8a09b..07bb9b1 100644
--- a/SHOPEE_011_AGV出库分拣_开发计划.md
+++ b/SHOPEE_011_AGV出库分拣_开发计划.md
@@ -4,7 +4,7 @@
## 1. 目标
-实现 Shopee AGV 出库分拣:工作站登录 AGV 分拣模式后,系统把已释放的 AGV 区域拣货任务分配到分拣墙,按工作站 AGV 并发量下发周转箱搬运任务;周转箱到站后,操作员按批量或按单首选项扫描分拣,通过电子标签或手工确认完成格口任务,支持绑箱、满箱、封箱、缺料重分配和回库调度。
+实现 Shopee AGV 出库分拣:工作站登录 AGV 分拣模式后,系统把已释放的 AGV 区域拣货任务分配到分拣墙,按工作站 AGV 并发量下发周转箱搬运任务;周转箱到站后,操作员按批量或按单首选项扫描分拣,通过电子标签或手工确认完成格口任务,支持绑箱、满箱、缺料重分配、三段式完成回传和回库调度。
## 2. 总体模块拆分
@@ -12,7 +12,7 @@
| --- | --- | --- | --- |
| P0 | 数据模型与配置 | `agv_preference`、`wcs_sorting_wall` AGV 分拣字段、工作站 AGV 并发量、菜单/字典配置 | 部分完成:`agv_preference` 建表/domain/table JSON/bill JSON 已落地 |
| P1 | 工作站 AGV 分拣入口 | 登录工作站、开始/退出/暂停/继续、出库单类型选择 | 部分完成:已存在 `AgvSortingService` 统一服务骨架 |
-| P1 | 分拣墙箱绑定 | 绑箱、满箱、封箱、缺料按钮 | 待实现 |
+| P1 | 分拣墙箱绑定 | 绑箱、满箱、缺料按钮;删除独立封箱按钮 | 待实现 |
| P1 | 任务分配计划任务 | 拣货任务分配到分拣墙,绑定上游波次号和分拣墙 | 待实现 |
| P1 | AGV 下发计划任务 | 控制工作站未完成 AGV 数量,生成/下发 `wcs_dispatch_task` | 待实现 |
| P1 | 扫箱分拣服务 | 扫周转箱、按首选项展示批量/按单分拣信息 | 待实现 |
@@ -110,6 +110,23 @@ docx 多次提到任务头自定义字段:
建议先确认任务头“自定义1-4”在当前数据库实际列名,再决定字段落点。
+### 3.4 扩展 `shopee_wave` 格口/设备释放标记
+
+新增字段 `deviceReleaseSts int not null default 0`,用于记录 3.1.9 回传完成后的格口/设备释放结果:
+
+| 值 | 状态 | 处理规则 |
+| ---: | --- | --- |
+| `0` | 待释放 | 3.1.9 尚未成功,或成功后等待执行释放 |
+| `1` | 已释放 | 格口/设备释放和灭灯均处理成功 |
+| `-1` | 释放失败 | 保留现场绑定,由释放任务重试 |
+
+`completeTaskUploadSts` 继续只表示 3.1.9 的 `confirm/get/uploadStatus` 回传状态,不与 `deviceReleaseSts` 合并。`uploadStatus` 成功后触发释放;释放结果单独写入 `deviceReleaseSts`,避免上游回传成功但现场释放失败时丢失重试依据。
+
+计划文件:
+- 修改数据库迁移,给 `shopee_wave` 增加 `deviceReleaseSts`。
+- 修改 `ShopeeWave.groovy` 和 `shopee_wave.json`,补充字段及状态说明。
+- 三段式回传成功后的释放处理必须同时满足 `completeTaskUploadSts=SUCCESS` 和 `deviceReleaseSts in (0, -1)`,避免默认值为 `0` 的未完成波次被提前释放。
+
## 4. 后端服务计划
### 4.1 AGV 分拣工作站服务(部分完成)
@@ -137,10 +154,11 @@ docx 多次提到任务头自定义字段:
接口:
- `bindBox(session, warehouseCode, sortingWallCode, lpn)`:校验分拣墙存在、`lpn` 为空、调用 WMS 周转箱检查接口、回写 `wcs_sorting_wall.lpn`。
-- `replaceFullBox(session, warehouseCode, sortingWallCode, oldLpn, newLpn)`:满箱换箱;docx 此处描述仍写“检查可用并更新 LPN”,需确认是否要先封箱旧箱。
-- `sealBox(session, warehouseCode, sortingWallCode)`:要求 `syStatus=6`,清空 `taskId/lpn`,`syStatus=0`,下发灭灯。
+- `replaceFullBox(session, warehouseCode, sortingWallCode, oldLpn, newLpn)`:满箱换箱并更新 LPN,不调用独立封箱接口。
- `shortage(session, warehouseCode, sortingWallCode)`:二次确认后触发缺料重分配、缺料盘点单、继续显示下一货品或回库。
+删除 `sealBox/sealDevice` 独立封箱入口。完成拣货改为参考接口 3.1.9 的 `confirm -> get -> uploadStatus` 三段式回传;仅在 `uploadStatus` 成功后释放格口/设备并下发灭灯,释放结果写入 `shopee_wave.deviceReleaseSts`,失败时保留原绑定等待重试。
+
依赖待确认:
- 上游 WMS 周转箱检查接口。
- 电子标签灭灯接口。
@@ -306,8 +324,8 @@ docx 多次提到任务头自定义字段:
- 暂停/继续:切换 `acceptStatus`。
- 绑箱:扫货架号 + 周转箱号。
- 满箱:扫货架号 + 周转箱号。
-- 封箱:扫货架号。
- 缺料:二次确认后触发缺料流程。
+- 不显示封箱按钮,不提供封箱弹框或扫描货架号操作。
分拣区:
- 左侧:当前周转箱格口示意,支持 1/2/4/6/8 格,当前格口放大蓝色显示。
@@ -325,7 +343,7 @@ docx 多次提到任务头自定义字段:
待实现:
- 页面真实数据绑定。
- AGV 首选项弹窗/选择逻辑。
-- 开始/退出/暂停/绑箱/满箱/封箱/任务确认按钮接口联动。
+- 开始/退出/暂停/绑箱/满箱/任务确认按钮接口联动;删除封箱按钮逻辑。
## 6. 接口清单
@@ -334,6 +352,9 @@ docx 多次提到任务头自定义字段:
| 周转箱检查 | WES -> 上游 WMS | 待接口 |
| AGV 任务创建 | WES -> WCS/AGV | 可复用现有 `AgvTaskCreateWcsCmd`,需补出库分拣参数 |
| AGV 任务完成回调 | WCS/AGV -> WES | 现有回调需确认是否覆盖 |
+| 完成拣货 confirm | EDI -> WES | 参考 3.1.9,返回待回传任务 ID 列表并锁定批次 |
+| 完成拣货 get | EDI -> WES | 参考 3.1.9,按任务 ID 返回完整拣货明细 |
+| 完成拣货 uploadStatus | EDI -> WES | 参考 3.1.9,回写成功/失败;成功后触发释放并更新 `deviceReleaseSts` |
| AGV 任务取消/回库 | WES -> WCS/AGV | 可复用 `AgvTaskCancelWcsCmd`,回库任务需确认 |
| 电子标签亮红灯 | WES -> WCS | 待接口 |
| 电子标签灭灯 | WES -> WCS | 待接口 |
@@ -372,11 +393,11 @@ docx 多次提到任务头自定义字段:
- 已完成:`AgvPreferenceService` 通用 CRUD 服务。
- 待实现:用户权限过滤和默认首选项选择业务逻辑。
-### Task 4:分拣墙绑箱/满箱/封箱/缺料
+### Task 4:分拣墙绑箱/满箱/缺料
- 新增 `AgvSortingWallBoxService`。
- 实现货架校验、LPN 校验、周转箱检查接口占位。
-- 实现封箱清空和灭灯接口占位。
+- 删除封箱按钮及独立封箱接口,格口清理和灭灯由三段式完成回传成功状态触发。
- 实现缺料入口:先记录待重分配和待盘点,等接口确认后补全。
### Task 5:波次任务分配到分拣墙
@@ -436,7 +457,7 @@ docx 多次提到任务头自定义字段:
- AGV 分拣首选项页面。
- 已完成:AGV 分拣工作台静态页面和自适应样式。
- 工作站登录/开始/暂停/退出操作。
-- 绑箱/满箱/封箱/缺料弹框。
+- 绑箱/满箱/缺料弹框;不提供封箱按钮和弹框。
- 分拣动态图和电子标签/手工确认交互。
- 按单模式增加交付阶段按钮和 PTL 分播口状态展示。
@@ -465,7 +486,7 @@ docx 多次提到任务头自定义字段:
| Task 1 | 数据模型与配置落地 | 2 - 3 | 1 | 0 - 1 | 3 - 5 | 包含迁移、domain/service、表单配置和常量 |
| Task 2 | 工作站 AGV 分拣入口 | 1 - 2 | 1 | 0 - 1 | 2 - 4 | currentJobMode、acceptStatus、系统日志 |
| Task 3 | AGV 分拣首选项 | 1 - 2 | 1 - 2 | 0 - 1 | 2 - 5 | CRUD、默认首选项、权限过滤 |
-| Task 4 | 分拣墙绑箱/满箱/封箱/缺料入口 | 2 - 3 | 1 - 2 | 1 | 4 - 6 | 周转箱检查、灭灯接口先可占位 |
+| Task 4 | 分拣墙绑箱/满箱/缺料入口 | 2 - 3 | 1 - 2 | 1 | 4 - 6 | 删除封箱入口,周转箱检查、灭灯接口先可占位 |
| Task 5 | 波次任务分配到分拣墙 | 3 - 4 | 0 | 1 | 4 - 5 | 包含 Redis 锁、任务头/分拣墙回写 |
| Task 6 | AGV 任务下发 | 2 - 3 | 0 | 1 - 2 | 3 - 5 | 复用现有 dispatch task,需确认 AGV 参数 |
| Task 7 | 扫箱与分拣展示 | 3 - 5 | 2 - 3 | 1 - 2 | 6 - 10 | 批量/按单查询差异、SKU/UID 扫描 |
diff --git a/出库单接口文档/Shopee Automation Vendor 接入协议手册出库模块映射WES字段V1.2.1.docx b/出库单接口文档/Shopee Automation Vendor 接入协议手册出库模块映射WES字段V1.2.1.docx
new file mode 100644
index 0000000..23c022a
Binary files /dev/null and b/出库单接口文档/Shopee Automation Vendor 接入协议手册出库模块映射WES字段V1.2.1.docx differ
diff --git a/出库单接口文档/shopee-3.2.13-update-order-test-cases.md b/出库单接口文档/shopee-3.2.13-update-order-test-cases.md
new file mode 100644
index 0000000..0b35e78
--- /dev/null
+++ b/出库单接口文档/shopee-3.2.13-update-order-test-cases.md
@@ -0,0 +1,376 @@
+# Shopee 3.2.13 部分更新销售订单接口测试用例
+
+## 接口信息
+
+- 接口:3.2.13 WMS -> WES 部分更新销售订单
+- Shopee URL:`POST /api/v2/automation/tovendor/outbound/salesorder/update_order`
+- WES 本地入口:`POST http://127.0.0.1:9001/api/wms/api/sync/in`
+- Content-Type:`application/json`
+- EDI 配置 key:`update_order`
+- WMS API:`shopee.outbound.salesorder.updateOrder`
+- XSLT:`edi-shopee/src/main/resources/xslt/out/3_2_13_update_order.xslt`
+- 测试日期:2026-07-28
+- 测试仓库:`PHIXP`
+
+## 测试范围
+
+1. EDI XSLT 将 Shopee 原始字段转换为 WES 请求字段。
+2. WES 必填字段校验和订单存在性校验。
+3. `single_attr_list` 单选属性解析、订单扩展字段更新和属性字典同步。
+4. `multi_attr_list` 多选属性解析、订单扩展字段更新和属性字典同步。
+5. 相同请求重复执行时,属性字典不产生重复记录。
+6. 测试数据清理和订单原值恢复。
+
+本次请求格式以《Shopee Automation Vendor 接入协议手册出库模块映射WES字段V1.2.1》3.2.13 章节为准。协议请求使用 snake_case 字段;EDI XSLT 转换后,WES 内部请求使用 camelCase 字段。
+
+本次未调用 Shopee 外部测试环境。测试按“协议请求 -> 本地 XSLT 转换 -> WES `9001` 内部接口 -> 数据库核验”拆分执行,因此报告同时保留协议请求和 WES 实测响应。Shopee 对外响应协议需在 EDI 对外路由可用后另行联调。
+
+## 前置条件
+
+1. WES 服务已在 `9001` 端口启动。
+2. 数据库为 `ttx-xwms-test`,仓库为 `PHIXP`。
+3. 成功路径测试订单:`OBSGD0002607281457`。
+4. `config_detail` 已维护以下 `SHOPEE_ORDER_HEADER` 属性组:
+ - `service_code`
+ - `shop_id`
+ - `inner_packaging`
+5. WES 内部接口使用请求头 `X-DB: ttx-xwms-test`。
+
+## 字段转换说明
+
+| Shopee 字段 | WES 字段 | 数据库字段 |
+| --- | --- | --- |
+| `whs_id` | `warehouseCode` | `shipment_header.warehouseCode` |
+| `order_number` | `orderNumber` | `shipment_header.erpOrderCode` |
+| `single_attr_list.service_code` | `serviceCode` | `shipment_header_ext1.serviceCode` |
+| `multi_attr_list.shop_id` | `shopId`、`shopIdStr` | `shipment_header_ext1.shopId`、`shopIdStr` |
+| `multi_attr_list.inner_packaging` | `innerPackaging`、`innerPackagingType` | `shipment_header_ext1.innerPackaging`、`innerPackagingType` |
+| `single_attr_list` | `singleAttrList` | `config_detail` 单选属性字典 |
+| `multi_attr_list` | `multiAttrList` | `config_detail` 多选属性字典 |
+
+## 3.2.13 标准请求格式
+
+以下结构依据 V1.2.1 协议 3.2.13 章节。`whs_id`、`order_number` 必填,其余字段按“有传则更新,未传不更新”处理。
+
+```json
+{
+ "whs_id": "PHIXP",
+ "order_number": "OBSGD0002607281457",
+ "can_group_picking": 1,
+ "group_key": "standard_group",
+ "urgent_flag": 99,
+ "cut_off_time": 1767837541,
+ "ctime": 1767751167,
+ "ship_by_date": 1767751167,
+ "purchase_time": 1767751167,
+ "single_attr_list": [
+ {
+ "attr_key": "service_code",
+ "attr_value_id": "STANDARD",
+ "attr_value_type": "",
+ "attr_value_name": "STANDARD"
+ }
+ ],
+ "multi_attr_list": [
+ {
+ "attr_key": "shop_id",
+ "attr_value_list": [
+ {
+ "attr_value_id": "12345678",
+ "attr_value_type": "",
+ "attr_value_name": "Electronics Store SG"
+ },
+ {
+ "attr_value_id": "87654321",
+ "attr_value_type": "",
+ "attr_value_name": "Fashion Store SG"
+ }
+ ]
+ },
+ {
+ "attr_key": "inner_packaging",
+ "attr_value_list": [
+ {
+ "attr_value_id": "CONS-BUBBLE-S",
+ "attr_value_type": "3",
+ "attr_value_name": "Small Bubble Wrap"
+ },
+ {
+ "attr_value_id": "CONS-TAPE-01",
+ "attr_value_type": "3",
+ "attr_value_name": "Sealing Tape"
+ }
+ ]
+ }
+ ]
+}
+```
+
+协议允许的单选属性包括 `service_code`、`channel_id`、`fulfillment_chain_id`、`order_structure`、`order_size`、`shop_group`、`store_id`、`parcel_id`、`lm_tracking_no`、`pickup_region`、`delivery_region`、`sls_tracking_no`、`actual_weight`、`lane_code`、`handover`、`zone_code_id`、`outer_packaging`。
+
+协议允许的多选属性包括 `shop_id`、`shopee_order_sn`、`inner_packaging`。
+
+## 测试结果汇总
+
+| 用例 | 场景 | 预期结果 | 实测结果 |
+| --- | --- | --- | --- |
+| TC-01 | 空请求体 | 返回“无效的消息体” | 通过 |
+| TC-02 | 订单不存在 | 按协议返回成功 | **不通过:WES 返回“出库单不存在”** |
+| TC-03 | 现有订单,不传更新字段 | 返回成功且不修改业务字段 | 通过 |
+| TC-04 | XSLT 转换单选和多选属性 | 生成合法 JSON,字段和值完整 | 通过 |
+| TC-05 | `single_attr_list` 使用现有字典值 | 更新 `serviceCode`,字典记录保持唯一 | 通过 |
+| TC-06 | `multi_attr_list` 使用现有字典值 | 更新店铺和内包装字段,字典记录保持唯一 | 通过 |
+| TC-07 | 新单选、多选枚举增量同步 | 新增 1 条单选、2 条多选字典记录 | 通过 |
+| TC-08 | 相同属性请求重复执行 | 接口均成功,字典不重复 | 通过 |
+| TC-09 | 测试数据恢复与清理 | 订单恢复原值,临时字典记录为 0 | 通过 |
+
+## TC-01 空请求体
+
+协议请求:
+
+```http
+POST /api/v2/automation/tovendor/outbound/salesorder/update_order
+Content-Type: application/json
+
+{}
+```
+
+协议预期:请求无效。
+
+WES 内部实测结果:
+
+```json
+{"code":"1","msg":"无效的消息体","notify":true,"error":true}
+```
+
+结论:必填字段校验生效。
+
+## TC-02 订单不存在
+
+协议请求体:
+
+```json
+{
+ "whs_id": "PHIXP",
+ "order_number": "CODEX-3-2-13-NOT-EXIST"
+}
+```
+
+协议预期:订单不存在时返回成功。
+
+WES 内部实测结果:
+
+```json
+{"code":"1","msg":"出库单不存在","notify":true,"error":true}
+```
+
+结论:按仓库和订单号查询生效,但返回行为不符合 3.2.13 协议“若订单不存在返回成功即可”的要求,记录为待修复问题。
+
+## TC-03 现有订单无更新字段
+
+协议请求体:
+
+```json
+{
+ "whs_id": "PHIXP",
+ "order_number": "202607280002_0"
+}
+```
+
+预期及实测结果:
+
+```json
+{"code":"0","msg":"Success","notify":true,"error":false}
+```
+
+结论:订单存在时成功返回;未携带扩展字段和属性列表,不修改订单业务字段。
+
+## TC-04 EDI XSLT 属性转换
+
+协议 JSON 进入 EDI 后转换成供 XSLT 处理的中间 XML;本次用于验证的关键中间 XML:
+
+```xml
+
+ OBSGD0002607281457
+
+
+ service_code
+ STANDARD
+ STANDARD
+
+
+
+
+ shop_id
+
+ 12345678Electronics Store SG
+ 87654321Fashion Store SG
+
+
+
+ inner_packaging
+
+ CONS-BUBBLE-S3
+ CONS-TAPE-013
+
+
+
+
+```
+
+实测转换结果摘要:
+
+```json
+{
+ "serviceCode": "STANDARD",
+ "shopId": "[\"12345678\",\"87654321\"]",
+ "shopIdStr": "12345678,87654321",
+ "innerPackaging": "CONS-BUBBLE-S,CONS-TAPE-01",
+ "innerPackagingType": "3,3",
+ "singleAttrCount": 1,
+ "multiAttrCount": 2,
+ "shopValueCount": 2,
+ "innerValueCount": 2
+}
+```
+
+结论:转换结果是合法 JSON,单选、多选列表及派生字段完整。
+
+## TC-05 single_attr_list 现有字典值
+
+协议请求关键字段:
+
+```json
+{
+ "whs_id": "PHIXP",
+ "order_number": "OBSGD0002607281457",
+ "single_attr_list": [
+ {
+ "attr_key": "service_code",
+ "attr_value_id": "STANDARD",
+ "attr_value_type": "",
+ "attr_value_name": "STANDARD"
+ }
+ ]
+}
+```
+
+实测结果:
+
+- 接口返回 `code=0`。
+- `shipment_header_ext1.serviceCode = STANDARD`。
+- `config_detail` 中 `service_code / STANDARD` 数量为 `1`。
+- 字典说明为 `STANDARD`。
+
+## TC-06 multi_attr_list 现有字典值
+
+协议请求关键字段:
+
+```json
+{
+ "whs_id": "PHIXP",
+ "order_number": "OBSGD0002607281457",
+ "multi_attr_list": [
+ {
+ "attr_key": "shop_id",
+ "attr_value_list": [
+ {"attr_value_id":"12345678","attr_value_type":"","attr_value_name":"Electronics Store SG"},
+ {"attr_value_id":"87654321","attr_value_type":"","attr_value_name":"Fashion Store SG"}
+ ]
+ },
+ {
+ "attr_key": "inner_packaging",
+ "attr_value_list": [
+ {"attr_value_id":"CONS-BUBBLE-S","attr_value_type":"3","attr_value_name":"Small Bubble Wrap"},
+ {"attr_value_id":"CONS-TAPE-01","attr_value_type":"3","attr_value_name":"Sealing Tape"}
+ ]
+ }
+ ]
+}
+```
+
+数据库实测结果:
+
+```json
+{
+ "shopId": "[\"12345678\", \"87654321\"]",
+ "shopIdStr": "12345678,87654321",
+ "innerPackaging": "CONS-BUBBLE-S,CONS-TAPE-01",
+ "innerPackagingType": "3,3"
+}
+```
+
+字典核验:4 个多选枚举的记录数量均为 `1`,名称和 `attrValueType` 正确。
+
+## TC-07 新枚举增量同步
+
+测试值:
+
+| 属性类型 | groupType | identifier | description | value1 |
+| --- | --- | --- | --- | --- |
+| 单选 | `service_code` | `CODEX3213_SC_20260728` | `Codex 3.2.13 single test` | `test` |
+| 多选 | `shop_id` | `CODEX3213_SHOP_A` | `Codex 3.2.13 multi A` | `test` |
+| 多选 | `shop_id` | `CODEX3213_SHOP_B` | `Codex 3.2.13 multi B` | `test` |
+
+实测结果:
+
+- 接口返回 `code=0`。
+- 订单 `serviceCode`、`shopId`、`shopIdStr` 更新为请求值。
+- `config_detail` 新增 3 条记录,字段内容与请求一致。
+
+## TC-08 重复请求幂等性
+
+执行方式:连续两次发送 TC-07 的相同请求。
+
+两次接口响应:
+
+```json
+[
+ {"attempt":1,"http":200,"response":{"code":"0","msg":"Success","notify":true,"error":false}},
+ {"attempt":2,"http":200,"response":{"code":"0","msg":"Success","notify":true,"error":false}}
+]
+```
+
+数据库核验:
+
+- `CODEX3213_SC_20260728`:`count = 1`
+- `CODEX3213_SHOP_A`:`count = 1`
+- `CODEX3213_SHOP_B`:`count = 1`
+
+结论:重复请求不会生成重复属性字典记录。
+
+## TC-09 数据恢复和清理
+
+清理动作:
+
+1. 通过 3.2.13 接口将订单字段恢复为测试前的值。
+2. 先按仓库、记录类型和测试 identifier 查询临时记录 ID。
+3. 仅按查询到的主键 `2444`、`2445`、`2446` 删除临时测试记录。
+4. 再次查询订单字段和临时记录数量。
+
+最终核验结果:
+
+```json
+{
+ "restoreResponse": {"code":"0","msg":"Success","notify":true,"error":false},
+ "deleted": 3,
+ "remaining": 0,
+ "order": {
+ "serviceCode": "STANDARD",
+ "shopId": "[\"12345678\", \"87654321\"]",
+ "shopIdStr": "12345678,87654321",
+ "innerPackaging": "CONS-BUBBLE-S,CONS-TAPE-01",
+ "innerPackagingType": "3,3"
+ }
+}
+```
+
+结论:测试订单已恢复,临时测试字典无残留。
+
+## 测试结论
+
+3.2.13 接口的 XSLT 转换、单选属性、多选属性、属性字典新增、已有字典复用和重复请求去重均通过。
+
+发现 1 项协议偏差:V1.2.1 协议要求订单不存在时返回成功,当前 WES 实现返回“出库单不存在”。该场景 TC-02 判定为不通过,需调整实现后复测。
+
+本次未执行 Gradle 静态编译或自动化测试任务;结论来自 2026-07-28 的实际 HTTP 调用、XSLT 转换和数据库核验结果。
diff --git a/拣货/SHOPEE_011_AGV出库分拣V1.1.md b/拣货/SHOPEE_011_AGV出库分拣V1.1.md
index 05e6504..16a3206 100644
--- a/拣货/SHOPEE_011_AGV出库分拣V1.1.md
+++ b/拣货/SHOPEE_011_AGV出库分拣V1.1.md
@@ -65,6 +65,18 @@ operator_skills
| inventoryThreshold | 库存阈值-出库分拣使用 | int | | Y | 0 |
| 其他base预留字段等 | | | | | |
+Shopee 波次释放标记
+
+在 `shopee_wave` 新增 `deviceReleaseSts`,与 3.1.9 三段式回传字段 `completeTaskUploadSts` 分开维护。
+
+| 字段名 | 含义 | 类型 | 非空 | 默认值 | 状态说明 |
+| --- | --- | --- | --- | --- | --- |
+| `deviceReleaseSts` | 格口/设备释放状态 | int | Y | 0 | `0` 待释放、`1` 已释放、`-1` 释放失败 |
+
+- `completeTaskUploadSts` 仅表示 3.1.9 回传状态,不直接代表格口/设备已经释放。
+- `uploadStatus` 回写 3.1.9 成功后执行格口/设备释放;释放成功写 `deviceReleaseSts=1`,失败写 `deviceReleaseSts=-1` 并进入释放重试。
+- 释放任务必须同时满足 `completeTaskUploadSts=SUCCESS` 和 `deviceReleaseSts in (0, -1)`,不能只按释放标记查询。
+
## 实现状态 / TODO
### 第一阶段后端闭环已落地
@@ -75,9 +87,9 @@ operator_skills
- `exit`
- `assignShopeeWaveToAcceptedWorkStations`
- 已复用现有 `WcsWorkStationService.updateAcceptStatus` 做暂停/继续接单。
-- 已复用现有 `WaveRuleMatchService.findBestMatchedRuleByWorkStationAndOutboundType` 做分拣墙空闲格口的 Shopee 集波分配。
+- 自动集波通过 `WaveRuleMatchService.matchByStation` 完成分拣墙空闲格口的 Shopee 集波分配。
- 已在绑箱/满箱后端校验格口必须已有 `shopeeWaveCode`,避免未分配波次的格口绑定箱号。
-- 已在封箱后清理现有真实字段:`containerCode`、`shopeeWaveCode`、`currentWaveRule`、`useStatus`。
+- 工作站登录及 AGV 分拣页面不提供独立封箱按钮;完成拣货统一进入参考接口 3.1.9 的三段式回传流程。
### 第一阶段的最小承载策略
@@ -94,7 +106,7 @@ operator_skills
- 换箱场景仅补了基础后端限制:
- `ticketType=5` 的 MTO 任务拒绝换箱
- Flow Pick 换波次逻辑待确认是否复用现有集波服务,否则需后续补充
-- 封箱后的电子标签灭灯仍保留 TODO,待确认现场 PTL 服务接口后接入。
+- 三段式回传成功后的格口释放和电子标签灭灯仍保留 TODO,待确认现场 PTL 服务接口后接入。
- 任务确认阶段的 `targetContainer` 承载字段本轮未新增。
- 如后续确认现有任务明细已有合适字段,可优先复用;否则再补迁移。
@@ -178,15 +190,15 @@ A点击开始按钮,弹框选择出库单类型。弹框显示的内容为出
- 补充:出库单ticketType=5(即MTO下发任务类型)的不允许换箱,如果点换箱时出库单头ticketType=5则提示:MTO非需求池类型不允许换箱
-- G.封箱:点击封箱,弹框显示(该波次全部拣完的情况)
+- G.完成拣货回传:删除工作站登录及 AGV 分拣页面的封箱按钮、封箱弹框和扫描货架号逻辑,不再提供独立封箱接口。
-- 货架号:XXXXXXX
+完成拣货参考接口 3.1.9,采用 `confirm -> get -> uploadStatus` 三段式回传:
-- 扫描货架号,判断必须在wcs_sorting_wall_cell.containerCode存在,若不存在,则报错
+1. `confirm`:按仓库查询待回传任务,锁定本批任务并返回任务 ID 列表。
+2. `get`:按任务 ID 返回本任务完整拣货明细,重试时必须保持报文一致。
+3. `uploadStatus`:回写本次回传成功或失败;失败进入回传重试,成功后再处理 `deviceReleaseSts`,释放对应格口/设备并执行灭灯。
-另外wcs_sorting_wall_cell的useStatus使用必须=’ USED’ 绑定状态,且当前槽口绑定的波次必须全部完成(task_detail.cellCode槽口字段,task_detail. shopeeWave波次字段),则将当前格口wcs_sorting_wall_cell.containerCod清空,useStatus更新为‘IDLE’空闲,还需下发当前格口灭灯指令。(之前下发过亮绿灯指令)
-
-G:封箱后面增加缺料按钮
+缺料按钮保留在分拣操作区:
点击按钮,弹框进行二次确认:是否确定短拣并进行重新分配,点击确定更新出库单明细短拣数量shortPickQty,则拿当前周转箱货品+批次的订单需要重新分配(根据短缺数量shortPickQty进行再跑对应波次分配库存,如果分配成功则按集波逻辑进行更新波次号、槽口号等,前端提示分配成功,继续进行下面的作业,如果分配失败则前端提示无可用库存已确认短拣)。
@@ -266,4 +278,4 @@ H、订单分拣完成
任务确认的时候,判断wcs_sorting_wall_cell.shopeeWave分拣墙格口的任务是否完成,若整个任务分拣完成,则下发当前格口电子标签绿灯,useStatus更新‘PENDING_OUT’待离。
-UI分拣界面需要监测到电子标签拍灯接口,若报文返回的分拣墙货位wcs_sorting_wall_cell表的useStatus=‘PENDING_OUT’待离,则为封箱,将当前格口shopeeWave,containerCode清空,useStatus更新为‘IDLE’空闲
+UI 不再通过封箱按钮释放格口。任务完成后等待 3.1.9 三段式回传结果;`uploadStatus` 成功后处理 `deviceReleaseSts`,释放成功时清空当前格口 `shopeeWaveCode`、`containerCode`,将 `useStatus` 更新为 `IDLE` 并写 `deviceReleaseSts=1`;释放失败时保留现场绑定并写 `deviceReleaseSts=-1`,供释放任务重试。
diff --git a/蓝图/ISGB2510049_Shopee_PHIXP_FDS_v0.9.1 2026May5_2_中文.docx b/蓝图/ISGB2510049_Shopee_PHIXP_FDS_v0.9.1 2026May5_2_中文.docx
new file mode 100644
index 0000000..0c5d296
Binary files /dev/null and b/蓝图/ISGB2510049_Shopee_PHIXP_FDS_v0.9.1 2026May5_2_中文.docx differ
diff --git a/运维/AnyTLS-go服务器安装指南.md b/运维/AnyTLS-go服务器安装指南.md
new file mode 100644
index 0000000..5295fdc
--- /dev/null
+++ b/运维/AnyTLS-go服务器安装指南.md
@@ -0,0 +1,310 @@
+# AnyTLS-go 备用服务器安装指南
+
+## 1. 文档信息
+
+| 项目 | 内容 |
+| --- | --- |
+| 目标 | 在 Linux 服务器上安装 AnyTLS-go,作为 VLESS + REALITY 主入口之外的备用入口 |
+| 适用架构 | x86_64/amd64、aarch64/arm64 |
+| 示例版本 | `v0.0.13` |
+| 默认端口 | `8443/TCP` |
+| 更新日期 | 2026-07-25 |
+
+本文是 [VLESS + REALITY 与 AnyTLS 双协议部署方案](VLESS-Reality与AnyTLS双协议部署指南.md) 中 AnyTLS 备用入口的详细安装步骤。安装的是 [`anytls/anytls-go`](https://github.com/anytls/anytls-go) 参考服务端实现,默认独立监听 `8443/TCP`,不与监听 `443/TCP` 的 Xray 服务互相转发。执行前,将文中的端口、服务器地址和密码按实际环境填写。
+
+> **重要限制**:官方将 AnyTLS-go 定位为协议参考实现,而不是功能完备的生产级服务端。当前 `anytls-server` 会在每次启动时生成一个短期自签名证书,不支持通过命令行加载正式证书。客户端需要允许该实现对应的不安全证书校验方式。若生产环境要求校验证书、域名和 SNI,应改用支持 AnyTLS 的 sing-box 服务端方案。
+
+## 2. 部署参数
+
+安装前确认以下参数:
+
+| 参数 | 示例 | 说明 |
+| --- | --- | --- |
+| 服务器公网地址 | `` | 客户端连接地址 |
+| 服务端口 | `8443` | 必须为未占用的 TCP 端口 |
+| 密码 | `` | 建议使用至少 32 字节随机值 |
+| CPU 架构 | `amd64` 或 `arm64` | 由 `uname -m` 确认 |
+
+同时确认云平台安全组、主机防火墙和上游网络均允许所选 TCP 端口入站。
+
+## 3. 前置检查
+
+登录服务器后执行:
+
+```bash
+uname -m
+cat /etc/os-release
+sudo ss -lntp 'sport = :8443'
+```
+
+架构对应关系:
+
+| `uname -m` 输出 | 发布包架构 |
+| --- | --- |
+| `x86_64` | `amd64` |
+| `aarch64` 或 `arm64` | `arm64` |
+
+若端口检查有输出,说明 `8443` 已被占用,应先选择其他端口。
+
+安装依赖,按服务器发行版选择一组命令:
+
+```bash
+# Debian / Ubuntu
+sudo apt-get update
+sudo apt-get install -y curl unzip openssl
+```
+
+```bash
+# RHEL / Rocky Linux / AlmaLinux
+sudo dnf install -y curl unzip openssl
+```
+
+## 4. 下载并安装
+
+以下命令固定安装 `v0.0.13`,并使用 GitHub Release 提供的 SHA-256 摘要校验安装包:
+
+```bash
+ANYTLS_VERSION=0.0.13
+
+case "$(uname -m)" in
+ x86_64)
+ ANYTLS_ARCH=amd64
+ EXPECTED_SHA256=7e80fc099ea54a71110d256dd60648c47c63c70a3c499eb1f6d7aaa4edb7016f
+ ;;
+ aarch64|arm64)
+ ANYTLS_ARCH=arm64
+ EXPECTED_SHA256=88cb762c3c8eb56b46a2d8d6feab9c0858655192143fc164874229499246a956
+ ;;
+ *)
+ echo "不支持的 CPU 架构: $(uname -m)" >&2
+ exit 1
+ ;;
+esac
+
+WORK_DIR="$(mktemp -d)"
+PACKAGE="anytls_${ANYTLS_VERSION}_linux_${ANYTLS_ARCH}.zip"
+
+curl --fail --location \
+ --output "${WORK_DIR}/${PACKAGE}" \
+ "https://github.com/anytls/anytls-go/releases/download/v${ANYTLS_VERSION}/${PACKAGE}"
+
+echo "${EXPECTED_SHA256} ${WORK_DIR}/${PACKAGE}" | sha256sum --check -
+unzip "${WORK_DIR}/${PACKAGE}" -d "${WORK_DIR}/anytls"
+sudo install -m 0755 "${WORK_DIR}/anytls/anytls-server" /usr/local/bin/anytls-server
+/usr/local/bin/anytls-server -h
+```
+
+只有在校验结果显示 `OK` 后才继续安装。版本升级时,必须同时更新版本号和对应架构的官方 SHA-256 摘要。
+
+## 5. 创建运行用户和配置
+
+创建无登录权限的系统用户:
+
+```bash
+sudo groupadd --system anytls
+sudo useradd --system --gid anytls --no-create-home --shell /usr/sbin/nologin anytls
+sudo install -d -m 0750 -o root -g anytls /etc/anytls-go
+```
+
+生成一个 32 字节随机密码:
+
+```bash
+openssl rand -hex 32
+```
+
+记录输出结果,然后创建配置文件:
+
+```bash
+sudoedit /etc/anytls-go/anytls.env
+```
+
+写入以下内容,将占位符替换为刚生成的密码:
+
+```ini
+ANYTLS_LISTEN=0.0.0.0:8443
+ANYTLS_PASSWORD=
+```
+
+设置配置文件权限:
+
+```bash
+sudo chown root:anytls /etc/anytls-go/anytls.env
+sudo chmod 0640 /etc/anytls-go/anytls.env
+```
+
+> `anytls-server` 当前只支持通过 `-p` 参数接收密码,因此运行时密码会出现在服务进程的命令行参数中。配置文件权限只能减少静态文件泄露风险,不能消除这一实现限制。服务器应限制非必要的系统登录账号。
+
+## 6. 创建 systemd 服务
+
+创建服务文件:
+
+```bash
+sudoedit /etc/systemd/system/anytls-go.service
+```
+
+写入:
+
+```ini
+[Unit]
+Description=AnyTLS-go Server
+Documentation=https://github.com/anytls/anytls-go
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=anytls
+Group=anytls
+EnvironmentFile=/etc/anytls-go/anytls.env
+ExecStart=/usr/local/bin/anytls-server -l ${ANYTLS_LISTEN} -p ${ANYTLS_PASSWORD}
+Restart=on-failure
+RestartSec=5s
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+ProtectKernelTunables=true
+ProtectKernelModules=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET AF_INET6
+LockPersonality=true
+MemoryDenyWriteExecute=true
+UMask=0077
+
+[Install]
+WantedBy=multi-user.target
+```
+
+检查并启动服务:
+
+```bash
+sudo systemd-analyze verify /etc/systemd/system/anytls-go.service
+sudo systemctl daemon-reload
+sudo systemctl enable --now anytls-go
+sudo systemctl status anytls-go --no-pager
+```
+
+## 7. 放行端口
+
+除云平台安全组外,还需按服务器实际使用的防火墙选择一组命令。不要同时配置 UFW 和 firewalld。
+
+UFW:
+
+```bash
+sudo ufw allow 8443/tcp
+sudo ufw status
+```
+
+firewalld:
+
+```bash
+sudo firewall-cmd --permanent --add-port=8443/tcp
+sudo firewall-cmd --reload
+sudo firewall-cmd --list-ports
+```
+
+若使用了其他端口,需要同步修改防火墙规则和 `/etc/anytls-go/anytls.env`。
+
+## 8. 服务端验收
+
+确认服务处于运行状态并监听预期端口:
+
+```bash
+sudo systemctl is-active anytls-go
+sudo ss -lntp 'sport = :8443'
+sudo journalctl -u anytls-go -n 50 --no-pager
+```
+
+期望日志包含类似内容:
+
+```text
+[Server] anytls-go ...
+[Server] Listening TCP 0.0.0.0:8443
+```
+
+从另一台可访问服务器的设备检查公网端口:
+
+```bash
+nc -vz 8443
+```
+
+端口连通只说明 TCP 链路正常,完整验收仍需通过 AnyTLS 客户端发起代理请求。
+
+## 9. 客户端联调
+
+使用同版本参考客户端进行基础联调:
+
+```bash
+./anytls-client \
+ -l 127.0.0.1:1080 \
+ -s :8443 \
+ -p
+```
+
+另开一个终端,通过本地 SOCKS5 代理验证出口:
+
+```bash
+curl --proxy socks5h://127.0.0.1:1080 https://api.ipify.org
+```
+
+`v0.0.12` 及以上版本也支持 URI:
+
+```text
+anytls://@:8443
+```
+
+使用 sing-box、mihomo、Shadowrocket 等第三方客户端时,至少需要填写服务器地址、端口和相同密码。由于参考服务端使用临时自签名证书,还需要按客户端文档配置对应的跳过证书校验选项;不要将这一配置套用到支持正式证书的其他服务端。
+
+## 10. 日常运维
+
+常用命令:
+
+```bash
+sudo systemctl restart anytls-go
+sudo systemctl stop anytls-go
+sudo systemctl start anytls-go
+sudo journalctl -u anytls-go -f
+```
+
+修改端口或密码后执行:
+
+```bash
+sudo systemctl restart anytls-go
+sudo systemctl status anytls-go --no-pager
+```
+
+升级时下载目标版本对应架构的发布包,核对官方摘要,重新安装 `/usr/local/bin/anytls-server`,然后重启并重复第 8、9 节的验收步骤。
+
+## 11. 故障排查
+
+| 现象 | 检查项 |
+| --- | --- |
+| 服务启动失败 | `journalctl -u anytls-go -n 100 --no-pager`;检查环境文件格式及权限 |
+| 提示端口被占用 | `ss -lntp 'sport = :8443'`;更换端口或停止冲突服务 |
+| 外网无法连接 | 云安全组、主机防火墙、运营商或机房入站策略 |
+| TCP 可连接但代理失败 | 客户端协议类型、端口、密码、证书校验设置是否一致 |
+| 重启后客户端证书报错 | 参考服务端每次启动都会重新生成临时自签名证书 |
+| 下载速度异常 | 服务器线路质量、丢包、MTU、客户端实现和 CPU 使用率 |
+
+## 12. 卸载
+
+确认不再使用后执行:
+
+```bash
+sudo systemctl disable --now anytls-go
+sudo rm /etc/systemd/system/anytls-go.service
+sudo systemctl daemon-reload
+sudo rm /usr/local/bin/anytls-server
+sudo rm /etc/anytls-go/anytls.env
+sudo rmdir /etc/anytls-go
+sudo userdel anytls
+```
+
+最后删除云安全组和主机防火墙中的 AnyTLS 端口放行规则。上述操作会删除服务配置和密码,执行前应确认没有其他服务复用这些文件或账号。
+
+## 13. 参考资料
+
+- [AnyTLS-go 官方仓库](https://github.com/anytls/anytls-go)
+- [AnyTLS-go Releases](https://github.com/anytls/anytls-go/releases)
+- [AnyTLS 协议文档](https://github.com/anytls/anytls-go/blob/main/docs/protocol.md)
+- [sing-box AnyTLS 文档](https://sing-box.sagernet.org/configuration/inbound/anytls/)
diff --git a/运维/VLESS-Reality与AnyTLS双协议部署指南.md b/运维/VLESS-Reality与AnyTLS双协议部署指南.md
new file mode 100644
index 0000000..4067a4f
--- /dev/null
+++ b/运维/VLESS-Reality与AnyTLS双协议部署指南.md
@@ -0,0 +1,490 @@
+# VLESS + REALITY 与 AnyTLS 双协议部署指南
+
+## 1. 方案目标
+
+在同一台 Linux 服务器上部署两个相互独立的代理入口:
+
+| 优先级 | 协议 | 服务端实现 | 监听端口 | 用途 |
+| --- | --- | --- | --- | --- |
+| 主力 | VLESS + TCP + XTLS Vision + REALITY | Xray-core | `443/TCP` | 日常主要连接 |
+| 备用 | AnyTLS | AnyTLS-go | `8443/TCP` | 主力入口不可用时切换 |
+
+```text
+客户端
+ |-- 主力节点 ------ TCP/443 ------> Xray: VLESS + REALITY ------> Internet
+ `-- 备用节点 ------ TCP/8443 ------> AnyTLS-go -----------------> Internet
+```
+
+两个服务不互相转发,也不共享端口。所谓“主力/备用”由客户端节点选择或客户端故障转移策略决定;服务器不会自动把 VLESS 连接切换到 AnyTLS。
+
+本文示例版本和核对日期:
+
+| 软件 | 示例版本 | 核对日期 |
+| --- | --- | --- |
+| Xray-core | `v26.3.27` | 2026-07-25 |
+| AnyTLS-go | `v0.0.13` | 2026-07-25 |
+
+## 2. 部署前准备
+
+准备以下参数,文档中的尖括号内容均为待替换占位符:
+
+| 参数 | 用途 |
+| --- | --- |
+| `` | 服务器公网 IP;连接 REALITY 不要求拥有域名 |
+| `` | REALITY 借用 TLS 外观的目标站点域名 |
+| `` | VLESS 用户 ID |
+| `` | 只保存在服务端的 X25519 私钥 |
+| `` | 与私钥对应、配置在客户端的公开连接参数 |
+| `` | 最长 16 位、偶数长度的十六进制字符串 |
+| `` | AnyTLS 的独立随机密码 |
+
+安全组和服务器防火墙需要放行 `443/TCP` 与 `8443/TCP`。不需要放行 UDP 端口。
+
+## 3. 系统检查
+
+```bash
+uname -m
+cat /etc/os-release
+timedatectl status
+sudo ss -lntp 'sport = :443 or sport = :8443'
+```
+
+要求:
+
+- Linux 使用 systemd。
+- CPU 架构为 `x86_64`、`aarch64` 或 `arm64`。
+- `443` 和 `8443` 未被其他进程监听。
+- 系统时间同步正常。
+
+安装依赖时按发行版选择一组命令:
+
+```bash
+# Debian / Ubuntu
+sudo apt-get update
+sudo apt-get install -y curl unzip openssl ca-certificates
+```
+
+```bash
+# RHEL / Rocky Linux / AlmaLinux
+sudo dnf install -y curl unzip openssl ca-certificates
+```
+
+## 4. 安装 Xray-core
+
+以下命令固定安装 `v26.3.27`,并根据服务器架构选择 GitHub Release 文件与 SHA-256 摘要:
+
+```bash
+XRAY_VERSION=26.3.27
+
+case "$(uname -m)" in
+ x86_64|amd64)
+ XRAY_ARCH=64
+ XRAY_SHA256=23cd9af937744d97776ee35ecad4972cf4b2109d1e0fe6be9930467608f7c8ae
+ ;;
+ aarch64|arm64)
+ XRAY_ARCH=arm64-v8a
+ XRAY_SHA256=4d30283ae614e3057f730f67cd088a42be6fdf91f8639d82cb69e48cde80413c
+ ;;
+ *)
+ echo "不支持的 CPU 架构: $(uname -m)" >&2
+ exit 1
+ ;;
+esac
+
+XRAY_WORK_DIR="$(mktemp -d)"
+XRAY_PACKAGE="Xray-linux-${XRAY_ARCH}.zip"
+
+curl --fail --location \
+ --output "${XRAY_WORK_DIR}/${XRAY_PACKAGE}" \
+ "https://github.com/XTLS/Xray-core/releases/download/v${XRAY_VERSION}/${XRAY_PACKAGE}"
+
+echo "${XRAY_SHA256} ${XRAY_WORK_DIR}/${XRAY_PACKAGE}" | sha256sum --check -
+unzip "${XRAY_WORK_DIR}/${XRAY_PACKAGE}" -d "${XRAY_WORK_DIR}/xray"
+sudo install -m 0755 "${XRAY_WORK_DIR}/xray/xray" /usr/local/bin/xray
+/usr/local/bin/xray version
+```
+
+只有摘要检查显示 `OK` 后才继续。升级版本时必须同时更新版本号和相应架构的官方摘要。
+
+创建专用账号和配置目录:
+
+```bash
+sudo groupadd --system xray
+sudo useradd --system --gid xray --no-create-home --shell /usr/sbin/nologin xray
+sudo install -d -m 0750 -o root -g xray /usr/local/etc/xray
+```
+
+## 5. 选择 REALITY 目标站点
+
+REALITY 鉴权失败时,Xray 会将流量转发给 `target`。目标选择不当可能让服务器成为第三方站点的转发入口,因此不要直接照抄一个公共示例域名。
+
+目标站点应满足:
+
+- 从该服务器访问延迟低、连接稳定,优先选择与服务器相同 ASN 的站点。
+- 支持 TLS 1.3,并能正常完成 TLS 握手。
+- `serverNames` 使用目标证书 SAN 中存在的域名。
+- 避免使用 Cloudflare 等公共 CDN 站点,减少被扫描后偷跑转发流量的风险。
+- 不使用银行、支付、政府或其他敏感站点。
+
+对候选站点执行:
+
+```bash
+/usr/local/bin/xray tls ping :443
+openssl s_client -connect :443 -servername `。服务端 `target` 与 `serverNames`、客户端 `serverName` 必须使用同一目标域名。
+
+## 6. 生成 VLESS 和 REALITY 参数
+
+生成 VLESS UUID:
+
+```bash
+/usr/local/bin/xray uuid
+```
+
+生成 REALITY X25519 密钥对:
+
+```bash
+/usr/local/bin/xray x25519
+```
+
+记录输出中的两项:
+
+- `PrivateKey`:填入服务端 ``,不得发送给客户端或他人。
+- `Password`:填入客户端 ``。这是当前 Xray 对原 `publicKey` 字段的新名称,不是登录密码。
+
+生成 8 字节 short ID:
+
+```bash
+openssl rand -hex 8
+```
+
+将 16 位十六进制输出记录为 ``。UUID、私钥和 short ID 不要使用本文中的占位符或公共示例值。
+
+## 7. 配置 VLESS + REALITY 主入口
+
+创建配置:
+
+```bash
+sudoedit /usr/local/etc/xray/config.json
+```
+
+写入以下 JSON 并替换全部占位符:
+
+```json
+{
+ "log": {
+ "loglevel": "warning"
+ },
+ "inbounds": [
+ {
+ "tag": "vless-reality-in",
+ "listen": "0.0.0.0",
+ "port": 443,
+ "protocol": "vless",
+ "settings": {
+ "clients": [
+ {
+ "id": "",
+ "flow": "xtls-rprx-vision",
+ "email": "primary-client"
+ }
+ ],
+ "decryption": "none"
+ },
+ "streamSettings": {
+ "network": "tcp",
+ "security": "reality",
+ "realitySettings": {
+ "show": false,
+ "target": ":443",
+ "xver": 0,
+ "serverNames": [
+ ""
+ ],
+ "privateKey": "",
+ "shortIds": [
+ ""
+ ]
+ }
+ },
+ "sniffing": {
+ "enabled": true,
+ "destOverride": [
+ "http",
+ "tls",
+ "quic"
+ ],
+ "routeOnly": true
+ }
+ }
+ ],
+ "outbounds": [
+ {
+ "tag": "direct",
+ "protocol": "freedom"
+ },
+ {
+ "tag": "block",
+ "protocol": "blackhole"
+ }
+ ],
+ "routing": {
+ "domainStrategy": "IPIfNonMatch",
+ "rules": [
+ {
+ "type": "field",
+ "ip": [
+ "0.0.0.0/8",
+ "10.0.0.0/8",
+ "100.64.0.0/10",
+ "127.0.0.0/8",
+ "169.254.0.0/16",
+ "172.16.0.0/12",
+ "192.0.0.0/24",
+ "192.168.0.0/16",
+ "198.18.0.0/15",
+ "224.0.0.0/4",
+ "::1/128",
+ "fc00::/7",
+ "fe80::/10"
+ ],
+ "outboundTag": "block"
+ }
+ ]
+ }
+}
+```
+
+上述路由规则阻止代理客户端访问服务器所在的常见私网、链路本地地址和云元数据地址。若确实需要通过代理访问某个私网,应逐项评估后修改对应网段,不要直接删除整个限制。
+
+设置权限并验证配置:
+
+```bash
+sudo chown root:xray /usr/local/etc/xray/config.json
+sudo chmod 0640 /usr/local/etc/xray/config.json
+sudo -u xray /usr/local/bin/xray run -test -config /usr/local/etc/xray/config.json
+```
+
+验证失败时不要启动服务,先根据输出修正占位符和 JSON 格式。
+
+## 8. 创建 Xray systemd 服务
+
+```bash
+sudoedit /etc/systemd/system/xray.service
+```
+
+写入:
+
+```ini
+[Unit]
+Description=Xray VLESS REALITY Server
+Documentation=https://github.com/XTLS/Xray-core
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=xray
+Group=xray
+ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
+Restart=on-failure
+RestartSec=5s
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+CapabilityBoundingSet=CAP_NET_BIND_SERVICE
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+ProtectKernelTunables=true
+ProtectKernelModules=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET AF_INET6
+LockPersonality=true
+MemoryDenyWriteExecute=true
+UMask=0077
+
+[Install]
+WantedBy=multi-user.target
+```
+
+`CAP_NET_BIND_SERVICE` 只用于让非 root 的 `xray` 用户监听 `443`,不要将服务改成 root 运行。
+
+启动主入口:
+
+```bash
+sudo systemd-analyze verify /etc/systemd/system/xray.service
+sudo systemctl daemon-reload
+sudo systemctl enable --now xray
+sudo systemctl status xray --no-pager
+```
+
+## 9. 部署 AnyTLS 备用入口
+
+按照 [AnyTLS-go 服务器安装指南](AnyTLS-go服务器安装指南.md) 完成以下部分:
+
+1. 安装 AnyTLS-go `v0.0.13`。
+2. 创建独立的 `anytls` 系统账号。
+3. 生成与 VLESS UUID、REALITY 密钥完全无关的 AnyTLS 随机密码。
+4. 保持监听地址为 `0.0.0.0:8443`。
+5. 启动 `anytls-go.service`。
+
+AnyTLS-go 是协议参考实现,会在启动时生成临时自签名证书,客户端必须使用与该实现相符的跳过证书校验设置。它不应占用 `443`,也不要与 Xray 共用账号、配置或密钥。
+
+## 10. 防火墙和安全组
+
+云平台安全组放行:
+
+| 协议 | 端口 | 来源 |
+| --- | --- | --- |
+| TCP | `443` | 实际客户端来源;无法固定时为公网 |
+| TCP | `8443` | 实际客户端来源;无法固定时为公网 |
+
+主机使用 UFW 时:
+
+```bash
+sudo ufw allow 443/tcp
+sudo ufw allow 8443/tcp
+sudo ufw status
+```
+
+主机使用 firewalld 时:
+
+```bash
+sudo firewall-cmd --permanent --add-port=443/tcp
+sudo firewall-cmd --permanent --add-port=8443/tcp
+sudo firewall-cmd --reload
+sudo firewall-cmd --list-ports
+```
+
+UFW 与 firewalld 只选择服务器实际使用的一种,不要重复配置。
+
+## 11. 客户端参数
+
+### 11.1 VLESS + REALITY 主节点
+
+| 客户端字段 | 值 |
+| --- | --- |
+| 地址 | `` |
+| 端口 | `443` |
+| 协议 | VLESS |
+| UUID | `` |
+| Flow | `xtls-rprx-vision` |
+| 传输 | TCP/RAW |
+| 传输安全 | REALITY |
+| SNI/Server Name | `` |
+| Fingerprint | `chrome` |
+| Public Key/Password | `` |
+| Short ID | `` |
+| Spider X | `/` 或留空 |
+
+常见客户端仍可能把当前 Xray 文档中的 `password` 显示为 `Public Key` 或 `pbk`,填入的都是 `xray x25519` 输出的 `Password` 值。
+
+通用分享 URI 结构:
+
+```text
+vless://@:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=&fp=chrome&pbk=&sid=&type=tcp#VLESS-REALITY
+```
+
+### 11.2 AnyTLS 备用节点
+
+| 客户端字段 | 值 |
+| --- | --- |
+| 地址 | `` |
+| 端口 | `8443` |
+| 协议 | AnyTLS |
+| 密码 | `` |
+| 跳过证书验证 | 仅对 AnyTLS-go 参考服务端启用 |
+
+参考 URI:
+
+```text
+anytls://@:8443
+```
+
+### 11.3 主备策略
+
+客户端中创建两个独立节点,顺序保持 VLESS + REALITY 在前、AnyTLS 在后。需要自动切换时,使用客户端自身的 `fallback` 或健康检查组;需要稳定可控时,使用手动选择组。
+
+不要把两个协议配置成同时向同一连接转发。切换策略只发生在客户端节点层。
+
+## 12. 联合验收
+
+服务端检查:
+
+```bash
+sudo systemctl is-active xray
+sudo systemctl is-active anytls-go
+sudo ss -lntp 'sport = :443 or sport = :8443'
+sudo journalctl -u xray -n 50 --no-pager
+sudo journalctl -u anytls-go -n 50 --no-pager
+```
+
+预期结果:
+
+- `xray` 与 `anytls-go` 均返回 `active`。
+- Xray 监听 `0.0.0.0:443`,AnyTLS-go 监听 `0.0.0.0:8443`。
+- 日志中没有配置解析、权限或端口占用错误。
+
+客户端按以下顺序验收:
+
+1. 只选择 VLESS + REALITY 节点,访问出口 IP 检测网站并完成实际网页、下载测试。
+2. 只选择 AnyTLS 节点,重复相同测试。
+3. 在客户端关闭或临时禁用主节点,确认主备组能切换到 AnyTLS。
+4. 恢复主节点,确认客户端策略按预期回到 VLESS + REALITY。
+
+端口连通性可从外部设备辅助检查:
+
+```bash
+nc -vz 443
+nc -vz 8443
+```
+
+TCP 端口可连接不代表协议配置正确,最终以代理请求成功为准。
+
+## 13. 日常运维
+
+```bash
+# 查看状态
+sudo systemctl status xray anytls-go --no-pager
+
+# 查看实时日志
+sudo journalctl -u xray -f
+sudo journalctl -u anytls-go -f
+
+# 重启单个入口
+sudo systemctl restart xray
+sudo systemctl restart anytls-go
+```
+
+修改 Xray 配置前先备份私钥和客户端参数;修改后先执行配置测试,再重启:
+
+```bash
+sudo -u xray /usr/local/bin/xray run -test -config /usr/local/etc/xray/config.json
+sudo systemctl restart xray
+sudo systemctl status xray --no-pager
+```
+
+升级任一服务时,只替换对应二进制并单独验收,不要同时升级两个入口。这样其中一个入口出现兼容问题时,另一个仍可用于连接。
+
+## 14. 故障定位
+
+| 现象 | 优先检查 |
+| --- | --- |
+| Xray 无法启动 | JSON 校验、配置权限、`443` 占用、systemd capability |
+| REALITY 握手失败 | UUID、`flow`、`serverName`、`password/pbk`、short ID 是否完全一致 |
+| REALITY 偶发超时 | 目标站点连通性、服务器时间、线路丢包、目标是否变更 TLS 配置 |
+| AnyTLS 无法启动 | 环境文件权限、密码是否填写、`8443` 占用 |
+| 端口都通但无法代理 | 客户端协议字段、服务日志、安全软件或上游网络限制 |
+| 主节点故障后未切换 | 客户端是否使用 fallback/健康检查组及其探测 URL、间隔设置 |
+| 服务器出现异常转发流量 | REALITY 目标是否为公共 CDN;重新选择同 ASN 的非 CDN 目标 |
+
+## 15. 参考资料
+
+- [Xray-core 官方仓库](https://github.com/XTLS/Xray-core)
+- [Xray 官方 REALITY 配置文档](https://xtls.github.io/config/transports/reality.html)
+- [Xray 官方 VLESS + TCP + XTLS Vision + REALITY 示例](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision-REALITY)
+- [Xray-core Releases](https://github.com/XTLS/Xray-core/releases)
+- [AnyTLS-go 官方仓库](https://github.com/anytls/anytls-go)
+- [AnyTLS-go 服务器安装指南](AnyTLS-go服务器安装指南.md)
diff --git a/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程图.md b/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程图.md
index 37e7ee0..31d3900 100644
--- a/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程图.md
+++ b/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程图.md
@@ -1,316 +1,215 @@
-# WaveRuleMatchService 流程图
+# WaveRuleMatchService 当前流程图
-> 对应实现:`C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy`
->
-> 说明:本文是 AI 可直接读取的纯文本流程图,不使用图片。
-> 口径说明:`WES-WMS` 指当前项目实现侧,`Shopee` 指需要调用或接收交互的外部接口方。
-> 目标:把当前代码的入口、分流、外部接口调用、回滚和状态回写,整理成可检索的文本流程图。
+> 对应当前 `WaveRuleMatchService.groovy`,不包含已删除的历史方法或未落地占位流程。
----
-
-## 1. 读取方式
-
-建议按下面顺序理解本文:
-
-1. 先看 `2. 主流程总图`
-2. 再看 `3. 分支流程`
-3. 最后看 `4. 方法映射表`
-
-如果只想快速定位代码路径,直接看 `4. 方法映射表`。
-
----
-
-## 2. 主流程总图
-
-### 2.1 计划集波工作站入口
+## 1. 总入口
```text
-assignShopeeWaveToAcceptedWorkStations(session, warehouseCode[, workStationCode])
-├─ 查询候选工作站
-│ ├─ currentJobMode 为拣选模式
-│ ├─ acceptStatus = OPEN
-│ └─ status = ENABLE
-├─ 统计各工作站的可用空格口
-│ ├─ 分播墙 status = ENABLE
-│ ├─ 格口 status = ENABLE
-│ └─ 格口 shopeeWaveCode 为空
-├─ 多工作站排序
-│ ├─ 可用空格口数量降序
-│ └─ 数量相同时工作站 ID 升序
-└─ 按排序结果逐个调用统一集波匹配流程
- ├─ 无启用分播墙 → 跳过当前工作站
- └─ 单个工作站失败 → 记录日志并继续后续工作站
+assignShopeeWaveToAcceptedWorkStations(session, warehouseCode, workStationCode)
+│
+├─ 查询启用且可接单的拣选工作站
+├─ 按空格口数降序、工作站 ID 升序
+└─ for each workstation
+ └─ matchByStation(session, workstation)
+ ├─ 校验 status / warehouseCode / acceptStatus
+ ├─ 获取 ruleLockKey(warehouseCode)
+ ├─ ORDER_PICK
+ │ └─ assignSingles
+ └─ BATCH_PICK
+ ├─ 操作员技能与工作站 waveRule 取交集
+ ├─ fillShortage
+ └─ 无补单结果时按 ticketType 分流
+ ├─ 1/5 → assignTaskTicket
+ ├─ 2/3/4 → assignOrder
+ └─ 6 → assignRt
```
-### 2.2 单工作站集波入口
+## 2. 快速拣选
```text
-自动集波工作站扫描
-└─ matchByStation(session, workstation)
- ├─ [1] 从 workstation 读取 id、warehouseCode、code、shipmentType
- │
- ├─ [2] 工作站状态校验
- │ ├─ status != ENABLE → MSG_WRM_0005
- │ └─ warehouseCode 为空 → MSG_WRM_0006
- │
- ├─ [3] 接单状态校验
- │ ├─ acceptStatus != OPEN → MSG_WRM_0015
- │ └─ 通过 → 继续
- │
- ├─ [4] 仓库维度 Redis 锁
- │ ├─ ruleLockKey(warehouseCode)
- │ ├─ tryLock 失败 → MSG_WRM_0018
- │ └─ 获取成功 → 继续
- │
- ├─ [5] ORDER_PICK → assignSingles
- │
- ├─ [6] BATCH_PICK
- │ ├─ 取操作员与工作站 Wave Rule 交集
- │ ├─ fillShortage
- │ └─ 补单未分配时,按规则 ticketType 进入独立方法
- │ ├─ 1/5 → assignTaskTicket
- │ ├─ 2/3/4 → assignOrder
- │ └─ 6 → assignRt
- │
- ├─ [7] 有任一分流完成分配 → success
- │
- └─ [8] finally
- ├─ 写入结束日志
- └─ unlock
+assignSingles(warehouseCode, workstation)
+├─ findSingleEmptyCells
+├─ findSingleShipments
+└─ for each shipment
+ ├─ findShipmentExt
+ ├─ 波次号来源
+ │ ├─ 已有 shopeeWaveCode → 复用
+ │ ├─ RT → shipmentCode
+ │ └─ 其他 → acquireWave
+ ├─ RT → backfillRtShopeeWaveCode
+ ├─ 已有外部波次号 → createInternalShopeeWave
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ syncShopeePickingTaskAndFilterAcceptedShipments(ticketType)
+ │ └─ RT 跳过外部调用
+ ├─ updateLocalShopeeWaveBinding(ruleCode='')
+ └─ addToWave
```
----
-
-## 3. 分支流程
-
-### 3.1 一波一单分流
+## 3. 任务单
```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
-│ │ ├─ RT 或已有 shopeeWaveCode 时创建 shopee_wave
-│ │ └─ 传 ticketType 同步 picking task(仅 RT 跳过外部调用)后直接本地回写
-│ ├─ createInternalWave
-│ ├─ addShipmentsToInternalWave
-│ └─ 记录成功日志
-└─ 返回 success 或 error
+assignTaskTicket(warehouseCode, workstation, rule, ticketType)
+│ ticketType = SALES_TASK / MTO_TASK
+│
+├─ findSingleEmptyCells(workstation.id, rule)
+├─ findTaskTicketShipments
+└─ for each shipment
+ ├─ 读取 shipment_header_ext1.shopeeWaveCode
+ ├─ createInternalShopeeWave
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ syncShopeePickingTaskAndFilterAcceptedShipments(ticketType)
+ ├─ updateLocalShopeeWaveBinding
+ └─ addToWave
```
-关键点:
-
-- `RT` 不申请 Shopee 外部波次号,使用 `shipmentCode` 作为本地 `shopeeWaveCode`。
-- `backfillRtShopeeWaveCode` 仅由 RT 分支调用,方法内部不再重复判断 `ticketType`。
-- 非 `RT` 的一波一单,若本地没有 `shopeeWave`,会先取号再绑定。
-- 一波一单是“1 单 -> 1 格口 -> 1 内部波次”的结构。
-
-### 3.2 补缺口分流
+## 4. 普通订单
```text
-fillShortage(warehouseCode, workstation) → shipmentType 直接读取 workstation.shipmentType
-├─ findShortage(workstationId) → 仅选择 shopee_wave.status=200 的关联格口
-├─ for each cell
-│ ├─ 如果 cell 未绑定 shopeeWaveCode → 跳过
-│ ├─ calcNeedQty
-│ ├─ needQty <= 0 → 跳过
-│ ├─ needQty > 0 → 加入需补货格口明细
-│ ├─ resolveBoundShipmentGroup
-│ ├─ findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
-│ ├─ assignToCell
-│ └─ 记录补单成功或失败日志
-├─ MSG_WRM_0244 → 记录需补货格口数量及 cellCode|shopeeWaveCode|currentWaveRule|ticketType|needQty
-└─ 返回
+assignOrder(warehouseCode, workstation, rule, ticketType)
+│ ticketType = SALES_ORDER / RTS / MTO_ORDER
+│
+├─ findUnusedCells(workstation.id, rule)
+└─ 首个可用格口
+ ├─ findAvailableShipments
+ ├─ hasEmptyCellMinShipments
+ ├─ acquireWave
+ └─ assignAcquiredWaveToCell
+ └─ assignToCell
```
-关键点:
-
-- 候选资格只判断当前工作站格口关联的 `shopee_wave.status=200`。
-- 不按分播墙状态、格口状态、ticketType、任务完成时间或 `waitingTime` 过滤候选。
-- 补单执行时从已绑定订单解析 `groupKey`,保持同一 Shopee 波次订单特征一致。
-- 补缺口不重新取新 Shopee 波次号。
-
-### 3.3 空格口首次分配
+## 5. RT
```text
-assignEmptyCells(warehouseCode, shipmentType, workstationId, rules)
-├─ for each rule
-│ ├─ findCells(session, workstationId)
-│ ├─ assignCellNumbers(warehouseCode, shipmentType, workstationId, rule, cells)
-│ ├─ 若本轮分配成功 → break
-│ └─ 否则继续下一个 rule
-└─ 返回 success
+assignRt(warehouseCode, workstation, rule)
+├─ findSingleEmptyCells(workstation.id, rule)
+├─ findRtTicketShipments
+└─ for each shipment
+ ├─ shipmentCode 作为本地 Shopee 波次号
+ ├─ backfillRtShopeeWaveCode
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ updateLocalShopeeWaveBinding
+ └─ addToWave
```
-`assignCellNumbers` 的核心逻辑:
+RT 不申请接口号段,不调用 Shopee picking task 外部接口。
+
+## 6. 缺口补单
```text
-assignCellNumbers(...)
-├─ for each cell
-│ ├─ 读取 cell.shopeeWaveCode
-│ ├─ 如果 cell 没有 shopeeWaveCode
-│ │ ├─ findAvailableShipments(session, warehouseCode, rule, ..., workstation)
-│ │ ├─ acquireUnusedShopeeWave
-│ │ └─ 得到新 shopeeWaveCode
-│ ├─ 如果 cell 已有 shopeeWaveCode
-│ │ ├─ calcNeedQty
-│ │ ├─ needQty <= 0 → 跳过
-│ │ ├─ resolveBoundShipmentGroup
-│ │ └─ findAvailableShipments(..., shopeeWaveCode, groupKey)
-│ └─ assignToCell
-└─ 返回 success / error
+fillShortage(warehouseCode, workstation)
+├─ findShortage(workstation.id)
+│ └─ 只选择关联 shopee_wave.status=200 的格口
+└─ for each cell
+ ├─ 按 currentWaveRule 读取启用规则
+ ├─ calcNeedQty
+ ├─ 记录需补单格口处理日志
+ ├─ resolveBoundShipmentGroup
+ ├─ findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
+ └─ assignToCell
```
----
-
-## 4. 关键子流程
-
-### 4.1 assignToCell
+## 7. 单格口分配
```text
-assignToCell(warehouseCode, rule, cell, shipmentIds, shopeeWaveCode, ticketType, workstation)
-├─ pickCellShipmentIds
-├─ ensureCellReadyForWave
-│ ├─ 已有同 wave 同 rule 的 USED 格口 → 直接复用
-│ └─ 否则走 occupySortingWallCell
+assignToCell(...)
+├─ 获取 cellLockKey(cell.id)
+├─ pickCellShipments
+├─ occupyCell
+├─ createWave
+├─ Shopee 同步
+│ ├─ 新格口 → syncShopeePickingTaskAndFilterAcceptedShipments
+│ └─ 同波次补单 → syncShopeeAppendOrderAndFilterAcceptedShipments
├─ bindBatchShipmentWave
+│ ├─ waveBindLockKey
│ ├─ isWaveAvailable
│ └─ updateLocalShopeeWaveBinding
-├─ createInternalWave
-├─ addShipmentsToInternalWave
-│ ├─ shipmentHeaderService.addMultipleToWave
-│ └─ waveSvc.run
-└─ 清空当前候选池,返回 success
+├─ addToWave
+├─ 达到 maxShipments → status=FULL
+└─ finally unlock
```
-### 4.2 分类型 Shopee 波次绑定
+## 8. Shopee 同步
```text
-updateLocalShopeeWaveBinding(...) [快速拣选直接调用]
-
-updateLocalShopeeWaveBinding(...) [Sales/MTO 任务单直接调用]
-
-bindBatchShipmentWave(...) [普通批量订单]
-├─ 校验参数和调用方传入的 ticketType
-├─ waveBindLockKey
-├─ 获取 Shopee 波次绑定锁
-├─ isWaveAvailable
-│ └─ 方法内直接查询并确认 shopee_wave 状态可用
-├─ 若成功
-│ └─ updateLocalShopeeWaveBinding
-│ └─ 统一回写格口、内部 wave 和 shopee_wave
-└─ finally 解锁
+syncShopeePickingTaskAndFilterAcceptedShipments(..., ticketType)
+├─ RT → 返回 acceptedIds,不调用外部接口
+└─ 其他类型
+ ├─ sendShopeePickingTaskRequest
+ ├─ updateShopeeWaveStartPickUploadStatus
+ ├─ resolveKickedShipmentIds
+ ├─ markKickOut
+ └─ writePickingTaskSyncWaveCode
```
-重要说明:
-
-- 快速拣选和 Sales/MTO 任务单直接调用本地回写方法;普通批量订单使用独立绑定方法。
-- 普通批量订单由调用方传入当前分支已确定的 `ticketType`。
-- 真实的 3.1.6/3.4.6 绑定接口在工作站绑箱时同步调用。
-
-### 4.3 createInternalWave / addShipmentsToInternalWave
-
```text
-createInternalWave(session, warehouseCode, rule)
-├─ resolveMasterCode
-├─ waveSvc.createInternalWave
-└─ 返回 waveId
-
-addShipmentsToInternalWave(shipmentIds, waveId)
-├─ shipmentHeaderService.addMultipleToWave
-├─ waveSvc.run
-└─ run 失败时 cancelInternalWave
+syncShopeeAppendOrderAndFilterAcceptedShipments(...)
+├─ sendShopeeAppendOrderRequest
+├─ resolveKickedShipmentIds
+├─ markKickOut
+└─ 返回 acceptedIds
```
-### 4.4 回滚与释放
+## 9. 波次号与本地绑定
```text
-回滚点
-├─ 格口抢占失败
-│ └─ 直接返回 false / error,继续尝试下一个格口或规则
-├─ Shopee 波次绑定失败
-│ └─ markKickOutWave
-├─ 内部波次创建失败
-│ └─ releaseSingleShipmentSortingWallCell / 返回 error
-├─ 内部波次运行失败
-│ └─ cancelInternalWave
-└─ finally
- └─ unlock
+acquireWave(warehouseCode, ticketType)
+├─ waveAcquireLockKey(ticketType)
+└─ shopee_wave
+ ├─ ticketType 匹配
+ ├─ sourceType=INTERFACE
+ ├─ waveDate >= now - 10 hours
+ ├─ status=UNUSED
+ └─ order by rand() limit 1
```
----
+```text
+createInternalShopeeWave(shopeeWaveCode, ticketType)
+├─ code + ticketType 已存在 → success
+└─ 不存在 → 创建 INTERNAL / USING 记录
+```
-## 5. 方法映射表
+```text
+updateLocalShopeeWaveBinding(...)
+├─ resolveShipmentTicketType
+├─ sortingWallCellLockKey(cell.id)
+├─ 更新 wcs_sorting_wall_cell
+├─ 更新内部 wave
+└─ 更新 shopee_wave.status
+```
-| 方法 | 作用 | 在流程图中的位置 |
-|---|---|---|
-| `assignShopeeWaveToAcceptedWorkStations` | 计划集波工作站扫描、排序与调度 | 计划集波工作站入口 |
-| `matchByStation` | 单工作站统一分流入口 | 主流程总图 |
-| `assignTaskTicket` | Sales/MTO Task 共用分流,补建缺失的 shopee_wave | 主流程总图 [6] |
-| `assignOrder` | Sales/MTO/RTS Order 共用分流 | 主流程总图 [6] |
-| `assignRt` | RT 独立分流 | 主流程总图 [6] |
-| `hasUserShipmentTypePermission` | 用户出库类型权限 | 主流程总图 [1] |
-| `findEnabledWorkStation` | 工作站校验 | 主流程总图 [3] |
-| `hasDifferentActiveShipmentType` | 同工作站出库类型隔离 | 主流程总图 [5] |
-| `findEnabledRules` | 查启用规则 | 主流程总图 [7] |
-| `assignSingleShipmentWaves` | 一波一单分流 | 3.1 |
-| `fillShortage` | status=200 Shopee 波次补缺口分流 | 3.2 |
-| `assignEmptyCells` | 空格口首次分配 | 3.3 |
-| `assignCellNumbers` | 规则驱动的格口分配 | 3.3 |
-| `assignToCell` | 单个格口承接一批单据 | 4.1 |
-| `bindBatchShipmentWave` | 普通批量订单 Shopee 波次绑定 | 4.2 |
-| `createInternalWave` | 创建内部波次 | 4.3 |
-| `addShipmentsToInternalWave` | 订单入波次并运行 | 4.3 |
-| `cancelInternalWave` | 运行失败回滚 | 4.4 |
+## 10. 失败处理
----
+```text
+格口占用失败 → 返回错误
+createWave 失败 → releaseSingleCell
+Shopee 同步失败 → releaseSingleCell + cancelWave
+Shopee 踢单 → markKickOut
+本地绑定失败 → releaseSingleCell + cancelWave
+addToWave 失败 → releaseSingleCell + cancelWave
+```
-## 6. 外部接口关系
+## 11. 方法映射
-| 接口编号 | `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 绑定 + 内部波次运行”的串联中枢,不是纯规则查询服务。
+| 方法 | 当前职责 |
+|---|---|
+| `assignShopeeWaveToAcceptedWorkStations` | 工作站扫描与调度 |
+| `matchByStation` | 单工作站统一入口 |
+| `assignSingles` | 快速拣选 |
+| `assignTaskTicket` | SALES_TASK / MTO_TASK |
+| `assignOrder` | SALES_ORDER / RTS / MTO_ORDER |
+| `assignRt` | RT 专用分流 |
+| `fillShortage` | USING 波次格口补单 |
+| `assignToCell` | 普通订单单格口完整分配 |
+| `assignAcquiredWaveToCell` | 使用已取得号段进入单格口分配 |
+| `createWave` | 创建内部 wave |
+| `addToWave` | 加单并运行内部 wave |
+| `updateLocalShopeeWaveBinding` | 回写格口、内部 wave、shopee_wave |
+| `bindBatchShipmentWave` | 普通订单波次校验与本地绑定 |
+| `acquireWave` | 从接口号段池选择 UNUSED 号段 |
+| `createInternalShopeeWave` | 补建单据已有波次号记录 |
+| `backfillRtShopeeWaveCode` | RT 本地波次记录维护 |
+| `releaseSingleCell` | 释放无未完成任务的格口 |
+| `cancelWave` | 取消失败内部 wave |
diff --git a/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md b/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md
index 3244756..2309526 100644
--- a/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md
+++ b/集波/SHOPEE_010_出库集波V1.2_WaveRuleMatchService_流程文档.md
@@ -1,915 +1,330 @@
-# WaveRuleMatchService 流程文档
+# WaveRuleMatchService 当前流程文档
-> 对应实现:`C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy`
+> 对应代码:`wms-wave/src/main/groovy/com/ittx/wms/wave/service/shopee/WaveRuleMatchService.groovy`
>
-> 本文档描述该服务的**当前代码流程**,按调用入口到出口的完整链路展开,包括主流程、分流流程、补缺口流程、回滚流程和详细的规则匹配机制。
->
-> 需求标准来源:`C:\work\gitlab\shopee\ttx-project-doc\集波\SHOPEE_010_出库集波V1.2.docx`,当前同步到 V1.4 口径:`wave_rule.waveType` 与 `shipment_header.orderStructure` 包含关系、同一 `groupKey` 集波约束、字段匹配字典规则、`Max SKU Pieces Per Order` / `Mix Mode Max SKU Pieces Filter` 标准要求、`multi_attr_list` 全包含匹配要求。
+> 本文只描述当前代码已经存在的流程,不保留已删除方法或历史占位方案。
----
+## 1. 服务职责
-## 目录
+`WaveRuleMatchService` 负责 Shopee 自动集波的工作站调度、规则匹配、Shopee 波次号准备、格口占用、picking task 同步、内部 wave 创建与运行,以及失败回滚和处理日志。
-1. [服务概述](#1-服务概述)
-2. [入口与总流程](#2-入口与总流程)
-3. [前置校验与并发控制](#3-前置校验与并发控制)
-4. [一波一单分流流程](#4-一波一单分流流程)
-5. [推拣货任务当前口径](#5-推拣货任务当前口径)
-6. [标准集波主流程](#6-标准集波主流程)
-7. [格口分配策略](#7-格口分配策略)
-8. [Shopee 波次绑定与 ESS 校验](#8-shopee-波次绑定与-ess-校验)
-9. [内部波次创建与运行](#9-内部波次创建与运行)
-10. [回滚机制](#10-回滚机制)
-11. [首次集波内补缺口流程](#11-首次集波内补缺口流程)
-12. [Flow Pick 换箱换波流程](#12-flow-pick-换箱换波流程)
-13. [字段匹配规则(WAVE_RULE_FIELD_MATCHING)](#13-字段匹配规则wave_rule_field_matching)
-14. [方法调用关系图](#14-方法调用关系图)
-15. [标准需求与当前实现对照](#15-标准需求与当前实现对照)
+当前 `ticketType` 分流:
----
+| ticketType | 类型 | 分流方法 |
+|---|---|---|
+| `1` | SALES_TASK | `assignTaskTicket` |
+| `2` | SALES_ORDER | `assignOrder` |
+| `3` | RTS | `assignOrder` |
+| `4` | MTO_ORDER | `assignOrder` |
+| `5` | MTO_TASK | `assignTaskTicket` |
+| `6` | RT | `assignRt` |
-## 1. 服务概述
+`MTO_TASK` 保持任务单流程;`SALES_ORDER`、`MTO_ORDER`、`RTS` 共用订单流程。
-`WaveRuleMatchService` 是 Shopee 出库集波的核心服务,职责:
+## 2. 调度入口
-- 接收「工作站 + 出库类型」请求
-- 查询启用的波次规则(`wave_rule`),按优先级排序
-- 查找工作站下可用的分播墙格口(`wcs_sorting_wall_cell`)
-- 从订单池(`shipment_header`)中匹配符合条件的出库单
-- 完成 Shopee 波次号绑定、ESS 合波校验、内部波次创建与运行
-- 首次集波成功返回 `ResponseMessageFactory.success()`,不返回「格口 + 命中规则 + 波次」明细
+### 2.1 assignShopeeWaveToAcceptedWorkStations
-**当前匹单核心口径:**
+计划任务入口:
-| 维度 | 当前规则 |
-|------|----------|
-| 规则优先级 | `wave_rule.wavePriority` 数字越大越优先;高优先级规则订单数不足 `maxShipments` 时仍优先消费当前可匹配单据 |
-| 抓单数量 | 只使用 `wave_rule.maxShipments`;`wave_master.maxShipments` 不参与当前逻辑 |
-| 订单结构 | 出库单头 `shipment_header.orderStructure` 记录数字编码:`1=SSSQ`、`2=SSAQ`、`3=MSAQ` |
-| 规则类型匹配 | `wave_rule.waveType` 通过 `resolveMatchedOrderStructures` 转换为可抓的 `orderStructure` 集合 |
-| 字段匹配 | 通过 `WAVE_RULE_FIELD_MATCHING` 字典追加 `wave_rule` 字段与 `shipment_header` 字段的 `IN/EQ/LE` 条件 |
-| 同波约束 | 首次抓单会先按最高优先级候选组解析 `groupKey`;补缺口时通过 `resolveBoundShipmentGroup` 从当前波次已绑定订单解析 `groupKey` |
-| V1.4 新增 SKU 件数过滤 | `Max SKU Pieces Per Order`、`Mix Mode Max SKU Pieces Filter` 是 docx 标准要求;当前 `WaveRule` 域和匹单 SQL 未见对应落地字段,列为待实现 |
-
-**三种分流入口:**
-
-| 分支 | 条件 | 处理方式 |
-|------|------|----------|
-| 一波一单分流 | `pickType=1`、`XSCK+ticketType=1`、`ticketType=5`、`ticketType=6` | 一单一个内部波次;RT 不使用 Shopee 波次号 |
-| 已绑波次格口补缺口 | 格口关联的 `shopee_wave.status=200` | 候选只按波次状态判断;进入执行后读取格口规则和 ticketType,并按 `lowThreshold/maxShipments` 计算补单数量 |
-| 空格口首次分配 | 其他普通批量候选单 | 按规则优先级逐规则匹配,先抢占格口,再绑定 Shopee 波次,再创建并运行内部波次 |
-
-### PDF 补充:WMS 出库 vendor 交互范围
-
-来源:`WMS出库流程.pdf`。该 PDF 主要是 vendor 视角交互时序图,可抽取文字较少;以下只同步与集波/拣货任务边界相关的口径。名词口径按当前项目统一:`vendor` 是本项目 / WES-WMS 实现侧,`Shopee` 是需要调用或接收交互的外部接口方。
-
-| PDF 流程 | 创建源 | PDF 说明 | 与当前 `WaveRuleMatchService` 的关系 |
-|----------|--------|----------|-------------------------------------|
-| 销售出库自动化 `SubPickingTask` | Shopee | `RunWave` 生成的拣货任务占到自动化区时,拆分 normal 拣货任务和自动化拣货任务;自动化部分下发到 vendor,本项目侧执行 WMS 创建的任务,不能混合拣货 | 与当前内部波次创建、运行和 Shopee picking task 调用方向相关;自动化任务下发属于当前项目 vendor 侧链路,本文不标记为本服务已完全落地 |
-| 销售出库自动化 `Order` | vendor | 订单预命中自动化区时,由本项目 vendor 侧接收/组织任务,并按 PDF 口径合并多个订单后创建并执行混合拣货任务 | 属当前项目 vendor 侧创建任务链路,不是当前服务的首次集波直接创建内部波次逻辑 |
-| RTS 需求池模式 | vendor | RTS 需求到自动化区后,由本项目 vendor 侧按需求创建并执行拣货任务 | 属当前项目 vendor 侧需求池链路;当前服务只按 `ticketType=3` 参与集波匹单/接口类型分流 |
-| MTO 需求池模式 | vendor | MTO 需求到自动化区后,由本项目 vendor 侧按需求创建并执行拣货任务 | 属当前项目 vendor 侧需求池链路;当前服务只按 `ticketType=4/5` 参与集波匹单/接口类型分流 |
-| MTO 非需求池模式 | Shopee | 库存占用到自动化库位后拆分 `PickingTask`,任务下发到 vendor,本项目侧不能混合拣货 | 与 Shopee 外部创建任务方向相关;任务拆分和下发细节不在当前服务内闭环 |
-
-PDF 名词口径:`波次ID` 约等于 WMS 拣货任务;`PickingTask` 通常对应多个业务订单,可由多个拣货员执行,当前项目 vendor 侧不需要感知多拣货员层;`SubPickingTask` 用于任务过大或跨区拆分,本次交互中一个销售出库波次 ID 对应一个 `SubPickingTask`。
-
----
-
-## 2. 入口与总流程
-
-### 调用链总图
-
-```
-assignShopeeWaveToAcceptedWorkStations(TtxSession, String, String)
- │
- ├─ 扫描开启接单的工作站
- └─ matchByStation(TtxSession, WcsWorkStation)
- │
- ├─ [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] ORDER_PICK → assignSingles
- │ → 只按 pickType=1 进入快速拣选分流
- │
- ├─ [6] fillShortage(status=200 Shopee 波次格口补缺口)
- │ → 空闲 + shopeeWaveCode 有值 + currentWaveRule 匹配
- │
- ├─ [7] BATCH_PICK 按规则 ticketType 独立分流
- │ → 1/5: assignTaskTicket
- │ → 2/3/4: assignOrder
- │ → 6: assignRt
- │ → 普通批量候选单进入空格口分配
- │
- └─ [8] 成功返回 ResponseMessageFactory.success()
+```text
+assignShopeeWaveToAcceptedWorkStations(session, warehouseCode, workStationCode)
+├─ 查询当前仓库可接单且启用的拣选工作站
+├─ 指定 workStationCode 时只处理该工作站
+├─ 按可用空格口数量降序、工作站 ID 升序排序
+├─ 逐个调用 matchByStation(session, workstation)
+└─ 单个工作站失败不阻断后续工作站
```
----
+只要方法已经接收 `WcsWorkStation`,`shipmentType` 都直接读取 `workstation.shipmentType`,不再单独传参。
-## 3. 前置校验与并发控制
+### 2.2 matchByStation
-### 3.1 工作站校验
-
-```
-findEnabledWorkStation(warehouseCode, workStation)
+```text
+matchByStation(session, workstation)
+├─ 校验 workstation.status=ENABLE
+├─ 校验 warehouseCode
+├─ 校验 acceptStatus=OPEN
+├─ 获取 warehouseCode 维度 Redis 锁
+├─ ORDER_PICK
+│ └─ assignSingles
+└─ BATCH_PICK
+ ├─ 查询操作员技能与工作站 waveRule 交集
+ ├─ findEnabledRules
+ ├─ fillShortage,优先补当前工作站已占用格口
+ └─ 无补单结果时按规则 ticketType 分流
+ ├─ 1/5 → assignTaskTicket
+ ├─ 2/3/4 → assignOrder
+ └─ 6 → assignRt
```
-- 入参 `workStation` 兼容 **编码**(String)和 **数字 ID**(自动识别)
-- 校验:`status=ENABLE`、`warehouseCode` 匹配
-- 失败返回对应 `MSG_WRM_0004/0005/0006`
+仓库级锁覆盖同一工作站匹配轮次,最终在 `finally` 中解锁并记录结束日志。
-### 3.2 Redis 锁
+## 3. 快速拣选
-```
-buildRuleMatchLockKey → "wave_rule_match:{warehouseCode}:{workstationId}:{shipmentType}"
-RedissonLockService.getAndTryLock(lockKey)
+`assignSingles` 处理 `pickType=1` 候选,每单独占一个格口和一个内部 wave。
+
+```text
+assignSingles(warehouseCode, workstation)
+├─ findSingleEmptyCells(workstation.id)
+├─ findSingleShipments(warehouseCode, emptyCellCount, workstation)
+└─ 逐单处理
+ ├─ findShipmentExt
+ ├─ 确定 Shopee 波次号
+ │ ├─ shopeeWaveCode 已有值 → 直接使用
+ │ ├─ RT → 使用 shipmentCode
+ │ └─ 其他 → acquireWave
+ ├─ RT → backfillRtShopeeWaveCode
+ ├─ 非 RT 且使用已有波次号 → createInternalShopeeWave
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ syncShopeePickingTaskAndFilterAcceptedShipments(..., ticketType)
+ │ └─ 仅 RT 跳过外部接口
+ ├─ updateLocalShopeeWaveBinding
+ └─ addToWave
```
-- 锁粒度:**仓库 + 工作站 + 出库类型**
-- 获取失败时不阻塞等待,直接返回 `MSG_WRM_0012`(前端可提示用户稍后重试)
-- 在 finally 块中 `WmsRedissionLockService.unlock(lock)`
+快速拣选不写 `waveRule`,调用 `updateLocalShopeeWaveBinding` 时传空规则编码。
-### 3.3 同工作站出库类型隔离
+## 4. 任务单分流
-```
-hasDifferentActiveShipmentType(warehouseCode, workstationId, shipmentType)
- → findActiveWorkstationShipmentTypes(warehouseCode, workstationId)
+`SALES_TASK` 与 `MTO_TASK` 共用 `assignTaskTicket`。
+
+```text
+assignTaskTicket(warehouseCode, workstation, rule, ticketType)
+├─ findSingleEmptyCells(workstation.id, rule)
+├─ findTaskTicketShipments(..., ticketType, rule)
+└─ 逐单处理
+ ├─ 从 shipment_header_ext1 读取 shopeeWaveCode
+ ├─ createInternalShopeeWave
+ │ └─ code + ticketType 不存在时创建 shopee_wave
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ syncShopeePickingTaskAndFilterAcceptedShipments(..., ticketType)
+ ├─ updateLocalShopeeWaveBinding
+ └─ addToWave
```
-- 查询当前工作站已占用的 `USED` 格口。
-- 通过格口 `shopeeWaveCode` 关联 `shipment_header.shopeeWave`,获取当前工作站正在作业的 `shipmentType`。
-- 若存在非本次请求的出库类型,入口直接拒绝本次集波。
-- 拒绝时记录系统处理日志,并返回 `MSG_WRM_0014`。
-- 该校验位于 Redis 锁之前,避免无效请求占用集波锁。
+任务单同步完成后直接执行本地绑定,不再经过独立绑定转发方法,也不重复执行 Shopee 波次锁和可用性检查。
-### 3.4 工作站出库类型权限
+## 5. 订单分流
-标准要求:登录工作站时按用户权限控制可选择的出库类型,配置后工作站只能下拉选择该用户有权限的单据类型。
+`SALES_ORDER`、`MTO_ORDER`、`RTS` 共用 `assignOrder`。
-当前实现/待实现:`WaveRuleMatchService` 已做同一工作站不同出库类型隔离;用户与出库类型的权限绑定仍是 TODO,不能在本文档中描述为已落地。
-
-### 3.5 计划集波工作站调度
-
-`assignShopeeWaveToAcceptedWorkStations` 扫描指定仓库的候选工作站,并按工作站当前 `shipmentType` 依次执行集波匹配。
-
-- 候选工作站必须满足:`currentJobMode` 为拣选模式、`acceptStatus=OPEN`、`status=ENABLE`。
-- 指定工作站编码时,只处理该工作站;未指定时处理仓库内全部候选工作站。
-- 多个候选工作站按可用空格口数量降序处理;数量相同时按工作站 ID 升序处理。
-- 可用空格口统计口径:工作站绑定的分播墙为启用状态、格口为启用状态,且格口 `shopeeWaveCode` 为空。
-- 工作站没有启用分播墙时跳过;`shipmentType` 不作为调度过滤条件。只要方法已接收 `WcsWorkStation`,就不再单独传递 `shipmentType`,方法内直接读取 `wcs_work_station.shipmentType`。单个工作站匹配失败不阻断后续工作站。
-- 候选 SQL 不再额外排除 `ticketType=1/5/6`,以当前分支的精确 `ticketType` 条件为准。
-- 任务单空格口查询使用 `findTaskTicketShipments`,RT 空格口查询使用 `findRtTicketShipments`;两个方法各只有一个调用分支,且均不与补格口共用。
-
----
-
-## 4. 一波一单分流流程
-
-### 触发条件
-
-`pickType=1`、`XSCK+ticketType=1`、`ticketType=5`、`ticketType=6`
-
-### 方法
-
-```
-assignSingleShipmentWaves(warehouseCode, shipmentType, workstationId, rules)
+```text
+assignOrder(warehouseCode, workstation, rule, ticketType)
+├─ findUnusedCells(workstation.id, rule)
+└─ 对首个可用格口
+ ├─ findAvailableShipments(..., ticketType)
+ ├─ hasEmptyCellMinShipments
+ ├─ acquireWave(warehouseCode, ticketType)
+ └─ assignAcquiredWaveToCell
+ └─ assignToCell
```
-### 流程图
+首次分配每轮只启动一个新格口;后续容量缺口由 `fillShortage` 优先处理。
-```
-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
+## 6. RT 分流
-无任何规则命中 → MSG_WRM_0010
+`assignRt` 是 RT 专用的一单一格口流程:
+
+```text
+assignRt(warehouseCode, workstation, rule)
+├─ findSingleEmptyCells(workstation.id, rule)
+├─ findRtTicketShipments
+└─ 逐单处理
+ ├─ 使用 shipmentCode 作为本地 Shopee 波次号
+ ├─ backfillRtShopeeWaveCode
+ ├─ occupySingleCell
+ ├─ createWave
+ ├─ updateLocalShopeeWaveBinding
+ └─ addToWave
```
-### 特点
+RT 不申请接口号段,也不调用 Shopee picking task 外部接口。
-- **RT 不申请 Shopee 外部波次号**,使用 `shipmentCode` 作为本地 `shopeeWaveCode`
-- `backfillRtShopeeWaveCode` 是 RT 专用方法,仅按仓库、出库单和当前波次字段是否为空执行回填,不再重复判断 `ticketType`
-- **当前一波一单分流不走 ESS 校验**
-- **不走格口分配**(不绑定格口)
-- **一单一个内部波次**
-- **同样受 waveType/orderStructure 包含关系约束**,避免一波一单候选绕过规则订单结构过滤
+## 7. 缺口补单
----
+`fillShortage` 只按关联 `shopee_wave.status=200(USING)` 判断格口是否需要进入补单检查。
-## 5. 推拣货任务当前口径
-
-### 触发条件
-
-`pickType=2`(出库单由上游推入时设置,同时预写入 `shopeeWave`)当前作为待恢复的独立分流口径。
-
-### 方法
-
-当前代码中未保留 `assignPickingTaskSingleWaves` 和 `findPickingTaskShipments` 方法。
-
-### 流程图
-
-```
-当前已落地:
- appendBatchWaveFilter 排除 pickType=2,避免推拣货任务进入普通批量集波。
-
-待恢复:
- 上游推单写入 pickType=2 + shopeeWave 后,
- WES 独立分流入口按一单一波一槽口处理。
+```text
+fillShortage(warehouseCode, workstation)
+├─ findShortage(workstation.id)
+├─ 逐格口读取 currentWaveRule 与 ticketType
+├─ calcNeedQty
+├─ 记录需要补单的格口处理日志
+├─ resolveBoundShipmentGroup
+├─ findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
+└─ assignToCell
```
-### 特点
+补单不重新申请 Shopee 波次号,并保持同一 Shopee 波次的 `groupKey` 一致。
-| 特性 | 说明 |
-|------|------|
-| 波次号来源 | 上游预写入 `shipment_header.shopeeWave`,WES 不走取号逻辑 |
-| 当前状态 | 普通批量集波已过滤 `pickType=2` |
-| 待实现 | 独立分流入口、一单一波一槽口、内部波次运行 |
-| 补单 | 后续仍按“不走补单逻辑”处理 |
-| 取号 | 标准要求推拣货任务使用上游同步的 Shopee 波次号,WES 不走取号逻辑 |
+## 8. 单格口完整分配
----
+`assignToCell` 在格口锁内完成普通订单首次分配或补单:
-## 6. 标准集波主流程
-
-### 6.1 主循环
-
-```
-for (WaveRule rule : rules) {
- List cells = findCells(workstationId)
- // 三层级分组
- List> cellGroups = [
- sameCurRuleCells, // currentWaveRule == rule.code
- ruleCells, // currentWaveRule 为空 && waveRule 匹配
- emptyCells // 两者均为空
- ]
- for (group : cellGroups) {
- assignCellNumbers(warehouseCode, shipmentType, workstationId, rule, group)
- // 命中即返回,不继续尝试下一组格口
- }
-}
+```text
+assignToCell(...)
+├─ 校验 ticketType、shipmentIds、shopeeWaveCode
+├─ 获取 cellLockKey(cell.id)
+├─ pickCellShipments,按剩余容量截断候选
+├─ occupyCell
+├─ createWave
+├─ Shopee 同步
+│ ├─ 新格口 → syncShopeePickingTaskAndFilterAcceptedShipments
+│ └─ 已有同波次格口 → syncShopeeAppendOrderAndFilterAcceptedShipments
+├─ bindBatchShipmentWave
+│ ├─ waveBindLockKey
+│ ├─ isWaveAvailable
+│ └─ updateLocalShopeeWaveBinding
+├─ addToWave
+├─ 非 Flow Pick 达到 maxShipments → shopee_wave.status=400(FULL)
+└─ finally 释放格口锁
```
-### 6.2 assignCellNumbers 核心流程
+`assignAcquiredWaveToCell` 只承接已经取得的 Shopee 波次号,并调用 `assignToCell`。
-```
-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
- │ ├─ bindBatchShipmentWave → 批量订单绑定;成功整批写 shopeeWave,失败整批写 kickOutWave
- │ ├─ addShipmentsToInternalWave → 加入波次 + 运行
- │ └─ 成功后消费当前候选池
- │
- └─ 一个格口消费一批后 break(不再占用其他格口)
-```
+## 9. Shopee picking task 同步
-### 6.3 waveType 与 orderStructure 匹配
+### 9.1 首次同步
-标准文档 V1.4 要求:匹单时除 `WAVE_RULE_FIELD_MATCHING` 字段匹配外,还要先按 `wave_rule.waveType` 与 `shipment_header.orderStructure` 的包含关系过滤候选出库单。
+`syncShopeePickingTaskAndFilterAcceptedShipments` 显式接收 `ticketType`:
-当前实现方法:
+- `RT`:不调用外部接口,直接返回当前单据。
+- 其他类型:始终调用 `sendShopeePickingTaskRequest`。
+- 同步成功后写 `pickingTaskSyncWaveCode`,并更新 `shopee_wave.startPickUploadSts`。
+- 根据 Shopee 返回失败列表解析踢单,失败单写入 `kickOutWave`。
-```
-appendWaveTypeOrderStructureFilter(sql, params, rule)
- → resolveMatchedOrderStructures(rule)
- → SQL: and orderStructure in (:matchedOrderStructures)
-```
+接口映射:
-匹配关系如下:
+| ticketType | 接口 |
+|---|---|
+| SALES_TASK | Sales start picking task |
+| SALES_ORDER | Sales create picking task |
+| RTS / MTO_ORDER | RTS/MTO create picking task |
+| MTO_TASK | MTO start picking task |
-| `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 |
+### 9.2 补单同步
-说明:
+已有同波次格口通过 `syncShopeeAppendOrderAndFilterAcceptedShipments` 调用 append order:
-- `shipment_header.orderStructure` 由 `ShipmentHeaderService#calcOrderStructure` 写入,当前编码为 `1=SSSQ`、`2=SSAQ`、`3=MSAQ`。
-- `wave_rule.waveType` 是规则侧单选类型,不直接等值匹配订单结构,而是通过上表转换成可包含的订单结构集合。
-- `appendWaveTypeOrderStructureFilter` 同时用于普通批量候选和一波一单候选,保证入口一致。
-- 未识别的 `waveType` 不追加订单结构条件;此场景通常表示规则数据异常,应通过规则主数据维护修正。
-- docx 中同时出现 `SSSQ` 与 `SSSQ(Same SKU same Qty)` 两个名称,其中编码 `2` 映射到 `MSAQ`;命名含义按 docx 待确认,当前实现按编码表执行。
+- SALES_ORDER → Sales append order
+- RTS / MTO_ORDER → RTS/MTO append order
-### 6.4 订单池 SQL 基础条件
+## 10. Shopee 波次记录
+
+### 10.1 acquireWave
+
+`acquireWave(warehouseCode, ticketType)` 在 `waveAcquireLockKey(ticketType)` 锁内查询:
```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 = '') -- 未挂起
+where ticketType = :ticketType
+ and sourceType = INTERFACE
+ and waveDate >= 当前时间减 10 小时
+ and status = UNUSED
+order by rand()
+limit 1
```
-### 6.5 groupKey 集波约束
+该方法只返回候选号段,不在此处更新状态。
-标准文档要求:匹单时除规则字段匹配外,还需要根据 `shipment_header.groupKey` 特征值一致才能集在一起。
+### 10.2 createInternalShopeeWave
-当前实现:
+对单据已有的 `shopeeWaveCode`,先按 `code + ticketType` 查询;不存在时创建:
-| 场景 | 方法 | 规则 |
-|------|------|------|
-| 首次空格口集波 | `findAvailableShipmentBatch(..., groupKey='')` | 先查询候选分组,再按优先级选择本轮 `groupKey` |
-| 已绑波次格口补缺口 | `resolveBoundShipmentGroup` + `findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)` | 从当前波次已绑定订单解析特征值,只补相同 `groupKey` 的单 |
-| 空 `groupKey` | `appendGroupKeyFilter` | 空值只匹配空值,避免空值与非空值串波 |
+- `sourceType=INTERNAL`
+- `bizType=NONE`
+- `status=USING`
-这意味着同一 Shopee 波次内不会混入不同 `groupKey` 的订单;如果候选池存在多个 `groupKey`,当前按抓单优先级排序后的首个候选单决定本轮特征值。
+已存在记录不修改。
----
+### 10.3 backfillRtShopeeWaveCode
-## 7. 格口分配策略
+RT 使用已有 `shopeeWaveCode` 或 `shipmentCode`:
-### 7.1 三层级匹配
+- `pickingTaskSyncWaveCode` 为空时回写本地标识。
+- 创建或更新 RT 类型的 `shopee_wave`。
+- 状态设置为 `USING`。
-| 层级 | 方法 | 条件 | 优先级 |
-|------|------|------|--------|
-| 1 | `matchCurRuleCells` | `currentWaveRule == rule.code` | 最高 |
-| 2 | `matchRuleCells` | `currentWaveRule` 为空 且 `waveRule` 逗号分隔匹配 rule.code | 中 |
-| 3 | `matchEmptyCells` | `currentWaveRule` 和 `waveRule` 均为空 | 兜底 |
+## 11. 本地绑定与内部 wave
-标准要求:分播墙明细界面中 `wcs_sorting_wall_cell.waveRule` 取值来自 `wave_rule`,支持多选,多个规则中间用逗号隔开。当前 `matchRuleCells` 通过逗号拆分匹配当前规则编码。
+### 11.1 updateLocalShopeeWaveBinding
-### 7.2 格口抢占
+在 `sortingWallCellLockKey(cell.id)` 锁内:
+1. 从出库单解析唯一 `ticketType`。
+2. 更新 `wcs_sorting_wall_cell` 的 Shopee 波次、当前规则和 ticketType。
+3. 更新内部 `wave` 的规则、分播墙、格口、Shopee 波次和 ticketType。
+4. 更新 `shopee_wave.status`:一波一单类型置为 `FULL`,普通订单保持 `USING`。
+
+该方法不回写 `shipment_header_ext1` 的绑定关系;单据与本地 wave 的关系由内部 wave 承载。
+
+### 11.2 createWave / addToWave
+
+- `createWave`:解析 masterCode,调用 `waveSvc.createInternalWave`,并回写内部 wave 的规则、格口和 ticketType。
+- `addToWave`:调用 `shipmentHeaderService.addMultipleToWave` 后执行 `waveSvc.run`。
+
+## 12. 失败处理
+
+| 失败点 | 当前处理 |
+|---|---|
+| 格口占用失败 | 返回错误,不覆盖其他波次 |
+| 内部 wave 创建失败 | 新占格口调用 `releaseSingleCell` |
+| Shopee 同步失败 | 释放新占格口并调用 `cancelWave` |
+| Shopee 返回踢单 | `markKickOut` 写入 `shipment_header_ext1.kickOutWave` |
+| 本地绑定失败 | 释放新占格口并调用 `cancelWave` |
+| 加入或运行内部 wave 失败 | 释放新占格口并调用 `cancelWave` |
+
+`cancelFailedShopeeWaves` 供计划任务取消状态为 FAILED 且已绑定格口的内部 wave。
+
+## 13. 查询与规则匹配
+
+当前候选查询入口:
+
+- `findSingleShipments`:快速拣选候选。
+- `findTaskTicketShipments`:SALES_TASK / MTO_TASK 候选。
+- `findRtTicketShipments`:RT 候选。
+- `findAvailableShipments` / `findAvailableShipmentBatch`:普通订单首次分配和补单。
+
+规则过滤包括:
+
+- `appendSingleFilter`
+- `appendKickOutFilter`
+- `appendGroupKeyFilter`
+- `appendOrderStructureFilter`
+- `appendRuleMatch`
+- `buildOrderByClause`
+
+普通订单首先确定候选 `groupKey`,再抓取同组订单;补单通过 `resolveBoundShipmentGroup` 延续已有波次的组特征。
+
+## 14. 当前方法关系
+
+```text
+assignShopeeWaveToAcceptedWorkStations
+└─ matchByStation
+ ├─ assignSingles
+ └─ BATCH_PICK
+ ├─ fillShortage
+ │ └─ assignToCell
+ ├─ assignTaskTicket
+ ├─ assignOrder
+ │ └─ assignAcquiredWaveToCell
+ │ └─ assignToCell
+ └─ assignRt
+
+assignSingles / assignTaskTicket / assignRt
+├─ occupySingleCell
+├─ createWave
+├─ updateLocalShopeeWaveBinding
+└─ addToWave
+
+assignToCell
+├─ occupyCell
+├─ createWave
+├─ syncShopeePickingTaskAndFilterAcceptedShipments
+│ 或 syncShopeeAppendOrderAndFilterAcceptedShipments
+├─ bindBatchShipmentWave
+├─ addToWave
+└─ cancelWave / releaseSingleCell
```
-occupySortingWallCell(warehouseCode, cell, rule, shopeeWaveCode)
-```
-
-```sql
-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 波次绑定
-
-```
-快速拣选: updateLocalShopeeWaveBinding(warehouseCode, shopeeWaveCode, shipmentIds, cell, waveId, '', workstation)
-任务单: updateLocalShopeeWaveBinding(warehouseCode, shopeeWaveCode, shipmentIds, cell, waveId, ruleCode, workstation)
-批量订单: bindBatchShipmentWave(warehouseCode, shipmentIds, shopeeWaveCode, cell, waveId, ruleCode, ticketType, workstation)
-```
-
-快速拣选在完成取号、`shopee_wave` 准备、格口抢占和内部 wave 创建后,将 `ticketType` 传入 Shopee picking task 同步方法;仅 RT 跳过外部调用,其他类型均发起同步。快速拣选和任务单同步完成后都直接调用 `updateLocalShopeeWaveBinding`;普通批量订单仍完成参数校验、波次锁、可用性检查、本地绑定回写和解锁。
-
-**流程:**
-
-```
-1. waveBindLockKey
- → 同一 Shopee 波次号绑定串行,避免多个入口同时使用同一号段。
-
-2. isWaveAvailable
- → 获取锁后在方法内直接查询并检查 shopee_wave 是否仍可用,无内部转发层。
-
-3. 状态校验通过 → updateLocalShopeeWaveBinding
- → 统一回写格口、内部 wave 和 shopee_wave 的绑定字段
-
-4. finally 解锁并返回 ResponseMessage
-```
-
-### 8.3 接口对接状态
-
-真实的 3.1.6/3.4.6 绑定接口在工作站绑箱时同步调用;计划分配阶段不调用上游三段式接口。
-
-标准要求补充:销售 `create_picking_task`、RTS-MTO 下发/变更等上游链路需要带入或校验 Shopee 波次号;当前服务侧保留统一合波校验入口,真实 create_picking_task / RTS-MTO 接口契约仍待接入验证。
-
----
-
-## 9. 内部波次创建与运行
-
-### 9.1 createInternalWave
-
-```
-createInternalWave(warehouseCode, rule)
-```
-
-1. `resolveMasterCode(warehouseCode)` — 从系统参数读取波次主表编码(`WCS/EQUIPMENT_MANUFACTURER`)
-2. `waveSvc.createInternalWave(session, warehouseCode, masterCode, ruleCode)` — 创建内部波次
-3. 返回 waveId(Long)
-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`;上游撤单接口仍需在回池链路接入。
-
-标准要求:库存分配优先按效期/先到期先出与 FIFO,其次按工作站点位与库存点位半径最小,同半径内低层货位优先;库存不足单据需踢单并通知上游撤单。当前本文只记录集波服务入口与回池清理口径,库存最优分配和上游撤单通知不应描述为 `WaveRuleMatchService` 已完成。
-
----
-
-## 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 而非 DELETE,Shopee 波次号恢复 100 而非删除
-- **不取消出库单**:`handleFlowPickChangeWaveFailure` 明确注释"不取消出库单"
-- **先绑后解**:绑定顺序为 Shopee 波次号 → 格口 → 内部波次,回滚按反序执行
-
----
-
-## 11. 首次集波内补缺口流程
-
-### 11.1 入口
-
-当前状态:`pollReplenishment/pollReplenishmentOnce` 已删除。补缺口能力在首次集波入口中通过 `fillShortage` 执行。
-
-已具备的依赖方法:
-
-- `fillShortage(...)`:处理当前工作站关联使用中 Shopee 波次的格口。
-- `findShortage(Long workStationId)`:候选资格只判断关联 `shopee_wave.status=200`。
-- `calcNeedQty(...)`:计算当前 Shopee 波次的剩余容量。
-- `assignToCell(...)`:先抢占格口,再请求 Shopee 波次绑定接口,再创建并运行内部波次。
-
-入口不返回分配明细;成功、失败和跳过原因通过系统处理日志记录。
-
-### 11.2 流程
-
-```
-matchByStation(TtxSession, WcsWorkStation)
- │
- └─ fillShortage(warehouseCode, workstation) → shipmentType 直接读取 workstation.shipmentType
- │
- └─ findShortage(workstationId)
- → wcs_sorting_wall_cell
- → shopee_wave.status = 200
- │
- └─ for each cell:
- calcNeedQty
- findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
- assignToCell
-```
-
-### 11.3 fillShortage 逻辑
-
-```
-fillShortage(warehouseCode, workstation):
- │
- ├─ shipmentType 直接读取 workstation.shipmentType
- │
- ├─ findShortage(workstationId) → 仅按 shopee_wave.status=200 选择候选格口
- │
- ├─ for each cell:
- │ ├─ 按 currentWaveRule 读取启用规则
- │ ├─ calcNeedQty → maxShipments - 当前 Shopee 波次已绑单量
- │ ├─ needQty <= 0 → 跳过
- │ ├─ needQty > 0 → 加入需补货格口明细
- │ ├─ resolveBoundShipmentGroup → 从已绑定订单解析当前波次 groupKey
- │ ├─ findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
- │ │ ├─ waveType/orderStructure 匹配
- │ │ ├─ WAVE_RULE_FIELD_MATCHING 字段匹配
- │ │ ├─ 排除 kickOutWave 过滤的已拒单
- │ │ └─ 只补同一 groupKey 单据
- │ └─ assignToCell
- │ ├─ ensureCellReadyForWave → 先抢占格口
- │ ├─ bindBatchShipmentWave → 普通批量订单绑定入口
- │ ├─ createInternalWave
- │ └─ addShipmentsToInternalWave → WaveService#run
- │
- ├─ MSG_WRM_0244 → 记录需补货格口数量及 cellCode|shopeeWaveCode|currentWaveRule|ticketType|needQty
- └─ 返回补单结果
-```
-
-### 11.4 缺口计算
-
-```
-calcNeedQty(warehouseCode, rule, shopeeWaveCode, ticketType)
-```
-
-| 规则类型 | 触发条件 | 补单数量 |
-|----------|----------|----------|
-| Flow Pick | 未完成订单数不高于 `lowThreshold` | `maxShipments - 未完成订单数` |
-| Dynamic Wave | status=200 候选进入执行 | `maxShipments - 当前波次已绑单量` |
-| ticketType 无效 | 执行元数据不完整 | `0` |
-
----
-
-## 12. Flow Pick 换箱换波流程
-
-### 12.1 触发
-
-当前 Flow Pick 换箱换波接口为占位流程,需结合真实触发入口恢复;原计划任务补单入口已删除。
-
-`isFlowPickCellFull`:单品单件 + `countShopeeShipments >= maxShipments`
-
-标准要求:Flow Pick 包含 SSSQ、MSSQ 规则;RTS/MTO 下发单据因按行拆单,也按 Flow Pick 处理。当前服务中的满槽换箱判断以单品单件规则为主,RTS/MTO Flow Pick 细分触发口径按 docx 待确认。
-
-### 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.waveType` 与 `shipment_header.orderStructure` 的包含关系不是通过 `WAVE_RULE_FIELD_MATCHING` 配置,而是由 `appendWaveTypeOrderStructureFilter + resolveMatchedOrderStructures` 固定维护。`WAVE_RULE_FIELD_MATCHING` 只负责其它规则属性与出库单字段的匹配。
-
-### 13.1 数据字典结构
-
-通过数据字典表 `config_detail` + `config_value` 配置,`recordType = 'WAVE_RULE_FIELD_MATCHING'`。
-
-| 字段 | 含义 | 示例 |
-|------|------|------|
-| `identifier` | 出库单字段 | `userDef1`、`shipmentCategory2`、`priority` |
-| `value1` | 规则字段 | `channelId`、`shopId`、`urgentFlag` |
-| `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/value4` 填 `Y`,则只有 `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`, `outerPackagingType`, `outerPackagingId`, `deliveryRegion`, `fragile`, `liquid`, `highValue`, `battery`, `danger`
-
-### 13.5 V1.4 新增/待确认匹单字段
-
-| 标准字段/规则 | 标准要求 | 当前实现/待实现 |
-|---------------|----------|-----------------|
-| `Max SKU Pieces Per Order` | 单订单任一 SKU 件数大于规则阈值时,该订单排除出当前 wave rule,转给低优先级的大件数规则 | 当前 `WaveRule` 域与 `appendRuleFieldMatching` 未见对应字段/SQL,待实现 |
-| `Mix Mode Max SKU Pieces Filter` | 仅 Mix-mode/MSAQ 规则生效,排除单 SKU 件数过高的混合订单,避免混合波次槽位过度丰富 | 当前未见对应字段/SQL,待实现 |
-| SKU 主档字段匹配 | docx 要求 API 列为 SKU 的规则字段需与单据所有明细 SKU 主档字段匹配,全部匹配才能进该规则 | 当前字段匹配 SQL 只处理单头/扩展字段;明细 SKU 全量匹配待实现或待接入 |
-| `multi_attr_list` | docx 要求多值字段用逗号存储时必须全包含;按示例,规则 `A,B,C` 可匹配单据 `A,B`,规则 `B,C` 不匹配单据 `A,B` | 当前 `channelId/fulfillmentChainId` 逗号字段按任一值命中;全包含方向按 docx 待确认,当前待调整 |
-
-### 13.6 执行流程
-
-```
-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 首次集波
-
-```
-assignShopeeWaveToAcceptedWorkStations(TtxSession, String, String) [计划任务入口]
- └─ matchByStation(TtxSession, WcsWorkStation) [统一入口]
- ├─ findEnabledRules
- ├─ assignSingles [快速拣选]
- ├─ assignTaskTicket [Sales/MTO 共用,缺失时创建 shopee_wave]
- ├─ assignOrder [Sales/MTO/RTS 订单共用]
- ├─ assignRt [RT 独立分流]
- ├─ fillShortage [status=200 Shopee 波次格口补缺口]
- │ ├─ findShortage
- │ ├─ calcNeedQty
- │ ├─ resolveBoundShipmentGroup
- │ ├─ findAvailableShipmentBatch(..., shopeeWaveCode, groupKey)
- │ └─ assignToCell
- └─ (标准集波) for each rule: [标准集波]
- ├─ findCells → matchCurRuleCells / matchRuleCells / matchEmptyCells
- └─ assignCellNumbers
- ├─ findSingleShipmentWaveShipments
- ├─ findAvailableShipments
- ├─ createInternalWave
- ├─ acquireUnusedShopeeWave
- └─ for each cell:
- ├─ skipSingleRpln (单品单件)
- ├─ assignToCell
- │ ├─ pickCellShipmentIds
- │ ├─ calcNeedQty
- │ ├─ ensureCellReadyForWave → occupySortingWallCell
- │ ├─ bindBatchShipmentWave
- │ │ ├─ isWaveAvailable
- │ │ └─ updateLocalShopeeWaveBinding
- │ ├─ addShipmentsToInternalWave → run
- │ └─ 成功后消费当前候选池
- └─ (回滚)
- ├─ rollbackInternalWaveIfEmpty
- ├─ rollbackWaveShipments → removeShipmentsFromWave
- └─ rollbackShipmentShopeeWave
-```
-
-### 14.2 首次集波内补缺口
-
-```
-fillShortage
- └─ findShortage(workstationId) → shopee_wave.status=200
- └─ for each cell:
- ├─ calcNeedQty
- ├─ resolveBoundShipmentGroup
- ├─ findAvailableShipmentBatch(..., 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)
-
-工作站查询:
- assignShopeeWaveToAcceptedWorkStations(session, warehouseCode[, workStationCode])
- └─ 候选工作站按启用空格口数降序、工作站 ID 升序处理
- findEnabledWorkStation(warehouseCode, workStation)
-
-波次查询:
- countShopeeShipments(warehouseCode, shopeeWaveCode)
- resolveBoundShipmentGroup(warehouseCode, shopeeWaveCode, ticketType)
- 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=SSSQ`、`2=SSAQ`、`3=MSAQ` | 已落地 |
-| `wave_rule.waveType` 与 `shipment_header.orderStructure` 包含关系 | `appendWaveTypeOrderStructureFilter` + `resolveMatchedOrderStructures` | 已落地 |
-| 同一波次 `groupKey` 一致 | `findAvailableShipmentBatch` 首次选择候选组;补缺口由 `resolveBoundShipmentGroup` 从已绑定订单解析 | 已落地 |
-| 上墙优先级 | `findEnabledRules` 按 `wavePriority desc, id asc`;高优先级不足 `maxShipments` 仍优先消费 | 已落地 |
-| 抓单优先级 | `priority desc`、`cutOffTime asc`、`purchaseTime asc`、`orderTime asc`、`id asc` | 已落地 |
-| `wave_rule.maxShipments` | `resolveShipmentLimit` 使用规则级 `maxShipments`,默认 30 | 已落地 |
-| `wcs_sorting_wall_cell.waveRule` 多选 | 逗号分隔匹配当前规则编码 | 已落地 |
-| `WAVE_RULE_FIELD_MATCHING` | `appendRuleFieldMatching` 按字典追加 `IN/EQ/LE` 条件 | 已落地 |
-| `kickOutWave` 过滤 | `appendKickOutFilter` 对同一 Shopee 波次过滤已拒单 | 已落地 |
-| `Max SKU Pieces Per Order` | 标准字段已同步到文档,当前未见字段/SQL | 待实现 |
-| `Mix Mode Max SKU Pieces Filter` | 标准字段已同步到文档,当前未见字段/SQL | 待实现 |
-| `multi_attr_list` 全包含 | 当前逗号字段按任一值命中;docx 示例方向已记录 | 待调整,按 docx 待确认 |
-| `wave_rule` 字段命名规范 | WES 小驼峰 + base 逻辑创建自定义字段 / 时间 / 用户等字段 | 标准要求已在 docx 强调;当前主数据字段命名仍以代码与迁移为准 |
-| `shipment_header.pickType` 手工拣选切换 | 仅订单池 `100` 状态可更新为 `1/0` | 当前服务侧仅有按单拣选过滤,界面/接口切换待接入 |
-| `ticketType` 映射 | XSCK=1/2、MTO=5/4、RTS=3、RT=6 | 当前文档已在 13.3 详细列出,按此口径匹配 |
-
-### 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 标准需求中仍为占位或待恢复的点
-
-| 标准需求点 | 当前实现状态 | 后续落点 |
-|------------|--------------|----------|
-| create_picking_task / RTS-MTO 上游校验 | 标准要求与 Shopee 波次获取、合波校验衔接;当前仅有服务侧统一预留入口 | 真实接口契约确认后接入 |
-| 库存不足踢单通知上游撤单 | 当前由回池链路清空 `shopeeWave/waveRule`,上游撤单接口仍待接入 | `wms-wave` 回池链路 |
-| 计划任务轮询补单 | 原 `pollReplenishment/pollReplenishmentOnce` 已删除;当前补缺口在首次集波入口 `fillShortage` 执行 | 如需后台轮询,需恢复独立计划任务入口 |
-| 推拣货任务一单一波一槽口 | 普通批量已过滤 `pickType=2`,独立分流入口当前未保留 | 待恢复 `pickType=2 + shopeeWave` 专用分流 |
-| Flow Pick 封箱/换箱接口 | 本地流程已有占位方法,真实 ESS/AGV 接口未接入 | `requestFlowPickSealBox` / `requestFlowPickChangeBox` / `handleFlowPickChangeWaveFailure` |
-| 工作站用户权限控制出库类型 | 当前只校验工作站启用、仓库和不同出库类型隔离 | 用户权限与出库类型绑定仍待实现 |
-| 库存最优分配策略 | docx 要求效期/FIFO、工作站/库存半径、低层货位优先 | 属库存分配链路,本文不标记为 `WaveRuleMatchService` 已落地 |
-
-## 附录:TODO 清单(代码内标注)
-
-| 位置 | TODO | 优先级 |
-|------|------|--------|
-| `resolveMasterCode` 前注释 | 库存最优分配规则对齐 | P2 |
-| `acquireUnusedShopeeWave` 前注释 | Shopee 波次号段申请接口 & 用完续号 | P1 |
-| `requestFlowPickSealBox` Javadoc | Flow Pick 封箱接口正式对接 | P1 |
-| `requestFlowPickChangeBox` Javadoc | Flow Pick 换箱接口正式对接 | P1 |
-| `handleFlowPickChangeWaveFailure` Javadoc | 换波失败完整回滚(订单池恢复/取消任务/AGV 回库) | P1 |