From 5dc79ef2e6c11125f148bc844ae2a11f0d0d4650 Mon Sep 17 00:00:00 2001 From: jw Date: Wed, 30 Sep 2026 22:38:06 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#8516=20=E5=9B=A2=E6=9C=9F?= =?UTF-8?q?=E6=A0=B8=E5=8D=95=E3=80=81=E7=BB=93=E7=AE=97=E6=8B=86=E6=88=90?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E7=8B=AC=E7=AB=8B=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E5=87=BA=E8=A1=8C=E5=AE=8C=E6=AF=95=E6=94=B9=E5=90=8D=E5=BE=85?= =?UTF-8?q?=E6=A0=B8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两份: - 新增接口(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 --- ...—状态字段且出行完毕改名待核单-修改接口-管理后台.md | 1060 +++++++++++++++++ ...¸Ž结算状态财务内部回写接口-新增接口-管理后台.md | 590 +++++++++ 2 files changed, 1650 insertions(+) create mode 100644 changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md create mode 100644 changelogs-v2/2026-09/30_8516_团期核单与结算状态财务内部回写接口-新增接口-管理后台.md diff --git a/changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md b/changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md new file mode 100644 index 00000000..d8cc5a59 --- /dev/null +++ b/changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md @@ -0,0 +1,1060 @@ +--- +schema: "hl-changelog/v2" +ticket: "8516" +title: "团期状态「出行完毕」TRIP_FINISHED 改名「待核单」PENDING_REVIEW(破坏性,前端须同批发布);团期详情 / 分页新增整团核单、结算状态四个字段;进度条核单 / 结算节点新增分支;三处入参旧值兼容" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "① 团期主状态取值 TRIP_FINISHED「出行完毕」改为 PENDING_REVIEW「待核单」,存量数据与时间线历史行已随迁移改写,所有返回团期主状态的接口只输出新值;出行子状态 tripSubStatus 同步由 TRIP_FINISHED「已返团」改为 PENDING_REVIEW「待核单」。② A2 团期详情、A1 团期分页新增 reviewStatus / reviewStatusName / settlementStatus / settlementStatusName。③ 详情进度条 REVIEW、SETTLE 节点各新增一条分支,文案如「核单·已核单」「结算·结算中」,字段为 NONE 时「待开始」;当前节点仍按主状态分桶。④ 团期分页 batchStatus、房务配房列表(含 scope=mine)batchStatus、房务团期看板 batchStatus 传旧值 TRIP_FINISHED 时按 PENDING_REVIEW 处理;opsStage=TRIP_FINISHED 旧桶名保留。⑤ 团期时间线新增「核单状态变更」「结算状态变更」两类事件。⑥ 发起核单、首笔共享成本、/settle、/settle/reopen 四个既有写入口请求与响应不变,/settle 不再逐户推 ORDER 报账单。hl-ui 必须同批改:按 TRIP_FINISHED 判断的 7 处与显示映射 3 处,不改则「发起核单」按钮消失、财务面板「完成核单」点不了。TEST 已于 2026-09-30 部署(dev-v3 dd0452916,合并提交 54e64c50f)并实测;jw 09-30 定 TEST 先行,正式环境前后端同批发布,故 frontend_status 记 pending。财务回写用的两个内部接口见同日 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 +> **影响范围**: 团期详情 / 分页 / 进度条 / 时间线的核单、结算展示;团期状态筛选入参;所有按团期状态值做判断的前端代码 + +--- + +## ⚠️ 关键变化 + +**破坏性变更,前端必须与后端同批发布。** + +| 变了什么 | 以前 | 现在 | +|---|---|---| +| 团期主状态 `batchStatus` 的一个取值 | `TRIP_FINISHED`「出行完毕」 | `PENDING_REVIEW`「待核单」。存量团期、时间线历史行已统一改写,**任何接口的返回值里都不再出现 `TRIP_FINISHED`** | +| 出行子状态 `tripSubStatus` 的一个取值 | `TRIP_FINISHED`「已返团」 | `PENDING_REVIEW`「待核单」;进度条出行节点 label 同步变为「待核单」 | +| 团期详情、分页 | 没有整团核单 / 结算状态 | 新增 `reviewStatus` / `reviewStatusName` / `settlementStatus` / `settlementStatusName` | +| 详情进度条核单、结算节点 | `subFlows=null` | 各带一条分支,如「核单·已核单」「结算·结算中」 | +| `/settle` 整团结算 | 逐户做财务复核并推 ORDER 报账单(#8341) | 只把子订单状态置为已结算,**不推报账单**;请求 / 响应不变 | + +前端代码里凡是写死 `'TRIP_FINISHED'` 当状态值判断的地方,不改就会静默失效——最明显的是团期详情「发起核单」按钮会消失、财务面板「完成核单」点不了(清单见六.7)。 + +--- + +## 一、背景 + +jw 09-29 定口径:团期上加核单、结算两个独立状态(写法参照配房、配车),出行结束后团期自动进入「待核单」,此后核单中、已核单、待结算、结算中、已结算都由财务从外部更新,团期主状态由这两个状态推导,子订单跟着同步。 + +「出行完毕」描述的是出行结束这件事,事件之后团期所处的状态是「待核单」,所以主状态改名;时间线里的事件「出行完毕」`BATCH_TRIP_FINISH` 不改名。财务回写这两个状态用的内部接口见同日 `30_8516_团期核单与结算状态财务内部回写接口-新增接口-管理后台.md`(前端无需对接)。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/:groupBatchId` | 修改接口 | 新增 4 个字段;`batchStatus` / `tripSubStatus` 取值改名;进度条核单、结算节点新增分支 | +| 2 | A1 团期分页 | GET | `/v3/admin/order/group-batch` | 修改接口 | 新增 4 个字段;取值改名;入参 `batchStatus` 旧值兼容;`opsStage=TRIP_FINISHED` 桶名保留 | +| 3 | 房务配房列表(团期) | GET | `/v3/admin/order/house-allocation/group-batches` | 修改接口 | 入参 `batchStatus` 旧值兼容(含 `scope=mine`,改前返回 400);`batchStatus` / `batchStatusLabel` 取值改名 | +| 4 | 房务团期看板列表 | GET | `/v3/admin/house/group-batches` | 修改接口 | 入参 `batchStatus` 旧值兼容(改前退回默认筛选);`batchStatus` / `batchStatusText` 取值改名 | +| 5 | 团期状态流水(时间线) | GET | `/v3/admin/order/group-batch/:groupBatchId/status-logs` | 修改接口 | 新增「核单状态变更」「结算状态变更」两类事件;`fromStatus` / `toStatus` 取值改名(含历史行) | + +--- + +## 三、接口详情 + +### 1. A2 团期详情 `GET /v3/admin/order/group-batch/:groupBatchId` + +**VO**: `GroupBatchDetailRespVO`(进度条节点 `GroupBatchProgressNodeVO`,分支 `GroupBatchProgressSubFlowVO`) + +#### 使用场景 + +团期详情页页头状态、进度条、核单 / 结算信息。本次新增整团核单 / 结算状态四个字段,进度条核单、结算节点新增分支,主状态与出行子状态的「出行完毕 / 已返团」改为「待核单」。其余出参不变。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 团期 ID | 不存在返回 589500;未变 | + +#### 出参 `Result` + +只列本次新增或取值有变化的字段。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| reviewStatus | String | **新增**。整团核单状态:`NONE` 未核单(还没出行完毕)/ `PENDING` 待核单 / `IN_PROGRESS` 核单中 / `COMPLETED` 已核单。与子订单核单状态同名同值。恒非 null | +| reviewStatusName | String | **新增**。未核单 / 待核单 / 核单中 / 已核单 | +| settlementStatus | String | **新增**。整团结算状态:`NONE` 未结算(核单还没完成)/ `PENDING` 待结算 / `IN_PROGRESS` 结算中 / `COMPLETED` 已结算。恒非 null。与财务 tab 逐户付款结清的 `settleStatus` 无关 | +| settlementStatusName | String | **新增**。未结算 / 待结算 / 结算中 / 已结算 | +| batchStatus | String | 取值 `TRIP_FINISHED` 改为 `PENDING_REVIEW`,其余八个取值不变 | +| batchStatusName | String | 「出行完毕」改为「待核单」 | +| tripSubStatus | String | 仅 `stage=TRIP` 时有值:`PENDING_DEPARTURE` 待出发 / `TRAVELLING` 出行中 / `PENDING_REVIEW` 待核单(原 `TRIP_FINISHED` 已返团) | +| tripSubStatusName | String | 「已返团」改为「待核单」 | +| progressStepper[].label | String | 出行节点为当前节点且团期待核单时,由「已返团」改为「待核单」 | +| progressStepper[].subFlows | List\ | **REVIEW、SETTLE 两个节点由 `null` 改为各 1 条分支**;CONFIGURE 仍是 5 条;RECRUIT / CONFIRM / TRIP 仍为 `null` | +| progressStepper[].subFlows[].code | String | 核单节点 `REVIEW`,结算节点 `SETTLE` | +| progressStepper[].subFlows[].name | String | 核单 / 结算 | +| progressStepper[].subFlows[].status | String | 直接取 `reviewStatus` / `settlementStatus` 的值:`PENDING` / `IN_PROGRESS` / `COMPLETED`;字段为 `NONE` 时为 `WAITING` | +| progressStepper[].subFlows[].statusName | String | 核单节点:待核单 / 核单中 / 已核单;结算节点:待结算 / 结算中 / 已结算;字段为 `NONE` 时「待开始」 | +| progressStepper[].subFlows[].displayText | String | `name·statusName`,如「核单·已核单」「结算·结算中」「结算·待开始」 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2105223438312652802 HTTP/1.1 +Host: api.test.1814.love +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +T26-5936(核单已完成、结算中):注意核单节点 label 仍是「核单中」(按主状态分桶),分支是「核单·已核单」(TEST 2026-09-30 实测,只列与本次相关的字段): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2105223438312652802", + "batchNo": "T26-5936", + "batchName": "呼伦贝尔草原3日游·9月26日团", + "batchStatus": "REVIEWING", + "batchStatusName": "核单中", + "reviewStatus": "COMPLETED", + "reviewStatusName": "已核单", + "settlementStatus": "IN_PROGRESS", + "settlementStatusName": "结算中", + "stage": "REVIEW", + "stageName": "核单", + "tripSubStatus": null, + "tripSubStatusName": null, + "progressStepper": [ + {"step": 1, "code": "RECRUIT", "name": "招募", "status": "DONE", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 2, "code": "CONFIGURE", "name": "配置", "status": "DONE", "label": null, "isCurrent": false, "subFlows": [ + {"code": "HOTEL", "name": "配房", "status": "UNMET", "statusName": "未配齐", "displayText": "配房·未配齐"}, + {"code": "VEHICLE", "name": "配车", "status": "UNMET", "statusName": "未配齐", "displayText": "配车·未配齐"}, + {"code": "GUIDE", "name": "配导游", "status": "WAIVED", "statusName": "无需", "displayText": "配导游·无需"}, + {"code": "PHOTOGRAPHER", "name": "配摄影", "status": "WAIVED", "statusName": "无需", "displayText": "配摄影·无需"}, + {"code": "MATERIAL", "name": "配物资", "status": "UNMET", "statusName": "未确认", "displayText": "配物资·未确认"} + ] }, + {"step": 3, "code": "CONFIRM", "name": "确认", "status": "DONE", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 4, "code": "TRIP", "name": "出行", "status": "DONE", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 5, "code": "REVIEW", "name": "核单", "status": "PROCESSING", "label": "核单中", "isCurrent": true, "subFlows": [ + {"code": "REVIEW", "name": "核单", "status": "COMPLETED", "statusName": "已核单", "displayText": "核单·已核单"} + ] }, + {"step": 6, "code": "SETTLE", "name": "结算", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "SETTLE", "name": "结算", "status": "IN_PROGRESS", "statusName": "结算中", "displayText": "结算·结算中"} + ] } + ] + }, + "traceId": null, + "success": true +} +``` + +T26-2325(待核单):主状态、出行子状态、出行节点 label 都是「待核单」,核单分支「核单·待核单」: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2105223439872933889", + "batchNo": "T26-2325", + "batchName": "呼伦贝尔草原3日游·9月25日团", + "batchStatus": "PENDING_REVIEW", + "batchStatusName": "待核单", + "reviewStatus": "PENDING", + "reviewStatusName": "待核单", + "settlementStatus": "NONE", + "settlementStatusName": "未结算", + "stage": "TRIP", + "stageName": "出行", + "tripSubStatus": "PENDING_REVIEW", + "tripSubStatusName": "待核单", + "progressStepper": [ + {"step": 1, "code": "RECRUIT", "name": "招募", "status": "DONE", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 2, "code": "CONFIGURE", "name": "配置", "status": "DONE", "label": null, "isCurrent": false, "subFlows": [ + {"code": "HOTEL", "name": "配房", "status": "UNMET", "statusName": "未配齐", "displayText": "配房·未配齐"}, + {"code": "VEHICLE", "name": "配车", "status": "WAIVED", "statusName": "整团免车", "displayText": "配车·整团免车"}, + {"code": "GUIDE", "name": "配导游", "status": "WAIVED", "statusName": "无需", "displayText": "配导游·无需"}, + {"code": "PHOTOGRAPHER", "name": "配摄影", "status": "WAIVED", "statusName": "无需", "displayText": "配摄影·无需"}, + {"code": "MATERIAL", "name": "配物资", "status": "UNMET", "statusName": "未确认", "displayText": "配物资·未确认"} + ] }, + {"step": 3, "code": "CONFIRM", "name": "确认", "status": "DONE", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 4, "code": "TRIP", "name": "出行", "status": "PROCESSING", "label": "待核单", "isCurrent": true, "subFlows": null }, + {"step": 5, "code": "REVIEW", "name": "核单", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "REVIEW", "name": "核单", "status": "PENDING", "statusName": "待核单", "displayText": "核单·待核单"} + ] }, + {"step": 6, "code": "SETTLE", "name": "结算", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "SETTLE", "name": "结算", "status": "WAITING", "statusName": "待开始", "displayText": "结算·待开始"} + ] } + ] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 出行完毕之前(招募中 ~ 出行中):`reviewStatus` / `settlementStatus` 恒为 `NONE`(不会是 null),核单、结算分支都是「待开始」。 +- 已流团、`batchStatus` 为 null 或不在九态内:`progressStepper` 仍为空列表 `[]`(与 #8478 相同,未改);四个新字段照常输出(已流团团期为 `NONE`)。 +- 库里是枚举外的脏值时:`reviewStatus` / `settlementStatus` 原样输出、中文名回原值;进度条分支按 `NONE` 处理(`WAITING`「待开始」),不报错。 + +T26-5936 招募中时(17:18 实测,只列相关字段): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2105223438312652802", + "batchStatus": "RECRUITING", + "batchStatusName": "招募中", + "reviewStatus": "NONE", + "reviewStatusName": "未核单", + "settlementStatus": "NONE", + "settlementStatusName": "未结算", + "progressStepper": [ + {"step": 1, "code": "RECRUIT", "name": "招募", "status": "PROCESSING", "label": "招募中", "isCurrent": true, "subFlows": null }, + {"step": 2, "code": "CONFIGURE", "name": "配置", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "HOTEL", "name": "配房", "status": "WAITING", "statusName": "待开始", "displayText": "配房·待开始"}, + {"code": "VEHICLE", "name": "配车", "status": "WAITING", "statusName": "待开始", "displayText": "配车·待开始"}, + {"code": "GUIDE", "name": "配导游", "status": "WAITING", "statusName": "待开始", "displayText": "配导游·待开始"}, + {"code": "PHOTOGRAPHER", "name": "配摄影", "status": "WAITING", "statusName": "待开始", "displayText": "配摄影·待开始"}, + {"code": "MATERIAL", "name": "配物资", "status": "WAITING", "statusName": "待开始", "displayText": "配物资·待开始"} + ] }, + {"step": 3, "code": "CONFIRM", "name": "确认", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 4, "code": "TRIP", "name": "出行", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": null }, + {"step": 5, "code": "REVIEW", "name": "核单", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "REVIEW", "name": "核单", "status": "WAITING", "statusName": "待开始", "displayText": "核单·待开始"} + ] }, + {"step": 6, "code": "SETTLE", "name": "结算", "status": "WAITING", "label": null, "isCurrent": false, "subFlows": [ + {"code": "SETTLE", "name": "结算", "status": "WAITING", "statusName": "待开始", "displayText": "结算·待开始"} + ] } + ] + }, + "traceId": null, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589500, + "message": "团期不存在", + "data": null, + "traceId": null, + "success": false +} +``` + +| 错误码 | 触发条件 | +|--------|----------| +| 589500 | 团期不存在(未变) | +| 589507 | 缺 `group-batch:view` 权限码(未变) | + +#### 业务边界 + +- 当前节点(`isCurrent` / 节点 `status` / `label`)**仍按主状态分桶**,不看这两个新字段;两个新字段只决定核单、结算分支的文案。 +- 因此「核单已完成、结算未完成」时:核单节点 label 仍是「核单中」,分支是「核单·已核单」;结算节点仍是 `WAITING`,分支显示「结算·待结算 / 结算中」。前端要表达「核单已完成」,请展示分支 `displayText`,不要只看节点 label。 +- 进度条只供展示;业务判断仍读 `batchStatus`。 +- TEST 实测到的分支组合: + +| 团期所处 | 出行节点 label | 核单节点 label | 核单分支 | 结算分支 | +|---|---|---|---|---| +| 招募中 / 出行中 | —(出行中时为「出行中」) | — | 核单·待开始 | 结算·待开始 | +| 待核单 | 待核单 | — | 核单·待核单 | 结算·待开始 | +| 核单中 | — | 核单中 | 核单·核单中 | 结算·待开始 | +| 已核单 + 待结算 | — | 核单中 | 核单·已核单 | 结算·待结算 | +| 已核单 + 结算中 | — | 核单中 | 核单·已核单 | 结算·结算中 | +| 已结算 | — | —(结算节点 DONE + 当前,label「已结算」) | 核单·已核单 | 结算·已结算 | + +### 2. A1 团期分页 `GET /v3/admin/order/group-batch` + +**VO**: `GroupBatchPageItemRespVO`(入参 `GroupBatchListReqVO`) + +#### 使用场景 + +团期列表页。本次每行新增整团核单 / 结算状态四个字段;`batchStatus` 筛选传旧值 `TRIP_FINISHED` 时按 `PENDING_REVIEW` 查询(改前静默返回空);看板桶筛选 `opsStage=TRIP_FINISHED` 照常可用。 + +#### 入参 + +只列本次有变化的入参,其余(productId、keyword、deadlineFrom / To、departFrom / To、month、scope、sortBy、sortOrder 等)不变。 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| batchStatus | Query | String | 否 | 九态之一 | 新值传 `PENDING_REVIEW`;旧值 `TRIP_FINISHED` 按 `PENDING_REVIEW` 处理(兼容用,新代码请传新值)。其他取值规则不变 | +| opsStage | Query | String | 否 | 七桶或旧八桶别名 | `TRIP` 桶展开为 待出发 + 出行中 + 待核单;旧桶名 `TRIP_FINISHED` **保留**,展开为 `PENDING_REVIEW`(它是桶名,不是状态值) | +| pageNo | Query | Integer | 否 | 默认 1 | 未变 | +| pageSize | Query | Integer | 否 | 默认 20 | 未变 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].reviewStatus | String | **新增**,同 A2:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` | +| records[].reviewStatusName | String | **新增**,未核单 / 待核单 / 核单中 / 已核单 | +| records[].settlementStatus | String | **新增**,同 A2:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` | +| records[].settlementStatusName | String | **新增**,未结算 / 待结算 / 结算中 / 已结算 | +| records[].batchStatus | String | 取值 `TRIP_FINISHED` 改为 `PENDING_REVIEW` | +| records[].batchStatusName | String | 「出行完毕」改为「待核单」 | +| records[].tripSubStatus | String | `TRIP_FINISHED` 改为 `PENDING_REVIEW` | +| records[].tripSubStatusName | String | 「已返团」改为「待核单」 | +| total / page / pageSize | Long / Integer / Integer | 未变 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch?batchStatus=TRIP_FINISHED&pageNo=1&pageSize=50 HTTP/1.1 +Host: api.test.1814.love +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +传旧值 `TRIP_FINISHED`,返回的是两个待核单团期,与传 `PENDING_REVIEW` 结果一致(TEST 实测,只列与本次相关的字段): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "groupBatchId": "2105223438312652802", + "batchNo": "T26-5936", + "batchName": "呼伦贝尔草原3日游·9月26日团", + "batchStatus": "PENDING_REVIEW", + "batchStatusName": "待核单", + "reviewStatus": "PENDING", + "reviewStatusName": "待核单", + "settlementStatus": "NONE", + "settlementStatusName": "未结算", + "stage": "TRIP", + "stageName": "出行", + "tripSubStatus": "PENDING_REVIEW", + "tripSubStatusName": "待核单", + "opsStage": "TRIP", + "opsStageName": "出行", + "departDate": "2026-09-26", + "endDate": "2026-09-28" + }, + { + "groupBatchId": "2105223439872933889", + "batchNo": "T26-2325", + "batchName": "呼伦贝尔草原3日游·9月25日团", + "batchStatus": "PENDING_REVIEW", + "batchStatusName": "待核单", + "reviewStatus": "PENDING", + "reviewStatusName": "待核单", + "settlementStatus": "NONE", + "settlementStatusName": "未结算", + "stage": "TRIP", + "stageName": "出行", + "tripSubStatus": "PENDING_REVIEW", + "tripSubStatusName": "待核单", + "opsStage": "TRIP", + "opsStageName": "出行", + "departDate": "2026-09-25", + "endDate": "2026-09-27" + } + ], + "total": 2, + "page": 1, + "pageSize": 50 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无匹配时 `records=[]`、`total=0`(未变,下例为结构示意)。已流团团期照常返回,四个新字段为 `NONE` / 未核单 / `NONE` / 未结算(TEST 上 T27-2584 实测): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [], + "total": 0, + "page": 1, + "pageSize": 20 + }, + "traceId": null, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "traceId": null, + "success": false +} +``` + +| 错误码 | 触发条件 | +|--------|----------| +| 589507 | 缺 `group-batch:list` 权限码(未变) | + +#### 业务边界 + +- 入参只兼容旧值,返回值一律输出新值。 +- `opsStage` 旧八桶别名(FORMED / PENDING_TRIP / TRAVELLING / TRIP_FINISHED / AUDITING / CHECKED)照常接受;`TRIP_FINISHED` 桶现在就是「待核单」团期。 +- 本次**没有**做按 `reviewStatus` / `settlementStatus` 筛选(jw 09-29 定不做),列表只输出这两个字段。 +- 团期统计条 `GET /v3/admin/order/group-batch/summary` 的旧桶统计键 `TRIP_FINISHED` 同样保留,计数对象是待核单团期。 + +### 3. 房务配房列表(团期) `GET /v3/admin/order/house-allocation/group-batches` + +**VO**: `HouseAllocationGroupPageRespVO`(入参 `HouseAllocationGroupPageReqVO`,列表项 `HouseAllocationGroupRespVO`) + +#### 使用场景 + +房务配房列表(一行一团),同时承接原房务「我的团」(`scope=mine`,原「我的团」接口已随 #8491 下线)。本次 `batchStatus` 筛选传旧值 `TRIP_FINISHED` 时按 `PENDING_REVIEW` 查询(改前直接返回 400)。 + +#### 入参 + +只列本次有变化的入参,其余不变。 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| scope | Query | String | 否 | `all` / `mine`,默认 `all` | 未变;两种范围都享受旧值兼容 | +| batchStatus | Query | String | 否 | 九态之一或空串 | 合法值中 `TRIP_FINISHED` 换成 `PENDING_REVIEW`;传旧值 `TRIP_FINISHED` 先按 `PENDING_REVIEW` 处理再校验,不再 400;其他非法值仍 400 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| list[].batchStatus | String | 取值 `TRIP_FINISHED` 改为 `PENDING_REVIEW` | +| list[].batchStatusLabel | String | 「出行完毕」改为「待核单」 | +| total / stats | Long / Object | 未变 | + +#### 请求示例 + +```http +GET /v3/admin/order/house-allocation/group-batches?scope=mine&batchStatus=TRIP_FINISHED HTTP/1.1 +Host: api.test.1814.love +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +传旧值返回房务-赵丽娟整团认领的待核单团期(TEST 实测,只列与本次相关的字段): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "list": [ + { + "groupBatchId": "2105223439872933889", + "batchNo": "T26-2325", + "batchName": "呼伦贝尔草原3日游·9月25日团", + "batchStatus": "PENDING_REVIEW", + "batchStatusLabel": "待核单", + "departDate": "2026-09-25", + "endDate": "2026-09-27", + "houseClaimerName": "房务-赵丽娟", + "isMine": true + } + ], + "total": 1, + "stats": { "pendingClaim": 0, "claimed": 1, "all": 1 } + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无匹配时 `list=[]`、`total=0`;`stats` 按同一筛选条件计数(未变,下例为结构示意): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "list": [], + "total": 0, + "stats": { "pendingClaim": 0, "claimed": 0, "all": 0 } + }, + "traceId": null, + "success": true +} +``` + +#### 错误响应 + +非法状态值仍被拒(TEST 实测): + +```json +{ + "code": 400, + "message": "batchStatus 不是合法的团期阶段", + "data": null, + "traceId": null, + "success": false +} +``` + +#### 业务边界 + +- 只兼容 `TRIP_FINISHED` 这一个旧值;返回值一律输出新值。 +- `scope=mine` 与 `scope=all` 的兼容行为一致(均已实测)。 + +### 4. 房务团期看板列表 `GET /v3/admin/house/group-batches` + +**VO**: `HouseGroupBatchBoardSimpleRespVO`(入参 `HouseGroupBatchBoardPageReqVO`) + +#### 使用场景 + +房务团期看板(只显示已认领的团)。本次 `batchStatus` 多选筛选里的旧值 `TRIP_FINISHED` 按 `PENDING_REVIEW` 处理(改前被当成非法值丢弃,整页退回默认筛选)。 + +#### 入参 + +只列本次有变化的入参,其余不变。 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| scope | Query | String | 否 | `MINE` / `ALL`,默认 `MINE` | 未变 | +| batchStatus | Query | String | 否 | 逗号分隔多选,最长 200 | 旧值 `TRIP_FINISHED` 按 `PENDING_REVIEW` 处理;`CANCELLED` 与非法值仍忽略;不传时默认四态(资源准备中 / 物料准备中 / 待出发 / 出行中,不含待核单,未变) | +| pageSize | Query | Long | 否 | 1~50,默认 20 | 未变 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].batchStatus | String | 取值 `TRIP_FINISHED` 改为 `PENDING_REVIEW` | +| records[].batchStatusText | String | 「出行完毕」改为「待核单」 | +| total / page / pageSize | Long | 未变 | + +#### 请求示例 + +```http +GET /v3/admin/house/group-batches?batchStatus=TRIP_FINISHED HTTP/1.1 +Host: api.test.1814.love +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +传旧值返回待核单团期,没有退回默认筛选(TEST 实测,只列与本次相关的字段): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "groupBatchId": "2105223439872933889", + "batchNo": "T26-2325", + "batchName": "呼伦贝尔草原3日游·9月25日团", + "batchStatus": "PENDING_REVIEW", + "batchStatusText": "待核单", + "departDate": "2026-09-25", + "endDate": "2026-09-27", + "claimerName": "房务-赵丽娟", + "boardStatus": "NOT_STARTED" + } + ], + "total": 1, + "page": 1, + "pageSize": 20 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +不传 `batchStatus` 走默认四态,待核单团期不在其中;同一账号此时为空(TEST 实测): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [], + "total": 0, + "page": 1, + "pageSize": 20 + }, + "traceId": null, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 400, + "message": "pageSize 最大 50", + "data": null, + "traceId": null, + "success": false +} +``` + +#### 业务边界 + +- 只兼容 `TRIP_FINISHED` 这一个旧值;返回值一律输出新值。 +- `scope=ALL` 同样兼容(已实测)。 + +### 5. 团期状态流水(时间线) `GET /v3/admin/order/group-batch/:groupBatchId/status-logs` + +**VO**: `GroupBatchStatusLogItemVO` + +#### 使用场景 + +团期详情页时间线。本次新增两类事件:财务或管理后台入口改动整团核单 / 结算状态时各记一条;主状态值 `TRIP_FINISHED` 改名后,历史行的 `fromStatus` / `toStatus` 也已统一改写。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 团期 ID | 未变;按时间升序返回全部流水,不分页 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| eventType | String | **新增两个取值**:`BATCH_REVIEW_STATUS_CHANGE`「核单状态变更」、`BATCH_SETTLE_STATUS_CHANGE`「结算状态变更」。事件 `BATCH_TRIP_FINISH`「出行完毕」不改名 | +| eventTypeName | String | 核单状态变更 / 结算状态变更 | +| changeType | String | 新事件一般为 `DATA`;核单从核单中退回待核单(主状态 核单中 → 待核单)那一条为 `STATUS` | +| fromStatus / toStatus | String | 取值 `TRIP_FINISHED` 改为 `PENDING_REVIEW`(含历史行) | +| fromStatusName / toStatusName | String | 「出行完毕」改为「待核单」 | +| content | String | 如「核单状态:核单中 → 已核单」「结算状态:已结算 → 结算中」;核单从已核单退回时追加「;结算状态随之置回:结算中 → 未结算」 | +| reason | String | 调用方传入的原因;管理后台入口为验团意见或 null | +| operatorType / operatorId / operatorName | String / Long / String | 财务内部接口写入的行:`SYSTEM`、`null`、财务传入的姓名(如「财务-王丽华」);管理后台入口写入的行为登录管理员 | +| extra | Object | 新事件带 `field` / `from` / `to` / `fromName` / `toName` / `source`,有操作人时带 `operatorName`;核单退回时带 `settlementFrom` / `settlementTo`;有子订单同步时带 `subOrderSyncedCount` / `subOrderSkippedOrderIds`;结算离开已结算、核团被退回时带 `auditReverted: true` | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2105223438312652802/status-logs HTTP/1.1 +Host: api.test.1814.love +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +T26-5936 时间线中的四条(TEST 实测,节选): + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "logId": "2105228170246713346", + "groupBatchId": "2105223438312652802", + "changeType": "STATUS", + "eventType": "BATCH_TRIP_FINISH", + "eventTypeName": "出行完毕", + "fromStatus": "TRAVELLING", + "fromStatusName": "出行中", + "toStatus": "PENDING_REVIEW", + "toStatusName": "待核单", + "content": "系统自动出行完毕", + "reason": null, + "operatorType": "SYSTEM", + "operatorId": null, + "operatorName": "系统", + "extra": null, + "changedAt": "2026-09-30 17:28:04" + }, + { + "logId": "2105231057186156546", + "groupBatchId": "2105223438312652802", + "changeType": "STATUS", + "eventType": "BATCH_REVIEW_STATUS_CHANGE", + "eventTypeName": "核单状态变更", + "fromStatus": "REVIEWING", + "fromStatusName": "核单中", + "toStatus": "PENDING_REVIEW", + "toStatusName": "待核单", + "content": "核单状态:核单中 → 待核单", + "reason": "大巴费用票据未到,退回待核单", + "operatorType": "SYSTEM", + "operatorId": null, + "operatorName": "财务-王丽华", + "extra": { + "field": "reviewStatus", + "from": "IN_PROGRESS", + "to": "PENDING", + "fromName": "核单中", + "toName": "待核单", + "operatorName": "财务-王丽华", + "source": "GroupBatchReviewSettleService", + "subOrderSyncedCount": 2, + "subOrderSkippedOrderIds": [] + }, + "changedAt": "2026-09-30 17:39:32" + }, + { + "logId": "2105239217246531585", + "groupBatchId": "2105223438312652802", + "changeType": "DATA", + "eventType": "BATCH_SETTLE_STATUS_CHANGE", + "eventTypeName": "结算状态变更", + "fromStatus": "SETTLED", + "fromStatusName": "已结算", + "toStatus": "SETTLED", + "toStatusName": "已结算", + "content": "结算状态:未结算 → 已结算", + "reason": "报账款已付清,整团结算完成", + "operatorType": "SYSTEM", + "operatorId": null, + "operatorName": "财务-王丽华", + "extra": { + "field": "settlementStatus", + "from": "NONE", + "to": "COMPLETED", + "fromName": "未结算", + "toName": "已结算", + "operatorName": "财务-王丽华", + "source": "GroupBatchReviewSettleService", + "subOrderSyncedCount": 1, + "subOrderSkippedOrderIds": ["2105223438178435074"] + }, + "changedAt": "2026-09-30 18:11:58" + }, + { + "logId": "2105241608285024259", + "groupBatchId": "2105223438312652802", + "changeType": "DATA", + "eventType": "BATCH_SETTLE_STATUS_CHANGE", + "eventTypeName": "结算状态变更", + "fromStatus": "REVIEWING", + "fromStatusName": "核单中", + "toStatus": "REVIEWING", + "toStatusName": "核单中", + "content": "结算状态:已结算 → 结算中", + "reason": "出纳付款失败需重付,结算改回结算中", + "operatorType": "SYSTEM", + "operatorId": null, + "operatorName": "财务-王丽华", + "extra": { + "field": "settlementStatus", + "from": "COMPLETED", + "to": "IN_PROGRESS", + "fromName": "已结算", + "toName": "结算中", + "operatorName": "财务-王丽华", + "source": "GroupBatchReviewSettleService", + "auditReverted": true, + "subOrderSyncedCount": 2, + "subOrderSkippedOrderIds": [] + }, + "changedAt": "2026-09-30 18:21:28" + } + ], + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +团期没有流水时返回空数组(未变): + +```json +{ + "code": 200, + "message": "成功", + "data": [], + "traceId": null, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "traceId": null, + "success": false +} +``` + +| 错误码 | 触发条件 | +|--------|----------| +| 589507 | 缺 `group-batch:view` 权限码(未变) | + +#### 业务边界 + +- 主状态随核单 / 结算变化时,除新事件这一行外,另有一条既有事件的主状态流转行:进入核单中记 `BATCH_TRIP_END`「发起核单」,进出已结算记 `BATCH_SETTLE`「结算」;只有「核单中 → 待核单」没有既有事件可用,由新事件那一行直接以 `STATUS` 记录。 +- 因此进入核单中时,新事件那一行是 `DATA` 行,`fromStatus` / `toStatus` 都是变化后的主状态(如 `REVIEWING` → `REVIEWING`),前端不要据此判断主状态有没有变。 +- 同值重复回写不记流水。 + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受 / 拒绝的规则,以及字段该怎么读。 + +### ✅ 正确 / ❌ 错误用法对照 + +| 场景 | 用法 | +|------|------| +| ✅ 判断团期是否待核单(如「发起核单」按钮) | `batchStatus === 'PENDING_REVIEW'`(与 `reviewStatus === 'PENDING'` 等价) | +| ❌ 仍按旧值判断 | `batchStatus === 'TRIP_FINISHED'` → 永远为 false,按钮消失 | +| ✅ 按待核单筛选列表 | `?batchStatus=PENDING_REVIEW` | +| ⚠️ 旧书签 / 旧代码传 `?batchStatus=TRIP_FINISHED` | 三处入参按 `PENDING_REVIEW` 处理,能查出数据;新代码请改传新值 | +| ✅ 看板页签按旧桶筛选 | `?opsStage=TRIP_FINISHED`(桶名保留)或七桶 `?opsStage=TRIP` | +| ✅ 展示「核单已完成」 | 读 `reviewStatus === 'COMPLETED'` 或进度条核单分支 `displayText` | +| ❌ 用核单节点 label 判断核单是否完成 | 核单已完成、结算未完成时 label 仍是「核单中」 | +| ❌ 用 `settlementStatus` 当逐户付款结清 | 那是财务 tab `items[].settleStatus`,与整团结算状态无关 | + +### 切换状态时的必要动作 + +- 管理后台没有直接改 `reviewStatus` / `settlementStatus` 的入口。整团核单 / 结算状态由财务通过内部接口回写;管理后台既有的「发起核单」「首笔共享成本」「整团结算 `/settle`」「反结算 `/settle/reopen`」会按固定规则改这两个状态(见六)。 +- 前端操作后如需刷新状态,重新拉 A2 详情即可,不要在前端自行推导主状态。 + +--- + +## 五、数据库行为 + +- 本文五个接口都是只读接口,不写数据。 +- 存量数据已由迁移一次性改写(TEST 迁移 `20260929.8516` 已执行): + - 主状态「出行完毕」统一改为「待核单」,团期时间线历史行、团级核单快照里的流程状态一并改名; + - 两个新状态按主状态回填:待核单 → 核单=待核单;核单中 → 核单=核单中;已结算 → 核单=已核单、结算=已结算;其余团期两者都是 `NONE`。回填可重跑,重跑不产生变化。 +- 管理后台写入口的数据变化见六「既有写入口」。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截,未变);缺权限码 → 589507(未变)。 +- 团期不存在 → 589500(未变)。 +- 入参传旧值 `TRIP_FINISHED`:A1 分页、房务配房列表、房务团期看板按 `PENDING_REVIEW` 处理;其他任何接口的返回值都不会再出现 `TRIP_FINISHED`。 +- 新字段 `reviewStatus` / `settlementStatus` 恒非 null;出行完毕前为 `NONE`。 + +### 既有写入口:请求 / 响应契约不变,行为变化 + +以下四个管理后台入口的路径、入参、出参结构、成功返回值**都没有变**,变的是它们对团期核单 / 结算状态与子订单的写入。子订单同步与团期改动同一事务,**任何一步都不推报账单**。 + +| 入口 | 请求 / 响应 | 行为变化 | TEST 实测 | +|------|-------------|----------|-----------| +| 发起核单 `POST /v3/admin/order/group-batch/:groupBatchId/review/start` | 不变:无请求体,返回 `groupBatchId` / `batchStatus` / `batchStatusDesc` / `alreadyStarted` | 核单改为核单中,团期推导为核单中,核单为待核单的在团户同事务进核单中(替代 #8341 的「整团带入核单中」);核单已是核单中或已核单时返回 `alreadyStarted=true`、零写入,不会把已核单退回;错误码 589564 文案由「须为「出行完毕」」改为「须为「待核单」」,589565 / 589566 不变 | 首次 `alreadyStarted=false`;重复调用、已核单时调用均 `alreadyStarted=true` | +| 首笔共享成本 `POST /v3/admin/order/group-batch/:groupBatchId/settlement/cost` | 不变:返回成本明细 ID | 核单为待核单时改为核单中并同步子订单;第二笔起不再改状态;状态不符仍 589501 | 待核单 → 核单中,两户同步进核单中 | +| 整团结算 `POST /v3/admin/order/group-batch/:groupBatchId/settle` | 不变:请求体 `checkNote` 可空,`data=null` | 核单置已核单(已是则跳过)、结算置已结算 → 团期已结算;待结算的在团户**只改状态**为已结算,**不再逐户推 ORDER 报账单**(停掉 #8341 的逐户财务复核推单);有在团户逐户核单未提交时仍整团 589568 并列出订单号、零写入;589555 / 589567 / 589573 不变 | 从已核单、从核单中两种起点都成功,核团 → 已结算(CHECKED),报账单行数不变;未提交户 589568 | +| 反结算 `POST /v3/admin/order/group-batch/:groupBatchId/settle/reopen` | 不变:无请求体,`data=null` | 结算改为待结算 → 团期核单中;核团从已结算退回已核算;已结算的在团户退回待结算,已推的报账单不撤;非已结算仍 589501 | 结算 → 待结算,两户退回待结算,之后重新核算、再结算均可用 | + +- 四个入口在极端并发下可能多出一个兜底码 589703「团期核单或结算状态已被他人修改,请刷新后重试」(内部已先加团期行锁,正常操作不会出现),前端透传 message 即可。 +- 团级核单定稿 `POST /v3/admin/order/group-batch/:groupBatchId/settlement/finalize` 的 584138 文案「需出行完毕或核单中」改为「需待核单或核单中」,码与触发条件不变。 + +## 六.5、枚举 / 数据字典 + +### batchStatus(`com.hulalv.order.groupbatch.enums.GroupBatchStatus`) + +**所属字段**: `GroupBatchDetailRespVO.batchStatus`、`GroupBatchPageItemRespVO.batchStatus` 等所有团期主状态字段 | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `RECRUITING` | 招募中 | 未变 | +| `RESOURCE_PREPARING` | 资源准备中 | 未变 | +| `MATERIAL_PREPARING` | 物料准备中 | 未变 | +| `PENDING_DEPARTURE` | 待出发 | 未变 | +| `TRAVELLING` | 出行中 | 未变 | +| `PENDING_REVIEW` | 待核单 | **原 `TRIP_FINISHED`「出行完毕」**。返团日已过、尚未开始核单;等价于 `reviewStatus=PENDING` | +| `REVIEWING` | 核单中 | 未变;`reviewStatus` 为核单中,或已核单但结算未到已结算 | +| `SETTLED` | 已结算 | 未变;`reviewStatus` 与 `settlementStatus` 都已完成 | +| `CANCELLED` | 已取消 | 未变(已流团) | + +### tripSubStatus(`GroupBatchStageBuckets.TripSubStatus`) + +**所属字段**: `GroupBatchDetailRespVO.tripSubStatus`、`GroupBatchPageItemRespVO.tripSubStatus` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `PENDING_DEPARTURE` | 待出发 | 未变 | +| `TRAVELLING` | 出行中 | 未变 | +| `PENDING_REVIEW` | 待核单 | **原 `TRIP_FINISHED`「已返团」** | + +### reviewStatus(`com.hulalv.order.settlement.enums.ReviewStatus`,与子订单核单状态同一枚举) + +**所属字段**: `GroupBatchDetailRespVO.reviewStatus`、`GroupBatchPageItemRespVO.reviewStatus` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `NONE` | 未核单 | 还没出行完毕 | +| `PENDING` | 待核单 | 出行完毕后自动进入 | +| `IN_PROGRESS` | 核单中 | 财务开始核单,或管理后台发起核单 / 首笔共享成本 | +| `COMPLETED` | 已核单 | 财务完成核单,或管理后台整团结算 | + +### settlementStatus(`com.hulalv.order.groupbatch.enums.GroupBatchSettlementStatus`) + +**所属字段**: `GroupBatchDetailRespVO.settlementStatus`、`GroupBatchPageItemRespVO.settlementStatus` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `NONE` | 未结算 | 核单完成之前恒为此值 | +| `PENDING` | 待结算 | 核单已完成、尚未开始结算。同一个值在子订单上叫「待财务复核」 | +| `IN_PROGRESS` | 结算中 | 团期独有,子订单没有这一态;含义由财务定义 | +| `COMPLETED` | 已结算 | 团期主状态随之为已结算 | + +### 进度条核单 / 结算分支 status(`GroupBatchProgressSubFlowVO.status`) + +**所属字段**: `progressStepper[].subFlows[].status`(仅 code 为 `REVIEW` / `SETTLE` 的分支) | **类型**: `String` + +| 值 | 中文(statusName) | 说明 | +|----|------|------| +| `WAITING` | 待开始 | 对应字段为 `NONE`(或脏值) | +| `PENDING` | 待核单 / 待结算 | 对应字段为 `PENDING` | +| `IN_PROGRESS` | 核单中 / 结算中 | 对应字段为 `IN_PROGRESS` | +| `COMPLETED` | 已核单 / 已结算 | 对应字段为 `COMPLETED` | + +### 时间线新增 eventType(`com.hulalv.order.groupbatch.enums.GroupBatchLogEventType`) + +**所属字段**: `GroupBatchStatusLogItemVO.eventType` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `BATCH_REVIEW_STATUS_CHANGE` | 核单状态变更 | 整团核单状态每次真的变化记一条 | +| `BATCH_SETTLE_STATUS_CHANGE` | 结算状态变更 | 整团结算状态每次真的变化记一条 | + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `batchStatus`(所有返回团期主状态的接口) | 可能为 `TRIP_FINISHED`「出行完毕」 | 该值改为 `PENDING_REVIEW`「待核单」,`TRIP_FINISHED` 不再出现 | +| `tripSubStatus` / `tripSubStatusName` | `TRIP_FINISHED` / 已返团 | `PENDING_REVIEW` / 待核单 | +| `progressStepper` 出行节点 label(待核单团期) | 已返团 | 待核单 | +| A2 / A1 `reviewStatus` / `reviewStatusName` | 无 | 新增 | +| A2 / A1 `settlementStatus` / `settlementStatusName` | 无 | 新增 | +| A2 `progressStepper` REVIEW / SETTLE 节点 `subFlows` | `null` | 各 1 条分支 | +| 时间线 `eventType` | 无核单 / 结算状态事件 | 新增 `BATCH_REVIEW_STATUS_CHANGE` / `BATCH_SETTLE_STATUS_CHANGE` | +| 时间线历史行 `fromStatus` / `toStatus` | 可能为 `TRIP_FINISHED` | 已改写为 `PENDING_REVIEW` | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| A1 分页 `?batchStatus=TRIP_FINISHED` | 静默返回空 | 返回待核单团期 | +| 房务配房列表 `?batchStatus=TRIP_FINISHED`(含 `scope=mine`) | 400「batchStatus 不是合法的团期阶段」 | 返回待核单团期 | +| 房务团期看板 `?batchStatus=TRIP_FINISHED` | 当非法值丢弃,退回默认四态 | 返回待核单团期 | +| `?opsStage=TRIP_FINISHED` | 出行完毕团期 | 待核单团期(桶名保留) | +| 团期主状态「待核单 / 核单中 / 已结算」 | 各入口直接推进主状态 | 由整团核单、结算两个状态推导 | +| `/settle` | 逐户财务复核并推 ORDER 报账单 | 子订单只改状态为已结算,不推报账单 | +| `/settle/reopen` | 本入口自己退核团 | 核团退回收进统一写口,本入口与财务内部接口行为一致 | +| 发起核单 589564 文案 | 须为「出行完毕」 | 须为「待核单」 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是。`batchStatus` / `tripSubStatus` 的 `TRIP_FINISHED` 取值改名,按旧值判断的前端代码会静默失效。 +- **前端是否必须同步发布**: 是。jw 09-30 定:TEST 先行(后端 09-30 16:31 已部署到 TEST,hl-ui 本次未改),正式环境前后端同批发布。 +- **前端必须改:按状态值做判断的代码**(hl-ui `origin/v2.1` 只读核对,行号以核对时为准): + - `order-v2/batch/detail/index.vue:832` `canStartReview`:改为判 `'PENDING_REVIEW'`,不改「发起核单」按钮会消失; + - `finance/settlement/components/GroupSettlementPanel.vue:281`:「完成核单」可点条件; + - `order-v2/batch/detail/components/FinanceTab.vue:309`:预支可发起的阶段; + - `order-v2/batch/detail/components/GroupVehicleRequirementSection.vue:399`:免车声明可用的阶段; + - `order-v2/detail/_shared/orderDetailActions.js:24`、`requirementFreeze.js:16`:状态集合; + - `order-v2/batch/_shared/batchLifecycle.js:53`:状态到节点的映射。 +- **前端必须改:显示用的映射表**:`GroupSettlementPanel.vue:260`、`housekeeper/orders/GroupBatchTable.vue:49`、`housekeeper/room-board/components/boardLabels.js:50`(`PENDING_REVIEW` → 待核单)。 +- **前端建议改**:团期详情 / 列表展示 `reviewStatusName` / `settlementStatusName`;进度条核单、结算节点渲染新分支(写法与配置节点五条分支相同)。 +- **前端不用改**:`opsStage=TRIP_FINISHED`(旧桶名,后端保留);`NO_VEHICLE_TRIP_FINISHED_CODE`(车队错误码常量,与团期状态无关)。 +- **其他返回团期主状态的接口**(团期看板 `/board`、团期统计 `/summary`、房务看板详情、车务派单相关读口、核团详情 `return-detail` 的 `flowStatus` 等):字段名与结构不变,取值同样由 `TRIP_FINISHED` 变为 `PENDING_REVIEW`、中文名变为「待核单」;统计键 `TRIP_FINISHED` 保留。 +- **BI / 报表 / 外部取数**:如按 `'TRIP_FINISHED'` 取数会取不到,需要改为 `PENDING_REVIEW`(代码仓 grep 不到这类依赖,无法替使用方确认)。 +- **前端 workaround 清理点**: 无。 +- **回滚**:先执行后端回滚 SQL 把存量值改回,再回退代码;前端须一起回退。 + +## 七、不影响范围 + +- **仅影响**:团期主状态「出行完毕」这一个取值的名称与中文名;团期详情 / 分页的四个新字段;详情进度条核单、结算分支;时间线两类新事件;三处入参旧值兼容;四个既有写入口对状态与子订单的写入方式。 +- **零影响**: + - 其余八个团期主状态取值、六节点 `stage` 取值、`opsStage` 七桶与旧桶名; + - 进度条配置节点五条分支、六个主节点的当前节点判定规则; + - 团期状态筛选以外的所有入参; + - 团级核单定稿 / 确认、逐户核单提交、逐户财务复核确认、逐户反确认的接口与行为(对团期子订单不加限制); + - 时间线事件「出行完毕」`BATCH_TRIP_FINISH` 的名称; + - 网关:路径未变,无新增路由与权限码。 + +--- + +## 八、测试环境已验证 + +2026-09-30 TEST(`api.test.1814.love`):合并提交 `54e64c50f`,部署构建 dev-v3 `dd0452916`(order-v3 双实例 8086 / 8186),迁移 `20260929.8516` 于 16:32:13 执行成功。验收团期为本次新建:T26-5936 呼伦贝尔草原3日游·9月26日团、T26-2325 呼伦贝尔草原3日游·9月25日团。证据目录 `HL/.evidence/8516/`。 + +```text +AC-03 迁移:两列存在;三处存量 TRIP_FINISHED 均为 0 行,时间线 38 行改名为 PENDING_REVIEW;回填重跑零变化 ✓ +AC-09 发起核单首次 alreadyStarted=false、重复与已核单时 true;首笔成本 待核单→核单中并同步;/settle 两种起点均 SETTLED、报账单行数不变;未提交户 589568;/settle/reopen 结算→待结算、户退回 ✓ +AC-10 GET /v3/admin/order/group-batch/:groupBatchId 与分页返回四个新字段;核单分支实测 待开始/待核单/核单中/已核单,结算分支实测 待开始/待结算/结算中/已结算;出行节点 label 与 tripSubStatusName 为「待核单」 ✓ +AC-11 分页 batchStatus=TRIP_FINISHED 与 PENDING_REVIEW 结果一致(2 条,不筛选 27 条);房务配房列表 scope=mine/all 只返回待核单团、非法值仍 400;房务看板传旧值返回待核单团、未退回默认筛选;opsStage=TRIP_FINISHED 返回 2 条 ✓ +AC-12 GET /v3/admin/order/group-batch/:groupBatchId/status-logs 可见「核单状态变更」「结算状态变更」,操作人与原因正确 ✓ +``` + +| AC | 证据 | +|----|------| +| AC-09 | `HL/.evidence/8516/AC-09/` | +| AC-10 | `HL/.evidence/8516/AC-10/` | +| AC-11 | `HL/.evidence/8516/AC-11/01-legacy-TRIP_FINISHED.json`、`02-discriminators.json` | +| AC-12 | `HL/.evidence/8516/AC-12/01-status-logs.json` | + +单元测试:进度条 `GroupBatchProgressStepperTest`、分桶 `GroupBatchStageBucketsTest`、转换 `GroupBatchConverterTest`、看板入参 `HouseGroupBatchBoardManagerTest`、迁移 `GroupBatchReviewSettlementStatusMysqlMigrationTest` 等;order-v3 全量两半对照干净 dev-v3 基线,本单新增失败 0。 + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| — | #7190 | 团期状态新增出行完毕 `TRIP_FINISHED`、看板八桶 | ⚠️ 状态值已改名 `PENDING_REVIEW`,旧桶名保留 | +| — | #7527 | 团期发起核单端点 | ✅ 有效,改为写整团核单状态 | +| — | #8341 | 团期核单 / 结算 / 反结算同步子订单,结算即整团财务复核并推报账单 | ⚠️ 部分被本单取代:同步规则按本单,`/settle` 不再推报账单;589568 门禁保留 | +| — | #8478 | 团期详情分叉进度条 | ✅ 有效,本单为核单、结算节点加分支 | +| **#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 diff --git a/changelogs-v2/2026-09/30_8516_团期核单与结算状态财务内部回写接口-新增接口-管理后台.md b/changelogs-v2/2026-09/30_8516_团期核单与结算状态财务内部回写接口-新增接口-管理后台.md new file mode 100644 index 00000000..8964ed64 --- /dev/null +++ b/changelogs-v2/2026-09/30_8516_团期核单与结算状态财务内部回写接口-新增接口-管理后台.md @@ -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` + +状态字段一律是**写后**的当前值。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| 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\ | 不在可同步状态、被跳过的子订单 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` + +与接口 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\ | 同接口 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