docs(order-v3): #7527 团期发起核单端点(TRIP_FINISHED → REVIEWING)
changelog-filename-gate / validate (push) Successful in 3s
changelog-filename-gate / validate (push) Successful in 3s
新增 POST /v3/admin/order/group-batch/{groupBatchId}/review/start。
补的是「团期卡死」的入口缺失:这一跳此前唯一入口是录共享成本的副作用,
不用大巴、没请导摄的团没有成本可录就没有触发点,永久停在「出行完毕」,
八桶看板的「核团中」桶对这类团恒为空。
面向 hl-ui 写清三件事:① 重复点击返 200 而非报错,必须看 data.alreadyStarted
区分「真的推进了」与「幂等命中」,否则会连弹两次成功;② 三个新错误码
589564/589565/589566 各对应不同的下一步动作(等团回来 / 去做验团反确认 /
这团已流团),给了逐条的前端提示建议,其中 589565 要带反确认跳转;
③ 录成本自动推进的老行为一行未改,两条入口并存,前端现有调用不需调整。
权限码复用录成本那一个(group-batch:finance:advance),按钮可见性判据可直接复用。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,278 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7527"
|
||||
title: "团期发起核单端点(TRIP_FINISHED → REVIEWING)"
|
||||
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-12"
|
||||
status_note: "团期详情/看板需加「发起核单」按钮。重复点击返 200 且 alreadyStarted=true,前端应据此提示「该团期已在核单中」而非再弹一次成功。录成本自动推进的老行为不变,两条入口并存。"
|
||||
updated_at: "2026-09-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 团期发起核单端点(TRIP_FINISHED → REVIEWING)
|
||||
|
||||
**服务**: hl-order-service-v3
|
||||
**PR**: #7568
|
||||
**Issue**: #7527
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
🔴 **补的是一个「团期卡死」的入口缺失,不是新功能。** `TRIP_FINISHED → REVIEWING` 这一跳此前
|
||||
**唯一入口是录共享成本的副作用**。于是**不用大巴、没请导摄的团根本没有共享成本可录**,
|
||||
就没有触发点,团期永久停在「出行完毕」——既进不了核单,也归不了档,
|
||||
八桶看板的「核团中」桶对这类团恒为空。
|
||||
|
||||
🔴 **两条入口并存,老行为一行未改。** 「先点发起核单再录成本」与「直接录第一笔成本自动推进」
|
||||
都继续可用。前端不需要为本端点调整任何现有调用。
|
||||
|
||||
🔴 **重复点击返 200,不是报错。** 用 `data.alreadyStarted` 区分:`false` = 本次真的推进了;
|
||||
`true` = 调用前团期已在核单中,本次是幂等命中、**零写入零时间线**。
|
||||
前端若只看 HTTP 码,重复点会连弹两次「已发起核单」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期生命周期第 6 跳(出行完毕 → 核单中)没有人工入口。运营在八桶看板上点不动「核团中」桶,
|
||||
只能等财务去改库,或者随便录一笔 0 元成本把状态机「骗」过去。
|
||||
这是把一个**流程动作**绑死在一个**数据录入动作**上的后果。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 发起核单 | POST | `/v3/admin/order/group-batch/{groupBatchId}/review/start` | 新增 | TRIP_FINISHED → REVIEWING 的显式人工入口;重复点幂等返 200 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 发起核单 `POST /v3/admin/order/group-batch/{groupBatchId}/review/start`
|
||||
|
||||
**VO**: `GroupBatchReviewStartRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
hl-ui 管理后台「团期详情 / 团期看板」的「发起核单」按钮。团期返团后(状态「出行完毕」)由
|
||||
运营或财务点一下,把团期推进到「核单中」,之后才能走验团归档。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | 雪花 ID,不存在返 589500 | 团期主订单 ID |
|
||||
|
||||
**无请求体。**
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | String | 团期主订单 ID(Long 经 ToStringSerializer 序列化,防精度丢失) |
|
||||
| batchStatus | String | 推进后的团期状态,**恒为** `REVIEWING` |
|
||||
| batchStatusDesc | String | 状态中文名,恒为「核单中」 |
|
||||
| alreadyStarted | Boolean | `false`=本次调用完成了推进;`true`=调用前团期**已在**核单中,本次为幂等命中、未产生任何写入与时间线 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/group-batch/2089679374176464898/review/start
|
||||
Authorization: Bearer <admin token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2089679374176464898",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusDesc": "核单中",
|
||||
"alreadyStarted": false
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
重复点击(团期已在核单中)返 200,`alreadyStarted=true`,**零写入、零时间线**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2089679374176464898",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusDesc": "核单中",
|
||||
"alreadyStarted": true
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
时间线写入失败时**不回滚状态推进**,接口仍返成功(时间线不是主链路)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 建议前端提示 |
|
||||
|----|------|------|------|
|
||||
| 589500 | `GROUP_BATCH_NOT_FOUND` | groupBatchId 查不到团期 | 团期不存在 |
|
||||
| 589507 | `GROUP_BATCH_PERMISSION_DENIED` | 角色未持 `group-batch:finance:advance` | 无操作权限 |
|
||||
| 589564 | `GROUP_BATCH_REVIEW_START_STATUS_INVALID` | 状态早于「出行完毕」(招募中/资源准备中/物料准备中/待出发/出行中),或库中为脏值 | 「团队还没返团,返团后才能发起核单」 |
|
||||
| 589565 | `GROUP_BATCH_REVIEW_START_AFTER_SETTLED` | 团期已验团归档 | 「该团已验团归档,如需重新核单请先做验团反确认」,并给一个跳转到反确认的入口 |
|
||||
| 589566 | `GROUP_BATCH_REVIEW_START_DISBANDED` | 团期已流团 / 取消 | 「该团已流团,不能发起核单」,按钮应置灰 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589564,
|
||||
"message": "当前团期状态不可发起核单(须为「出行完毕」)",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589565,
|
||||
"message": "团期已验团归档,如需重新核单请先做验团反确认",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589566,
|
||||
"message": "团期已流团,不可发起核单",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **幂等**:由 CAS 的 `from = TRIP_FINISHED` 条件天然保证。重复调用与并发双击的败者都落到
|
||||
`alreadyStarted=true` 的成功分支,不会写出第二条时间线。前端无需做防抖去重(做了更好)。
|
||||
- **三个错误码刻意分开**,不共用通用的 589501「团期状态不允许当前操作」:这三种拒绝对运营的
|
||||
下一步动作完全不同(等团回来 / 去做验团反确认 / 这团已经流了),共用一个码前端只能给出
|
||||
一句无法行动的提示。
|
||||
- **成功后团期进入「核单中」**,共享成本录入端点 `POST .../settlement/cost` 与验团归档端点
|
||||
`POST .../settle` 随之可用。
|
||||
- 权限码与录成本端点**同一个**(`group-batch:finance:advance`):今天能录成本的人就能发起核单,
|
||||
没有人凭空多出权限,前端的按钮可见性判据可直接复用录成本那一套。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- **不要把 200 当成「推进成功」**:必须看 `data.alreadyStarted`。`true` 时提示「该团期已在核单中」,
|
||||
不要再弹一次「发起成功」。
|
||||
- **不要用它做状态查询**:本端点是写动作,读团期状态请用团期详情接口。
|
||||
- **不要为它做重试**:幂等安全,但重试没有意义——失败的三个码都是业务拒绝,重试结果相同。
|
||||
- **589565 要给出口**:提示里带上「验团反确认」的跳转,对应端点
|
||||
`POST /v3/admin/order/group-batch/{groupBatchId}/settle/reopen`。
|
||||
- **老入口不变**:`POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost` 在
|
||||
`TRIP_FINISHED` 上录第一笔成本仍会自动推进到 `REVIEWING`,行为一行未改。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
**无表变更、无 Flyway、无新增列。** 本端点只写两处既有的地方:
|
||||
|
||||
- `order_group_batch.batch_status`:由 `TRIP_FINISHED` CAS 更新为 `REVIEWING`(幂等命中时不写);
|
||||
- `group_batch_status_log`:追加一条 `event_type=BATCH_TRIP_END` / `from_status=TRIP_FINISHED` /
|
||||
`to_status=REVIEWING` / `content=发起核单(管理员手动)` 的时间线(幂等命中时不写)。
|
||||
|
||||
时间线写入失败仅记 WARN 降级,**不回滚状态推进**——流水不是主链路,不能因为它写失败把
|
||||
状态推进撤销掉。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
| 场景 | 行为 |
|
||||
|---|---|
|
||||
| 团期在「出行完毕」 | 200,`alreadyStarted=false`,状态变「核单中」,时间线 +1 |
|
||||
| 团期已在「核单中」 | 200,`alreadyStarted=true`,**零写入、零时间线** |
|
||||
| 并发双击 / 与录成本并发 | 只有一个请求 CAS 成功;败者重读状态后也返 200 `alreadyStarted=true` |
|
||||
| 团期早于「出行完毕」 | 589564 |
|
||||
| 团期已「已结算」 | 589565 |
|
||||
| 团期已「已取消」(流团) | 589566 |
|
||||
| 团期不存在 | 589500 |
|
||||
| 无权限 | 589507,**零落库** |
|
||||
| 库中 `batch_status` 为枚举外脏值 | 589564(业务码,**不是 500**) |
|
||||
| 时间线写入失败 | 状态已推进,接口仍返 200 |
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **`POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost` 一行未改**——其内嵌的
|
||||
`TRIP_FINISHED → REVIEWING` 副作用保持原样,两条入口并存。
|
||||
- 验团归档 `POST .../settle` 与验团反确认 `POST .../settle/reopen` 未改动。
|
||||
- 无 Mapper 改动(复用既有 `casAdvanceToReviewing`)、无 Entity 改动、无网关路由改动
|
||||
(命中已有 `/v3/admin/**` 通配)、无 hl-user-service 权限种子改动(复用既有权限码)。
|
||||
- 核团业务内容(核团四表、主次报账人记账、成本分摊)**不在本单**,仍归 GB-ADM-050~052。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署**:`hl-order-service-v3` @ `feat/7527-group-batch-review-start` / `9f3ee37d9`,双实例滚动重启完成。
|
||||
**全部实测经真实网关 `api.test.1814.love:9443` + Bearer 鉴权**,载体是 TEST 上一个 0 子订单的空闲团期
|
||||
`2089679374176464898`。
|
||||
|
||||
| 场景 | 请求 | 响应 |
|
||||
|---|---|---|
|
||||
| 出行完毕 → 发起 | `POST .../2089679374176464898/review/start` | 200,`alreadyStarted=false`,`batchStatus=REVIEWING`;库 `batch_status` 变 `REVIEWING`;`group_batch_status_log` 新增 `BATCH_TRIP_END / TRIP_FINISHED → REVIEWING / 发起核单(管理员手动)` |
|
||||
| 再点一次 | 同上 | 200,`alreadyStarted=true`;状态不变,时间线**条数不变** |
|
||||
| 出行中 | 同上 | 589564「当前团期状态不可发起核单(须为「出行完毕」)」 |
|
||||
| 已结算 | 同上 | 589565「团期已验团归档,如需重新核单请先做验团反确认」 |
|
||||
| 已取消 | 同上 | 589566「团期已流团,不可发起核单」 |
|
||||
| 团期不存在 | `POST .../999999999/review/start` | 589500「团期不存在」 |
|
||||
| 无权限角色 | 同上(roleKey=GUIDE / CUSTOMER_SERVICE) | 589507 |
|
||||
| **老行为零回归** | 在「出行完毕」上 `POST .../settlement/cost` | 200,库 `batch_status` 仍自动变 `REVIEWING` |
|
||||
|
||||
> 前置条件达成方式:`TRIP_FINISHED` / `TRAVELLING` / `SETTLED` / `CANCELLED` 四态在 TEST 上一个都不存在
|
||||
> (它们由 `GroupBatchLifecycleJobService` 按班期日期定时推进),故用 SQL 直更 `batch_status` 逐个构造前置态,
|
||||
> 取证后已还原为原始的 `RESOURCE_PREPARING`。**该路径没有验到状态机上游的推进逻辑,只验本端点自身的判据。**
|
||||
|
||||
**本地全量**:`mvn -o -pl hl-order-service-v3 -am test` → **9935 例 / Failures 0**;
|
||||
`RedLineArchTest` / `LayerEnforcementTest` / `MapperBoundaryArchTest` 均通过(非 Skipped)。
|
||||
本单新增 10 例单测。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/7527
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/7568
|
||||
- 兄弟端点(验团归档 / 反确认):`POST /v3/admin/order/group-batch/{groupBatchId}/settle`、`.../settle/reopen`
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端(hl-ui 管理后台):mmg
|
||||
- 需求定案:wx
|
||||
在新工单中引用
屏蔽一个用户