--- schema: "hl-changelog/v2" ticket: "8215" title: "团期详情(A2)新增出参 subOrderCount——活跃子订单户数,与 A3 total / 看板 orderCount 同源" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "团期详情页右上角显示「子订单 0 户」,同页「已建子订单 12 户」「整团名单速览(12 户)」却是 12。2026-09-23 对 TEST 团期 2101506167098511362 逐接口实测,后端四个接口无一返回 0:A2 详情 formingRooms/enrolledRooms/maxRooms 全 12、A3 子订单列表 total=12 且 records 有数据、看板 orderCount=12、财务 items 12 条。右上角三个金额(应收 156760 / 已收 47940 / 待收 108820)与 A2 的 receivableAmount/receivedAmount/unpaidAmount 逐字一致,说明该 UI 绑的就是详情响应对象——而详情原有的 50 个字段里没有任何子订单户数字段,前端取到 undefined 渲染成 0。subOrderCount 这个名字在 order-v3 里原本只存在于看板统计条 VO(GroupBatchSummaryVO,口径是命中筛选的全部团期活跃子订单合计,属列表页统计条)。本次后端兜底:A2 详情新增出参 subOrderCount,取数复用既有契约方法 OrderService#countActiveByProductBatchIds——A3 的 total 与看板 orderCount 走的都是它,口径同为 order_status != CANCELLED 加 @TableLogic 软删过滤,刻意不另写 count,避免「同屏两个数字不一致」换个形式复发。字段恒非 null,无活跃子订单返 0 而非 null。已合并 dev-v3(PR #8216,merge commit fc0508981)并部署测试服,09:50 三接口同轮实测同为 12。路径、入参、权限码、其余出参字段零变化,网关无改动。前端侧需确认该页取的就是 subOrderCount 这个字段名,故 frontend_status 记 pending。 mmg 2026-09-23 复核: 页面上那个 0 的取数点是 BatchHero orderCount 计算属性 `d.orderCount ?? d.subOrderCount ?? 0`(src/views/order-v2/batch/detail/components/BatchHero.vue),第二棒早已读 subOrderCount——A2 部署后该处即渲染真实户数,前端零行为改动,后端选的名字正好接上。已补注释+spec 两例锁回落链(hl-ui@61a549bd chore),判 not_required。" updated_at: "2026-09-23" base: "dev-v3" --- # 团期详情(A2)新增出参 subOrderCount(管理后台) > **服务**: hl-order-service-v3(端口 8086/8186) ## 一、接口背景 团期详情页右上角同时渲染「成团状态 + 子订单户数 + 整团应收/已收/待收」。其中三个金额来自本接口, 而「子订单户数」在本次之前**本接口并不返回**——前端取到 `undefined`,渲染成 `0`, 与同页「已建子订单 12 户」「整团名单速览(12 户)」同屏打架。 本次在本接口补上该字段,取数与 A3 子订单列表、团期看板行**共用同一个契约方法**,三处必然同值。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 修改接口 | 新增出参 `subOrderCount`,其余字段与行为零变化 | ## 三、接口详情 ### 1. A2 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` **VO**: `GroupBatchDetailRespVO` #### 使用场景 团期详情页进入时拉取整团概览:状态机阶段、成团闸/满员闸计数、四项 ready 标志、整团金额三项, 以及本次新增的活跃子订单户数。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 团期 ID,非法或不存在返 GROUP_BATCH_NOT_FOUND | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | subOrderCount | Integer | **本次新增**。挂在本团期下的活跃子订单张数(户数)。口径:`order_main.product_batch_id` = 本团期 productBatchId 且 `order_status != CANCELLED`,软删由 `@TableLogic` 过滤。与 A3 子订单列表的 `total`、看板行的 `orderCount` 同一取数口径、同一契约方法,三处同值。恒非 null,无活跃子订单为 0 | | formingRooms | Integer | 成团判定用户数 = 线上已付款活跃订单数 + 产品域线下占位(#7287)。**与 subOrderCount 不是一回事**,有线下占位时必然大于后者 | | enrolledRooms | Integer | 已用房间数(既有字段,本次未改) | | receivableAmount | BigDecimal | 整团应收(既有字段,本次未改) | | receivedAmount | BigDecimal | 整团已收(既有字段,本次未改) | | unpaidAmount | BigDecimal | 整团待收 = max(0, 应收 − 已收)(既有字段,本次未改) | #### 请求示例 ```http GET /v3/admin/order/group-batch/2101506167098511362 HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2101506167098511362", "batchName": "jw测试1期", "opsStage": "FORMED", "opsStageName": "已成团", "subOrderCount": 12, "formingRooms": 12, "enrolledRooms": 12, "maxRooms": 12, "receivableAmount": "156760.00", "receivedAmount": "47940.00", "unpaidAmount": "108820.00" } } ``` #### 空数据 / 降级响应 团期存在但名下没有活跃子订单(全部 CANCELLED,或建团后尚未下单)时,`subOrderCount` 返 `0`, **不返 null、不缺字段**——null 在前端同样会渲染成空或 0,等于把本次要修的缺陷藏回去。 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2101506167098511363", "opsStage": "RECRUIT", "subOrderCount": 0, "receivableAmount": "0.00", "receivedAmount": "0.00", "unpaidAmount": "0.00" } } ``` #### 错误响应 ```json { "code": 589501, "message": "团期不存在", "data": null } ``` | 错误码 | 触发条件 | |---|---| | 589501 | groupBatchId 不存在或已软删 | | 403 | 缺 `group-batch:view` 权限码,或定制师访问非自己归属的团期 | #### 业务边界 - `subOrderCount` 只回答「这个团下面挂了几张活跃子订单」,**不含任何产品域线下占位**。 - 已取消(CANCELLED)子订单不计入;软删由 `@TableLogic` 过滤,与 A3 缺省(`includeCancelled=false`)一致。 - 与 A3 的 `total` 必然同值:两者调用同一个 `OrderService#countActiveByProductBatchIds` / 同一过滤条件。 - 退单户(withdraw)在未置 CANCELLED 前仍计入,与 A3 行为一致。 ## 四、契约约束与正确调用方式 - 前端渲染「子订单 N 户」请取 `data.subOrderCount`,**不要取 `formingRooms`**(那是成团判定用数,含线下占位)。 - 需要逐户明细时仍走 A3 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`,其分页包装为 `{ records, total, page, pageSize }`——列表字段名是 `records`,不是 `list`。 - 本字段是纯增出参,老调用方忽略它即可,无需改动。 ## 五、数据库行为 零数据库变更。本次不新增表/列/索引,不写任何数据;`subOrderCount` 的数据来源是既有列 `order_main.product_batch_id` + `order_main.order_status` 的只读聚合,走既有契约方法,未新增 mapper 查询。 ## 六、边界行为 - 团期不存在 → 589501,不返回半个对象。 - 团期存在、无活跃子订单 → `subOrderCount: 0`(见「空数据 / 降级响应」)。 - 团期下既有活跃单又有已取消单 → 只数活跃的,与 A3 缺省口径一致。 ## 六.6、修改前后对比 | 项 | 修改前 | 修改后 | |---|---|---| | 出参字段数 | 50 | 51 | | `subOrderCount` | **不返回**(前端取到 undefined,页面渲染成 0) | 返回活跃子订单户数,恒非 null | | 其余出参字段 | — | 逐字未变 | | 路径 / 入参 / 权限码 | — | 逐字未变 | | 取数来源 | — | 复用 `OrderService#countActiveByProductBatchIds`(A3 与看板同款),未新增查询 | ## 六.7、影响评估 - **兼容性**:纯增出参,老调用方忽略即可,无破坏性。 - **性能**:详情装配内多一次按单个 productBatchId 的活跃单计数,走既有契约方法(A1 列表/看板/合并行三处已在用), 单元素入参,无 N+1;落在既有 `@Transactional(readOnly = true)` 的库内装配段,不涉及 Feign。 - **回滚**:撤销 PR #8216 即可,无数据与配置残留。 - **未覆盖**:本次只证明后端返对了值。页面上那个 `0` 是否消失,取决于前端该处取的是不是 `subOrderCount` 这个字段名——属前端侧确认项,`frontend_status` 记 `pending`。 ## 七、不影响范围 - A3 团期下子订单列表、团期看板、团期财务总览:口径与字段零改动。 - `formingRooms` / `enrolledRooms` / 金额三项:取数来源与数值零改动。 - 网关:路径未变、无新增路由与权限码,`gateway_status: not_required`。 - 小程序端:本接口仅管理后台使用,未涉及。 ## 八、测试环境已验证 2026-09-23 09:50 测试服(api.test.1814.love:9443),团期 `2101506167098511362`(jw测试产品·第1期,已成团), 部署 `dev-v3 @ fc0508981`,三接口**同轮**实测: | 接口 | 字段 | 读数 | |---|---|---| | A2 团期详情 | `subOrderCount` | **12** | | A3 子订单列表 | `total` | **12** | | 团期看板 | `orderCount` | **12** | 同轮 `formingRooms=12`、`enrolledRooms=12`,与 `subOrderCount` 在本团期恰好同值(该团无线下占位)。 单元测试:`GroupBatchQueryServiceTest` 80 例 0 失败(新增 3 条,经 surefire XML 核实真执行,无 skipped), `GroupBatchQueryControllerTest` 9 例 0 失败,合计 89/0。 ## 十、相关文档 - 工单 #8215、PR #8216(merge commit `fc0508981`) - `formingRooms` 口径出处:#7287 - A3 分页包装形态(`records` 而非 `list`)出处:#7536 ## 关联 / 联系人 - 后端:jw - 前端:待确认该页取值字段名(`frontend_status: pending`)