docs(changelog): #8516 团期核单、结算拆成两个独立字段,出行完毕改名待核单
changelog-filename-gate / validate (push) Failing after 2s

两份:
- 新增接口(internal):POST /v3/internal/group-batch/:groupBatchId/review-status、
  /settlement-status,供财务回写整团核单、结算状态;主状态由两列推导,子订单同事务同步,
  任何一步都不推报账单。
- 修改接口(admin):团期主状态 TRIP_FINISHED 改为 PENDING_REVIEW「待核单」;详情 / 分页
  新增 reviewStatus / settlementStatus 及中文名;进度条核单、结算节点新增分支;三处入参
  旧值兼容;/settle 不再逐户推 ORDER 报账单。hl-ui 须同批改,TEST 先行、正式环境同批发布。

PR #8650 已合入 dev-v3(merge commit 54e64c50f),TEST 部署 dev-v3 dd0452916 并按
AC-01~15 验收通过,工单已关。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-30 22:38:06 +08:00
共同撰写人 Claude Opus 5.5
父节点 a29c0873e3
当前提交 5dc79ef2e6
共修改 2 个文件,包含 1650 行新增和 0 行删除
@@ -0,0 +1,590 @@
---
schema: "hl-changelog/v2"
ticket: "8516"
title: "团期新增核单 / 结算状态两个内部回写接口(财务调用):改一列即推导团期主状态、同事务同步子订单状态,任何一步都不推报账单"
consumer: "internal"
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: "新增 POST /v3/internal/group-batch/:groupBatchId/review-status 与 /settlement-status,供财务回写团期整团核单、结算状态。两个接口都是 internal:不经网关(公网网关对 /v3/internal/** 返回 code 403),直连 order-v3 带 X-Internal-Token,缺失或错误返回 HTTP 403;前端无需对接。团期主状态待核单 / 核单中 / 已结算由两列推导,与两列同一次原子更新落库;子订单在核单中、退回待核单、已结算、离开已结算四步同事务同步(只改状态、不推报账单),团期已核单及其退回不同步子订单。已合并 dev-v3(PR #8650,merge commit 54e64c50f),2026-09-30 部署 TEST(dev-v3 dd0452916,迁移 20260929.8516)并按 AC-05~AC-12 实测。管理后台读侧与既有写入口的变化见同日 30_8516 修改接口那份。"
updated_at: "2026-09-30"
base: "dev-v3"
---
# 团期:新增核单 / 结算状态两个内部回写接口(财务调用)
> **服务**: hl-order-service-v3(端口 8086 / 8186,双实例)
> **PR**: #8650(merge commit `54e64c50f`)
> **Issue**: #8516
> **日期**: 2026-09-30
> **影响范围**: 财务侧服务间调用,回写团期整团核单 / 结算状态;管理后台读侧见同日 `30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md`
---
## ⚠️ 关键变化
- 团期新增两个独立状态:核单 `reviewStatus`(与子订单同名同值)、结算 `settlementStatus`(比子订单多一个「结算中」),初始都是 `NONE`。
- 团期主状态里「待核单 / 核单中 / 已结算」三态**不再手动推进**,一律由这两个状态推导(推导表见六.5)。
- 财务通过本文两个接口回写这两个状态。系统唯一的自动写入是出行完毕定时任务(1042):团期出行结束时把核单置为「待核单」。
- 子订单跟着同步状态,**任何一步都不推报账单**;团期「已核单」及从已核单退回,**不同步**子订单。
---
## 一、背景
09-29 复盘团期出行完毕后的整条线:只走团级核单链路(一团一张报账单)时,子订单永远停在「待结算」、团期停在「核单中」,看板「结算」节点对这类团恒为空;代码里也没有「结算中」。jw 定口径:
1. 出行结束后团期自动进入「待核单」;
2. 核单中、已核单、待结算、结算中、已结算都由财务从外部更新;
3. 团期上加核单、结算两个独立状态,写法参照配房、配车;
4. 子订单跟着团期同步状态。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 改团期核单状态 | POST | `/v3/internal/group-batch/:groupBatchId/review-status` | 新增 | 财务回写整团核单状态(待核单 / 核单中 / 已核单),主状态随之推导,按规则同步子订单 |
| 2 | 改团期结算状态 | POST | `/v3/internal/group-batch/:groupBatchId/settlement-status` | 新增 | 财务回写整团结算状态(待结算 / 结算中 / 已结算),前提是核单已完成,按规则同步子订单与核团 |
---
## 三、接口详情
### 1. 改团期核单状态 `POST /v3/internal/group-batch/:groupBatchId/review-status`
**VO**: `GroupBatchReviewSettleStatusRespVO`(入参 `GroupBatchReviewSettleStatusReqVO`,两个接口共用)
#### 使用场景
财务开始核单、完成核单、或发现问题要退回核单时,回写团期整团核单状态。服务间调用:直连 order-v3 实例(8086 / 8186),请求头带 `X-Internal-Token`(经 Feign 调用时由内部令牌拦截器自动加头)。hl-finance 与订单服务同进程,也可以不走 HTTP,直接注入 `GroupBatchReviewSettleService#changeReviewStatus` 调用,规则完全相同。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| X-Internal-Token | Header | String | 是 | 服务间内部令牌 | 缺失或错误返回 HTTP 403 |
| groupBatchId | Path | Long | 是 | 团期 ID | 团期不存在返回 589500 |
| status | Body | String | 是 | 只能是 `PENDING` / `IN_PROGRESS` / `COMPLETED`,区分大小写 | 目标核单状态:待核单 / 核单中 / 已核单。`NONE`、空串、纯空白、null、小写、其他值一律 code 400,零写入 |
| operatorName | Body | String | 否 | 最长 64 字符 | 财务侧操作人姓名,写进团期时间线的操作人与子订单日志;超长返回 code 400 |
| reason | Body | String | 否 | 最长 500 字符 | 理由,写进团期时间线「原因」与子订单日志;超长返回 code 400 |
#### 出参 `Result<GroupBatchReviewSettleStatusRespVO>`
状态字段一律是**写后**的当前值。
| 字段 | 类型 | 说明 |
|------|------|------|
| groupBatchId | String | 团期 ID(按字符串输出) |
| batchStatus | String | 团期主状态(由两个状态推导):`PENDING_REVIEW` 待核单 / `REVIEWING` 核单中 / `SETTLED` 已结算 |
| batchStatusName | String | 主状态中文名 |
| reviewStatus | String | 核单状态:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` |
| reviewStatusName | String | 未核单 / 待核单 / 核单中 / 已核单 |
| settlementStatus | String | 结算状态:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` |
| settlementStatusName | String | 未结算 / 待结算 / 结算中 / 已结算 |
| changed | Boolean | `true` 本次改了库;`false` 目标值与当前值相同(幂等命中),零写入、不留痕、不同步子订单 |
| subOrderSyncedCount | Integer | 本次**真正改了状态**的子订单户数。本次变化不触发子订单同步时为 `null`;触发了但各户都已在目标状态时为 `0` |
| subOrderSkippedOrderIds | List\<String\> | 不在可同步状态、被跳过的子订单 ID(按字符串输出),需财务跟进。不触发同步时为 `null`;触发了但无人被跳过时为 `[]`。已经处于目标状态的户**不**列入,也不计入 subOrderSyncedCount |
#### 请求示例
```http
POST /v3/internal/group-batch/2105223439872933889/review-status HTTP/1.1
Host: 192.168.100.236:8086
X-Internal-Token: <内部令牌>
Content-Type: application/json
{
"status": "IN_PROGRESS",
"operatorName": "财务-王丽华",
"reason": "呼伦贝尔草原3日游·9月25日团开始核单"
}
```
#### 响应示例
团期 T26-2325 从待核单改为核单中:出行过的王海峰户同步进核单中;赵淑芬户出团时仍在定制中、没有随团进入待核单,本次被跳过(TEST 2026-09-30 17:34 实测):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223439872933889",
"batchStatus": "REVIEWING",
"batchStatusName": "核单中",
"reviewStatus": "IN_PROGRESS",
"reviewStatusName": "核单中",
"settlementStatus": "NONE",
"settlementStatusName": "未结算",
"changed": true,
"subOrderSyncedCount": 1,
"subOrderSkippedOrderIds": ["2105223440825020417"]
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
本接口没有列表型空数据。以下两种是「成功但没有同步动作」:
1. 同值幂等:目标值与当前值相同,`changed=false`,两个同步字段为 `null`,零写入(模拟财务重试,17:33 实测):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223438312652802",
"batchStatus": "PENDING_REVIEW",
"batchStatusName": "待核单",
"reviewStatus": "PENDING",
"reviewStatusName": "待核单",
"settlementStatus": "NONE",
"settlementStatusName": "未结算",
"changed": false,
"subOrderSyncedCount": null,
"subOrderSkippedOrderIds": null
},
"traceId": null,
"success": true
}
```
2. 改为已核单、或从已核单退回:只改团期,不同步子订单,`changed=true`,两个同步字段为 `null`(团期 T26-2325 从待核单直接改为已核单,17:46 实测):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223439872933889",
"batchStatus": "REVIEWING",
"batchStatusName": "核单中",
"reviewStatus": "COMPLETED",
"reviewStatusName": "已核单",
"settlementStatus": "NONE",
"settlementStatusName": "未结算",
"changed": true,
"subOrderSyncedCount": null,
"subOrderSkippedOrderIds": null
},
"traceId": null,
"success": true
}
```
#### 错误响应
缺少或错误的内部令牌(HTTP 403,body 字段名是 `msg`,与 `vehicle-ready` 等内部接口同形):
```json
{ "code": 403, "msg": "内部接口禁止外部访问" }
```
status 非法(HTTP 200,code 400,零写入):
```json
{
"code": 400,
"message": "status 取值只能是 PENDING、IN_PROGRESS、COMPLETED",
"data": null,
"traceId": null,
"success": false
}
```
结算已是已结算时退回核单(零写入):
```json
{
"code": 589702,
"message": "结算已完成,请先把结算状态改回待结算或结算中,再退回核单",
"data": null,
"traceId": null,
"success": false
}
```
| code | 触发条件 | 调用方下一步 |
|------|----------|--------------|
| HTTP 403 | 缺少或错误的 `X-Internal-Token` | 检查令牌配置 |
| 400 | status 缺失 / 空串 / 纯空白 / null(`status 不能为空`);取值不是三者之一(`status 取值只能是 PENDING、IN_PROGRESS、COMPLETED`,含 `NONE`、小写、`TRIP_FINISHED`);operatorName 超 64(`operatorName 不能超过 64 个字符`);reason 超 500(`reason 不能超过 500 个字符`)。多条同时违反时 message 以 `; ` 连接 | 修正入参 |
| 589500 | 团期不存在(`团期不存在`) | 核对团期 ID |
| 589700 | 团期还没出行完毕(招募中 ~ 出行中)或已流团:`团期当前状态为「招募中」,不可修改核单或结算状态(须已出行完毕且未流团)`,「」内是当前主状态中文名 | 不要调 |
| 589702 | 从已核单退回(改为待核单或核单中),而结算已是已结算 | 先调接口 2 把结算改回待结算或结算中 |
| 589703 | 读到写之间,团期主状态 / 核单 / 结算任一被并发改动(`团期核单或结算状态已被他人修改,请刷新后重试`),零写入 | 重新读取后决定是否重调 |
#### 业务边界
- **鉴权**:只认 `X-Internal-Token`,不经网关、不走管理员登录与团期权限码;本接口不另做判权。
- **阶段门**:团期主状态必须是待核单 / 核单中 / 已结算之一(已出行完毕且未流团),否则 589700。
- **幂等**:目标值等于当前值 → `changed=false`,不写库、不写时间线、不同步子订单。
- **从已核单退回**(改为核单中或待核单):结算未到已结算时,结算**自动置回 `NONE`**,时间线写明「结算状态随之置回」;结算已是已结算时拒绝 589702。
- **允许从待核单直接改为已核单**(跳过核单中):此时子订单停在待核单;之后结算改为已结算时,这些户不会被带上,出现在 `subOrderSkippedOrderIds` 里。由财务控制,系统不拦(jw 09-30 定)。
- **子订单同步**(与团期改动同一事务,全有全无;只改状态,不推报账单):
| 本次核单变化 | 哪些子订单被改 | 子订单核单 / 结算 / 流程状态改成 |
|---|---|---|
| 待核单 → 核单中 | 核单状态为待核单的户 | 核单中 / 未结算 / 核单中(`IN_PROGRESS` / `NONE` / `REVIEWING`) |
| 核单中 → 待核单 | 核单状态为核单中的户 | 待核单 / 未结算 / 待核单(`PENDING` / `NONE` / `PENDING_REVIEW`) |
| → 已核单 | **不同步**(子订单的已核单只靠逐户提交核单) | — |
| 已核单 → 核单中 / 待核单 | **不同步**,只改团期(已逐户提交的户保持已核单,某户要改走逐户反确认) | — |
- 范围是本团未取消的子订单;已在目标状态的户不改、不计数、不列入跳过名单;其余不在可同步状态的户列入 `subOrderSkippedOrderIds`。
- **留痕**:核单真的变化时写一条团期时间线「核单状态变更」(带操作人与原因);主状态随之变化时另有一条主状态流转行。每个被同步的子订单写一条订单日志,带团期 ID 与来源。
- **不带「期望的当前状态」**:以调用时库里的当前值为准。由财务保证不重试、不乱序(见四)。
### 2. 改团期结算状态 `POST /v3/internal/group-batch/:groupBatchId/settlement-status`
**VO**: `GroupBatchReviewSettleStatusRespVO`(入参 `GroupBatchReviewSettleStatusReqVO`,两个接口共用)
#### 使用场景
财务在团级报账单推出后把团期结算改为待结算、付款开始后改为结算中、付清后改为已结算;出纳冲正、付款失败等需要回退时,把结算从已结算改回待结算或结算中。调用方式同接口 1;同进程也可直接调用 `GroupBatchReviewSettleService#changeSettlementStatus`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| X-Internal-Token | Header | String | 是 | 服务间内部令牌 | 缺失或错误返回 HTTP 403 |
| groupBatchId | Path | Long | 是 | 团期 ID | 团期不存在返回 589500 |
| status | Body | String | 是 | 只能是 `PENDING` / `IN_PROGRESS` / `COMPLETED`,区分大小写 | 目标结算状态:待结算 / 结算中 / 已结算。`NONE`、空串、纯空白、null、小写、其他值一律 code 400,零写入 |
| operatorName | Body | String | 否 | 最长 64 字符 | 财务侧操作人姓名,写进团期时间线与子订单日志;超长返回 code 400 |
| reason | Body | String | 否 | 最长 500 字符 | 理由,写进团期时间线与子订单日志;超长返回 code 400 |
#### 出参 `Result<GroupBatchReviewSettleStatusRespVO>`
与接口 1 同一个 VO,状态字段一律是写后的当前值。
| 字段 | 类型 | 说明 |
|------|------|------|
| groupBatchId | String | 团期 ID(按字符串输出) |
| batchStatus | String | 团期主状态:结算为已结算时 `SETTLED`,否则 `REVIEWING` |
| batchStatusName | String | 主状态中文名 |
| reviewStatus | String | 核单状态(本接口不改它,恒为 `COMPLETED`) |
| reviewStatusName | String | 已核单 |
| settlementStatus | String | 结算状态:`PENDING` / `IN_PROGRESS` / `COMPLETED`(幂等命中时为当前值) |
| settlementStatusName | String | 待结算 / 结算中 / 已结算 |
| changed | Boolean | `true` 本次改了库;`false` 幂等命中,零写入 |
| subOrderSyncedCount | Integer | 同接口 1:只数本次真正改了状态的户;不触发同步时为 `null` |
| subOrderSkippedOrderIds | List\<String\> | 同接口 1:不触发同步时 `null`,触发了但无人被跳过时 `[]` |
#### 请求示例
```http
POST /v3/internal/group-batch/2105223438312652802/settlement-status HTTP/1.1
Host: 192.168.100.236:8086
X-Internal-Token: <内部令牌>
Content-Type: application/json
{
"status": "COMPLETED",
"operatorName": "财务-王丽华",
"reason": "报账款已付清,整团结算完成"
}
```
#### 响应示例
团期 T26-5936 结算改为已结算:已逐户提交核单、处于待结算的李秀英户同步为已结算;张建国户逐户核单未提交,被跳过(TEST 18:11 实测,全程报账单行数不变):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223438312652802",
"batchStatus": "SETTLED",
"batchStatusName": "已结算",
"reviewStatus": "COMPLETED",
"reviewStatusName": "已核单",
"settlementStatus": "COMPLETED",
"settlementStatusName": "已结算",
"changed": true,
"subOrderSyncedCount": 1,
"subOrderSkippedOrderIds": ["2105223438178435074"]
},
"traceId": null,
"success": true
}
```
结算从已结算改回结算中:两户都退回待结算,同时核团从「已结算」退回「已核算」(18:21 实测):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223438312652802",
"batchStatus": "REVIEWING",
"batchStatusName": "核单中",
"reviewStatus": "COMPLETED",
"reviewStatusName": "已核单",
"settlementStatus": "IN_PROGRESS",
"settlementStatusName": "结算中",
"changed": true,
"subOrderSyncedCount": 2,
"subOrderSkippedOrderIds": []
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
本接口没有列表型空数据。结算在未到已结算的范围内变化(未结算 → 待结算、待结算 → 结算中等)不同步子订单,两个同步字段为 `null`(17:41 实测):
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2105223438312652802",
"batchStatus": "REVIEWING",
"batchStatusName": "核单中",
"reviewStatus": "COMPLETED",
"reviewStatusName": "已核单",
"settlementStatus": "PENDING",
"settlementStatusName": "待结算",
"changed": true,
"subOrderSyncedCount": null,
"subOrderSkippedOrderIds": null
},
"traceId": null,
"success": true
}
```
同值重复回写时 `changed=false`,其余字段为当前值,零写入。
#### 错误响应
核单还没完成就改结算(零写入):
```json
{
"code": 589701,
"message": "核单尚未完成(当前「待核单」),不可修改结算状态,请先将核单状态改为「已核单」",
"data": null,
"traceId": null,
"success": false
}
```
已流团的团期(零写入):
```json
{
"code": 589700,
"message": "团期当前状态为「已取消」,不可修改核单或结算状态(须已出行完毕且未流团)",
"data": null,
"traceId": null,
"success": false
}
```
| code | 触发条件 | 调用方下一步 |
|------|----------|--------------|
| HTTP 403 | 缺少或错误的 `X-Internal-Token`(body `{"code":403,"msg":"内部接口禁止外部访问"}`) | 检查令牌配置 |
| 400 | 入参校验失败,规则与文案同接口 1 | 修正入参 |
| 589500 | 团期不存在 | 核对团期 ID |
| 589700 | 团期还没出行完毕或已流团 | 不要调 |
| 589701 | 核单状态不是已核单,「」内是当前核单状态中文名 | 先调接口 1 把核单改为已核单 |
| 589703 | 团期三个状态被并发改动,零写入 | 重新读取后决定是否重调 |
| 589573 | 结算离开已结算、退回核团时核团被并发修改(`整团核单数据已被他人修改…请刷新后重试`),整体回滚 | 重新读取后决定是否重调 |
#### 业务边界
- **鉴权、阶段门、幂等**:同接口 1。
- **前提**:核单状态必须是已核单,否则 589701。
- **主状态**:结算为已结算 → `SETTLED`;其余 → `REVIEWING`。
- **子订单同步**(同一事务,只改状态,不推报账单,已推的报账单也不撤):
| 本次结算变化 | 哪些子订单被改 | 子订单核单 / 结算 / 流程状态改成 |
|---|---|---|
| → 待结算 / 结算中(未离开已结算) | 不同步(子订单没有「结算中」,保持待结算) | — |
| → 已结算 | 结算状态为待结算(已逐户提交核单)的户 | 已核单 / 已结算 / 已结算(`COMPLETED` / `COMPLETED` / `SETTLED`),写结算时间 |
| 已结算 → 待结算 / 结算中 | 结算状态为已结算的户 | 已核单 / 待结算 / 待结算(`COMPLETED` / `PENDING` / `PENDING_SETTLE`),清结算时间 |
- **未提交户不拦**:改为已结算时,逐户核单没提交的户**不会**让本接口失败,而是被跳过、列入 `subOrderSkippedOrderIds`,之后一直留在已结算的团里。改为已结算前,请财务确认各户都已逐户提交核单(与管理后台 `/settle` 不同,那个入口遇未提交户整团拒绝 589568)。
- **已结算不要求报账单已推出**:由财务确认一团一张的报账单推出后再改为已结算(jw 09-29 定)。
- **结算离开已结算**(本接口与管理后台 `/settle/reopen` 同一处理):核团同事务从「已结算」(CHECKED)退回「已核算」(ALLOCATED),清空验团人、验团时间与意见;之后重新核算、再结算、再反结算都可正常使用。
- 「结算中」的业务含义由财务定义,接口只存值。
- **留痕**:结算真的变化时写一条团期时间线「结算状态变更」;主状态进出已结算时另有一条「结算」主状态流转行;核团被退回时时间线附 `auditReverted=true`(只在时间线里,不在接口响应里)。
---
## 四、契约约束与正确调用方式
### ✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload |
|------|---------|
| ✅ 开始核单 | `{ "status": "IN_PROGRESS", "operatorName": "财务-王丽华", "reason": "开始核单" }` |
| ✅ 只传状态 | `{ "status": "COMPLETED" }`(operatorName、reason 选填) |
| ❌ 想把状态清回初始值 | `{ "status": "NONE" }` → 400(`NONE` 不允许外部写入) |
| ❌ 小写或旧值 | `{ "status": "pending" }`、`{ "status": "TRIP_FINISHED" }` → 400 |
| ❌ 核单没到已核单就改结算 | 核单为待核单 / 核单中时调接口 2 → 589701 |
| ❌ 已结算时退回核单 | 结算为已结算时调接口 1 改为核单中 / 待核单 → 589702 |
### 正常调用顺序
```text
(1042 自动)核单=待核单
→ 接口 1 IN_PROGRESS(核单中)→ 接口 1 COMPLETED(已核单)
→ 接口 2 PENDING(待结算)→ 接口 2 IN_PROGRESS(结算中)→ 接口 2 COMPLETED(已结算)
```
### 对接注意事项(jw 09-29 / 09-30 已定,均由财务侧流程控制,系统不加门禁)
1. **核单可以从待核单直接跳到已核单**:此时子订单停在待核单,之后结算改为已结算的同步不会带上它们(出现在跳过名单里)。
2. **接口不带「期望的当前状态」**:由财务保证不重试、不乱序。迟到的重试会把状态改回去,并连带子订单、核团一起回退。
3. **团期已核单不代表每户都已核单**:团期改为已核单时不检查各户是否已逐户提交;财务确认各户都已提交后,再把结算改为已结算。
4. **结算改为已结算不要求报账单已推出**:可能出现没有报账单的已结算团,由财务把关。
5. **团期子订单不要做逐户「财务复核确认」**:逐户确认会推 ORDER 报账单,可能与团级 GROUP_BATCH 报账单重复;系统既不拦截,也不跳过推单。
6. **核单变为已核单后不冻结**团级共享成本录入和团级定稿。
7. 返回 `subOrderSkippedOrderIds` 非空时,名单里的户状态没有跟上团期,需要财务跟进。
---
## 五、数据库行为
- 团期主状态、核单状态、结算状态三者在**同一次原子更新**里写入,条件是三者都等于读到的旧值;未命中返回 589703,本次零写入。
- 子订单状态同步与团期改动**同一事务**,任一户写失败整体回滚。每个被改的户写一条订单日志:核单两步记「流程推进」,结算两步记「结算确认」/「核单反确认」,内容写明「团期同步、不推报账单」,日志附带团期 ID、来源 `GROUP_BATCH_REVIEW_SETTLEMENT_SYNC`、步骤、操作人与理由。
- 不生成、不撤回任何报账单(TEST 全程 88 次快照报账单行数恒定)。
- 结算离开已结算时,核团同事务退回已核算,并清空验团人、验团时间与意见。
- 团期时间线写入失败只记告警、不回滚(非主链路);子订单订单日志不降级。
- 幂等命中(`changed=false`)与所有错误返回:零写入。
---
## 六、边界行为
- 缺少或错误的内部令牌 → HTTP 403 `{"code":403,"msg":"内部接口禁止外部访问"}`,两个实例表现一致。
- 经公网网关访问 `/v3/internal/**` → 网关直接拒绝(`code 403`「接口不可访问」),请求到不了服务。
- 入参非法 → HTTP 200 + code 400,零写入。
- 团期不存在 → 589500;未出行完毕或已流团 → 589700。
- 两次调用并发打到同一团期:先拿到团期行锁的先执行,后到者读到的是先到者已提交的值,同值时走幂等分支;兜底冲突返回 589703。
- 在团子订单为空(团内户全部取消)时同步不报错,`subOrderSyncedCount=0`、`subOrderSkippedOrderIds=[]`。
- 团期「已核单」、从「已核单」退回:永远不同步子订单(`subOrderSyncedCount` / `subOrderSkippedOrderIds` 为 `null`)。
## 六.5、枚举 / 数据字典
### reviewStatus(`com.hulalv.order.settlement.enums.ReviewStatus`,与子订单核单状态同一枚举)
**所属字段**: `GroupBatchReviewSettleStatusRespVO.reviewStatus` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `NONE` | 未核单 | 初始值,还没出行完毕;外部不可写入 |
| `PENDING` | 待核单 | 出行完毕(1042 自动写入)或财务退回 |
| `IN_PROGRESS` | 核单中 | 财务开始核单;管理后台「发起核单」、首笔共享成本也会写入 |
| `COMPLETED` | 已核单 | 财务完成核单;管理后台 `/settle` 也会写入 |
### settlementStatus(`com.hulalv.order.groupbatch.enums.GroupBatchSettlementStatus`)
**所属字段**: `GroupBatchReviewSettleStatusRespVO.settlementStatus` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `NONE` | 未结算 | 核单完成之前恒为此值;外部不可写入;核单从已核单退回时自动置回 |
| `PENDING` | 待结算 | 核单已完成、尚未开始结算。注意同一个值在子订单上叫「待财务复核」 |
| `IN_PROGRESS` | 结算中 | 团期独有,子订单没有这一态;含义由财务定义 |
| `COMPLETED` | 已结算 | 团期主状态随之变为已结算 |
### batchStatus 推导表(`com.hulalv.order.groupbatch.enums.GroupBatchStatus`)
**所属字段**: `GroupBatchReviewSettleStatusRespVO.batchStatus` | **类型**: `String`
| 核单 | 结算 | → 主状态 |
|------|------|----------|
| `NONE` | `NONE` | 不适用(还没出行完毕,接口按 589700 拒绝,不改主状态) |
| `PENDING` | `NONE` | `PENDING_REVIEW` 待核单 |
| `IN_PROGRESS` | `NONE` | `REVIEWING` 核单中 |
| `COMPLETED` | `NONE` / `PENDING` / `IN_PROGRESS` | `REVIEWING` 核单中 |
| `COMPLETED` | `COMPLETED` | `SETTLED` 已结算 |
`PENDING_REVIEW`「待核单」即原 `TRIP_FINISHED`「出行完毕」,#8516 改名,存量数据已随迁移改写。
---
## 七、不影响范围
- **仅影响**:新增的两个内部接口;团期核单 / 结算状态的写入统一收进同一个写口。
- **零影响**:
- 团级核单链路(核单定稿 finalize / 确认 confirm)与回款监听不写这两个状态,口径不变;
- 逐户确认 `/{orderId}/settlement/confirm`、逐户反确认 `/{orderId}/settlement/final-snapshots/reopen` 对团期子订单不加限制;
- 子订单第一次录核单明细自动进入核单中的逐户逻辑保留;
- 出行完毕定时任务的内部触发接口 `POST /v3/internal/jobs/group-batch-trip-finish/run`:请求、响应(本次推进的团期数)不变;推进时顺带把团期核单置为待核单,出行中的子订单随团进入待核单(#8340 既有逻辑),当时不在出行中的户跳过并打告警,团期照常推进。
- 管理后台读接口(新增四个字段、`TRIP_FINISHED` 改名、进度条分支、入参旧值兼容)与既有写入口的行为变化,见同日修改接口那份。
---
## 八、测试环境已验证
2026-09-30 TEST:合并提交 `54e64c50f`,部署构建 dev-v3 `dd0452916`(order-v3 双实例 8086 / 8186,16:32 启动,运行字节经探针核对),迁移 `20260929.8516` 于 16:32:13 执行成功。验收团期为本次新建(T26-5936 呼伦贝尔草原3日游·9月26日团、T26-2325 呼伦贝尔草原3日游·9月25日团、T26-8714 流团用),内部接口用 `X-Internal-Token` 直连两个实例。证据目录 `HL/.evidence/8516/`。
```text
AC-04 POST /v3/internal/jobs/group-batch-trip-finish/run → data=2;两团 TRAVELLING→PENDING_REVIEW、核单=待核单;出行中的户同事务进待核单,定制中的户跳过并告警 ✓
AC-05 两接口 × 两实例:无令牌 / 伪造令牌 → HTTP 403;status 缺失/空串/空白/null/NONE/DONE/小写/TRIP_FINISHED → code 400,前后零写入 ✓
AC-06 推导表七行逐组合实测:NONE/NONE 不适用(589700)、PENDING/NONE→PENDING_REVIEW、IN_PROGRESS/NONE 与 COMPLETED/NONE/PENDING/IN_PROGRESS→REVIEWING、COMPLETED/COMPLETED→SETTLED ✓
AC-07 同值幂等 changed=false 零写入;589700(招募中/出行中/已流团)、589701、589702 各自触发;589500 团期不存在;核单退回时结算自动置回 NONE;结算离开已结算两条路径核团 CHECKED→ALLOCATED,之后重新核算、再结算、再反结算均成功 ✓
AC-08 子订单同步逐行正反向实测:跳过户进 subOrderSkippedOrderIds(无同步 null、有同步无跳过 []);已核单退回核单中 / 待核单时子订单未被带动;全程 88 次快照报账单行数不变 ✓
AC-12 时间线可见「核单状态变更」「结算状态变更」,operatorName=财务-王丽华,reason 为调用方传入原因 ✓
```
| AC | 证据 |
|----|------|
| AC-04 | `HL/.evidence/8516/AC-04/` |
| AC-05 | `HL/.evidence/8516/AC-05/01-auth-and-validation.json`、`02-controls-vehicle-ready-and-gateway.json` |
| AC-06 | `HL/.evidence/8516/AC-06/` |
| AC-07 | `HL/.evidence/8516/AC-07/` |
| AC-08 | `HL/.evidence/8516/AC-08/` |
| AC-12 | `HL/.evidence/8516/AC-12/01-status-logs.json` |
单元测试:推导全组合 `GroupBatchReviewSettleDeriverTest`、写入规则 `GroupBatchReviewSettleServiceTest`、内部接口切片与鉴权 `GroupBatchReviewSettleInternalControllerTest` / `GroupBatchReviewSettleInternalAuthTest`、子订单同步正反向与报账单行数守卫 `GroupBatchReviewSettleH2IT`;order-v3 全量两半对照干净 dev-v3 基线,本单新增失败 0。
---
## 九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| — | #7190 | 团期新增出行完毕 `TRIP_FINISHED` | ⚠️ 状态值已改名 `PENDING_REVIEW` 待核单 |
| — | #8340 | 团期出发 / 出行完毕时子订单随团推进 | ✅ 有效(出行完毕时另写核单=待核单) |
| — | #8341 | 团期核单 / 结算 / 反结算同步子订单,团期结算即整团财务复核并逐户推报账单 | ⚠️ 部分被本单取代:同步改按本单第 6 节,结算不再逐户推报账单 |
| — | #8361 / #8363 | 报账只认一团一张、成本只认团级快照(09-28 口径) | ✅ 有效,本单据此停掉逐户推单 |
| **#8650** | **#8516** | 团期核单 / 结算独立状态、财务内部回写接口 | ✅ 最新 |
---
## 十、相关文档
- 关联 Issue: [wx/HL#8516](https://git.1814.love/wx/HL/issues/8516)
- 关联 PR: [wx/HL#8650](https://git.1814.love/wx/HL/pulls/8650)
- 状态机文档:`docs/group/实施单/16-团期生命周期与状态机.html`(随 PR #8650 同步,提交 `30ed15777`);接口文档 `docs/group/团期模块接口文档-v2.0.html`
- 同日管理后台读侧:`changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md`
## 关联 / 联系人
### 链接
- **Issue**: [#8516](https://git.1814.love/wx/HL/issues/8516)
- **PR**: [#8650](https://git.1814.love/wx/HL/pulls/8650)
- **Merge commit**: [54e64c50f](https://git.1814.love/wx/HL/commit/54e64c50f)
### 联系人
- **后端负责人**: @jw