docs(order-v3): #7441 团期车务已上线部分 changelog(正式用车需求四接口、确认行程清单房车短路、团车内部回调、核单车侧硬阻断)
changelog-filename-gate / validate (push) Failing after 1s
changelog-filename-gate / validate (push) Failing after 1s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,567 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7441"
|
||||
title: "团期正式用车需求声明——新增分组×逐日整份提交、撤回、整团免车四个端点"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-15"
|
||||
status_note: "四个端点的成功路径与 809101-809110 共 10 个错误码均有测试服真实网关调用记录(见第八节,来源 testserver/LEDGER.md 与 pr2ev/ev/*.json)。809100(withdraw 无活跃正式需求)、809114(waive 时车务已开工)、809115(免车态下 PUT 带分组)三个错误码目前只有源码走查、无测试服网关取证记录。809111(GROUP_VEHICLE_REQUIREMENT_STAGE_INVALID)与 809113(GROUP_VEHICLE_WAIVE_HAS_GROUP)在当前实现中均无抛出点:前者保留给 #7442 复用,后者被 2026-09-15 定案 b1 的换版逻辑取代,源码保留常量占号。被测服务:order-v3 = dev-v3 `7d8cecb3e`,2026-09-15 15:38 部署(含 PR-1 ~ PR-3)。"
|
||||
updated_at: "2026-09-15"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 团期正式用车需求声明
|
||||
|
||||
> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3)
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8083)
|
||||
> **PR**: #7696(PR-1)、#7709(PR-1b)、#7763(PR-3,换版逻辑调整了本单的 waive 行为)
|
||||
> **Issue**: #7441
|
||||
> **日期**: 2026-09-15
|
||||
> **影响范围**: 团期需求页新增「正式用车需求」编辑区:整份保存(分组 × 逐日 × 成员)、只读回显、整份撤回、声明整团免车,共 4 个新端点;新增错误码 809100-809115(16 个码位,实占 13 个,2 个保留未用,1 个已停用)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
1. **团期管理员从此可以在需求页维护一份"正式"团级用车需求**——按乘车分组(如"大巴 A 组")× 逐日 × 成员三层结构整份提交,与定制师逐户所报的《用车需求》是**两份不同的数据**:既有的 `GET .../requirement-summary` 是定制师逐户所报的汇总视图(只读、未改),本单新增的这四个端点维护的是团期管理员按人数形成的正式团级需求,两者须在页面上**并列展示**,不能互相覆盖。
|
||||
2. **新增「本团无需用车」声明按钮**(`waive`),带原因必填;撤销走同一个 `withdraw` 端点,不另开撤销按钮。这是免车状态的**唯一运营可达入口**——此前该状态只能靠手工改库造出。
|
||||
3. **`waive`/`withdraw` 的失败提示三个新码**:`809113`(正式需求仍有分组)在当前实现中**已不会被抛出**(2026-09-15 换版逻辑上线后,带分组也能直接免车,见下文业务边界);`809114`(车务已开工,不能再声明免车)、`809115`(当前处于免车态,提交带分组前须先撤回)仍会触发,人话提示见「六.5 枚举」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`#7439` 交付了车侧地基(三层表、`requirement_kind` 分流、`GroupVehicleRequirementStatus` 枚举);`#7445` 交付了结算侧的团期用车户级闸门(当时是软预警)。本单在这两者基础上,给团期管理员一个真正可编辑、可审核留痕的"正式团级用车需求"入口,取代此前"逐户所报即定案"的隐式状态。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 保存团期正式用车需求 | PUT | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 新增 | 全量替换整份(分组 × 逐日 × 成员),乐观锁版本控制 |
|
||||
| 2 | 读团期正式用车需求 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 新增 | 只读回显,未形成正式需求时返回 `data: null` |
|
||||
| 3 | 整份撤回正式用车需求 | POST | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/withdraw` | 新增 | CONFIRMED 或 DISPATCHED 回 DRAFT,已配车辆不动 |
|
||||
| 4 | 声明整团无需用车 | POST | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive` | 新增 | 零分组已确认正式需求,撤销走 `withdraw` |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 保存团期正式用车需求 `PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement`
|
||||
|
||||
**VO**: `GroupVehicleRequirementSaveReqVO → Result<GroupVehicleRequirementRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期需求页"正式用车需求"编辑区点保存时调用。**全量替换**语义:本次提交里没出现的分组会被移出当前版本。首次保存 `version` 传 `null`,之后每次提交必须回传上次 `GET`/`PUT` 拿到的 `version` 做乐观锁。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
| version | Body | Integer | 否 | 首次传 null | 乐观锁版本号 |
|
||||
| remark | Body | String | 否 | ≤500 字符 | 整份备注 |
|
||||
| groups | Body | List<GroupItem> | 是(可空数组) | `@NotNull`,**不是** `@NotEmpty` | 全部乘车分组;空数组是合法提交(团里没有需车户时) |
|
||||
| groups[].groupId | Body | Long | 否 | 新增分组传 null | 既有分组主键;带上它则 `groupCode` 不得变更 |
|
||||
| groups[].groupCode | Body | String | 是 | ≤32 字符 | 分组键,直接作为车费分摊分组;不可改名 |
|
||||
| groups[].vehicleType | Body | String | 是 | ≤64 字符 | 车型文本/字典值,不设档位枚举 |
|
||||
| groups[].serviceStartDate | Body | LocalDate | 是 | - | 本组服务开始日 |
|
||||
| groups[].serviceEndDate | Body | LocalDate | 是 | 不早于开始日 | 本组服务结束日 |
|
||||
| groups[].days | Body | List<DayItem> | 是 | `@NotEmpty` | 逐日用车明细 |
|
||||
| groups[].days[].tripDate | Body | LocalDate | 是 | 须落在本组服务日范围内 | 团期行程日 |
|
||||
| groups[].days[].headcount | Body | Integer | 是 | `@Min(1)`,须 ≥ 当日成员户数 | 该组该日用车人数(乘车人数,非户数) |
|
||||
| groups[].days[].memberOrderIds | Body | List<Long> | 是 | `@NotEmpty`,须全属本团在团户 | 该组该日实际乘车的子订单集合 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | Long(String) | 正式需求主键 |
|
||||
| groupBatchId | Long(String) | 团期聚合主键 |
|
||||
| status | String | 保存后恒为 `DRAFT` |
|
||||
| version | Integer | 保存后版本号,下次提交须回传 |
|
||||
| remark | String | 整份备注 |
|
||||
| confirmedBy | String | 整份确认人(`DRAFT` 时为 null) |
|
||||
| confirmedAt | LocalDateTime | 整份确认时间(`DRAFT` 时为 null) |
|
||||
| groups[] | List<GroupItem> | 全部乘车分组回显(整团免车态为空数组) |
|
||||
| groups[].groupId | Long(String) | 分组主键,下次提交同一组须回传 |
|
||||
| groups[].groupCode/vehicleType/serviceStartDate/serviceEndDate | - | 回显,同入参 |
|
||||
| groups[].days[].tripDate/headcount/memberOrderIds | - | 回显,同入参 |
|
||||
| groups[].days[].memberOrderCount | Integer | 当日成员**户数**(= memberOrderIds.size()),供填 headcount 时对照,避免把户数当人数填 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 3,
|
||||
"remark": "9/13 起换大巴",
|
||||
"groups": [
|
||||
{
|
||||
"groupId": null,
|
||||
"groupCode": "A",
|
||||
"vehicleType": "35座大巴",
|
||||
"serviceStartDate": "2026-10-20",
|
||||
"serviceEndDate": "2026-10-24",
|
||||
"days": [
|
||||
{ "tripDate": "2026-10-20", "headcount": 9, "memberOrderIds": [2099463556918386689, 2099463556951941123] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-14 21:40:18,见 testserver/LEDGER.md G5 AC5-1)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"requirementId": "2099493442663985153",
|
||||
"groupBatchId": "2099463556951941122",
|
||||
"status": "DRAFT",
|
||||
"version": 1,
|
||||
"remark": "G5-AC5-step1-成功",
|
||||
"confirmedBy": null,
|
||||
"confirmedAt": null,
|
||||
"groups": [
|
||||
{
|
||||
"groupId": "2099493442672373761",
|
||||
"groupCode": "A",
|
||||
"vehicleType": "35座大巴",
|
||||
"serviceStartDate": "2026-10-20",
|
||||
"serviceEndDate": "2026-10-24",
|
||||
"days": [
|
||||
{ "tripDate": "2026-10-20", "headcount": 9, "memberOrderIds": ["2099463556918386689"], "memberOrderCount": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
不涉及空态:本端点是写操作,要么整份成功、要么整份零写入并抛错误码。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "groups": [] } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 网关取证 |
|
||||
|----|------|------|------|
|
||||
| 589500 | GROUP_BATCH_NOT_FOUND | 团期不存在 | 源码走查 |
|
||||
| 589501 | GROUP_BATCH_STATUS_INVALID | 团期阶段不可编辑 | 源码走查 |
|
||||
| 809101 | GROUP_VEHICLE_REQUIREMENT_STATUS_INVALID | 活跃正式需求不在 DRAFT,且不处于免车确认态 | 有 |
|
||||
| 809102 | GROUP_VEHICLE_REQUIREMENT_VERSION_CONFLICT | 提交版本与当前版本不一致 | 有 |
|
||||
| 809103 | GROUP_VEHICLE_REQUIREMENT_NO_GROUP | 本团存在需要用车的子订单,却零分组 | 有 |
|
||||
| 809104 | GROUP_VEHICLE_GROUP_CODE_INVALID | 分组键重复或试图改名 | 有 |
|
||||
| 809105 | 日期越界 | 逐日行不在本组服务日范围内或重复提交 | 有 |
|
||||
| 809106 | 日期缺失 | 分组缺少某日的用车人数 | 有 |
|
||||
| 809107 | 成员不属本团 | 子订单不属于本团期,不能作为乘车成员 | 有 |
|
||||
| 809108 | 成员重复归属 | 子订单同一日同时属于两个分组 | 有 |
|
||||
| 809109 | 成员未覆盖 | 子订单某日没有被任何乘车分组覆盖 | 有 |
|
||||
| 809110 | 人数不足 | 分组当日用车人数小于当日成员户数 | 有 |
|
||||
| 809115 | GROUP_VEHICLE_WAIVED_GROUP_SAVE_REJECTED | 当前处于免车确认态,提交带分组前须先 withdraw | 源码走查,无测试服网关取证 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809102,
|
||||
"message": "正式用车需求已被他人修改(提交版本 2,当前版本 3),请刷新后重试",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
(以上为测试服真实响应,2026-09-14 19:26:29,testserver/LEDGER.md G1 AC3-4-staleR4。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 判权:与整团需求确认/打回同一权限码(`group-batch:demand:confirm`),不另设专属码——四个端点动的是同一个 Tab 的同一份数据。
|
||||
- 幂等:`@Idempotent`(key 为 groupBatchId)挡 5 秒内双击;锁:与 `withdraw`/`waive`/`confirm`/`reject` 共用团级需求锁,同一团期同一时刻只有一个写操作能进。
|
||||
- 顺序不可调换:①团期阶段守卫 → ②免车态守卫(809115)→ ③源状态守卫(809101)→ ④乐观锁比对(809102,此时尚无任何写入)→ ⑤分组改名守卫(809104)→ ⑥六条逐日校验(809103/809105-809110)→ ⑦失活旧版本 → ⑧插新版本。前六步一律零写入。
|
||||
- `groups` 允许空数组:团里没有任何需要用车的户时,一个分组都不提交是合法提交;809103 只在"有在团需车户却零分组"时才抛。整团不需要用车请改用 `waive` 端点,那条路径带确认留痕与可撤回性。
|
||||
- 与既有 `GET .../requirement-summary`(定制师逐户所报,未改)是**两份不同数据**,前端须并列展示,不得混用或互相覆盖。
|
||||
|
||||
---
|
||||
|
||||
### 2. 读团期正式用车需求 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement`
|
||||
|
||||
**VO**: `无请求体 → Result<GroupVehicleRequirementRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员编辑页回填。只读、零副作用,可任意重复调用。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | Long(String) | 正式需求主键(同「三、1」出参,结构不重复列出) |
|
||||
| groupBatchId | Long(String) | 团期聚合主键 |
|
||||
| status | String | DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED |
|
||||
| version | Integer | 版本号 |
|
||||
| remark | String | 整份备注 |
|
||||
| confirmedBy/confirmedAt | String/LocalDateTime | 整份确认人/时间 |
|
||||
| groups[] | List<GroupItem> | 全部乘车分组,字段同「三、1」出参表 |
|
||||
| data | GroupVehicleRequirementRespVO | **该团期尚未形成正式需求时整个 data 为 null**(不抛业务错误码)——编辑页首次打开是正常场景 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2099463556951941122/vehicle-requirement HTTP/1.1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-14 22:11:15,testserver/LEDGER.md G6 step4-ready-get)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"requirementId": "2099493442663985153",
|
||||
"groupBatchId": "2099463556951941122",
|
||||
"status": "DRAFT",
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{ "groupCode": "A", "vehicleType": "35座大巴", "serviceStartDate": "2026-10-20", "serviceEndDate": "2026-10-24" },
|
||||
{ "groupCode": "B", "vehicleType": "35座大巴", "serviceStartDate": "2026-10-20", "serviceEndDate": "2026-10-22" }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": null, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 网关取证 |
|
||||
|----|------|------|------|
|
||||
| 589500 | GROUP_BATCH_NOT_FOUND | 团期不存在 | 源码走查 |
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 判权同「三、1」。
|
||||
- 与 `PUT` 响应体形状完全一致,前端可用同一套渲染逻辑处理保存回显与回填。
|
||||
|
||||
---
|
||||
|
||||
### 3. 整份撤回正式用车需求 `POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/withdraw`
|
||||
|
||||
**VO**: `GroupVehicleRequirementWithdrawReqVO → Result<GroupVehicleRequirementRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员把已确认(`CONFIRMED`/`DISPATCHED`)的正式需求整份撤回,退回 `DRAFT` 重新编辑。**已配车辆不动**——本端点不调用任何 fleet 释放,也不打回户级完成态。撤销「本团无需用车」声明同样走本端点,不另开撤销按钮。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
| reason | Body | String | 是 | ≤200 字符 | 撤回原因,追加写入整份备注留痕(不覆盖原备注) |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | Long(String) | 正式需求主键 |
|
||||
| groupBatchId | Long(String) | 团期聚合主键 |
|
||||
| status | String | 撤回后恒为 DRAFT |
|
||||
| version | Integer | 已 +1 |
|
||||
| remark | String | 追加了本次撤回留痕 |
|
||||
| confirmedBy/confirmedAt | String/LocalDateTime | 已清空,均为 null |
|
||||
| groups[] | List<GroupItem> | 分组数据保留(撤回不清分组),字段同「三、1」出参表 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "reason": "客户改主意" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-14 19:30:13,testserver/LEDGER.md G2 AC17-3-withdraw)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"requirementId": "2099460678996697090",
|
||||
"groupBatchId": "2099460543147388930",
|
||||
"status": "DRAFT",
|
||||
"version": 2,
|
||||
"remark": "免车(2026-09-14 19:30):纯自驾 | 撤回(2026-09-14 19:30):客户改主意",
|
||||
"confirmedBy": null,
|
||||
"confirmedAt": null,
|
||||
"groups": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
不涉及空态;不存在活跃正式需求时抛 809100(见错误响应)。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "status": "DRAFT" } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 网关取证 |
|
||||
|----|------|------|------|
|
||||
| 589500 | GROUP_BATCH_NOT_FOUND | 团期不存在 | 源码走查 |
|
||||
| 809100 | GROUP_VEHICLE_REQUIREMENT_NOT_FOUND | 该团期无活跃正式需求 | 源码走查,无测试服网关取证 |
|
||||
| 809101 | GROUP_VEHICLE_REQUIREMENT_STATUS_INVALID | 当前状态不在 `{CONFIRMED, DISPATCHED}`(例如已是 DRAFT) | 有 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809101,
|
||||
"message": "正式用车需求当前状态 DRAFT 不允许本次操作,期望 [DISPATCHED, CONFIRMED]",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
(以上为测试服真实响应,2026-09-14 19:30:20,testserver/LEDGER.md G2 AC17-5-withdraw2;连续两次撤回间隔须 ≥5 秒,否则会先撞上 `@Idempotent` 而不是本条业务码。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **不带团期阶段守卫**:撤回是"已经确认过、要改回去"的纠错动作,冻结期也可用——加阶段守卫会让冻结期的团再也退不回来。
|
||||
- `reason` 追加写入整份备注,不覆盖原备注;整段超过 500 字符时从头部截,本次 `reason` 完整保留。
|
||||
- 撤回后正式需求的分组数据仍在,可再次 `PUT` 编辑;不触发户级完成态回退(户级只在 fleet 真的清零释放时才回退,见另一份 PR-2 内部接口分册的 changelog)。
|
||||
|
||||
---
|
||||
|
||||
### 4. 声明整团无需用车 `POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive`
|
||||
|
||||
**VO**: `GroupVehicleRequirementWaiveReqVO → Result<GroupVehicleRequirementRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员在需求页显式声明"本团整团无需用车"。这是免车状态的**唯一运营可达入口**——改前该状态只能靠手工改库造出。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
| reason | Body | String | 是 | ≤200 字符 | 免车原因,追加写入整份备注留痕 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | Long(String) | 正式需求主键(无活跃需求时为新建行主键;带分组换版时为新版本主键) |
|
||||
| groupBatchId | Long(String) | 团期聚合主键 |
|
||||
| status | String | 成功后恒为 CONFIRMED |
|
||||
| version | Integer | 已 +1(新建行为 1;换版取历史最大 +1) |
|
||||
| remark | String | 追加了本次免车留痕 |
|
||||
| confirmedBy/confirmedAt | String/LocalDateTime | 已落,均非 null |
|
||||
| groups[] | List<GroupItem> | 恒为空数组 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "reason": "纯自驾团,客户自理交通" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-14 19:30:06,testserver/LEDGER.md G2 AC17-1-waive)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"requirementId": "2099460678996697090",
|
||||
"groupBatchId": "2099460543147388930",
|
||||
"status": "CONFIRMED",
|
||||
"version": 1,
|
||||
"remark": "免车(2026-09-14 19:30):纯自驾",
|
||||
"confirmedBy": "1001",
|
||||
"groups": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
已处于免车态时重复调用**幂等成功**(不抛错、不推版本),响应与上次成功响应一致。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "status": "CONFIRMED", "groups": [] } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 网关取证 |
|
||||
|----|------|------|------|
|
||||
| 589500 | GROUP_BATCH_NOT_FOUND | 团期不存在 | 源码走查 |
|
||||
| 589501 | GROUP_BATCH_STATUS_INVALID | 团期不在可免车阶段(招募中/已验团/已取消或流团) | 源码走查 |
|
||||
| 809101 | GROUP_VEHICLE_REQUIREMENT_STATUS_INVALID | 带分组的旧活跃版本不在 `{DRAFT, CONFIRMED}`;或 CAS 落空 | 源码走查,无测试服网关取证 |
|
||||
| 809114 | GROUP_VEHICLE_WAIVE_BLOCKED_BY_DISPATCH | 车务已开工:团级配车已就绪,或任一在团户车需求已进入 `PENDING`/`PROCESSING`/`DONE` | 源码走查,无测试服网关取证 |
|
||||
| ~~809113~~ | ~~GROUP_VEHICLE_WAIVE_HAS_GROUP~~ | ~~正式需求仍有分组~~ | **当前实现不会抛出**,见「业务边界」 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809114,
|
||||
"message": "车务已开工(子订单 2098979230573330434:用车需求已到 PENDING),不能再声明整团免车",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
(该条为按源码模板拼出的示例,非测试服实测原文——本码本轮取证未覆盖。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **带分组也能直接免车**(2026-09-15 定案 b1,PR-3 生效):旧版本状态为 `DRAFT`/`CONFIRMED` 时,`waive` 会失活旧版本(连同其分组,原样留在历史里可查)、插入一条零分组的新版本并直接置 `CONFIRMED`,版本号取历史最大 +1。旧版本自身若已是 `DISPATCHED`/`DONE`/`PENDING_RECONFIRM`,视为车务已按它发出,`809101` 拒绝失活。
|
||||
- 已处于免车态(零分组且状态 ∈ `{CONFIRMED, DISPATCHED, DONE}`)时重复声明**幂等成功、不推版本**。
|
||||
- 阶段守卫比保存草稿宽:除可确认四态外,另放行 `TRIP_FINISHED`/`REVIEWING`——出团前忘了点免车的团,核单时仍可补点(本轮取证未覆盖该放宽本身,仅覆盖了 `RESOURCE_PREPARING` 阶段的免车)。
|
||||
- `809114` 只挡两类:团级 `vehicle_ready` 已置位、或户级车需求已进 `PENDING` 及之后;已知缺口是 fleet 已建团级派车行但上述两条都未触发时,仍可免车,详见另一份 PR-3 changelog 的「业务边界」。
|
||||
- 撤销免车走 `withdraw`,不另开撤销端点。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 正确 / 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| 首次保存 | `version: null` |
|
||||
| 后续保存(正确) | `version` 回传上次 GET/PUT 返回值 |
|
||||
| 改名分组(错误理解) | 提交同一 `groupCode` 不同 `groupId` 达不到改名效果,服务端按"新组"处理;带 `groupId` 且 `groupCode` 变了会抛 809104 |
|
||||
| 整团不需要用车(正确) | 调用 `waive` 端点,不是提交 `groups: []` 的 PUT(后者只在"没有在团需车户"时才合法) |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
`groupId` 是区分"改名"与"删旧建新"的唯一依据:编辑既有分组必须原样回传其 `groupId`;新增分组必须显式传 `null`,不能省略字段。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
`PUT` 全量替换:每次保存都是"失活旧版本 + 插入新版本"(版本号递增),不做增量更新,旧版本连同其分组、逐日行原样保留在历史里可查(历史版本本单没有开放查询端点,仅供后端排障)。`withdraw`/`waive` 同样是原地状态流转(`withdraw`)或换版(`waive` 带分组时),不删除任何历史行。四个端点均不触发跨服务写(不发 Feign/MQ),仅在本服务事务内完成。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 无权限 → 沿用 `group-batch:demand:confirm` 判权
|
||||
- 团期不存在 → 589500
|
||||
- 并发编辑冲突 → 809102(PUT)
|
||||
- 状态机不匹配 → 809101(四个端点通用码,具体触发条件见各自错误响应表)
|
||||
- 老数据兼容:团期首次打开需求页、从未编辑过车需求时,`GET` 返回 `data: null`,不异常
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举
|
||||
|
||||
### 809100-809115(com.hulalv.order.groupbatch.errorcode.GroupVehicleRequirementErrorCode)
|
||||
|
||||
**所属字段**: 各端点错误响应的 `code`/`message` | **类型**: `Integer`/`String`
|
||||
|
||||
| 码 | 符号 | 文案模板 | 抛出端点 | 状态 |
|
||||
|----|------|------|------|------|
|
||||
| 809100 | GROUP_VEHICLE_REQUIREMENT_NOT_FOUND | 团期 {0} 尚未形成正式用车需求 | withdraw | 有抛出点,无网关取证 |
|
||||
| 809101 | GROUP_VEHICLE_REQUIREMENT_STATUS_INVALID | 正式用车需求当前状态 {0} 不允许本次操作,期望 {1} | PUT/withdraw/waive | 有网关取证 |
|
||||
| 809102 | GROUP_VEHICLE_REQUIREMENT_VERSION_CONFLICT | 正式用车需求已被他人修改(提交版本 {0},当前版本 {1}),请刷新后重试 | PUT | 有网关取证 |
|
||||
| 809103 | GROUP_VEHICLE_REQUIREMENT_NO_GROUP | 本团存在需要用车的子订单,至少要提交一个乘车分组 | PUT | 有网关取证 |
|
||||
| 809104 | GROUP_VEHICLE_GROUP_CODE_INVALID | 乘车分组 {0} 重复或试图改名(改名请删除旧组后新增) | PUT | 有网关取证 |
|
||||
| 809105 | 日期越界 | 乘车分组 {0} 的逐日行 {1} 不在本组服务日范围内或重复提交 | PUT | 有网关取证 |
|
||||
| 809106 | 日期缺失 | 乘车分组 {0} 缺少 {1} 的逐日用车人数 | PUT | 有网关取证 |
|
||||
| 809107 | 成员不属本团 | 子订单 {0} 不属于本团期,不能作为乘车成员 | PUT | 有网关取证 |
|
||||
| 809108 | 成员重复归属 | 子订单 {0} 在 {1} 同时属于分组 {2}、{3},同一户同一日只能属于一个分组 | PUT | 有网关取证 |
|
||||
| 809109 | 成员未覆盖 | 子订单 {0} 的 {1} 没有被任何乘车分组覆盖 | PUT | 有网关取证 |
|
||||
| 809110 | 人数不足 | 乘车分组 {0} 在 {1} 的用车人数 {2} 小于当日成员户数 {3} | PUT | 有网关取证 |
|
||||
| 809111 | GROUP_VEHICLE_REQUIREMENT_STAGE_INVALID | 团期当前状态 {0} 不允许编辑或确认正式用车需求 | 无(本组端点均无抛出点,保留给 #7442 复用) | 未启用 |
|
||||
| 809113 | GROUP_VEHICLE_WAIVE_HAS_GROUP | 正式用车需求还有 {0} 个乘车分组,暂不能声明免车(本码已停用) | 无(waive 已改为换版逻辑,不再抛出本码) | 已停用 |
|
||||
| 809114 | GROUP_VEHICLE_WAIVE_BLOCKED_BY_DISPATCH | 车务已开工({0}:{1}),不能再声明整团免车 | waive | 有抛出点,无网关取证 |
|
||||
| 809115 | GROUP_VEHICLE_WAIVED_GROUP_SAVE_REJECTED | 团期 {0} 已声明整团免车,提交乘车分组前请先整份撤回(withdraw)回草稿 | PUT | 有抛出点,无网关取证 |
|
||||
|
||||
(809112 `GROUP_VEHICLE_DISPATCH_CAS_FAILED` 属整团确认端点 `POST .../requirement/confirm` 使用,不属于本单四个端点,见另一份 changelog。)
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 团期需求页新增的 4 个端点,不改任何既有端点的契约。
|
||||
- **零影响**:
|
||||
- `GET .../requirement-summary`(定制师逐户所报汇总)——只读,未改。
|
||||
- `POST .../requirement/confirm`、`GET .../requirement/confirm-check`——整团确认接入车侧属另一份工作(`#7441` PR-4),本单不涉及。
|
||||
- `POST /v3/admin/order/{id}/vehicle-requirement/dispatch`(逐单放行)——本单未改。
|
||||
- `hl-common-*`、`hl-fleet-service`、`hl-gateway` 路由——本单只改 `hl-order-service-v3`,`/v3/admin/**` 路由沿用既有通配。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**取证环境**:order-v3 = dev-v3,主要批次在 `61eb5c4b5`(2026-09-14 21:32:56)与 `79e3b9da7`(2026-09-14 21:59:54,PR-1b)两次部署上取证,均已并入本单最终形态 `7d8cecb3e`(2026-09-15 15:38 部署,含 PR-1~PR-3);部署版本核对留痕见工单 #7441 验收评论。
|
||||
|
||||
四个端点的成功路径(`PUT` 保存、`GET` 回显、`withdraw` 撤回、`waive` 免车)均有测试服真实网关调用记录,示例见「三、接口详情」各节。809101-809110 共 10 个错误码均有网关实测(示例分别见对应端点小节,完整记录见 `testserver/LEDGER.md` G1/G3/G5/G6 四批)。
|
||||
|
||||
**本轮未覆盖的分支(如实列出)**:
|
||||
- 809100(withdraw 无活跃正式需求)——源码走查,无网关取证。
|
||||
- 809114(waive 车务已开工)——源码走查,无网关取证;本码的第二条判据(团级配车已就绪)本身还是代偿实现(读 order-v3 侧标志而非 fleet 侧配车行),见「三、4」业务边界。
|
||||
- 809115(免车态下 PUT 带分组)——源码走查,无网关取证。
|
||||
- `waive` 阶段放宽到 `TRIP_FINISHED`/`REVIEWING`——本轮取证只覆盖了 `RESOURCE_PREPARING` 阶段的免车成功路径,未实测扩展到的两个新阶段。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- 关联 PR: #7696(PR-1)、#7709(PR-1b)
|
||||
- 前置依赖:`#7439`(车侧地基三层表与枚举)、`#7445`(结算侧车侧闸门,本单免车声明是它 584131 硬阻断的跳过条件,见另一份 PR-3 changelog)
|
||||
- 相关分册:`#7441` PR-2 内部接口分册(vehicle-ready/vehicle-ready-reset 团车完成回写)、`#7441` PR-2b/2c(确认行程清单 HOTEL_DONE/VEHICLE_DONE 改动)、`#7441` PR-3(finalize 584131 硬阻断)、`#7441` PR-4(整团确认接车侧,另案)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- **PR**: [#7696](https://git.1814.love:8443/wx/HL/pulls/7696)、[#7709](https://git.1814.love:8443/wx/HL/pulls/7709)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7441"
|
||||
title: "团期用车内部回调——团车配完/清零双向回写户级需求状态(内部接口分册)"
|
||||
consumer: "internal"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "两个端点均为 /v3/internal/** Feign 接口,经网关访问返回 403(JwtAuthFilter 按设计拒绝),取证走直连 order-v3 服务端口(192.168.100.236:8086)+ X-Internal-Token,与 hl-fleet-service 实际调用路径一致。正向(vehicle-ready)与反向(vehicle-ready-reset)均有四次真实调用的完整前后快照(S1-S4,见第八节,来源 pr2ev/EVIDENCE.md 与 pr2ev/ev/025、027、029 三个 json)。被测服务:order-v3 = dev-v3 64c3f72a3(2026-09-15 12:06:01 部署),行为在 870610927(含 PR-3)上未再变化。"
|
||||
updated_at: "2026-09-15"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 团期用车内部回调——团车完成双向回写(内部接口分册)
|
||||
|
||||
> **本文件是分册。** 本单(`#7441`)同批还有 3 份面向前端的 changelog(正式用车需求声明四端点、确认行程清单房车安排短路判定、结算核单车侧闸门升级),本册收件人是后端与 fleet 侧,**不是 mmg**——两个端点均为跨服务 Feign 调用,hl-ui 调不到。拆分依据 `BACKEND_CHANGELOG_DELIVERY_GUIDE.md` 2.5 节:`/v3/internal/*` 必须单独成篇,不许和 admin/mp 接口塞同一份。
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8083)
|
||||
> **消费方**: hl-fleet-service(Feign,`/v3/internal/**`,不经 JWT,靠网关不暴露该前缀做隔离)
|
||||
> **PR**: #7742(PR-2)
|
||||
> **Issue**: #7441
|
||||
> **日期**: 2026-09-15
|
||||
> **影响范围**: 既有内部端点 `POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready` 与 `POST .../vehicle-ready-reset`,路径/参数/响应结构/错误码均未改,新增户级用车需求状态双向回写副作用
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
1. **团级配车完成回调(`vehicle-ready`)从此会连带把在团户的行程用车(TRAVEL)需求状态回写为完成**——改前该回调只置团级 `vehicle_ready=true` 一列,户级需求行一行不动;改后同一次调用内,未经逐户 DAILY_V3 快照完成的在团户,会被批量推到"已完成"状态并同步订单镜像列。
|
||||
2. **团级配车清零回调(`vehicle-ready-reset`)从此会连带把"由团车路径完成"的户打回"处理中"**——只接管由本回调正向产出的那批户,已经由逐户 DAILY_V3 快照完成的户、以及完成来源标识为空的历史行,一律不动。
|
||||
3. **两个端点自身的请求/响应契约完全不变**(`Result<Void>`,团期不存在也返回 200 幂等友好),fleet 侧调用代码无需任何改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
fleet 团级配车完成后经 Outbox → Feign 回调 `vehicle-ready`;团级清零/释放(取消成团、流团、fleet 侧整团释放)后回调 `vehicle-ready-reset`。改前这两个回调只维护团期聚合根自己的 `vehicle_ready` 标志位,户级用车需求(`order_vehicle_requirement`)一行都不碰——全仓唯一把户级需求置完成的生产者是逐户 DAILY_V3 派车快照回调,而团期用车此时已经走团级配车(不逐户派车),于是"整团车已配好、各户需求仍停在待处理",被 `#7445` 的结算闸拦死且无任何运营操作能解开。本单补上这个双向回写。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 回填配车就绪(正向) | POST | `/v3/internal/group-batch/{groupBatchId}/vehicle-ready` | 行为变化 | 新增在团户 TRAVEL 需求批量完成回写,契约不变 |
|
||||
| 2 | 重置配车就绪(反向) | POST | `/v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset` | 行为变化 | 新增"由团车完成"的户打回处理中,契约不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 回填配车就绪(正向) `POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready`
|
||||
|
||||
**VO**: `无请求体 → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
fleet-service 整团配车完成后回调。仅限内部 Feign 调用,不经网关(`JwtAuthFilter` 按设计拒绝 `/v3/internal/**`),fleet 侧走服务发现直连。幂等:重复调用安全。回填成功后内部自动检测四项资源就绪门,满足则推进团期状态。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) |
|
||||
|
||||
(无请求体,不变。)
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | null | 结构完全不变,`Result<Void>` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/internal/group-batch/2099716821799047169/vehicle-ready HTTP/1.1
|
||||
X-Internal-Token: ***
|
||||
```
|
||||
|
||||
(无请求体。测试环境经服务端口直连,生产环境由 fleet-service 经 Feign LB 直连。)
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-15 12:28:19,直连 order-v3:8086,pr2ev/ev/025-T5-vehicle-ready-1.json)
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": null, "traceId": null, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期不存在时仍返回上述成功结构(幂等友好,不抛错),仅记 WARN 日志,不做任何户级回写。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本单不新增错误码。团期不存在按幂等成功处理(见上),不返回错误响应;其余异常沿用全局异常处理,无本单专属错误码。
|
||||
|
||||
```json
|
||||
{ "code": 500, "message": "系统异常,请稍后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 同一事务内先置团级 `vehicle_ready=true`,再取在团户,把 `kind=TRAVEL` 且状态在"待处理/处理中"的 active 需求 CAS 推进为"已完成",并同步订单镜像列;团期状态推进在事务外进行。
|
||||
- **已由逐户 DAILY_V3 快照完成的户跳过、不覆盖**:这类户是更权威的完成来源(带逐日车辆/司机明细),团车回写不覆盖它。
|
||||
- **仍停在"待审核"(配车后才入团、尚未经整团放行)的户跳过并记 WARN,不强推**。
|
||||
- **重复回调幂等**:已完成的户读侧直接跳过、不发 CAS;CAS 命中 0 行(读到未完成后被并发推进)记 WARN 跳过,不抛错。
|
||||
- 团期不存在只记 WARN,不抛错,不影响 Outbox 重投语义。
|
||||
- 本次回写**只改状态与完成来源标识两列,不写任何逐户配车明细列**(不产生逐户车辆/司机数据)——车与司机在团级承担,任何逐户配车行都只可能是历史残留。
|
||||
|
||||
---
|
||||
|
||||
### 2. 重置配车就绪(反向) `POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset`
|
||||
|
||||
**VO**: `无请求体 → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
fleet-service 整团清零、或取消成团/流团释放后回调,把 `vehicle_ready` 重置为与当前有效计划一致的值。仅限内部 Feign 调用,同上不经网关。幂等:重复调用安全。不触发任何团期状态推进(与置位方向相反)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) |
|
||||
|
||||
(无请求体,不变。)
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | null | 结构完全不变,`Result<Void>` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/internal/group-batch/2099716821799047169/vehicle-ready-reset HTTP/1.1
|
||||
X-Internal-Token: ***
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(测试服真实响应,2026-09-15 12:28:20,直连 order-v3:8086,pr2ev/ev/027-T5-vehicle-ready-reset-1.json)
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": null, "traceId": null, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期不存在时仍返回成功结构,仅记 WARN,不做任何户级回写。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本单不新增错误码,异常处理同「三、1」。
|
||||
|
||||
```json
|
||||
{ "code": 500, "message": "系统异常,请稍后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 同一事务内先重置团级 `vehicle_ready=false`,再取在团户逐一按行锁 + 锁内复读判定资格后打回"处理中":**只接管"完成来源标识为团车"且"非逐户 DAILY_V3 契约版本"的户**——这是必要条件,不是可选加强。
|
||||
- **两类户一律跳过、三列一字不改**:①已由逐户 DAILY_V3 快照完成的户;②完成来源标识为空的历史 legacy 完成行(这类行即使表面状态与团车形态相同,也不会被本回调碰)。测试服实测两类对照户在四步(基线 → reset → ready)全程逐列不变。
|
||||
- **反向不清"完成来源"标识列**:只把状态打回"处理中",完成来源标识保留,供下一次团车正向回写复用同一判据(幂等)。
|
||||
- **锁内复读**:判定资格必须在拿到需求行锁之后重新读取,不能用取锁前的内存值判断——避免与并发的逐户 DAILY 回调交错时把刚完成的快照户误判打回。
|
||||
- 团期不存在只记 WARN,不抛错。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
fleet-service 侧调用代码**无需任何改动**——两个端点的路径、方法、请求体(均无)、响应结构(均为 `Result<Void>`)与调用时机(配车完成后调正向、清零释放后调反向)全部不变。本单的行为变化完全由 order-v3 内部消化,不要求消费方感知或配合。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
正向回调新增的写行为:同一事务内,在置团级就绪标志之后,把符合条件的在团户行程用车需求状态由"待处理/处理中"推进为"已完成",同步一列"完成来源"标识,并同步订单侧的用车控制状态镜像列;不写入任何逐户配车明细(车辆/司机/座位等)。反向回调新增的写行为:把"完成来源标识为团车"且"非逐户快照契约"的户由"已完成"打回"处理中",同步镜像列;"完成来源"标识本身不清空。两个方向均在各自既有事务内完成,不额外引入跨服务同步写。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 团期不存在 → 两个端点均只记 WARN,返回成功结构,不抛错(不变)
|
||||
- 已由逐户 DAILY_V3 快照完成的户 → 正向跳过不覆盖,反向跳过不动
|
||||
- 完成来源标识为空的历史 legacy 完成行 → 反向跳过不动(正向不适用,因为该状态本就不是"待处理/处理中")
|
||||
- 仍在"待审核"的户 → 正向跳过并记 WARN,不强推
|
||||
- 重复调用(Outbox 重投)→ 两个方向均幂等,不抛错、不重复产生副作用
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本单不改两个端点的请求/响应字段结构。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 团车配完回调(正向) | 只置团级 vehicle_ready=true,户级需求一行不动 | 同一事务内追加:在团户 TRAVEL 需求批量推进为已完成,同步镜像列 |
|
||||
| 户已由逐户 DAILY_V3 快照完成 | 不适用(正向改前不碰户级) | 跳过、不覆盖 |
|
||||
| 户仍在"待审核" | 不适用 | 跳过并记 WARN,不强推 |
|
||||
| 团车清零回调(反向) | 只置团级 vehicle_ready=false | 同一事务内追加:把"由团车完成"的户打回处理中 |
|
||||
| 逐户 DAILY_V3 快照户 / 历史 legacy 完成行 | 不适用(反向改前不碰户级) | 一律跳过,三列不变 |
|
||||
| 重复回调 | 幂等(改前无户级副作用可言) | 仍幂等:已完成的户直接跳过不发 CAS |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否,端点契约完全不变,fleet 侧零改动。
|
||||
- **前端是否必须同步上线**: 不适用(`frontend_status: not_required`,本册收件人是后端与 fleet 侧,hl-ui 调不到这两个端点)。
|
||||
- **前端 workaround 清理点**: 无。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 两个内部端点在"团车配完/清零"场景下的户级用车需求状态副作用,端点本身契约不变。
|
||||
- **零影响**:
|
||||
- `GET /v3/internal/group-batch/{groupBatchId}/dispatch-baseline`——同 Controller 第三个端点,本单未改。
|
||||
- 逐户 DAILY_V3 派车快照回调链路——完全独立,本单不改其任何代码。
|
||||
- fleet-service 自身代码——零改动,仅消费方数据(户级需求状态)随之变化。
|
||||
- `hl-gateway` 路由——`/v3/internal/**` 本就不上网关,本单不涉及网关配置。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**取证环境**:order-v3 = dev-v3 `64c3f72a3`(2026-09-15 12:06:01 部署),经直连服务端口 `192.168.100.236:8086` + `X-Internal-Token` 调用(与 fleet-service 实际调用路径一致;经 `hl-gateway` 访问 `/v3/internal/**` 返回 403,非本单行为,属既有设计)。
|
||||
|
||||
四组户级快照对照(S1→S4,来源 `pr2ev/EVIDENCE.md`):
|
||||
|
||||
| 快照 | 操作 | 团车户(GV/MIX) | DAILY_V3 快照户(DV) |
|
||||
|---|---|---|---|
|
||||
| S1→S2 | 正向 `vehicle-ready` | 待处理 → 已完成,完成来源写入 | 逐列不变(含 update_time) |
|
||||
| S2→S3 | 反向 `vehicle-ready-reset` | 已完成 → 处理中,完成来源保留 | 仅 update_time 变化(取锁刷新),其余不变 |
|
||||
| S3→S4 | 再次正向 `vehicle-ready` | 处理中 → 已完成,完成来源仍为团车 | 幂等跳过,逐列不变 |
|
||||
|
||||
历史 legacy 完成行(LEG 户,SQL 构造成"已完成+完成来源为空")在同一组四步全程三列逐字不变,反向回调按"跳过并记 WARN"处理,日志打出了具体判定值。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- 关联 PR: [#7742](https://git.1814.love:8443/wx/HL/pulls/7742)
|
||||
- 前置依赖:`#7439`(户级需求表与状态枚举)、`#5319`(vehicle-ready-reset 端点首次交付)、`#3857`(vehicle-ready 端点首次交付)
|
||||
- 相关分册:`#7441` 正式用车需求声明四端点、确认行程清单房车安排短路判定(消费本单产出的完成状态)、结算核单车侧闸门升级(同样消费本单产出的完成状态)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- **PR**: [#7742](https://git.1814.love:8443/wx/HL/pulls/7742)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7441"
|
||||
title: "确认订单前置清单——不需要住宿的订单直接通过 HOTEL_DONE,团车完成路径认 VEHICLE_DONE"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-15"
|
||||
status_note: "HOTEL_DONE 短路(needsHotel=false)与 VEHICLE_DONE 认团车完成路径(isGroupVehicleCompleted)均有测试服真实网关调用前后对照(见第八节,来源 pr2ev/ev/053-F-confirm-checklist-GV.json 与 ac2734ev/ev/001-baseline-checklist-GV.json,同一订单同一端点,PR-2b/2c 部署前后各一次)。被测服务:order-v3 = dev-v3 7d8cecb3e,2026-09-15 15:38 部署(含 PR-1~PR-3)。"
|
||||
updated_at: "2026-09-15"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 确认订单前置清单——房车安排短路判定
|
||||
|
||||
> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3)
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8083)
|
||||
> **PR**: #7753(PR-2b)、#7762(PR-2c)
|
||||
> **Issue**: #7441
|
||||
> **日期**: 2026-09-15
|
||||
> **影响范围**: 既有端点 `GET /v3/admin/order/{id}/confirm-checklist`(及其内部复用的确认行程弹框预览),`items[]` 中 `HOTEL_DONE`/`VEHICLE_DONE` 两项的判定条件变化,响应结构与错误码均未改
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
1. **`needsHotel=false` 的订单,`HOTEL_DONE` 项从此直接通过**——改前该类订单恒得"未提交用房需求",永久卡在"确认订单"这一步走不到结算定稿;本单起与既有的 `needsVehicle=false` 短路同口径。
|
||||
2. **团期用车走"团车"路径完成(而非逐户 DAILY_V3 快照)的订单,`VEHICLE_DONE` 项从此能通过**——改前该类订单即使团车已经配好、订单镜像与需求状态都已是 `DONE`,仍会因为找不到逐户配车记录而恒得"未找到有效配车记录",同样永久卡在确认订单这一步。这是团期用车走团级配车(而不是逐户派车)这一新路径(`#7441` D-C22)与本清单既有判据之间的缺口,本单补上。
|
||||
3. **确认行程弹框预览的司机信息**:团车完成路径的订单不再尝试查逐户配车记录来展示司机/车牌(那本就查不到,展示出来也是误导),预览里该块留空,其余预览字段不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`GET /v3/admin/order/{id}/confirm-checklist` 是"确认订单"前的 5 项前置校验(款项、出行人、住宿、用车、合同方案),本单只涉及其中住宿(`HOTEL_DONE`)与用车(`VEHICLE_DONE`)两项,接口本身在此前已有 changelog 记录(见 `13_4941`/`13_4853` 等既有文档),完整字段与另外三项判据不在本单重复。
|
||||
|
||||
`#7441` 落地了"团车"完成路径(`D-C22`:fleet 团级配车完成后,order-v3 只回写户级需求状态与镜像列为 `DONE`,不落任何逐户配车记录)。`VEHICLE_DONE` 原判据要求"当前 active 需求必须有对应的逐户配车记录",这与团车路径天然矛盾——2026-09-15 测试服取证时发现该缺口(团车完成的订单到不了"确认行程",见发现记录),当场排进本单一并修复;同批顺带修了此前已知的 `needsHotel=0` 卡死问题。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 5 项 checklist 校验(确认订单前置) | GET | `/v3/admin/order/{id}/confirm-checklist` | 判定逻辑修正 | HOTEL_DONE 对 needsHotel=false 短路;VEHICLE_DONE 认团车完成路径 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 5 项 checklist 校验(确认订单前置) `GET /v3/admin/order/{id}/confirm-checklist`
|
||||
|
||||
**VO**: `无请求体 → Result<ConfirmChecklistRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
订单详情页点"确认订单"前调用,5 项全部通过时返回确认预览数据(`allPassed=true`),否则返回逐项失败原因(`allPassed=false`)。本单只影响 `HOTEL_DONE`/`VEHICLE_DONE` 两项的判定条件,接口结构、调用方式不变。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | 是 | - | 订单 ID(不变) |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| allPassed | Boolean | 是否全部通过(唯一开关,不变) |
|
||||
| items | List<ChecklistItemVO> | 5 项详细结果(仅 allPassed=false 时返回,不变) |
|
||||
| items[].code | String | 检查项代码:TRAVELER_COMPLETE/PAYMENT_OK/HOTEL_DONE/VEHICLE_DONE/CONTRACT_TEMPLATE_OK(不变) |
|
||||
| items[].checkName | String | 检查项中文名(不变) |
|
||||
| items[].passed | Boolean | 是否通过(本单:HOTEL_DONE/VEHICLE_DONE 两项的判定条件变化,见业务边界) |
|
||||
| items[].failReason | String | 未通过原因(通过时为 null,不变) |
|
||||
| preview | PreviewVO | 确认行程弹框预览(仅 allPassed=true 时返回,不变) |
|
||||
| preview.driverName/driverPhoneMasked | String | 司机信息;团车完成路径的订单本单起不再查逐户配车记录,恒为 null(其余预览字段不受影响) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2099716821748715521/confirm-checklist HTTP/1.1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
改前(测试服真实响应,2026-09-15 12:30:55,dev-v3 64c3f72a3,pr2ev/ev/053-F-confirm-checklist-GV.json):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"allPassed": false,
|
||||
"items": [
|
||||
{ "code": "PAYMENT_OK", "checkName": "款项校验", "passed": true, "failReason": null },
|
||||
{ "code": "TRAVELER_COMPLETE", "checkName": "出行人信息", "passed": true, "failReason": null },
|
||||
{ "code": "HOTEL_DONE", "checkName": "房型安排", "passed": false, "failReason": "未提交用房需求" },
|
||||
{ "code": "VEHICLE_DONE", "checkName": "用车安排", "passed": false, "failReason": "未找到有效配车记录" },
|
||||
{ "code": "CONTRACT_TEMPLATE_OK", "checkName": "合同方案配置", "passed": false, "failReason": "产品未配置合同方案" }
|
||||
],
|
||||
"preview": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
改后(测试服真实响应,2026-09-15 15:44:23,dev-v3 7d8cecb3e,同一订单,ac2734ev/ev/001-baseline-checklist-GV.json):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"allPassed": false,
|
||||
"items": [
|
||||
{ "code": "PAYMENT_OK", "checkName": "款项校验", "passed": true, "failReason": null },
|
||||
{ "code": "TRAVELER_COMPLETE", "checkName": "出行人信息", "passed": true, "failReason": null },
|
||||
{ "code": "HOTEL_DONE", "checkName": "房型安排", "passed": true, "failReason": null },
|
||||
{ "code": "VEHICLE_DONE", "checkName": "用车安排", "passed": true, "failReason": null },
|
||||
{ "code": "CONTRACT_TEMPLATE_OK", "checkName": "合同方案配置", "passed": false, "failReason": "产品未配置合同方案" }
|
||||
],
|
||||
"preview": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
(该订单其余 3 项判定未变,`allPassed` 此时仍为 false 是因为 `CONTRACT_TEMPLATE_OK` 未过——与本单无关,如实随原始响应带出。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
互斥语义不变:`allPassed=true` 时 `items` 为 null、`preview` 有值;`allPassed=false` 时相反。本单不改这一互斥规则。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "allPassed": true, "items": null } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本单不新增错误码,沿用既有:
|
||||
|
||||
```json
|
||||
{ "code": 404, "message": "订单不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- HOTEL_DONE 判定顺序:0. needsHotel=false 直接通过(本单新增);1. roomControlStatus=DONE 直接通过;2. 镜像非 DONE 且子表无记录,"未提交用房需求";3. 子表最新版本非 DONE,"用房需求未完成"。第 0 步只在 Boolean.FALSE.equals(needsHotel) 时短路,null 仍按"需要配房"从严处理(生产库该列 NOT NULL DEFAULT 0,不会读出 null)。
|
||||
- VEHICLE_DONE 判定顺序:1. needsVehicle=false 直接通过(不变);2. 订单镜像与当前 active 需求必须同时为 DONE(不变);3. 完成来源按序三分支(本单改):① 契约版本 DAILY_V3,校验本地快照合格(不变);② 团车合法缺席(本单新增):非 DAILY_V3 且需求 DONE 且完成来源标识为 GROUP_VEHICLE,直接通过,不要求逐户配车记录;③ 其余仍要求当前 active 需求存在对应逐户配车记录(不变)。
|
||||
- ② 为何必须用正向标识、不能用"非 DAILY_V3 即团车"反推:历史上存在一类逐户完成行,完成来源标识同样为空,与团车形态在其余列上逐列相同;用排除法会把这批历史订单也一并放行,而这类订单在结算侧仍会被拦(见另一份 PR-3 changelog 的 584100),造成"清单放行、核单拦截"的分叉。测试服实测确认历史形态(LEG 户)仍被本判据正确拦住,"未找到有效配车记录"。
|
||||
- 与结算侧同一份判据、同一处代码来源,保证"清单放行"与"核单不因车侧被拦"结论一致,不会出现两处矛盾。
|
||||
- 老数据兼容:存量订单的 HOTEL_DONE/VEHICLE_DONE 若此前一直显示"未提交需求"类失败,本单合并部署后若满足上述新增短路条件会变为通过——这是修复缺陷,不是引入新的不通过场景。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
本单不改请求参数或响应结构,前端无需修改调用方式。唯一需要知道的是:`HOTEL_DONE`/`VEHICLE_DONE` 从"失败"变为"通过"的订单,此前若因为这两项失败而在前端做过特殊提示或跳转,需要确认该提示不再误触发(正常情况下前端只是照 `items[].passed` 渲染,无需改动)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本端点全程只读,不产生任何写入。本单只改判定条件,不改任何查询语句涉及的表。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 订单不存在 → 404(不变)
|
||||
- needsHotel/needsVehicle 为 null(仅可能出现在极少数历史数据或测试环境)→ 从严按"需要"处理,不短路
|
||||
- 老数据兼容:不改变任何已通过订单的结果,只让此前被误拦的两类订单变为通过
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本单不改字段结构,仅改 `items[].passed`/`items[].failReason` 在特定条件下的取值。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 订单形态 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| needsHotel=false | HOTEL_DONE 恒 false,"未提交用房需求" | HOTEL_DONE 直接 true |
|
||||
| 团车完成路径(需求 DONE + 完成来源 GROUP_VEHICLE,非 DAILY_V3) | VEHICLE_DONE 恒 false,"未找到有效配车记录" | VEHICLE_DONE 直接 true |
|
||||
| 历史 legacy 逐户完成行(需求 DONE,完成来源标识为空,非 DAILY_V3) | VEHICLE_DONE false | 仍为 false(本单未改,与团车形态用完成来源标识区分开) |
|
||||
| DAILY_V3 逐户快照完成 | 按本地快照校验(不变) | 不变 |
|
||||
| needsHotel=true 且用房需求未完成 | HOTEL_DONE false(不变) | 不变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。本单只放宽两项误拦,不收紧任何既有通过条件。
|
||||
- **前端是否必须同步上线**: 否,接口契约不变。若前端此前对这两项失败做过特殊 UI 处理(例如"该订单团车已配好但系统显示未完成,请联系技术"之类的人工绕过提示),可以清理。
|
||||
- **前端 workaround 清理点**: 若存在上述人工绕过提示,可清理;未做特殊处理则零改动。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `GET /v3/admin/order/{id}/confirm-checklist` 的 `HOTEL_DONE`/`VEHICLE_DONE` 两项判定条件与确认预览的司机信息展示。
|
||||
- **零影响**:
|
||||
- `PAYMENT_OK`/`TRAVELER_COMPLETE`/`CONTRACT_TEMPLATE_OK` 三项判定——未改。
|
||||
- `POST /v3/admin/order/{id}/confirm-itinerary`(确认行程,内部复用同一套 checklist 判定)——契约未改,行为随 checklist 联动。
|
||||
- 结算侧车侧闸门(584131/584100)——判据来源同一处代码,但那是另一个端点,另案说明(见另一份 PR-3 changelog)。
|
||||
- hl-common-*、hl-fleet-service、hl-gateway 路由——本单只改 hl-order-service-v3,未新增路由。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**取证环境**:order-v3 = dev-v3,改前批次 `64c3f72a3`(2026-09-15 12:06:01 部署),改后批次 `7d8cecb3e`(2026-09-15 15:38:56 部署,含 PR-1~PR-3);同一订单(orderId=2099716821748715521,团期用车走团车路径,needsHotel=0)在两次部署上各调用一次 `GET .../confirm-checklist`,经 hl-gateway 网关实调,响应对比见「三、1」响应示例。历史 legacy 完成行的对照户(orderId=2099716832188338178)同一时刻仍返回 VEHICLE_DONE=false,证明本单没有连带放宽这类订单,详见 `ac2734ev/ev/002-baseline-checklist-LEG.json`。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- 关联 PR: [#7753](https://git.1814.love:8443/wx/HL/pulls/7753)(PR-2b,VEHICLE_DONE 团车路径)、[#7762](https://git.1814.love:8443/wx/HL/pulls/7762)(PR-2c,HOTEL_DONE 短路)
|
||||
- 既有基线:`13_4941_确认订单checklist删除actionPath-修改接口-管理后台.md`、`13_4853_确认订单预览提交接口-修改接口-管理后台.md`(本单不重复其内容,仅描述本次增量)
|
||||
- 相关分册:`#7441` PR-1(正式用车需求声明四端点)、`#7441` PR-2 内部接口分册(团车完成回写是本单能通过的前提数据来源)、`#7441` PR-3(finalize 584131 硬阻断)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- **PR**: [#7753](https://git.1814.love:8443/wx/HL/pulls/7753)、[#7762](https://git.1814.love:8443/wx/HL/pulls/7762)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7441"
|
||||
title: "完成核单车侧闸门升级为硬阻断——缺行改抛 584131,warnings[].category=VEHICLE 取值退场,免车团整户跳过"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-15"
|
||||
status_note: "finalize 成功路径(含免车团整户跳过车侧、团车完成路径正常结算)与 584100 对照均有测试服真实网关调用记录(第八节,来源 ac2734ev/EVIDENCE.md 与 ac2734ev/ev/*.json)。584131 的两个实际触发分支(户级需求缺失/未完成)在本轮测试服取证中未见网关调用记录,只有源码走查,已在第八节与错误响应表中如实标注;#7445 changelog(11_7445)记录的是本单改造前的旧版软预警取证,不能作为本次硬阻断分支的证据。被测服务:order-v3 = dev-v3 7d8cecb3e,2026-09-15 15:38 部署。"
|
||||
updated_at: "2026-09-15"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 完成核单车侧闸门升级为硬阻断
|
||||
|
||||
> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3)
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8083)
|
||||
> **PR**: #7763(PR-3)
|
||||
> **Issue**: #7441
|
||||
> **日期**: 2026-09-15
|
||||
> **影响范围**: 既有端点 `POST /v3/admin/order/{orderId}/settlement/finalize`(完成核单),团期子订单车侧户级闸门由软预警升级为硬阻断,方法、路径、入参、响应结构均未改
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
1. **团期子订单"用车需求缺失"从软预警升级为硬阻断**——`#7445` 交付时,该场景只在成功响应的 `warnings[]` 里追加一条 `category=VEHICLE` 的提示,`finalize` 仍会成功;本单起改为直接抛业务错误码 `584131`,`finalize` 不再成功。
|
||||
2. **`warnings[].category="VEHICLE"` 这一取值从此不再出现**——`#7445` changelog 曾让前端为它做过渲染容错,本单起该取值退场,不要因为看不到它而报错,也不要把 `584131` 当系统异常展示。
|
||||
3. **整团声明"本团无需用车"(`waive`)的团,该户整户跳过车侧闸门**——`finalize` 照常成功,不判用车需求状态。
|
||||
4. **`waive` 声明的可用阶段放宽到核单中**:出团前忘了点免车的团,在核单阶段(`TRIP_FINISHED`/`REVIEWING`)仍可补点免车(见另一份 PR-1 changelog 的「三、4」)。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`#7445` 交付了团期子订单车侧户级闸门:用车需求存在但未完成时硬阻断(`584131`),需求缺失时因为当时没有任何运营可达的"这个团不要车"的表达,只能降级为软预警,代价是"车根本没提需求"的户照样把钱结掉。`#7441` PR-1 交付了团级免车声明(`waive` 端点)后,"不要车"有了显式、留痕的表达,本单据此把缺行分支也升级为硬阻断,并让免车团整户跳过本闸。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 车侧闸门升级 | 缺行改硬阻断 584131;免车团整户跳过;warnings VEHICLE 取值退场 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 完成核单 `POST /v3/admin/order/{orderId}/settlement/finalize`
|
||||
|
||||
**VO**: `Long(Path 参数 orderId,无请求体) → Result<SettlementSubmitRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台核单页点击"完成核单"按钮时调用,原子完成结算并推订单进终态。调用方式、路径、入参完全不变。团期子订单(经统一判团门面判定)车侧户级闸门本单起行为变化,见下。完整响应字段(`totalAmount`/`roomCost`/`profitAmount` 等结算金额字段)已在 `11_7445_团期用车结算闸-修改接口-管理后台.md` 完整列出,本单不重复,仅列出本次实际变化的部分。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | 是 | 大于等于 1 | 订单 ID(不变) |
|
||||
|
||||
(finalize 本身无请求体,本单未改。)
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| warnings | List<WarningItemVO> | 软预警列表;**`category="VEHICLE"` 这一取值本单起不再出现**(缺行改抛硬错误码,不再走软预警) |
|
||||
| warnings[].category | String | 仍可能出现 `HOTEL`/`TICKET`(既有取值,未改) |
|
||||
| (其余字段:summaryId/finalSnapshotId/totalAmount/roomCost/vehicleCost/profitAmount 等) | - | 结构与语义均未改,完整定义见 `11_7445` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2099716821748715521/settlement/finalize HTTP/1.1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
(无请求体,仅 Path 参数 orderId;本单未改请求形态。)
|
||||
|
||||
#### 响应示例
|
||||
|
||||
团车完成路径、免车团整户跳过:均能成功。以下为团车完成路径成功响应(测试服真实响应,2026-09-15,ac2734ev/EVIDENCE.md AC-27③,`warnings` 未出现 VEHICLE 相关项):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"orderId": "2099716821748715521",
|
||||
"orderStatusAfter": "待财务复核",
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本闸不产生独立的空态/降级响应:`warnings` 在没有任何软预警时为空数组,判定不可达时按错误响应处理,不发生静默降级(不变)。
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "warnings": [] } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 本单 | 网关取证 |
|
||||
|----|------|------|------|------|
|
||||
| 584130 | SETTLEMENT_GROUP_HOTEL_NOT_READY | 住宿闸未过(排在车侧闸之前,未改) | 不变 | 见 11_7445 |
|
||||
| 584131 | SETTLEMENT_GROUP_VEHICLE_NOT_READY | 团期子订单用车未安排完成,或需求缺失且未免车 | **本单起:缺行分支改为硬阻断(改前软预警)** | 源码走查,本轮测试服取证未覆盖实际触发 |
|
||||
| 584100 | FLEET_VEHICLE_FEE_UNAVAILABLE | 历史 legacy 逐户完成行(非团车、非快照来源)过车侧闸后在车费链路被拦 | 未改,本单不动 | 有(测试服真实触发,见第八节) |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584131,
|
||||
"message": "团期子订单 GB202609200007 的用车尚未安排完成(用车需求当前状态:未提交用车需求),请等车务配车完成后再提交核单",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
(该条示例按源码报文模板拼出(`{1}` 占位符在需求缺失分支固定取值"未提交用车需求",见另一份 PR-1 changelog 的「六.5」枚举),非测试服实测原文——本轮取证未覆盖该分支的实际触发。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **闸序不变**:住宿闸(584130)→ 用车闸(584131)→ CASH_PAID 软预警。两闸同时未就绪仍只返回先触发的住宿闸错误码。
|
||||
- **免车判断排在判团之后、查户级需求之前**:免车是团级结论,优先于户级需求状态——免车团的户即便残留一份非"已完成"的需求也照常放行。免车判断使用的 `groupBatchId` 取判团门面解析值,不读订单自身冗余列(`#7083` 回填窗口期该列可能为空)。
|
||||
- **缺行分支升级为硬阻断的前提**:团期处于可免车阶段、且免车未被"车务已开工"守卫拦下时,缺行不再有"只能这样"的合法解释。该前提不成立时(例如团期已结算/已取消,免车阶段守卫拒绝;或车务已开工,免车被拒),缺行仍会把该户卡在本闸,需人工处置——这是已知例外,不是本单的缺陷。
|
||||
- **只判 TRAVEL(行程用车),不判 TRANSFER(接送机)**:结算车费日快照固定按 TRAVEL 取,闸门与账对齐;接送机需求未完成不会拦住本次结算(未改)。
|
||||
- **判定只读户级当前 active 需求的状态,不读车费派生行、不读实配行、不读团级镜像列**——删除派生行、清空实配行都不能绕过闸门(未改)。
|
||||
- **并发与幂等**:判定与后续结算写入在同一 finalize 事务、同一把锁内,不存在 TOCTOU 窗口;本闸是只读判定,不加 `@Idempotent`(未改)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 正确 / 错误 调用结果对照
|
||||
|
||||
| 场景(订单形态) | 结果 |
|
||||
|------|------|
|
||||
| 团期子订单,用车需求"已完成"(正确) | 200 成功,warnings 无车侧项 |
|
||||
| 团期子订单,用车需求存在但未完成(错误) | 584131(改前后行为一致) |
|
||||
| 团期子订单,无 active 用车需求行,团未免车(本单变化点) | 改前:200 成功 + warnings 追加 VEHICLE 软预警;改后:**584131 硬阻断** |
|
||||
| 团期子订单,所在团已声明整团免车(正确) | 200 成功,整户跳过车侧闸,不判需求状态 |
|
||||
| 非团期订单(正确) | 200 成功,本闸不进(未改) |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端无需改动请求参数——finalize 无请求体,本闸完全由后端按订单/团期当前状态判定。前端需要改的是响应解析与提示:不再需要处理 `warnings[].category="VEHICLE"` 这一取值(该分支已改走错误响应,不再出现在成功响应里);`584131` 建议直接展示后端 `message`(已含团号与需求状态或"未提交用车需求"字样)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本单不新增任何数据库写操作。闸门仍是纯只读判定,随 finalize 既有事务执行;若判定不通过(`584131`),finalize 在写入结算数据之前即中止、整个事务回滚,不产生部分写入(未改)。不新增表、不新增列、不写 Flyway、不改索引。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 订单不存在/状态不允许核单 → 沿用 finalize 既有前置校验(未改)
|
||||
- 团期子订单但 `needs_vehicle=0/null` → 直接放行,不查用车需求(未改)
|
||||
- 非团期订单 → 直接放行,不查用车需求(未改)
|
||||
- 用车需求行缺失,团已免车 → 直接放行,不判需求状态(本单新增分支)
|
||||
- 用车需求行缺失,团未免车 → **584131 硬阻断**(本单变化:改前是软预警放行)
|
||||
- 用车需求存在但未完成 → 584131(未改)
|
||||
- 老数据兼容:存量老响应结构不受影响,`warnings[]` 少了一种可能取值属于"取值集合收窄",未识别 `VEHICLE` 的前端旧代码本就不会崩溃,只是从此永远不会再看到它
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `warnings[].category` 可能取值 | HOTEL / TICKET / VEHICLE | HOTEL / TICKET(VEHICLE 退场) |
|
||||
| 错误码集合 | 584130/584131(仅需求未完成分支)/584100 | 584130/584131(需求未完成 **或** 需求缺失且未免车两个分支)/584100 |
|
||||
| 其余响应字段 | 无变化 | 无变化 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前(#7445 交付后) | 改后(本单) |
|
||||
|------|------|------|
|
||||
| 团期子订单 + needs_vehicle=1 + 无 active 用车需求,未免车 | 200 成功,warnings 追加 category=VEHICLE 软预警 | **584131 硬阻断** |
|
||||
| 团期子订单 + 该团已整团免车 | 按上一行处置(软预警或阻断,取决于是否有需求行) | **整户跳过本闸,warnings 无车侧项,结算照常成功** |
|
||||
| 团期子订单 + 用车需求非"已完成" | 584131 | 不变,仍 584131 |
|
||||
| 团期子订单 + 用车需求"已完成" | 200 成功 | 不变 |
|
||||
| 非团期订单 / needs_vehicle=0 | 200 成功 | 不变,本闸不进 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是。团期子订单在"用车需求缺失且未免车"这一状态下,由"可结算(软预警提示)"变为"584131 阻断"——这正是本单的设计目的,用于堵住"车根本没提需求也能结算"的洞。
|
||||
- **前端是否必须同步上线**: 是。需要新增对 `584131`(若之前只处理需求未完成分支,现在还需处理缺失分支,文案已由后端一并给出)的错误提示;`warnings[]` 渲染逻辑不再需要兼容 `category=VEHICLE`(可保留兼容代码不强制删除,但新场景不会再触发)。
|
||||
- **前端 workaround 清理点**: 若前端此前专门为 `category=VEHICLE` 软预警写过展示逻辑,可以保留(向前兼容,不会报错)也可以清理(该取值不会再出现)。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `POST /v3/admin/order/{orderId}/settlement/finalize` 在团期子订单"用车需求缺失"这一状态下的错误码与 `warnings[]` 取值集合。
|
||||
- **零影响**:
|
||||
- 核心(非团期)订单的 finalize 行为——完全不变。
|
||||
- `needs_vehicle=0/null` 的存量团期订单——完全不变。
|
||||
- finalize 内其余既有检查(住宿闸 584130、CASH_PAID 软预警、金额汇总写入)——未改动。
|
||||
- `POST /v3/admin/order/{orderId}/settlement/submit` 及分步保存/草稿接口——本闸只挂在 finalize。
|
||||
- `hl-fleet-service`、`hl-common-*`、`hl-gateway` 路由——本单只改 `hl-order-service-v3`,未新增路由配置。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**取证环境**:order-v3 = dev-v3 `7d8cecb3e`(2026-09-15 15:38:56 部署)。
|
||||
|
||||
**已有网关取证的场景**:
|
||||
- 团车完成路径子订单 finalize 成功,未出现 `584131`/`584100`/`REPORT_SOURCE_CHANGED`,`order_settlement_vehicle_fee` 该单零行——`ac2734ev/EVIDENCE.md` AC-27③。
|
||||
- 团车/接送机混存子订单 finalize 成功,逐户车费只保留接送机一条——同上 AC-27(乙)。
|
||||
- 历史 legacy 完成行子订单 finalize 抛 `584100`(车费链路拦截,未改行为,作为"团车路径与历史路径待遇不同"的对照)——`ac2734ev/EVIDENCE.md` AC-34④。
|
||||
|
||||
**本轮未覆盖的分支(如实列出)**:
|
||||
- `584131` 的两个实际触发分支(户级需求缺失、需求存在但未完成)本轮测试服取证均未实际触发——已有证据均为"未出现该错误码"的成功路径对照,不是错误响应本身的网关实测。`#7445` 旧版软预警的取证(`11_7445`)覆盖的是改造前的行为,不能替代本次硬阻断分支的证据。
|
||||
- `waive` 声明放宽到 `TRIP_FINISHED`/`REVIEWING` 阶段这一变化,本轮未实测在这两个新增阶段调用 `waive` 补点免车、随后 finalize 放行的完整链路。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- 关联 PR: [#7763](https://git.1814.love:8443/wx/HL/pulls/7763)
|
||||
- 前置/关联依赖:`#7445`(`11_7445_团期用车结算闸-修改接口-管理后台.md`,本单改造的正是它交付的软预警分支,完整响应字段定义见该文)、`#7439`(团车来源在逐户费用链路里的正向标识判据,本单不改此逻辑)
|
||||
- 相关分册:`#7441` 正式用车需求声明四端点(本单免车判据的唯一生产者)、内部接口分册(团车完成回写,本单"用车需求已完成"判据的数据来源)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441)
|
||||
- **PR**: [#7763](https://git.1814.love:8443/wx/HL/pulls/7763)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
在新工单中引用
屏蔽一个用户