--- schema: "hl-changelog/v2" ticket: "7154" title: "团期财务总览与预支复用订单预支:财务 Tab 三个只读端点 + 预支创建语义改造" consumer: "admin" author: "jw(GIT)" change_type: "新增接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "af7cc62b" target_release: "hl-ui@af7cc62b" verified_at: "2026-09-08" status_note: "后端 PR #7170 已合 dev-v3 并部署测试服(网关 finance/advances/payee-candidates 实测 200),原 backend_status/gateway_status=pending 系 09-06 陈旧快照已修正为 deployed/verified。前端团期财务 Tab 与预支接真已交付:groupBatchFinance.js 五端点+FinanceTab(四金额卡/逐户付款表/预支记录/整团核算口径行)+GroupAdvanceModal+detail 挂财务 Tab+store 键缓存+AdvanceApprovalList scope 筛选;金额全后端权威值零反算。ref af7cc62b,checkpoint 全绿含 Vitest 全量+生产构建。" updated_at: "2026-09-24" base: "dev-v3" --- # 团期: 财务总览与预支复用订单预支 > **服务**: hl-order-service-v3 > **PR**: #7170 > **Issue**: #7154 > **日期**: 2026-09-06 > **影响范围**: 管理后台团期详情「财务」Tab、底部操作条「预支」弹窗、财务管理→预支审批列表 --- ## ⚠️ 关键变化 团期财务 Tab 与团期预支弹窗此前**在 hl-ui 里完全不存在**(全仓零调用点),本次补齐后端。 四条前端必读: 1. **`POST .../group-batch/{id}/advance` 入参整体更换**。旧契约 `{amount, remark}` 是后端造了、前端从没接过的端点(已核 hl-ui v2.1 全仓无调用点),故**不是破坏性变更**;新契约与订单级预支**逐字一致**,预支弹窗组件可整体复用。 2. **「已预支」拆成三个数,旧 `advanceTotal` 已废**。旧语义是「提交即计入」(无审批),与新口径不等价:展示用 `advanceApproved`(只计已通过),额度用 `advanceAvailable`。**不要再用一个数**。 3. **预支上限一律读后端 `advanceAvailable`,不要前端自算**。订单级弹窗现在是本地 `balanceAmount − Σ记录` 算的,依赖记录列表已拉全;团期场景该算法不可靠。 4. **「设置报账人」传的是产品侧 `productBatchId`**,从团期详情接口取,**不是**财务 Tab 路径上的 `groupBatchId`,两者不同值。该端点后端零改动。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 团期财务总览 | GET | `/v3/admin/order/group-batch/:id/finance` | 新增 | 四张金额卡 + 逐户付款 + 整团合计 | | 2 | 团期预支记录 | GET | `/v3/admin/order/group-batch/:id/advances` | 新增 | 团期级 + 各子订单级逐笔,不分页 | | 3 | 领款人候选 | GET | `/v3/admin/order/group-batch/:id/advance/payee-candidates` | 新增 | 预支弹窗「借款对象」下拉 | | 4 | 发起团期预支 | POST | `/v3/admin/order/group-batch/:id/advance` | 修改 | 路径不变,入参与返回体更换;创建即进待审批 | | 5 | 预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 修改 | 容忍团期级行 + `scope` 筛选 + keyword 六路 | | 6 | 整团核算汇总 | GET | `/v3/admin/order/group-batch/:id/settlement/summary` | 修改 | 出参新增两个整团预支只读字段 | **零改动复用**(同一套审批流程,前端无需改):`PUT /v3/admin/order/advance/:id/approve`、 `PUT /v3/admin/order/advance/:id/reject`、`DELETE /v3/admin/order/advance/:id`、 `PUT /v3/admin/group-batch/:id/staff/:id/reporter-rank`。 --- ## 三、接口详情 ### 1. 团期财务总览 `GET /v3/admin/order/group-batch/:id/finance` **VO**: `GroupBatchFinanceRespVO` #### 使用场景 团期详情切到「财务」Tab 时加载。一次返回顶部四张金额卡、逐户付款表与整团合计、已退团户数、主/次报账人,Tab 全部内容一个请求搞定。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `id` | path | Long | ✓ | 正整数 | 团期 ID(订单侧 `order_group_batch.group_batch_id`) | 无 query 参数,无请求体。需 `group-batch:finance:view` 权限码(比 `group-batch:view` 更严)。 #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `receivableAmount` | BigDecimal | 整团应收 = Σ 活跃子订单(订单金额 + 附加费 − 优惠) | | `receivedAmount` | BigDecimal | 整团已收 = Σ 已付(累计毛额,不冲抵退款) | | `unpaidAmount` | BigDecimal | 整团待收 = 应收 − 已收,下限 0,即本期总尾款 | | `advanceApproved` | BigDecimal | 已预支(只计已通过)。**第四张卡取此值,不是三个数之和** | | `advancePending` | BigDecimal | 待审批预支,占额度但未出账,不计入「已预支」卡 | | `advanceAvailable` | BigDecimal | 可支取余额 = 尾款池 − (已通过 + 待审批),下限 0,即预支上限 | | `withdrawnCount` | Integer | 已退团户数(前端只拉活跃集,算不出这个数) | | `primaryPayeeName` | String | 主报账人姓名,未设置为 `null`(后端不兜底默认导游) | | `secondaryPayeeName` | String | 次报账人姓名,未设置为 `null` | | `totals.totalPrice` / `.paidAmount` / `.unpaidAmount` | BigDecimal | 逐户表末行「整团合计」,**服务端算**,与顶部卡同源 | | `items[].orderId` | String | 子订单 ID(字符串回传防精度丢失) | | `items[].orderNo` | String | 子订单号 | | `items[].customerName` | String | 客户姓名(不返回手机号与证件号) | | `items[].consultantName` | String | 定制师姓名 | | `items[].totalPrice` | BigDecimal | 本户应收 | | `items[].paidAmount` | BigDecimal | 本户已付。⚠️ 是**全额已付**,不是真定金拆分 | | `items[].unpaidAmount` | BigDecimal | 本户待收尾款 | | `items[].payStatus` | String | 仅 `UNPAID` / `DEPOSIT_PAID` / `FULLY_PAID` **三值** | | `items[].settleStatus` | String | `SETTLED` / `PENDING_BALANCE` / `WITHDRAWN`,**状态胶囊取此值** | | `items[].settleStatusText` | String | 结清状态中文,可直接渲染 | #### 请求示例 ```http GET /v3/admin/order/group-batch/90211/finance ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": { "receivableAmount": 40200.00, "receivedAmount": 32900.00, "unpaidAmount": 7300.00, "advanceApproved": 0.00, "advancePending": 0.00, "advanceAvailable": 7300.00, "withdrawnCount": 0, "primaryPayeeName": "张领队", "secondaryPayeeName": null, "totals": { "totalPrice": 40200.00, "paidAmount": 32900.00, "unpaidAmount": 7300.00 }, "items": [ { "orderId": "770145", "orderNo": "GT-26-0081", "customerName": "罗敏", "consultantName": "李雯", "totalPrice": 12300.00, "paidAmount": 12300.00, "unpaidAmount": 0.00, "payStatus": "FULLY_PAID", "settleStatus": "SETTLED", "settleStatusText": "已结清" }, { "orderId": "770147", "orderNo": "GT-26-0083", "customerName": "汪洋", "consultantName": "陈璐", "totalPrice": 12300.00, "paidAmount": 5000.00, "unpaidAmount": 7300.00, "payStatus": "DEPOSIT_PAID", "settleStatus": "PENDING_BALANCE", "settleStatusText": "待收尾款" } ] } } ``` #### 空数据 / 降级响应 无活跃子订单时,六个金额字段均为 `0.00`(**不是 null**),`items` 为空数组,`withdrawnCount` 为 `0`,两个报账人为 `null`。 #### 错误响应 | 码 | 含义 | |---|---| | `589500` | 团期不存在或已软删 | | `589507` | 缺 `group-batch:finance:view` 权限 | ```json { "code": 589500, "msg": "团期不存在", "data": null } ``` #### 业务边界 - 已取消与已软删子订单**不进任何金额、不进 `items`**,只贡献 `withdrawnCount`。 - 隐式团(`IMPLICIT_SINGLE`)按一单退化开放,字段结构不变,**前端不得因团类型走两套渲染分支**。 - 顶部「已收(定金)」卡与逐户「已付定金」列,语义都是**全额已付**,不区分定金/尾款;已结清户该列等于应收。 - 整团应收与团期详情 `totalReceivable`、看板列表该行应收**三处同源**,数值必然一致。 --- ### 2. 团期预支记录 `GET /v3/admin/order/group-batch/:id/advances` **VO**: `List` #### 使用场景 财务 Tab 下半部「预支记录」区块,以及预支弹窗内的记录列表。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `id` | path | Long | ✓ | 正整数 | 团期 ID | 无 query 参数,**不分页**,一次返回本期全部。 #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `id` | String | 预支单 ID | | `payeeStaffId` | String | 领款人主键快照 | | `payeeName` / `payeeRole` / `payeeRoleText` | String | 领款人姓名 / 角色码 / 角色中文 | | `advanceType` | String | 借款类型 | | `amount` | BigDecimal | 预支金额 | | `purpose` / `voucherUrl` | String | 用途说明 / 凭证 URL | | `status` / `statusText` | String | `SUBMITTED` / `APPROVED` / `REJECTED` 及其中文 | | `rejectReason` | String | 驳回原因 | | `createdByName` | String | 申请人姓名 | | `createTime` / `submittedAt` / `approvedAt` | String | 创建 / 提交 / 审批时间 | | `approvedBy` | String | 审批人姓名 | | `scope` | String | `ORDER` 订单级 / `GROUP_BATCH` 团期级(**本次新增**) | | `scopeName` | String | 归属维度中文,可直接渲染标签(**本次新增**) | | `orderNo` | String | 子订单号,`scope=ORDER` 时非空;团期级为 `null`(**本次新增**) | #### 请求示例 ```http GET /v3/admin/order/group-batch/90211/advances ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": [ { "id": "31005", "scope": "GROUP_BATCH", "scopeName": "团期预支", "orderNo": null, "payeeName": "朝鲁门", "payeeRole": "DRIVER", "payeeRoleText": "司机", "advanceType": "住宿押金", "amount": 5000.00, "purpose": "沿途住宿押金", "status": "APPROVED", "statusText": "已通过", "createdByName": "李雯", "submittedAt": "2026-08-18 22:58:26", "approvedAt": "2026-08-19 09:12:03" } ] } ``` #### 空数据 / 降级响应 无预支时返回空数组 `[]`,前端显示空态文案「暂无预支 · 在底部「预支」发起,记录将在此显示」(文案由前端提供,后端不返回)。 #### 错误响应 | 码 | 含义 | |---|---| | `589500` | 团期不存在 | | `589507` | 缺 `group-batch:finance:view` 权限 | ```json { "code": 589500, "msg": "团期不存在", "data": null } ``` #### 业务边界 - 返回**团期级 + 各子订单级**全部预支,按创建时间倒序。 - **本接口不返回累计金额**,累计取接口 1 的 `advanceApproved` / `advancePending` / `advanceAvailable`。 --- ### 3. 领款人候选 `GET /v3/admin/order/group-batch/:id/advance/payee-candidates` **VO**: `List` #### 使用场景 预支弹窗「借款对象」下拉。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `id` | path | Long | ✓ | 正整数 | 团期 ID(订单侧),服务端内部换算成产品侧,前端不感知 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `id` | Long | 候选 ID,即创建预支的 `payeeStaffId`(资源域 `staff.staff_id`) | | `staffName` | String | 姓名 | | `staffRole` | String | 角色代码 | | `staffRoleText` | String | 角色中文 | | `reporterRank` | String | `PRIMARY` / `SECONDARY` / `NONE` | | `isDefault` | Boolean | 主报账人为 `true`,前端默认选中 | #### 请求示例 ```http GET /v3/admin/order/group-batch/90211/advance/payee-candidates ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": [ { "id": 88101, "staffName": "张领队", "staffRole": "GUIDE", "staffRoleText": "导游", "reporterRank": "PRIMARY", "isDefault": true }, { "id": 88102, "staffName": "朝鲁门", "staffRole": "DRIVER", "staffRoleText": "司机", "reporterRank": "NONE", "isDefault": false } ] } ``` #### 空数据 / 降级响应 团期未配人员时返回空数组 `[]`;此时预支无法提交(借款对象必填)。 #### 错误响应 | 码 | 含义 | |---|---| | `589500` | 团期不存在 | | `589507` | 缺 `group-batch:finance:view` 权限 | ```json { "code": 589500, "msg": "团期不存在", "data": null } ``` #### 业务边界 - 候选 = 本团**全部**人员(与订单级「可选本单任一人员」口径一致),排序:主报账人 → 次报账人 → 其余。 - **不返回手机号**,与订单级候选保持一致。 --- ### 4. 发起团期预支 `POST /v3/admin/order/group-batch/:id/advance` **VO**: `OrderAdvanceRespVO` #### 使用场景 财务 Tab 底部操作条「预支」→ 弹窗填写 → 「申请预支」。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `id` | path | Long | ✓ | 正整数 | 团期 ID | | `payeeStaffId` | body | Long | ✓ | 须属本团人员 | 借款对象,取候选下拉项的 `id` | | `advanceType` | body | String | ✓ | 字典 `advance_type` 的 `dictValue` | 借款类型 | | `amount` | body | BigDecimal | ✓ | > 0 且 ≤ `advanceAvailable` | 预支金额 | | `purpose` | body | String | — | ≤ 255 | 用途说明 | | `voucherUrl` | body | String | — | ≤ 512 | 凭证 URL | > ⚠️ 旧入参 `{amount, remark}` **已废除**。`remark` 无对应字段,改用 `purpose`。 #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `id` | String | 新建的预支单 ID(旧实现返回 `Void`,**现在回传**) | | `payeeStaffId` | String | 领款人主键快照(团期级存资源域 `staff.staff_id`) | | `payeeName` / `payeeRole` / `payeeRoleText` | String | 领款人姓名 / 角色码 / 角色中文 | | `advanceType` | String | 借款类型 | | `amount` | BigDecimal | 预支金额 | | `purpose` / `voucherUrl` | String | 用途说明 / 凭证 URL | | `status` / `statusText` | String | 创建后恒为 `SUBMITTED` / 「待审批」 | | `createdByName` | String | 申请人姓名 | | `createTime` / `submittedAt` | String | 创建 / 提交时间 | #### 请求示例 ```http POST /v3/admin/order/group-batch/90211/advance { "payeeStaffId": 88102, "advanceType": "住宿押金", "amount": "3000.00", "purpose": "沿途住宿押金", "voucherUrl": null } ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": { "id": "31006", "payeeStaffId": "88102", "payeeName": "朝鲁门", "payeeRole": "DRIVER", "payeeRoleText": "司机", "advanceType": "住宿押金", "amount": 3000.00, "purpose": "沿途住宿押金", "status": "SUBMITTED", "statusText": "待审批", "createdByName": "李雯", "submittedAt": "2026-09-06 15:20:11" } } ``` #### 空数据 / 降级响应 数据字典服务不可用时,借款类型校验降级为服务端内置集合,**不影响正常类型提交**。 #### 错误响应 | 码 | 含义 | |---|---| | `589500` | 团期不存在 | | `589507` | 缺 `group-batch:finance:advance` 权限 | | `589541` | 团期状态不可发起预支。⚠️ 2026-09-24 订正:原文误写为 `589538`(号段顺延后实际落 589541);允许状态已两次放宽,现为「进入核单前都可以」(#8270 / #8322) | | `589542` | 房 / 车 / 导 / 摄四项未配齐。⚠️ 2026-09-24 订正:原文误写为 `589539`(该号实为 #7178「名额调整量不能为 0」);**#8270 起本码不再返回** | | `585003` | 预支金额必须大于 0 | | `585004` | 预支金额超过可用余额上限 | | `585006` | 借款类型非法 | | `585007` | 借款对象不属于本团期人员 | ```json { "code": 585004, "msg": "预支金额超过可用余额上限", "data": null } ``` #### 业务边界 - **双前置闸门**:团期状态 + 四项资源全就绪,缺一即拒(此前服务端两道都没有,只有前端做了置灰)。⚠️ 已变更:#8270 删除四项配齐门,#8322 起进入核单前都可发起,且领款人限定为本团主报账人(589557),见 `24_8322_…` 条目。 - **上限走团期统一池**:团期级与各子订单级**共扣一池**。团期把整团尾款预支满后,该团任一子订单再发起订单级预支同样会被 `585004` 拒——这是本次修复的超支漏洞。 - 创建后进入**站内财务审批**,走既有 `advance-approvals` 列表与 approve / reject / 撤回三端点,与订单级完全一致。 - 一期仍**只记账不出款**,实际出款走财务既有付款流程。 - 同一团期 + 同一金额 5 秒内重复提交只成功一次(幂等)。 --- ### 5. 预支审批列表 `GET /v3/admin/order/advance-approvals/page` **VO**: `PageResult` #### 使用场景 财务管理 → 预支审批。团期级预支与订单级预支**混排在同一列表、走同一审批流程**。 #### 入参 新增一个可选 query 参数,其余入参不变: | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `scope` | query | String | — | `ALL` / `ORDER` / `GROUP_BATCH` | 归属维度筛选,缺省 `ALL`(两类混排,与改造前行为一致) | `keyword` 语义扩展:由「订单号 / 团号 / 产品名」三路扩为**六路**,另加「团期号 / 团期名 / 团期产品名」。改造前搜索框标着「团号 / 产品」却搜不出团期级预支。 #### 出参 每行新增四个字段,其余不变: | 字段 | 类型 | 说明 | |---|---|---| | `scope` | String | `ORDER` / `GROUP_BATCH` | | `scopeName` | String | 归属维度中文 | | `batchStatus` | String | 团期状态编码,仅 `scope=GROUP_BATCH` 时非空 | | `batchStatusName` | String | 团期状态中文 | **既有字段按 `scope` 择一填充,前端零改动即可显示**: | 字段 | `scope=ORDER` | `scope=GROUP_BATCH` | |---|---|---| | `teamNo`(团号列) | 订单团号 | **团期号** | | `productName` | 订单产品名 | 团期产品名 | | `departDate` / `returnDate`(行程列) | 订单出行日期 | 团期出团 / 返程日 | | `orderAmount` / `paidAmount` | 本单应收 / 已收 | **整团应收 / 已收** | | `orderNo` / `consultantName` | 有值 | `null`(前端显「—」) | | `orderStatus` / `flowStatus` / `payStatus` / `settlementStatus` | 有值 | `null`,改看 `batchStatus` | #### 请求示例 ```http GET /v3/admin/order/advance-approvals/page?status=SUBMITTED&scope=GROUP_BATCH&page=1&pageSize=20 ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": { "records": [ { "id": "31006", "scope": "GROUP_BATCH", "scopeName": "团期预支", "orderNo": null, "teamNo": "GT-26-05", "productName": "呼伦贝尔草原 5 日游", "departDate": "2026-09-20", "returnDate": "2026-09-24", "consultantName": null, "orderStatus": null, "batchStatus": "PENDING_DEPARTURE", "batchStatusName": "待出发", "orderAmount": 40200.00, "paidAmount": 32900.00, "payeeName": "朝鲁门", "advanceType": "住宿押金", "amount": 3000.00, "status": "SUBMITTED", "statusText": "待审批" } ], "total": 1, "page": 1, "pageSize": 20 } } ``` #### 空数据 / 降级响应 无匹配记录时 `records` 为空数组,`total` 为 `0`。 #### 错误响应 沿用改造前,本次未新增错误码。 ```json { "code": 589507, "msg": "无操作权限", "data": null } ``` #### 业务边界 - **前端必须容忍 `orderId` / `orderNo` 为 `null`**(团期级行不挂订单)。 - 审批通过 / 驳回 / 撤回三个端点**零改动**,对团期级行行为与订单级完全一致。 - 团期级预支走**站内财务审批(资金审批)**,与流团 / 退单户的企微 OA **业务审批**是两条线,不合并。 --- ### 6. 整团核算汇总 `GET /v3/admin/order/group-batch/:id/settlement/summary` **VO**: `GroupBatchSettlementSummaryRespVO` #### 使用场景 整团核算页。本次新增两个只读字段,用于**核单时把整团预支从主报账人代收的尾款中扣回**。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `id` | path | Long | ✓ | 正整数 | 团期 ID。入参未变 | #### 出参 新增两个字段,其余不变: | 字段 | 类型 | 说明 | |---|---|---| | `groupAdvanceApproved` | BigDecimal | 整团已拨付预支(**只含团期级**),核算时单独成一行扣回 | | `groupAdvancePending` | BigDecimal | 整团待审批预支,占额度未出账,仅提示 | #### 请求示例 ```http GET /v3/admin/order/group-batch/90211/settlement/summary ``` #### 响应示例 ```json { "code": 0, "msg": "success", "data": { "settledOrderCount": 3, "totalActiveOrderCount": 3, "subOrderTotalActualCost": 21000.00, "sharedCostTotal": 6000.00, "grandTotalCost": 27000.00, "groupAdvanceApproved": 5000.00, "groupAdvancePending": 0.00 } } ``` #### 空数据 / 降级响应 无团期级预支时两个字段均为 `0.00`(不是 null)。 #### 错误响应 沿用改造前。 ```json { "code": 589500, "msg": "团期不存在", "data": null } ``` #### 业务边界 - 🚫 **只含团期级预支,不含子订单级**。子订单级预支已进各户的核单报销单与对账,整团层再算一次会**同一笔钱扣两遍**。 - 🚫 **不计入 `grandTotalCost`**。预支是**资金拨付**不是成本,计入会与核销后的真实成本科目重复计成本。前端渲染为「整团成本 X / 其中已拨付预支 Y」的独立一行,**不参与任何成本或毛利公式**。 - 逐户报销单与逐户对账**一个字未改**。 --- ## 四、契约约束与正确调用方式 1. **财务 Tab 一次请求拿全**:接口 1 已含四张卡、逐户表、整团合计、退团户数、报账人,不要再拼 `/orders`。 2. **金额一律用服务端给的值**。整团合计由服务端算并与四张卡同源;前端自行 sum 会与卡片对不上。 3. **预支上限读 `advanceAvailable`**,不要用「待收尾款 − 记录之和」本地推算。 4. **状态胶囊读 `settleStatus`**,不要用 `payStatus` 推——后者只有三值,表达的是支付事件不是结清与否,已退团户用它渲染会显示成「已付定金」。 5. **设置报账人用产品侧 `productBatchId`**(团期详情接口取),不是财务 Tab 路径上的 `groupBatchId`。 --- ## 五、数据库行为 > 只写外部可观察行为。 | 动作 | 可观察结果 | |---|---| | 发起团期预支 | 新增一条待审批预支记录,立即出现在本团期预支记录列表与预支审批列表;团期与订单的任何金额字段**均不变动**(预支不改应收/已收/待收) | | 审批通过 | 该记录转为「已通过」,财务 Tab 的「已预支」增加、「待审批预支」减少、「可支取余额」不变(原本就已占额度) | | 审批驳回 / 撤回 | 该记录转为「已驳回」或从列表消失,占用的额度**当场释放**,「可支取余额」回升 | | 上线迁移(一次性) | 存量团期原有的累计预支金额,会转成一条「已通过」的**期初结转**记录出现在预支记录列表,金额与迁移前的累计值相等;该记录无领款人与凭证,按借款类型「期初结转」可识别 | **幂等**:同一团期 + 同一金额 5 秒内重复提交只成功一次。 **并发**:同一团期的预支申请串行处理,两个管理员同时提交不会双双突破可支取余额。 --- ## 六、边界行为 | 场景 | 行为 | |---|---| | 团期无活跃子订单 | 六个金额字段 `0.00`,`items` 空数组 | | 子订单已取消 | 不进金额、不进 `items`,只计入 `withdrawnCount` | | 未设置报账人 | `primaryPayeeName` 为 `null`,后端不兜底默认导游 | | 团期状态为招募中 / 核单中 | 发起预支返回 `589541`(2026-09-24 订正码值;#8322 起招募中已可发起,仅核单中及之后返回) | | 四项资源缺任一 | 原返回 `589542`(2026-09-24 订正码值);#8270 起不再拦截 | | 团期尾款池已被预支占满 | 团期级与该团任一子订单级预支**均**返回 `585004` | | 数据字典服务不可用 | 借款类型降级到内置集合校验,正常类型仍可提交 | | 审批列表出现团期级行 | `orderId` / `orderNo` / `consultantName` / 订单四态均为 `null` | --- ## 七、不影响范围 - **订单级预支全链路行为零变化**:创建 / 审批 / 驳回 / 撤回 / 本单列表五个端点与改造前逐字一致(底表 `order_id` 由非空放宽为可空,但既有查询全是等值匹配,空值行天然不命中)。 - **逐户核单报销单与逐户对账未改**:仍只含本户预支。 - **审批通过 / 驳回 / 撤回三端点未改**。 - **设置报账人端点未改**。 - 前端页面、打印导出、发票区、预支实际出款均不在本次范围。 --- ## 八、测试环境已验证 > ⏳ **尚未验证**。本文档随 PR #7170 提交,待合并并部署测试服后过网关实测,届时更新 > `backend_status` / `gateway_status` / `verified_at` 并补本节实测结果。 计划实测要点: 1. 财务 Tab 四张卡与逐户表末行合计一致,且与团期详情、看板列表三处应收同源。 2. **超支被堵死**:团期级把整团尾款预支满 → 该团任一子订单再发起订单级预支被 `585004` 拒。 3. **核单零重复扣**:团期级预支 5,000 通过 + 某户订单级预支 2,000 通过 → 该户报销单含 2,000,整团 `groupAdvanceApproved` 仍是 5,000;`grandTotalCost` 不变。 4. 两道闸门各拒一次(`589541` / `589542`,2026-09-24 订正码值)。 5. 团期级预支出现在预支审批列表,「团号 / 产品」「行程」两列有值,按团期号搜得到,就地通过 / 驳回 / 撤回正常。 6. 订单级预支五端点回归无变化。 --- ## 十、相关文档 - 团期模块接口文档 v2.0 §0B(预支权威契约)、§0A.3(金额口径)、卡片 GB-ADM-040 / 041 / 042 / 043 - 团期模块数据模型 §A.11(预支复用订单预支)、§A.11.10(团期层单独对账) - 团期模块表结构 v1.2 §4-5(`order_advance` 改造 DDL) **待回写正式稿**(本次实现与文档不一致处): | 处 | 文档现状 | 实际 | |---|---|---| | §0B.9 错误码 | 「统一用 `AdvanceErrorCode`(585 段)」 | 改落 `589541` / `589542`(原拟 589538 / 589539,被 #7158 / #7178 先占后顺延,2026-09-24 订正);585 段被 v2/v3 整段重叠声明且 585001-585010 已被 order-v2 实占 | | GB-ADM-040 出参 | `payStatus` 写五值含 `REFUNDING` / `REFUNDED` | 代码只有三值;结清状态另出 `settleStatus` 字段 | | GB-ADM-040 出参 | 含 `advanceTotal` | 已废,改三个数 | | GB-ADM-042 | 返回 `GroupBatchWriteResultVO`、不回传 `advanceId` | 改返 `OrderAdvanceRespVO` 并回传 `advanceId` | | §0A.3 | 「金额缺口是本期 P0,详情恒返回 0.00」 | 已被 #6905 / #6902 实时聚合关闭 | | §0B.6 | 「查询键列名骗人,必须进 CR checklist」 | `V20260904_001` 改名后该坑消失 | --- ## 关联 / 联系人 - **Issue**: #7154 - **PR**: #7170(base `dev-v3`) - **后端**: jw - **前端**: 待认领(三个新端点 + 预支弹窗入参更换 + 审批列表 `scope` 标签)