22 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 | 7536 | 团期接口返回值整改·破坏批:A3 子订单列表改分页信封 + 删 5 冗余字段 + include* 默认开 + 财务 12 金额转字符串 + 3 逐行 VO 补团号 | admin | jw(GIT) | 修改接口 | deployed | verified | verified | mmg | 657f14dd | 2026-09-14 | 破坏性变更,前端必须整页面批量切换、提前协调。A3『报名清单』tab 主数据源 `GET .../orders` 从裸数组改标准分页信封 `Result<PageResult<T>>`(新增 page/pageSize,默认首页 20 条并带 total),且 includeNeeds/includeTravelers 缺省由 false 改 true(房型/房数/出行人默认返回);同时删除 5 个冗余字段(estimatedCost/adultCount/childCount/youngChildCount/babyCount,人数改用 participantCount + tierName)。财务 tab `GET .../finance` 的 12 个金额字段 JSON 形态由 number 改 string(与报名清单 tab 对齐)。A3/财务/预支三个逐行 VO 各补一个 teamNo(本户团号,逐户不同,未付订金为 null)。零 DDL、零网关路由改动。**注:groupChatUnreadCount 本轮保留不删——hl-ui 全文搜索零命中但未取得 mmg 书面回执,按工单 AC-1 兜底口径保留该字段。** 前端已交付(mmg):报名清单真分页 UI(rosterPage 服务端分页+整团名单 pageSize=200 分离)+teamNo 列+下线预计毛利列+退单候选 records 兜底;commit 657f14dd。 | 2026-09-14 | dev-v3 |
order-v3: 团期接口返回值整改·破坏批(A3 子订单列表 / 财务 / 预支)
服务: hl-order-service-v3 PR: #7688 Issue: #7536
⚠️ 关键变化
🔴 破坏性变更,前端必须整页面批量切换(不提供兼容期双形态返回):
- A3 子订单列表
GET .../orders返回包装从裸数组改分页信封:Result<List<GroupBatchOrderItemRespVO>>→Result<PageResult<GroupBatchOrderItemRespVO>>。data由裸数组变为{records, total, page, pageSize};新增page/pageSize查询参数(默认首页 20 条、pageSize上限 200)。老前端读data.length会拿到undefined、列表整个空白——这是本单最危险的失败形态。 - 删除 5 个冗余字段(
GroupBatchOrderItemRespVO):estimatedCost(无写入点、恒 null)、adultCount/childCount/youngChildCount/babyCount(人数四件套)。前端毛利列删除、人数改用participantCount(总数)+tierName/tierCode(档位)。 includeNeeds/includeTravelers缺省值由false改为true:roomCount/roomType/specialNeeds与travelers[]默认返回;前端要精简响应须显式传false。- 财务 tab
GET .../finance的 12 个BigDecimal金额字段 JSON 形态 number → string(补@JsonSerialize(using = ToStringSerializer.class),与报名清单 tab 对齐)。前端勿按 number 解析这些字段。 - A3 / 财务 / 预支三个逐行 VO 各补一个
teamNo(纯加):本户团号(order_main.team_no,形如26-0001),逐户各不相同,该户订金未支付成功时为null(不返空串、不回退orderNo)。
🟢 纯加(已随零破坏批 PR-1 先行合入,本单一并归档):A3 子订单项补 6 个中文配对字段(roomTypeName + payStatusName/contractStatusName/insuranceStatusName/hotelRequirementStatusName/vehicleRequirementStatusName)。
无 DDL、无 Flyway、无网关路由改动(/v3/admin/order/** 既有覆盖)、无新增错误码。
一、背景
团期控制台(hl-ui 管理后台)「团期详情 → 报名清单 / 财务」两个 tab 有三处肉眼可见坏结果:页头「子订单 N 户」恒显示 0(列表接口无 total)、报名清单默认缺「房型/房数」「出行人」两列(藏在 opt-in 开关后)、报名清单多给了「预计毛利」「会话未读」两列(原型没有)。口径来源 docs/group/团期闭环整改方案-v1.1.md §6,判据是 wx 定的三条「原型有的必须有;可以多给;不能给离谱的」。本单是三批整改中的破坏批,独占「团期子订单列表 A3」「团期财务总览」「团期预支记录」三个端点的全部改动。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期下子订单列表(报名清单 tab) | GET | /v3/admin/order/group-batch/:groupBatchId/orders |
修改 | 裸数组→分页信封 PageResult;新增 page/pageSize;include* 默认改 true;删 5 冗余字段;补 teamNo |
| 2 | 团期财务总览(财务 tab) | GET | /v3/admin/order/group-batch/:groupBatchId/finance |
修改 | 12 个金额字段 JSON number→string;逐户行补 teamNo |
| 3 | 团期预支记录 | GET | /v3/admin/order/group-batch/:groupBatchId/advances |
修改 | 预支逐行补 teamNo(团期级行 scope=GROUP_BATCH 恒 null) |
三、接口详情
1. 团期下子订单列表 GET /v3/admin/order/group-batch/:groupBatchId/orders
VO: Result<PageResult<GroupBatchOrderItemRespVO>>(path: groupBatchId;query: page / pageSize / includeTravelers / includeNeeds / includeCancelled)
使用场景
团期详情页「报名清单」tab 主数据源。改造后默认返首页 20 条并带 total,房型/房数/出行人默认返回,6 个裸 code 带中文配对,逐户带团号。沿用既有鉴权 GroupBatchPermissionGuard.PERMISSION_VIEW 与网关路由。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | string(Long) | 是 | 雪花 ID | 团期主订单 ID(运营侧团期主键,不是 productBatchId) |
| page | query | int | 否 | 缺省 1;<1 归一为 1 | ★新增。页码,从 1 起 |
| pageSize | query | int | 否 | 缺省 20;<1 归一为 20;>200 截断为 200 | ★新增。每页条数 |
| includeTravelers | query | boolean | 否 | ★默认 false→true |
是否附 travelers[](证件号/手机号任何情况不返回) |
| includeNeeds | query | boolean | 否 | ★默认 false→true |
是否附 roomCount/roomType/specialNeeds |
| includeCancelled | query | boolean | 否 | 缺省 false(不变) |
是否含已取消子订单(只返活跃集,剔除 CANCELLED) |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data | object | ★改前是裸数组,改后是 PageResult 信封 |
| data.records | array | 本页子订单行(GroupBatchOrderItemRespVO) |
| data.total | int | ★整团活跃子订单总数(含未在本页的) |
| data.page / data.pageSize | int | ★当前页码 / 每页条数 |
| records[].orderId | string(Long) | 子订单 ID(字符串防精度丢失) |
| records[].orderNo | string | 订单编号 |
| records[].teamNo | string | ★本户团号(order_main.team_no),逐户不同,未付订金为 null |
| records[].customerName | string | 客户姓名 |
| records[].participantCount | int | 出行人总数(= adult+child+youngChild+baby) |
| records[].tierCode / tierName | string | 档位码 / 档位名 |
| records[].payStatus / payStatusName | string | 支付状态 code / 中文(★Name 纯加) |
| records[].contractStatus / contractStatusName | string | 合同状态 code / 中文(无合同时均 null) |
| records[].insuranceStatus / insuranceStatusName | string | 保险状态 code / 中文(无保险时均 null) |
| records[].hotelRequirementStatus / hotelRequirementStatusName | string | 房需求状态 code / 中文 |
| records[].vehicleRequirementStatus / vehicleRequirementStatusName | string | 车需求状态 code / 中文 |
| records[].paidAmount / balanceAmount / totalPrice | string(decimal) | 已付 / 待收尾款 / 本户应收(字符串两位小数) |
| records[].roomCount / roomType / roomTypeName / specialNeeds | int/string | ★默认返回;roomTypeName 按「、」逐段翻译再拼回 |
| records[].travelers | array | ★默认返回;name/type/age/birthdayInTrip(无证件号/手机号) |
| — | ★已删除(毛利改核单页,人数改用 participantCount + tierName) |
请求示例
GET /v3/admin/order/group-batch/2096412454643802114/orders?page=1&pageSize=20
Authorization: Bearer <admin token>
响应示例
{
"code": 200,
"message": "success",
"success": true,
"traceId": "…",
"data": {
"records": [
{
"orderId": "2000000001",
"orderNo": "HL20260601001",
"teamNo": "26-0001",
"customerName": "王先生家庭",
"participantCount": 4,
"tierCode": "2A1C",
"tierName": "2成人1儿童",
"payStatus": "DEPOSIT_PAID",
"payStatusName": "已付定金",
"paidAmount": "3000.00",
"balanceAmount": "9000.00",
"totalPrice": "12000.00",
"roomCount": 2,
"roomType": "FAMILY",
"roomTypeName": "家庭房",
"specialNeeds": "需要婴儿床",
"travelers": [ { "name": "王小明", "type": "CHILD", "age": 6, "birthdayInTrip": false } ]
}
],
"total": 55,
"page": 1,
"pageSize": 20
}
}
空数据 / 降级响应
团期无任何活跃子订单 → data 为 {"records":[],"total":0,"page":1,"pageSize":20}(不返 data:null)。page 超末页 → records 为空数组、total 仍为真实总数、code=200 不抛异常(前端据 total 回退首页)。字典不可达时 roomTypeName 回落 roomType 原值。
错误响应
{ "code": 589500, "message": "团期不存在", "data": null }
团期不存在 → 589500(GROUP_BATCH_NOT_FOUND);无 group-batch:view → 589507(GROUP_BATCH_PERMISSION_DENIED)。分页参数越界不产生错误码(归一/截断)。
业务边界
- 分页在内存内做(切片在
assembleSubOrders装配之后、事务外);不下推 SQL——数据源经 OrderService 跨聚合逐单装配,下推会打散成 N+1,且@TableLogic与 CANCELLED 过滤都在内存侧。团期子订单 N 上界为单团满员名额,本端点页面级低频。 page<1归一为 1;pageSize<1归一为 20、>200截断为 200(读接口对越界宽容,不抛异常)。includeTravelers/includeNeeds传 null 按 true;includeCancelled传 null 按 false。- 保留一个不分页的内部全量方法(
listSubOrders(id, boolean, boolean, boolean))供 #7533 团级文档出全团名册;HTTP 入口才走分页(复审修正 1)。
2. 团期财务总览 GET /v3/admin/order/group-batch/:groupBatchId/finance
VO: Result<GroupBatchFinanceRespVO>(path: groupBatchId)
使用场景
团期详情页「财务」tab(GB-ADM-040:四张金额卡 + 逐户付款 + 整团合计)。本次只改金额字段 JSON 形态(number→string)与逐户行补 teamNo,其余字段名与语义全部不变。权限 GroupBatchPermissionGuard.PERMISSION_FINANCE_VIEW(比 view 更严)。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | string(Long) | 是 | 雪花 ID | 团期主订单 ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| receivableAmount / receivedAmount / unpaidAmount | string(decimal) | ★形态 number→string。整团应收 / 已收 / 待收 |
| advanceApproved / advancePending / advanceAvailable | string(decimal) | ★形态 number→string。已预支 / 待审批 / 可支取余额 |
| totals.totalPrice / totals.paidAmount / totals.unpaidAmount | string(decimal) | ★形态 number→string。整团合计三列 |
| items[].totalPrice / items[].paidAmount / items[].unpaidAmount | string(decimal) | ★形态 number→string。逐户应收 / 已付 / 待收 |
| items[].teamNo | string | ★纯加。本户团号,逐户不同,未付订金为 null |
| withdrawnCount | int | 不变(Integer) |
| primaryPayeeName / secondaryPayeeName | string | 不变 |
请求示例
GET /v3/admin/order/group-batch/2096412454643802114/finance
Authorization: Bearer <admin token>
响应示例
{
"code": 200, "message": "success", "success": true, "traceId": "…",
"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", "teamNo": "26-0001",
"customerName": "罗敏", "consultantName": "李雯",
"totalPrice": "12300.00", "paidAmount": "12300.00", "unpaidAmount": "0.00" }
]
}
}
空数据 / 降级响应
无活跃子订单团期 items 为空数组、四张金额卡为 "0.00"(字符串);金额字段恒非 null(BigDecimal.ZERO 参与计算)。未付订金的逐户行 teamNo 为 null。
错误响应
{ "code": 589507, "message": "无权限", "data": null }
团期不存在 → 589500;无 group-batch:finance:view → 589507。
业务边界
- 12 个金额字段(顶层 6 +
totals3 +items[]3)JSON 形态统一为带双引号的字符串;withdrawnCount(Integer)不动。本次只消除同页两个 tab 金额形态不一致,不作通用精度保证(按分折整数 ≤1e12 在 2^53 内不丢量级,但算术会累积误差)。 items[].teamNo逐户取值(同一次orderService.selectBatchByIds,零新增查询),不是全团共用一个值。
3. 团期预支记录 GET /v3/admin/order/group-batch/:groupBatchId/advances
VO: Result<List<GroupBatchAdvanceItemVO>>(path: groupBatchId;倒序、不分页)
使用场景
团期详情页预支记录列表。本次只在逐行补 teamNo(纯加),其余不变。权限 GroupBatchPermissionGuard.PERMISSION_FINANCE_VIEW。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | string(Long) | 是 | 雪花 ID | 团期主订单 ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data | array | 预支记录逐行(倒序、不分页;裸数组,非分页信封) |
| data[].orderNo | string | 归属订单号(团期级行 scope=GROUP_BATCH 为 null) |
| data[].teamNo | string | ★纯加。归属订单团号;scope=GROUP_BATCH 团期级行恒 null(与 orderNo 同规则) |
| data[].scope | string | ORDER / GROUP_BATCH |
| data[].amount / status 等 | — | 不变 |
请求示例
GET /v3/admin/order/group-batch/2096412454643802114/advances
Authorization: Bearer <admin token>
响应示例
{
"code": 200, "message": "success", "success": true, "traceId": "…",
"data": [
{ "orderNo": "GT-26-0081", "teamNo": "26-0001", "scope": "ORDER", "amount": "500.00" },
{ "orderNo": null, "teamNo": null, "scope": "GROUP_BATCH", "amount": "1000.00" }
]
}
空数据 / 降级响应
无预支记录返空 records。scope=GROUP_BATCH 的团期级行没有归属订单,orderNo 与 teamNo 同时为 null(前端按 scope 判断渲染,勿把团期级行的空团号当异常)。
错误响应
{ "code": 589507, "message": "无权限", "data": null }
团期不存在 → 589500;无 group-batch:finance:view → 589507。
业务边界
teamNo与orderNo从同一张Map<Long, OrderInfo>(同一次selectBatchByIds)取,不新增查询;activeIds为空时一次都不查。- 团期级行(
orderId == null)teamNo恒 null,与既有orderNo处理同规则。
四、契约约束与正确调用方式
- A3 分页:
data是PageResult信封,读data.records(不是data);data.total为整团活跃总数、data.records.length只是本页条数。不传page/pageSize返首页 20 条。 - 金额字符串:A3 的
paidAmount/balanceAmount/totalPrice与财务 tab 的 12 个金额字段均为字符串,勿按 number 解析;orderId/groupBatchId亦为字符串防精度丢失。 - teamNo 语义:本户团号、逐户不同、订金支付成功后才生成;未付订金/团期级行为
null——前端不得回退成orderNo、填空串或拿团期侧编号顶替(否则无法区分「未付订金」与「已生成」)。 - 默认视图:报名清单默认已带房型/房数/出行人(无需再传
includeNeeds=true/includeTravelers=true);毛利列与人数四件套字段已删除,人数改用participantCount+tierName/tierCode。
五、数据库行为
- 无表变更、无 Flyway、无新增列、无索引调整、无 H2
*-schema.sql改动。 - 分页在内存做(
assembleSubOrders返回全量后切片),无 SQL 变更;teamNo取自已取全列的订单对象,无新增查询与投影。
六、边界行为
- 分页参数越界一律归一/截断、HTTP 与
code均 200,不新增错误码。 - 空团期返
{records:[],total:0}而非data:null。 groupChatUnreadCount字段保留:hl-ui 全文搜索groupChatUnreadCount零命中,但未取得 mmg 书面回执,按工单 AC-1 兜底口径「拿不到回执则该字段保留、其余五个冗余字段照删」。- 复用既有错误码 589500 / 589507,本单错误码配额为「无新增」。
六.6、修改前后对比
| 端点 / 字段 | 改前 | 改后 |
|---|---|---|
A3 data 包装 |
裸数组 List<GroupBatchOrderItemRespVO> |
分页信封 PageResult:{records,total,page,pageSize} |
| A3 查询参数 | 无 page/pageSize | 新增 page(默认 1)/pageSize(默认 20,上限 200) |
A3 includeNeeds / includeTravelers 默认 |
false |
true(房型/房数/出行人默认返回) |
| A3 字段 | 含 estimatedCost/adultCount/childCount/youngChildCount/babyCount |
删 5 个(人数改 participantCount + tierName/tierCode;毛利改核单页) |
| A3 逐行 | 无 teamNo |
补 teamNo(逐户不同,未付订金 null) |
财务顶层 6 + totals 3 + items[] 3 金额 |
JSON number(如 40200.00) |
JSON string(如 "40200.00") |
| 财务逐户行 | 无 teamNo |
补 teamNo |
| 预支逐行 | 无 teamNo |
补 teamNo(团期级行 null) |
(🟢 A3 的 6 个中文配对字段与三处 teamNo 装配随零破坏批 PR-1 先行合入,此表为本单破坏批与 PR-1 合并后的整体前后对比。)
六.7、影响评估
- 最危险失败形态:老前端读
data.length(原裸数组)改后拿到undefined,报名清单整个空白。缓解:本 changelog 先行 + 拿 mmg 「已知悉、将同步改前端」回执;后端上线后做后端契约回归(断言records/total/分页字段),页面回归由frontend_status单独跟踪、不作为后端关单条件(复审修正 2)。不提供兼容期双形态返回。 - 金额形态 number→string:前端解析财务 tab 的 12 个金额字段与 A3 三个金额字段的代码需改(不能直接当数字算术)。
- 删字段:前端「预计毛利」列删除;人数四件套的直接引用改用
participantCount(总数)+tierName/tierCode(档位)。 - teamNo null 分支:前端逐户/逐行按
null渲染,不得兜底成orderNo/空串/团期编号。 - 回滚:后端零 DDL、零网关路由、零新增错误码,回滚只需回退代码分支;
#7533团级文档经保留的内部全量方法读取,不受本单分页影响。
七、不影响范围
GroupBatchService.java零行改动;订单级端点、团期列表/看板/详情端点结构未改;无网关路由、无 Feign/MQ、无 DDL。- 财务包内其余 26 处未加
@JsonSerialize的BigDecimal字段(含 3 处 ReqVO)本单一处未碰。 - 6 个中文配对字段与 3 处 teamNo 的装配逻辑随零破坏批 PR-1 先行合入;本破坏批只做删字段 + 分页 + include* 默认 + 金额形态。
八、测试环境已验证
2026-09-14 TEST 网关(https://api.test.1814.love:9443,自签 admin token)+ 真 MySQL 实测(部署归属 hl-order-service-v3 | refactor/7536-a3-breaking-pr2 | 513258beb | 0/N | ok;本单已合入 dev-v3,PR #7688):
- A3 分页:56 户团
?page=1&pageSize=20→data={records:[20],total:56,page:1,pageSize:20};page=9→records=[]、total=56、不抛异常;pageSize=500→pageSize=200、records=56;空团 →{records:[],total:0}(非data:null)。 - include 默认*:不传 →
roomCount/roomType/specialNeeds/travelers装配;显式includeNeeds=false&includeTravelers=false→ 四项值 null。 - 中文配对:
roomTypeName(STANDARD→标间)+ 5 个<字段>Name;contractStatus=null → contractStatusName=null、payStatus=DEPOSIT_PAID → 已付定金。 - 删字段:
records[0]无estimatedCost/adultCount/childCount/youngChildCount/babyCount;groupChatUnreadCount按 AC-1 兜底保留。 - 财务 12 金额:顶层 6 +
totals3 +items[]3 全为字符串(receivableAmount="327600.00"等);withdrawnCount=17仍为数字。 - teamNo:有团号户
orders/finance返26-7464与 SQL 逐字相等;未付订金户两端点返 JSONnull。 - AC-R1(与 #7533 共验):56 户团
print-itinerarytotalHouseholds=56、roster56 户、A3 首页仍 20 条、旧内部全量调用点编译通过。 - 全量单测:
mvn -o -pl hl-order-service-v3 -am test(Testcontainers)Tests run 10811 / 0 fail / 0 error / 7 skip,含 RedLineArchTest/MapperBoundaryArchTest,BUILD SUCCESS。
十、相关文档
- 工单 #7536
- 方案文档:
docs/group/团期闭环整改方案-v1.1.md§6.1 / §6.2 / §6.3 - 全过程记录:HL 仓
dev-records/records/2026-09-14-local-7536-a3-paging-breaking.md
关联 / 联系人
- 后端:jw(破坏批:分页 + 删冗余 + 金额形态;中文配对/teamNo 装配随 PR-1)
- 前端:mmg(必须整页面批量切换:读
data.records/data.total;删「预计毛利」「人数四件套」列,人数改 participantCount + tierName;财务金额按字符串解析;三处 teamNo 逐户渲染、null 不兜底)