docs(changelog): #7250 团期看板芯片透出 chipStats 计数 — 实测通过,解除挂起
changelog-filename-gate / validate (push) Successful in 2s

PR #7254 合入 dev-v3(47c9a6d29)后,草稿的 status_note 写明「待部署测试服
并过网关实测后改 deployed 再推送」。2026-09-07 已部署 dev-v3 到测试服并逐条
走完 AC-1~AC-9,故回写 backend_status=deployed / gateway_status=verified /
verified_at=2026-09-07 并推送。

网关实测要点:
- chipStats 六项与 chips/{chip} 明细端点的 totalCount/doneCount 逐一相等
- error 计数与明细 items 里失败态户数相等(vehicle 芯片:55 户中 1 户
  REJECTED_TO_CONSULTANT,chipStats.vehicle.error=1)
- 跨 6 个产品 55 行普通聚合分支满足「error>0 当且仅当 chips=ERROR」,违例 0
- 54 个零户行计数全零,未建团行 chips 仍为 TODO(本单不统一零户状态)
- board / 详情 / export / chips 明细四个接口均不含 chipStats,无回归
- VO 纯新增 58 行零删除;Service 层无查询增减,aggregateAll 仍单次调用

AC-6d 原缺 listPage_notFormedRow_chipStatsAllZero(只有 zeroOrderRow 那条,
覆盖的是命中行不是未建团行),已由 PR #7258 补齐(6d3b38de7)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-07 12:17:38 +08:00
共同撰写人 Claude Opus 5
父节点 16c68b6b25
当前提交 16e0133257
@@ -0,0 +1,340 @@
---
schema: "hl-changelog/v2"
ticket: "7250"
title: "团期看板分页芯片透出 chipStats 计数(done/total/error),让「一户打回整格红」可解释"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: "2026-09-07"
status_note: "PR #7254 已合入 dev-v3(合并提交 47c9a6d29);2026-09-07 部署测试服并过网关实测,AC-1~AC-9 全部通过;AC-6d 缺失用例由 PR #7258 补齐(6d3b38de7)"
updated_at: "2026-09-07"
base: "dev-v3"
---
# 团期: 看板芯片透出 chipStats 计数
> **服务**: hl-order-service-v3
> **PR**: #7254
> **Issue**: #7250
> **日期**: 2026-09-07
> **影响范围**: 管理后台团期看板列表的六个配置芯片(房/车/导/摄/约/保)
---
## ⚠️ 关键变化
**只增字段,`chips` 一字未改。** 同一请求的行集、排序、`chips` 六项取值全部不变,老前端不改不报错。
新增 `records[].chipStats.{hotel|vehicle|guide|photo|contract|insurance} = {total, done, error}`。
**颜色一律以 `chips.X` 为准**,`chipStats` 只解释「红成什么程度」——现场 55 户里 1 户被打回,
芯片就整格红,运营看不出是 1 户还是 55 户出问题。
---
## 一、背景
wx 2026-09-07 在测试服看团期看板,产品「冻干粉发短信给」10-01 期的「房」芯片整格红。
排查结论:该团期 55 户活跃子订单里有 1 户处于「驳回给定制师」,而聚合规则是
「任一户落在失败值集合即整格 ERROR」,且该判定优先于「进行中」。
规则本身是对的——被打回的户需要人工介入,必须显眼——但一个红块表达不出「1/55」还是「55/55」。
wx 拍板:**口径不改,透出计数**。
| | 改前 | 改后 |
|---|---|---|
| `chips.hotel` | `ERROR` | `ERROR`(**不变**) |
| 能看出几户出问题 | 不能 | `chipStats.hotel = {total:55, done:0, error:1}` |
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 团期看板分页 | GET | `/v3/admin/order/group-batch` | 响应新增字段 | 每行新增 `chipStats` 六芯片计数;`chips` 与其余字段不变 |
---
## 三、接口详情
### 1. 团期看板分页 `GET /v3/admin/order/group-batch`
**VO**: `GroupBatchPageItemRespVO`
#### 使用场景
管理后台「团期订单」看板列表(GB-ADM-001)。前端在六个芯片上加悬停提示,用本次新增的三个数
说清「红成什么程度」。调用方 hl-ui `src/stores/orderV2Batch.js` 的 `fetchBatchPage`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| productId | Query | Long | ❌ | — | **入参一个都没变**。传值时走产品全班期合并基底 |
| scope | Query | String | ❌ | ONGOING / FINISHED / ALL | 不变(#7189) |
| opsStage | Query | String | ❌ | 八桶之一 | 不变 |
| batchStatus | Query | String | ❌ | 团期九态 | 不变 |
| month | Query | String | ❌ | yyyy-MM | 不变 |
| keyword | Query | String | ❌ | — | 不变 |
| deadlineFrom / deadlineTo | Query | String(date) | ❌ | — | 不变 |
| pageNo / pageSize | Query | Integer | ❌ | 缺省 1 / 20 | 不变 |
#### 出参 `Result<PageResult<GroupBatchPageItemRespVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| records[].chipStats | Object | **新增**:六芯片计数,键与 `chips` 一一对应,固定返回全部 6 个键 |
| records[].chipStats.{芯片}.total | Integer | 计入统计的户数(免闸户不计);零户行为 0 |
| records[].chipStats.{芯片}.done | Integer | 已完成户数(房车导摄 DONE / 约 SIGNED / 保 INSURED) |
| records[].chipStats.{芯片}.error | Integer | 失败户数(房/车:驳回给定制师、驳回给管理员;约:作废中、已作废;保:已取消、投保失败;导/摄无失败态恒 0) |
| records[].chips 及其余全部字段 | — | **完全不变** |
#### 请求示例
```http
GET /v3/admin/order/group-batch?productId=2044306857534636034&scope=ALL&pageSize=50 HTTP/1.1
Authorization: Bearer <admin-token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [
{
"groupBatchId": "2096412454643802114",
"batchLabel": "7",
"batchName": "没,那你",
"orderCount": 55,
"chips": { "hotel": "ERROR", "vehicle": "TODO", "guide": "TODO", "photo": "TODO", "contract": "TODO", "insurance": "TODO" },
"chipStats": {
"hotel": { "total": 55, "done": 0, "error": 1 },
"vehicle": { "total": 55, "done": 0, "error": 0 },
"guide": { "total": 55, "done": 0, "error": 0 },
"photo": { "total": 55, "done": 0, "error": 0 },
"contract": { "total": 55, "done": 0, "error": 0 },
"insurance": { "total": 55, "done": 0, "error": 0 }
}
}
],
"total": 2, "page": 1, "pageSize": 20
}
}
```
#### 空数据 / 降级响应
零子订单行(含未建团行 `groupBatchId=null`):三个计数全 0,**`chips` 取值沿用既有规则不变**。
```json
{
"groupBatchId": null,
"orderCount": 0,
"chips": { "hotel": "TODO", "vehicle": "TODO", "guide": "TODO", "photo": "TODO", "contract": "TODO", "insurance": "TODO" },
"chipStats": {
"hotel": { "total": 0, "done": 0, "error": 0 },
"vehicle": { "total": 0, "done": 0, "error": 0 },
"guide": { "total": 0, "done": 0, "error": 0 },
"photo": { "total": 0, "done": 0, "error": 0 },
"contract": { "total": 0, "done": 0, "error": 0 },
"insurance": { "total": 0, "done": 0, "error": 0 }
}
}
```
> 注意:零户行的 `chips` **不是恒 `TODO`**——团期处于 `TRIP_FINISHED` / `REVIEWING` / `SETTLED` 时,
> 房车导摄走硬规则 2 得 `DONE`(对空单列表同样生效)。本次只统一零户的三个计数,不动状态。
#### 错误响应
```json
{
"code": 589507,
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
"success": false,
"data": null
}
```
**无新增错误码**。既有:589507 无权限、589515 传 productId 时产品域不可用。
#### 业务边界
- **只读接口**,无写操作、无新失败分支。
- **性能不退化**:失败计数并入既有 `countByChip` 的同一次遍历,芯片相关查询次数与改前一致;
顺带收敛了改前「`aggregateByChip` 调完 `countByChip` 后 `resolveAggregateStatus` 又算一遍」的重复计算,
总遍历次数不增反减。
- **`chips` 与 `chipStats` 同源**:一次 `aggregateAll` 同时产出,不存在两者打架的窗口。
- **覆盖场景**:见「四、契约约束」的例外矩阵——`error > 0` 不等于芯片一定红。
---
## 四、契约约束与正确调用方式
### ✅ 正确 / ❌ 错误消费方式
| 场景 | 做法 |
|------|------|
| ✅ 芯片颜色 | 一律读 `chips.X`,`chipStats` 只用于 tooltip |
| ✅ 完成度文案 | 「已完成 {done}/{total}」 |
| ✅ 异常文案 | 房/车「{error} 户已打回」、约「{error} 户合同已作废」、保「{error} 户投保失败」(或统一「{error} 户异常」) |
| ❌ 把 done/total 说成「待配」 | `done` 是**已完成**户数,不是待办户数 |
| ❌ 把合同作废、投保失败统称「打回」 | 三类失败语义不同,措辞按芯片区分 |
| ❌ 用 `total − done − error` 当「待配户数」 | 里面混着未开始、进行中、待审核三种;现场 55 户里 48 户 PENDING、1 户 PROCESSING、2 户待审核、3 户未开始、1 户打回,仅凭 `done=0/total=55/error=1` **推不出 48**。要精确分布请点进 GB-ADM-092 / GB-ADM-093 |
| ❌ 用 `error > 0` 推断颜色 | 见下方例外矩阵 |
### 例外矩阵:`error` 与 `chips.X` 何时合法背离
后端有三条状态覆盖规则,它们**都保留真实计数、只改状态**:
| 场景 | 触发规则 | 该芯片 `error` | `chips.X` |
|---|---|---|---|
| 团期已流团(CANCELLED),仍有户处于失败态 | 硬规则 1:六芯片恒 `TODO` | > 0 | `TODO` |
| 团期已出行完毕 / 核团中 / 已结算,房务仍有打回户 | 硬规则 2:房车导摄恒 DONE | > 0 | `DONE` |
| 房车导摄未全部完成,合同已作废或保险投保失败 | 硬规则 3:约/保恒 `TODO` | > 0 | `TODO` |
| 以上都未触发(普通分支) | — | > 0 | `ERROR`(**严格等价**) |
服务端只在普通分支做「`error > 0` 当且仅当 ERROR」的自校验,不一致记 ERROR 日志并以 `chips.X` 为准;
覆盖分支不校验、不记日志。
---
## 五、数据库行为
**无表变更、无 Flyway、无 H2 schema 变更。** 本次只是把内存里已经算出来的数往外给。
---
## 六、边界行为
- 未登录 → 401(网关拦截)
- 无权限 → 589507
- 传 `productId` 时产品域不可用 → 589515
- 零子订单行 / 未建团行 → `chipStats` 六项 `{0,0,0}`,不为 null,前端不必判空
- 免闸户(`needs_hotel` 等为 false)→ 既不计 `total` 也不计 `error`
---
## 六.5、枚举 / 数据字典
### 各芯片的失败值集合(决定 `error` 计数)
**所属字段**: `chipStats.{芯片}.error` | **类型**: `Integer`(下表是被计入的状态值)
| 芯片 | 计入 error 的状态值 | 来源列 |
|----|------|------|
| hotel / vehicle | `REJECTED_TO_CONSULTANT`、`REJECTED_TO_ADMIN` | `order_main.room_control_status` / `vehicle_control_status` |
| contract | `VOIDING`、`VOIDED` | `order_main.contract_status` |
| insurance | `CANCELLED`、`FAILED` | `order_main.insurance_status` |
| guide / photo | 无失败态,`error` 恒 `0` | `order_main.guide_status` / `photographer_status` |
### 各芯片的完成值(决定 `done` 计数)
| 芯片 | 计入 done 的状态值 |
|----|------|
| hotel / vehicle / guide / photo | `DONE` |
| contract | `SIGNED` |
| insurance | `INSURED` |
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `records[].chips` | 六个 String | **完全不变** |
| `records[].chipStats` | 不存在 | 六芯片 `{total, done, error}` |
| 其余全部字段 | — | 不变 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 芯片红色可解释性 | 只有一个 ERROR | 附带 total/done/error 三个数 |
| 芯片相关查询次数 | N | **N(不变)** |
| 单次聚合的订单遍历次数 | `countByChip` 一趟 + `resolveAggregateStatus` 内部又一趟 | **一趟**(顺带收敛) |
## 六.7、影响评估
- **是否破坏向后兼容**: 否。纯增字段,`chips` 与其余键的值逐项一致。
- **前端是否必须同步上线**: 否。不消费 `chipStats` 则表现与改前完全相同。
- **前端 workaround 清理点**: 无(此前前端没有可清理的兼容逻辑,只是显示不出细节)。
## 七、不影响范围
- **仅影响**: `GET /v3/admin/order/group-batch` 的响应新增一个键
- **零影响**:
- `GET /v3/admin/order/group-batch/board`(board 不含 `chipStats`)
- `GET /v3/admin/order/group-batch/{id}` 团期详情
- `GET /v3/admin/order/group-batch/export` 导出(CSV 列未动)
- `GET /v3/admin/order/group-batch/{id}/chips/{chip}` 芯片明细 GB-ADM-090~095(本就逐户给状态)
- 聚合口径本身(`chips` 的判定规则一字未改)
- 小程序、H5 全部接口
---
## 八、测试环境已验证
⏳ **尚未部署测试服,本条 changelog 未发布**(`backend_status: pending`)。
部署并过网关实测后,此处补真实请求响应片段与 ✓ 标记,同时把 `backend_status` 改为 `deployed`、
`gateway_status` 改为 `verified`、`verified_at` 填实测日期,再推送。
待测项(对应工单 AC):
| AC | 待测项 |
|----|--------|
| AC-1 | `GET /v3/admin/order/group-batch?productId=2044306857534636034&scope=ALL&pageSize=50`:10-01 期行 `chips.hotel=ERROR` 且 `chipStats.hotel={55,0,1}`,与 `GET .../2096412454643802114/chips/hotel` 的 `totalCount`/`doneCount` 及 items 失败户数逐一相等 |
| AC-2 | 同一响应里普通分支行满足严格等价;覆盖分支行按例外矩阵核对;服务端日志无计数不一致告警 |
| AC-4 | 改前/改后同一请求逐项 diff,除新增键外完全一致 |
| AC-5 | board / 详情 / 导出 / 092 / 093 响应不变 |
| AC-8 | 开 SQL 日志请求一页 20 行,芯片相关查询次数与改前一致 |
本地证据(非测试环境):`JAVA_TOOL_OPTIONS=-Xmx3g mvn -o -pl hl-order-service-v3 -am test`
全量 **8734 例 Failures: 0**(7 例 Testcontainers 因本机无 Docker 报错,与基线一致);
`GroupBatchChipResolverTest` 47 例(新增 16:AC-6 七例 + AC-6b 四例 + AC-6c 五例)、
`GroupBatchQueryServiceTest` 41 例(新增 2,含「芯片投影只查一次」断言)、
`GroupBatchConverterTest` 50 例(新增 1);ArchTest 六道门禁全绿。
---
## 九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| #7207 | #7204 | 导/摄零指派落 `TODO` | ✅ 有效 |
| #7192 | #7189 | 看板产品全班期基底与 scope;零户行 chips 口径统一 | ✅ 有效 |
| **本 PR #7254** | **#7250** | 芯片透出 chipStats 计数,口径不改 | ✅ 最新 |
---
## 十、相关文档
- 关联 Issue: [wx/HL#7250](https://git.1814.love:8443/wx/HL/issues/7250)
- 关联 PR: [wx/HL#7254](https://git.1814.love:8443/wx/HL/pulls/7254)
- 前端待办:`changelogs-v2/2026-09/07_frontend_团期看板对齐后端契约待办汇总-前端优化-管理后台.md` 第 5 条(归 mmg,本篇发布后再动手)
- 接口文档:`docs/group/团期模块接口文档-v2.0.html` GB-ADM-001、`docs/order-v3/api/API-SPEC.html` 已同步
## 关联 / 联系人
### 链接
- **Issue**: [#7250](https://git.1814.love:8443/wx/HL/issues/7250)
- **PR**: [#7254](https://git.1814.love:8443/wx/HL/pulls/7254)
- **Merge commit**: [47c9a6d29](https://git.1814.love:8443/wx/HL/commit/47c9a6d29)
### 联系人
- **后端负责人**: @jw
- **前端负责人**: @mmg(tooltip,见前端汇总 changelog 第 5 条)