From a7c44c0caa9b64b7b835fe078af168868bd924c0 Mon Sep 17 00:00:00 2001 From: jw Date: Sun, 6 Sep 2026 15:15:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20=E5=9B=A2=E6=9C=9F=E8=B4=A2?= =?UTF-8?q?=E5=8A=A1=E6=80=BB=E8=A7=88=E4=B8=8E=E9=A2=84=E6=94=AF=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=E8=AE=A2=E5=8D=95=E9=A2=84=E6=94=AF=EF=BC=88#7154=20/?= =?UTF-8?q?=20PR=20#7170=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- ...务总览与预支复用订单预支-新增接口-管理后台.md | 644 ++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 changelogs-v2/2026-09/06_7154_团期财务总览与预支复用订单预支-新增接口-管理后台.md diff --git a/changelogs-v2/2026-09/06_7154_团期财务总览与预支复用订单预支-新增接口-管理后台.md b/changelogs-v2/2026-09/06_7154_团期财务总览与预支复用订单预支-新增接口-管理后台.md new file mode 100644 index 00000000..8922b5fb --- /dev/null +++ b/changelogs-v2/2026-09/06_7154_团期财务总览与预支复用订单预支-新增接口-管理后台.md @@ -0,0 +1,644 @@ +--- +schema: "hl-changelog/v2" +ticket: "7154" +title: "团期财务总览与预支复用订单预支:财务 Tab 三个只读端点 + 预支创建语义改造" +consumer: "admin" +author: "jw(GIT)" +change_type: "新增接口" +backend_status: "pending" +gateway_status: "pending" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "待部署测试服并过网关实测后改 backend_status=deployed 再推送" +updated_at: "2026-09-06" +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` 权限 | +| `589538` | 团期状态不可发起预支(须为物料准备中 / 待出发 / 出行中) | +| `589539` | 房 / 车 / 导 / 摄四项未配齐 | +| `585003` | 预支金额必须大于 0 | +| `585004` | 预支金额超过可用余额上限 | +| `585006` | 借款类型非法 | +| `585007` | 借款对象不属于本团期人员 | + +```json +{ "code": 585004, "msg": "预支金额超过可用余额上限", "data": null } +``` + +#### 业务边界 + +- **双前置闸门**:团期状态 + 四项资源全就绪,缺一即拒(此前服务端两道都没有,只有前端做了置灰)。 +- **上限走团期统一池**:团期级与各子订单级**共扣一池**。团期把整团尾款预支满后,该团任一子订单再发起订单级预支同样会被 `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`,后端不兜底默认导游 | +| 团期状态为招募中 / 核单中 | 发起预支返回 `589538` | +| 四项资源缺任一 | 发起预支返回 `589539` | +| 团期尾款池已被预支占满 | 团期级与该团任一子订单级预支**均**返回 `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. 两道闸门各拒一次(`589538` / `589539`)。 +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 段)」 | 改落 `589538` / `589539`;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` 标签)