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 |