文件
hl-api-changelog/changelogs-v2/2026-10/03_8747_团期行程汇总按中途终止日截断-修改接口-管理后台.md
T

422 行
20 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8747"
title: "团期行程汇总与下钻按出行中终止日截断:终止户之后的天不再计入覆盖户数,天头列出终止户"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "17c98e509a296fc356835808f8741606d2aad8be"
target_release: "v2.1"
verified_at: "2026-10-03"
status_note: "已合并 dev-v3(9d1e87b38)并部署 TEST,自签 token 经网关实测:一团 3 户同一份 3 天行程、1 户 D1 出行中终止(真实终止接口),D1 仍 3/3 全团一致,D2、D3 各项 2/3 部分户、天头列出终止户、本日合计由 385.00 / 730.01 变为 0;下钻终止户 terminated=true、endDayNumber=1;对照团与两个存量团改前改后去掉新字段逐字一致(38 项);团级行程单、签单凭证改前改后逐字一致。前端待做:天头渲染「N 户已于 Dx 终止」并可展开户清单,不在每个节点行上重复标;下钻行按 terminated / endDayNumber 标「已于 Dx 终止」。前端已交付(2026-10-03):batch-itinerary.js 加 4 展示纯函数(天头提示停同日「N 户已于 Dx 终止」/不同天退化、清单行、下钻行按 endDayNumber!=null 且本行 dayNumber>endDayNumber 判标记,terminated 仅户级开关);ItineraryTab 天头渲染+点击展开户清单(节点行不重复标);ItineraryNodeModal 截断行标「已于 Dx 终止」;API spec 新增 4 例+组件 spec 新建 3 例全绿,提交 17c98e50。"
updated_at: "2026-10-03"
base: "dev-v3"
---
# order-v3: 团期行程汇总按出行中终止日截断
**服务**: hl-order-service-v3
**PR**: `#8766`(已合入 `dev-v3`,合并提交 `9d1e87b38`)
**Issue**: #8747
---
## ⚠️ 关键变化
🟢 **两个接口纯加字段**:汇总 `days[]` 新增 `terminatedOrderCount`、`terminatedOrders[]`;下钻 `items[]` 新增 `terminated`、`endDayNumber`。其余字段、入参、判权、错误码不变。
🔴 **有户出行中终止的团,终止日之后的读数会变**:该户仍计入分母 `totalHouseholds`,但不再计入那几天任何项的户数 `householdCount`。原本全团一致的项会变成部分户(如 2/3),不再计入本日合计,`dayTotalAmount` 相应变小(可能为 0)。这是需求口径,不是回归。
🟢 **团内没有终止户时,响应除新字段(0 / 空列表 / `false` / `null`)外逐字不变。**
---
## 一、背景
出行中终止(`POST /v3/admin/order/:id/terminate`)只把订单置为 `COMPLETED`,并在 `order_terminate_refund.end_day_number` 记下停在第几天;行程表一行不动。团期行程逐日汇总(GB-ADM-018)与逐户下钻(GB-ADM-019)此前只排除已取消户,所以终止日之后那几天,这一户仍按完整行程计入覆盖户数,节点会被判成「全团一致」并计入本日合计——页面显示整团都去了,实际少一户。
实施单 04 §3.11.3 在 2026-09-01 已定「保留、可见、按天截断」,本单按此落地。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 团期行程逐日汇总(GB-ADM-018) | GET | `/v3/admin/order/group-batch/:groupBatchId/itinerary` | 修改 | 终止户按天截断;`days[]` 新增 `terminatedOrderCount` / `terminatedOrders[]` |
| 2 | 团期行程某项逐户下钻(GB-ADM-019) | GET | `/v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey` | 修改 | 户数与一致性按截断口径;`items[]` 新增 `terminated` / `endDayNumber` |
---
## 三、接口详情
**终止户的计数口径**(两个接口相同):
| 项 | 口径 |
|---|---|
| 怎么算「已终止」 | `order_terminate_refund` 有该户未软删的行;截断天取 `end_day_number`。按全团活跃户一次批量查,与户数无关 |
| 分母 `totalHouseholds` | **计入**终止户(已取消户照旧排除) |
| `dayNumber ≤ endDayNumber` 的天 | 该户照常计入 |
| `dayNumber > endDayNumber` 的天 | 该户**不计入**任何项的户数 `householdCount`,**不参与**该天的一致性判定与本日合计 |
| 行程记录 | 不隐藏:下钻照常列出该户那一行(`has=true`、带价量);汇总项的 `nodeIds` 照常含该户的节点 ID,核单下钻要用 |
| 节点级「已用」 | 不做(实施单 04 §3.11.3:那是核单的职责) |
**本日合计为什么会变小**:本日合计只算 `ALL_SAME`(全团一致)的项。终止户退出后,终止日之后的项最多只有 M−1 户有,按「只有部分户有」判为 `PARTIAL`,不再计入。所以有户中途终止的团,终止日之后各天的 `dayTotalAmount` 会变小,所有项都变成部分户时为 0。
### 1. 团期行程逐日汇总(GB-ADM-018) `GET /v3/admin/order/group-batch/:groupBatchId/itinerary`
**VO**: `Result<GroupBatchItineraryRespVO>`(无请求体)
#### 使用场景
团期详情「行程」页签。天头按 `terminatedOrderCount` 渲染「N 户已于 Dx 终止」,点开看 `terminatedOrders`;节点卡片的「N/M 户」与本日合计按新口径显示。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期 ID | **不变** |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| days[].terminatedOrderCount | Integer | 🆕 本天已处于出行中终止的户数(截断天 < 本天)。这些户仍计入分母,但不计入本天任何项的户数;无终止户为 0,前端为 0 时不渲染天头提示 |
| days[].terminatedOrders[] | Array | 🆕 本天已终止户清单,按 `orderId` 升序;无终止户为空数组 |
| days[].terminatedOrders[].orderId | String | 🆕 子订单 ID(雪花,按字符串传) |
| days[].terminatedOrders[].teamNo | String | 🆕 团号;无团号为 null |
| days[].terminatedOrders[].orderNo | String | 🆕 子订单编号 |
| days[].terminatedOrders[].customerName | String | 🆕 客户姓名 |
| days[].terminatedOrders[].endDayNumber | Integer | 🆕 截断天:该户行程停在第几天 |
| days[].nodes[].householdCount | Integer | **口径变**:终止户在截断天之后不计入;只有终止户排了的项可为 0 |
| days[].nodes[].consistency / countedInDayTotal | String / Boolean | **口径变**:按截断后的户数判;终止日之后原全团一致的项变为 `PARTIAL`、不计入合计 |
| days[].dayTotalAmount / excludedNodeCount | BigDecimal / Integer | **口径变**:随上面的一致性结果重算,可能变小 |
| 其余字段 | — | **不变**(`totalHouseholds` 含终止户;`nodeIds` 含终止户的节点 ID) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2106297832652881921/itinerary HTTP/1.1
Authorization: Bearer <管理员 token>
```
#### 响应示例
TEST 实测(截取第 2 天与其首项;乙户「娜仁其其格」第 1 天行程结束后出行中终止):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2106297832652881921",
"totalHouseholds": 3,
"dayCountConsistent": true,
"dayCount": 3,
"dayCountOutliers": [],
"days": [
{
"dayNumber": 2,
"dayDate": "2026-10-21",
"dayTitle": "是的复古风的水果",
"dayTotalAmount": 0,
"excludedNodeCount": 7,
"terminatedOrderCount": 1,
"terminatedOrders": [
{
"orderId": "2106297833152004097",
"teamNo": "26-5148",
"orderNo": "HL20261003161831138",
"customerName": "娜仁其其格",
"endDayNumber": 1
}
],
"nodes": [
{
"nodeKey": "c44f160ad5e71a8b",
"nodeIds": ["2106297832707407873", "2106297833231695873", "2106297834452238339"],
"nodeName": "巴尔虎蒙古部落",
"nodeType": "SCENIC",
"resourceType": "SCENIC_SPOT",
"resourceId": "3001000000000000016",
"resourceName": "巴尔虎蒙古部落",
"startTime": null,
"timePeriod": "EARLY_MORNING",
"consistency": "PARTIAL",
"householdCount": 2,
"totalHouseholds": 3,
"unitPrice": null,
"unitPriceMin": 80.0,
"unitPriceMax": 80.0,
"quantity": null,
"quantityMin": 1,
"quantityMax": 1,
"totalAmount": null,
"countedInDayTotal": false
}
]
}
]
}
}
```
同一团改前(旧构建)第 2 天:各项 `householdCount=3`、`ALL_SAME`,`dayTotalAmount=385.0`,没有终止字段。
#### 空数据 / 降级响应
- 团期无活跃子订单:`days=[]`,不查行程、不查截断天。
- 团内没有终止户:每天 `terminatedOrderCount=0`、`terminatedOrders=[]`,其余字段与改前逐字一致。
- 终止在最后一天(截断天 ≥ 行程天数):没有被截断的天,各天 `terminatedOrderCount=0`。
#### 错误响应
| code | 场景 |
|---|---|
| 589507 | 无 `group-batch:view`,或定制师查看非本人名下的团期(**不变**) |
| 589500 | 团期不存在(**不变**) |
```json
{
"code": 589500,
"message": "团期不存在",
"data": null
}
```
#### 业务边界
- 截断只影响计数,不改行程数据,不隐藏任何项。
- 已取消户照旧排除,不进分母,也不进截断天查询。
- `dayDate` / `dayTitle` 仍按全部活跃户的多数值取,不受终止影响。
- 天数一致性预警(`dayCountConsistent` / `dayCountOutliers`)不受终止影响:终止不改行程天数。
### 2. 团期行程某项逐户下钻(GB-ADM-019) `GET /v3/admin/order/group-batch/:groupBatchId/itinerary/nodes/:nodeKey`
**VO**: `Result<GroupBatchItineraryNodeDetailVO>`(无请求体)
#### 使用场景
汇总卡片点进来看「哪几户有、哪几户不一样」。终止户那一行照常列出,前端按 `terminated` / `endDayNumber` 标「已于 Dx 终止」。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期 ID | **不变** |
| nodeKey | Path | String | ✅ | 取自汇总接口,原样回传 | **不变** |
| dayNumber | Query | Integer | ❌ | 取自汇总卡片所在天;不传 = 跨天全看 | **不变** |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| items[].terminated | Boolean | 🆕 该户是否已出行中终止(户级标记,与本行是哪天无关) |
| items[].endDayNumber | Integer | 🆕 截断天;未终止为 null。本行 `dayNumber` 大于它时,前端标「已于 Dx 终止」 |
| householdCount / consistency | Integer / String | **口径变**:只认未被截断的出现,与汇总卡片同口径;跨天下钻时,终止户在截断天及之前有出现就计户 |
| items[].has | Boolean | **不变**:该户行程里有没有这一项;终止户截断后的出现仍为 `true`(记录不隐藏),但不计入 `householdCount` |
| 其余字段 | — | **不变** |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2106297832652881921/itinerary/nodes/c44f160ad5e71a8b?dayNumber=2 HTTP/1.1
Authorization: Bearer <管理员 token>
```
#### 响应示例
TEST 实测(第 2 天「巴尔虎蒙古部落」,截取前两户):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2106297832652881921",
"nodeKey": "c44f160ad5e71a8b",
"dayNumber": 2,
"nodeName": "巴尔虎蒙古部落",
"consistency": "PARTIAL",
"householdCount": 2,
"totalHouseholds": 3,
"items": [
{
"orderId": "2106297832598355970",
"teamNo": "26-8025",
"orderNo": "HL20261003161830984",
"contactName": "孟庆和",
"customerName": "孟庆和",
"has": true,
"dayNumber": 2,
"unitPrice": 80.0,
"quantity": 1,
"totalAmount": 80.0,
"startTime": null,
"timePeriod": "EARLY_MORNING",
"terminated": false,
"endDayNumber": null
},
{
"orderId": "2106297833152004097",
"teamNo": "26-5148",
"orderNo": "HL20261003161831138",
"contactName": "娜仁其其格",
"customerName": "娜仁其其格",
"has": true,
"dayNumber": 2,
"unitPrice": 80.0,
"quantity": 1,
"totalAmount": 80.0,
"startTime": null,
"timePeriod": "EARLY_MORNING",
"terminated": true,
"endDayNumber": 1
}
]
}
}
```
#### 空数据 / 降级响应
- `nodeKey` 没有任何户命中:`householdCount=0`,每户一行 `has=false`,不报错(**不变**);`terminated` / `endDayNumber` 照常按户填。
- 团内没有终止户:每行 `terminated=false`、`endDayNumber=null`,其余字段与改前逐字一致。
#### 错误响应
| code | 场景 |
|---|---|
| 589507 | 无 `group-batch:view`,或定制师查看非本人名下的团期(**不变**) |
| 589500 | 团期不存在(**不变**) |
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"data": null
}
```
#### 业务边界
- 同一户跨天多行时,`terminated` / `endDayNumber` 每行相同(户级);是否被截断看本行 `dayNumber` 是否大于 `endDayNumber`。
- 跨天下钻(不带 `dayNumber`)的一致性只看未被截断的出现:终止户在截断后改过的价不会把该项判成各户不一致。
---
## 四、契约约束与正确调用方式
- 天头提示只看 `days[].terminatedOrderCount`,为 0 时不渲染;不要在每个节点行上重复标终止。
- 「N/M 户」直接用 `householdCount` / `totalHouseholds`,不要自己从下钻行数 `has=true` 去数——终止户截断后的行 `has=true` 但不计户。
- 下钻行的终止标记按 `endDayNumber != null && dayNumber > endDayNumber` 判,`terminated` 只是户级开关。
- `nodeIds` 仍含终止户的节点 ID,原样传给核单下钻 `POST /v3/admin/order/group-batch/:groupBatchId/settlement/node-lines` 即可看到全团核单行(含终止户已退未用的门票)。
---
## 五、数据库行为
- 零 DDL、零数据迁移、零写入。
- 新增一条只读批量查询:按全团活跃户 `order_id IN (...)` 读 `order_terminate_refund.end_day_number`(与核单下钻同一读口)。两个接口的查询次数由 2 次批量变为 3 次批量,均与户数无关。
---
## 六、边界行为
- 截断天是 `end_day_number`(停在第几天,1 起),该天本身照常计入,之后的天才截断。
- 终止户的 `order_status` 是 `COMPLETED`,留在活跃集里;已取消户(`CANCELLED`)照旧整体排除。
- 只有终止户在截断后的某天排了的项:汇总里照常列出,`householdCount=0`、`PARTIAL`、不计入合计,`nodeIds` 含该户节点 ID。
## 六.6、修改前后对比
| 场景 | 改前 | 改后 |
|---|---|---|
| 3 户同一行程,1 户 D1 终止,看 D2 某项 | `3/3`、`ALL_SAME`、计入合计 | `2/3`、`PARTIAL`、不计入合计 |
| 同上,D2 本日合计 | 385.00 | 0 |
| 同上,D1 | `3/3`、`ALL_SAME` | 不变 |
| 同上,D2 天头 | 无终止信息 | `terminatedOrderCount=1`,列出该户与 `endDayNumber=1` |
| 下钻 D2 终止户那一行 | 与其他户无区别 | `terminated=true`、`endDayNumber=1`,不计入 `householdCount` |
| 团内无终止户 | — | 除新字段外逐字不变 |
## 六.7、影响评估
- **是否破坏向后兼容**:字段层面否(纯新增);读数层面,有终止户的团在终止日之后的户数、一致性与本日合计会变——这是需求要修的错误读数。
- **前端是否必须同步上线**:否。不改前端时页面照常显示,只是终止日之后的卡片显示部分户、本日合计变小,没有「已终止」的说明;改了才能在天头说明原因。
- **团级文档**:团级行程单(`GET .../print-itinerary`)与签单凭证(`GET .../sign-voucher`)也复用行程汇总,但本单让它们走不截断的入口,输出保持原样(TEST 上含终止户的团改前改后逐字一致)。文档是否也按实际截断另行定口径。
- **回滚**:revert PR #8766 后重新部署 order-v3,无数据需要恢复。
---
## 七、不影响范围
- 两个接口的入参、判权(`group-batch:view` + 定制师归属)、错误码:不变。
- 团级行程单、签单凭证:不变。
- 核单下钻(GB-ADM-056):不变(本来就按户带截断天)。
- 订单级行程、终止行程接口本身、核单与退款计算:不变。
- 散客单(非团期):不涉及。小程序端:无影响。
---
## 八、测试环境已验证
**环境**:TEST(`https://api.test.1814.love`) **验证时间**:2026-10-03 16:18~16:57
**构建身份**:TEST 后端检出 `dev-v3 @ d778c9a71`(包含本单合并提交 `9d1e87b38`),order-v3 两个实例 16:51 前后重启。零写入判据:部署后连查 6 次对照团汇总,每次 `days[]` 都带 `terminatedOrderCount`(旧字节没有这个键)。
**身份**:自签 token 直打网关;造数用超管,读口取证用管理员账号(非超管)。
### 8.1 造数(全部经业务接口,唯一 SQL 见下)
| 团 | 团号 | 内容 |
|---|---|---|
| 主团 | `T26-6215`(`2106297832652881921`) | 新班期出发 2026-10-20,3 天行程每天 8 项;3 户各 2 成人线下全款,第 4 户下单后取消;成团 → 声明整团免车 → 乙户「娜仁其其格」出行中终止 `endDayNumber=1` |
| 对照团 | `T26-2604`(`2106297838424244226`) | 新班期,2 户全款,不终止 |
| 存量团 | `2106039583672299521`(5 户)、`2104839654727618562`(3 户) | 只读,近 6 小时无改动、无终止户 |
终止接口要求订单处于出行中,TEST 上从成团走到出行中要过七道出团门和夜间任务,故只对乙户一张订单用 SQL 把 `order_status` 由 `CUSTOMIZING` 置为 `TRAVELLING`,随后调真实终止接口;终止后订单 `COMPLETED`、`order_terminate_refund.end_day_number=1`。
### 8.2 改前 / 改后(同一批数据,旧构建取一次、新构建取一次)
| 检查 | 结果 |
|---|---|
| 主团 D1 | 8 项均 `3/3`、`ALL_SAME`,`terminatedOrderCount=0` |
| 主团 D2 / D3 | 改前各项 `3/3`、`ALL_SAME`,本日合计 385.00 / 730.01;改后各项 `2/3`、`PARTIAL`、不计入合计,本日合计 0 / 0,`terminatedOrderCount=1`,清单为乙户、`endDayNumber=1` |
| 主团 D2 / D3 `nodeIds` | 仍含 3 户的节点 ID |
| 主团下钻 D2(两项) | 乙户 `terminated=true`、`endDayNumber=1`;其余两户 `false` / `null`;`householdCount=2`、`PARTIAL` |
| 主团 `totalHouseholds` | 3(终止户计入、取消户排除);取消户在汇总与下钻中均不出现 |
| 对照团、两个存量团 | 汇总、逐项下钻(按天与跨天)去掉新字段后改前改后逐字一致;新字段全为 0 / 空 / `false` / `null` |
| 团级行程单、签单凭证(四个团) | 改前改后逐字一致,含主团 |
| 数据指纹 | 四个团改前改后订单与行程行的最近改动时间一致,比对期间没有他人写入 |
合计 94 项检查全部通过。
### 本地证据
| 项 | 读数 |
|---|---|
| `GroupBatchItineraryServiceTest` | 41 例全过,其中本单新增 10 例(截断、截断当天、最后一天终止、只有终止户的项、跨天下钻、无终止户、取消户、截断天一次批量查询、`summaryAsPlanned` 不查截断天) |
| 截断天查询次数 | 4 户团断言 `mapTerminateEndDayByOrderIds` 只调用一次且入参为全部活跃户 |
| 范围回归(有 Docker) | `groupbatch` + `settlement` + `archunit` 共 340 类 / 5066 例,源码可执行类与报告逐类对账 340/340;2 例失败均为基底既有(`TeamNoResponseFieldGateTest`、`GroupBatchAdminReadEndpointOwnershipArchTest`,引入于 `788b9c149`,基底对照跑同样红),本单零新增 |
---
## 十、相关文档
- Issue `#8747`;PR `#8766`
- 需求:`docs/group/实施单/04-团期行程安排.html` §3.11.2 / §3.11.3
- 前置:Issue `#7378`(GB-ADM-018 / 019 原实现)、`#7873`(核单下钻同一读口)
## 关联 / 联系人
### 链接
- **Issue**: [#8747](https://git.1814.love/wx/HL/issues/8747)
- **PR**: [#8766](https://git.1814.love/wx/HL/pulls/8766)
- **Merge commit**: [9d1e87b38](https://git.1814.love/wx/HL/commit/9d1e87b38)
### 联系人
- **后端负责人**: @jw