16 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8045 | 团期详情返回整团待收 unpaidAmount | admin | jw(GIT) | 修改接口 | deployed | verified | verified | mmg | e680cf10c2b8c0377ea89d21ab13eea1e740f429 | v2.1 | 2026-09-21 | 团期详情端点(A2)新增响应字段 unpaidAmount(整团待收),值 = max(0, receivableAmount − receivedAmount),恒非 null 恒非负,字符串型金额。此前该端点已同时返回 receivableAmount 与 receivedAmount,但没有待收字段,前端只能自己做减法——而本页子订单项的 balanceAmount 是另一套算法(扣退款、取消单归 0),前端自算会在同一屏里产生第二套口径,故由后端给出。【前端交付 2026-09-21 mmg:BatchHero 金额区补「待收」直显 detail.unpaidAmount(warning 色,缺失兜底 —,不自算),NTooltip 标注「退款不回减,权威待收以财务 tab 明细/合计为准」;FinanceTab 早已展示 totals/items unpaidAmount 零改动;BatchHero 8 例全过,hl-admin v2.1 e680cf10。】列表页(A1)早已有同名字段,本次是把详情页补齐,两处同源同公式。⚠️ 两点必须读:① 本字段按「毛累计已付」算,发生过退款的团偏小;逐户扣退款的权威待收在财务 tab 的 items/totals,不是财务 tab 的顶层 unpaidAmount(顶层与本字段同源同公式,数值一致)——同页展示两者时请在 UI 上区分标注(实测见「八」)。② 不要把它与子订单项的 balanceAmount 混用。本页既有四个金额字段(receivableAmount / receivedAmount / totalReceivable / totalReceived)的值、名称、形态逐字未变,纯增量。 | 2026-09-21 | dev-v3 |
order-v3: 团期详情返回整团待收 unpaidAmount
服务: hl-order-service-v3 (端口 8086) PR: #8101 Issue: #8045 日期: 2026-09-21 影响范围: 管理后台「团期详情」页顶部金额区(列表点团期进去的那一页)
⚠️ 关键变化
- 纯增量:既有字段一个都没改,只多返回一个
unpaidAmount。不接入的前端不受影响。 - 不要再自己拿
receivableAmount − receivedAmount—— 后端已给出这个值,且做了解析保护(负数不透出)。 - 不要与本页子订单项的
balanceAmount混用:balanceAmount是 per-order「应收 − 已退 − 已付」(扣退款、取消单归 0),与本字段同名不同义。#7066 曾专门花一节向前端解释过这个坑,这里再强调一次。 - 🔴 有退款的团,本字段会偏大:本字段用的是「毛累计已付」(退款不回减),财务 tab 的逐户明细与合计(
items[].unpaidAmount/totals.unpaidAmount)是逐户扣退款的。注意是「明细与合计」,不是财务 tab 的顶层unpaidAmount—— 财务 tab 顶层的unpaidAmount与本字段同源同公式,数值一致(实测见第八节)。若把本字段与财务 tab 的合计放在同一屏,两者会差一个「累计已退」的量,请在 UI 上区分标注,权威待收以财务 tab 的逐户明细/合计为准。
一、背景
#7535 那一批统一了团期金额字段命名,同时给列表页(A1 分页项 GroupBatchPageItemRespVO)加了 unpaidAmount,但对详情页(A2)只做了 totalReceivable/totalReceived → receivableAmount/receivedAmount 的命名统一,漏了待收字段。
于是出现「列表页有待收、点进详情页反而没有」的观感缺口:运营要一眼看到「这个团还差多少钱没收」,只能心算,或退回列表页看。
需求来源:前端 2026-09-20《前端待后端交付清单》第 3 项,原文标注「无工单,待排期」;本单为该事项排期。前端明确拒绝自算,理由是「口径会漂移」——这个担心成立,见上一节。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期详情(A2) | GET | /v3/admin/order/group-batch/:id |
修改 | 响应新增 unpaidAmount(整团待收) |
财务 tab 端点
GET /v3/admin/order/group-batch/:id/finance本次一行未改,列在本节仅为对照口径。
三、接口详情
1. 团期详情 GET /v3/admin/order/group-batch/:id
VO: Result<GroupBatchDetailRespVO>
使用场景
管理后台「团期详情」页主数据。进入该页时调用一次,页面顶部的整团应收 / 已收 / 待收 三格都读本响应。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | String(雪花 ID) | ✅ | 团期 ID | 不存在或已软删返回 589500 |
出参 Result<GroupBatchDetailRespVO>
本次只新增 1 个字段,其余字段一律不动、不改名、不改类型。
| 字段 | 类型 | 说明 |
|---|---|---|
| receivableAmount | String(金额) | 整团应收(Σ 在团子订单应付总额)。不变 |
| receivedAmount | String(金额) | 整团已收(Σ 毛累计已付,退款不回减)。不变 |
| unpaidAmount | String(金额) | 【新增】 整团待收 = max(0, receivableAmount − receivedAmount)。恒非 null、恒 ≥ 0 |
| totalReceivable | String(金额) | 已废弃,与 receivableAmount 逐字同值。不变(只标记不删) |
| totalReceived | String(金额) | 已废弃,与 receivedAmount 逐字同值。不变(只标记不删) |
金额一律是字符串(如 "12000.00"),不是 JSON 数字,不要当数字解析。
请求示例
GET /v3/admin/order/group-batch/2101908228566908930
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2101908228566908930",
"receivableAmount": "4000.00",
"receivedAmount": "2000.00",
"unpaidAmount": "2000.00",
"totalReceivable": "4000.00",
"totalReceived": "2000.00"
},
"success": true
}
(仅列相关字段,其余字段保持原样。)
空数据 / 降级响应
无在团子订单的空团,三个金额同为 "0"(注意不是 "0.00",与列表页 A1 的行为一致,前端自行格式化):
{
"code": 200,
"data": {
"receivableAmount": "0",
"receivedAmount": "0",
"unpaidAmount": "0"
},
"success": true
}
错误响应
团期不存在或已软删:
{
"code": 589500,
"message": "团期不存在",
"data": null,
"success": false
}
无查看权:
{
"code": 589507,
"message": "无权限访问该团期",
"data": null,
"success": false
}
| 码 | 触发 |
|---|---|
| 589500 | groupBatchId 不存在或已软删 |
| 589507 | 调用方无团期查看权;定制师读不属于自己的团期 |
鉴权失败在本项目是 HTTP 200 + body code=401,不要只看 HTTP 状态码。
业务边界
- 本字段是纯内存计算,不新增查询、不新增 Feign 调用;响应变慢与它无关。
- 不是权限变更:A2 走一般查看权(
PERMISSION_VIEW),本字段不扩大任何信息暴露面——它是由同一响应里已经返回的receivableAmount与receivedAmount相减得到的,信息增量为零。 - 口径:
receivedAmount是毛累计已付、退款不回减,所以发生过退款的团本字段偏小(应收没变、已付也没被退款冲减,差额仍然是「待收」)。 - 标度:沿用同一行
receivableAmount/receivedAmount的原始标度,不做二次收敛。同一行三个金额标度一致。 - 负值保护:已收多于应收(退款 / 多收留下的历史脏数据)时返回
"0",不会出现负号。
四、契约约束与正确调用方式
- 不要自己算:直接读
unpaidAmount,不要写receivableAmount - receivedAmount。 - 不要用
balanceAmount顶替:那是子订单项的 per-order 口径(应收 − 已退 − 已付,取消单归 0),与本字段同名不同义。 - 财务 tab 的对照口径要说清是哪一个:
- 财务 tab 顶层
unpaidAmount—— 与本字段同源同公式,数值一致(不是逐户扣退款的)。 - 财务 tab
items[].unpaidAmount/totals.unpaidAmount—— 逐户扣退款的权威口径。有退款的团,它与本字段会差一个「累计已退」。
- 财务 tab 顶层
- 三个金额都是字符串,
"0"与"0.00"都可能出现(空团是前者),比较时请按数值比较而不是字符串相等。
六、边界行为
| 场景 | 行为 |
|---|---|
| 正常有在团子订单 | unpaidAmount = 应收 − 已收(两位小数) |
| 无在团子订单(空团) | 三个金额同为 "0",非 null |
| 已收 > 应收(多收 / 退款脏数据) | unpaidAmount 返回 "0",不透负数。TEST 实测见第八节 |
| 发生过退款的团 | 本字段按毛已付算,比财务 tab 的逐户合计偏大;与财务 tab 顶层一致 |
| 团期不存在 / 已软删 | 589500 |
| 无查看权 | HTTP 200 + body code=589507 |
| 老前端不读新字段 | 纯增量,无影响 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
unpaidAmount |
不存在(响应体里没有这个 key) | "12000.00"(字符串型金额) |
receivableAmount |
"48000.00" |
"48000.00"(逐字未变) |
receivedAmount |
"36000.00" |
"36000.00"(逐字未变) |
totalReceivable |
"48000.00"(已废弃) |
"48000.00"(逐字未变,仍只标记不删) |
totalReceived |
"36000.00"(已废弃) |
"36000.00"(逐字未变) |
| 其余全部字段 | — | 一个都没动 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 详情页「待收尾款」从哪来 | 前端自己拿 receivableAmount − receivedAmount 算 |
后端返回 unpaidAmount,前端直接读 |
| 已收大于应收时 | 前端自算会拿到负数,得自己处理 | 后端返回 "0",不透负数 |
| HTTP 状态 / 错误码 / 判权 | — | 不变 |
| 响应耗时 | — | 不变(新增字段是内存计算,不加查询、不加 Feign) |
六.7、影响评估
- 是否破坏向后兼容: 否。纯增量字段,既有四个金额字段的值 / 名称 / 类型 / JSON 形态逐字未变(TEST 改前改后逐字对照见第八节)。
- 前端是否必须同步上线: 否。不接入则详情页保持现状(没有待收那一格)。
- 前端 workaround 清理点: 若前端此前在详情页自己算过待收(
receivableAmount - receivedAmount),现在可以撤掉,改读unpaidAmount。 - 回滚: 回滚本 PR 即可,只读端点、无数据变更、无表结构变更。
七、不影响范围
GET /v3/admin/order/group-batch/:id/finance(财务 tab)一字未改。GET /v3/admin/order/group-batch(A1 列表)一字未改,它的unpaidAmount早就存在。- 子订单项
balanceAmount(GroupBatchOrderItemRespVO)未动。 - 判权、错误码、网关路由、事务与锁均未变。
- 其他微服务(hl-finance / hl-product-service-v2 / hl-user-service 等)未动。
- 无数据库表结构变更,无 Flyway 迁移。
八、测试环境已验证
TEST 环境(hl-order-service-v3 + hl-gateway 均为 dev-v3 @ c238f38c3,BEHIND 0/N,STATE ok),2026-09-21,走真实网关 https://api.test.1814.love:9443。
构建身份探针(改前 / 改后同端点对照,确认跑的确实是本单代码):
改前(dev-v3 旧字节)GET /v3/admin/order/group-batch/2101908228566908930 → 响应体没有 unpaidAmount 这个 key ✓
改后(dev-v3 c238f38c3)同 URL → 出现 "unpaidAmount": "2000.00" ✓
AC-1 金额正确 + 形态:团 2101908228566908930
receivableAmount "4000.00" / receivedAmount "2000.00" / unpaidAmount "2000.00"
手算 max(0, 4000.00 − 2000.00) = 2000.00,一致;三个都是 JSON 字符串 ✓
团 2101906599658618882:应收 "6000.00" / 已收 "0.00" / 待收 "6000.00" ✓
AC-2 已收 > 应收边界(自造 fixture,见「造数清单」):
应收 "2000.00" / 已收 "3000.00" → unpaidAmount = "0",无负号,非 null ✓
AC-3 空团:团 2101250106387726338 无在团子订单
receivableAmount "0" / receivedAmount "0" / unpaidAmount "0"(同值同形态) ✓
AC-4 纯增量:四个既有字段改前 → 改后逐字一致(4 个团全部比对,含空团与有退款的团) ✓
AC-5 注解四层文案(公式 / null 按 0 参与 / 退款不回减 / 权威口径指向财务 tab),
并由新增的单测钉住,删掉任一层即红 ✓
AC-6 「应收减已收」仍只有一处实现:新增行不含 `.subtract(`;实参调既有 calcUnpaid ✓
AC-7 calcUnpaid 的「只此一处减法」javadoc 已列入 toDetailVO 这条新调用方 ✓
AC-8 精确测试:GroupBatchConverterTest 108 + GroupBatchAliasFieldSerializationTest 7
+ GroupBatchQueryControllerTest 9 = 124 跑 / 0 失败 / 0 错误 ✓
变异证明:注释掉 toDetailVO 那一行后恰好这 4 个转换器用例红(108 跑 / 4 失败),
控制器与序列化类保持绿 —— 新断言不是恒真 ✓
AC-9 MapperBoundaryArchTest 门禁:本单零新增违规,门禁红系既有基线
(干净的 c62907410 上同样 27 跑 / 1 失败,9 处违规全在 payment → refund.mapper,
见 [#7989](https://git.1814.love:8443/wx/HL/issues/7989),P1,yst 属主)
AC-10 网关实测(上方 URL 与 deploy-status 两行) ✓
AC-11 有退款的团与财务 tab 的口径对照,见下 ✓
AC-11 口径对照(团 2099459274966016001,子订单 HL20260914192431793 有一笔 100.00 的成功退款):
| 取值处 | 数值 | 是否扣退款 |
|---|---|---|
本单新增的 A2 unpaidAmount |
"1900.00" |
否(毛已付) |
财务 tab 顶层 unpaidAmount |
"1900.00" |
否(与本字段同源同公式) |
财务 tab totals.unpaidAmount |
"1800.00" |
是(逐户扣退款) |
财务 tab items[0].unpaidAmount |
"1800.00" |
是 |
1900.00 − 1800.00 = 100.00 = 该团那笔退款金额。即:本字段与财务 tab 顶层一致,与财务 tab 的逐户合计差一个「累计已退」——前端要对齐的权威口径是后者。
扫描口径补充:TEST 上 237 个团期,顶层
unpaidAmount与「应收 − 已收」无一不等(两者同源);顶层与totals不等的恰好 1 个,就是上表这个有退款的团。
送测数据(自造,请勿清理):
| groupBatchId | 名称 | 用途 | 现状 |
|---|---|---|---|
2101927290046943234 |
#8045-AC2多收边界 |
AC-2「已收 > 应收」边界造数(其子订单 2101927289925308418 的 paid_amount 曾临时置 3000.00,已还原为 0.00) |
挂 1 个子订单(PENDING_PAY),班期出发日 2026-12-20 |
该班期挂着订单,删不掉(
BATCH_HAS_ENROLLED_ORDERS),留着不干扰他人:未被可报名谓词 / 选品列表 / order-v3 下单闸采纳。
其他 AC 用的是 TEST 上既有真实团期,未改任何既有数据。
十、相关文档
- 工单 #8045
- #7535 团期金额字段命名统一(A1 加了
unpaidAmount,A2 漏了,本单补齐) - #7066 解释过「两个
balanceAmount同名不同义」的那个坑 - #7989 order-v3 ArchTest 门禁既有基线红(与本单无关,登记在案)