--- schema: "hl-changelog/v2" ticket: "7066" title: "团期子订单列表补应收总额 totalPrice" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "8980e197" target_release: "" verified_at: "2026-09-04" status_note: "PR #7068 已合入 dev-v3(合并提交 e1ad8550b);2026-09-04 部署测试环境,网关实测 8 组用例全部通过(含取消单归 0)。#7097 复审收敛:应收字段名 totalAmount 未上线即收敛为 totalPrice(#7113 合入 dev-v3 合并提交 11b677198),前端以 totalPrice 为准" updated_at: "2026-09-04" base: "dev-v3" --- # 团期子订单: 出参新增应收总额 totalPrice > **服务**: hl-order-service-v3 > **PR**: #7068 | **Issue**: #7066 | **合并提交**: `e1ad8550b` > **影响范围**: 管理后台「团期详情 → 子订单」页签 --- ## ⚠️ 关键变化 **前端不要再用 `paidAmount + balanceAmount` 反推应收总额,改读新增的 `totalPrice`。** > **字段名收敛说明(#7097)**:本单初版字段名 `totalAmount`,与 #6929 已在 003 接口下发的 `totalPrice` 构成同义双字段;复审 #7097 在未上线前收敛为 `totalPrice`,`totalAmount` 已移除。本 changelog 全文按收敛后字段名 `totalPrice` 表述,字段语义与两位小数格式不变。 旧反推在两种场景下**偏大**: 1. `balanceAmount` 被钳在 ≥0(`OrderAmountUtil.calcBalance` 末行),超付或退款后已付大于应收时反推值偏大 2. 已取消子订单 `calcBalance` 直接返 0,反推得到的是已付而非应收 **这不是理论风险**:2026-09-04 测试环境实测,现存子订单 `paidAmount=3270.00`、`balanceAmount=0.00`, 而真实应收 `totalPrice=2943.00`——**旧算法多显示 327.00**。 --- ## 一、背景 团期详情「子订单」页签每张卡片显示「已付 / 应收总额」,但列表接口出参此前没有应收总额字段, 前端只能自行反推。本次补齐该字段,口径与订单域既有展示口径统一。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 团期下子订单列表 | GET | `/v3/admin/order/group-batch/:groupBatchId/orders` | **出参新增字段** | 新增 `totalPrice` 应收总额 | --- ## 三、接口详情 ### 1. 团期下子订单列表 `GET /v3/admin/order/group-batch/:groupBatchId/orders` **VO**: `GroupBatchOrderItemRespVO` #### 使用场景 团期详情「子订单」页签加载时调用,渲染每户卡片。卡片右下角「已付 ¥X / ¥Y」中的 Y 即本次新增的 `totalPrice`。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `groupBatchId` | Path | Long | ✅ | — | 运营团期 ID(订单侧 `order_group_batch` 主键) | | `includeTravelers` | Query | Boolean | ❌ | 缺省 false | 附出行人明细,证件号一律不返回 | | `includeNeeds` | Query | Boolean | ❌ | 缺省 false | 附房数 / 房型 / 特殊需求 | | `includeCancelled` | Query | Boolean | ❌ | 缺省 false | 是否含已取消子订单,缺省只返活跃集 | #### 出参 `Result>` 本次仅新增一个字段,其余 28 个字段不变: | 字段 | 类型 | 说明 | |------|------|------| | `totalPrice` | String | **本次新增**。应收总额 = `orderAmount + 增项 − 优惠`;**已取消子订单归 0**。2 位小数,字符串输出 | | `paidAmount` | String | 已支付金额(未变) | | `balanceAmount` | String | 待支付尾款,**钳在 ≥0**(未变) | > 三个金额字段均带 `@JsonSerialize(ToStringSerializer)`,**JSON 里是字符串**,前端直接解析为数字会有精度风险,应按字符串处理或用高精度解析。 #### 请求示例 ```http GET /v3/admin/order/group-batch/2089713777065832450/orders ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": [ { "orderId": 2094597053924487170, "customerName": "6915验收造单01", "orderStatus": "CUSTOMIZING", "orderStatusName": "定制中", "adultCount": 2, "childCount": 0, "participantCount": 2, "contactPhone": "138****0001", "paidAmount": "3270.00", "balanceAmount": "0.00", "totalPrice": "2943.00" } ] } ``` #### 空数据 / 降级响应 团期下无子订单时返回空数组,不报错: ```json { "code": 200, "message": "成功", "success": true, "data": [] } ``` #### 错误响应 未携带管理端令牌: ```json { "code": 401, "message": "缺少有效的 Authorization 头", "success": false, "data": null } ``` #### 业务边界 - 只读接口,不产生任何写入 - `totalPrice` **不等于** `paidAmount + balanceAmount`,超付、退款、取消单三种情况下都会不等 - 已取消子订单(`includeCancelled=true` 才可见)的 `totalPrice` 与 `balanceAmount` 同为 0,闭合财务勾稽 - 金额一律 2 位小数(HALF_UP),与订单域其他接口格式一致 --- ## 四、契约约束与正确调用方式 - **应收总额一律读 `totalPrice`**,不要自行用 `paidAmount + balanceAmount` 计算。 - **`totalPrice` 与 `balanceAmount` 是不同语义**:前者是「客户总共该付多少」,后者是「现在还差多少」。已付清时后者为 0,前者仍是原值。 - **三个金额字段是 JSON 字符串**,不是数字。 - 取消单场景下 `totalPrice` 归 0 是**有意设计**(对齐 `calcBalance` 的取消单归 0),用于闭合「应收 0 / 已付 0 / 已退 0 / 待收 0」的勾稽链,不是缺陷。 --- ## 五、数据库行为 本接口只读,**不产生任何写入**,也不涉及表结构调整。`totalPrice` 由既有字段实时计算,不新增存储列。 --- ## 六、边界行为 - 超付单 → `totalPrice` 为真实应收,小于 `paidAmount` - 取消单 → `totalPrice` 归 0 - 团期无子订单 → 返回 `[]` - 未登录 → 401(网关拦截) --- ## 六.5、枚举 / 数据字典 本次无新增枚举。 --- ## 六.6、修改前后对比 | 项 | 变更前 | 变更后 | |---|---|---| | 出参字段数 | 28 | **29** | | 应收总额 | **无字段**,前端用 `paidAmount + balanceAmount` 反推 | 新增 `totalPrice`,服务端按既有 `payableForDisplay` 口径给出 | | 超付单显示 | 反推值偏大(实测多 327.00) | `totalPrice` 为真实应收 | | 取消单显示 | 反推得到已付金额,非应收 | `totalPrice` 归 0,与 `balanceAmount` 一致 | | `paidAmount` / `balanceAmount` | — | **语义与取值均不变** | | 路径 / 入参 | — | **不变** | ## 六.7、影响评估 | 维度 | 评估 | |---|---| | 兼容性 | **纯 additive**,老调用方不读新字段即无感,无破坏性变更 | | 前端 | 需改为读 `totalPrice`;不改也不会报错,但超付 / 取消单场景显示值偏大 | | 数据 | 无 DDL、无迁移、无回填;`totalPrice` 实时计算不落库 | | 性能 | 无额外查询,复用同一 `OrderInfo` 实体计算,零新增 IO | | 回滚 | 移除字段即可,无数据侧残留 | | 风险 | 低。口径复用 `AdjustmentService` 已在生产使用的既有方法,未新造公式 | ## 七、不影响范围 - **零影响**:`paidAmount` / `balanceAmount` 的现有语义与取值 - **零影响**:其余 28 个出参字段 - **零影响**:老调用方——纯新增字段,不读即无感 - **未新建端点**,未改动路径与入参 --- ## 八、测试环境已验证 ✅ 2026-09-04 于测试环境网关实测,真实鉴权(管理端 admin)。 - 网关 `https://api.test.1814.love`,分支 `dev-v3`,合并提交 `e1ad8550b` - 部署方式:双实例滚动更新(8186 → 8086),各 11s 就绪,零停机 | # | 用例 | 期望 | 实测 | |---|---|---|---| | 1 | 字段上线 | 出参含 `totalPrice` | ✅ 字段数 27 → 29 | | 2 | 真实数据口径 | `totalPrice` 为真实应收 | ✅ `2943.00`;旧反推得 `3270.00`,**偏差 327.00** | | 3 | 序列化格式 | 字符串、2 位小数 | ✅ `"totalPrice":"2943.00"` | | 4 | `includeCancelled=true` | 正常返回 | ✅ 返回 1 条 | | 5 | 无 Authorization | 401 | ✅ `401 缺少有效的 Authorization 头` | | 6 | 老字段回归 | 未受影响 | ✅ 8 个字段逐一核对一致 | | 7 | 缺省过滤取消单 | 取消单不出现 | ✅ `GET .../orders` 返回 0 条 | | 8 | **取消单归 0** | `totalPrice = 0` 且与 `balanceAmount` 一致 | ✅ `totalPrice="0.00"`、`balanceAmount="0.00"`、`paidAmount="3270.00"` | **部署前基线对照**:同一接口部署前出参 27 字段、无 `totalPrice`,确认变更确实生效。 **本地单测**:`GroupBatchConverterTest` 新增 5 例(正常单 / 含增项优惠 / **超付单断言反推不等** / **取消单归 0** / 2 位小数); 本地全量 **750 例全过**。 **取消单分支已实测**(用例 7–8):验收期间该子订单被退单,恰好提供了取消单样本。 实测 `totalPrice="0.00"`,而 `paidAmount="3270.00"`——**旧算法 `paidAmount + balanceAmount` 会把一条 已取消的子订单显示成「应收 3270」,真实应收应为 0**。本字段同时修正了这一场景。 全部 8 组用例通过,无遗留未验分支。 --- ## 九、相关历史 PR - #7068 本次变更 - 口径来源:`OrderAmountUtil.payableForDisplay`(#4456 展示用应收总额,取消单归 0) - 相关:#5733(`calcBalance` 改为退款冲减应收)、#2896 P1-3(金额统一 2 位小数) --- ## 十、相关文档 - 团期需求文档:`docs/group/`(dev-v3 分支) - 3 天开发计划:`docs/group/团期模块3天开发计划.md` --- ## 关联 / 联系人 - **Issue**: #7066 - **PR**: #7068(合并提交 `e1ad8550b`) - **服务**: hl-order-service-v3 - **后端**: jw - **前端**: 待认领(`frontend_status: pending`)