diff --git a/changelogs-v2/2026-09/20_8005_退单户候选列表与提交前预览-新增接口-管理后台.md b/changelogs-v2/2026-09/20_8005_退单户候选列表与提交前预览-新增接口-管理后台.md new file mode 100644 index 00000000..30f724f9 --- /dev/null +++ b/changelogs-v2/2026-09/20_8005_退单户候选列表与提交前预览-新增接口-管理后台.md @@ -0,0 +1,329 @@ +--- +schema: "hl-changelog/v2" +ticket: "8005" +title: "退单户候选列表 GB-ADM-070b 与提交前预览 GB-ADM-070c" +consumer: "admin" +author: "jw(GIT)" +change_type: "新增接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-20" +status_note: "团期「退单户」弹窗两个新端点:候选下拉 GET .../{groupBatchId}/withdraw-candidates 每行给「联系人·团号·订单号·已付金额」并只返回当前可退的户(排除已取消/已完成/出行中,以及已提交待审的户);摘要条 GET .../{groupBatchId}/sub-order/{orderId}/withdraw-preview 在点「提交退团审核」之前给出「待退多少」,零写入,与提交响应逐分一致。A3 /orders 与 GB-ADM-070 提交端点不变。后端已合并 dev-v3(0139aebe4)并部署 TEST,网关实测 AC-1~AC-10 全通过。前端待办:下拉改调新端点并展示四段;选中一户后调预览端点渲染摘要条(不要用 /v3/admin/order/{id}/cancel-preview,那个恒按退改政策算,团期早期阶段会少显示)。注意人数字段是 participantCount(与 A3、GB-ADM-072/073 同名),不是 peopleCount。" +updated_at: "2026-09-20" +base: "dev-v3" +--- + +# order-v3: 退单户候选列表与提交前预览 + +> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3) +> +> **服务**: hl-order-service-v3 (端口 8086) +> **PR**: #8010 +> **Issue**: #8005 +> **日期**: 2026-09-20 +> **影响范围**: 管理后台「团期订单 → 团期详情 → 退单户」弹窗 + +--- + +## ⚠️ 关键变化 + +1. **新增候选下拉端点**,每行给四段:订单联系人 · 团号 · 订单号 · 已付金额。原先前端拿 A3 `/orders` 喂下拉,那是带出行人/需求明细的重装配分页接口,且**没有可退性过滤**。 +2. **新增提交前预览端点**,在点「提交退团审核」之前就能给出「待退定金 ¥11,860」。原先唯一算这个数的入口是提交端点本身(写方法、会建待审申请单)。 +3. **候选只返回当前可退的户**:已取消 / 已完成 / 出行中的户,以及**已提交退单待审**的户,不再出现在下拉里。 +4. 人数字段是 **`participantCount`**,与 A3 子订单项、GB-ADM-072/073 同名。 + +--- + +## 一、背景 + +退单弹窗的下拉每行只显示「未命名 · HL20260920105808925」——兜底文案加一串订单号,运营无法判断要退的是哪一户、退了要退多少钱。 + +后端其实一直在返回姓名与已付金额(A3 的 `customerName` / `paidAmount`),「未命名」是前端兜底文案;但后端确有三个缺口:候选集没有可退性过滤(已提交待审的户能被重复选中,提交端才靠 589529 兜底)、联系人姓名零回落、以及**提交前拿不到预计退款额**。本单补前两个缺口并新开预览端点,A3 与提交端点均不动。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 退单户候选列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/withdraw-candidates` | 新增 | 下拉候选行:联系人/团号/订单号/已付金额 + 人数 + 支付状态中文名 | +| 2 | 退单预览 | GET | `/v3/admin/order/group-batch/{groupBatchId}/sub-order/{orderId}/withdraw-preview` | 新增 | 提交前给两个金额与退改政策,零写入 | + +--- + +## 三、接口详情 + +### 1. 退单户候选列表 `GET /v3/admin/order/group-batch/{groupBatchId}/withdraw-candidates` + +**VO**: `Result>` + +#### 使用场景 + +「退单户」弹窗的「选择要退的子订单」下拉。前端按 `customerName · teamNo · orderNo · 已付 ¥paidAmount` 渲染每一行;`teamNo` 为 null 时省略团号那一段,该行仍要展示。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 团期主订单 ID | 团期不存在返 589500 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|------|------|------| +| orderId | String | 子订单 ID(Long 序列化为字符串,避免精度丢失) | +| orderNo | String | 订单号,`HL` + 17 位,创单即生成、永不变 | +| teamNo | String | 团号,形如 `26-3627`。**订金支付成功后才生成,未付订金为 null** | +| customerName | String | 订单联系人。`order_main.customer_name` 为空时回落**首位出行人**姓名;仍为空返 `null`(前端自行兜文案,后端不返「未命名」) | +| participantCount | Integer | 该户人数(成人+儿童+小童+婴儿)。**与 A3、GB-ADM-072/073 同名**,不是 `peopleCount` | +| paidAmount | BigDecimal | 该户已付(毛累计,含订金+尾款,退款不回减),与 GB-ADM-070 提交响应同源 | +| payStatusName | String | 支付状态中文名:未支付 / 已付定金 / 已付全款 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2101506167098511362/withdraw-candidates +Authorization: Bearer +``` + +#### 响应示例 + +(测试服真实响应,2026-09-20;团期「jw测试1期」5 户,其中 2 户未付订金) + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "orderId": "2101506167043985410", + "orderNo": "HL20260920105808925", + "teamNo": "26-3627", + "customerName": "张三", + "participantCount": 6, + "paidAmount": 2000.00, + "payStatusName": "已付定金" + }, + { + "orderId": "2101507603395973121", + "orderNo": "HL20260920110351387", + "teamNo": null, + "customerName": "晓辉", + "participantCount": 2, + "paidAmount": 0.00, + "payStatusName": "未支付" + } + ], + "success": true +} +``` + +#### 空数据 / 降级响应 + +团期下没有可退的户(全部已取消/已完成/出行中,或全部已提交待审)时返回空数组,不是 null: + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 错误响应 + +```json +{ "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null } +``` + +| code | 触发条件 | +|------|----------| +| 589500 | 团期不存在(含已软删) | +| 589507 | 没有 `group-batch:view` 权限 | +| 401 | 未登录(网关拦截) | + +#### 业务边界 + +- 只读,零副作用。 +- 只返回订单状态 ∈ {`PENDING_PAY`,`CUSTOMIZING`,`PENDING_DEPARTURE`} 且**没有在途(PENDING)退单申请**的户。 +- 未付订金的户**不排除**:`teamNo` 为 null、`paidAmount` 为 `0.00` 照常下发。 +- 排序与 A3 一致(按子订单 ID 升序)。 +- 该户一旦提交退单申请,立即从本列表消失;申请被驳回后重新出现。 + +### 2. 退单预览 `GET /v3/admin/order/group-batch/{groupBatchId}/sub-order/{orderId}/withdraw-preview` + +**VO**: `Result`(与 GB-ADM-070 提交响应同构,仅 `approvalId` 为 null) + +#### 使用场景 + +下拉里选中一户后、点「提交退团审核」**之前**,渲染摘要条「退款户:陈昊 · 2 人 · 定制师 王浩 · 待退定金 ¥11,860」。联系人与人数取候选行,定制师与两个金额取本端点。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 团期主订单 ID | 团期不存在返 589500 | +| orderId | Path | Long | 是 | 子订单 ID | 不属于该团期返 589502 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|------|------|------| +| approvalId | String | **恒为 null**——尚未建单,本端点不写库 | +| paidAmount | BigDecimal | 该户已付 | +| estimatedRefundAmount | BigDecimal | 预计退款额。`FULL_DEPOSIT` 模式下基数是**订金应付额**(不是已付额) | +| refundMode | String | `FULL_DEPOSIT`(招募中/资源准备中全退定金)/ `POLICY`(物资准备中/待出发按退改政策阶梯扣) | +| refundPolicy | Object | 退改政策明细,仅 `POLICY` 模式给,否则 null | +| consultantName | String | 该户定制师姓名(信息项,不是审批人) | +| warnings | Array | 非阻断提示,如满团提示、订金为 0 提示;无提示时为空数组 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2101506167098511362/sub-order/2101506167043985410/withdraw-preview +Authorization: Bearer +``` + +#### 响应示例 + +(测试服真实响应,2026-09-20;团期招募中 → `FULL_DEPOSIT`,该户已付 2000、订金应付 9000) + +```json +{ + "code": 200, + "message": "成功", + "data": { + "approvalId": null, + "paidAmount": 2000.00, + "estimatedRefundAmount": 9000.00, + "refundMode": "FULL_DEPOSIT", + "refundPolicy": null, + "consultantName": "金卫", + "warnings": [] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +`POLICY` 模式下退改政策取不到时 `refundPolicy` 为 null,两个金额照常返回;`warnings` 无提示时是空数组而非 null。 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "approvalId": null, + "paidAmount": 500.00, + "estimatedRefundAmount": 500.00, + "refundMode": "POLICY", + "refundPolicy": null, + "consultantName": null, + "warnings": [] + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ "code": 589501, "message": "团期状态不允许当前操作", "data": null } +``` + +| code | 触发条件 | +|------|----------| +| 589500 | 团期不存在 | +| 589501 | 团期已到出行中及以后(与提交端点同口径,走售后退款) | +| 589502 | 该子订单不属于本团期 | +| 589507 | 没有 `group-batch:view` 权限 | + +#### 业务边界 + +- **零写入**:不建 PENDING 申请单、不写状态时间线、不动订单/名额/金额。测试服实测调用前后 `group_batch_approval` 行数不变。 +- 与提交端点走**同一套**退款模式解析与金额估算,两个金额与提交后逐分一致(`FULL_DEPOSIT` 与 `POLICY` 两种模式均已实测对照)。 +- 不判「是否已有在途申请」:已提交待审的户本就不在候选列表里,预览是只读的。 +- 实退以**审批通过时**按当时团期状态重算的为准;`POLICY` 下距出发天数每天在变。 + +--- + +## 四、契约约束与正确调用方式 + +| 场景 | 做法 | +|------|------| +| ✅ 渲染退单下拉 | 调 `withdraw-candidates`,按 `customerName · teamNo · orderNo · 已付 ¥paidAmount` 四段渲染 | +| ✅ `teamNo` 为 null | 省略团号那一段,**该行仍要展示**(未付订金的户照样能退) | +| ✅ `customerName` 为 null | 前端自行兜文案(如「未命名」);后端只在真没有姓名时返 null | +| ✅ 渲染摘要条的「待退多少」 | 调 `withdraw-preview` | +| ❌ 继续拿 A3 `/orders` 喂下拉 | 它没有可退性过滤,已取消户、已提交待审的户都会出现,运营可能重复提交 | +| ❌ 用 `/v3/admin/order/{id}/cancel-preview` 算待退金额 | 那个恒按退改政策算,团期早期阶段(全退定金)会少显示 | +| ❌ 用 `peopleCount` 取人数 | 本端点是 `participantCount`,与 A3、GB-ADM-072/073 一致 | +| ❌ 把 `estimatedRefundAmount` 当最终退款额 | 实退按审批通过时重算 | + +--- + +## 六、边界行为 + +- 团期下无可退户 → `data: []`。 +- 未付订金户 → `teamNo: null`、`paidAmount: 0.00`,仍在候选内。 +- 联系人为空 → 回落首位出行人姓名;出行人也无名或无出行人 → `customerName: null`。 +- 该户已提交退单待审 → 从候选消失;驳回后重新出现(实测:5 户 → 提交后 4 户 → 驳回后 5 户)。 +- 团期到出行中及以后 → 预览返 589501,与提交端点一致。 +- `FULL_DEPOSIT` 模式 `estimatedRefundAmount` 取**订金应付额**,可能大于 `paidAmount`(实测已付 2000 / 预计退 9000)。 +- 低权限角色(无 `group-batch:view`)→ 589507;`SUPER_ADMIN` / `GROUP_BATCH_MANAGER` / `ADMIN` / `FINANCE` 放行。 + +--- + +## 七、不影响范围 + +- **仅新增**两个只读端点,未改任何既有接口的入参、出参与行为。 +- **零影响**: + - A3 `GET .../{groupBatchId}/orders`(不动,仍带出行人/需求明细) + - GB-ADM-070 提交退单 `POST .../sub-order/{orderId}/withdraw`(不动,重复提交仍按 589529 拒) + - GB-ADM-072/073/074/075 审批列表、详情、通过、驳回 + - 数据库:无表结构变更、无 Flyway + - 网关:新端点落在既有 `/v3/admin/**` 通配路由内,无需改路由 + +--- + +## 八、测试环境已验证 + +被测版本:hl-order-service-v3 = dev-v3 `0139aebe4`(2026-09-20 12:53 部署,双实例滚动完成)。取证前确认面板检出 `dev-v3 @ 0139aebe4`、`hl-order-service-v3` 与 `hl-gateway` 均 running;部署前同样两条路径返回业务码 404,可证被测的就是本单代码。网关 `https://api.test.1814.love:9443` 实测: + +``` +AC-1 候选 5 行,四段齐全 + participantCount + payStatusName ✓ +AC-2 未付订金 2 户(2101507603395973121 / 2101508527082336258)teamNo=null、paidAmount=0.00,仍在列 ✓ +AC-3 customer_name 置空串 → 回落「回落出行人甲」;删出行人 → null;出行人姓名为空格 → null; + 三种情况该户均仍在候选内,响应中「未命名」出现 0 次 ✓(夹具已回滚) +AC-4 对张三提交退单 → 候选 5→4 户、该户消失;驳回后 4→5 户恢复 ✓ +AC-5 候选 paidAmount 与 GB-ADM-070 提交响应逐分一致:FULL_DEPOSIT 2000.00=2000.00、POLICY 500.00=500.00 ✓ +AC-6 SALES → 589507(candidates 与 preview 均拦);SUPER_ADMIN / GROUP_BATCH_MANAGER / ADMIN / FINANCE → 200 ✓ +AC-7 预览 vs 提交 estimatedRefundAmount:FULL_DEPOSIT 9000.00=9000.00、POLICY 500.00=500.00,refundMode 亦一致 ✓ +AC-8 预览调用前后 group_batch_approval 行数 55→55 不变;提交后才 56 ✓ +AC-9 TRIP_FINISHED 团期:预览 589501、提交 589501,口径一致 ✓ +AC-10 提交时传 reason → 审批列表与详情均回读到该原因 ✓ +``` + +本地:定向 61/0/0(新增 12 例);order-v3 全量两遍 11453/F1 + 748/F0,唯一失败 `MapperBoundaryArchTest.non_refund_not_depend_on_refund_mapper` 为 payment 域基线既存(干净 dev-v3 同样失败),与本单无交点。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8005](https://git.1814.love:8443/wx/HL/issues/8005) +- 关联 PR: [wx/HL#8010](https://git.1814.love:8443/wx/HL/pulls/8010) +- 接口文档:`docs/group/团期模块接口文档-v2.0.html` 已补 GB-ADM-070b / GB-ADM-070c 两节与判权表两行 + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8005](https://git.1814.love:8443/wx/HL/issues/8005) +- **PR**: [#8010](https://git.1814.love:8443/wx/HL/pulls/8010) +- **Merge commit**: [0139aebe4](https://git.1814.love:8443/wx/HL/commit/0139aebe4) + +### 联系人 + +- **后端负责人**: @jw +- **前端负责人**: @mmg