文件
hl-api-changelog/changelogs-v2/2026-09/14_7536_团期子订单列表分页删冗余与财务金额字符串-修改接口-管理后台.md
T
Mimingguang 0b2853224e
changelog-filename-gate / validate (push) Failing after 2s
chore(changelog): #7536 回写前端 verified(mmg,657f14dd)
2026-09-14 17:22:41 +08:00

22 KiB
原始文件 Blame 文件历史

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


⚠️ 关键变化

🔴 破坏性变更,前端必须整页面批量切换(不提供兼容期双形态返回):

  1. A3 子订单列表 GET .../orders 返回包装从裸数组改分页信封:Result<List<GroupBatchOrderItemRespVO>> → Result<PageResult<GroupBatchOrderItemRespVO>>。data 由裸数组变为 {records, total, page, pageSize};新增 page/pageSize 查询参数(默认首页 20 条、pageSize 上限 200)。老前端读 data.length 会拿到 undefined、列表整个空白——这是本单最危险的失败形态。
  2. 删除 5 个冗余字段(GroupBatchOrderItemRespVO):estimatedCost(无写入点、恒 null)、adultCount/childCount/youngChildCount/babyCount(人数四件套)。前端毛利列删除、人数改用 participantCount(总数)+ tierName/tierCode(档位)。
  3. includeNeeds / includeTravelers 缺省值由 false 改为 true:roomCount/roomType/specialNeeds 与 travelers[] 默认返回;前端要精简响应须显式传 false。
  4. 财务 tab GET .../finance 的 12 个 BigDecimal 金额字段 JSON 形态 number → string(补 @JsonSerialize(using = ToStringSerializer.class),与报名清单 tab 对齐)。前端勿按 number 解析这些字段。
  5. 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(无证件号/手机号)
records[].estimatedCost / adultCount / childCount / youngChildCount / babyCount — ★已删除(毛利改核单页,人数改用 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 + totals 3 + 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 + totals 3 + items[] 3 全为字符串(receivableAmount="327600.00" 等);withdrawnCount=17 仍为数字。
  • teamNo:有团号户 orders/finance 返 26-7464 与 SQL 逐字相等;未付订金户两端点返 JSON null。
  • AC-R1(与 #7533 共验):56 户团 print-itinerary totalHouseholds=56、roster 56 户、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 不兜底)