比较提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
21b5b5cf01 | ||
|
|
f75c679c9d | ||
|
|
8c44c113f3 | ||
|
|
938ca1702f | ||
|
|
bb3034ac3d | ||
|
|
3fc70f0cf4 | ||
|
|
a20540e6ee | ||
|
|
aa6f8b0897 | ||
|
|
60be944703 | ||
|
|
9bb8004bb1 | ||
|
|
a9f9a4a783 | ||
|
|
a31b427897 | ||
|
|
b676873eec | ||
|
|
f1b8cdf123 | ||
|
|
58c7675b2f | ||
|
|
1a319196ed | ||
|
|
2049120145 | ||
|
|
42fff93c72 | ||
|
|
f88592504e | ||
|
|
f8b70ec7a8 | ||
|
|
d8cb0d6128 | ||
|
|
f96f5302af | ||
|
|
02c4ef0552 | ||
|
|
142f81ffb3 | ||
|
|
c0d07a0db4 | ||
|
|
d8355c3c57 | ||
|
|
abe546707c | ||
|
|
9c4d8eab39 | ||
|
|
c53a6d6875 | ||
|
|
33d0999a68 | ||
|
|
39acbeb0e8 | ||
|
|
12f5dffb07 | ||
|
|
0910cbae7c | ||
|
|
27c7442900 | ||
|
|
9a6edddd38 | ||
|
|
1076c68896 | ||
|
|
5951c3023b | ||
|
|
5b59232713 | ||
|
|
a93d1f7fe6 | ||
|
|
642bed1e69 | ||
|
|
9ff8eb4c8c | ||
|
|
fcb002f662 | ||
|
|
364828e5ee | ||
|
|
3ad9f1ec85 | ||
|
|
da06fa538c | ||
|
|
2608ed2bfa | ||
|
|
fe6b35d235 | ||
|
|
e8b3414967 | ||
|
|
bf138d29d3 | ||
|
|
319cc684ac | ||
|
|
584f0b76ed | ||
|
|
ea6f5bacb1 | ||
|
|
317e71ef52 | ||
|
|
c3f0ebd9fd | ||
|
|
7332ac282a | ||
|
|
62ab4cdb2c | ||
|
|
eb96028029 | ||
|
|
7c7914bd34 | ||
|
|
bb01b978b8 | ||
|
|
7a90d75b5a | ||
|
|
e94e8d7e6e | ||
|
|
493b62dd8c | ||
|
|
8bb947ea99 | ||
|
|
07dbd2b595 | ||
|
|
2bc7571953 | ||
|
|
d197a6c3aa | ||
|
|
5e2c374ab1 | ||
|
|
cb2bbc7d46 | ||
|
|
ce5bac10e6 | ||
|
|
74461331cb | ||
|
|
575111baee | ||
|
|
4a88454172 | ||
|
|
cb3c557702 | ||
|
|
a9fafb8a14 | ||
|
|
e3c0209f5b | ||
|
|
8319198461 | ||
|
|
1e47fb9069 | ||
|
|
3d59972940 | ||
|
|
88fb95dabb | ||
|
|
e6b9c86b0e | ||
|
|
f852024d4a | ||
|
|
98740244ca | ||
|
|
da7c3808c9 | ||
|
|
e8ce58c503 | ||
|
|
86b3f7c799 | ||
|
|
d5941cb36c | ||
|
|
f49d448d1d | ||
|
|
41b7152d8d | ||
|
|
43cc42cecb | ||
|
|
1cb2149e08 | ||
|
|
301a05d5cb | ||
|
|
255427ca51 |
@@ -197,16 +197,7 @@ GET /v3/admin/order/2075415307597357058/payment/manual-receipt/options
|
||||
"channel": "BANK_TRANSFER",
|
||||
"channelText": "银行转账",
|
||||
"allowedPayTypes": ["DEPOSIT", "FULL"],
|
||||
"collectors": [
|
||||
{
|
||||
"collectorType": "COMPANY_ACCOUNT",
|
||||
"collectorId": null,
|
||||
"collectorName": "公司账户",
|
||||
"collectorRole": "COMPANY_ACCOUNT",
|
||||
"collectorRoleText": "公司账户",
|
||||
"defaultSelected": true
|
||||
}
|
||||
]
|
||||
"collectors": []
|
||||
},
|
||||
{
|
||||
"channel": "DRIVER_CASH",
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
# 【修改接口·管理后台】费用明细已收款拆分 (#5022)
|
||||
|
||||
> **PR**: #5025 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-18 00:00
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台订单费用明细需要区分展示已收订金、已收尾款、已收全款。原接口只返回 `paidAmount` 已收总额,无法直接区分不同收款类型。本次在订单费用接口响应 `data` 内新增 3 个拆分金额字段,`paidAmount` 仍表示已收总额。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 订单费用信息 | GET | `/v3/admin/order/{id}/finance` | 修改接口 | 响应 `data` 新增 `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 订单费用信息
|
||||
|
||||
- **使用场景**: 查询单个订单的费用汇总、优惠、加价、退款、线上支付交易明细。
|
||||
- **认证**: 需要管理后台登录态 JWT。
|
||||
- **幂等性**: 查询接口,幂等。
|
||||
- **限流**: 无新增限流规则。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | String | 是 | 订单 ID,路径参数。示例:`2077233785174179841` |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
GET 请求,无请求体。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 顶层响应字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | 业务状态码,`200` 表示成功 |
|
||||
| `message` | String | 响应消息 |
|
||||
| `data` | Object | 订单费用信息 |
|
||||
| `traceId` | String / null | 链路追踪 ID,可能为 `null` |
|
||||
| `success` | Boolean | 请求是否成功 |
|
||||
|
||||
### 5.2 data 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `totalAmount` | String | 订单总金额,金额字符串,单位元 |
|
||||
| `payableAmount` | String | 应付金额,金额字符串,单位元 |
|
||||
| `paidAmount` | String | 已收总额,包含成功线上收款和未撤销线下收款 |
|
||||
| `depositPaidAmount` | String | 新增。实际已收订金金额,成功线上订金 + 未撤销线下订金 |
|
||||
| `balancePaidAmount` | String | 新增。实际已收尾款金额,成功线上尾款 + 未撤销线下尾款 |
|
||||
| `fullPaidAmount` | String | 新增。实际已收全款金额,成功线上全款 + 未撤销线下全款 |
|
||||
| `balanceAmount` | String | 待收余额,金额字符串,单位元 |
|
||||
| `discountAmount` | String | 优惠总额,金额字符串,单位元 |
|
||||
| `surchargeAmount` | String | 加价总额,金额字符串,单位元 |
|
||||
| `refundAmount` | String | 已退金额,金额字符串,单位元 |
|
||||
| `payments` | Array | 线上支付交易明细;仍只表示线上交易,不包含线下收款明细 |
|
||||
| `discounts` | Array | 优惠明细 |
|
||||
| `surcharges` | Array | 加价明细 |
|
||||
|
||||
### 5.3 payments 字段项
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 支付交易 ID |
|
||||
| `paymentNo` | String | 支付流水号 |
|
||||
| `amount` | String | 支付金额,单位元 |
|
||||
| `paymentType` | String | 支付类型 |
|
||||
| `status` | String | 支付状态 |
|
||||
| `paidAt` | String / null | 支付成功时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
> 本次未改变 `payments` 语义:它仍只表示线上支付交易明细。线下收款明细仍通过 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 获取;线下金额已聚合进 `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 和 `paidAmount`。
|
||||
|
||||
### 5.4 discounts 字段项
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 优惠明细 ID |
|
||||
| `name` | String | 优惠名称 |
|
||||
| `amount` | String | 优惠金额,单位元 |
|
||||
| `type` | String | 优惠类型 |
|
||||
| `source` | String | 优惠来源 |
|
||||
| `createdAt` | String | 创建时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
### 5.5 surcharges 字段项
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 加价明细 ID |
|
||||
| `name` | String | 加价名称 |
|
||||
| `amount` | String | 加价金额,单位元 |
|
||||
| `type` | String | 加价类型 |
|
||||
| `createdAt` | String | 创建时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 paymentType
|
||||
|
||||
**所属字段**: `payments[].paymentType` | **类型**: String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `DEPOSIT` | 订金 | 订金支付 |
|
||||
| `BALANCE` | 尾款 | 尾款支付 |
|
||||
| `FULL` | 全款 | 全款支付 |
|
||||
|
||||
### 6.2 status
|
||||
|
||||
**所属字段**: `payments[].status` | **类型**: String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SUCCESS` | 支付成功 | 计入对应已收金额 |
|
||||
| `PENDING` | 待支付 | 不计入对应已收金额 |
|
||||
| `CLOSED` | 已关闭 | 不计入对应已收金额 |
|
||||
| `FAILED` | 支付失败 | 不计入对应已收金额 |
|
||||
|
||||
### 6.3 type
|
||||
|
||||
**所属字段**: `discounts[].type`、`surcharges[].type` | **类型**: String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `EARLY_BIRD` | 早鸟优惠 | 早鸟规则产生的优惠 |
|
||||
| `MANUAL` | 手工调整 | 人工录入的优惠或加价 |
|
||||
| `OTHER` | 其他 | 其他类型 |
|
||||
|
||||
### 6.4 source
|
||||
|
||||
**所属字段**: `discounts[].source` | **类型**: String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `EARLY_BIRD_PLAN` | 早鸟方案 | 来源于早鸟优惠方案 |
|
||||
| `MANUAL` | 手工录入 | 来源于人工录入 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `200` | 成功 | 订单费用信息查询成功 |
|
||||
| `401` | 未登录或登录失效 | 未携带有效管理后台 JWT |
|
||||
| `403` | 无权限 | 当前账号无权访问该订单费用信息 |
|
||||
| `404` | 订单不存在 | 路径参数 `id` 对应订单不存在 |
|
||||
| `500` | 系统异常 | 服务端处理异常 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233785174179841/finance HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalAmount": "3105.00",
|
||||
"payableAmount": "2955.00",
|
||||
"paidAmount": "2500.00",
|
||||
"depositPaidAmount": "2000.00",
|
||||
"balancePaidAmount": "500.00",
|
||||
"fullPaidAmount": "0",
|
||||
"balanceAmount": "455.00",
|
||||
"discountAmount": "150.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"refundAmount": "0.00",
|
||||
"payments": [],
|
||||
"discounts": [
|
||||
{
|
||||
"id": "2077233785199345666",
|
||||
"name": "早鸟优惠:早鸟-小团减150(适用人群:成人/儿童/小童)",
|
||||
"amount": "150.00",
|
||||
"type": "EARLY_BIRD",
|
||||
"source": "EARLY_BIRD_PLAN",
|
||||
"createdAt": "2026-07-15 11:28:22"
|
||||
}
|
||||
],
|
||||
"surcharges": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**场景说明**: 订单暂无任何成功线上收款和未撤销线下收款时,所有已收拆分金额均返回 0 金额;明细数组可为空数组。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233785174179841/finance HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalAmount": "3105.00",
|
||||
"payableAmount": "2955.00",
|
||||
"paidAmount": "0",
|
||||
"depositPaidAmount": "0",
|
||||
"balancePaidAmount": "0",
|
||||
"fullPaidAmount": "0",
|
||||
"balanceAmount": "2955.00",
|
||||
"discountAmount": "150.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"refundAmount": "0.00",
|
||||
"payments": [],
|
||||
"discounts": [],
|
||||
"surcharges": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**场景说明**: 订单 ID 不存在。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9999999999999999999/finance HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "订单不存在",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用场景**: 管理后台查询订单费用明细时调用,订单存在且当前账号有访问权限。
|
||||
- **不适用场景**: 用该接口获取线下收款明细列表;线下收款明细仍由 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 返回。
|
||||
- **特殊边界**: `payments` 为空不代表订单没有已收金额;可能存在未撤销线下收款,已聚合到本次新增的拆分金额和 `paidAmount`。
|
||||
- **金额口径**: `paidAmount` 为已收总额;`depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 为按收款类型拆分后的已收金额。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.depositPaidAmount` | 不返回 | 返回实际已收订金金额 |
|
||||
| `data.balancePaidAmount` | 不返回 | 返回实际已收尾款金额 |
|
||||
| `data.fullPaidAmount` | 不返回 | 返回实际已收全款金额 |
|
||||
| `data.paidAmount` | 返回已收总额 | 继续返回已收总额,语义不变 |
|
||||
| `data.payments` | 返回线上支付交易明细 | 继续只返回线上支付交易明细,语义不变 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 已收金额拆分 | 只能读取 `paidAmount` 总额 | 可读取订金、尾款、全款 3 类已收金额 |
|
||||
| 线下收款聚合 | `paidAmount` 中包含线下收款,无法按类型拆分 | 线下收款按类型聚合进新增拆分字段和 `paidAmount` |
|
||||
| 线上支付明细 | `payments` 表示线上支付交易明细 | 保持不变,仍不包含线下收款明细 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。本次只新增响应字段,未删除或改名已有字段。
|
||||
- **前端是否必须同步上线**: 否。旧前端继续读取 `paidAmount` 不受影响;需要区分已收订金、已收尾款、已收全款时读取新增字段。
|
||||
- **影响已有数据**: 否。历史订单按成功线上收款和未撤销线下收款聚合返回。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**: 回滚 PR #5025 后,接口不再返回 3 个新增字段。
|
||||
- **回滚后兼容**: 只依赖 `paidAmount` 的旧逻辑不受影响;依赖新增字段的消费方需要兼容字段缺失。
|
||||
- **回滚后清理**: 无需清理前端侧数据。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 是金额字符串,单位元。
|
||||
- 金额为 0 时可能返回 `"0"` 或 `"0.00"`,消费方不要依赖固定小数位判断金额语义。
|
||||
- `payments` 为空时仍可能存在已收金额,因为线下收款不进入 `payments`。
|
||||
- 线下收款明细列表仍由 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 获取。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5022](https://git.1814.love:8443/wx/HL/issues/5022)
|
||||
- **PR**: [#5025](https://git.1814.love:8443/wx/HL/pulls/5025)
|
||||
- **Merge commit**: [aeb2d6d24](https://git.1814.love:8443/wx/HL/commit/aeb2d6d248fcc0fe49a540bfb3b864f06bc00123)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: 腰苏图
|
||||
@@ -0,0 +1,261 @@
|
||||
# 【新增接口·管理后台】核单操作日志查询 (#5038)
|
||||
|
||||
> **PR**: [#5042](https://git.1814.love:8443/wx/HL/pulls/5042) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-18 15:30
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单页新增独立操作日志页签,用于查看当前订单在核单流程中的关键写入动作,包括住宿核单、门票/活动核单、人员费用、补助、返还记录、主报账对账、提交核单和财务确认。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 查询核单操作日志 | GET | `/v3/admin/order/{orderId}/settlement/logs` | 新增接口 | 按订单分页返回核单专用操作日志 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询核单操作日志
|
||||
|
||||
- **使用场景**: 订单详情核单页签内展示核单操作历史。
|
||||
- **认证**: 需要管理后台 JWT。
|
||||
- **幂等性**: 是。GET 查询不产生写入。
|
||||
- **排序**: 按 `operatedAt` 倒序;同一时间按 `id` 倒序。
|
||||
- **请求体**: 无。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `orderId` | string | 是 | 订单 ID。后端按 64 位整数处理,前端按字符串保存和传递。 |
|
||||
|
||||
### 4.2 Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 校验规则 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `page` | number | 否 | `1` | 最小 `1` | 当前页码。 |
|
||||
| `pageSize` | number | 否 | `20` | `1` 到 `100` | 每页条数。 |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
接口返回 `Result<PageResult<RecordVO>>`。
|
||||
|
||||
### 5.1 顶层响应字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | number | 状态码,成功为 `200`。 |
|
||||
| `message` | string | 响应消息,成功为 `成功`。 |
|
||||
| `data` | object | 分页数据。 |
|
||||
| `traceId` | string \| null | 链路追踪 ID。 |
|
||||
| `success` | boolean | 是否成功。 |
|
||||
|
||||
### 5.2 `data` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `records` | array | 操作日志记录列表。无日志时为空数组。 |
|
||||
| `total` | number | 总记录数。 |
|
||||
| `page` | number | 当前页码。 |
|
||||
| `pageSize` | number | 每页条数。 |
|
||||
|
||||
### 5.3 `records[]` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | string | 日志 ID。 |
|
||||
| `orderId` | string | 订单 ID。 |
|
||||
| `operationType` | string | 操作类型编码,见第 6 节。 |
|
||||
| `operationTypeName` | string | 操作类型中文名。 |
|
||||
| `operationObject` | string | 操作对象编码,见第 6 节。 |
|
||||
| `operationObjectName` | string | 操作对象中文名。 |
|
||||
| `content` | string | 操作内容。 |
|
||||
| `operatorType` | string | 操作人类型,见第 6 节。 |
|
||||
| `operatorId` | string \| null | 操作人 ID。系统自动操作时可为 `null`。 |
|
||||
| `operatorName` | string | 操作人名称。 |
|
||||
| `operatedAt` | string | 操作时间,格式示例 `2026-07-18 15:15:12`。 |
|
||||
| `beforeSnapshot` | object \| array \| null | 改动前快照。结构随操作对象变化。 |
|
||||
| `afterSnapshot` | object \| array \| null | 改动后快照。结构随操作对象变化。 |
|
||||
| `changeItems` | array | 改动项列表。无差异时为空数组。 |
|
||||
|
||||
### 5.4 `changeItems[]` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `field` | string | 改动字段。非对象快照整体变化时为 `snapshot`。 |
|
||||
| `fieldName` | string | 改动字段展示名。当前与 `field` 同值;整体变化时为 `整体快照`。 |
|
||||
| `beforeValue` | any | 改动前值。 |
|
||||
| `afterValue` | any | 改动后值。 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `operationType`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `SAVE_STEP1` | 保存住宿核单 | 保存 Step1 住宿核单明细时生成。 |
|
||||
| `SAVE_STEP2` | 保存门票/活动核单 | 保存 Step2 门票/活动核单明细时生成。 |
|
||||
| `SAVE_STEP3` | 保存人员费用 | 保存 Step3 人员费用核单时生成。 |
|
||||
| `SAVE_STEP4` | 保存补助 | 保存 Step4 补助时生成。 |
|
||||
| `ADD_REFUND` | 新增返还记录 | 新增 Step5 返还记录时生成;终止行程自动生成返还记录也使用该类型。 |
|
||||
| `DELETE_REFUND` | 删除返还记录 | 删除 Step5 返还记录时生成。 |
|
||||
| `SAVE_RECON` | 保存主报账对账 | 保存主报账对账信息时生成。 |
|
||||
| `SUBMIT` | 提交核单 | Step6 提交核单时生成。 |
|
||||
| `CONFIRM` | 财务确认 | 财务确认结算时生成。 |
|
||||
|
||||
### 6.2 `operationObject`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `HOTEL` | 住宿核单 | 住宿核单相关操作对象。 |
|
||||
| `TICKET` | 门票/活动核单 | 门票或活动核单相关操作对象。 |
|
||||
| `STAFF_FEES` | 人员费用 | 司机、导游、摄影或其他人员费用。 |
|
||||
| `SUBSIDY` | 补助 | 补助核单相关操作对象。 |
|
||||
| `REFUND` | 返还记录 | 返还记录相关操作对象。 |
|
||||
| `RECON` | 主报账对账 | 主报账对账相关操作对象。 |
|
||||
| `SETTLEMENT` | 核单结算 | 提交核单或财务确认等整体结算操作对象。 |
|
||||
|
||||
### 6.3 `operatorType`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `ADMIN` | 管理后台用户 | 管理后台人工操作。 |
|
||||
| `SYSTEM` | 系统自动 | 定时任务或内部流程自动触发。 |
|
||||
| `USER` | C 端用户 | 当前核单日志一般不使用,保留统一操作人类型。 |
|
||||
| `MQ` | 消息回调 | 当前核单日志一般不使用,保留统一操作人类型。 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| `200` | 成功 | 查询成功,含无日志空列表。 |
|
||||
| `400` | 参数校验失败 | `page < 1`、`pageSize < 1`、`pageSize > 100`,或 `orderId` 无法解析为整数。 |
|
||||
| `401` | 未认证 | 未携带有效管理后台 JWT。 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233855281971202/settlement/logs?page=1&pageSize=20
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2078378034477375490",
|
||||
"orderId": "2077233855281971202",
|
||||
"operationType": "SAVE_STEP4",
|
||||
"operationTypeName": "保存补助",
|
||||
"operationObject": "SUBSIDY",
|
||||
"operationObjectName": "补助",
|
||||
"content": "保存补助",
|
||||
"operatorType": "ADMIN",
|
||||
"operatorId": "1001",
|
||||
"operatorName": "admin",
|
||||
"operatedAt": "2026-07-18 15:15:12",
|
||||
"beforeSnapshot": [],
|
||||
"afterSnapshot": [],
|
||||
"changeItems": []
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:暂无日志
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2076236236812345346/settlement/logs?page=1&pageSize=20
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 异常:未登录
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233855281971202/settlement/logs?page=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "缺少有效 Authorization 头",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 该接口只查询核单专用操作日志,不返回订单状态日志、支付流水、调整订单记录或房车操作日志。
|
||||
- 无核单日志时返回空分页,不视为异常。
|
||||
- `beforeSnapshot`、`afterSnapshot` 的内部字段随操作对象变化,前端应把它们作为 JSON 快照展示或按对象类型做兼容解析。
|
||||
- `changeItems` 只表达快照层面的差异;数组类快照整体变化时可能只返回一条 `field=snapshot` 的整体改动项。
|
||||
- 查询接口本身不会生成日志;日志由对应核单写入动作生成。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口,无历史接口可对比。
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**: 否,新增接口。
|
||||
- **前端是否必须同步上线**: 否。不接入该接口时只是不展示核单操作日志页签。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 前端展示长整型 ID 时按字符串处理,避免精度丢失。
|
||||
- 前端不要根据 `operationTypeName` 或 `operationObjectName` 反推状态;需要判断类型时使用编码字段。
|
||||
- 空列表是合法状态,适用于未开始核单或日志功能上线前的历史订单。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5038](https://git.1814.love:8443/wx/HL/issues/5038)
|
||||
- **PR**: [#5042](https://git.1814.love:8443/wx/HL/pulls/5042)
|
||||
- **Merge commit**: [176405d](https://git.1814.love:8443/wx/HL/commit/176405d489df22a184e848df88a64fe104a6672f)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
- **前端对接**: 管理后台前端
|
||||
@@ -0,0 +1,319 @@
|
||||
# 【新增接口·管理后台】预支审批列表接口 (#5041)
|
||||
|
||||
> **PR**: #5044 / #5046 | **服务**: hl-order-service-v3 + hl-user-service | **更新时间**: 2026-07-18 15:40
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台需要在财务菜单下独立查看待审批、已通过、已驳回的订单预支记录。此前预支记录只能从订单详情上下文查看,财务人员缺少全局审批列表入口。
|
||||
|
||||
本次新增全局分页查询接口,并新增菜单入口:
|
||||
|
||||
- 菜单目录:`财务管理`
|
||||
- 菜单名称:`预支审批`
|
||||
- 菜单路由:`advance-approvals`
|
||||
- 前端组件:`finance/AdvanceApprovalList`
|
||||
- 列表权限:`order:advance-approval:page`
|
||||
- 通过按钮权限:`order:advance-approval:approve`
|
||||
- 驳回按钮权限:`order:advance-approval:reject`
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 分页查询预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 新增接口 | 按审批状态分页查询预支记录,并返回订单摘要字段 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 分页查询预支审批列表
|
||||
|
||||
- **使用场景**:财务人员进入“财务管理 / 预支审批”页面时查询预支审批列表。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **权限点**:`order:advance-approval:page`。
|
||||
- **幂等性**:是。该接口只读,不修改数据。
|
||||
- **排序**:按 `submittedAt` 倒序,其次按 `createTime` 倒序,再按 `id` 倒序。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 说明 | 校验规则 |
|
||||
|------|------|------|--------|------|----------|
|
||||
| `page` | Integer | 否 | `1` | 页码 | 最小值 `1` |
|
||||
| `pageSize` | Integer | 否 | `20` | 每页条数 | 最小值 `1`,最大值 `100` |
|
||||
| `status` | String | 否 | `SUBMITTED` | 审批状态 | 可传 `SUBMITTED` / `APPROVED` / `REJECTED`;大小写不敏感,后端会转大写 |
|
||||
| `orderId` | String | 否 | 无 | 订单 ID 精确筛选 | 雪花 ID,前端按字符串处理 |
|
||||
| `keyword` | String | 否 | 无 | 订单关键字 | 模糊匹配订单号、团号、产品名 |
|
||||
| `payeeName` | String | 否 | 无 | 收款人姓名 | 模糊匹配 |
|
||||
| `createdByName` | String | 否 | 无 | 申请人姓名 | 模糊匹配 |
|
||||
| `submittedAtFrom` | String | 否 | 无 | 提交时间开始 | 格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||
| `submittedAtTo` | String | 否 | 无 | 提交时间结束 | 格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体。
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"traceId": "可选链路追踪ID",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 `data` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `records` | Array | 当前页记录列表 |
|
||||
| `total` | Integer | 总记录数 |
|
||||
| `page` | Integer | 当前页码 |
|
||||
| `pageSize` | Integer | 每页条数 |
|
||||
|
||||
### 5.3 `records[]` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 预支 ID |
|
||||
| `orderId` | String | 订单 ID |
|
||||
| `payeeStaffId` | String | 收款人对应的订单人员分配 ID |
|
||||
| `payeeName` | String | 收款人姓名 |
|
||||
| `payeeRole` | String | 收款人角色编码 |
|
||||
| `payeeRoleText` | String | 收款人角色中文 |
|
||||
| `advanceType` | String | 预支类型 |
|
||||
| `amount` | Number | 预支金额 |
|
||||
| `purpose` | String | 用途说明 |
|
||||
| `voucherUrl` | String | 凭证 URL,可为空 |
|
||||
| `status` | String | 预支审批状态编码 |
|
||||
| `statusText` | String | 预支审批状态中文 |
|
||||
| `rejectReason` | String | 驳回原因,仅驳回记录通常有值 |
|
||||
| `createdByName` | String | 申请人姓名 |
|
||||
| `createTime` | String | 创建时间,格式为 ISO 日期时间 |
|
||||
| `submittedAt` | String | 提交审批时间,格式为 ISO 日期时间 |
|
||||
| `approvedAt` | String | 审批时间,通过或驳回后有值 |
|
||||
| `approvedBy` | String | 审批人姓名 |
|
||||
| `orderNo` | String | 订单号 |
|
||||
| `teamNo` | String | 团号 |
|
||||
| `productName` | String | 产品名称 |
|
||||
| `departDate` | String | 出发日期,格式 `yyyy-MM-dd` |
|
||||
| `returnDate` | String | 返程日期,格式 `yyyy-MM-dd` |
|
||||
| `consultantName` | String | 定制师姓名 |
|
||||
| `orderStatus` | String | 订单状态编码 |
|
||||
| `orderStatusName` | String | 订单状态中文 |
|
||||
| `flowStatus` | String | 流程状态编码 |
|
||||
| `flowStatusName` | String | 流程状态中文 |
|
||||
| `payStatus` | String | 支付状态编码 |
|
||||
| `payStatusName` | String | 支付状态中文 |
|
||||
| `settlementStatus` | String | 结算状态编码 |
|
||||
| `settlementStatusName` | String | 结算状态中文 |
|
||||
| `orderAmount` | String | 订单应收金额 |
|
||||
| `paidAmount` | String | 订单已收金额 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `status` / `records[].status`:预支审批状态
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SUBMITTED` | 待审批 | 创建预支后进入待审批状态;默认查询此状态 |
|
||||
| `APPROVED` | 已通过 | 财务审批通过 |
|
||||
| `REJECTED` | 已驳回 | 财务审批驳回,通常带 `rejectReason` |
|
||||
|
||||
### 6.2 `records[].payeeRole`:收款人角色
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `DRIVER` | 司机 | 司机人员 |
|
||||
| `LEADER` | 导游 | 导游人员 |
|
||||
| `PHOTOGRAPHER` | 摄影师 | 摄影人员 |
|
||||
| `OTHER` | 其他 | 其他人员 |
|
||||
|
||||
### 6.3 订单状态类字段
|
||||
|
||||
`orderStatus`、`flowStatus`、`payStatus`、`settlementStatus` 返回系统内已有状态编码;对应中文展示优先使用同记录里的 `orderStatusName`、`flowStatusName`、`payStatusName`、`settlementStatusName`,前端不需要硬编码中文。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `200` | 成功 | 查询成功 |
|
||||
| `585005` | 预支当前状态不允许此操作 | `status` 传入值不在 `SUBMITTED` / `APPROVED` / `REJECTED` 内 |
|
||||
| `400` | 参数校验失败 | `page < 1`、`pageSize < 1`、`pageSize > 100` 或日期格式不符合要求 |
|
||||
| `401` | 未认证 | 未携带有效管理后台 JWT |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:查询待审批列表
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=SUBMITTED HTTP/1.1
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2077000000000000001",
|
||||
"orderId": "2076000000000000001",
|
||||
"payeeStaffId": "2076000000000000101",
|
||||
"payeeName": "张三",
|
||||
"payeeRole": "DRIVER",
|
||||
"payeeRoleText": "司机",
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"amount": 500.00,
|
||||
"purpose": "住宿押金",
|
||||
"voucherUrl": "https://example.test/voucher/advance-001.jpg",
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"rejectReason": null,
|
||||
"createdByName": "腰苏图",
|
||||
"createTime": "2026-07-18T10:20:30",
|
||||
"submittedAt": "2026-07-18T10:20:30",
|
||||
"approvedAt": null,
|
||||
"approvedBy": null,
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "T202607180001",
|
||||
"productName": "草原亲子 3 日游",
|
||||
"departDate": "2026-07-21",
|
||||
"returnDate": "2026-07-23",
|
||||
"consultantName": "腰苏图",
|
||||
"orderStatus": "PENDING_DEPARTURE",
|
||||
"orderStatusName": "待出行",
|
||||
"flowStatus": "PENDING_DEPARTURE",
|
||||
"flowStatusName": "待出行",
|
||||
"payStatus": "PAID",
|
||||
"payStatusName": "已支付",
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未核单",
|
||||
"orderAmount": "3600.00",
|
||||
"paidAmount": "3600.00"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况:无记录
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=20&status=APPROVED&keyword=NO_MATCH_KEYWORD HTTP/1.1
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 异常情况:非法审批状态
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=BAD_STATUS HTTP/1.1
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585005,
|
||||
"message": "预支当前状态不允许此操作",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- `status` 不传时默认查 `SUBMITTED`。
|
||||
- `status` 支持小写或混合大小写,后端统一转大写后校验。
|
||||
- `keyword` 只匹配订单号、团号、产品名。
|
||||
- `payeeName` 只匹配收款人姓名。
|
||||
- `createdByName` 只匹配申请人姓名。
|
||||
- `submittedAtFrom` 与 `submittedAtTo` 都是闭区间过滤条件。
|
||||
- 金额字段中,预支金额 `amount` 为数值;订单金额 `orderAmount`、`paidAmount` 为字符串,前端按字符串展示或转高精度数值处理。
|
||||
- ID 类字段均按字符串处理,避免 JS 数字精度问题。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口,无旧接口对比。
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
新增接口和新增菜单入口,不破坏已有接口契约。
|
||||
|
||||
- **是否破坏向后兼容**:否。
|
||||
- **前端是否必须同步上线**:否;未接入该页面时不影响原订单详情预支能力。
|
||||
- **回滚影响**:回滚后“财务管理 / 预支审批”菜单和列表接口不可用。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 前端页面应挂到 `财务管理 / 预支审批`。
|
||||
- 查询接口只负责列表展示,不执行审批动作。
|
||||
- 审批通过、驳回按钮权限已经随菜单一起下发,按钮可以按权限点控制展示或禁用。
|
||||
- 当前菜单按钮节点 `visible=false`,用于权限控制,不作为侧边栏可见菜单展示。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5041](https://git.1814.love:8443/wx/HL/issues/5041)
|
||||
- **PR**: [#5044](https://git.1814.love:8443/wx/HL/pulls/5044)
|
||||
- **部署修复 Issue**: [#5045](https://git.1814.love:8443/wx/HL/issues/5045)
|
||||
- **部署修复 PR**: [#5046](https://git.1814.love:8443/wx/HL/pulls/5046)
|
||||
- **Merge commit**: [b1ac11c03](https://git.1814.love:8443/wx/HL/commit/b1ac11c030a56a94fd8623f44b5a630fb41f2da9)
|
||||
|
||||
### 13.2 验证记录
|
||||
|
||||
- 测试服 `hl-user-service` 8081/8181 双实例部署成功。
|
||||
- 测试服 `hl-order-service-v3` 8086/8186 双实例部署成功。
|
||||
- 网关实调 `GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=SUBMITTED` 返回 `code=200`、`records=1`。
|
||||
- 网关实调非法 `status=BAD_STATUS` 返回 `code=585005`。
|
||||
- 网关实调 `/admin/menu/my` 已返回“预支审批”菜单和通过/驳回按钮权限。
|
||||
|
||||
### 13.3 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
@@ -0,0 +1,359 @@
|
||||
# 【修改接口·管理后台】核单 Step1 住宿成本字段 (#5043)
|
||||
|
||||
> **PR**: #5047 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-18 16:50
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单 Step1 住宿成本明细需要对齐原型里的酒店资源、房型资源、核算单价、来源和确认状态展示。现有接口保留原路径,在 `GET/PUT /v3/admin/order/{orderId}/settlement/step1` 上做兼容增强。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 查询住宿核算明细 | GET | `/v3/admin/order/{orderId}/settlement/step1` | 修改接口 | `HotelItemVO` 出参新增 10 个字段 |
|
||||
| 2 | 保存住宿核算明细 | PUT | `/v3/admin/order/{orderId}/settlement/step1` | 修改接口 | `HotelItemVO` 入参支持保存酒店/房型资源、单价、来源和确认状态字段 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询住宿核算明细
|
||||
|
||||
- **使用场景**:进入核单 Step1 住宿页签时查询住宿成本明细。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:幂等,只读查询。
|
||||
- **响应结构**:`data` 为 `HotelItemVO[]` 数组。
|
||||
|
||||
### 3.2 保存住宿核算明细
|
||||
|
||||
- **使用场景**:保存核单 Step1 住宿成本明细。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:全量替换保存;同一份 `items` 重复提交后,以最后一次提交为准。
|
||||
- **请求体兼容**:推荐使用 `{ "items": [...] }`;历史数组 body `[...]` 仍兼容。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | string | 是 | 订单 ID,长整型字符串 |
|
||||
|
||||
### 4.2 PUT 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| `items` | array | 是 | 住宿成本明细行数组,全量替换保存 | 不允许为 `null` |
|
||||
| `items[].id` | string | 否 | 已存在行 ID;全量替换保存时可不传 | 长整型字符串 |
|
||||
| `items[].hotelAssignmentId` | string/null | 否 | 配房 assignment ID;手工行可为 `null` | 长整型字符串或 `null` |
|
||||
| `items[].hotelId` | string/null | 否 | 酒店资源 ID;本次新增 | 长整型字符串或 `null` |
|
||||
| `items[].roomTypeId` | string/null | 否 | 房型资源 ID;本次新增 | 长整型字符串或 `null` |
|
||||
| `items[].stayDate` | string | 是 | 入住日期 | `yyyy-MM-dd`,不能早于订单出发日 |
|
||||
| `items[].hotelName` | string | 是 | 酒店名称 | 1-200 字符 |
|
||||
| `items[].roomType` | string/null | 否 | 房型分类或旧展示字段 | 最大 64 字符 |
|
||||
| `items[].roomTypeName` | string/null | 否 | 房型/规格名称;本次新增 | 最大 64 字符 |
|
||||
| `items[].roomCount` | integer | 是 | 总间数 | 正整数 |
|
||||
| `items[].unitPrice` | number/null | 否 | 核算单价,单位元/间夜;本次新增;不传时按 `actualCost / roomCount` 降级计算 | `>= 0` |
|
||||
| `items[].plannedCost` | number | 是 | 计划成本,单位元 | `>= 0` |
|
||||
| `items[].actualCost` | number | 是 | 实际成本,单位元 | `>= 0` |
|
||||
| `items[].paymentMethod` | string | 否 | 付款方式;与 `settleType` 二选一 | `SIGNED` / `COMPANY_PAID` / `CASH_PAID` |
|
||||
| `items[].paymentMethodName` | string | 否 | 付款方式中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||
| `items[].settleType` | string | 否 | 配房结算类型;与 `paymentMethod` 二选一 | `cash` / `sign` / `company` |
|
||||
| `items[].sourceType` | string | 否 | 来源类型;本次新增;不传时按是否有 `hotelAssignmentId` 派生 | `HOUSE_ASSIGNMENT` / `MANUAL` / `TEMPLATE` |
|
||||
| `items[].sourceTypeName` | string | 否 | 来源类型中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||
| `items[].sourceId` | string/null | 否 | 来源业务 ID;本次新增;配房来源默认等于 `hotelAssignmentId` | 长整型字符串或 `null` |
|
||||
| `items[].settlementConfirmStatus` | string | 否 | 核单确认状态;本次新增;不传默认 `CONFIRMED` | `UNCONFIRMED` / `CONFIRMED` |
|
||||
| `items[].settlementConfirmStatusName` | string | 否 | 核单确认状态中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||
| `items[].remark` | string/null | 否 | 备注 | 最大 500 字符 |
|
||||
| `items[].voucherUrls` | array | 否 | 凭证图片 URL 数组 | 字符串数组 |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 GET 响应字段:`HotelItemVO`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string/null | 核单住宿明细行 ID;首次派生未保存的行可为 `null` |
|
||||
| `hotelAssignmentId` | string/null | 配房 assignment ID;手工行可为 `null` |
|
||||
| `hotelId` | string/null | 酒店资源 ID;本次新增 |
|
||||
| `roomTypeId` | string/null | 房型资源 ID;本次新增 |
|
||||
| `stayDate` | string | 入住日期,`yyyy-MM-dd` |
|
||||
| `hotelName` | string | 酒店名称 |
|
||||
| `roomType` | string/null | 房型分类或旧展示字段 |
|
||||
| `roomTypeName` | string/null | 房型/规格名称;本次新增 |
|
||||
| `roomCount` | integer | 总间数 |
|
||||
| `unitPrice` | number/null | 核算单价,单位元/间夜;本次新增 |
|
||||
| `plannedCost` | number | 计划成本,单位元 |
|
||||
| `actualCost` | number | 实际成本,单位元 |
|
||||
| `paymentMethod` | string | 付款方式 |
|
||||
| `paymentMethodName` | string/null | 付款方式中文名;本次新增 |
|
||||
| `settleType` | string/null | 配房结算类型;保存草稿后可能为空 |
|
||||
| `sourceType` | string | 来源类型;本次新增 |
|
||||
| `sourceTypeName` | string/null | 来源类型中文名;本次新增 |
|
||||
| `sourceId` | string/null | 来源业务 ID;本次新增 |
|
||||
| `settlementConfirmStatus` | string | 核单确认状态;本次新增 |
|
||||
| `settlementConfirmStatusName` | string/null | 核单确认状态中文名;本次新增 |
|
||||
| `remark` | string/null | 备注 |
|
||||
| `voucherUrls` | array | 凭证图片 URL 数组 |
|
||||
|
||||
### 5.2 PUT 响应字段:`SettlementHotelSaveRespVO`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `addedIds` | string[] | 本次保存新增的核单住宿明细行 ID 列表 |
|
||||
| `updatedIds` | string[] | 本次保存更新的核单住宿明细行 ID 列表;当前全量替换语义下通常为空数组 |
|
||||
| `deletedIds` | string[] | 本次保存删除的核单住宿明细行 ID 列表;当前返回通常为空数组 |
|
||||
| `totalActualCost` | string | 保存后 Step1 实际成本合计,单位元 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `paymentMethod`
|
||||
|
||||
**所属字段**:`items[].paymentMethod`、`data[].paymentMethod` | **类型**:String | **必填**:否
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SIGNED` | 签单 | 现场签单 |
|
||||
| `COMPANY_PAID` | 公司付款 | 公司统一付款 |
|
||||
| `CASH_PAID` | 现付 | 现场现金或线下现付 |
|
||||
|
||||
### 6.2 `settleType`
|
||||
|
||||
**所属字段**:`items[].settleType`、`data[].settleType` | **类型**:String | **必填**:否
|
||||
|
||||
| 值 | 中文 | 映射后的 `paymentMethod` |
|
||||
|----|------|--------------------------|
|
||||
| `cash` | 现付 | `CASH_PAID` |
|
||||
| `sign` | 签单 | `SIGNED` |
|
||||
| `company` | 公司付款 | `COMPANY_PAID` |
|
||||
|
||||
### 6.3 `sourceType`
|
||||
|
||||
**所属字段**:`items[].sourceType`、`data[].sourceType` | **类型**:String | **必填**:否
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `HOUSE_ASSIGNMENT` | 配房结果 | 来自房务配房结果 |
|
||||
| `MANUAL` | 手工 | 核单手工补充住宿行 |
|
||||
| `TEMPLATE` | 模板 | 模板来源住宿行,当前预留 |
|
||||
|
||||
### 6.4 `settlementConfirmStatus`
|
||||
|
||||
**所属字段**:`items[].settlementConfirmStatus`、`data[].settlementConfirmStatus` | **类型**:String | **必填**:否
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `UNCONFIRMED` | 未确认 | 核单住宿明细未确认 |
|
||||
| `CONFIRMED` | 已确认 | 核单住宿明细已确认;不传时默认该值 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `584002` | 当前核单状态不允许录住宿核单 | PUT 保存时,订单不是「待核单」或「核单中」 |
|
||||
| `584008` | 订单缺出发日期,无法派生 dayNumber | PUT 保存时订单出发日期为空 |
|
||||
| `584009` | `stayDate` 早于订单出发日期 | PUT 保存时日期越界 |
|
||||
| `584062` | 临时行必须指定付款方式 | `paymentMethod` 和 `settleType` 都为空 |
|
||||
| `584064` | 配房记录 `settleType` 字典值非法 | `settleType` 不是 `cash/sign/company` |
|
||||
| `100001` | 参数非法 | 字段格式不符合校验,例如枚举值不在允许范围内、金额小于 0 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:GET 查询
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233855281971202/settlement/step1
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "2077328317421187001",
|
||||
"hotelAssignmentId": "2077233886282088401",
|
||||
"hotelId": "50001",
|
||||
"roomTypeId": "51001",
|
||||
"stayDate": "2026-07-18",
|
||||
"hotelName": "海拉尔海棠酒店",
|
||||
"roomType": "STANDARD",
|
||||
"roomTypeName": "精品标间",
|
||||
"roomCount": 2,
|
||||
"unitPrice": 440.00,
|
||||
"plannedCost": 880.00,
|
||||
"actualCost": 880.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现付",
|
||||
"settleType": "cash",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果",
|
||||
"sourceId": "2077233886282088401",
|
||||
"settlementConfirmStatus": "CONFIRMED",
|
||||
"settlementConfirmStatusName": "已确认",
|
||||
"remark": "已核对",
|
||||
"voucherUrls": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界成功:PUT 保存手工住宿行
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2077233855281971202/settlement/step1
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"hotelAssignmentId": null,
|
||||
"hotelId": null,
|
||||
"roomTypeId": null,
|
||||
"stayDate": "2026-07-18",
|
||||
"hotelName": "临时补充酒店",
|
||||
"roomType": "STANDARD",
|
||||
"roomTypeName": "标准间",
|
||||
"roomCount": 1,
|
||||
"unitPrice": 300.00,
|
||||
"plannedCost": 300.00,
|
||||
"actualCost": 300.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceId": null,
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"voucherUrls": [],
|
||||
"remark": "核单临时补充"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"addedIds": ["2078398065684692001"],
|
||||
"updatedIds": [],
|
||||
"deletedIds": [],
|
||||
"totalActualCost": "300.00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败:缺付款方式
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2077233855281971202/settlement/step1
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"hotelAssignmentId": null,
|
||||
"stayDate": "2026-07-18",
|
||||
"hotelName": "临时补充酒店",
|
||||
"roomType": "STANDARD",
|
||||
"roomCount": 1,
|
||||
"plannedCost": 300.00,
|
||||
"actualCost": 300.00
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584062,
|
||||
"message": "临时行(无配房关联)必须指定 paymentMethod",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- `PUT` 是全量替换保存;前端保存时应提交页面当前完整明细列表。
|
||||
- `hotelAssignmentId` 有值时通常表示配房来源;`hotelAssignmentId` 为空时通常表示手工补充行。
|
||||
- `sourceType` 不传时,`hotelAssignmentId` 有值默认 `HOUSE_ASSIGNMENT`,否则默认 `MANUAL`。
|
||||
- `sourceId` 不传且 `sourceType=HOUSE_ASSIGNMENT` 时,默认使用 `hotelAssignmentId`;其他来源可为 `null`。
|
||||
- `unitPrice` 不传且 `roomCount > 0`、`actualCost` 有值时,返回时会按 `actualCost / roomCount` 保留 2 位小数。
|
||||
- `settlementConfirmStatus` 不传时默认 `CONFIRMED`。
|
||||
- `paymentMethod` 与 `settleType` 二选一;`paymentMethod` 优先,`settleType` 会映射成 `paymentMethod`。
|
||||
- 订单核单状态必须是「待核单」或「核单中」才允许保存 Step1。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| `hotelId` | 无 | 新增,酒店资源 ID |
|
||||
| `roomTypeId` | 无 | 新增,房型资源 ID |
|
||||
| `roomTypeName` | 无 | 新增,房型/规格名称 |
|
||||
| `unitPrice` | 无 | 新增,核算单价 |
|
||||
| `paymentMethodName` | 无 | 新增,付款方式中文名 |
|
||||
| `sourceType` | 无 | 新增,来源类型 |
|
||||
| `sourceTypeName` | 无 | 新增,来源类型中文名 |
|
||||
| `sourceId` | 无 | 新增,来源业务 ID |
|
||||
| `settlementConfirmStatus` | 无 | 新增,核单确认状态 |
|
||||
| `settlementConfirmStatusName` | 无 | 新增,核单确认状态中文名 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 住宿来源展示 | 只能通过 `hotelAssignmentId` 粗略判断 | 返回 `sourceType/sourceTypeName/sourceId` |
|
||||
| 单价展示 | 前端只能根据总价和间数自行推算 | 返回 `unitPrice`,缺失时后端按实际成本和间数降级计算 |
|
||||
| 确认状态展示 | 无独立字段 | 返回 `settlementConfirmStatus/settlementConfirmStatusName` |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。新增字段为兼容性新增,旧字段保留。
|
||||
- **前端是否必须同步上线**:否。旧页面可继续按原字段展示;需要原型新增列时读取新增字段。
|
||||
- **影响已有数据**:历史行新增字段可能为 `null`,前端需要保留空值展示逻辑。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 回滚接口代码后,前端不要再依赖本次新增字段。
|
||||
- 如果页面已使用新增列,回滚期间新增列需要降级为空态展示。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `paymentMethodName`、`sourceTypeName`、`settlementConfirmStatusName` 都是展示字段,保存时可不传。
|
||||
- `roomType` 是旧字段,`roomTypeName` 是本次新增的房型/规格名称;两者可能同时存在。
|
||||
- `settlementConfirmStatus` 是核单明细确认状态,与房务配房确认状态不是同一个字段。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5043](https://git.1814.love:8443/wx/HL/issues/5043)
|
||||
- **PR**: [#5047](https://git.1814.love:8443/wx/HL/pulls/5047)
|
||||
- **Merge commit**: [0a9e83b39](https://git.1814.love:8443/wx/HL/commit/0a9e83b390d135c05b43bc18afe1b190329bf85c)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,306 @@
|
||||
# 【修改接口·管理后台】核单 Step2 景区游玩项目字段 (#5049)
|
||||
|
||||
> **PR**: #5051 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-18 16:40
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单 Step2 的景区/游玩项目核算明细需要展示来源、行程天数、规格/票型、销售单价、销售小计和付款方式中文名。现有接口保留原路径,在原 `GET/PUT /v3/admin/order/{orderId}/settlement/step2` 上做兼容增强。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 查询门票/游玩项目核算明细 | GET | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `TicketItemVO` 出参新增 6 个字段 |
|
||||
| 2 | 保存门票/游玩项目核算明细 | PUT | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `sourceType` 新增 `CUSTOM_ASSIGNMENT`,入参支持保存规格、销售单价、销售小计 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询门票/游玩项目核算明细
|
||||
|
||||
- **使用场景**:进入核单 Step2 景区/游玩项目页签时查询明细。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:幂等,只读查询。
|
||||
- **响应结构**:`data` 为 `TicketItemVO[]` 数组。
|
||||
|
||||
### 3.2 保存门票/游玩项目核算明细
|
||||
|
||||
- **使用场景**:保存核单 Step2 景区/游玩项目核算明细。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:全量替换保存;同一份 `items` 重复提交后,以最后一次提交为准。
|
||||
- **请求体兼容**:推荐使用 `{ "items": [...] }`;历史数组 body `[...]` 仍兼容。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | string | 是 | 订单 ID,长整型字符串 |
|
||||
|
||||
### 4.2 PUT 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| `items` | array | 是 | 门票/游玩项目明细行数组,全量替换保存 | 不允许为 `null` |
|
||||
| `items[].id` | string | 否 | 已存在行 ID;全量替换保存时可不传 | 长整型字符串 |
|
||||
| `items[].sourceType` | string | 是 | 来源类型 | `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `CUSTOM_ASSIGNMENT` |
|
||||
| `items[].sourceTypeName` | string | 否 | 来源类型中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||
| `items[].scenicAssignmentId` | string | 否 | 来源 assignment ID;手工项目传 `null` | 长整型字符串或 `null` |
|
||||
| `items[].dayNumber` | integer | 否 | 行程第几天;保存时以后端根据 `dayDate` 计算后的值为准 | 从 1 开始 |
|
||||
| `items[].dayDate` | string | 是 | 行程日期 | `yyyy-MM-dd`,不能早于订单出发日 |
|
||||
| `items[].scenicName` | string | 是 | 景区/游玩项目名称 | 1-200 字符 |
|
||||
| `items[].specName` | string | 否 | 规格/票型名称 | 最大 128 字符 |
|
||||
| `items[].ticketCount` | integer | 是 | 实际购票数量 | 建议非负整数 |
|
||||
| `items[].ticketUnitPrice` | number | 否 | 参考成本单价,单位元 | 小数 |
|
||||
| `items[].sellPrice` | number | 否 | 客户成交单价,单位元 | `>= 0` |
|
||||
| `items[].totalAmount` | number | 否 | 客户成交小计,单位元;为空且有 `sellPrice` 时后端按 `sellPrice * ticketCount` 降级计算 | `>= 0` |
|
||||
| `items[].plannedCost` | number | 是 | 计划成本,单位元 | `>= 0` |
|
||||
| `items[].actualCost` | number | 是 | 实际成本,单位元 | `>= 0` |
|
||||
| `items[].paymentMethod` | string | 否 | 付款方式;为空时默认 `COMPANY_PAID` | `SIGNED` / `COMPANY_PAID` / `CASH_PAID` |
|
||||
| `items[].paymentMethodName` | string | 否 | 付款方式中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||
| `items[].voucherUrls` | array | 否 | 凭证图片 URL 数组 | 字符串数组 |
|
||||
| `items[].remark` | string | 否 | 备注 | 最大 500 字符 |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 GET 响应字段:`TicketItemVO`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 核单明细行 ID |
|
||||
| `sourceType` | string | 来源类型 |
|
||||
| `sourceTypeName` | string | 来源类型中文名;本次新增 |
|
||||
| `scenicAssignmentId` | string/null | 来源 assignment ID;手工项目为 `null` |
|
||||
| `dayNumber` | integer/null | 行程第几天;本次新增 |
|
||||
| `dayDate` | string | 行程日期,`yyyy-MM-dd` |
|
||||
| `scenicName` | string | 景区/游玩项目名称 |
|
||||
| `specName` | string/null | 规格/票型名称;本次新增 |
|
||||
| `ticketCount` | integer | 实际购票数量 |
|
||||
| `ticketUnitPrice` | number/null | 参考成本单价 |
|
||||
| `sellPrice` | number/null | 客户成交单价;本次新增 |
|
||||
| `totalAmount` | number/null | 客户成交小计;本次新增 |
|
||||
| `plannedCost` | number | 计划成本 |
|
||||
| `actualCost` | number | 实际成本 |
|
||||
| `paymentMethod` | string | 付款方式 |
|
||||
| `paymentMethodName` | string/null | 付款方式中文名;本次新增 |
|
||||
| `voucherUrls` | array | 凭证图片 URL 数组 |
|
||||
| `remark` | string/null | 备注 |
|
||||
|
||||
### 5.2 PUT 响应字段:`SettlementTicketSaveRespVO`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `addedIds` | string[] | 本次保存新增的核单明细行 ID 列表 |
|
||||
| `updatedIds` | string[] | 本次保存更新的核单明细行 ID 列表;当前全量替换语义下通常为空数组 |
|
||||
| `deletedIds` | string[] | 本次保存删除的核单明细行 ID 列表;当前返回通常为空数组 |
|
||||
| `totalActualCost` | string | 保存后 Step2 实际成本合计,单位元 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `sourceType`
|
||||
|
||||
**所属字段**:`items[].sourceType`、`data[].sourceType` | **类型**:String | **必填**:是
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SCENIC_ASSIGNMENT` | 景区 | 景区来源行 |
|
||||
| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 游玩项目来源行 |
|
||||
| `CUSTOM_ASSIGNMENT` | 手工项目 | 本次新增;核单手工补充行,`scenicAssignmentId` 可为 `null` |
|
||||
|
||||
### 6.2 `paymentMethod`
|
||||
|
||||
**所属字段**:`items[].paymentMethod`、`data[].paymentMethod` | **类型**:String | **必填**:否
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SIGNED` | 签单 | 现场签单 |
|
||||
| `COMPANY_PAID` | 公司付款 | 公司统一付款;未传 `paymentMethod` 时默认该值 |
|
||||
| `CASH_PAID` | 现付 | 现场现金/线下现付 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `584011` | 当前核单状态不允许录门票核单 | PUT 保存时,订单不是「待核单」或「核单中」 |
|
||||
| `100001` | 参数非法 | 字段格式不符合校验,例如 `sourceType` 不在允许枚举内、金额小于 0 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:GET 查询
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233855281971202/settlement/step2
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "2077328317421187073",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "景区",
|
||||
"scenicAssignmentId": "2077233886282088450",
|
||||
"dayNumber": 5,
|
||||
"dayDate": "2026-07-18",
|
||||
"scenicName": "呼和诺尔草原旅游区",
|
||||
"specName": null,
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 59.00,
|
||||
"sellPrice": null,
|
||||
"totalAmount": null,
|
||||
"plannedCost": 59.00,
|
||||
"actualCost": 59.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司付款",
|
||||
"voucherUrls": [],
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界成功:PUT 保存手工项目
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2077233855281971202/settlement/step2
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"sourceType": "CUSTOM_ASSIGNMENT",
|
||||
"scenicAssignmentId": null,
|
||||
"dayDate": "2026-07-18",
|
||||
"scenicName": "临时补充游玩项目",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 2,
|
||||
"ticketUnitPrice": 12.34,
|
||||
"sellPrice": 56.78,
|
||||
"totalAmount": 113.56,
|
||||
"plannedCost": 24.68,
|
||||
"actualCost": 24.68,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"voucherUrls": [],
|
||||
"remark": "核单临时补充"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"addedIds": ["2078398065684692994"],
|
||||
"updatedIds": [],
|
||||
"deletedIds": [],
|
||||
"totalActualCost": "24.68"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败:已核单订单禁止保存
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2077233886248534018/settlement/step2
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584011,
|
||||
"message": "当前核单状态为「已核单」,不允许录门票核单,必须为「待核单」或「核单中」",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- `PUT` 是全量替换保存;前端保存时应提交页面当前完整明细列表。
|
||||
- `CUSTOM_ASSIGNMENT` 表示核单手工补充项目,`scenicAssignmentId` 可以为 `null`。
|
||||
- `dayNumber` 保存时以后端根据 `dayDate` 和订单出发日计算的结果为准。
|
||||
- `totalAmount` 为空且 `sellPrice` 有值时,后端会按 `sellPrice * ticketCount` 降级计算。
|
||||
- 未传 `paymentMethod` 时,后端默认使用 `COMPANY_PAID`。
|
||||
- 订单核单状态必须是「待核单」或「核单中」才允许保存 Step2。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| `sourceType` | `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` | 新增 `CUSTOM_ASSIGNMENT` |
|
||||
| `sourceTypeName` | 无 | 新增,返回来源中文名 |
|
||||
| `dayNumber` | 无 | 新增,返回行程第几天 |
|
||||
| `specName` | 无 | 新增,返回/保存规格或票型名称 |
|
||||
| `sellPrice` | 无 | 新增,返回/保存客户成交单价 |
|
||||
| `totalAmount` | 无 | 新增,返回/保存客户成交小计 |
|
||||
| `paymentMethodName` | 无 | 新增,返回付款方式中文名 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 手工补充项目来源 | 只能用既有来源类型兜底表达 | 可明确传 `CUSTOM_ASSIGNMENT` |
|
||||
| 手工项目 assignment ID | 前端容易误以为必须有来源 ID | `CUSTOM_ASSIGNMENT` 下 `scenicAssignmentId` 可为 `null` |
|
||||
| 销售金额展示 | 只能展示成本字段 | 可展示 `sellPrice` / `totalAmount` |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。新增字段为兼容性新增;旧字段继续保留。
|
||||
- **前端是否必须同步上线**:否。旧页面可继续按原字段展示;需要原型新增列时再读取新字段。
|
||||
- **影响已有数据**:不需要前端做数据迁移;历史行新字段可能为 `null`。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 回滚接口代码后,前端不要再依赖 `CUSTOM_ASSIGNMENT` 和新增字段。
|
||||
- 如已保存手工项目,回滚前应确认旧版本是否能识别该来源类型。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 前端不要把 `sourceTypeName`、`paymentMethodName` 当作提交必填项;它们是展示字段。
|
||||
- 前端保存时建议保留并回传用户编辑后的 `specName`、`sellPrice`、`totalAmount`。
|
||||
- 已核单订单保存 Step2 会返回 `584011`,这不是接口异常。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5049](https://git.1814.love:8443/wx/HL/issues/5049)
|
||||
- **PR**: [#5051](https://git.1814.love:8443/wx/HL/pulls/5051)
|
||||
- **Merge commit**: [d60324fde](https://git.1814.love:8443/wx/HL/commit/d60324fde228b50c7ba70d1f39a641e851dc351d)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
@@ -0,0 +1,752 @@
|
||||
# 【新增/修改接口·管理后台】核团核算列表与详情聚合 (#5055)
|
||||
|
||||
> **PR**: [#5058](https://git.1814.love:8443/wx/HL/pulls/5058) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-18 18:20
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核团核算页面需要一个常规产品订单入口列表,并且详情页需要一次性拿到订单信息、出行人、司机车辆、应收构成、线上支付和线下收款记录。此前详情接口字段不完整,前端需要自行合并多个接口;本次把核团入口和详情聚合契约收敛到订单服务接口。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 核团核算任务列表 | GET | `/v3/admin/order-settlement/tasks` | 新增接口 | 查询常规 CORE 产品、无团期批次、已完成订单的核团任务列表 |
|
||||
| 2 | 查询核团详情 | GET | `/v3/admin/order/{orderId}/settlement/return-detail` | 修改接口 | 扩展订单信息、出行人中文枚举、司机车辆集合、应收汇总/明细、支付+线下收款合并记录 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 核团核算任务列表
|
||||
|
||||
- **使用场景**: 核团核算页的常规产品列表。
|
||||
- **认证**: 需要管理后台 JWT。
|
||||
- **幂等性**: 是,只读查询。
|
||||
- **请求体**: 无。
|
||||
- **响应结构**: `Result<PageResult<SettlementTaskRespVO>>`。
|
||||
|
||||
#### 3.1.1 Query 入参
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 校验规则 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `page` | number | 否 | `1` | 最小 `1` | 当前页码 |
|
||||
| `pageSize` | number | 否 | `20` | `1` 到 `100` | 每页条数 |
|
||||
| `keyword` | string | 否 | - | - | 关键词,按订单号、团号、产品名模糊查询 |
|
||||
| `departureDateFrom` | string | 否 | - | `yyyy-MM-dd` | 出发日期开始 |
|
||||
| `departureDateTo` | string | 否 | - | `yyyy-MM-dd` | 出发日期结束 |
|
||||
| `settlementStatus` | string | 否 | - | `NONE` / `PENDING` / `COMPLETED` | 核算状态筛选 |
|
||||
|
||||
#### 3.1.2 响应字段
|
||||
|
||||
顶层统一响应:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | number | 成功为 `200` |
|
||||
| `message` | string | 成功为 `成功` |
|
||||
| `data` | object | 分页数据 |
|
||||
| `traceId` | string/null | 链路追踪 ID |
|
||||
| `success` | boolean | 是否成功 |
|
||||
|
||||
`data` 分页字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `records` | array | 核团任务行列表,空结果返回 `[]` |
|
||||
| `total` | number | 总记录数 |
|
||||
| `page` | number | 当前页码 |
|
||||
| `pageSize` | number | 每页条数 |
|
||||
|
||||
`records[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderId` | string | 订单 ID |
|
||||
| `orderNo` | string | 订单号 |
|
||||
| `teamNo` | string/null | 团号 |
|
||||
| `productName` | string | 产品名称 |
|
||||
| `departureDate` | string/null | 出发日期,格式 `yyyy-MM-dd` |
|
||||
| `returnDate` | string/null | 返团日期,格式 `yyyy-MM-dd` |
|
||||
| `peopleCount` | number | 出行人总数 |
|
||||
| `peopleSummary` | string | 人数文案,如 `2成人2儿童`、`2成人1婴儿`、`0人` |
|
||||
| `systemBalanceAmount` | number | 系统计算待收尾款 |
|
||||
| `settlementStatus` | string | 核算状态 |
|
||||
| `settlementStatusName` | string | 核算状态中文名 |
|
||||
|
||||
列表不返回 `routeName`、`driverName`、`vehiclePlateNo`。
|
||||
|
||||
#### 3.1.3 示例
|
||||
|
||||
典型成功:
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&settlementStatus=COMPLETED
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"orderId": "2077233886248534018",
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "T20260718001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"departureDate": "2026-07-20",
|
||||
"returnDate": "2026-07-24",
|
||||
"peopleCount": 3,
|
||||
"peopleSummary": "2成人1婴儿",
|
||||
"systemBalanceAmount": 0.00,
|
||||
"settlementStatus": "COMPLETED",
|
||||
"settlementStatusName": "已结算"
|
||||
}
|
||||
],
|
||||
"total": 12,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
空结果:
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&keyword=不存在的订单
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 查询核团详情
|
||||
|
||||
- **使用场景**: 点击核团任务后进入返团核算详情页。
|
||||
- **认证**: 需要管理后台 JWT。
|
||||
- **幂等性**: 是,只读查询。
|
||||
- **请求体**: 无。
|
||||
- **响应结构**: `Result<SettlementReturnDetailRespVO>`。
|
||||
|
||||
#### 3.2.1 路径入参
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `orderId` | string | 是 | 长整型字符串,必须大于 `0` | 订单 ID |
|
||||
|
||||
#### 3.2.2 响应字段
|
||||
|
||||
`data` 顶层字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderInfo` | object/null | 订单信息;取消订单可能为空 |
|
||||
| `travelers` | array | 出行人列表,敏感字段已脱敏 |
|
||||
| `driverVehicles` | array | 当前有效司机车辆连续服务区间;无有效派车返回 `[]` |
|
||||
| `receivableSummary` | object/null | 应收汇总 |
|
||||
| `receivableItems` | array | 应收计算明细 |
|
||||
| `collectionSummary` | object/null | 收款汇总 |
|
||||
| `collectionRecords` | array | 线上支付和线下收款合并记录 |
|
||||
|
||||
`orderInfo` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderId` | string | 订单 ID |
|
||||
| `orderNo` | string | 订单号 |
|
||||
| `teamNo` | string/null | 团号 |
|
||||
| `productName` | string | 产品名称 |
|
||||
| `departureDate` | string/null | 出发日期,格式 `yyyy-MM-dd` |
|
||||
| `returnDate` | string/null | 返团日期,格式 `yyyy-MM-dd` |
|
||||
| `peopleCount` | number | 出行人总数 |
|
||||
| `adultCount` | number | 成人数 |
|
||||
| `childCount` | number | 儿童数 |
|
||||
| `youngChildCount` | number | 幼童数 |
|
||||
| `babyCount` | number | 婴儿数 |
|
||||
| `peopleSummary` | string | 人数文案 |
|
||||
| `consultantId` | string/null | 定制师 ID |
|
||||
| `consultantName` | string/null | 定制师姓名 |
|
||||
| `houseStaffId` | string/null | 房务人员 ID |
|
||||
| `houseStaffName` | string/null | 房务人员姓名 |
|
||||
| `fleetStaffId` | string/null | 车务人员 ID |
|
||||
| `fleetStaffName` | string/null | 车务人员姓名 |
|
||||
| `settlementStatus` | string | 核算状态 |
|
||||
| `settlementStatusName` | string | 核算状态中文名 |
|
||||
|
||||
`travelers[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `travelerId` | string | 出行人 ID |
|
||||
| `travelerName` | string | 出行人姓名 |
|
||||
| `travelerType` | string | 出行人类型 |
|
||||
| `travelerTypeName` | string | 出行人类型中文名 |
|
||||
| `idType` | string/null | 证件类型 |
|
||||
| `idTypeName` | string/null | 证件类型中文名 |
|
||||
| `phone` | string/null | 脱敏手机号 |
|
||||
| `idCardNo` | string/null | 脱敏证件号 |
|
||||
|
||||
`driverVehicles[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `driverId` | string/null | 司机 ID |
|
||||
| `driverName` | string/null | 司机姓名 |
|
||||
| `driverPhone` | string/null | 脱敏司机手机号 |
|
||||
| `vehicleId` | string/null | 车辆 ID |
|
||||
| `vehiclePlateNo` | string/null | 车牌号 |
|
||||
| `vehicleModelName` | string/null | 车型名称 |
|
||||
| `seatCount` | number/null | 座位数 |
|
||||
| `startDate` | string | 连续服务开始日期,格式 `yyyy-MM-dd` |
|
||||
| `endDate` | string | 连续服务结束日期,格式 `yyyy-MM-dd` |
|
||||
|
||||
`receivableSummary` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderAmount` | number | 订单基础金额 |
|
||||
| `surchargeAmount` | number | 附加费金额 |
|
||||
| `discountAmount` | number | 优惠金额 |
|
||||
| `payableAmount` | number | 应收总额 |
|
||||
| `formulaText` | string | 应收总额公式文案,固定为 `订单金额 + 附加费 - 优惠 = 应收总额` |
|
||||
|
||||
`receivableItems[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `sourceRecordId` | string/null | 来源记录 ID |
|
||||
| `itemType` | string | 应收项类型 |
|
||||
| `itemTypeName` | string | 应收项类型中文名 |
|
||||
| `itemCode` | string/null | 应收项编码 |
|
||||
| `itemName` | string/null | 应收项名称 |
|
||||
| `direction` | string | 方向,`ADD` 增加应收,`DEDUCT` 减少应收 |
|
||||
| `amount` | number | 金额 |
|
||||
|
||||
`collectionSummary` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `payableAmount` | number | 应收总额 |
|
||||
| `paidAmount` | number | 累计已收 |
|
||||
| `refundedAmount` | number | 累计已退 |
|
||||
| `netPaidAmount` | number | 净已收,等于已收减已退 |
|
||||
| `balanceAmount` | number | 待收尾款 |
|
||||
| `depositPaidAmount` | number | 已收订金 |
|
||||
| `balancePaidAmount` | number | 已收尾款 |
|
||||
| `fullPaidAmount` | number | 已收全款 |
|
||||
|
||||
`collectionRecords[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `recordId` | string | 收款记录 ID |
|
||||
| `recordType` | string | 记录类型,线上支付或线下收款 |
|
||||
| `recordTypeName` | string | 记录类型中文名 |
|
||||
| `payType` | string/null | 款项类型 |
|
||||
| `payTypeName` | string/null | 款项类型中文名 |
|
||||
| `channel` | string/null | 支付或收款渠道 |
|
||||
| `channelName` | string/null | 支付或收款渠道中文名 |
|
||||
| `receiptMethod` | string/null | 线下收款方式;在线支付和对公转账可为空 |
|
||||
| `receiptMethodName` | string/null | 线下收款方式中文名 |
|
||||
| `amount` | number | 金额 |
|
||||
| `collectedAt` | string/null | 收款时间,格式 `yyyy-MM-dd HH:mm:ss` 或 ISO 时间字符串 |
|
||||
| `status` | string/null | 收款记录状态 |
|
||||
| `statusName` | string/null | 收款记录状态中文名 |
|
||||
| `collectorName` | string/null | 代收人姓名 |
|
||||
| `operatorName` | string/null | 登记人姓名 |
|
||||
| `thirdPartyNoMasked` | string/null | 脱敏第三方交易号 |
|
||||
| `transferRef` | string/null | 转账流水号 |
|
||||
| `voucherUrls` | string[]/null | 凭证图片 URL |
|
||||
| `remark` | string/null | 备注 |
|
||||
| `includedInPaidAmount` | boolean | 是否计入已收金额 |
|
||||
|
||||
详情接口不返回 `needsVehicle`、`vehicleControlStatus`、`vehicleControlStatusName`。
|
||||
|
||||
#### 3.2.3 示例
|
||||
|
||||
典型成功:
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233886248534018/settlement/return-detail
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"orderInfo": {
|
||||
"orderId": "2077233886248534018",
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "T20260718001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"departureDate": "2026-07-20",
|
||||
"returnDate": "2026-07-24",
|
||||
"peopleCount": 3,
|
||||
"adultCount": 2,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 1,
|
||||
"peopleSummary": "2成人1婴儿",
|
||||
"consultantId": "1001",
|
||||
"consultantName": "admin",
|
||||
"houseStaffId": "20001",
|
||||
"houseStaffName": "房务A",
|
||||
"fleetStaffId": "30001",
|
||||
"fleetStaffName": "车务A",
|
||||
"settlementStatus": "COMPLETED",
|
||||
"settlementStatusName": "已结算"
|
||||
},
|
||||
"travelers": [
|
||||
{
|
||||
"travelerId": "2077233886248535001",
|
||||
"travelerName": "张三",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人",
|
||||
"idType": "ID_CARD",
|
||||
"idTypeName": "身份证",
|
||||
"phone": "138****0000",
|
||||
"idCardNo": "150***********1234"
|
||||
}
|
||||
],
|
||||
"driverVehicles": [
|
||||
{
|
||||
"driverId": "2078304714008522754",
|
||||
"driverName": "李师傅",
|
||||
"driverPhone": "176****3787",
|
||||
"vehicleId": "2065329514644152321",
|
||||
"vehiclePlateNo": "蒙C01E01",
|
||||
"vehicleModelName": "丰田埃尔法",
|
||||
"seatCount": 7,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-24"
|
||||
}
|
||||
],
|
||||
"receivableSummary": {
|
||||
"orderAmount": 6000.00,
|
||||
"surchargeAmount": 200.00,
|
||||
"discountAmount": 90.00,
|
||||
"payableAmount": 6110.00,
|
||||
"formulaText": "订单金额 + 附加费 - 优惠 = 应收总额"
|
||||
},
|
||||
"receivableItems": [
|
||||
{
|
||||
"sourceRecordId": "2077233886248534018",
|
||||
"itemType": "BASE_ORDER",
|
||||
"itemTypeName": "订单基础应收",
|
||||
"itemCode": "ORDER_AMOUNT",
|
||||
"itemName": "呼伦贝尔草原 5 日游",
|
||||
"direction": "ADD",
|
||||
"amount": 6000.00
|
||||
},
|
||||
{
|
||||
"sourceRecordId": "2077233886248536001",
|
||||
"itemType": "SURCHARGE",
|
||||
"itemTypeName": "附加费",
|
||||
"itemCode": "SINGLE_ROOM",
|
||||
"itemName": "单房差",
|
||||
"direction": "ADD",
|
||||
"amount": 200.00
|
||||
},
|
||||
{
|
||||
"sourceRecordId": "2077233886248537001",
|
||||
"itemType": "DISCOUNT",
|
||||
"itemTypeName": "优惠",
|
||||
"itemCode": "PROMOTION",
|
||||
"itemName": "活动优惠",
|
||||
"direction": "DEDUCT",
|
||||
"amount": 90.00
|
||||
}
|
||||
],
|
||||
"collectionSummary": {
|
||||
"payableAmount": 6110.00,
|
||||
"paidAmount": 6110.00,
|
||||
"refundedAmount": 0.00,
|
||||
"netPaidAmount": 6110.00,
|
||||
"balanceAmount": 0.00,
|
||||
"depositPaidAmount": 1000.00,
|
||||
"balancePaidAmount": 5110.00,
|
||||
"fullPaidAmount": 0.00
|
||||
},
|
||||
"collectionRecords": [
|
||||
{
|
||||
"recordId": "2077233886248538001",
|
||||
"recordType": "ONLINE_PAYMENT",
|
||||
"recordTypeName": "在线支付",
|
||||
"payType": "DEPOSIT",
|
||||
"payTypeName": "订金",
|
||||
"channel": "WECHAT",
|
||||
"channelName": "微信支付",
|
||||
"receiptMethod": null,
|
||||
"receiptMethodName": null,
|
||||
"amount": 1000.00,
|
||||
"collectedAt": "2026-07-10 10:30:00",
|
||||
"status": "SUCCEEDED",
|
||||
"statusName": "支付成功",
|
||||
"collectorName": null,
|
||||
"operatorName": null,
|
||||
"thirdPartyNoMasked": "4200****0001",
|
||||
"transferRef": null,
|
||||
"voucherUrls": null,
|
||||
"remark": null,
|
||||
"includedInPaidAmount": true
|
||||
},
|
||||
{
|
||||
"recordId": "2077233886248539001",
|
||||
"recordType": "MANUAL_RECEIPT",
|
||||
"recordTypeName": "线下收款",
|
||||
"payType": "BALANCE",
|
||||
"payTypeName": "尾款",
|
||||
"channel": "DRIVER_CASH",
|
||||
"channelName": "报账人收款",
|
||||
"receiptMethod": "WECHAT_TRANSFER",
|
||||
"receiptMethodName": "微信转账",
|
||||
"amount": 5110.00,
|
||||
"collectedAt": "2026-07-20 18:30:00",
|
||||
"status": "CONFIRMED",
|
||||
"statusName": "已确认",
|
||||
"collectorName": "李师傅",
|
||||
"operatorName": "admin",
|
||||
"thirdPartyNoMasked": null,
|
||||
"transferRef": null,
|
||||
"voucherUrls": [
|
||||
"https://oss.example.com/receipt/a.jpg"
|
||||
],
|
||||
"remark": "现场收尾款",
|
||||
"includedInPaidAmount": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
边界成功:无司机车辆、无收款记录。
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2077233886248534019/settlement/return-detail
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"orderInfo": {
|
||||
"orderId": "2077233886248534019",
|
||||
"orderNo": "HL202607180002",
|
||||
"teamNo": null,
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"departureDate": "2026-07-20",
|
||||
"returnDate": "2026-07-24",
|
||||
"peopleCount": 1,
|
||||
"adultCount": 1,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 0,
|
||||
"peopleSummary": "1成人",
|
||||
"consultantId": "1001",
|
||||
"consultantName": "admin",
|
||||
"houseStaffId": null,
|
||||
"houseStaffName": null,
|
||||
"fleetStaffId": null,
|
||||
"fleetStaffName": null,
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未结算"
|
||||
},
|
||||
"travelers": [],
|
||||
"driverVehicles": [],
|
||||
"receivableSummary": {
|
||||
"orderAmount": 3000.00,
|
||||
"surchargeAmount": 0.00,
|
||||
"discountAmount": 0.00,
|
||||
"payableAmount": 3000.00,
|
||||
"formulaText": "订单金额 + 附加费 - 优惠 = 应收总额"
|
||||
},
|
||||
"receivableItems": [
|
||||
{
|
||||
"sourceRecordId": "2077233886248534019",
|
||||
"itemType": "BASE_ORDER",
|
||||
"itemTypeName": "订单基础应收",
|
||||
"itemCode": "ORDER_AMOUNT",
|
||||
"itemName": "呼伦贝尔草原 5 日游",
|
||||
"direction": "ADD",
|
||||
"amount": 3000.00
|
||||
}
|
||||
],
|
||||
"collectionSummary": {
|
||||
"payableAmount": 3000.00,
|
||||
"paidAmount": 0.00,
|
||||
"refundedAmount": 0.00,
|
||||
"netPaidAmount": 0.00,
|
||||
"balanceAmount": 3000.00,
|
||||
"depositPaidAmount": 0.00,
|
||||
"balancePaidAmount": 0.00,
|
||||
"fullPaidAmount": 0.00
|
||||
},
|
||||
"collectionRecords": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
业务失败:订单不存在。
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/999999999999999999/settlement/return-detail
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 入参汇总
|
||||
|
||||
| 接口 | 入参位置 | 字段 |
|
||||
|---|---|---|
|
||||
| `GET /v3/admin/order-settlement/tasks` | Query | `page`、`pageSize`、`keyword`、`departureDateFrom`、`departureDateTo`、`settlementStatus` |
|
||||
| `GET /v3/admin/order/{orderId}/settlement/return-detail` | Path | `orderId` |
|
||||
|
||||
## 5. 出参汇总
|
||||
|
||||
| 接口 | 出参根结构 | 主要字段 |
|
||||
|---|---|---|
|
||||
| `GET /v3/admin/order-settlement/tasks` | `PageResult<SettlementTaskRespVO>` | `records[]`、`total`、`page`、`pageSize` |
|
||||
| `GET /v3/admin/order/{orderId}/settlement/return-detail` | `SettlementReturnDetailRespVO` | `orderInfo`、`travelers`、`driverVehicles`、`receivableSummary`、`receivableItems`、`collectionSummary`、`collectionRecords` |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `settlementStatus`
|
||||
|
||||
**所属字段**: `settlementStatus`、`records[].settlementStatus`、`orderInfo.settlementStatus` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `NONE` | 未结算 | 初始态,尚未提交核单 |
|
||||
| `PENDING` | 待财务复核 | 已提交核单,等待财务复核 |
|
||||
| `COMPLETED` | 已结算 | 财务复核已完成 |
|
||||
|
||||
### 6.2 `travelerType`
|
||||
|
||||
**所属字段**: `travelers[].travelerType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `ADULT` | 成人 | 成人出行人 |
|
||||
| `CHILD` | 儿童 | 儿童出行人 |
|
||||
| `YOUNG_CHILD` | 幼童 | 幼童出行人 |
|
||||
| `BABY` | 婴儿 | 婴儿出行人 |
|
||||
|
||||
### 6.3 `idType`
|
||||
|
||||
**所属字段**: `travelers[].idType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `ID_CARD` | 身份证 | 居民身份证 |
|
||||
| `PASSPORT` | 护照 | 护照 |
|
||||
| `BIRTH_CERT` | 出生证明 | 出生医学证明 |
|
||||
|
||||
### 6.4 `itemType`
|
||||
|
||||
**所属字段**: `receivableItems[].itemType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `BASE_ORDER` | 订单基础应收 | 订单基础金额 |
|
||||
| `SURCHARGE` | 附加费 | 附加费用,增加应收 |
|
||||
| `DISCOUNT` | 优惠 | 优惠项目,减少应收 |
|
||||
|
||||
### 6.5 `direction`
|
||||
|
||||
**所属字段**: `receivableItems[].direction` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `ADD` | 增加 | 计入应收增加项 |
|
||||
| `DEDUCT` | 扣减 | 计入应收扣减项 |
|
||||
|
||||
### 6.6 `recordType`
|
||||
|
||||
**所属字段**: `collectionRecords[].recordType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `ONLINE_PAYMENT` | 在线支付 | 线上支付流水 |
|
||||
| `MANUAL_RECEIPT` | 线下收款 | 管理后台登记的线下收款 |
|
||||
|
||||
### 6.7 `payType`
|
||||
|
||||
**所属字段**: `collectionRecords[].payType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `DEPOSIT` | 订金 | 订金 |
|
||||
| `FULL` | 全款 | 全款 |
|
||||
| `BALANCE` | 尾款 | 尾款 |
|
||||
|
||||
### 6.8 `channel`
|
||||
|
||||
**所属字段**: `collectionRecords[].channel` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `WECHAT` | 微信支付 | 在线微信支付 |
|
||||
| `ALIPAY` | 支付宝 | 在线支付宝支付 |
|
||||
| `OFFLINE_TRANSFER` | 线下转账 | 线下转账渠道 |
|
||||
| `DRIVER_CASH` | 报账人收款 | 报账人代收 |
|
||||
| `BANK_TRANSFER` | 对公转账 | 对公银行转账 |
|
||||
| `CONSULTANT_COLLECTION` | 定制师代收 | 定制师代收 |
|
||||
|
||||
### 6.9 `receiptMethod`
|
||||
|
||||
**所属字段**: `collectionRecords[].receiptMethod` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `WECHAT_TRANSFER` | 微信转账 | 线下微信转账 |
|
||||
| `CASH` | 现金收款 | 现金收款 |
|
||||
|
||||
### 6.10 `collectionRecords[].status`
|
||||
|
||||
**所属字段**: `collectionRecords[].status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `SUCCEEDED` | 支付成功 | 成功在线支付,计入已收 |
|
||||
| `PENDING` | 待支付 | 在线支付待支付,不计入已收 |
|
||||
| `CLOSED` | 已关闭 | 在线支付已关闭,不计入已收 |
|
||||
| `REFUNDED` | 已退款 | 在线支付已退款 |
|
||||
| `CONFIRMED` | 已确认 | 线下收款已确认,计入已收 |
|
||||
| `VOIDED` | 已撤销 | 线下收款已撤销,不计入已收 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| `200` | 成功 | 查询成功 |
|
||||
| `400` | 参数校验失败 | `page < 1`、`pageSize > 100`、`orderId <= 0`、日期格式非法、`settlementStatus` 非法 |
|
||||
| `401` | 未认证 | 未携带有效管理后台 JWT |
|
||||
| `581007` | 订单不存在 | 详情接口查询不存在的订单 |
|
||||
| `581045` | 房务角色无权查看订单详情,房务仅可配房 | 房务管理员或房务组长访问列表或详情 |
|
||||
| `584072` | 车务司机车辆信息暂时不可用,请稍后重试 | 详情接口读取司机车辆信息不可用 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
示例已按接口放在 `3.1.3` 和 `3.2.3`:列表接口包含典型成功和空结果;详情接口包含典型成功、无司机车辆/无收款记录边界成功、订单不存在业务失败。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 列表只包含常规 CORE 产品、无团期批次、已完成订单;团期、小蒙马、导游/摄影团队独立列表不在本接口范围。
|
||||
- 列表按返团日期倒序、订单 ID 倒序返回。
|
||||
- 详情接口取消订单返回成功响应,但 `orderInfo`、汇总对象可为空,数组字段为空数组。
|
||||
- 详情中的手机号、身份证号、第三方交易号、司机手机号均为脱敏值。
|
||||
- `collectionRecords` 已合并在线支付和线下收款,前端不需要再把支付记录接口与线下收款接口自行合并。
|
||||
- `receivableSummary.payableAmount` 是应收总额,计算项通过 `receivableItems` 返回。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 列表接口
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 常规产品核团列表 | 无专用接口 | 新增 `GET /v3/admin/order-settlement/tasks` |
|
||||
| 人数文案 | 无 | 返回 `peopleSummary` |
|
||||
| 核算状态中文 | 无 | 返回 `settlementStatusName` |
|
||||
| 司机/车牌 | 不适用 | 列表不返回司机和车牌字段 |
|
||||
|
||||
### 10.2 详情接口
|
||||
|
||||
| 字段/结构 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `orderInfo.peopleSummary` | 无 | 新增 |
|
||||
| `orderInfo.adultCount/childCount/youngChildCount/babyCount` | 无 | 新增 |
|
||||
| `orderInfo.consultantId/consultantName` | 无 | 新增 |
|
||||
| `orderInfo.houseStaffId/houseStaffName` | 无 | 新增 |
|
||||
| `orderInfo.fleetStaffId/fleetStaffName` | 无 | 新增 |
|
||||
| `orderInfo.settlementStatusName` | 无 | 新增 |
|
||||
| `travelers[].travelerTypeName` | 无 | 新增 |
|
||||
| `travelers[].idTypeName` | 无 | 新增 |
|
||||
| `driverVehicles` | 无 | 新增司机车辆集合 |
|
||||
| `receivableSummary` | 不完整 | 新增订单金额、附加费、优惠、应收总额、公式文案 |
|
||||
| `receivableItems` | 无 | 新增应收计算明细 |
|
||||
| `collectionSummary` | 不完整 | 新增已收/已退/净已收/待收/订金/尾款/全款汇总 |
|
||||
| `collectionRecords` | 无 | 新增在线支付+线下收款合并记录 |
|
||||
| `needsVehicle/vehicleControlStatus/vehicleControlStatusName` | 可能需要前端关注 | 本接口不返回 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。列表为新增接口;详情为新增出参字段和新增集合结构。
|
||||
- **前端是否必须同步上线**: 建议同步。核团列表页应改用新增列表接口;详情页可直接使用新增聚合字段,减少前端合并接口逻辑。
|
||||
- **影响已有数据**: 不需要数据迁移。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**: 回滚 PR #5058。
|
||||
- **回滚后前端影响**: 新增列表接口不可用,详情新增字段消失;前端需要回退到原有多接口合并方案或旧页面逻辑。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `orderId`、`travelerId`、`driverId`、`vehicleId` 等长整型 ID 均按字符串处理,避免 JS 精度丢失。
|
||||
- 列表不要展示司机和车牌;司机车辆只在详情的 `driverVehicles` 中展示。
|
||||
- 详情里的线下收款和在线支付已经按统一记录结构返回,`recordType` 用于区分来源。
|
||||
- 金额字段单位均为元。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5055](https://git.1814.love:8443/wx/HL/issues/5055)
|
||||
- **PR**: [#5058](https://git.1814.love:8443/wx/HL/pulls/5058)
|
||||
- **Merge commit**: [d916901f8](https://git.1814.love:8443/wx/HL/commit/d916901f8)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **需求确认**: @yaosutu
|
||||
@@ -0,0 +1,255 @@
|
||||
# 🔧【消费方式纠正·管理后台】调整订单尾款显示纠正(#5116)
|
||||
|
||||
> **接口**:`GET /v3/admin/order/{id}/adjustment/snapshot`
|
||||
> **服务**:`hl-order-service-v3`
|
||||
> **更新时间**:2026-07-21
|
||||
> **重要说明**:**后端接口契约、字段和金额计算均未变;本通知仅要求管理后台纠正字段取值,前端必须同步处理。**
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台订单详情与“调整订单”弹窗对同一订单展示了不同的待收尾款。订单存在 150.00 元优惠时:
|
||||
|
||||
- 订单详情展示待收尾款 `14350.00`;
|
||||
- 调整快照实际返回 `data.basic.balanceAmount = "14350.00"`;
|
||||
- 调整弹窗却展示 `14500.00`。
|
||||
|
||||
错误值恰好等于 `16000.00 - 1500.00 = 14500.00`,说明弹窗使用订单基价减已付金额自行计算,遗漏了 `150.00` 优惠。后端快照已经返回包含优惠、附加费、实付及退款口径的最终尾款,前端不应再次计算。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 调整订单预填快照查询 | GET | `/v3/admin/order/{id}/adjustment/snapshot` | 前端消费方式纠正 | 弹窗尾款直接读取 `data.basic.balanceAmount`;后端接口无变更 |
|
||||
|
||||
本次没有新增、删除或重命名任何请求字段、响应字段、枚举值或错误码。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 调整订单预填快照查询
|
||||
|
||||
- **使用场景**:打开管理后台“调整订单”弹窗时,获取当前订单的基础金额及所选子领域快照。
|
||||
- **认证**:管理后台登录态。
|
||||
- **幂等性**:是;只读查询。
|
||||
- **限流**:无本接口专属限流约定。
|
||||
- **尾款取值**:直接读取 `data.basic.balanceAmount`。
|
||||
- **禁止用法**:不要使用 `orderAmount - paidAmount`、`订单总额 - 已付订金`等公式自行计算尾款。
|
||||
|
||||
`snapshot` 响应中没有供前端重算尾款使用的 `paidAmount` 字段;`balanceAmount` 已是后端统一金额口径下的最终结果。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 位置 | 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|:---:|------|----------|
|
||||
| Path | `id` | Long | 是 | 订单 ID | 必须是存在且当前账号可访问的订单 ID;前端按字符串传递,避免 JavaScript 大整数精度丢失 |
|
||||
| Query | `scope` | String | 否 | 限定返回子领域;多个值用英文逗号分隔 | 不传返回全部子领域;合法值见第 6 节 |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
GET 请求无请求体。本次请求参数没有变化。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 响应包装
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | 业务状态码;`200` 表示成功 |
|
||||
| `message` | String | 结果说明;失败时为错误信息 |
|
||||
| `data` | Object / null | 成功时为调整快照;失败时为 `null` |
|
||||
| `data.basic` | Object | 订单基础信息;无论 `scope` 取何合法值均返回 |
|
||||
|
||||
### 5.2 `data.basic` 本问题涉及的金额字段
|
||||
|
||||
| 字段 | JSON 类型 | 语义 | 示例值 |
|
||||
|------|-----------|------|--------|
|
||||
| `orderAmount` | String | 订单基价,不等同于优惠后的应收金额 | `"16000.00"` |
|
||||
| `surchargeAmount` | String | 已有附加费合计 | `"0.00"` |
|
||||
| `discountAmount` | String | 已有优惠合计 | `"150.00"` |
|
||||
| `receivableAmount` | String | 应收总额,口径为 `max(0, orderAmount + surchargeAmount - discountAmount)` | `"15850.00"` |
|
||||
| `balanceAmount` | String | 待收尾款;已综合应收、净已付和退款口径,前端直接展示 | `"14350.00"` |
|
||||
|
||||
金额字段均为十进制金额字符串。前端可按金额组件的统一规则格式化显示,但不得从其他字段重新推导 `balanceAmount`。
|
||||
|
||||
本问题订单的金额核对:
|
||||
|
||||
```text
|
||||
应收金额 = 16000.00 + 0.00 - 150.00 = 15850.00
|
||||
待收尾款 = 15850.00 - 1500.00 = 14350.00
|
||||
错误展示 = 16000.00 - 1500.00 = 14500.00(漏减优惠 150.00)
|
||||
```
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `scope`(调整快照子领域)
|
||||
|
||||
**所属字段**:Query 参数 `scope`|**类型**:String|**必填**:否|**本次变化**:无
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `BASIC` | 基础信息 | 仅请求基础视图;`basic` 本身始终返回 |
|
||||
| `PEOPLE` | 出行人 | 返回出行人子领域,同时返回 `basic` |
|
||||
| `SCHEDULE` | 改期 | 返回日期/天数子领域,同时返回 `basic` |
|
||||
| `ITINERARY` | 行程 | 返回行程子领域,同时返回 `basic` |
|
||||
| `HOTEL_REQ` | 住宿需求 | 返回住宿需求子领域,同时返回 `basic` |
|
||||
| `VEHICLE_REQ` | 用车需求 | 返回用车需求子领域,同时返回 `basic` |
|
||||
| `FEE` | 费用兼容值 | 不返回独立费用列表;金额统一读取 `basic` |
|
||||
|
||||
多个子领域可用英文逗号连接,例如 `PEOPLE,SCHEDULE`。尾款展示只依赖始终返回的 `basic.balanceAmount`,无需为了尾款额外指定 `scope`。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `200` | 成功 | 快照查询成功 |
|
||||
| `581007` | 订单不存在 | `id` 对应订单不存在 |
|
||||
| `587003` | scope 枚举值非法 | `scope` 中任一值不在第 6 节合法值范围内 |
|
||||
|
||||
本次未新增或修改错误码。登录失效、无访问权限等通用网关错误沿用管理后台现有统一处理。
|
||||
|
||||
## 8. 示例(典型 / 边界 / 异常)
|
||||
|
||||
### 8.1 典型成功:订单存在优惠
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=BASIC
|
||||
Authorization: Bearer <管理后台登录凭证>
|
||||
|
||||
无请求体
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"basic": {
|
||||
"orderAmount": "16000.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"discountAmount": "150.00",
|
||||
"receivableAmount": "15850.00",
|
||||
"balanceAmount": "14350.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端底部“尾款”应展示 `14350.00`,取值路径为 `data.basic.balanceAmount`。
|
||||
|
||||
### 8.2 边界情况:无优惠、无附加费
|
||||
|
||||
**场景说明**:优惠和附加费均为 0 时,错误公式可能碰巧得到相同结果,仍必须读取 `balanceAmount`,不可据此保留自行计算逻辑。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=BASIC
|
||||
Authorization: Bearer <管理后台登录凭证>
|
||||
|
||||
无请求体
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"basic": {
|
||||
"orderAmount": "16000.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"discountAmount": "0.00",
|
||||
"receivableAmount": "16000.00",
|
||||
"balanceAmount": "14500.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败:非法 scope
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=UNKNOWN
|
||||
Authorization: Bearer <管理后台登录凭证>
|
||||
|
||||
无请求体
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 587003,
|
||||
"message": "scope 枚举值非法",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ `basic` 对所有合法 `scope` 始终返回,尾款统一读取 `data.basic.balanceAmount`。
|
||||
- ✅ 优惠、附加费、实付和退款等金额因素由后端统一计入口径;前端无需也不应复算。
|
||||
- ✅ `discountAmount = "0.00"` 时仍按同一路径读取尾款,避免代码按“有无优惠”产生两个分支。
|
||||
- ✅ 取消订单的 `receivableAmount` 和 `balanceAmount` 为 `"0.00"`,前端按返回值展示。
|
||||
- ⚠️ 金额是字符串;不得先转为 JavaScript `Number` 后自行进行财务运算。
|
||||
- ❌ 不要把 `orderAmount` 当成应收金额或待收尾款。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 后端请求字段 | 现有契约 | **不变** |
|
||||
| 后端响应字段 | 已返回 `basic.balanceAmount` | **不变** |
|
||||
| 后端枚举 / 错误码 | 现有契约 | **不变** |
|
||||
| 前端尾款取值 | 疑似用 `orderAmount - paidAmount` 自行计算 | 直接读取 `data.basic.balanceAmount` |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 存在 150.00 元优惠 | 弹窗显示 `14500.00`,比正确金额多 150.00 | 弹窗显示后端返回的 `14350.00` |
|
||||
| 无优惠 | 可能因错误公式碰巧显示正确 | 始终按统一字段展示 |
|
||||
| 存在附加费或退款口径 | 自行计算可能继续出现偏差 | 由后端统一口径的 `balanceAmount` 保证一致 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否;后端契约无变化。
|
||||
- **前端是否必须同步上线**:是;当前调整弹窗已展示错误尾款。
|
||||
- **影响范围**:管理后台“调整订单”弹窗底部尾款展示;订单详情页无需调整。
|
||||
|
||||
### 11.2 回滚说明
|
||||
|
||||
- 本通知没有后端变更,不涉及后端回滚。
|
||||
- 前端若回滚本次取值纠正,会恢复错误展示,因此不建议回滚;需要紧急处理时应暂时隐藏尾款展示,不应恢复自行计算。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 删除或停用弹窗内“订单总额减已付金额”的尾款计算逻辑。
|
||||
- 尾款唯一取值路径为 `snapshot.data.basic.balanceAmount`;若前端请求封装已解包 `data`,则取 `snapshot.basic.balanceAmount`。
|
||||
- 不要使用 `orderAmount`、`receivableAmount` 与其他页面缓存的已付金额拼接计算尾款。
|
||||
- 建议增加至少两条前端回归用例:存在优惠时尾款一致;存在附加费时尾款一致。
|
||||
- **后端契约未变、后端无需修改;本通知是现存前端消费问题的纠正通知。**
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**:[#5116](https://git.1814.love:8443/wx/HL/issues/5116)
|
||||
- **后端 PR**:无(后端无需改动)
|
||||
- **后端 commit**:无(后端无需改动)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **负责人**:@yst
|
||||
@@ -0,0 +1,411 @@
|
||||
# 📝【契约纠正·管理后台】对公转账不需要代收人 (#5120)
|
||||
|
||||
> **变更性质**:现有接口契约澄清 + 历史文档示例纠错|**端类型**:管理后台|**更新日期**:2026-07-21
|
||||
>
|
||||
> 本次没有发布新的后端字段、枚举或行为变更;下文说明接口已有的稳定契约。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台在“登记线下收款”中选择“对公转账”后仍显示“代收人”,与当前接口契约不一致。对公转账不由某位员工代收,只需填写转账流水号;代收人仅在“报账人收款”渠道下需要选择。
|
||||
|
||||
2026-07-10 的历史通知曾在示例中给 `BANK_TRANSFER.collectors` 放入“公司账户”对象,该示例与实际响应不符,本次一并纠正为空数组。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 通知类型 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/order/{orderId}/payment/manual-receipt/options` | 契约澄清 | `BANK_TRANSFER.collectors` 始终为 `[]`;各渠道使用各自的候选项 |
|
||||
| 2 | POST | `/v3/admin/order/{orderId}/payment/manual-receipt` | 契约澄清 | `collectorStaffId` 仅对 `DRIVER_CASH` 条件必填;`BANK_TRANSFER` 条件必填 `transferRef` |
|
||||
| 3 | 文档 | `2026-07/10_4884_线下收款代收人-修改接口-管理后台.md` | 示例纠错 | 将对公转账的错误 `collectors` 对象改为 `[]` |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询线下收款选项
|
||||
|
||||
- **方法与路径**:`GET /v3/admin/order/{orderId}/payment/manual-receipt/options`
|
||||
- **使用场景**:打开登记线下收款表单时,查询当前订单可用的渠道、款项类型、代收人和收款方式。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:幂等,只读查询。
|
||||
- **限流**:无接口专属限流规则。
|
||||
|
||||
### 3.2 登记线下收款
|
||||
|
||||
- **方法与路径**:`POST /v3/admin/order/{orderId}/payment/manual-receipt`
|
||||
- **使用场景**:按 options 当前返回的可用渠道和款项类型登记一笔线下收款。
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:非幂等,每次成功请求会新增一条收款记录。
|
||||
- **限流**:无接口专属限流规则。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数(两个接口通用)
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `orderId` | Path | String(Long) | 是 | 订单 ID,按字符串处理 |
|
||||
|
||||
GET 接口无 Query 参数、无请求体。
|
||||
|
||||
### 4.2 POST 请求体
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|---|---|---|---|---|
|
||||
| `channel` | String | 是 | 收款渠道 | `DRIVER_CASH` / `BANK_TRANSFER` / `CONSULTANT_COLLECTION` |
|
||||
| `payType` | String | 是 | 款项类型 | 必须取 options 中当前渠道的 `allowedPayTypes` |
|
||||
| `amount` | Decimal | 是 | 收款金额 | 最小 `0.01`,不能超过当前可收余额 |
|
||||
| `receivedAt` | String(LocalDateTime) | 否 | 收款时间 | `yyyy-MM-dd'T'HH:mm:ss`;不传默认当前时间 |
|
||||
| `transferRef` | String | 条件必填 | 对公转账流水号 | `BANK_TRANSFER` 必填,其他渠道不使用 |
|
||||
| `receiptMethod` | String | 否 | 收款方式 | 取当前渠道 `receiptMethods[].value`;`BANK_TRANSFER` 为空 |
|
||||
| `collectorStaffId` | String(Long) | 条件必填 | 代收人 assignmentId | **仅 `DRIVER_CASH` 必填**,且必须取当前渠道 `collectors[].collectorId` |
|
||||
| `collectorType` | String | 否 | 实际代收人类型 | 不传时按 `channel` 推导;如传入,必须与渠道匹配 |
|
||||
| `voucherUrls` | Array<String> | 否 | 凭证图片 URL 列表 | 可为空数组或不传 |
|
||||
| `remark` | String | 否 | 备注 | 最长 500 字 |
|
||||
|
||||
### 4.3 渠道联动必填矩阵
|
||||
|
||||
| `channel` | `collectorStaffId` | `collectorType` | `transferRef` | 代收人规则 |
|
||||
|---|---|---|---|---|
|
||||
| `BANK_TRANSFER` | 不需要;误传也不作为员工代收人处理 | 可不传;如传只能为 `COMPANY_ACCOUNT` | **必填** | 不选择任何员工,公司账户是收款归属而非代收人候选项 |
|
||||
| `CONSULTANT_COLLECTION` | 不需要 | 可不传;如传只能为 `CONSULTANT` | 不需要 | 使用订单定制师,不使用员工选择器 |
|
||||
| `DRIVER_CASH` | **必填** | 可不传;如传只能为 `ORDER_STAFF` | 不需要 | 仅能选当前订单 options 返回的有效报账人 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 通用响应包装
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | Integer | `200` 表示成功,其他值为业务错误码 |
|
||||
| `message` | String | 结果或错误说明 |
|
||||
| `data` | Object/null | 业务数据;失败时通常为 `null` |
|
||||
| `success` | Boolean | 是否成功 |
|
||||
|
||||
### 5.2 GET options 的 `data`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `channels` | Array<ChannelOption> | 当前订单的线下收款渠道列表 |
|
||||
| `channels[].channel` | String | 渠道枚举值 |
|
||||
| `channels[].channelText` | String | 渠道展示文案 |
|
||||
| `channels[].allowedPayTypes` | Array<String> | 当前订单状态下该渠道允许的款项类型;以本次响应为准 |
|
||||
| `channels[].disabled` | Boolean | `true` 表示当前不可提交该渠道 |
|
||||
| `channels[].disabledReason` | String/null | 禁用原因;可用时为 `null` |
|
||||
| `channels[].collectors` | Array<CollectorOption> | **该渠道自己的代收人候选列表**;`BANK_TRANSFER` 为 `[]` |
|
||||
| `channels[].receiptMethods` | Array<OptionItem> | 该渠道可选收款方式;`BANK_TRANSFER` 为 `[]` |
|
||||
| `collectors[].collectorType` | String | 代收人类型 |
|
||||
| `collectors[].collectorId` | String(Long) | `ORDER_STAFF` 为 assignmentId,`CONSULTANT` 为管理员 ID |
|
||||
| `collectors[].collectorName` | String | 代收人姓名 |
|
||||
| `collectors[].collectorRole` | String | 代收人角色值 |
|
||||
| `collectors[].collectorRoleText` | String | 代收人角色文案 |
|
||||
| `collectors[].defaultSelected` | Boolean | 是否默认选中 |
|
||||
| `receiptMethods[].value` | String | 收款方式值 |
|
||||
| `receiptMethods[].label` | String | 收款方式文案 |
|
||||
| `receiptMethods[].defaultSelected` | Boolean | 是否默认选中 |
|
||||
|
||||
### 5.3 POST 的 `data`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | String(Long) | 收款凭据 ID |
|
||||
| `orderId` | String(Long) | 订单 ID |
|
||||
| `channel` / `channelLabel` | String | 收款渠道值 / 文案 |
|
||||
| `payType` / `payTypeLabel` | String | 款项类型值 / 文案 |
|
||||
| `amount` | Decimal | 本次收款金额 |
|
||||
| `receivedAt` | String(LocalDateTime) | 收款时间 |
|
||||
| `collectorStaffId` / `collectorStaffName` | String(Long)/String/null | 仅 `DRIVER_CASH` 有值 |
|
||||
| `collectorType` | String | 实际代收人类型 |
|
||||
| `collectorAdminId` | String(Long)/null | `CONSULTANT_COLLECTION` 为定制师管理员 ID |
|
||||
| `collectorName` / `collectorRole` | String | 代收归属快照名称 / 角色 |
|
||||
| `transferRef` | String/null | 对公转账流水号,仅 `BANK_TRANSFER` 有值 |
|
||||
| `receiptMethod` / `receiptMethodLabel` | String/null | 收款方式值 / 文案;`BANK_TRANSFER` 为空 |
|
||||
| `voucherUrls` | Array<String> | 凭证图片 URL 列表 |
|
||||
| `remark` | String/null | 备注 |
|
||||
| `operatorName` | String | 登记人姓名 |
|
||||
| `createTime` | String(LocalDateTime) | 登记时间 |
|
||||
| `voided` | Boolean | 是否已撤销;新登记为 `false` |
|
||||
| `voidedByName` / `voidedAt` / `voidReason` | String/null | 撤销信息;新登记时为 `null` |
|
||||
| `paidAmountAfter` | Decimal | 登记后订单累计已付金额 |
|
||||
| `payStatusAfter` | String | 登记后订单支付状态 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `channel`
|
||||
|
||||
**所属字段**:`channel` / `channels[].channel`|**类型**:String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `BANK_TRANSFER` | 对公转账 | 无员工代收人,必须填 `transferRef` |
|
||||
| `CONSULTANT_COLLECTION` | 定制师代收 | 使用订单定制师,不传 `collectorStaffId` |
|
||||
| `DRIVER_CASH` | 报账人收款 | 仅允许尾款,必须从本渠道 `collectors` 选择代收人 |
|
||||
|
||||
### 6.2 `payType`
|
||||
|
||||
**所属字段**:`payType` / `channels[].allowedPayTypes[]`|**类型**:String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `DEPOSIT` | 订金 | 是否可登记以 options 当前返回为准 |
|
||||
| `FULL` | 全款 | 是否可登记以 options 当前返回为准 |
|
||||
| `BALANCE` | 尾款 | 是否可登记以 options 当前返回为准;`DRIVER_CASH` 只允许此值 |
|
||||
|
||||
> `BANK_TRANSFER` 的通用契约可支持 `DEPOSIT` / `FULL` / `BALANCE`,但具体订单当次能提交哪些值,必须以 options 的 `allowedPayTypes` 为准,不要将某个实例的 `BALANCE` 硬编码为全局规则。
|
||||
|
||||
### 6.3 `collectorType`
|
||||
|
||||
**所属字段**:`collectorType` / `collectors[].collectorType`|**类型**:String
|
||||
|
||||
| 值 | 中文 | 匹配渠道 |
|
||||
|---|---|---|
|
||||
| `COMPANY_ACCOUNT` | 公司账户 | `BANK_TRANSFER` |
|
||||
| `CONSULTANT` | 定制师 | `CONSULTANT_COLLECTION` |
|
||||
| `ORDER_STAFF` | 订单工作人员 | `DRIVER_CASH` |
|
||||
|
||||
### 6.4 `receiptMethod`
|
||||
|
||||
**所属字段**:`receiptMethod` / `receiptMethods[].value`|**类型**:String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `WECHAT_TRANSFER` | 微信转账 | 人员代收渠道的当前默认字典值 |
|
||||
| `CASH` | 现金收款 | 人员代收渠道的当前默认字典值 |
|
||||
|
||||
`BANK_TRANSFER.receiptMethods=[]`;该字典可扩展,实际可选值以 options 当次返回为准。
|
||||
|
||||
### 6.5 `payStatusAfter`
|
||||
|
||||
**所属字段**:POST 响应 `payStatusAfter`|**类型**:String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `UNPAID` | 未付款 | 尚未完成有效收款 |
|
||||
| `DEPOSIT_PAID` | 已付订金 | 订金已收 |
|
||||
| `FULLY_PAID` | 已付全款 | 应收金额已收齐 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | message / 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| `520011` | 支付类型无效或与订单状态不匹配 | `payType` 不在当前 options 允许范围内 |
|
||||
| `520401` | 收款渠道非法 | `channel` 不在三个渠道枚举中 |
|
||||
| `520402` | 对公转账渠道必须填写转账流水号 | `BANK_TRANSFER` 未传 `transferRef` |
|
||||
| `520403` | 报账人收款渠道必须指定代收人 | `DRIVER_CASH` 未传 `collectorStaffId` |
|
||||
| `520404` | 代收人不属于本订单人员 | `collectorStaffId` 不是本订单有效人员 |
|
||||
| `520407` | 订单已取消,不允许登记线下收款 | 已取消订单提交 POST |
|
||||
| `520408` | 收款金额必须大于 0 | `amount < 0.01` |
|
||||
| `520409` | 线下收款代收人类型非法 | `collectorType` 与 `channel` 不匹配 |
|
||||
| `520410` | 报账人收款只能登记尾款 | `DRIVER_CASH` 提交 `DEPOSIT` 或 `FULL` |
|
||||
| `520411` | 报账人收款必须选择本订单报账人 | 选中的订单人员不是报账人 |
|
||||
| `520412` | 订单没有可用定制师,不能登记定制师代收 | `CONSULTANT_COLLECTION` 无可用定制师 |
|
||||
| `520413` | 本次收款金额超过当前可收余额 | `amount` 大于当前可收金额 |
|
||||
|
||||
## 8. 示例(典型 + 边界 + 异常)
|
||||
|
||||
### 8.1 典型:查询选项,对公转账无代收人
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2079454953641836546/payment/manual-receipt/options
|
||||
Authorization: Bearer <admin-jwt>
|
||||
|
||||
无请求体
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"channels": [
|
||||
{
|
||||
"channel": "CONSULTANT_COLLECTION",
|
||||
"channelText": "定制师代收",
|
||||
"allowedPayTypes": ["BALANCE"],
|
||||
"disabled": false,
|
||||
"disabledReason": null,
|
||||
"collectors": [
|
||||
{
|
||||
"collectorType": "CONSULTANT",
|
||||
"collectorId": "2037350531801993218",
|
||||
"collectorName": "张三",
|
||||
"collectorRole": "CONSULTANT",
|
||||
"collectorRoleText": "定制师",
|
||||
"defaultSelected": true
|
||||
}
|
||||
],
|
||||
"receiptMethods": [
|
||||
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
|
||||
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "BANK_TRANSFER",
|
||||
"channelText": "对公转账",
|
||||
"allowedPayTypes": ["BALANCE"],
|
||||
"disabled": false,
|
||||
"disabledReason": null,
|
||||
"collectors": [],
|
||||
"receiptMethods": []
|
||||
},
|
||||
{
|
||||
"channel": "DRIVER_CASH",
|
||||
"channelText": "报账人收款",
|
||||
"allowedPayTypes": [],
|
||||
"disabled": true,
|
||||
"disabledReason": "本订单暂无可代收报账人",
|
||||
"collectors": [],
|
||||
"receiptMethods": [
|
||||
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
|
||||
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:对公转账不传代收人
|
||||
|
||||
**场景说明**:`collectorStaffId` 和 `collectorType` 都不传;仅提交 options 当前允许的款项类型与对公转账流水号。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
|
||||
Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"channel": "BANK_TRANSFER",
|
||||
"payType": "BALANCE",
|
||||
"amount": 100.00,
|
||||
"transferRef": "BANK202607210001",
|
||||
"voucherUrls": [],
|
||||
"remark": "客户对公转账"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"id": "2079600000000000001",
|
||||
"orderId": "2079454953641836546",
|
||||
"channel": "BANK_TRANSFER",
|
||||
"channelLabel": "对公转账",
|
||||
"payType": "BALANCE",
|
||||
"payTypeLabel": "尾款",
|
||||
"amount": 100.00,
|
||||
"receivedAt": "2026-07-21T15:30:00",
|
||||
"collectorStaffId": null,
|
||||
"collectorStaffName": null,
|
||||
"collectorType": "COMPANY_ACCOUNT",
|
||||
"collectorAdminId": null,
|
||||
"collectorName": "公司账户",
|
||||
"collectorRole": "COMPANY_ACCOUNT",
|
||||
"transferRef": "BANK202607210001",
|
||||
"receiptMethod": null,
|
||||
"receiptMethodLabel": null,
|
||||
"voucherUrls": [],
|
||||
"remark": "客户对公转账",
|
||||
"operatorName": "管理员",
|
||||
"createTime": "2026-07-21T15:30:00",
|
||||
"voided": false,
|
||||
"voidedByName": null,
|
||||
"voidedAt": null,
|
||||
"voidReason": null,
|
||||
"paidAmountAfter": 1600.00,
|
||||
"payStatusAfter": "DEPOSIT_PAID"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 异常:报账人收款未选择代收人
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
|
||||
Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"channel": "DRIVER_CASH",
|
||||
"payType": "BALANCE",
|
||||
"amount": 100.00,
|
||||
"receiptMethod": "CASH"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 520403,
|
||||
"message": "报账人收款渠道必须指定代收人",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- `collectors` 是每个 channel 自己的候选列表,不是所有渠道共用的必选列表。
|
||||
- `BANK_TRANSFER`:`collectors=[]`,不传 `collectorStaffId`,必须传 `transferRef`;即使误传 `collectorStaffId`,响应中员工代收人 ID 仍为空。
|
||||
- `CONSULTANT_COLLECTION`:不要提交 `collectorStaffId`;当订单没有可用定制师时,渠道禁用。
|
||||
- `DRIVER_CASH`:仅允许 `BALANCE`,必须传当前订单有效报账人的 assignmentId。
|
||||
- options 的 `allowedPayTypes` 随订单状态和可收余额变化。待支付订单中,对公转账/定制师代收可返回 `DEPOSIT`、`FULL`;非待支付且仍有可收余额时可返回 `BALANCE`。
|
||||
- 渠道 `disabled=true` 或 `allowedPayTypes=[]` 时,当前不可提交该渠道。
|
||||
- 已取消订单、无可收余额的订单不能登记线下收款。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
> 本节对比的是“错误理解 / 错误文档示例”与“正确的现有契约”,不表示后端今日发布了新的接口变更。
|
||||
|
||||
| 项目 | 错误理解 / 历史错误示例 | 正确契约 |
|
||||
|---|---|---|
|
||||
| 对公转账的代收人候选 | `BANK_TRANSFER.collectors` 含“公司账户”对象 | `BANK_TRANSFER.collectors=[]` |
|
||||
| `collectorStaffId` 字段 | 所有渠道都要选代收人,或该字段已从后端删除 | 字段仍保留,**仅 `DRIVER_CASH` 条件必填** |
|
||||
| 对公转账必填项 | 代收人 | `transferRef` 转账流水号 |
|
||||
| 定制师代收 | 复用员工代收人选择器并传 `collectorStaffId` | 不要传 `collectorStaffId`,使用订单定制师 |
|
||||
| 对公转账款项类型 | 固定只能是某一种款项 | 以 options 当前返回的 `allowedPayTypes` 为准 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。后端字段、枚举和行为没有变更。
|
||||
- **前端是否必须同步上线**:是。已有页面在 `BANK_TRANSFER` 下显示代收人,需要按正确契约纠正。
|
||||
|
||||
### 11.2 回滚说明
|
||||
|
||||
- 本次仅修正通知文档,不涉及后端接口回滚。
|
||||
- 若前端回滚渠道联动修正,对公转账将再次错误显示代收人。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 选中 `BANK_TRANSFER` 时,隐藏代收人选择器,并清空从其他渠道切换前残留的 `collectorStaffId`。
|
||||
- 选中 `BANK_TRANSFER` 时,显示并校验 `transferRef`,不要根据统一响应结构中“存在 `collectors` 字段”就认定代收人必选。
|
||||
- 仅 `DRIVER_CASH` 把 `collectorStaffId` 设为必填,候选项取当前 channel 的 `collectors`。
|
||||
- `CONSULTANT_COLLECTION` 不要复用 `DRIVER_CASH` 的员工代收人校验。
|
||||
- 不要把测试订单中 `BANK_TRANSFER.allowedPayTypes=["BALANCE"]` 固化为全局规则;每次均以 options 返回为准。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 关联
|
||||
|
||||
- **Issue**:[#5120](https://git.1814.love:8443/wx/HL/issues/5120)
|
||||
- **后端 PR**:无(本次无后端代码变更)
|
||||
- **后端 commit**:无(本次无后端代码变更)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**:腰苏图
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
ticket: "5131"
|
||||
title: "车队独立管理及车队字典下线"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T10:46:00+08:00"
|
||||
---
|
||||
|
||||
# 【新增接口·修改接口·前端需联调·管理后台/H5】车队独立管理及车队字典下线
|
||||
|
||||
> **服务**: hl-fleet-service + hl-user-service
|
||||
> **日期**: 2026-07-22
|
||||
> **工单**: #5131
|
||||
> **影响范围**: 车队管理、车辆档案、司机 H5、自带车审核、派车候选、矩阵、车队对账
|
||||
|
||||
## 关键变化
|
||||
|
||||
`fleet_attribution` 不再是车队数据源。后端新增 `fleet_team` 主数据,统一维护:
|
||||
|
||||
- `teamName`:车队名称。
|
||||
- `teamType`:`SELF_OPERATED` 自有 / `COOPERATIVE` 合作。
|
||||
- `leaderName`、`leaderPhone`:负责人及电话;列表电话脱敏,详情返回编辑原值。
|
||||
- `settleType`:直接复用资源付款方式 `resource_settle_type`,当前值为 `cash` / `sign` / `company`。
|
||||
- `status`:`ACTIVE` / `DISABLED`。
|
||||
|
||||
车辆及相关链路以 `fleetTeamId` 为权威关联。旧 `fleet` 稳定编码仅在客户端切换期保留兼容,不得再用于生成选项或写死 `own/coopA/coopB`。
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 车队管理
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/admin/fleet/teams/page` | 分页;支持 `keyword/teamType/status/settleType` |
|
||||
| GET | `/admin/fleet/teams/options` | 有效车队下拉;编辑存量时可传 `includeDisabledId` 回显当前停用车队 |
|
||||
| GET | `/admin/fleet/teams/:fleetTeamId` | 详情;负责人电话返回原值供编辑 |
|
||||
| POST | `/admin/fleet/teams` | 新增 |
|
||||
| PUT | `/admin/fleet/teams/:fleetTeamId` | 编辑 |
|
||||
| POST | `/admin/fleet/teams/:fleetTeamId/disable` | 停用;仍有在役车辆返回 `601103` |
|
||||
| POST | `/admin/fleet/teams/:fleetTeamId/enable` | 启用 |
|
||||
|
||||
保存请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"teamName": "合作车队一队",
|
||||
"teamType": "COOPERATIVE",
|
||||
"leaderName": "张三",
|
||||
"leaderPhone": "13800138000",
|
||||
"settleType": "sign",
|
||||
"sortOrder": 20,
|
||||
"remark": "旺季合作车队"
|
||||
}
|
||||
```
|
||||
|
||||
下拉响应项:
|
||||
|
||||
```json
|
||||
{
|
||||
"fleetTeamId": "2080000000000000001",
|
||||
"teamCode": "ft_fsq1ab23cd",
|
||||
"teamName": "合作车队一队",
|
||||
"teamType": "COOPERATIVE",
|
||||
"settleType": "sign",
|
||||
"status": "ACTIVE"
|
||||
}
|
||||
```
|
||||
|
||||
雪花 ID 一律按字符串处理,禁止 `Number()` / `parseInt()`。
|
||||
|
||||
## 修改接口
|
||||
|
||||
### 车辆档案
|
||||
|
||||
- `POST /admin/fleet/vehicles`、`PUT /admin/fleet/vehicles/:id`:新增 `fleetTeamId`,新前端必传。
|
||||
- `GET /admin/fleet/vehicles/page`:新增筛选参数 `fleetTeamId`;列表项新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||
- `GET /admin/fleet/vehicles/:id`:详情新增同上字段。
|
||||
- 车辆导入模板把车队列改为“车队名称”,填写独立车队管理中的有效名称;历史表头和稳定编码仍兼容。
|
||||
|
||||
### 司机 H5 与审核
|
||||
|
||||
- `GET /app/h5/driver-onboard/init`:链接可编辑时新增 `fleetTeamOptions[]`,只包含 `fleetTeamId/teamName/teamType`,不暴露负责人和结算资料;续签会额外包含当前已停用车队用于原值回显。
|
||||
- `SubmitVehicleVO`、续签常驻车回显新增 `fleetTeamId`。
|
||||
- 待审核详情 `vehicle`、审核通过请求 `ownVehicle` 新增 `fleetTeamId`。
|
||||
- H5 和管理端都必须提交 ID;旧 `fleet` 仅兼容已打开的旧页面。
|
||||
|
||||
### 派车候选与矩阵
|
||||
|
||||
- 派车车辆候选新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||
- `GET /admin/fleet/board/orders` 已派车辆新增 `currentVehicleFleetTeamId/currentVehicleFleetTeamName/currentVehicleFleetTeamType/currentVehicleFleetTeamSettleType`。
|
||||
- `GET /admin/fleet/matrix/grid` 新增 `fleetTeamIds[]`;`fleets[]` 废弃。
|
||||
- 矩阵车辆行新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||
- 响应新增 `fleetTeamCounts[]`,每项包含 `fleetTeamId/teamName/count`;`fleetCount` 仅过渡兼容。
|
||||
|
||||
### 对账
|
||||
|
||||
- 车费车队分组新增 `fleetTeamId/fleetType/settleType`,名称使用对账快照。
|
||||
- `GET /admin/fleet/reconciliation/cars` 与 CSV 导出新增 `fleetTeamIds[]`;传入后优先于旧 `fleets[]`。
|
||||
- 保险车队分组新增 `fleetTeamId/fleetName/fleetType/settleType`。
|
||||
- 实际结算保存新增 `fleetTeamId`;旧 `fleet` 废弃。
|
||||
- 后端按车队类型派生 `OWN_COST/COOP_QUOTE`,不再把 `own` 当特殊业务编码。
|
||||
|
||||
## 独立菜单与权限
|
||||
|
||||
user-service 新增顶级菜单:
|
||||
|
||||
- 路由:`/fleet/teams`
|
||||
- 组件:`fleet/teams/index`
|
||||
- 权限:`fleet:team:list`、`fleet:team:create`、`fleet:team:update`、`fleet:team:status`
|
||||
- 默认角色:`SUPER_ADMIN`、`ADMIN`、`VEHICLE_MANAGER`
|
||||
|
||||
前端必须新增对应组件,否则菜单发布后会出现空路由。
|
||||
|
||||
## 前端必须修改的范围
|
||||
|
||||
### 管理后台
|
||||
|
||||
1. 新增 `src/api/fleet/teams.js` 和 `src/views/fleet/teams/index.vue`,完成车队分页、新增、编辑、启停。
|
||||
2. 车辆档案:
|
||||
- `src/views/fleet/vehicles/index.vue`
|
||||
- `src/views/fleet/vehicles/components/VehicleEditModal.vue`
|
||||
- `src/api/fleet/vehicles.js`
|
||||
使用 `/admin/fleet/teams/options`,表单和筛选绑定 `fleetTeamId`,展示 `fleetTeamName`。
|
||||
3. 自带车审核和车辆选择:
|
||||
- `src/views/fleet/drivers/pending/index.vue`
|
||||
- `src/views/fleet/drivers/components/VehiclePickerModal.vue`
|
||||
- `src/api/fleet/drivers.js`
|
||||
不再读取 `fleet_attribution`。
|
||||
4. 派车看板、矩阵和共享甘特:删除 `own/coopA/coopB` 固定数组和固定颜色映射,按 API 返回的 ID/名称动态分组。涉及:
|
||||
- `src/views/fleet/board/composables/useVehicleDriverPicker.js`
|
||||
- `src/views/fleet/board/components/VehiclePickerList.vue`
|
||||
- `src/views/fleet/matrix/**`
|
||||
- `src/views/fleet/_shared/fleetDisplay.js`
|
||||
- `src/views/fleet/_shared/gantt/**`
|
||||
5. 车队对账:`src/views/fleet/recon/**` 删除三车队固定循环、固定展开状态和固定 CSV 顺序;实际结算提交 `fleetTeamId`。
|
||||
|
||||
动态车队颜色可由 `fleetTeamId` 做稳定哈希映射,但不得用数组下标产生每次刷新变化的颜色。
|
||||
|
||||
### 司机 H5
|
||||
|
||||
以下文件把硬编码 `<option value="own/coopA/coopB">` 改为初始化响应的 `fleetTeamOptions`,提交 `fleetTeamId`:
|
||||
|
||||
- `src/views/h5/driver-intake/DriverIntakeForm.vue`
|
||||
- `src/views/h5/driver-intake/composables/useIntakeForm.js`
|
||||
- `src/views/h5/driver-intake/composables/useRenewPrefill.js`
|
||||
- `src/views/h5/driver-intake/steps/StepVehicleReg.vue`
|
||||
- `src/views/h5/driver-intake/steps/RenewUpdate.vue`
|
||||
|
||||
## 删除字典与发布顺序
|
||||
|
||||
user-service 迁移会精确删除:
|
||||
|
||||
```sql
|
||||
DELETE FROM sys_dict_data WHERE dict_type = 'fleet_attribution';
|
||||
DELETE FROM sys_dict_type WHERE dict_type = 'fleet_attribution';
|
||||
```
|
||||
|
||||
必须按以下顺序发布,禁止先删字典:
|
||||
|
||||
1. 发布 `hl-fleet-service`,完成 `fleet_team` 建表、存量回填和兼容接口上线。
|
||||
2. 发布已完成本清单的 `hl-ui`,确认车辆、审核、H5、矩阵和对账不再读取该字典。
|
||||
3. 最后发布 `hl-user-service`,新增独立菜单并删除字典。
|
||||
|
||||
若环境中曾在字典里新增但从未被车辆、待审核或对账引用的车队,发布前需先在独立车队管理中补建;迁移会自动收集所有已有业务引用编码,但不会跨服务读取未使用的字典配置。
|
||||
|
||||
## 兼容与业务规则
|
||||
|
||||
- 车队名称唯一;内部 `teamCode` 创建后不可修改。
|
||||
- 车队已关联车辆后不能切换自有/合作类型,防止历史结算语义漂移。
|
||||
- 停用车队不出现在普通下拉;存量车辆编辑可回显当前停用车队,但不能切入其他停用车队。
|
||||
- 车队下仍有 `ACTIVE` 车辆时禁止停用,须先转移或停用车辆。
|
||||
- 停用车队的存量车辆不得恢复在役,也不会进入派车候选或矩阵。
|
||||
- 对账保存车队名称、类型和付款方式快照,后续改主档不修改历史账期。
|
||||
- 负责人电话属于敏感信息,列表只展示脱敏值,不得写日志或进入前端埋点。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 独立车队菜单可分页、新增、编辑、启停,付款方式与资源页选项一致。
|
||||
- [ ] 车辆新增/编辑/筛选/详情/导入均使用动态车队,不再出现固定三项。
|
||||
- [ ] 司机 H5 新招、续签和管理端自带车审核均可选择动态车队并正确回显。
|
||||
- [ ] 派车候选、矩阵、甘特和对账能展示任意新增车队,颜色和分组稳定。
|
||||
- [ ] 全前端搜索不到 `fleet_attribution` 运行时读取,也没有业务代码写死 `own/coopA/coopB` 车队集合。
|
||||
- [ ] 按发布顺序上线后,删除字典不会导致下拉为空、标签显示编码或请求失败。
|
||||
- [ ] 雪花 ID 全程按字符串处理,负责人电话未出现在日志、埋点或列表明文。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- `mvn -pl hl-fleet-service -am -DskipTests compile`:通过。
|
||||
- 受影响链路 12 个测试类定向执行:388 项通过,0 failure,0 error。
|
||||
- user-service 菜单迁移审计:1 项通过,0 failure,0 error。
|
||||
- `mvn -pl hl-user-service,hl-fleet-service -am test`:通过。
|
||||
- `mvn -pl hl-fleet-service -am verify`:通过;fleet 绑定的 `spotless:check` 同步通过。
|
||||
- 测试环境已部署 `hl-fleet-service@feat/fleet-team-management`,8087/8187 双实例健康。
|
||||
- 测试网关只读实测:车队分页、有效车队下拉、车辆分页均 HTTP/业务码 200;动态车队字段齐全,负责人电话列表脱敏。
|
||||
- 前端页面联调及 `hl-user-service` 菜单/删字典迁移:待前端完成动态车队与独立菜单页面后按发布顺序执行。
|
||||
|
||||
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
@@ -1,6 +1,6 @@
|
||||
# 【行为变更·管理后台】订单调整保留配房与房务驳回定制师待办(#4907)
|
||||
|
||||
> 2026-07-14 最终状态:#4907 后端链路已完成并关闭;最终全量复测与前端接入总览见 `57_房务全量API复测与前端最终接入核对-管理后台.md`。前端页面验收属于独立交付,不作为后端工单关单门禁。
|
||||
> 2026-07-16 最终后端状态:零配房最终确认动作契约已由 PR #5014 补齐并合入 `dev-v3`。最新代码、部署、网关 API、DB/库存和日志证据均通过;前端页面实现属于独立交付,不作为后端工单关单门禁。
|
||||
|
||||
> 服务:`hl-order-service-v3`
|
||||
>
|
||||
@@ -8,9 +8,9 @@
|
||||
>
|
||||
> 接口结构:新增“单条配房晚次与资源原子调整”接口;其余沿用订单调整、房务详情、最终确认和房务驳回现有接口
|
||||
>
|
||||
> 后端状态:PR [#4915](https://git.1814.love:8443/wx/HL/pulls/4915)、[#4925](https://git.1814.love:8443/wx/HL/pulls/4925)、[#4926](https://git.1814.love:8443/wx/HL/pulls/4926)、[#4928](https://git.1814.love:8443/wx/HL/pulls/4928)、[#4931](https://git.1814.love:8443/wx/HL/pulls/4931) 已合并;最新测试环境部署任务 `eb216570` 成功,`8086/8186` 双实例 UP
|
||||
> 后端状态:既有 PR [#4915](https://git.1814.love:8443/wx/HL/pulls/4915)、[#4925](https://git.1814.love:8443/wx/HL/pulls/4925)、[#4926](https://git.1814.love:8443/wx/HL/pulls/4926)、[#4928](https://git.1814.love:8443/wx/HL/pulls/4928)、[#4931](https://git.1814.love:8443/wx/HL/pulls/4931) 与最新 PR [#5014](https://git.1814.love:8443/wx/HL/pulls/5014) 均已合并;最终验证基线 `dev-v3@d54435af7`,测试环境部署任务 `86d9bf11` 成功,`8086/8186` 双实例 UP
|
||||
>
|
||||
> 联调证据:2026-07-12 最终网关四场景探针通过,覆盖连续改需求、跨晚次原子移动、最终确认、供应商驳回、订单取消、库存迁移与库存不足补偿;报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260712-212622.json` 为 `ok=true`
|
||||
> 联调证据:2026-07-16 部署后重新使用隔离订单执行接口面、缺口流程、订单日志和调整闭环四组探针,全部通过;最终调整报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260716-184129.json` 为 `ok=true`
|
||||
|
||||
## 0. 2026-07-12 追加:返工标签唯一口径与前端未完成项
|
||||
|
||||
@@ -202,6 +202,29 @@ POST /admin/house/assignments/requirements/{requirementId}/finalize
|
||||
|
||||
前端收到该错误后保留当前配房数据,提示房务逐日处理;不得清空页面状态或隐藏超出新行程的旧配房。
|
||||
|
||||
### 2.1 零配房动作契约
|
||||
|
||||
详情接口与最终确认写接口现已使用同一业务口径:
|
||||
|
||||
- 当前生效需求由当前房务持有。
|
||||
- `houseStatus=CLAIMING`。
|
||||
- 没有任何配房记录。
|
||||
- 没有未闭环询房。
|
||||
|
||||
满足以上条件时,房务详情返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"actions": {
|
||||
"canFinalize": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下场景仍保持禁用:存在部分配房、存在未闭环询房、非当前持有人、非当前生效需求或需求已经完成。最终确认成功后必须重新加载详情和待办;后端会关闭相关待办且不会创建配房或变更库存。
|
||||
|
||||
## 3. 房务驳回后的定制师待办
|
||||
|
||||
```http
|
||||
@@ -247,10 +270,14 @@ Content-Type: application/json
|
||||
|
||||
## 5. 后端验证证据
|
||||
|
||||
- PR:`wx/HL#4910/#4911/#4913/#4915/#4925/#4926/#4928/#4931`
|
||||
- 最新测试环境部署任务:`eb216570`,`8086/8186` 双实例均 UP
|
||||
- 定向测试:276 项通过;模块全量 5523 项仅复现 clean baseline 的 3 失败 + 2 错误,无新增回归
|
||||
- 最终网关全流程报告:`D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260712-212622.json`,四场景全部 `ok=true`
|
||||
- 实测通过:增晚、减晚、人数变化、连续调整新旧待办替代、改期+增晚同次提交、酒店/房型/房间数替换、入住日期对齐、人工清空、零配房最终确认、供应商驳回、订单取消、库存成功迁移、库存不足整单回滚
|
||||
- 返工标签:`REQUIREMENT_ADJUSTED` 压制同需求 `PENDING_ARRANGE`;最终确认、供应商驳回、订单取消后统计均从 1 回到 0
|
||||
- PR:`wx/HL#4910/#4911/#4913/#4915/#4925/#4926/#4928/#4931/#5014`
|
||||
- 最终验证基线:`dev-v3@d54435af7`
|
||||
- 模块全量:5609 项测试,0 failure,0 error,15 skipped,`BUILD SUCCESS`
|
||||
- 最新测试环境部署任务:`86d9bf11`,`8086/8186` 双实例均 UP
|
||||
- 网关接口面:68/68 通过,报告 `D:/work2/HL-v3/.tmp/house-api-surface-probe-20260716-182908.json`
|
||||
- 缺口流程:3/3 通过,报告 `D:/work2/HL-v3/.tmp/house-api-gap-flow-probe-20260716-183121.json`
|
||||
- 订单日志:3/3 通过,报告 `D:/work2/HL-v3/.tmp/house-order-log-flow-probe-20260716-183224.json`
|
||||
- 调整闭环:4/4 通过,报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260716-184129.json`
|
||||
- 实测通过:增晚、减晚、人数变化、连续调整新旧待办替代、改期+增晚、跨晚次原子调整、酒店/房型/房间数替换、入住日期对齐、人工清空、零配房最终确认、供应商驳回、订单取消、库存成功迁移、库存不足整单回滚
|
||||
- 返工标签:`REQUIREMENT_ADJUSTED` 压制同需求 `PENDING_ARRANGE`;最终确认、供应商驳回、订单取消后返工待办均正确关闭
|
||||
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
# 【前端对接·管理后台】车务需求级派单完成回调与滚动发布契约
|
||||
|
||||
> Issue: [wx/HL#4935](https://git.1814.love:8443/wx/HL/issues/4935)
|
||||
>
|
||||
> PR: [wx/HL#4994](https://git.1814.love:8443/wx/HL/pulls/4994)、[wx/HL#5009](https://git.1814.love:8443/wx/HL/pulls/5009)
|
||||
>
|
||||
> 服务: `hl-fleet-service` / `hl-order-service-v3`
|
||||
>
|
||||
> 日期: 2026-07-16
|
||||
>
|
||||
> 影响范围: 车务派单完成、用车需求驳回、订单资源状态、看板刷新与部署兼容
|
||||
|
||||
## 一、关键纠正
|
||||
|
||||
此前链路可能在单个日期或单辆车派定后提前把整个用车需求写成 `DONE`。本次改为:
|
||||
|
||||
- 一个用车需求只做一次最终完成回调。
|
||||
- 只有全部服务日期、全部车型项均已生成有效派单,并且每条逐日配置同时绑定车辆和司机,后端才允许整个需求完成。
|
||||
- 前端不得根据“某一天已派”“某一辆车已派”自行把需求或订单资源节点标成完成。
|
||||
- 前端继续直接使用看板/详情接口返回的状态、文案和能力字段,不维护独立状态映射。
|
||||
|
||||
## 二、前端接口结论
|
||||
|
||||
本次不新增前端调用接口,管理后台继续使用:
|
||||
|
||||
| 接口 | 方法 | 路径 | 前端用途 |
|
||||
|---|---|---|---|
|
||||
| 派单看板汇总 | GET | `/admin/fleet/board/summary` | 状态选项、文案、数量 |
|
||||
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 分页卡片、状态与能力字段 |
|
||||
| 派单看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 当前需求、逐日行程、当前派单 |
|
||||
| 派单时间线 | GET | `/admin/fleet/board/orders/{orderId}/timeline` | 已发生操作记录 |
|
||||
|
||||
前端处理规则:
|
||||
|
||||
1. 状态筛选使用 `summary.statusOptions`,卡片文案使用 `assignmentStatusLabel`。
|
||||
2. 派车入口只看 `canAssign`,驳回入口只看 `canRejectRequirement`。
|
||||
3. 派单、驳回或重试成功后重新请求汇总、列表和当前详情,不能只在本地改一张卡片。
|
||||
4. 同一需求仍有未完成日期或其他车辆项时,后端保持进行中;前端不得提前展示“已完成”。
|
||||
5. 后端部署开关关闭期间,需求级完成/驳回事件会保留待重放;前端不需要轮询内部 Outbox,也不得调用内部回调。
|
||||
|
||||
## 三、管理后台响应示例
|
||||
|
||||
### 3.1 仍有未完成配置
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"assignmentStatus": "holding",
|
||||
"assignmentStatusLabel": "排车中",
|
||||
"canAssign": true,
|
||||
"canRejectRequirement": false,
|
||||
"currentAssignment": {
|
||||
"requirementId": "2075001000000000001"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该响应只表示需求仍在处理,不能因 `currentAssignment` 非空推断整个需求已完成。
|
||||
|
||||
### 3.2 整个需求完成后
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"assignmentStatus": "assigned",
|
||||
"assignmentStatusLabel": "已派车",
|
||||
"canAssign": false,
|
||||
"canRejectRequirement": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实际字段以看板接口当前 OpenAPI 为准;状态中文和能力判断均由后端返回。
|
||||
|
||||
## 四、内部回调契约
|
||||
|
||||
> 本节供后端与 QA 验收。以下 `/v3/internal/**` 接口不经过管理后台,不配置公网网关路由,前端禁止调用。
|
||||
|
||||
### 4.1 最终完成回调
|
||||
|
||||
```http
|
||||
POST /v3/internal/order/vehicle-assignment/callback
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2074746808742928386",
|
||||
"requirementId": "2075001000000000001",
|
||||
"vehicleId": "2076001000000000001",
|
||||
"vehicleType": "suv",
|
||||
"vehicleCount": 1,
|
||||
"licensePlate": "蒙A12345",
|
||||
"brand": "丰田汉兰达",
|
||||
"seats": 7,
|
||||
"plannedDailyFee": "1300.00",
|
||||
"dailyFeeSource": "PRICE_CALENDAR",
|
||||
"driverStaffId": "2077001000000000001",
|
||||
"driverName": "测试司机",
|
||||
"driverPhone": "13800000000",
|
||||
"topologyFingerprint": "<64位 SHA-256 摘要>",
|
||||
"remark": "需求级最终派单快照"
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
新版 Fleet 必填字段:`orderId`、`requirementId`、`vehicleId`、`vehicleType`、`vehicleCount`、`topologyFingerprint`。其中 `topologyFingerprint` 是 Fleet 根据该需求全部有效逐日派单生成的 64 位 SHA-256 摘要;其余快照字段允许为空,后端不得伪造车牌、品牌、座位、价格或司机信息。
|
||||
|
||||
`vehicleId`、车牌和司机字段是稳定排序后的代表派单,`vehicleCount` 是该需求实际车辆组总数。完整逐日、多车辆和多司机拓扑仍以 Fleet 派单明细为准,不能从该轻量快照反推完整派车表。
|
||||
|
||||
滚动发布期间,旧版 Fleet 不传 `topologyFingerprint` 时,Order-v3 会根据完整回调快照生成 `legacy:` 前缀摘要并持久化。该兼容仅用于先升级 Order-v3、后升级 Fleet 的过渡期;新版 Fleet 仍必须发送摘要。
|
||||
|
||||
### 4.2 轻量进度回写
|
||||
|
||||
```http
|
||||
POST /v3/internal/order/orders/{orderId}/requirement/vehicle/status?requirementId={requirementId}&status=PROCESSING
|
||||
```
|
||||
|
||||
该接口只允许 `PROCESSING`。`DONE` 必须走最终完成回调并冻结快照。
|
||||
|
||||
### 4.3 驳回回写
|
||||
|
||||
```http
|
||||
POST /v3/internal/order/orders/{orderId}/requirement/vehicle/reject
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementId": "2075001000000000001",
|
||||
"returnRemark": "车型需求不完整,请定制师补充",
|
||||
"operatorId": "2078001000000000001"
|
||||
}
|
||||
```
|
||||
|
||||
只有当前需求不存在 `holding/assigned` 有效派单时才允许驳回。
|
||||
|
||||
## 五、状态、幂等和错误分支
|
||||
|
||||
| 场景 | 结果 | 副作用 |
|
||||
|---|---|---|
|
||||
| `PENDING/PROCESSING` 且无快照 | 原子写快照并完成需求 | 同事务写需求 `DONE`、订单车辆状态 `DONE`、待办/日志并尝试推进订单 |
|
||||
| `DONE` 且摘要相同 | 幂等成功 | 不加需求写锁、不更新 `update_time`,不重复同步司机、待办、日志或推进订单 |
|
||||
| `DONE` 且摘要变化 | 刷新轻量快照 | 只更新同一需求快照和司机信息,不重复推进订单、待办或时间线 |
|
||||
| 旧版回调未传摘要 | 兼容成功 | Order-v3 生成稳定 `legacy:` 摘要;相同旧请求重放仍为零写入 |
|
||||
| `DONE` 但无快照 | 返回 `582081` | 禁止补造快照,禁止继续副作用 |
|
||||
| active 状态已有快照 | 返回 `582082` | 禁止重复回写 |
|
||||
| 需求不存在或失效 | 返回 `582080` | 无写入 |
|
||||
| 非法状态流转 | 返回 `582083` | 无写入 |
|
||||
| 订单/需求已取消 | 跳过 | 不写完成快照,不推进订单 |
|
||||
|
||||
并发与重放需区分:同一摘要在 5 秒互斥窗口外再次提交时返回成功且数据库零写入;互斥窗口内的并发重复请求返回可识别冲突 `100502`,同样不得重复写快照、待办、流水或推进订单。前端遇到该冲突应刷新当前需求状态,不得自行补写完成状态。
|
||||
|
||||
错误响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 582081,
|
||||
"message": "用车需求状态不允许回写配车",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 六、滚动发布与回滚
|
||||
|
||||
1. 先部署全部 `hl-order-service-v3` 实例并确认 Flyway 成功。
|
||||
2. 保持 `FLEET_REQUIREMENT_LIFECYCLE_ENABLED=false`,再部署全部 `hl-fleet-service` 实例。
|
||||
3. 确认 Fleet Flyway、健康与普通 Outbox 消费正常后,再启用开关。
|
||||
4. 开关关闭时,需求级 Outbox 事件不会占用普通事件扫描窗口,也不会被丢弃;开启后继续重放。
|
||||
5. 异常时先关闭开关,再回滚服务制品;兼容字段和历史 Outbox 不做破坏性回滚。
|
||||
|
||||
## 七、前端必须处理
|
||||
|
||||
1. 不新增内部回调请求,不把内部错误码做成独立前端流程。
|
||||
2. 不按每日派单行数或单车派定结果推导需求完成。
|
||||
3. 继续使用后端返回的 `statusOptions`、`assignmentStatusLabel`、`canAssign`、`canRejectRequirement`。
|
||||
4. 操作成功后刷新服务端状态;并发处理中若能力字段变化,以最新接口响应为准。
|
||||
5. 不修改既有分页、团号、联系人、定制师、逐日行程和大交通字段接法;这些仍以 `57_4882` 文档为准。
|
||||
|
||||
## 八、不影响范围
|
||||
|
||||
- 不修改 `hl-ui`,本文件仅做后端契约告知。
|
||||
- 不新增管理后台分页或不分页接口。
|
||||
- 不改变车型大类、司机占一座、司机险只计车队成本等既有口径。
|
||||
- 不处理团期配车。
|
||||
|
||||
## 九、验证状态
|
||||
|
||||
### 9.1 合并前代码验证
|
||||
|
||||
```text
|
||||
hl-order-service-v3 targeted: 198 tests,0 failures,0 errors,0 skipped
|
||||
hl-order-service-v3 full verify: 5582 tests,0 failures,0 errors,15 skipped
|
||||
hl-fleet-service targeted: 226 tests,0 failures,0 errors,0 skipped
|
||||
hl-fleet-service full verify: 1721 tests,0 failures,0 errors,0 skipped
|
||||
独立终审: P0=0,P1=0,P2=0
|
||||
```
|
||||
|
||||
### 9.2 测试环境部署
|
||||
|
||||
- `hl-order-service-v3` Deploy Panel 任务 `29b541d6` 成功;`8086/8186` 双实例均启动并监听。
|
||||
- `hl-fleet-service` Deploy Panel 任务 `aaee8d01` 成功;`8087/8187` 双实例均启动并监听。
|
||||
- Nacos 已启用 `fleet.assign.requirement-lifecycle-enabled=true` 与 `fleet.feign.writeback.enabled=true`。
|
||||
- 发布顺序按“Order-v3 全实例 -> Fleet 全实例 -> 开启需求级生命周期开关”执行,未跨过滚动发布护栏。
|
||||
|
||||
### 9.3 真实 API 与数据验收
|
||||
|
||||
使用独立车务账号和真实测试订单完成 DIRECT、HOLD、取消后迟到回调、同摘要重放、摘要变化刷新、非法参数及失效需求分支验收;未使用 `admin`、`wx` 或 Mock 数据。
|
||||
|
||||
公网网关 `https://api.test.1814.love:9443` 最终验证:
|
||||
|
||||
| 请求 | 结果 |
|
||||
|---|---|
|
||||
| 车务账号登录 | HTTP 200 |
|
||||
| `GET /admin/fleet/board/summary` | HTTP 200,状态码/文案/数量由后端返回 |
|
||||
| `GET /admin/fleet/board/orders?status=assigned&orderNo=...` | HTTP 200,精准返回 1 条 |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` | HTTP 200,返回逐日行程、车型诉求、司机确认凭证及当前派单 |
|
||||
| `GET /admin/fleet/board/orders/{orderId}/timeline` | HTTP 200,返回完整操作时间线 |
|
||||
|
||||
HOLD 模式真实终态校验:
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementStatus": "DONE",
|
||||
"vehicleControlStatus": "DONE",
|
||||
"hasFleetAssigned": true,
|
||||
"snapshotCount": 1,
|
||||
"activeDailySlices": 6,
|
||||
"activeDailySliceStatus": "assigned",
|
||||
"driverConfirmationEvidenceCount": 1,
|
||||
"completionOutboxStatus": "SUCCESS"
|
||||
}
|
||||
```
|
||||
|
||||
取消订单迟到回调保持 `hasFleetAssigned=false`、快照数为 0、有效逐日派单数为 0;相同拓扑摘要在互斥窗口外重放返回 HTTP 200 且不重复推进待办、流水或订单状态,窗口内并发重复返回 `100502` 且无重复副作用。
|
||||
|
||||
### 9.4 OpenAPI 与日志
|
||||
|
||||
- Order-v3 OpenAPI 已公开内部最终回调及 `VehicleAssignmentCallbackReqVO` 的 6 个必填字段。
|
||||
- Fleet OpenAPI 已公开创建、预检、取消、改派、最终确认、司机确认/拒绝、提前结束、需求驳回与撤销取消等 10 个生命周期接口。
|
||||
- 2026-07-16 22:22 后四个目标实例均无 `ERROR` 级日志;目标订单与需求在四实例中均为 0 条 WARN/ERROR,日志可见司机确认、最终回调成功和 Order-v3 快照刷新。
|
||||
- 测试环境另有保险 PDF 缺失与历史脏订单降级 WARN,未关联本次目标订单,不作为本契约成功响应的一部分。
|
||||
|
||||
Issue #4935 的代码、部署、网关 API、MySQL 终态和服务日志证据均已补齐,可按后端验收清单关单。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 当前看板字段、分页、统计、行程与保险:`57_4882_车务派单看板当前订单字段统计行程与保险事务收口-管理后台.md`
|
||||
- 车务提需求与派单看板:`53_4871_车务提需求派单看板闭环契约-管理后台.md`
|
||||
- 团号与定制师筛选:`54_4876_派单看板团号与定制师下拉筛选-管理后台.md`
|
||||
- 后端最终回调契约:`hl-backend-changelog/changelogs/2026-07/14_1022_order-v3_vehicle-assignment-callback-contract.md`
|
||||
@@ -0,0 +1,775 @@
|
||||
# 【前端对接·管理后台】车务看板、详情、候选与矩阵读模型统一
|
||||
|
||||
> Issue: [wx/HL#4936](https://git.1814.love:8443/wx/HL/issues/4936)
|
||||
>
|
||||
> PR: [wx/HL#5031](https://git.1814.love:8443/wx/HL/pulls/5031)、[wx/HL#5032](https://git.1814.love:8443/wx/HL/pulls/5032)、[wx/HL#5034](https://git.1814.love:8443/wx/HL/pulls/5034)
|
||||
>
|
||||
> 服务: `hl-fleet-service` / `hl-order-service-v3`
|
||||
>
|
||||
> 日期: 2026-07-18
|
||||
>
|
||||
> 影响范围: 车务派单看板汇总与列表、派单详情、车辆/司机候选、矩阵月视图、相邻订单衔接风险
|
||||
|
||||
## 一、对接结论
|
||||
|
||||
1. 派单看板继续使用分页接口,`pageSize` 最大 100;没有新增“不分页全量接口”。
|
||||
2. `/summary` 与 `/orders` 共用日期、车型、司机、联系人、团号、定制师和 `keyword` 筛选;汇总忽略 `status/statuses/page/pageSize`,返回同一筛选范围内的全部状态分面。`pendingCount/pendingUrgentCount/todayDepartCount/holdingTimeoutCount` 同样随这些订单筛选变化;`idleVehicleCount/idleDriverCount` 是不随订单筛选变化的全局资源指标。
|
||||
3. 派单列表、详情和矩阵均以**当前订单 + 当前有效用车需求 + 当前有效派车组**为准,历史需求和历史派单不能覆盖当前数据。
|
||||
4. 详情一次返回逐日行程、大交通、当前需求、全部有效派车组、生命周期、凭证和操作记录。
|
||||
5. 候选车辆和司机分别分页,允许先选车或先选司机;返回完整闭区间可用时间窗、结构化可用性原因、冲突和常驻关系。
|
||||
6. 矩阵按整月查询,但同一跨月派车组先补齐完整组再裁剪显示;不会因只查到月内一天而丢失真实起止日期。
|
||||
7. 矩阵相邻订单衔接风险由后端返回 `status/statusLabel/style/reasonCode/reasonMessage`,前端不得自行根据颜色或时间重新推导。
|
||||
8. **大交通允许不填写。** 无大交通时仍可提交用车需求、查询候选、预检和派车;前端只能显示提示,不得禁用派车按钮。
|
||||
9. 所有雪花 ID 均按字符串处理,禁止转为 JavaScript `Number`。
|
||||
10. 本次未修改 `hl-ui`,前端只按本文完成接口对接。
|
||||
|
||||
## 二、接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 用途 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 看板汇总 | GET | `/admin/fleet/board/summary` | 同筛选状态计数、急单数、资源数、定制师选项 |
|
||||
| 2 | 看板列表 | GET | `/admin/fleet/board/orders` | 分页卡片、统一筛选、当前状态和操作能力 |
|
||||
| 3 | 看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 当前订单、当前需求、行程、大交通、派车组和日志 |
|
||||
| 4 | 出行人脱敏列表 | GET | `/admin/fleet/board/orders/{orderId}/travelers` | 默认脱敏查看出行人 |
|
||||
| 5 | 出行人明文查询 | POST | `/admin/fleet/board/orders/{orderId}/travelers/plain` | 有权限且有审计理由时查看明文 |
|
||||
| 6 | 派单候选 | POST | `/admin/fleet/assignments/candidates` | 车辆和司机独立分页、冲突、可用时间窗、常驻关系 |
|
||||
| 7 | 矩阵月视图 | GET | `/admin/fleet/matrix/grid` | 车辆月历、派车段、并行车辆、大交通和衔接风险 |
|
||||
|
||||
## 三、看板汇总与列表公共筛选
|
||||
|
||||
### 3.1 查询参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `statuses` | `string[]` | 否 | 多状态任一命中;支持重复 query 参数或逗号分隔。 |
|
||||
| `status` | `string` | 否 | 单状态/逗号分隔别名,与 `statuses` 合并。 |
|
||||
| `startDayFrom` | `date` | 否 | 日期区间起,与当前行程闭区间做重叠匹配。 |
|
||||
| `startDate` | `date` | 否 | `startDayFrom` 别名;前者未传时生效。 |
|
||||
| `startDayTo` | `date` | 否 | 日期区间止,与当前行程闭区间做重叠匹配。 |
|
||||
| `endDate` | `date` | 否 | `startDayTo` 别名;前者未传时生效。 |
|
||||
| `vehicleTypeKeys` | `string[]` | 否 | 车型大类:`suv/mpv/bus/sedan`,任一命中。 |
|
||||
| `typeKeys` | `string[]` | 否 | `vehicleTypeKeys` 别名。 |
|
||||
| `driverName` | `string` | 否 | 当前司机姓名模糊匹配。 |
|
||||
| `keyword` | `string` | 否 | 司机、联系人/客户、团号、订单号、当前负责定制师展示名任一包含即命中。定制师展示名为企业微信昵称优先、用户名兜底;order-v3 降级时回退派单快照,只匹配后端最终解析出的一个展示名。 |
|
||||
| `contactName` | `string` | 否 | 联系人/客户名模糊匹配。 |
|
||||
| `contactKeyword` | `string` | 否 | `contactName` 别名。 |
|
||||
| `teamNo` | `string` | 否 | 团号包含匹配,例如 `7218` 可命中 `26-7218`。 |
|
||||
| `consultantId` | `string` | 否 | 当前负责定制师管理员 ID 精确匹配。 |
|
||||
| `plannerName` | `string` | 否 | 定制师显示名模糊匹配兼容参数。 |
|
||||
| `consultantName` | `string` | 否 | `plannerName` 别名。 |
|
||||
| `variant` | `string` | 否 | `list` 默认;`grid` 为兼容值,其他值返回参数错误。 |
|
||||
| `page` | `int` | 列表否 | 默认 1;汇总忽略。 |
|
||||
| `pageSize` | `int` | 列表否 | 默认 20、最大 100;汇总忽略。 |
|
||||
|
||||
状态值:
|
||||
|
||||
```text
|
||||
unassigned / unassigned_urgent / holding / holding_urgent /
|
||||
assigned / change_requested / completed / canceled
|
||||
```
|
||||
|
||||
状态含义由后端 `statusOptions` 返回。`holding` 是“车务已排车、司机尚未完成确认链路”,不是“车务正在浏览详情”。
|
||||
|
||||
### 3.2 汇总请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/summary?startDate=2026-07-01&endDate=2026-07-31&typeKeys=suv&keyword=王
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
```
|
||||
|
||||
### 3.3 汇总响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"pendingCount": 3,
|
||||
"pendingUrgentCount": 1,
|
||||
"todayDepartCount": 1,
|
||||
"idleVehicleCount": 9,
|
||||
"idleDriverCount": 5,
|
||||
"holdingTimeoutCount": 1,
|
||||
"statusCounts": {
|
||||
"unassigned": 3,
|
||||
"holding": 1,
|
||||
"assigned": 2,
|
||||
"changeRequested": 0,
|
||||
"completed": 4,
|
||||
"canceled": 1,
|
||||
"unassignedUrgent": 1,
|
||||
"holdingUrgent": 1
|
||||
},
|
||||
"statusOptions": [
|
||||
{
|
||||
"value": "unassigned",
|
||||
"label": "待派车",
|
||||
"count": 3,
|
||||
"urgentCount": 1
|
||||
},
|
||||
{
|
||||
"value": "holding",
|
||||
"label": "排车中",
|
||||
"count": 1,
|
||||
"urgentCount": 1
|
||||
},
|
||||
{
|
||||
"value": "assigned",
|
||||
"label": "已派车",
|
||||
"count": 2,
|
||||
"urgentCount": 0
|
||||
}
|
||||
],
|
||||
"consultantOptions": [
|
||||
{
|
||||
"value": "2000000000000000001",
|
||||
"label": "企业微信昵称"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
一致性规则:
|
||||
|
||||
```text
|
||||
同一组非状态筛选条件下:
|
||||
summary.statusOptions[value=X].count
|
||||
== orders?statuses=X 返回的 data.total
|
||||
```
|
||||
|
||||
`idleVehicleCount/idleDriverCount` 是当前物理资源指标,不受订单文字筛选影响;其他订单状态计数使用同一筛选后的记录集。
|
||||
|
||||
### 3.4 列表请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders?page=1&pageSize=20&statuses=unassigned,holding&keyword=7218&consultantId=2000000000000000001
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
```
|
||||
|
||||
### 3.5 列表响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 1,
|
||||
"records": [
|
||||
{
|
||||
"id": "HL202607180001",
|
||||
"orderNo": "HL202607180001",
|
||||
"orderId": "2000000000000000101",
|
||||
"teamNo": "26-7218",
|
||||
"assignmentId": "2000000000000000201",
|
||||
"assignmentGroupId": "2000000000000000201",
|
||||
"fleetItemIndex": 0,
|
||||
"customerName": "测试联系人",
|
||||
"contactName": "测试联系人",
|
||||
"productName": "测试产品",
|
||||
"headcount": 4,
|
||||
"adultCount": 3,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 0,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"days": 3,
|
||||
"pickupAt": null,
|
||||
"dropoffAt": null,
|
||||
"isHailarPickup": false,
|
||||
"isHailarDropoff": false,
|
||||
"consultantId": "2000000000000000001",
|
||||
"plannerName": "企业微信昵称",
|
||||
"consultantName": "企业微信昵称",
|
||||
"consultantDisplayName": "企业微信昵称",
|
||||
"specialTags": ["中文司机", "大行李空间"],
|
||||
"requirementRemark": "无大交通,按行程安排车辆",
|
||||
"requiredVehicles": [
|
||||
{
|
||||
"vehicleType": "suv",
|
||||
"categoryLabel": "SUV系列",
|
||||
"seats": 7,
|
||||
"count": 1
|
||||
}
|
||||
],
|
||||
"assignmentStatus": "unassigned",
|
||||
"assignmentStatusLabel": "待派车",
|
||||
"lifecycleStageCode": "requirement_pending",
|
||||
"lifecycleStageLabel": "待车务派车",
|
||||
"currentStep": 1,
|
||||
"availableActionCodes": ["ASSIGN", "REJECT_REQUIREMENT"],
|
||||
"urgentBadge": null,
|
||||
"canAssign": true,
|
||||
"canRejectRequirement": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 列表字段绑定规则
|
||||
|
||||
| 字段 | 前端规则 |
|
||||
|---|---|
|
||||
| `teamNo` | 展示当前团号;空值不回退拼造。 |
|
||||
| `contactName` | 卡片联系人。 |
|
||||
| `consultantDisplayName` | 定制师展示名,企业微信昵称优先、用户名兜底。 |
|
||||
| `startDate/endDate/days` | 当前订单档期,闭区间含首尾。 |
|
||||
| `specialTags/requirementRemark` | 当前有效用车需求,不得混入历史需求。 |
|
||||
| `assignmentStatusLabel/lifecycleStageLabel` | 直接展示,前端不维护独立中文映射。 |
|
||||
| `availableActionCodes/canAssign/canRejectRequirement` | 决定操作入口;急单样式不得隐藏按钮。 |
|
||||
|
||||
列表按派车组聚合;底层一天一条派车切片不会把同一派车组重复成多张卡片。紧急待处理在前、普通进行中次之、终态沉底,同优先级以稳定 ID 兜底;前端不得二次排序。
|
||||
|
||||
## 四、派单详情
|
||||
|
||||
### 4.1 请求
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders/2000000000000000101
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
```
|
||||
|
||||
路径参数是数字订单 ID,按字符串传递,不是订单号。
|
||||
|
||||
### 4.2 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "HL202607180001",
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "26-7218",
|
||||
"customerName": "测试联系人",
|
||||
"headcount": 4,
|
||||
"adultCount": 3,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 0,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"pickupAt": null,
|
||||
"dropoffAt": null,
|
||||
"productName": "测试产品",
|
||||
"consultantId": "2000000000000000001",
|
||||
"plannerName": "企业微信昵称",
|
||||
"consultantName": "企业微信昵称",
|
||||
"consultantDisplayName": "企业微信昵称",
|
||||
"specialTags": ["中文司机", "大行李空间"],
|
||||
"requirementRemark": "无大交通,按行程安排车辆",
|
||||
"itinerary": {
|
||||
"theme": "草原三日",
|
||||
"route": "海拉尔 → 额尔古纳 → 满洲里",
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"date": "2026-07-20",
|
||||
"title": "抵达海拉尔",
|
||||
"detail": "市区行程"
|
||||
},
|
||||
{
|
||||
"dayNumber": 2,
|
||||
"date": "2026-07-21",
|
||||
"title": "额尔古纳",
|
||||
"detail": "草原行程"
|
||||
},
|
||||
{
|
||||
"dayNumber": 3,
|
||||
"date": "2026-07-22",
|
||||
"title": "满洲里",
|
||||
"detail": "返程"
|
||||
}
|
||||
]
|
||||
},
|
||||
"transport": {
|
||||
"transferTimeHint": "暂无接送机时间",
|
||||
"arrive": null,
|
||||
"depart": null,
|
||||
"batches": [],
|
||||
"pickupRequired": null
|
||||
},
|
||||
"currentAssignment": {
|
||||
"id": "2000000000000000201",
|
||||
"assignmentGroupId": "2000000000000000201",
|
||||
"fleetItemIndex": 0,
|
||||
"requiredVehicleType": "suv",
|
||||
"requiredSeats": 7,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"vehicleId": "2000000000000000301",
|
||||
"vehiclePlate": "蒙A·TEST1",
|
||||
"driverId": "2000000000000000401",
|
||||
"driverName": "测试司机",
|
||||
"baseAssignmentStatus": "assigned",
|
||||
"assignmentStatus": "assigned",
|
||||
"lifecycleStageCode": "confirmed",
|
||||
"lifecycleStageLabel": "已确认执行",
|
||||
"currentStep": 4,
|
||||
"availableActionCodes": ["CANCEL", "CHANGE_DRIVER", "COMPLETE_EARLY"],
|
||||
"protocolPrice": "1300.00",
|
||||
"driverConfirmationEvidenceFileIds": []
|
||||
},
|
||||
"activeAssignments": [
|
||||
{
|
||||
"assignmentGroupId": "2000000000000000201",
|
||||
"fleetItemIndex": 0,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"vehiclePlate": "蒙A·TEST1",
|
||||
"driverName": "测试司机",
|
||||
"assignmentStatus": "assigned"
|
||||
}
|
||||
],
|
||||
"operationLog": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"summary": {"totalCount": 0}
|
||||
},
|
||||
"relatedDetailReady": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 当前需求和派车组隔离
|
||||
|
||||
- `currentAssignment` 是最新一个有效派车组的兼容字段。
|
||||
- `activeAssignments` 才是当前需求下全部有效派车组;一单多车时必须渲染完整数组。
|
||||
- 历史需求、已取消组和旧订单快照不能进入当前需求详情。
|
||||
- `baseAssignmentStatus` 是落库基础态;`assignmentStatus` 是当前有效状态,前端展示后者。
|
||||
|
||||
### 4.4 逐日行程规则
|
||||
|
||||
- `itinerary.days` 来自 `order_itinerary_day`,一天一条事实数据。
|
||||
- 返回日期必须位于当前 `[startDate,endDate]`,按 `dayNumber` 稳定排序。
|
||||
- 改期后按当前订单档期对齐;越界、旧版本和非法日序数据不返回。
|
||||
- 无有效行程时返回 `days=[]`,前端显示空态,不得生成假行程。
|
||||
|
||||
### 4.5 大交通规则
|
||||
|
||||
有数据时:
|
||||
|
||||
- 到达接客使用大交通 `arriveTime`。
|
||||
- 返程送客使用大交通 `departTime`。
|
||||
- 分批接送完整返回 `batches`。
|
||||
|
||||
无数据时固定返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"transport": {
|
||||
"transferTimeHint": "暂无接送机时间",
|
||||
"arrive": null,
|
||||
"depart": null,
|
||||
"batches": [],
|
||||
"pickupRequired": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**空大交通是正常业务状态,不是参数错误,也不是派车阻断条件。** `pickupAt/dropoffAt` 同样允许为 `null`。
|
||||
|
||||
## 五、车辆与司机候选
|
||||
|
||||
### 5.1 请求
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/candidates
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2000000000000000101",
|
||||
"requirementId": "2000000000000000501",
|
||||
"fleetItemIndex": 0,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"headcount": 4,
|
||||
"selectedVehicleId": null,
|
||||
"selectedDriverId": null,
|
||||
"excludeAssignmentId": null,
|
||||
"vehicleKeyword": "GL8",
|
||||
"driverKeyword": "张",
|
||||
"vehiclePage": 1,
|
||||
"vehiclePageSize": 20,
|
||||
"driverPage": 1,
|
||||
"driverPageSize": 20
|
||||
}
|
||||
```
|
||||
|
||||
`pickupAt/dropoffAt` 未出现是合法请求;有城市信息时可额外传入,供城市衔接判断使用。
|
||||
|
||||
### 5.2 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `orderId` | `string` | 改派时是 | 当前订单 ID。 |
|
||||
| `requirementId` | `string` | 改派时是 | 当前有效用车需求 ID。 |
|
||||
| `fleetItemIndex` | `int` | 否 | 当前车型项序号,从 0 开始。 |
|
||||
| `startDate/endDate` | `date` | 是 | 请求用车闭区间。 |
|
||||
| `pickupAt/dropoffAt` | `string` | 否 | 城市衔接辅助信息;空值不阻断候选和派车。 |
|
||||
| `headcount` | `int` | 否 | 乘客人数,不含司机。车辆乘客容量=`seats-1`。 |
|
||||
| `selectedVehicleId` | `string` | 否 | 已选车辆,支持先选车。 |
|
||||
| `selectedDriverId` | `string` | 否 | 已选司机,支持先选司机。 |
|
||||
| `excludeAssignmentId` | `string` | 否 | 改派时排除当前派单,且必须属于当前订单和需求。 |
|
||||
| `vehicleKeyword` | `string` | 否 | 车牌或车型。 |
|
||||
| `driverKeyword` | `string` | 否 | 司机姓名或完整手机号。 |
|
||||
| `vehiclePage/driverPage` | `int` | 是 | 各自分页页码,默认 1。 |
|
||||
| `vehiclePageSize/driverPageSize` | `int` | 是 | 各自每页条数,最大 100。 |
|
||||
|
||||
### 5.3 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"vehicles": {
|
||||
"records": [
|
||||
{
|
||||
"vehicleId": "2000000000000000301",
|
||||
"plate": "蒙A·TEST1",
|
||||
"modelName": "测试车型",
|
||||
"seats": 7,
|
||||
"passengerCapacity": 6,
|
||||
"seatsEnough": true,
|
||||
"fleet": "own",
|
||||
"selected": false,
|
||||
"available": true,
|
||||
"availabilityReasonCode": "AVAILABLE",
|
||||
"availabilityReasonMessage": "所选服务日期内可用",
|
||||
"availabilityWindows": [
|
||||
{"startDate": "2026-07-20", "endDate": "2026-07-22"}
|
||||
],
|
||||
"residentMatch": false,
|
||||
"crossResident": false,
|
||||
"requiresCrossResidentConfirmation": false,
|
||||
"relationMessage": null,
|
||||
"conflicts": []
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"drivers": {
|
||||
"records": [
|
||||
{
|
||||
"driverId": "2000000000000000401",
|
||||
"name": "测试司机",
|
||||
"maskedPhone": "138****0000",
|
||||
"years": 8,
|
||||
"season": "active",
|
||||
"completedOrderCount": 12,
|
||||
"rating": 5.0,
|
||||
"ratingDefaulted": true,
|
||||
"selected": false,
|
||||
"available": true,
|
||||
"availabilityReasonCode": "CITY_JUNCTION_SHAREABLE",
|
||||
"availabilityReasonMessage": "仅存在可衔接的同城边界占用",
|
||||
"availabilityWindows": [
|
||||
{"startDate": "2026-07-20", "endDate": "2026-07-22"}
|
||||
],
|
||||
"residentMatch": false,
|
||||
"crossResident": false,
|
||||
"requiresCrossResidentConfirmation": false,
|
||||
"conflicts": []
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"selectedRelation": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 候选判定规则
|
||||
|
||||
| `availabilityReasonCode` | 含义 | `available` |
|
||||
|---|---|---|
|
||||
| `AVAILABLE` | 整个请求闭区间无阻塞占用 | `true` |
|
||||
| `CITY_JUNCTION_SHAREABLE` | 只有满足规则的城市边界衔接 | `true` |
|
||||
| `ASSIGNMENT_CONFLICT` | 请求区间内存在阻塞派单 | `false` |
|
||||
|
||||
- `availabilityWindows` 是请求区间内的实际可用**闭区间**,冲突会切分时间窗。
|
||||
- 车辆和司机都必须覆盖完整请求区间才可直接选中。
|
||||
- 司机无评价时返回 `rating=5.0` 且 `ratingDefaulted=true`;有真实评价时为 `false`。
|
||||
- 车辆总座位数包含司机,`passengerCapacity=seats-1`;前端不得把司机座位再次给乘客。
|
||||
- `selectedRelation` 在车辆和司机都已选时返回常驻关系。跨常驻组合必须显示后端提示并显式确认。
|
||||
|
||||
## 六、矩阵月视图
|
||||
|
||||
### 6.1 请求
|
||||
|
||||
```http
|
||||
GET /admin/fleet/matrix/grid?year=2026&month=7&season=active&fleets=own,coopA&typeKeys=suv&status=all
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
```
|
||||
|
||||
### 6.2 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `year` | `int` | 是 | 查询年份。 |
|
||||
| `month` | `int` | 是 | 1-12,越界返回车务月份错误。 |
|
||||
| `season` | `string` | 否 | `active` 默认;也支持 `pending/archived/blacklist`。 |
|
||||
| `fleets` | `string[]` | 否 | `own/coopA/coopB` 多选。 |
|
||||
| `typeKeys` | `string[]` | 否 | `suv/mpv/bus/sedan` 多选。 |
|
||||
| `status` | `string` | 否 | `all` 默认、`unassigned`、`assigned`。 |
|
||||
|
||||
### 6.3 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"year": 2026,
|
||||
"month": 7,
|
||||
"daysInMonth": 31,
|
||||
"todayDay": 18,
|
||||
"weekendDays": [4, 5, 11, 12, 18, 19, 25, 26],
|
||||
"fleetCount": {"own": 5, "coopA": 4, "coopB": 3},
|
||||
"statusCounts": {
|
||||
"totalAssignments": 21,
|
||||
"unassignedAssignments": 10,
|
||||
"assignedAssignments": 11,
|
||||
"totalOrders": 16,
|
||||
"unassignedOrders": 5,
|
||||
"partialOrders": 2,
|
||||
"assignedOrders": 11
|
||||
},
|
||||
"unassignedWindowCount": 5,
|
||||
"vehicles": [
|
||||
{
|
||||
"id": "2000000000000000301",
|
||||
"plate": "蒙A·TEST1",
|
||||
"modelName": "测试车型",
|
||||
"seats": 7,
|
||||
"fleet": "own",
|
||||
"primaryDriverName": "测试司机",
|
||||
"primaryDriverPhone": "138****0000",
|
||||
"assignments": [
|
||||
{
|
||||
"id": "2000000000000000201",
|
||||
"assignmentGroupId": "2000000000000000201",
|
||||
"orderNumericId": "2000000000000000101",
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "26-7218",
|
||||
"consultantId": "2000000000000000001",
|
||||
"consultantName": "企业微信昵称",
|
||||
"customerName": "测试联系人",
|
||||
"headcount": 4,
|
||||
"adultCount": 3,
|
||||
"childCount": 1,
|
||||
"startDay": 20,
|
||||
"endDay": 22,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"clippedHead": false,
|
||||
"clippedTail": false,
|
||||
"vehicleCategory": "suv",
|
||||
"categoryLabel": "SUV系列",
|
||||
"assignmentStatus": "assigned",
|
||||
"protocolPrice": "1300.00",
|
||||
"vehicleSpecialTags": ["中文司机"],
|
||||
"vehicleRequirementRemark": "按行程安排",
|
||||
"pickupTransports": [],
|
||||
"dropoffTransports": [],
|
||||
"parallelAssignments": [
|
||||
{
|
||||
"assignmentGroupId": "2000000000000000201",
|
||||
"fleetItemIndex": 0,
|
||||
"vehicleCategory": "suv",
|
||||
"categoryLabel": "SUV系列",
|
||||
"requiredSeats": 7,
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"vehicleId": "2000000000000000301",
|
||||
"vehiclePlate": "蒙A·TEST1",
|
||||
"driverId": "2000000000000000401",
|
||||
"driverName": "测试司机",
|
||||
"assignmentStatus": "assigned",
|
||||
"protocolPrice": "1300.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"fromAssignmentGroupId": "2000000000000000201",
|
||||
"toAssignmentGroupId": "2000000000000000202",
|
||||
"fromEndDate": "2026-07-22",
|
||||
"toStartDate": "2026-07-23",
|
||||
"previousDepartureTime": null,
|
||||
"nextArrivalTime": null,
|
||||
"previousCity": null,
|
||||
"nextCity": null,
|
||||
"connectionMinutes": null,
|
||||
"thresholdMinutes": 120,
|
||||
"status": "MISSING_TIME",
|
||||
"statusLabel": "缺少接送时间",
|
||||
"style": "RED_DASHED",
|
||||
"reasonCode": "PREVIOUS_REQUIRED_DEPARTURE_MISSING",
|
||||
"reasonMessage": "前一订单缺少必需送客批次"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 派单统计口径
|
||||
|
||||
- `totalAssignments` 是派车行数,不是订单数。
|
||||
- `totalOrders` 是去重订单数。
|
||||
- 一单同时存在已派和未派项时计入 `partialOrders`,也计入 `unassignedOrders`。
|
||||
- `unassignedWindowCount` 是含未派项的去重订单数,不等于未派逐日切片条数。
|
||||
- 跨月派车组使用完整原始 `startDate/endDate`,仅 `startDay/endDay` 裁剪到当前月;`clippedHead/clippedTail` 告诉前端是否跨月续接。
|
||||
|
||||
### 6.5 相邻订单衔接四态
|
||||
|
||||
| `status` | `style` | 含义 |
|
||||
|---|---|---|
|
||||
| `MISSING_TIME` | `RED_DASHED` | 缺少必需大交通、时刻或城市,无法完成衔接判断。 |
|
||||
| `DIFFERENT_CITY` | `DARK_RED` | 前后订单城市不同。 |
|
||||
| `SAME_CITY_TOO_SHORT` | `LIGHT_RED` | 同城但间隔小于配置阈值。 |
|
||||
| `SAME_CITY_OK` | `GREEN` | 同城且间隔达到配置阈值。 |
|
||||
|
||||
原因码:
|
||||
|
||||
```text
|
||||
PREVIOUS_REQUIRED_DEPARTURE_MISSING
|
||||
NEXT_REQUIRED_ARRIVAL_MISSING
|
||||
PREVIOUS_DEPARTURE_TIME_MISSING
|
||||
NEXT_ARRIVAL_TIME_MISSING
|
||||
PREVIOUS_DEPARTURE_CITY_MISSING
|
||||
NEXT_ARRIVAL_CITY_MISSING
|
||||
DIFFERENT_CITY
|
||||
SAME_CITY_INTERVAL_TOO_SHORT
|
||||
SAME_CITY_INTERVAL_SUFFICIENT
|
||||
```
|
||||
|
||||
同城最小衔接阈值读取 Nacos 配置,默认 120 分钟;恰好等于阈值属于 `SAME_CITY_OK`。
|
||||
|
||||
注意:`MISSING_TIME` 是矩阵风险提示,不表示订单不能派车。用户未填写大交通时仍允许完成派车。
|
||||
|
||||
## 七、出行人权限与审计
|
||||
|
||||
### 7.1 脱敏列表
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders/2000000000000000101/travelers
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
```
|
||||
|
||||
默认返回姓名、年龄类型和脱敏证件/手机号;前端日常派车只使用此接口。
|
||||
|
||||
### 7.2 明文查询
|
||||
|
||||
```http
|
||||
POST /admin/fleet/board/orders/2000000000000000101/travelers/plain
|
||||
Authorization: Bearer <fleet-manager-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"reason": "司机出发前核对接客人信息"
|
||||
}
|
||||
```
|
||||
|
||||
明文接口必须经过权限校验并记录操作人、订单、理由和时间。前端不得缓存、日志打印或二次持久化明文个人信息。
|
||||
|
||||
## 八、前端必须处理
|
||||
|
||||
1. 看板使用分页接口,分页器读取 `data.total/page/pageSize`。
|
||||
2. 状态项、状态中文、急单数读取 `summary.statusOptions`,不维护独立枚举和独立计数。
|
||||
3. 汇总请求必须携带与列表相同的非状态筛选;不要把当前状态筛选传成汇总统计范围。
|
||||
4. 卡片展示 `teamNo/contactName/headcount/consultantDisplayName/startDate/endDate/specialTags/requirementRemark`。
|
||||
5. 定制师选择值传 `consultantId`;显示使用后端返回的企业微信优先名称。
|
||||
6. 统一文本框传 `keyword`,可同时匹配司机、联系人、团号、订单号和当前定制师展示名;定制师按企业微信昵称优先、用户名兜底,前端不得自行并行匹配多个名称别名。
|
||||
7. 操作按钮使用 `availableActionCodes/canAssign/canRejectRequirement`,不能按颜色或前端状态猜测。
|
||||
8. 详情多车读取 `activeAssignments`;`currentAssignment` 只是兼容的最新一组。
|
||||
9. `transport.transferTimeHint="暂无接送机时间"` 时显示提示,但保持派车入口可用。
|
||||
10. 候选资源分别读取 `vehicles/drivers` 分页;显示后端原因和常驻关系提示。
|
||||
11. 矩阵直接使用 `connections[].status/style/reasonCode/reasonMessage`;大红、淡红、虚线红和绿色语义不能自行交换。
|
||||
12. 所有雪花 ID 当字符串处理。
|
||||
|
||||
## 九、兼容性与不影响范围
|
||||
|
||||
- 保留 `status/startDate/endDate/typeKeys/contactKeyword/consultantName` 等兼容别名。
|
||||
- 不新增前端内部接口,不改变现有派车写接口。
|
||||
- 不要求填写大交通,也不把空大交通改成校验错误。
|
||||
- 不改变“一天一条派车切片”的数据库事实模型;本轮只修正读模型聚合。
|
||||
- 不处理团期配车。
|
||||
- 不修改 `hl-ui`。
|
||||
|
||||
## 十、验证证据
|
||||
|
||||
### 10.1 代码与测试
|
||||
|
||||
```text
|
||||
hl-fleet-service 定向测试:253/253 通过
|
||||
hl-fleet-service 全量 verify:1799/1799 通过
|
||||
hl-order-service-v3 相关契约测试:30/30 通过
|
||||
独立代码评审:无 P0/P1 阻断项
|
||||
OpenAPI 说明定向校验:spotless:check + compile 通过
|
||||
```
|
||||
|
||||
Order-v3 全量测试中 4 项环境/基线失败已在同提交干净基线复现:3 项为 H2 缺少 `payment_manual_receipt`,1 项为既有本地缓存架构门禁;不由本次车务改动引入。
|
||||
|
||||
### 10.2 部署
|
||||
|
||||
```text
|
||||
hl-order-service-v3:Deploy Panel 任务 22ec81e8,8086/8186 双实例成功
|
||||
hl-fleet-service:Deploy Panel 任务 68487f94,8087/8187 双实例成功
|
||||
hl-fleet-service OpenAPI 口径补充:Deploy Panel 任务 249594e3,8087/8187 双实例成功
|
||||
```
|
||||
|
||||
部署后已通过网关读取车务 OpenAPI,确认线上文档明确区分“同一订单筛选范围内的状态计数”与“不随订单筛选变化的全局空闲资源数”,并包含统一关键词对当前定制师展示名的匹配规则。
|
||||
|
||||
### 10.3 真实网关 API
|
||||
|
||||
使用独立车务和定制师测试账号,经 `https://api.test.1814.love:9443` 完成真实订单全流程回归;未使用 `admin`、`wx` 或 Mock 数据。
|
||||
|
||||
```text
|
||||
最终回归:161/161 通过,失败 0
|
||||
覆盖:看板汇总/列表/详情、统一筛选、定制师、脱敏/明文出行人、候选车辆/司机、
|
||||
常驻与跨常驻、预检、派车、司机确认、取消/恢复/改派/提前完结、矩阵、价格、
|
||||
车辆/司机、对账、模板,以及无大交通订单完整派车链路。
|
||||
```
|
||||
|
||||
无大交通真实场景额外断言:
|
||||
|
||||
```text
|
||||
- 新建真实订单并补齐 2 名出行人
|
||||
- 不创建任何大交通计划
|
||||
- 成功提交有效用车需求
|
||||
- 详情:arrive=null、depart=null、batches=[]、transferTimeHint=暂无接送机时间
|
||||
- 候选查询成功,pickupAt/dropoffAt 均省略
|
||||
- 派车预检成功,conflict=false
|
||||
- 直派成功并进入已派车状态
|
||||
- DB:派车组逐日 6 条,pickup_at/dropoff_at 6 条均为空
|
||||
```
|
||||
|
||||
## 十一、相关文档
|
||||
|
||||
- 看板字段、统计、行程和保险事务:`57_4882_车务派单看板当前订单字段统计行程与保险事务收口-管理后台.md`
|
||||
- 需求级最终完成回调:`59_4935_车务需求级派单完成回调与滚动发布契约-管理后台.md`
|
||||
- 提需求和派单基础契约:`53_4871_车务提需求派单看板闭环契约-管理后台.md`
|
||||
- 团号与定制师筛选:`54_4876_派单看板团号与定制师下拉筛选-管理后台.md`
|
||||
@@ -0,0 +1,213 @@
|
||||
# 【前端对接·管理后台】房务待办新增“待最终确认”派生项
|
||||
|
||||
> Issue: [wx/HL#5053](https://git.1814.love:8443/wx/HL/issues/5053)
|
||||
>
|
||||
> PR: [wx/HL#5059](https://git.1814.love:8443/wx/HL/pulls/5059)
|
||||
>
|
||||
> 合并提交: `989318c3925b`
|
||||
>
|
||||
> 服务: `hl-order-service-v3` / `hl-user-service`
|
||||
>
|
||||
> 日期: 2026-07-18
|
||||
>
|
||||
> 影响范围: 房务“待处理”列表、待办类型筛选、订单详情跳转、房务工作台待办统计
|
||||
|
||||
## 一、对接结论
|
||||
|
||||
1. 房务“待处理”页的唯一主数据源仍是 `GET /v3/admin/order/todos`,不要改用任何 `my-claims` 接单列表接口。`my-claims` 不返回完整的待办类型聚合,不能替代待办接口。
|
||||
2. 待办接口新增可选类型 `PENDING_FINALIZE`,中文标签为“待最终确认”。它是查询时派生的虚拟待办,不落 `house_todo`,因此 `derived=true`、标签级 `todoId=null`。
|
||||
3. 当前房务持有的 active 住宿需求处于 `status=PROCESSING`、`houseStatus=PENDING_FINALIZE` 时,接口返回该派生项;最终确认后需求进入 `DONE/CONFIRMED`,该项从列表、筛选结果和统计中自然消失。
|
||||
4. `list[].todoTypes[]` 是一订单多标签的权威数据。点击 `PENDING_FINALIZE` 标签时必须使用该标签自己的 `requirementId`,不能依赖聚合行顶层 `requirementId`。
|
||||
5. 派生项不可调用待办 `RESOLVE`。用户应打开 `OrderDetailModal` 完成“最终确认”,成功后刷新待办列表与工作台仪表盘。
|
||||
6. 房务工作台 `GET /admin/profile/dashboard` 的 `todoSummary` 同步新增大写键 `PENDING_FINALIZE`。
|
||||
7. 本次没有修改 `hl-ui`;下文列出的现有前端筛选和标签级参数问题需由前端处理。
|
||||
|
||||
## 二、待办接口变化
|
||||
|
||||
### 2.1 请求
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/todos?scope=mine&status=OPEN&page=1&pageSize=20
|
||||
Authorization: Bearer <room-manager-token>
|
||||
```
|
||||
|
||||
只看“待最终确认”时:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/todos?scope=mine&status=OPEN&todoType=PENDING_FINALIZE&page=1&pageSize=20
|
||||
Authorization: Bearer <room-manager-token>
|
||||
```
|
||||
|
||||
查询参数名是 `todoType`,不是 `type`。`todoType` 支持逗号分隔多选。
|
||||
|
||||
### 2.2 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": null,
|
||||
"orderId": "2000000000000000001",
|
||||
"orderNo": "HL202607180001",
|
||||
"todoType": "PENDING_FINALIZE",
|
||||
"todoTypeLabel": "待最终确认",
|
||||
"title": "待最终确认",
|
||||
"urgency": "normal",
|
||||
"status": "OPEN",
|
||||
"derived": true,
|
||||
"requirementId": "2000000000000000101",
|
||||
"orderTodoCount": 1,
|
||||
"todoTypes": [
|
||||
{
|
||||
"typeCode": "PENDING_FINALIZE",
|
||||
"typeLabel": "待最终确认",
|
||||
"urgency": "normal",
|
||||
"count": 1,
|
||||
"derived": true,
|
||||
"todoId": null,
|
||||
"requirementId": "2000000000000000101",
|
||||
"unreadCount": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"stats": {
|
||||
"PENDING_FINALIZE": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段规则:
|
||||
|
||||
| 字段 | 前端规则 |
|
||||
|---|---|
|
||||
| `list[].todoTypes[]` | 一订单多待办类型的权威标签数组,必须完整渲染。 |
|
||||
| `todoTypes[].typeCode` | 新增可选值 `PENDING_FINALIZE`。 |
|
||||
| `todoTypes[].typeLabel` | 直接展示后端中文“待最终确认”。 |
|
||||
| `todoTypes[].derived` | `true` 表示虚拟待办,由源业务状态自然消失。 |
|
||||
| `todoTypes[].todoId` | `PENDING_FINALIZE` 固定为 `null`,禁止调用 `RESOLVE`。 |
|
||||
| `todoTypes[].requirementId` | 打开该标签对应房务详情时使用;雪花 ID 按字符串透传。 |
|
||||
| `list[].requirementId` | 只兼容聚合行主标签;一行多标签时不能替代标签级字段。 |
|
||||
| `stats.PENDING_FINALIZE` | 当前 scope 内“待最终确认”需求数;无数据也返回 `0`。 |
|
||||
|
||||
`stats` 是当前 scope 的完整分类计数,不因本次 `todoType` facet 收窄;因此筛选结果 `total` 可以是 `1`,同时其他统计槽仍保留其真实值。
|
||||
|
||||
### 2.3 生命周期
|
||||
|
||||
```text
|
||||
当前房务 + active requirement
|
||||
status=PROCESSING + houseStatus=PENDING_FINALIZE
|
||||
→ /todos 出现 PENDING_FINALIZE 派生标签
|
||||
→ 用户从该标签进入订单详情并执行最终确认
|
||||
→ status=DONE + houseStatus=CONFIRMED
|
||||
→ /todos、todoType facet、stats 和 dashboard 中该项均消失/归零
|
||||
```
|
||||
|
||||
该链路不创建 `house_todo` 记录,也不改变既有持久化待办的 RESOLVE 语义。
|
||||
|
||||
## 三、房务工作台统计变化
|
||||
|
||||
房务角色调用:
|
||||
|
||||
```http
|
||||
GET /admin/profile/dashboard?period=today
|
||||
Authorization: Bearer <room-manager-token>
|
||||
```
|
||||
|
||||
`data.todoSummary` 新增:
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 4,
|
||||
"PENDING_FINALIZE": 1
|
||||
}
|
||||
```
|
||||
|
||||
- 键名固定为大写 `PENDING_FINALIZE`,与待办接口 `stats`、`todoTypes[].typeCode` 共用同一常量。
|
||||
- 最终确认后该值归零;前端刷新列表时应同时刷新工作台数据或使对应查询缓存失效。
|
||||
|
||||
## 四、前端必须处理的现有问题
|
||||
|
||||
### 4.1 筛选器混用了“房务状态”和“待办类型”
|
||||
|
||||
当前 `src/views/housekeeper/todos/index.vue:220-227` 把以下值放在同一个 `STATUS_OPTIONS` 中:
|
||||
|
||||
```text
|
||||
HOTEL_REPLY_TIMEOUT / PENDING_ARRANGE /
|
||||
CLAIMING / IN_INQUIRY / PENDING_FINALIZE / EXCEPTION
|
||||
```
|
||||
|
||||
但同文件 `:370` 固定请求 `status=OPEN`,`:374` 又把所有非“全部”选项都作为 `todoType`,最终由 `:386` 请求待办接口。
|
||||
|
||||
- `HOTEL_REPLY_TIMEOUT`、`PENDING_ARRANGE`、`PENDING_FINALIZE` 是合法 `HouseTodoType`。
|
||||
- `CLAIMING`、`EXCEPTION` 是房务业务状态,不是待办类型,作为 `todoType` 请求会得到空结果。
|
||||
- `IN_INQUIRY` 已不再是顶层房务状态,也不是待办类型。
|
||||
|
||||
前端应删除这三个无效 `todoType` 选项;如产品确实需要按房务状态筛选,应另行使用声明支持该参数的数据源,不能继续混传给 `/todos.todoType`。
|
||||
|
||||
同时,`src/api/housekeeper/todos.js:53` 的注释仍写 `params.type`,实际页面和后端均使用 `params.todoType`;请同步修正文档注释,API 调用本身仍是 `:66-67` 的 `/v3/admin/order/todos`。
|
||||
|
||||
### 4.2 标签级 `requirementId` 在映射时丢失
|
||||
|
||||
当前 `src/views/housekeeper/todos/index.vue:290-313` 把 `todoTypes[]` 映射成 `reasonTags` 时只保留了 `typeCode/label/derived`,未保留标签自己的 `requirementId`;`:267` 和 `:453` 只使用聚合行顶层 `requirementId`。
|
||||
|
||||
一订单多标签时,顶层字段属于“主标签”,不保证就是用户点击的 `PENDING_FINALIZE` 标签。建议保留标签字段并按点击项分发:
|
||||
|
||||
```js
|
||||
const pendingFinalizeTag = row.todoTypes.find(
|
||||
(item) => item.typeCode === 'PENDING_FINALIZE'
|
||||
)
|
||||
|
||||
openOrderDetail({
|
||||
orderId: row.orderId,
|
||||
requirementId: pendingFinalizeTag?.requirementId,
|
||||
claimScope: 'mine',
|
||||
})
|
||||
```
|
||||
|
||||
实际组件仍应复用现有 `OrderDetailModal`,传入:
|
||||
|
||||
```vue
|
||||
<OrderDetailModal
|
||||
:order-id="orderId"
|
||||
:requirement-id="requirementId"
|
||||
claim-scope="mine"
|
||||
/>
|
||||
```
|
||||
|
||||
所有 ID 均按字符串处理,禁止转为 JavaScript `Number`。
|
||||
|
||||
### 4.3 完成动作
|
||||
|
||||
`PENDING_FINALIZE` 的处理入口是订单详情内“最终确认”,不是待办 `RESOLVE`:
|
||||
|
||||
1. 从点击标签取得 `orderId + todoTypes[].requirementId`。
|
||||
2. 以 `claimScope=mine` 打开 `OrderDetailModal`。
|
||||
3. 用户执行一次“最终确认”。
|
||||
4. 成功后刷新 `/v3/admin/order/todos` 和 `/admin/profile/dashboard`。
|
||||
5. 列表行、筛选计数和工作台徽章应同步消失/归零。
|
||||
|
||||
## 五、已完成的后端与测试环境验证
|
||||
|
||||
- PR #5059 已合并到 `dev-v3`,合并提交为 `989318c3925b`。
|
||||
- `hl-order-service-v3` TEST 部署任务 `fd8522fe` 成功,`8086/8186` 双实例健康。
|
||||
- `hl-user-service` TEST 部署任务 `372a9f3e` 成功,`8081/8181` 双实例健康。
|
||||
- 网关 API 实测:进入待最终确认前 `PENDING_FINALIZE=0`;测试需求进入 `PROCESSING/PENDING_FINALIZE` 后,列表、facet、`stats` 和 dashboard 均为 `1`;最终确认后均恢复为 `0`。
|
||||
- TEST DB 对账:派生项出现时没有新增 `house_todo(PENDING_FINALIZE)`;最终确认后需求为 `DONE/CONFIRMED`,两晚配房仍为 `CONFIRMED`,房务归属和配房数据均保留。
|
||||
- 真实调试 Chrome 已验证“待最终确认”标签可见、筛选只剩目标订单、最终确认后列表空态;浏览器验收仅作为前端对接参考,不改变上述接口契约。
|
||||
|
||||
## 六、前端验收清单
|
||||
|
||||
- [ ] “待处理”页仅以 `GET /v3/admin/order/todos` 为主数据源。
|
||||
- [ ] `STATUS_OPTIONS` 不再把 `CLAIMING/IN_INQUIRY/EXCEPTION` 作为 `todoType` 发送。
|
||||
- [ ] 使用 `todoType=PENDING_FINALIZE` 可筛出“待最终确认”订单。
|
||||
- [ ] 完整渲染 `list[].todoTypes[]`,显示后端 `typeLabel`。
|
||||
- [ ] 映射和点击事件保留 `todoTypes[].requirementId`。
|
||||
- [ ] 以 `orderId + 标签级 requirementId + claimScope=mine` 打开 `OrderDetailModal`。
|
||||
- [ ] 派生标签不调用待办 `RESOLVE`。
|
||||
- [ ] 最终确认成功后刷新待办列表和 dashboard,标签与计数同步消失。
|
||||
- [ ] 所有雪花 ID 均按字符串透传。
|
||||
@@ -0,0 +1,510 @@
|
||||
# 【#4938 前端对接·管理后台】车务创建、修改与最终确认返回订单调整基线差异
|
||||
|
||||
> Issue: [wx/HL#4938](https://git.1814.love:8443/wx/HL/issues/4938)
|
||||
>
|
||||
> PR: [wx/HL#5065](https://git.1814.love:8443/wx/HL/pulls/5065)
|
||||
>
|
||||
> 服务: `hl-fleet-service`
|
||||
>
|
||||
> 日期: 2026-07-18
|
||||
>
|
||||
> 影响范围: 车务直接派车、直接改派、`holding → assigned` 最终确认,以及订单调整后的日期、人数、车辆容量差异处理
|
||||
>
|
||||
> 状态: 后端已合并并部署测试环境;待管理后台按本文完成页面联调
|
||||
|
||||
## 一、前端对接结论
|
||||
|
||||
以下三个既有管理后台接口现在共用业务码 `605041` 返回结构化逐日差异:
|
||||
|
||||
| 操作 | 接口 | 触发 605041 的条件 |
|
||||
|---|---|---|
|
||||
| 创建派单 | `POST /admin/fleet/assignments` | `holdMode=0` 直接派车时最终基线不一致 |
|
||||
| 修改派单 | `POST /admin/fleet/assignments/{assignmentId}/change` | `holdMode=0` 直接改派时最终基线不一致 |
|
||||
| 最终确认 | `POST /admin/fleet/assignments/{assignmentId}/confirm` | 订单、需求、行程、逐日派单、人数或容量基线不一致 |
|
||||
|
||||
前端必须遵守以下判断:
|
||||
|
||||
1. 三个接口的 `605041` 当前均返回 HTTP 200,但统一响应体为 `code=605041`、`success=false`,不能只判断 HTTP 状态。
|
||||
2. `605041` 的 `data` 不为空:
|
||||
- create 返回 `AssignmentWriteRespVO`,保证 `data.dailyDifferences` 可读;
|
||||
- change 返回 `ChangeAssignmentRespVO`,保证 `data.dailyDifferences` 可读;
|
||||
- confirm 返回 `ConfirmRespVO`,保证 `data.confirmed=false` 和 `data.dailyDifferences` 可读。
|
||||
3. `605041` 不会提交派单及其关联业务状态写入:
|
||||
- create 不会新增或激活派单;
|
||||
- change 不会取消旧派单,也不会生成可用的新派车版本;
|
||||
- confirm 不会推进派单状态或确认时间;
|
||||
- 三者都不会触发车辆/司机占用、保险、对账或订单派定结果变化。
|
||||
- change 仍会按既有设计在独立事务保留一条 `CHANGE_FAILED` 操作审计;它不是有效派单版本,也不表示业务写入成功。
|
||||
4. 收到 `605041` 后保持操作前页面状态,展示逐日差异,并重新拉取最新订单和车务详情。
|
||||
5. 所有派单、车辆槽位、派车组、订单、车辆和司机雪花 ID 均按 JSON String 发送和读取,禁止转为 JavaScript `Number`。金额字段也按 String 读取。
|
||||
|
||||
## 二、605041 公共响应契约
|
||||
|
||||
### 2.1 统一响应外层
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605041,
|
||||
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||
"data": {
|
||||
"dailyDifferences": [
|
||||
{
|
||||
"serviceDate": "2026-07-22",
|
||||
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": 5,
|
||||
"capacityGap": 3,
|
||||
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938f09a001",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `data` 的完整类型取决于调用的是 create、change 还是 confirm,不能跨接口复用成功响应模型。
|
||||
- 除本文件明确保证的失败字段外,其余成功态字段在 `605041` 时为空,前端不得用它们推断写入结果。
|
||||
- `traceId` 用于反馈和日志定位,不参与业务判断。
|
||||
|
||||
### 2.2 `dailyDifferences[]`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `serviceDate` | String/null | 发生差异的服务日,格式 `yyyy-MM-dd`;无法定位到单日时为空 |
|
||||
| `differenceType` | String | 差异类型,取值见下表 |
|
||||
| `assignmentId` | String/null | 可定位到具体派单时返回;仅用于定位差异,不代表本次写入成功 |
|
||||
| `assignmentSlotId` | String/null | 可定位到稳定车辆槽位时返回 |
|
||||
| `passengerCount` | Integer/null | 当前比对使用的乘客人数,不含司机 |
|
||||
| `passengerCapacity` | Integer/null | 当日车辆合计可载客人数,每辆车已扣除司机一座 |
|
||||
| `capacityGap` | Integer/null | 缺少座位数,等于 `passengerCount - passengerCapacity` |
|
||||
| `message` | String | 后端生成的差异说明,可直接辅助展示 |
|
||||
|
||||
差异类型:
|
||||
|
||||
| `differenceType` | 含义 |
|
||||
|---|---|
|
||||
| `REQUIREMENT_VERSION_MISMATCH` | 当前生效用车需求已变化,或订单/需求已不可继续派单 |
|
||||
| `ORDER_DATE_MISMATCH` | 订单当前日期与用车需求冻结日期不一致 |
|
||||
| `ITINERARY_DATE_MISMATCH` | 逐日行程日期与用车需求冻结日期不一致,或缺少逐日行程 |
|
||||
| `ASSIGNMENT_DATE_MISSING` | 某服务日缺少有效派单或车辆槽位 |
|
||||
| `ASSIGNMENT_DATE_EXTRA` | 派单仍包含已不属于当前需求的服务日 |
|
||||
| `HEADCOUNT_BASELINE_MISMATCH` | 订单当前人数、需求冻结人数或派单人数快照不一致 |
|
||||
| `CAPACITY_INSUFFICIENT` | 当日所有车辆合计载客量不足 |
|
||||
|
||||
`dailyDifferences` 可能同时包含多种类型、多条服务日记录。前端应遍历数组展示,不得只取第一条,也不得自行重算人数或车辆容量。
|
||||
|
||||
## 三、创建派单 create
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2078001000000000101",
|
||||
"orderNo": "26-0719",
|
||||
"requirementId": "2078001000000000201",
|
||||
"fleetItemIndex": 1,
|
||||
"vehicleId": "2078001000000000301",
|
||||
"driverId": "2078001000000000401",
|
||||
"startDate": "2026-07-21",
|
||||
"endDate": "2026-07-23",
|
||||
"pickupAt": "海拉尔",
|
||||
"dropoffAt": "满洲里",
|
||||
"headcount": 8,
|
||||
"protocolPrice": "1300.00",
|
||||
"holdMode": 0,
|
||||
"fromEntry": "from-board",
|
||||
"skipCityJunctionException": false,
|
||||
"strictSeats": true,
|
||||
"confirmCrossResident": false,
|
||||
"requestId": "fleet-create-4938-20260719-001"
|
||||
}
|
||||
```
|
||||
|
||||
`605041` 只适用于 `holdMode=0`。原有 `holdMode=1` 排车锁定流程不执行本次最终基线门禁。
|
||||
|
||||
### 3.2 holdMode=0 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2078001000000000501",
|
||||
"assignmentGroupId": "2078001000000000601",
|
||||
"assignmentSlotId": "2078001000000000701",
|
||||
"assignmentStatus": "assigned",
|
||||
"stageCode": "assigned",
|
||||
"stageLabel": "已派车",
|
||||
"currentStep": 4,
|
||||
"skippedStepCodes": [
|
||||
"DRIVER_CONFIRMATION",
|
||||
"DRIVER_CONFIRMATION_EVIDENCE"
|
||||
],
|
||||
"protocolPrice": "1300.00",
|
||||
"holdSentAt": null,
|
||||
"confirmedAt": "2026-07-19 14:20:00",
|
||||
"sideEffects": {
|
||||
"vehicleStatusUpdated": "busy",
|
||||
"driverStatusUpdated": "busy",
|
||||
"reconPrepRowsCreated": 0,
|
||||
"reconPrepMarkedCanceled": null
|
||||
},
|
||||
"dailyDifferences": null
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938c200001",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
仅在 `code=200` 时把新派单加入页面;`id`、`assignmentGroupId`、`assignmentSlotId` 均按 String 保存。
|
||||
|
||||
### 3.3 605041 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605041,
|
||||
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||
"data": {
|
||||
"id": null,
|
||||
"assignmentGroupId": null,
|
||||
"assignmentSlotId": null,
|
||||
"assignmentStatus": null,
|
||||
"stageCode": null,
|
||||
"stageLabel": null,
|
||||
"currentStep": null,
|
||||
"skippedStepCodes": null,
|
||||
"protocolPrice": null,
|
||||
"holdSentAt": null,
|
||||
"confirmedAt": null,
|
||||
"sideEffects": null,
|
||||
"dailyDifferences": [
|
||||
{
|
||||
"serviceDate": "2026-07-22",
|
||||
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": 5,
|
||||
"capacityGap": 3,
|
||||
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938c605041",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
此时不得把临时响应内容加入派单列表,不得本地占用车辆/司机;刷新后仍以服务端最新详情为准。
|
||||
|
||||
## 四、修改派单 change
|
||||
|
||||
### 4.1 请求
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/2078001000000000501/change
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"effectiveDate": "2026-07-22",
|
||||
"newVehicleId": "2078001000000000302",
|
||||
"newDriverId": "2078001000000000402",
|
||||
"holdMode": 0,
|
||||
"protocolPrice": "1688.00",
|
||||
"confirmCrossResident": false,
|
||||
"reason": "订单调整后更换车辆和司机",
|
||||
"requestId": "fleet-change-4938-20260719-001"
|
||||
}
|
||||
```
|
||||
|
||||
`newVehicleId`、`newDriverId` 至少传一个。`605041` 只适用于 `holdMode=0`;`holdMode=1` 仍按排车待司机确认流程处理。
|
||||
|
||||
### 4.2 holdMode=0 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"assignmentId": "2078001000000000502",
|
||||
"assignmentSlotId": "2078001000000000701",
|
||||
"previousAssignmentGroupId": "2078001000000000601",
|
||||
"newAssignmentGroupId": "2078001000000000602",
|
||||
"assignmentStatus": "assigned",
|
||||
"effectiveDate": "2026-07-22",
|
||||
"affectedDays": 2,
|
||||
"protocolPrice": "1688.00",
|
||||
"otherVehicleCount": 1,
|
||||
"warningCode": "ORDER_HAS_OTHER_VEHICLES",
|
||||
"warningMessage": "该订单另有1个车辆槽位,当前仅修改本车辆,请核对其它车辆安排",
|
||||
"otherVehicles": [
|
||||
{
|
||||
"assignmentSlotId": "2078001000000000702",
|
||||
"vehiclePlate": "蒙B-66666",
|
||||
"driverName": "李师傅",
|
||||
"startDate": "2026-07-21",
|
||||
"endDate": "2026-07-23"
|
||||
}
|
||||
],
|
||||
"dailyDifferences": null
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938a200001",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
change 成功响应没有 `confirmed` 和 `sideEffects` 字段。只有 `code=200` 时才能用新派车组替换页面中的旧版本;`warningCode=ORDER_HAS_OTHER_VEHICLES` 时继续保留既有强提示。
|
||||
|
||||
### 4.3 605041 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605041,
|
||||
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||
"data": {
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"previousAssignmentGroupId": null,
|
||||
"newAssignmentGroupId": null,
|
||||
"assignmentStatus": null,
|
||||
"effectiveDate": null,
|
||||
"affectedDays": null,
|
||||
"protocolPrice": null,
|
||||
"otherVehicleCount": null,
|
||||
"warningCode": null,
|
||||
"warningMessage": null,
|
||||
"otherVehicles": null,
|
||||
"dailyDifferences": [
|
||||
{
|
||||
"serviceDate": null,
|
||||
"differenceType": "HEADCOUNT_BASELINE_MISMATCH",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": null,
|
||||
"capacityGap": null,
|
||||
"message": "订单当前人数与用车需求冻结人数不一致"
|
||||
},
|
||||
{
|
||||
"serviceDate": "2026-07-22",
|
||||
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": 5,
|
||||
"capacityGap": 3,
|
||||
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938a605041",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
此时旧派单和原派车组仍是有效业务状态。后端可能新增一条 `CHANGE_FAILED` 操作审计,但不会生成可用的新派车版本。不得用 `dailyDifferences[].assignmentId` 替换页面主键,也不得本地切换车辆、司机或状态。
|
||||
|
||||
## 五、最终确认 confirm
|
||||
|
||||
### 5.1 请求
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/2078001000000000501/confirm
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"requestId": "fleet-final-confirm-4938-20260719-001"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `assignmentId` | Path | String | 是 | 有效派单 ID | 当前派车组中的任一派单 ID |
|
||||
| `requestId` | Body | String | 是 | 非空,最长 64 | 最终确认幂等标识 |
|
||||
|
||||
### 5.2 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"confirmed": true,
|
||||
"assignmentStatus": "assigned",
|
||||
"stageCode": "assigned",
|
||||
"stageLabel": "已派车",
|
||||
"currentStep": 4,
|
||||
"assignmentGroupId": "2078001000000000601",
|
||||
"confirmedAt": "2026-07-19 14:30:00",
|
||||
"itineraryUrl": "https://h5.example.com/#/itinerary/<signed-token>",
|
||||
"sideEffects": {
|
||||
"vehicleStatusUpdated": "busy",
|
||||
"driverStatusUpdated": "busy",
|
||||
"reconPrepRowsCreated": 0,
|
||||
"reconPrepMarkedCanceled": null
|
||||
},
|
||||
"dailyDifferences": null
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938f200001",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
`itineraryUrl` 签发配置不可用时允许为空,不影响确认成功。前端只有在 `code=200 && data.confirmed===true` 时展示最终确认成功,并刷新派单详情、看板列表和汇总。
|
||||
|
||||
### 5.3 日期、人数与容量同时存在差异
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605041,
|
||||
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||
"data": {
|
||||
"confirmed": false,
|
||||
"assignmentStatus": null,
|
||||
"stageCode": null,
|
||||
"stageLabel": null,
|
||||
"currentStep": null,
|
||||
"assignmentGroupId": null,
|
||||
"confirmedAt": null,
|
||||
"itineraryUrl": null,
|
||||
"sideEffects": null,
|
||||
"dailyDifferences": [
|
||||
{
|
||||
"serviceDate": "2026-07-21",
|
||||
"differenceType": "ORDER_DATE_MISMATCH",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": null,
|
||||
"passengerCapacity": null,
|
||||
"capacityGap": null,
|
||||
"message": "订单当前日期与用车需求冻结日期不一致"
|
||||
},
|
||||
{
|
||||
"serviceDate": null,
|
||||
"differenceType": "HEADCOUNT_BASELINE_MISMATCH",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": null,
|
||||
"capacityGap": null,
|
||||
"message": "订单当前人数与用车需求冻结人数不一致"
|
||||
},
|
||||
{
|
||||
"serviceDate": "2026-07-22",
|
||||
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": 8,
|
||||
"passengerCapacity": 5,
|
||||
"capacityGap": 3,
|
||||
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938f605041",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 缺少某日车辆槽位
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605041,
|
||||
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||
"data": {
|
||||
"confirmed": false,
|
||||
"assignmentStatus": null,
|
||||
"stageCode": null,
|
||||
"stageLabel": null,
|
||||
"currentStep": null,
|
||||
"assignmentGroupId": null,
|
||||
"confirmedAt": null,
|
||||
"itineraryUrl": null,
|
||||
"sideEffects": null,
|
||||
"dailyDifferences": [
|
||||
{
|
||||
"serviceDate": "2026-07-23",
|
||||
"differenceType": "ASSIGNMENT_DATE_MISSING",
|
||||
"assignmentId": null,
|
||||
"assignmentSlotId": null,
|
||||
"passengerCount": null,
|
||||
"passengerCapacity": null,
|
||||
"capacityGap": null,
|
||||
"message": "该服务日缺少第2个车辆槽位派单"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": "7db459fd-0b8e-4f29-9c46-4938f605042",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 六、前端统一处理顺序
|
||||
|
||||
1. 先判断业务 `code`,再读取端点自己的 `data`:
|
||||
- `code=200`:按对应成功模型处理;
|
||||
- `code=605041`:按对应失败模型读取 `dailyDifferences`;
|
||||
- 其他业务码:继续走既有错误处理。
|
||||
2. `605041` 时不要乐观更新:
|
||||
- create 不新增本地派单;
|
||||
- change 不替换旧派单;
|
||||
- confirm 不改为 `assigned` 或“已确认”。
|
||||
3. 展示顶层 `message`,并按 `dailyDifferences` 列出服务日、差异类型、人数、容量和缺口;字段为空时隐藏对应展示项。
|
||||
4. 重新请求最新订单和车务详情,避免继续使用操作前缓存。
|
||||
5. 用户修正订单日期、行程、人数或派单后,生成新的 `requestId` 再提交新动作;仅在同一次动作网络结果不确定时复用原 `requestId`。
|
||||
|
||||
## 七、其他直接错误分支
|
||||
|
||||
| 业务码 | 常见场景 | 前端处理 |
|
||||
|---|---|---|
|
||||
| `605009` | 派单不存在 | 刷新详情和看板,停止操作旧记录 |
|
||||
| `605020` | 当前状态不允许操作 | 刷新最新生命周期与操作能力 |
|
||||
| `605025` | 最终确认前缺少司机确认或有效凭证 | 引导先完成司机确认与凭证登记 |
|
||||
| `605001` / `605003` | 车辆或司机档期冲突 | 展示后端错误并重新选车/司机 |
|
||||
| `605036` | 跨常驻车未显式确认 | 二次提示后携带 `confirmCrossResident=true` 重试 |
|
||||
| `605041` | 订单、需求、行程、逐日派单、人数或容量基线不一致 | 使用当前端点的 `data.dailyDifferences` 展示并处理 |
|
||||
|
||||
请求字段为空、格式不正确或超过长度限制时走统一参数校验错误,前端应在发请求前完成同样约束。
|
||||
|
||||
## 八、不影响范围
|
||||
|
||||
- 不新增管理后台接口,三个接口的请求字段结构保持不变。
|
||||
- `holdMode=1` 的排车锁定、司机确认与凭证登记流程保持不变。
|
||||
- 不改变取消、司机拒接、驳回需求、撤销取消和提前完结的前端调用契约。
|
||||
- 不要求前端计算订单人数、逐日服务日期或车辆载客量,这些均由后端权威校验并返回差异。
|
||||
- 本文件只描述管理后台直接消费的 HTTP 契约,不包含服务间调用或发布实现细节。
|
||||
|
||||
## 九、验收状态与待补证据
|
||||
|
||||
已完成:
|
||||
|
||||
- 当前 worktree 中 create、change、confirm Controller 的 `605041` 强类型 `data` 静态核对。
|
||||
- `AssignmentWriteRespVO`、`ChangeAssignmentRespVO`、`ConfirmRespVO` 与 `dailyDifferences` 字段静态核对。
|
||||
- 三条失败路径不提交派单及其关联业务状态变化的源码顺序核对。
|
||||
- 后端 PR #5065 已合并至 `dev-v3`;Fleet 双实例 `8087/8187` 已完成滚动部署并通过健康/Nacos 验证。
|
||||
- 最终源码指纹下 Fleet `verify` 1909/1909、Order-v3 受影响回归 438/438、User Quartz 桥接 5/5 均通过。
|
||||
|
||||
前端联调仍需补充:
|
||||
|
||||
- 三个接口在测试环境 OpenAPI 中的请求/响应模型截图或导出差异。
|
||||
- 经网关分别取得 create、change、confirm 的成功响应和 `605041` 真实响应,记录 HTTP 状态、业务码、`traceId` 与完整 `data`。
|
||||
- 对 `605041` 前后做测试业务数据对照,确认派单版本/状态、车辆司机占用、保险、对账和订单派定结果未发生变化。
|
||||
- 管理后台页面联调证据:差异列表展示、空字段处理、刷新行为、禁止乐观更新,以及修正后重新提交成功。
|
||||
@@ -0,0 +1,83 @@
|
||||
# 【修改接口·管理后台】订单工作台统计口径与金额字符串收口(#5062)
|
||||
|
||||
> Issue: [wx/HL#5062](https://git.1814.love:8443/wx/HL/issues/5062)
|
||||
>
|
||||
> 服务: `hl-user-service`、`hl-order-service-v3`
|
||||
>
|
||||
> 日期: 2026-07-18
|
||||
>
|
||||
> 影响入口: `GET /admin/profile/dashboard?period={today|week|month}`
|
||||
|
||||
## 一、前端结论
|
||||
|
||||
1. 路径、请求参数和角色分流不变,不需要新增接口调用。
|
||||
2. GMV/收入改按真实收款时间归属:线上只统计成功支付,线下只统计未撤销收款;不再按订单创建时间归属订单累计实付。
|
||||
3. 退款改按真实成功退款时间归属;财务近 30 天趋势返回真实每日收入和退款。
|
||||
4. 所有金额字段固定按 JSON String 处理;比例 `gmvDiffRate` 仍为 JSON Number。
|
||||
5. 雪花 ID(例如排行 `adminId`、即将出行 `orderId`)固定按 JSON String 处理,禁止转换为 JavaScript `Number`。
|
||||
6. 排行订单数为期间发生有效收款的订单去重数,同一订单多笔收款只计一单、金额全部累加。
|
||||
7. 权威统计源不可用时接口失败关闭,不会用部分成功数据或全零数据伪装成功。
|
||||
|
||||
## 二、受影响字段
|
||||
|
||||
### 2.1 ADMIN / CUSTOMIZER 工作台
|
||||
|
||||
| 字段 | JSON 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `overview.gmv` | String | 当前 period 内真实收款金额 |
|
||||
| `overview.gmvDiffRate` | Number | 与上一等长期间相比的变化比例 |
|
||||
| `trend[].gmv` | String | 对应日期的真实收款金额 |
|
||||
| `ranking[].adminId` | String | 定制师雪花 ID |
|
||||
| `ranking[].gmv` | String | 对应定制师期间真实收款金额 |
|
||||
| `ranking[].orderCount` | Number | 发生有效收款的去重订单数 |
|
||||
| `ranking[].avatar` | String/null | 定制师头像;用户信息降级时允许为空 |
|
||||
| `upcomingTrips[].orderId` | String | 订单雪花 ID |
|
||||
|
||||
### 2.2 FINANCE 工作台
|
||||
|
||||
| 字段 | JSON 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `periodIncome` | String | 当前 period 内真实收入 |
|
||||
| `periodRefund` | String | 当前 period 内成功退款 |
|
||||
| `monthIncome` | String | 自然月真实收入 |
|
||||
| `monthRefund` | String | 自然月成功退款 |
|
||||
| `financeTrend[].date` | String | 日期,`yyyy-MM-dd` |
|
||||
| `financeTrend[].income` | String | 当日真实收入 |
|
||||
| `financeTrend[].refund` | String | 当日成功退款 |
|
||||
|
||||
## 三、响应片段
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"role": "FINANCE",
|
||||
"periodIncome": "128000.00",
|
||||
"periodRefund": "5600.00",
|
||||
"monthIncome": "328000.00",
|
||||
"monthRefund": "8600.00",
|
||||
"financeTrend": [
|
||||
{
|
||||
"date": "2026-07-18",
|
||||
"income": "12000.00",
|
||||
"refund": "600.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 四、前端检查清单
|
||||
|
||||
- [ ] 金额展示使用字符串格式化,不执行 `Number(amount)`。
|
||||
- [ ] `gmvDiffRate` 继续按 Number 计算百分比。
|
||||
- [ ] 所有 Long ID 保持字符串透传到路由和请求参数。
|
||||
- [ ] 不再用订单创建日解释趋势 GMV;趋势日期是支付/收款发生日。
|
||||
- [ ] 财务趋势同时渲染 `income` 与 `refund`,空日后端返回 `"0.00"`。
|
||||
- [ ] 接口业务失败时展示重试,不把缺失统计源当作全零成功。
|
||||
|
||||
## 五、后端验证
|
||||
|
||||
- Dashboard、User 聚合、支付/退款 Feign、序列化与失败关闭相关测试已通过。
|
||||
- Issue #5062 最终五模块全量测试:14,518 个测试,0 失败、0 错误;Fleet Reactor verify:1,824 个测试,0 失败、0 错误、0 跳过。
|
||||
@@ -0,0 +1,73 @@
|
||||
# 房务 bug房务:管理后台修复清单
|
||||
|
||||
## 来源
|
||||
|
||||
桌面文件:`bug房务.docx`。
|
||||
|
||||
## 后端当前状态
|
||||
|
||||
`dev-v3` 已包含房务需求版本、改期平移、库存原子迁移、增减晚次、人数变化、作废需求保护、返工待办互斥和最终确认动作契约修复。TEST 回归数据由 Codex 生成,5 条王骁订单已进入 `PENDING / PENDING_CLAIM` 抢单池。
|
||||
|
||||
## 管理后台必须修复
|
||||
|
||||
### 1. 酒店多房型展示
|
||||
|
||||
当定制师选择同一酒店的多个房型时,房务卡片必须按 `hotelId + roomTypeId` 展开,禁止把多个房型合并为一个房型后叠加房间数。展示的房型名称、房间数、价格和晚次必须与需求明细逐项对应。
|
||||
|
||||
### 2. 待办标签颜色
|
||||
|
||||
按后端 `todoType` 使用统一颜色:待配房、需求变更重配、待最终确认、酒店超时、异常/取消必须视觉可区分;不能只显示文字而丢失优先级。
|
||||
|
||||
### 3. 指定酒店但不指定房型
|
||||
|
||||
酒店已指定、房型为空时,仍应允许进入房务流程;候选列表限定指定酒店,房型由房务选择。不能把“房型为空”误判为需求无效。
|
||||
|
||||
### 4. 最终确认后修改
|
||||
|
||||
已最终确认订单进入详情后,仍需显示“修改/替换酒店、调整房型、修改房间数、清空配房”入口。操作前调用重新询房/重开接口,成功后刷新详情;不能在前端用 `m.finalized` 直接隐藏或禁用所有修改入口。
|
||||
|
||||
重点文件:`src/views/housekeeper/components/OrderDetailModal.vue` 中 `canClearAssignments`、修改入口和 `ensureRequirementEditable` 的状态判断必须统一。
|
||||
|
||||
### 5. 作废需求展示
|
||||
|
||||
作废需求必须展示:
|
||||
|
||||
- 作废状态和红色视觉标记
|
||||
- 准确易懂的作废原因,例如“定制师修改住宿需求,原房务需求已作废”
|
||||
- 仅保留“查看”操作
|
||||
- 隐藏领取、配房、替换、清空、确认、最终确认等所有写操作
|
||||
|
||||
### 6. 改出发日期
|
||||
|
||||
改期后页面必须展示新日期;当前有效晚次的配房日期由后端按 `dayNumber` 对齐。原日期库存先恢复,新日期库存全部预占成功后才提交;失败时订单、配房和库存保持原状。页面必须展示“已改期,配房需按新日期重新确认”的说明。
|
||||
|
||||
### 7. 增加出行人数
|
||||
|
||||
订单调整摘要和房务详情必须显示“增加 X 人”,同时展示调整前人数、调整后人数和新增出行人;既有酒店、房型、房间数和库存字段不得丢失。
|
||||
|
||||
### 8. 增加行程天数
|
||||
|
||||
必须展示“新增第 N 晚住宿,新增日期待配房”;原日期配房保留,新增晚次为空白候选,未完成新增晚次时禁止最终确认。
|
||||
|
||||
## 验收订单
|
||||
|
||||
使用以下 5 条 TEST 订单逐项验证:
|
||||
|
||||
- `HL20260719092421973`
|
||||
- `HL20260719092425403`
|
||||
- `HL20260719092428569`
|
||||
- `HL20260719092431853`
|
||||
- `HL20260719092435001`
|
||||
|
||||
每条订单均为定制师“王骁”,已模拟支付、补全出行人并提交住宿需求。
|
||||
|
||||
## 验收要求
|
||||
|
||||
必须同时提供:
|
||||
|
||||
1. 房务首页截图
|
||||
2. 待办列表截图
|
||||
3. 订单详情截图
|
||||
4. 改期/增人/增晚前后对比截图
|
||||
5. 浏览器 Network 请求确认只提交 `dayNumber`,不由前端提交 `stayDate`
|
||||
6. 最终确认后订单从待办和工作台消失
|
||||
@@ -0,0 +1,244 @@
|
||||
# 【修改接口·管理后台】核团核算状态改用 `review_status`(#5066)
|
||||
|
||||
> **PR**: [#5067](https://git.1814.love:8443/wx/HL/pulls/5067) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-19 10:22
|
||||
|
||||
## 1. 关键变化
|
||||
|
||||
> ⚠️ 两个接口的字段名 `settlementStatus` / `settlementStatusName` 均保持不变,但字段的数据来源、可选枚举和业务语义已经变化。前端不得继续复用财务结算状态字典。
|
||||
|
||||
- 核团核算状态的数据来源由 `order_main.settlement_status` 改为 `order_main.review_status`。
|
||||
- 页面状态统一为:
|
||||
- `PENDING`:待核算
|
||||
- `IN_PROGRESS`:核算中
|
||||
- `COMPLETED`:已完成
|
||||
- 列表查询参数名仍为 `settlementStatus`,但合法值改为 `PENDING / IN_PROGRESS / COMPLETED`。
|
||||
- 旧值 `NONE` 不再是合法查询参数;历史 `review_status = NONE / NULL` 的订单统一投影为 `PENDING / 待核算`。
|
||||
- 本次仅调整核团页面的查询和返回投影,不修改财务复核及结算完成所使用的 `settlement_status`。
|
||||
|
||||
## 2. 变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 核团核算任务列表 | GET | `/v3/admin/order-settlement/tasks` | 修改接口 | 筛选和返回状态改用 `review_status`;参数名 `settlementStatus` 保持不变 |
|
||||
| 2 | 查询核团详情 | GET | `/v3/admin/order/{orderId}/settlement/return-detail` | 修改接口 | `orderInfo` 中的核算状态改用 `review_status` 投影 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 核团核算任务列表
|
||||
|
||||
`GET /v3/admin/order-settlement/tasks`
|
||||
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:是,只读查询。
|
||||
- **请求体**:无。
|
||||
- **响应结构**:`Result<PageResult<SettlementTaskRespVO>>`。
|
||||
- 分页、关键词、出发日期筛选和列表范围均保持不变。
|
||||
|
||||
#### Query 入参
|
||||
|
||||
| 字段 | 类型 | 必填 | 合法值 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `settlementStatus` | string | 否 | `PENDING` / `IN_PROGRESS` / `COMPLETED` | 核团页面核算状态;字段名保留,实际筛选 `order_main.review_status` |
|
||||
|
||||
其他 Query 参数保持不变:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `page` | number | 否 | 当前页码,默认 `1` |
|
||||
| `pageSize` | number | 否 | 每页条数,默认 `20`,范围 `1` 到 `100` |
|
||||
| `keyword` | string | 否 | 按订单号、团号、产品名模糊查询 |
|
||||
| `departureDateFrom` | string | 否 | 出发日期开始,格式 `yyyy-MM-dd` |
|
||||
| `departureDateTo` | string | 否 | 出发日期结束,格式 `yyyy-MM-dd` |
|
||||
|
||||
#### 受影响的响应字段
|
||||
|
||||
| 字段 | JSON 类型 | 修改后说明 |
|
||||
|---|---|---|
|
||||
| `data.records[].settlementStatus` | string | 核团核算状态:`PENDING / IN_PROGRESS / COMPLETED` |
|
||||
| `data.records[].settlementStatusName` | string | 页面文案:`待核算 / 核算中 / 已完成` |
|
||||
|
||||
列表中的其他字段、分页结构和排序规则均保持不变。
|
||||
|
||||
#### 请求与响应示例
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&settlementStatus=IN_PROGRESS
|
||||
Authorization: Bearer <JWT>
|
||||
```
|
||||
|
||||
**响应片段**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"orderId": "2077233886248534018",
|
||||
"orderNo": "HL202607180001",
|
||||
"teamNo": "T20260718001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"departureDate": "2026-07-20",
|
||||
"returnDate": "2026-07-24",
|
||||
"peopleCount": 3,
|
||||
"peopleSummary": "2成人1婴儿",
|
||||
"systemBalanceAmount": 0.00,
|
||||
"settlementStatus": "IN_PROGRESS",
|
||||
"settlementStatusName": "核算中"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 查询核团详情
|
||||
|
||||
`GET /v3/admin/order/{orderId}/settlement/return-detail`
|
||||
|
||||
- **认证**:需要管理后台 JWT。
|
||||
- **幂等性**:是,只读查询。
|
||||
- **请求体**:无。
|
||||
- **路径参数和响应整体结构保持不变。**
|
||||
|
||||
#### 受影响的响应字段
|
||||
|
||||
| 字段 | JSON 类型 | 修改后说明 |
|
||||
|---|---|---|
|
||||
| `data.orderInfo.settlementStatus` | string | 核团核算状态:`PENDING / IN_PROGRESS / COMPLETED`,来源为 `review_status` |
|
||||
| `data.orderInfo.settlementStatusName` | string | 页面文案:`待核算 / 核算中 / 已完成` |
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"orderInfo": {
|
||||
"orderId": "2077233886248534018",
|
||||
"orderNo": "HL202607180001",
|
||||
"settlementStatus": "COMPLETED",
|
||||
"settlementStatusName": "已完成"
|
||||
},
|
||||
"travelers": [],
|
||||
"driverVehicles": [],
|
||||
"receivableItems": [],
|
||||
"collectionRecords": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 示例仅展示本次相关字段;详情接口原有的订单信息、出行人、司机车辆、应收和收款字段均保持不变。
|
||||
|
||||
## 4. 枚举与状态映射
|
||||
|
||||
| `settlementStatus` | `settlementStatusName` | 核团页面语义 |
|
||||
|---|---|---|
|
||||
| `PENDING` | 待核算 | 尚未开始核算 |
|
||||
| `IN_PROGRESS` | 核算中 | 已开始录入或处理核算数据 |
|
||||
| `COMPLETED` | 已完成 | 核单已经提交完成 |
|
||||
|
||||
### 历史数据兼容
|
||||
|
||||
| `order_main.review_status` 实际值 | 接口返回 `settlementStatus` | 接口返回 `settlementStatusName` |
|
||||
|---|---|---|
|
||||
| `NULL`、空值或 `NONE` | `PENDING` | 待核算 |
|
||||
| `PENDING` | `PENDING` | 待核算 |
|
||||
| `IN_PROGRESS` | `IN_PROGRESS` | 核算中 |
|
||||
| `COMPLETED` | `COMPLETED` | 已完成 |
|
||||
|
||||
### 列表筛选规则
|
||||
|
||||
| Query 参数 | 后端筛选行为 |
|
||||
|---|---|
|
||||
| 不传 `settlementStatus` | 不追加核算状态过滤,返回符合其他条件的任务 |
|
||||
| `PENDING` | 匹配 `review_status = PENDING / NONE / NULL`,兼容历史订单 |
|
||||
| `IN_PROGRESS` | 精确匹配 `review_status = IN_PROGRESS` |
|
||||
| `COMPLETED` | 精确匹配 `review_status = COMPLETED` |
|
||||
| `NONE` 或其他值 | 参数校验失败,HTTP 200、业务码 `400` |
|
||||
|
||||
## 5. 修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 接口字段名 | `settlementStatus` / `settlementStatusName` | 保持不变 |
|
||||
| 状态数据源 | 财务结算态 `settlement_status` | 核团核算流程态 `review_status` |
|
||||
| 查询参数枚举 | `NONE / PENDING / COMPLETED` | `PENDING / IN_PROGRESS / COMPLETED` |
|
||||
| `PENDING` 文案/语义 | 待财务复核 | 待核算 |
|
||||
| `COMPLETED` 文案/语义 | 已结算 | 已完成 |
|
||||
| 处理中状态 | 无独立值 | 新增 `IN_PROGRESS / 核算中` |
|
||||
| 历史 `NONE / NULL` 返回值 | `NONE / 未结算` | 归一为 `PENDING / 待核算` |
|
||||
|
||||
## 6. 前端适配清单
|
||||
|
||||
- [ ] 核团状态下拉改为 `PENDING / IN_PROGRESS / COMPLETED`。
|
||||
- [ ] 下拉文案依次使用“待核算 / 核算中 / 已完成”。
|
||||
- [ ] 删除核团页面向接口传递 `NONE` 的逻辑。
|
||||
- [ ] 不修改 Query 参数名,继续传 `settlementStatus`。
|
||||
- [ ] 不修改响应字段名,继续读取 `settlementStatus` 和 `settlementStatusName`。
|
||||
- [ ] 不再复用财务结算状态字典解释这两个核团接口。
|
||||
- [ ] 若前端自行维护状态文案,必须同步更新;优先使用后端返回的 `settlementStatusName`。
|
||||
- [ ] 对历史未开始核算的订单统一按 `PENDING / 待核算` 展示。
|
||||
|
||||
## 7. 错误与边界行为
|
||||
|
||||
| 场景 | 行为 |
|
||||
|---|---|
|
||||
| 未传 `settlementStatus` | 正常查询,不按核算状态过滤 |
|
||||
| 传 `settlementStatus=NONE` | 参数校验失败,HTTP 200、业务码 `400` |
|
||||
| 传其他非法状态 | 参数校验失败,HTTP 200、业务码 `400` |
|
||||
| 历史 `review_status=NONE/NULL` | 列表和详情均返回 `PENDING / 待核算` |
|
||||
| 房务角色访问 | 保持原权限规则,不因本次变更放开 |
|
||||
| 订单不存在 | 详情接口保持原订单不存在错误 |
|
||||
| 空列表 | 返回成功响应,`records=[]` |
|
||||
|
||||
## 8. 不影响范围
|
||||
|
||||
- 财务复核和财务结算完成仍使用 `order_main.settlement_status`。
|
||||
- 核单提交后写入 `settlement_status=PENDING`、财务确认后写入 `settlement_status=COMPLETED` 的流程不变。
|
||||
- 两个接口的 URL、HTTP 方法、认证方式、分页结构和其他字段均不变。
|
||||
- 不涉及数据库表结构或数据迁移。
|
||||
- 不影响核团详情中的出行人、司机车辆、应收明细和收款明细契约。
|
||||
- 不影响其他财务页面对 `settlementStatus` 的既有使用;本次语义仅适用于本文列出的两个核团接口。
|
||||
|
||||
## 9. 影响评估与回滚
|
||||
|
||||
- **字段结构是否破坏兼容**:否,字段名和 JSON 类型不变。
|
||||
- **业务语义是否变化**:是,同名字段的数据来源、枚举和中文含义均发生变化。
|
||||
- **前端是否需要同步适配**:是,核团状态下拉和本地状态字典必须同步。
|
||||
- **是否影响已有数据**:不改写已有数据;读取时兼容历史 `NONE / NULL`。
|
||||
- 回滚 PR #5067 后,两个接口会重新使用旧的财务结算状态语义。
|
||||
- 因 `PENDING`、`COMPLETED` 是同名但不同含义的值,前后端版本回滚必须同步,不能仅根据字段是否存在判断版本。
|
||||
- 无数据库迁移,无需清理或恢复数据。
|
||||
|
||||
## 10. 后端验证与发布状态
|
||||
|
||||
- PR #5067 原定向测试:48 tests,0 failures,0 errors。
|
||||
- 与审计分支融合后的核团状态/快照/Feign 定向测试:121 tests,0 failures,0 errors。
|
||||
- 融合后的 `hl-order-service-v3` 全量测试:5,797 tests,0 failures,0 errors,15 条件跳过。
|
||||
- PR #5067 已于 2026-07-19 10:22 合并到 `dev-v3`。
|
||||
- 本文未取得测试环境部署或网关真实接口调用证据;合并完成不等同于测试环境已经生效。
|
||||
|
||||
## 11. 历史契约说明
|
||||
|
||||
| 文档/PR | 说明 | 当前有效性 |
|
||||
|---|---|---|
|
||||
| Changelog `18_5055_核团核算列表详情-修改接口-管理后台.md` / PR #5058 | 首次交付核团列表与详情聚合接口 | 接口结构及非状态字段仍有效 |
|
||||
| 上述文档中的 `settlementStatus` 枚举和示例 | 使用 `NONE / PENDING / COMPLETED` 及“未结算 / 待财务复核 / 已结算” | 已被本文纠正,不再作为核团页面契约 |
|
||||
| PR #5067 / Issue #5066 | 核团核算状态改用 `review_status` | 当前最新契约 |
|
||||
|
||||
## 12. 关联链接
|
||||
|
||||
- **Issue**: [#5066](https://git.1814.love:8443/wx/HL/issues/5066)
|
||||
- **PR**: [#5067](https://git.1814.love:8443/wx/HL/pulls/5067)
|
||||
- **Merge commit**: [f60241f3d](https://git.1814.love:8443/wx/HL/commit/f60241f3d6fdb2f36c091dc93d0232f2dcfe4775)
|
||||
@@ -0,0 +1,30 @@
|
||||
# 房务契约补充:作废与订单调整历史字段
|
||||
|
||||
前端反馈“作废、人数、改期、增晚缺少明确出参”。现按当前后端实现明确如下,禁止按页面文案猜测:
|
||||
|
||||
## 房务详情/需求历史
|
||||
|
||||
接口:`GET /admin/house/orders/{orderId}`、`GET /admin/house/orders/{orderId}/requirement-history`
|
||||
|
||||
- `requirement.recentHistory[].status`:`PENDING`、`PROCESSING`、`DONE`、`REJECTED_TO_CONSULTANT`、`REJECTED_TO_ADMIN`、`SUPERSEDED`。
|
||||
- `requirement.recentHistory[].returnReason`:退回/驳回原因;未退回为 `null`。
|
||||
- `requirement.recentHistory[].returnedBy`、`returnedAt`:退回操作人和时间;未退回为 `null`。
|
||||
- 作废订单本身使用 `order.status` 及 `statusLabel`;房务流程使用 `progress.houseStatus` 及 `houseStatusLabel`,不能把二者混用。
|
||||
|
||||
## 人数、改期、增晚的调整记录
|
||||
|
||||
接口:`GET /v3/admin/order/{orderId}/adjustment-record`
|
||||
|
||||
`items[].type` 是稳定枚举,`label/before/after` 已由后端生成,前端直接展示:
|
||||
|
||||
- `HEADCOUNT`:人数变化;`before/after` 为调整前后人数摘要。
|
||||
- `DEPART_DATE`:改期;`before/after` 为旧/新出发日期。
|
||||
- `TRIP_DAYS`:增减行程天数;`before/after` 为旧/新“X天Y晚”摘要。
|
||||
- `TRAVELER_EDIT`:仅出行人资料字段编辑,不代表人数变化。
|
||||
- `HOTEL_REQ`:住宿需求调整。
|
||||
|
||||
调整记录同时返回 `occurredAt`、`changeCount`、`statusNote`、`balanceBefore`、`balanceAfter`。新增出行人明细不从房务详情猜测,使用既有订单出行人接口;`HEADCOUNT` 记录用于展示人数前后变化。
|
||||
|
||||
## 验收说明
|
||||
|
||||
当前后端测试环境已有登录态,后续验收使用仓库 CDP 调试浏览器和 Network 证据;不得因普通浏览器无登录态改用插件或猜测实现。
|
||||
@@ -0,0 +1,86 @@
|
||||
# 【修改接口·管理后台】核团详情出行人补充出生日期和年龄(#5068)
|
||||
|
||||
> **Issue**: [#5068](https://git.1814.love:8443/wx/HL/issues/5068) | **PR**: [#5069](https://git.1814.love:8443/wx/HL/pulls/5069) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-19 11:23
|
||||
|
||||
## 1. 关键变化
|
||||
|
||||
- 核团详情 `travelers[]` 新增可空字段 `birthday` 和 `age`。
|
||||
- `birthday` 为出行人出生日期,格式 `yyyy-MM-dd`。
|
||||
- `age` 为按订单出发日期计算的周岁。
|
||||
- 出生日期为空、订单出发日期为空,或出生日期晚于出发日期时,`age` 返回 `null`。
|
||||
- 出行人手机号和证件号继续沿用原有脱敏规则。
|
||||
|
||||
## 2. 受影响接口
|
||||
|
||||
`GET /v3/admin/order/{orderId}/settlement/return-detail`
|
||||
|
||||
- HTTP 方法、URL、认证、路径参数及响应整体结构均不变。
|
||||
- 本次只增加 `data.travelers[]` 的响应字段,不增加请求参数。
|
||||
|
||||
## 3. 新增响应字段
|
||||
|
||||
| 字段 | JSON 类型 | 是否可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `data.travelers[].birthday` | string | 是 | 出生日期,格式 `yyyy-MM-dd` |
|
||||
| `data.travelers[].age` | number | 是 | 以订单出发日期为基准计算的周岁 |
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"travelers": [
|
||||
{
|
||||
"travelerId": "71001",
|
||||
"travelerName": "张三",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人",
|
||||
"birthday": "1990-07-20",
|
||||
"age": 36,
|
||||
"idType": "ID_CARD",
|
||||
"idTypeName": "身份证",
|
||||
"phone": "138****1234",
|
||||
"idCardNo": "110***********1234"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 示例仅展示本次相关结构;核团详情中的订单、司机车辆、应收和收款等字段保持不变。
|
||||
|
||||
## 4. 年龄计算与空值边界
|
||||
|
||||
| 场景 | `birthday` | `age` |
|
||||
|---|---|---|
|
||||
| 出生日期和订单出发日期均有效 | 返回出生日期 | 返回两个日期之间的完整周岁 |
|
||||
| 出生日期为空 | `null` | `null` |
|
||||
| 订单出发日期为空 | 返回出生日期 | `null` |
|
||||
| 出生日期晚于订单出发日期 | 返回出生日期 | `null` |
|
||||
|
||||
当前已合并实现不会在订单出发日期缺失时改用服务器当前日期。Issue #5068 初始描述中的“按当前日期兜底”尚未进入代码;若业务仍需要该口径,应另行变更后端实现和本通知。
|
||||
|
||||
## 5. 前端适配清单
|
||||
|
||||
- [ ] 在核团详情出行人列表展示 `birthday` 和 `age`。
|
||||
- [ ] 对两个字段均做 `null` 兼容,不拼接 `null岁` 或展示无效日期。
|
||||
- [ ] 年龄直接使用后端返回值,不在浏览器端按当前日期重新计算。
|
||||
- [ ] 继续使用现有脱敏后的 `phone` 和 `idCardNo`,不要尝试恢复明文。
|
||||
- [ ] 不改变接口 URL、请求参数和其他响应字段的解析逻辑。
|
||||
|
||||
## 6. 兼容性与发布边界
|
||||
|
||||
- 新增字段对忽略未知 JSON 字段的旧客户端向后兼容。
|
||||
- 字段为可空值,前端不能把 `birthday` 或 `age` 设为必填。
|
||||
- PR #5069 已于 2026-07-19 10:47 合并到 `dev-v3`,合并提交为 `1da9389fbbc567dfd8b98703a6b9ebbfb2ea1d69`。
|
||||
- 测试环境公网网关已验证新字段返回,见下方验证证据。
|
||||
|
||||
## 7. 验证证据
|
||||
|
||||
- 后端定向测试:`mvn -pl hl-order-service-v3 -am -DfailIfNoTests=false -Dtest=SettlementReturnDetailQueryServiceTest,SettlementControllerTest test`。
|
||||
- 测试环境公网网关验证:`GET https://web.test.1814.love:9443/v3/admin/order/2077233855281971202/settlement/return-detail` 连续 6 次返回 `code=200`。
|
||||
- 实测订单出发日为 `2026-07-13`,首位出行人 `birthday=1991-05-27`,接口返回 `age=35`,与按订单出发日计算的周岁一致。
|
||||
- 实测响应中 `phone`、`idCardNo` 仍为脱敏值。
|
||||
@@ -0,0 +1,19 @@
|
||||
# #5074 调整记录新增本次新增出行人 ID
|
||||
|
||||
接口:`GET /v3/admin/order/{orderId}/adjustment-record`
|
||||
|
||||
每条调整记录新增:
|
||||
|
||||
```json
|
||||
{
|
||||
"addedTravelerIds": ["2078739881671921666", "2078739895240560641"]
|
||||
}
|
||||
```
|
||||
|
||||
- 类型:`string[]`,雪花 ID 必须按字符串处理。
|
||||
- 含义:仅包含该次订单调整事务实际新增的出行人 ID。
|
||||
- 多人同时新增:完整返回全部新增 ID;数组顺序不承载业务语义。
|
||||
- 仅编辑、仅删除、未涉及出行人或旧历史记录:返回 `[]`。
|
||||
- 前端将当前出行人列表中的 `id` 与 `addedTravelerIds` 精确匹配后标记“本次新增”;禁止按列表位置或 ID 大小推断。
|
||||
|
||||
后端 Issue:`wx/HL#5074`;PR:`wx/HL#5075`。
|
||||
@@ -0,0 +1,13 @@
|
||||
# #5078 住宿需求允许指定酒店暂不指定房型
|
||||
|
||||
影响接口:住宿需求首次提交及订单调整提交。
|
||||
|
||||
业务规则调整:
|
||||
|
||||
- 允许候选酒店 `hotelId` 有值,同时房型行 `roomTypeId=null`、`roomCategory=null`。
|
||||
- 此时 `roomCount` 仍必须为正数,用于表达“酒店已指定,具体房型由房务后续确认”。
|
||||
- 后端不会伪造房型 ID 或协议价;房务配房时再选择该酒店的真实房型。
|
||||
- 指定真实房型时继续按原契约传 `roomTypeId`;完整房型、多房型、未指定酒店场景不变。
|
||||
- `roomCount` 为 0、负数或缺失时仍按非法房数拒绝。
|
||||
|
||||
后端 Issue:`wx/HL#5078`;PR:`wx/HL#5079`。
|
||||
@@ -0,0 +1,160 @@
|
||||
# 【前端待处理·管理后台】管理后台消息 SSE 鉴权重连与旧会话恢复
|
||||
|
||||
> **模块**:管理后台全局消息 / 在线状态 / 聊天信令 | **服务**:`hl-gateway` + `hl-user-service`<br>
|
||||
> **类型**:前端待处理 + 联调告知 | **更新时间**:2026-07-19<br>
|
||||
> **影响范围**:管理后台全局 SSE 连接、顶部未读角标、聊天、在线状态与抢单池信令<br>
|
||||
> **状态**:后端已完成根因定位;前端尚未修复;接口契约未变
|
||||
|
||||
## 1. 结论与处理优先级
|
||||
|
||||
> ⚠️ 2026-07-19 测试环境启用 SSE 连接角色一致性校验后,发布前已签发且仍在有效期内的旧登录会话可能缺少当前角色标记。此时网关能够识别 access token,但用户服务会拒绝建立 SSE,前端当前实现会持续使用同一登录会话无限重连。
|
||||
|
||||
- 接口 URL、HTTP 方法、事件结构均未修改。
|
||||
- 这不是 `token` Query 参数名写错;当前前端 URL 拼接方式与网关读取方式一致。
|
||||
- **用户立即恢复方式**:退出当前账号,重新登录并选择当前角色,再建立 SSE。
|
||||
- **前端必须处理**:Token/角色变化时主动重建连接、限制连续失败重试、给出重新登录提示,并消除默认 `message` 事件的重复注册。
|
||||
- 本次现象包含后端发布前旧会话兼容问题;前端改造用于正确管理连接生命周期和避免无限重试,不代表把后端兼容责任转移给前端。
|
||||
|
||||
## 2. 当前接口契约
|
||||
|
||||
```http
|
||||
GET /ws/admin-msg/stream?token=<accessToken>
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
- 认证:管理后台 access token。
|
||||
- 当前使用原生 `EventSource`,浏览器 API 不能自定义 `Authorization` Header,因此现有实现通过 Query 参数传递 token。
|
||||
- `token` 必须使用当前 Store 中的 access token,并通过 `encodeURIComponent` 做 URL 编码。
|
||||
- 成功建连后,请求应长期保持 `Pending`,响应类型为 `text/event-stream`。
|
||||
- 首个握手事件:
|
||||
|
||||
```text
|
||||
event: connected
|
||||
data: ok
|
||||
```
|
||||
|
||||
- 后续仍沿用现有具名事件,包括 `unread-count`、`im-chat`、`im-chat-read`、`presence` 和 `grab-pool-changed`;本次没有修改事件数据结构。
|
||||
|
||||
## 3. 已确认的问题链路
|
||||
|
||||
### 3.1 旧登录会话与新角色标记不兼容
|
||||
|
||||
测试环境运行链路已确认:
|
||||
|
||||
1. 网关可以从 `?token=` 读取并校验管理后台 JWT。
|
||||
2. 网关向用户服务转发可信的管理员身份及角色信息。
|
||||
3. 用户服务在下发任何 SSE 数据前校验“连接角色是否仍为当前登录角色”。
|
||||
4. 发布前签发的旧登录会话没有初始化新角色标记时,校验按安全策略失败并关闭连接。
|
||||
5. 新登录或重新选择角色会重新写入角色标记,因此重新登录后可恢复。
|
||||
|
||||
该校验采用 fail-closed(失败时拒绝)策略,目的是避免角色切换后旧 Token 继续接收不属于当前角色的消息。
|
||||
|
||||
### 3.2 前端当前会无限重试同一失败会话
|
||||
|
||||
当前 `src/composables/useAdminMessageSSE.js` 在 `EventSource.onerror` 后执行关闭和指数退避,但没有连续失败上限,也没有触发重新登录或鉴权恢复流程。
|
||||
|
||||
原生 `EventSource.onerror` 不暴露 HTTP 状态码和响应正文,前端不能仅凭 `onerror` 精确区分 401/403、服务异常和临时断网。因此不能把所有错误都直接判定为 Token 失效,但必须限制无休止重连。
|
||||
|
||||
### 3.3 默认 `message` 事件被重复注册
|
||||
|
||||
当前实现同时注册:
|
||||
|
||||
```js
|
||||
es.onmessage = handleMessage
|
||||
es.addEventListener('message', handleMessage)
|
||||
```
|
||||
|
||||
两种写法都会监听默认 `message` 事件,并不是互斥兜底。后端发送默认 `message` 时,同一数据可能被处理两次,必须只保留一种注册方式。
|
||||
|
||||
## 4. 用户立即恢复步骤
|
||||
|
||||
1. 关闭当前页面产生的旧 SSE 连接。
|
||||
2. 正常退出管理后台。
|
||||
3. 重新登录,并重新选择当前需要使用的角色。
|
||||
4. 进入主布局后重新建立 `/ws/admin-msg/stream`。
|
||||
5. 在浏览器 Network 中确认请求保持 `Pending`,并收到一次 `connected` 事件。
|
||||
|
||||
不要通过手工复制、修改或在地址栏粘贴完整 Token 的方式恢复连接。
|
||||
|
||||
## 5. 【前端·管理后台】适配清单
|
||||
|
||||
### 5.1 让 SSE 生命周期跟随登录凭证和角色
|
||||
|
||||
- [ ] 监听 `userStore.token` 变化;值变化时先关闭旧 `EventSource`,再使用最新 Token 建立唯一的新连接。
|
||||
- [ ] 角色切换成功并更新 Token 后,立即重建 SSE,不等待旧连接自行报错。
|
||||
- [ ] 登出、主布局卸载或 Token 被清空时,关闭连接、清理重连定时器并禁止再次拉起。
|
||||
- [ ] 保证全局最多只有一个管理后台消息 SSE 实例,避免布局重复挂载造成多连接。
|
||||
- [ ] 重建连接时始终从 Store 现取 Token,不缓存旧登录会话中的 Token 字符串。
|
||||
|
||||
### 5.2 限制连续失败,避免无限重连
|
||||
|
||||
- [ ] 保留指数退避和最大间隔,但增加“连续失败次数/总时长”上限。
|
||||
- [ ] **仅在收到后端 `connected` 事件后**清零连续失败计数;`EventSource.onopen` 不能作为鉴权成功依据,也不能清零计数。
|
||||
- [ ] 达到上限后停止自动重试,并显示中性、可操作的提示,例如“消息连接连续失败,请检查网络或重新登录”。
|
||||
- [ ] 用户完成重新登录、Token 刷新、角色切换或主动点击重试后,才开启新一轮连接。
|
||||
- [ ] 临时断网恢复后仍允许重连;可结合 `online` 事件或显式重试入口恢复,而不是永久静默失效。
|
||||
|
||||
> 注意:由于原生 `EventSource` 无法在 `onerror` 中读取响应状态,前端不要根据一次 `onerror` 立即清空登录态。需要使用连续失败阈值,并结合普通鉴权接口结果或既有 Token 刷新状态判断。
|
||||
|
||||
### 5.3 消除重复消息处理
|
||||
|
||||
- [ ] `es.onmessage` 与 `es.addEventListener('message', ...)` 只保留一种。
|
||||
- [ ] `connected`、`unread-count`、`im-chat`、`im-chat-read`、`presence`、`grab-pool-changed` 等具名事件继续分别注册。
|
||||
- [ ] 验证单条默认 `message`、聊天信令和未读数信令都只被业务层消费一次。
|
||||
|
||||
### 5.4 失败信息与联调反馈
|
||||
|
||||
- [ ] 前端提示中不要展示 Token、完整 SSE URL、Cookie 或管理员标识。
|
||||
- [ ] 如重新登录后仍失败,只反馈发生时间、页面、错误 `message` 和 `X-Trace-Id`。
|
||||
- [ ] 若 Network 原始响应确实为“未提供有效的Token”,请附 `X-Trace-Id` 交后端继续检查路由/拦截器链;不要附 Token。
|
||||
|
||||
## 6. 验收场景
|
||||
|
||||
| 场景 | 期望结果 |
|
||||
|---|---|
|
||||
| 重新登录后首次进入主布局 | 只建立 1 条 SSE;请求保持 `Pending`;收到 1 次 `connected` |
|
||||
| access token 刷新 | 旧连接关闭,使用新 Token 只重建 1 次 |
|
||||
| 切换管理后台角色 | 旧角色连接立即关闭;新角色 Token 建立新连接;不接收旧角色后续数据 |
|
||||
| 发布前旧会话无法建连 | 退避重试达到阈值后停止,并明确提示重新登录;不无限刷请求 |
|
||||
| 只触发 `onopen`、未收到 `connected`、随后触发 `onerror` | 仍累计连续失败次数,不得被 `onopen` 反复清零 |
|
||||
| 临时断网后恢复 | 在受控退避或用户重试后恢复连接,不产生并发 SSE |
|
||||
| 收到默认 `message` | 同一事件只处理 1 次 |
|
||||
| 正常登出 | SSE 和重连定时器均被清理,退出页不再发起连接 |
|
||||
| 重新登录后仍失败 | 联调材料仅包含时间、页面、错误消息、`X-Trace-Id`,不包含 Token |
|
||||
|
||||
## 7. 后端状态与边界
|
||||
|
||||
- 当前接口路径、Query 参数名和 SSE 事件结构未变,不需要前端调整数据模型。
|
||||
- 新登录/角色切换链路会写入当前角色标记,重新登录是当前可用的恢复手段。
|
||||
- 发布前旧会话没有迁移标记是本次问题的触发条件;后端尚未交付旧会话兼容补丁。
|
||||
- 角色一致性校验必须保留,不能为了兼容旧会话而允许旧角色 Token 接收消息。
|
||||
- 若后续改为一次性 SSE Ticket、Fetch Streaming 或其他不在 URL 中携带 access token 的方案,将另发接口契约,不在本次前端适配范围内。
|
||||
|
||||
## 8. 安全要求
|
||||
|
||||
- 禁止把完整 Token、带 Token 的完整 SSE URL、Cookie 或真实管理员信息写入 Issue、PR、Changelog、日志和截图。
|
||||
- Token 一旦通过聊天、工单或截图暴露,应立即停止使用和传播,通知后端/运维按当前鉴权策略显式吊销或拒绝该旧 Token,并验证它已无法访问;随后重新登录获取新 Token。
|
||||
- 重新登录只是恢复 SSE 和获取新 Token,不等于旧 JWT 已自动吊销;尤其在仅校验 JWT 签名的环境中,必须单独完成旧 Token 的失效处置。
|
||||
- 不得在前端代码中硬编码 Token,也不得把 Token 写入错误上报或埋点参数。
|
||||
|
||||
## 9. 影响范围
|
||||
|
||||
| 文件/能力 | 说明 |
|
||||
|---|---|
|
||||
| `src/composables/useAdminMessageSSE.js` | 连接、重连、事件监听和清理逻辑 |
|
||||
| `src/layouts/BasicLayout.vue` | 主布局挂载、登出和 SSE 生命周期 |
|
||||
| 角色切换流程 | Token 更新后主动重建 SSE |
|
||||
| 顶部未读角标、聊天、在线状态、抢单池信令 | 共用同一 SSE,需防止连接缺失或事件重复消费 |
|
||||
|
||||
## 10. 发布说明
|
||||
|
||||
- 本文是前端联调和修复通知,不代表已修改或发布前端代码。
|
||||
- 本文没有包含任何真实 Token、管理员 ID、Cookie 或其他敏感信息。
|
||||
- 前端完成后应在 `mmg/hl-ui` 走自身 Issue、分支、PR、测试和发布流程。
|
||||
|
||||
## 11. 相关历史契约
|
||||
|
||||
| 文档 | 当前说明 |
|
||||
|---|---|
|
||||
| [内部员工站内信收件箱 + SSE 实时推送](../2026-06/04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md) | SSE 路径、Query 鉴权和事件契约仍有效;其中“断线自动重连”的建议被本文补充为有上限的受控重连,鉴权持续失败时不得无限请求 |
|
||||
| [切角色 / 刷新令牌原子保存](../2026-06/38_4529_切角色与刷新token原子保存_前端必改-管理后台.md) | `token` 与 `refreshToken` 原子保存要求仍有效;保存新 access token 后还必须关闭旧 SSE 并主动重建 |
|
||||
@@ -0,0 +1,49 @@
|
||||
# 房务详情混合房型逐行出参(Issue #5080)
|
||||
|
||||
## 背景
|
||||
|
||||
同一晚存在多个房型时,旧兼容标量会把 `rooms[]` 的首行房型与所有房数相加,导致“标间 1 + 大床房 1”被错误展示为“标间 2”。
|
||||
|
||||
## 接口
|
||||
|
||||
`GET /admin/house/orders/{orderId}`
|
||||
|
||||
`GET /v3/admin/order/{orderId}`(订单详情中的住宿需求摘要)
|
||||
|
||||
## 新增字段
|
||||
|
||||
`data.itinerary[].expectedRooms[]`:当天逐房型预期房间列表,混合房型展示和业务判断以此字段为准。
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedRoom": {
|
||||
"roomCategory": null,
|
||||
"roomCategoryLabel": null,
|
||||
"roomCount": 2
|
||||
},
|
||||
"expectedRooms": [
|
||||
{ "roomCategory": "STANDARD", "roomCategoryLabel": "标间", "roomCount": 1 },
|
||||
{ "roomCategory": "KING", "roomCategoryLabel": "大床房", "roomCount": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 兼容规则
|
||||
|
||||
- 单一房型:`expectedRoom` 继续返回原标量,`expectedRooms[]` 同时提供逐行数据。
|
||||
- 混合房型:`expectedRoom.roomCategory` 与 `roomCategoryLabel` 返回 `null`,防止形成“首房型 × 总房数”的错误含义;`roomCount` 仍为总间数。
|
||||
- 未指定房型:房型字段保持 `null`,房数按需求返回。
|
||||
- `requirement.current.days[].segments[].candidates[].rooms[]` 仍是候选酒店房型行的权威明细。
|
||||
|
||||
## 前端适配要求
|
||||
|
||||
1. 房务详情及“选择酒店”弹窗不得再用 `days[].roomCategory` 或首个酒店 `roomCategory` 表示混合房型。
|
||||
2. 标题按 `expectedRooms[]` 渲染,例如“标间 1 间 + 大床房 1 间”。
|
||||
3. 候选房型筛选与默认数量应逐条读取 `expectedRooms[]`;不得以首行房型套用总间数。
|
||||
4. 兼容后端尚未部署时,可从 `segments[].candidates[0].rooms[]` 读取同等权威明细,但不得猜测列表顺序。
|
||||
|
||||
## 订单详情补充(Issue #5082)
|
||||
|
||||
- `hotelRequirement.days[].hotels[]` 与 `segments[]` 的兼容 `roomCategory/roomCategoryLabel` 仅在对应 `rooms[]` 全部属于同一房型大类时返回。
|
||||
- 混合房型时,上述兼容房型字段返回 `null`,`roomCount` 仍返回总间数;页面标题必须由 `rooms[]` 逐行生成。
|
||||
- 这可避免“标间 1 + 大床房 1”被标题错误展示为“标间 2”。
|
||||
@@ -0,0 +1,370 @@
|
||||
# 【前端对接·管理后台】车务派单可靠通知、取消后重派与发送状态契约
|
||||
|
||||
> Issue: [wx/HL#4933](https://git.1814.love:8443/wx/HL/issues/4933)
|
||||
>
|
||||
> PR: [wx/HL#5073](https://git.1814.love:8443/wx/HL/pulls/5073)、[wx/HL#5084](https://git.1814.love:8443/wx/HL/pulls/5084)
|
||||
>
|
||||
> 服务: `hl-fleet-service` / `hl-user-service` / `hl-order-service-v3` / `hl-gateway`
|
||||
>
|
||||
> 日期: 2026-07-19
|
||||
>
|
||||
> 影响范围: 派单/改派弹窗、派单详情操作记录、通知发送日志、订单详情推送记录、取消后重新派车
|
||||
|
||||
## 一、前端结论
|
||||
|
||||
- `holdMode=1` 的创建派单和改派现在会冻结本次通知模板与正文,并由后端异步执行可靠短信发送。
|
||||
- 创建 HOLD 成功只表示派单和通知意图已落库;首次响应中的 `holdSentAt` 固定为 `null`。只有供应商真实受理后,派单详情的 `currentAssignment.holdSentAt` 才会回显发送时间。
|
||||
- 通知日志 `status` 已从旧的少量状态扩展为 `0~6`。前端必须展示“投递中、结果不确定、授权撤销”,不得把它们归并成发送成功或失败。
|
||||
- 取消派单成功后,后端会可靠地把当前生效用车需求重新打开,允许再次派车;该过程为最终一致。前端刷新看板和详情,并以最新 `canAssign`/当前需求状态决定是否开放重派,不调用内部重开接口。
|
||||
- 订单详情推送记录的归一化状态枚举已调整,前端需要同步新枚举。
|
||||
- `/internal/**`、`/v3/internal/**` 均为服务间接口,经网关调用返回业务码 `403`;任何 Web/小程序代码都不得调用。
|
||||
|
||||
## 二、前端可调用接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 本轮变化 |
|
||||
|---|---|---|---|
|
||||
| 创建派单 | POST | `/admin/fleet/assignments` | 新增 `messageTemplateId/customBody`;明确 `holdSentAt` 语义 |
|
||||
| 修改派单 | POST | `/admin/fleet/assignments/{assignmentId}/change` | HOLD 改派新增 `messageTemplateId/customBody` |
|
||||
| 派单看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 回显真实 `holdSentAt`;操作记录补齐取消/退保完整时间线 |
|
||||
| 派单看板汇总 | GET | `/admin/fleet/board/summary` | 取消后刷新当前状态与能力字段 |
|
||||
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 取消后刷新当前状态与能力字段 |
|
||||
| 通知发送日志 | GET | `/admin/notification/logs` | 状态扩展为 `0~6`,新增可靠投递审计字段 |
|
||||
| 通知发送统计 | GET | `/admin/notification/logs/stats` | 新增跳过、投递中、不确定、撤销等统计 |
|
||||
| 人工核对可靠短信 | PUT | `/admin/notification/logs/{id}/resolve-reliable` | 新增,仅专用权限可用 |
|
||||
| 订单详情推送记录 | GET | `/v3/admin/order/{id}/push-records` | 归一化状态枚举调整 |
|
||||
|
||||
## 三、创建/修改 HOLD 派单
|
||||
|
||||
### 3.1 请求字段
|
||||
|
||||
两个写接口新增相同的可选字段:
|
||||
|
||||
| 字段 | 类型 | 规则 |
|
||||
|---|---|---|
|
||||
| `messageTemplateId` | string | HOLD 通知模板 ID;可空,空时使用 `hold_notify` 默认模板;`holdMode=0` 时忽略 |
|
||||
| `customBody` | string | 本次通知自定义正文;可空,最大 4000 字符;只冻结本次内容,不回写模板 |
|
||||
|
||||
所有雪花 ID 继续按字符串传递和保存,禁止转为 JavaScript `Number`。
|
||||
|
||||
创建 HOLD 请求示例:
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <fleet-admin-token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2074746808742928386",
|
||||
"requirementId": "2075001000000000001",
|
||||
"vehicleId": "2076001000000000001",
|
||||
"driverId": "2077001000000000001",
|
||||
"startDate": "2026-07-20",
|
||||
"endDate": "2026-07-22",
|
||||
"headcount": 4,
|
||||
"holdMode": 1,
|
||||
"messageTemplateId": "20260706000101",
|
||||
"customBody": "王师傅您好,26-7218 团 7 月 20 日待确认。",
|
||||
"fromEntry": "from-board",
|
||||
"requestId": "hold-2074746808742928386-001"
|
||||
}
|
||||
```
|
||||
|
||||
修改为 HOLD 请求示例:
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/2078001000000000001/change
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <fleet-admin-token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"effectiveDate": "2026-07-21",
|
||||
"newVehicleId": "2076001000000000002",
|
||||
"newDriverId": "2077001000000000002",
|
||||
"holdMode": 1,
|
||||
"messageTemplateId": "20260706000101",
|
||||
"customBody": "李师傅您好,本团 7 月 21 日起调整由您服务,请确认。",
|
||||
"reason": "原司机临时无法执行",
|
||||
"requestId": "change-2078001000000000001-001"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 创建响应与 `holdSentAt`
|
||||
|
||||
HOLD 创建成功响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "2078001000000000001",
|
||||
"assignmentGroupId": "2078001000000000001",
|
||||
"assignmentSlotId": "2078001000000000001",
|
||||
"assignmentStatus": "holding",
|
||||
"stageCode": "holding_wait_driver",
|
||||
"stageLabel": "排车中·等待司机确认",
|
||||
"currentStep": 3,
|
||||
"skippedStepCodes": [],
|
||||
"protocolPrice": "1300.00",
|
||||
"holdSentAt": null,
|
||||
"confirmedAt": null,
|
||||
"sideEffects": null,
|
||||
"dailyDifferences": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端处理规则:
|
||||
|
||||
1. `code=200` 且 `assignmentStatus=holding` 后立即关闭重复提交入口,并刷新详情。
|
||||
2. `holdSentAt=null` 不是接口失败,也不能显示“短信已发送”;应显示“通知处理中/等待发送结果”。
|
||||
3. 后续读取 `GET /admin/fleet/board/orders/{orderId}`,仅当 `currentAssignment.holdSentAt` 非空时显示真实发送时间。
|
||||
4. 模板缺失、供应商失败或结果不确定时,派单仍保持 `holding`,前端通过通知日志查看真实状态,不自行改派单状态。
|
||||
|
||||
## 四、通知发送日志状态
|
||||
|
||||
### 4.1 状态枚举
|
||||
|
||||
`GET /admin/notification/logs` 的请求筛选参数和响应字段 `status` 统一使用:
|
||||
|
||||
| status | 含义 | 前端展示建议 |
|
||||
|---:|---|---|
|
||||
| 0 | 发送成功,供应商明确受理 | 成功 |
|
||||
| 1 | 明确失败 | 失败 |
|
||||
| 2 | 无收件人 | 已跳过·无收件人 |
|
||||
| 3 | 无模板 | 已跳过·无模板 |
|
||||
| 4 | 投递中 | 投递中 |
|
||||
| 5 | 结果不确定 | 待核对 |
|
||||
| 6 | 授权撤销 | 已撤销 |
|
||||
|
||||
前端不得把 `4/5/6` 计入成功或失败。状态 `5` 也不能自动重发,避免供应商实际已发送时重复通知司机。
|
||||
|
||||
单条日志新增字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2080001000000000001,
|
||||
"eventCode": "FLEET_DISPATCH_CREATED",
|
||||
"channel": "SMS",
|
||||
"bizId": "2078001000000000001",
|
||||
"bizType": "FLEET_ASSIGNMENT_HOLD",
|
||||
"status": 5,
|
||||
"latestProviderAttemptAt": "2026-06-19T10:00:00",
|
||||
"providerSentAt": null,
|
||||
"resultTime": null,
|
||||
"manualResolvedAt": null,
|
||||
"manualResolvedBy": null,
|
||||
"manualResolutionReason": null
|
||||
}
|
||||
```
|
||||
|
||||
新增统计字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalToday": 20,
|
||||
"successToday": 12,
|
||||
"failToday": 2,
|
||||
"skippedToday": 3,
|
||||
"dispatchingToday": 1,
|
||||
"unknownToday": 1,
|
||||
"canceledToday": 1,
|
||||
"terminalAttemptToday": 14,
|
||||
"successRate": 85.71,
|
||||
"channelStats": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`successRate` 的分母是 `terminalAttemptToday = successToday + failToday`,前端不要再用 `totalToday` 自行计算。
|
||||
|
||||
## 五、人工核对结果不确定短信
|
||||
|
||||
该入口只处理超过供应商 29 天查询窗口、仍为 `status=5` 的车务可靠短信,并要求 `NOTIFICATION_RELIABLE_RESOLVE` 专用权限。当前后端只授予 `SUPER_ADMIN`;普通管理员即使手工构造请求也会被拒绝。
|
||||
|
||||
确认已发送:
|
||||
|
||||
```http
|
||||
PUT /admin/notification/logs/2080001000000000001/resolve-reliable
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <super-admin-token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"resolution": "SUCCESS",
|
||||
"reason": "阿里云控制台发送记录核对,工单 SMS-20260719-001",
|
||||
"externalMessageId": "SMS-20260719-001",
|
||||
"providerSentAt": "2026-06-19T10:00:30"
|
||||
}
|
||||
```
|
||||
|
||||
确认未发送:
|
||||
|
||||
```json
|
||||
{
|
||||
"resolution": "NOT_SENT",
|
||||
"reason": "阿里云控制台未查到对应发送记录",
|
||||
"externalMessageId": null,
|
||||
"providerSentAt": null
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
- `SUCCESS` 必须传 `externalMessageId` 和 `providerSentAt`;事实时间必须位于最近一次供应商尝试时间前后 5 分钟内。
|
||||
- `NOT_SENT` 不得传 `providerSentAt`。
|
||||
- 请求返回 `100001` 表示参数或证据时间不合法;返回 `100003` 表示无权限、日志不符合人工核对条件或状态已变化。
|
||||
- 操作成功后刷新当前日志行和统计;不要在前端直接篡改状态。
|
||||
|
||||
## 六、订单详情推送记录状态
|
||||
|
||||
`GET /v3/admin/order/{id}/push-records` 的 `records[].status` 改为:
|
||||
|
||||
| status | 含义 |
|
||||
|---|---|
|
||||
| `SENT` | 供应商明确受理 |
|
||||
| `FAILED` | 明确失败 |
|
||||
| `SKIPPED_NO_RECIPIENT` | 无收件人 |
|
||||
| `SKIPPED_NO_TEMPLATE` | 无模板 |
|
||||
| `DISPATCHING` | 投递中 |
|
||||
| `UNKNOWN` | 结果不确定 |
|
||||
| `CANCELED` | 授权已撤销 |
|
||||
| `UNRECOGNIZED` | 未识别的存量状态 |
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 1,
|
||||
"records": [
|
||||
{
|
||||
"id": 2080001000000000001,
|
||||
"eventCode": "FLEET_DISPATCH_CREATED",
|
||||
"channel": "SMS",
|
||||
"channelName": "短信",
|
||||
"kind": "sms",
|
||||
"target": "王师傅",
|
||||
"status": "UNKNOWN",
|
||||
"statusName": "结果不确定",
|
||||
"rawStatus": 5,
|
||||
"failReason": null,
|
||||
"bizId": "2078001000000000001",
|
||||
"bizType": "FLEET_ASSIGNMENT_HOLD",
|
||||
"sentAt": "2026-07-19T10:00:00"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"all": 1,
|
||||
"sms": 1,
|
||||
"miniapp": 0,
|
||||
"officialAccount": 0,
|
||||
"inapp": 0,
|
||||
"internal": 0,
|
||||
"wework": 0,
|
||||
"other": 0,
|
||||
"failed": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`summary.failed` 只统计 `rawStatus=1`,不包含 `UNKNOWN/DISPATCHING/CANCELED`。
|
||||
|
||||
## 七、取消后重新派车
|
||||
|
||||
前端仍调用既有接口取消:
|
||||
|
||||
```http
|
||||
DELETE /admin/fleet/assignments/{assignmentId}
|
||||
```
|
||||
|
||||
成功后的正确流程:
|
||||
|
||||
1. 接受取消响应中的 `assignmentStatus=canceled`。
|
||||
2. 重新请求 `/admin/fleet/board/summary`、`/admin/fleet/board/orders` 和 `/admin/fleet/board/orders/{orderId}`。
|
||||
3. 后端完成需求重开后,当前订单重新出现可派状态;按钮只看最新响应的 `canAssign`,不要本地强制改为可派。
|
||||
4. 如果首次刷新仍未开放重派,保持处理中并短暂重试刷新;不要调用 `/v3/internal/order/**`,也不要让用户重复取消。
|
||||
5. 重新派车成功后再次刷新服务端状态,不能沿用已取消派单的 `assignmentId`。
|
||||
|
||||
派单详情 `operationLog.records[]` 会保留不可变取消时间线,新增/强化的 `opType` 包括:
|
||||
|
||||
- `cancel_requested`
|
||||
- `driver_notification_recorded`
|
||||
- `cancel_evidence_recorded`
|
||||
- `insurance_refund_pending`
|
||||
- `insurance_refund_succeeded`
|
||||
- `insurance_refund_failed`
|
||||
- `cancel_completed`
|
||||
- `cancel_restored`
|
||||
- `cancel_failed`
|
||||
|
||||
前端优先展示后端返回的 `opTypeLabel`、`operationStatusLabel` 和 `summary`,不要另维护中文文案。`operationStatus` 允许 `pending/succeeded/failed`。
|
||||
|
||||
## 八、网关 internal 边界
|
||||
|
||||
下列路径全部禁止客户端调用:
|
||||
|
||||
```text
|
||||
/internal
|
||||
/internal/**
|
||||
/v3/internal
|
||||
/v3/internal/**
|
||||
```
|
||||
|
||||
网关按项目协议返回 HTTP 200,但响应体为:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 403,
|
||||
"message": "接口不可访问",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
请前端全仓检查是否仍有 `/v3/internal/mp/**` 等历史调用;如存在,不要自行改成另一个 internal 地址,应反馈后端补正式 BFF/admin 契约。
|
||||
|
||||
## 九、前端待处理清单
|
||||
|
||||
- [ ] 派单/改派弹窗在 HOLD 模式支持 `messageTemplateId/customBody`,DIRECT 模式不提交或忽略这两个字段。
|
||||
- [ ] HOLD 创建成功时把 `holdSentAt=null` 展示为处理中,不显示“已发送”。
|
||||
- [ ] 通知日志筛选、标签和统计适配 `0~6` 状态及新增字段。
|
||||
- [ ] 仅对具备专用权限的账号展示“人工核对可靠短信”入口,并实现 `SUCCESS/NOT_SENT` 两种表单校验。
|
||||
- [ ] 订单详情推送记录适配新的归一化状态枚举。
|
||||
- [ ] 取消派单后刷新服务端状态,以 `canAssign` 控制重新派车入口。
|
||||
- [ ] 确认前端不存在任何 `/internal/**` 或 `/v3/internal/**` 调用。
|
||||
- [ ] 所有雪花 ID 保持字符串。
|
||||
|
||||
## 十、后端交付与测试环境状态
|
||||
|
||||
- 后端 PR #5073、#5084 已合并到 `dev-v3`。
|
||||
- `hl-order-service-v3`、`hl-fleet-service`、`hl-gateway` 已按顺序部署 TEST,双实例健康;当前 OpenAPI 已公开本文全部管理端接口。
|
||||
- 已用真实测试订单完成 DIRECT、取消、需求重开、再次 DIRECT、司机同步和退保时间线验收。
|
||||
- TEST 当前 `hold_notify` 短信模板仍是占位配置,真实 HOLD 短信会失败关闭,`holdSentAt` 保持 `null`;这是环境配置阻塞,不应由前端伪造成发送成功。
|
||||
- 本文件只做契约交接,不修改 `hl-ui`。
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 房务选择酒店误传 preferredHotelId 导致只显示 1 家(前端待处理)
|
||||
|
||||
## 现象
|
||||
|
||||
订单 `2078739922130243586` 第 1 晚打开“选择酒店”弹窗,只显示定制师指定的“呼伦贝尔香格里拉大酒店”,分页显示“共 1 条”,页面提示“已限定定制师指定酒店”。
|
||||
|
||||
## 已确认原因
|
||||
|
||||
`192.168.100.160:9527` 当前 Vite 服务实际返回的 `PickHotelModal.vue` 仍在候选请求中传递:
|
||||
|
||||
```js
|
||||
preferredHotelId:
|
||||
on.specifiedHotelIds?.length === 1 ? String(on.specifiedHotelIds[0]) : undefined
|
||||
```
|
||||
|
||||
该参数会要求后端按指定酒店过滤,因此响应只剩 1 家。这与当前产品意图“定制师指定酒店置顶并标记,房务仍可选择其他酒店”冲突。
|
||||
|
||||
当前 `D:/work2/hl-ui` 源码已经不再传该参数,说明 `192.168.100.160:9527` 运行的是未同步的工作树或旧代码。
|
||||
|
||||
## 接口证据
|
||||
|
||||
接口:`GET /v3/admin/hotel-candidates`
|
||||
|
||||
公共参数:
|
||||
|
||||
- `orderId=2078739922130243586`
|
||||
- `dayNumber=1`
|
||||
- `stayDate=2026-07-22`
|
||||
- `roomCount=2`
|
||||
- `limit=50`
|
||||
|
||||
结果:
|
||||
|
||||
- 不传 `preferredHotelId`、无关键词:返回 21 家;香格里拉为 `isConsultantRecommended=true` 且排第 1。
|
||||
- 不传 `preferredHotelId`、`keyword=满洲里`:返回 3 家,包括香格里拉、满洲里凯旋大酒店、满洲里饭店(百年俄式)。
|
||||
- 当前截图环境传入唯一 `preferredHotelId`:只返回指定酒店 1 家。
|
||||
|
||||
## 前端处理要求
|
||||
|
||||
1. 房务候选请求不得传 `preferredHotelId`,无论 `specifiedHotelIds` 是 1 个还是多个。
|
||||
2. `specifiedHotelIds` 仅用于页面提示;推荐标记以接口 `isConsultantRecommended` 为准。
|
||||
3. 不输入关键词时展示后端返回的全部候选;跨城搜索继续使用 `keyword`。
|
||||
4. 确认 `192.168.100.160:9527` 的 Vite 进程工作目录与 `D:/work2/hl-ui` 当前目标分支一致,重启 Vite 后清除模块缓存并复测。
|
||||
|
||||
## 验收
|
||||
|
||||
- 打开本订单第 1 晚选择酒店,不输入关键词时不再显示“共 1 条”,可看到其他酒店。
|
||||
- 搜索“满洲里”返回 3 家。
|
||||
- 香格里拉仍显示“定制师推荐”,但不会阻止选择其他酒店。
|
||||
- Network 中 `/v3/admin/hotel-candidates` 请求不含 `preferredHotelId`。
|
||||
@@ -0,0 +1,66 @@
|
||||
# 房务最终确认后修改配房按钮被旧前端隐藏(前端待处理)
|
||||
|
||||
## 目标前端
|
||||
|
||||
- **端类型:管理后台(Web)**
|
||||
- **目标仓库:`mmg/hl-ui`**
|
||||
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||
- **前端本地测试环境:`http://192.168.100.160:9527`**
|
||||
- **小程序:无需处理**
|
||||
|
||||
本通知应由管理后台前端负责人在 `mmg/hl-ui` 处理,不属于后端仓库 `wx/HL`,也不属于小程序前端。
|
||||
|
||||
## 现象
|
||||
|
||||
订单 `HL20260719151313174` 已最终确认、配房进度 `2/2`,房务详情配房行程只显示“询房”按钮;既有配房行未显示“替换”“改协议价”“移除”等修改入口。
|
||||
|
||||
业务要求:最终确认后房务仍可修改配房。发起修改时先将住宿需求从完成态解冻回配房中,再执行替换、移除、改价等操作。
|
||||
|
||||
## 已确认原因
|
||||
|
||||
`192.168.100.160:9527` 当前 Vite 服务实际返回的 `OrderDetailModal.vue` 仍包含旧门槛:
|
||||
|
||||
```js
|
||||
const canMutateRequirement = computed(
|
||||
() =>
|
||||
canEditHouseOrder.value &&
|
||||
!requirementReadOnly.value &&
|
||||
(merged.value?.finalized !== true || merged.value?.reopenAction?.enabled === true)
|
||||
)
|
||||
```
|
||||
|
||||
后端详情当前有意将 `reopenAction` 设为 disabled,不再把“回配”作为单独按钮;后端各直接编辑入口会调用 `reopenIfFinalizedForDirectEdit()` 自动解冻。因此旧前端条件在 `finalized=true` 时恒为 false,连真正的修改按钮也全部隐藏。
|
||||
|
||||
当前 `D:/work2/hl-ui` 源码已经改为:
|
||||
|
||||
```js
|
||||
const canMutateRequirement = computed(
|
||||
() => canEditHouseOrder.value && !requirementReadOnly.value
|
||||
)
|
||||
```
|
||||
|
||||
并由 `ensureRequirementEditable(reqId)` 在写操作前调用 `reopenRequirement(reqId)`,与后端自动解冻语义一致。
|
||||
|
||||
## 前端处理要求
|
||||
|
||||
1. 同步当前 `D:/work2/hl-ui` 正确实现到 `192.168.100.160:9527` 实际运行工作树,重启 Vite 服务。
|
||||
2. `canMutateRequirement` 不得用 `finalized` 或 `reopenAction.enabled` 隐藏配房修改入口。
|
||||
3. 最终确认后,只要订单属于当前房务、需求仍生效且未驳回/作废,应继续显示:
|
||||
- 当晚“替换”;
|
||||
- 已确认配房行“改协议价”;
|
||||
- 已确认配房行“移除”。
|
||||
4. 写操作前沿用 `ensureRequirementEditable()`;不得要求用户先点击一个独立“回配”按钮。
|
||||
5. 驳回需求、作废需求、非本人订单、组长只读入口仍保持只读,不得放宽权限边界。
|
||||
|
||||
## 后端依据
|
||||
|
||||
- `HouseAssignmentService.reopenIfFinalizedForDirectEdit()`:最终确认后的直接编辑自动解冻。
|
||||
- 替换、移除、改协议价等多个写入口均已调用该方法。
|
||||
- `HouseDetailAggregator` 不暴露独立 reopen action 属预期行为,不需要后端恢复该按钮。
|
||||
|
||||
## 验收
|
||||
|
||||
- 打开订单 `HL20260719151313174`,完成态仍可看到“替换”“改协议价”“移除”。
|
||||
- 点击修改后 Network 先出现 reopen 或对应写接口自动解冻,操作成功,房务状态回到配房中。
|
||||
- 重新配房并逐日确认后,可再次最终确认。
|
||||
- 非本人、驳回、作废及只读入口仍不显示写操作。
|
||||
@@ -0,0 +1,45 @@
|
||||
# 房务调整提醒按总人数展示(修改接口)
|
||||
|
||||
## 目标前端
|
||||
|
||||
- **端类型:管理后台(Web)**
|
||||
- **目标仓库:`mmg/hl-ui`**
|
||||
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||
- **联调/验收环境:`http://192.168.100.160:9527`**
|
||||
- **小程序:无需处理**
|
||||
|
||||
## 背景
|
||||
|
||||
订单调整删除一名出行人后,房务端“订单调整提醒”曾显示人员类型变化,例如“儿童人数 2 → 1”。房务只需要核对订单总人数,因此后端统一调整为“总人数 4 → 3”。
|
||||
|
||||
## 接口语义变更
|
||||
|
||||
涉及调整记录及房务详情中复用的 `changeItems`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "HEADCOUNT",
|
||||
"label": "总人数",
|
||||
"before": "4",
|
||||
"after": "3"
|
||||
}
|
||||
```
|
||||
|
||||
- 总人数发生变化时,只返回一条 `HEADCOUNT`,`label` 固定为 `总人数`。
|
||||
- 不再按成人、儿童、小童、婴儿分别返回多条 `HEADCOUNT`。
|
||||
- 人员类型变化但总人数不变时,不返回 `HEADCOUNT`。
|
||||
- 出行人明细及 `addedTravelerIds` 契约不变。
|
||||
|
||||
## 管理后台处理要求
|
||||
|
||||
1. 房务“订单调整提醒”直接展示 `label + before → after`,不得自行按人员类型重新计算。
|
||||
2. 不要依赖旧的“成人人数/儿童人数/小童人数/婴儿人数”标签。
|
||||
3. 历史调整记录仍可能保留旧标签,前端需要兼容只读展示;新记录按“总人数”展示。
|
||||
|
||||
## 验收
|
||||
|
||||
- 订单出行人由 4 人删除 1 人后,房务提醒显示“总人数 4 → 3”。
|
||||
- 页面不显示“儿童人数 2 → 1”等人员类型变化。
|
||||
- 总人数不变时不出现人数调整提醒。
|
||||
|
||||
后端关联:`wx/HL#5090`、PR `wx/HL#5091`。
|
||||
@@ -0,0 +1,127 @@
|
||||
# 作废房务需求只读与历史详情(修改接口)
|
||||
|
||||
## 目标前端
|
||||
|
||||
- **端类型:管理后台(Web)**
|
||||
- **目标仓库:`mmg/hl-ui`**
|
||||
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||
- **联调/验收环境:`http://192.168.100.160:9527`**
|
||||
- **小程序:无需处理**
|
||||
|
||||
## 业务硬规则
|
||||
|
||||
作废房务需求只能查看。页面不得提供联系房务、联系定制师、转单、配房、询房、替换、移除、改价、清空配房、驳回、最终确认等任何业务操作。
|
||||
|
||||
## 问题与原因
|
||||
|
||||
同一订单调整后会保留旧的失活需求并生成新的生效需求。此前列表虽返回 `voided=true`,但前端未标红、未展示原因;点击旧行又只按 `orderId` 请求详情,导致打开当前生效需求,出现旧记录与当前配房串版。
|
||||
|
||||
## 接口变更
|
||||
|
||||
### 1. 我的房务订单列表
|
||||
|
||||
`GET /v3/admin/order/grab-pool/my-claims/hotel`
|
||||
|
||||
作废行新增/明确字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "2078779808241668097",
|
||||
"orderId": "2078739922130243586",
|
||||
"requirementVersion": 2,
|
||||
"voided": true,
|
||||
"voidReason": "订单调整生成新版本,原需求已作废",
|
||||
"voidedAt": "2026-07-20T16:52:29",
|
||||
"primaryAction": {
|
||||
"type": "VIEW",
|
||||
"url": "/admin/order/2078739922130243586/arrange?requirementId=2078779808241668097"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意:列表字段 `id` 就是本行的房型需求 ID,打开详情时必须连同该 ID 传给详情接口,不能只传 `orderId`。
|
||||
|
||||
### 2. 房务详情支持指定历史需求
|
||||
|
||||
`GET /admin/house/orders/{orderId}?requirementId={requirementId}`
|
||||
|
||||
该接口使用既有房务详情命名空间 `/admin/house`,请求时必须沿用 API 模块的绝对路径配置,不得自行添加 `/v3`。错误请求 `/v3/admin/house/orders/{orderId}` 会返回“接口不存在”。
|
||||
|
||||
### 2026-07-20 本地测试环境 Network 复核
|
||||
|
||||
`http://192.168.100.160:9527` 点击作废行“查看”时实际发出:
|
||||
|
||||
```text
|
||||
错误:GET /v3/admin/house/orders/2078739922130243586?requirementId=2078779808241668097
|
||||
正确:GET /admin/house/orders/2078739922130243586?requirementId=2078779808241668097
|
||||
```
|
||||
|
||||
同一弹窗的需求历史请求已经使用正确命名空间:
|
||||
|
||||
```text
|
||||
GET /admin/house/orders/2078739922130243586/requirement-history
|
||||
```
|
||||
|
||||
因此请检查详情 API 方法是否误传 `baseURL: '/v3'`、V3 request config 或再次拼接 `/v3`。只修改详情请求,`operation-log` 仍按它自己的既有 `/v3/admin/house/...` 契约处理,不得全局替换。
|
||||
|
||||
- 不传 `requirementId`:保持原行为,返回当前生效需求。
|
||||
- 传 `requirementId`:精确返回该订单的指定历史需求;ID 不属于该订单时返回业务错误。
|
||||
- 作废历史需求不会混入当前需求的配房数据。
|
||||
|
||||
详情新增顶层字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"viewedRequirementId": "2078779808241668097",
|
||||
"historicalRequirement": true,
|
||||
"voided": true,
|
||||
"voidReason": "订单调整生成新版本,原需求已作废",
|
||||
"voidedAt": "2026-07-20T16:52:29"
|
||||
}
|
||||
```
|
||||
|
||||
`requirement.history[]` 同步增加 `requirementId`、`voided`、`voidReason`、`voidedAt`。
|
||||
|
||||
历史作废详情中:
|
||||
|
||||
- `actions` 下全部动作的 `enabled=false`;
|
||||
- `permissions.canEdit=false`;
|
||||
- `permissions.canSendMessage=false`;
|
||||
- `requirement.actions.canSendMessage=false`,其他写动作同样为 `false`;
|
||||
- `permissions.canViewMessage=true` 只代表允许查看既有留言,不代表可回复。
|
||||
|
||||
## 管理后台处理要求
|
||||
|
||||
1. `voided=true` 的列表行和详情必须使用明确的红色作废样式,并展示“已作废”、`voidReason` 和作废时间。
|
||||
2. 作废列表行只能显示“查看”;不得显示“更多”菜单或任何联系、流转、配房按钮。
|
||||
3. 点击作废行必须携带本行 `id` 作为 `requirementId` 请求详情,不得复用当前有效需求详情。
|
||||
4. 详情只要 `voided=true` 或 `historicalRequirement=true`,前端必须再次强制只读并隐藏全部业务操作,不能只依赖某一个按钮字段。
|
||||
5. 人数调整提醒直接展示后端 `changeItems`;按通知 70,人数仅显示“总人数 4 → 3”,不显示成人/儿童等具体人员类型变化。
|
||||
|
||||
### 当前订单详情增加“作废记录”入口
|
||||
|
||||
在当前有效订单的房务详情中增加按钮:`作废记录(N)`,让房务不必返回列表寻找红色卡片。
|
||||
|
||||
- `N` 为该订单历史需求中 `voided=true` 的数量;没有作废记录时可隐藏按钮或显示禁用的 `作废记录(0)`。
|
||||
- 按钮建议放在详情标题区或需求信息区,与普通业务写操作分开,避免误认为可以恢复作废需求。
|
||||
- 点击后打开只读抽屉/弹窗,列出该订单全部作废需求,至少展示:需求版本、提交/作废时间、作废原因、原状态。
|
||||
- 列表数据可使用详情响应的 `requirement.history[]`,按 `voided=true` 过滤;每项必须使用自身 `requirementId`。
|
||||
- 点击某条“查看详情”时调用:
|
||||
|
||||
```text
|
||||
GET /admin/house/orders/{orderId}?requirementId={该条requirementId}
|
||||
```
|
||||
|
||||
- 历史详情继续执行严格只读规则,只能关闭/返回,不能联系、转单、配房、清空、驳回、最终确认或执行其他业务操作。
|
||||
- 作废记录列表按 `voidedAt DESC` 展示,最新作废记录在前;本入口不改变“我的订单”主列表中作废卡片统一置底的规则。
|
||||
|
||||
## 验收
|
||||
|
||||
- 同一订单的作废旧行与当前有效行能明确区分,旧行标红并显示原因。
|
||||
- 旧行仅有“查看”,不存在任何写操作或联系操作。
|
||||
- 打开旧行后 `viewedRequirementId` 等于该行 `id`,内容为旧需求快照,不出现当前配房。
|
||||
- 作废详情仅可阅读,所有动作均隐藏或禁用。
|
||||
- 删除一名出行人后,调整提醒显示“总人数 4 → 3”。
|
||||
- 当前有效订单详情显示“作废记录(1)”;点击可看到该订单的作废需求列表,并能打开对应只读历史详情。
|
||||
|
||||
后端关联:`wx/HL#5092`。
|
||||
@@ -0,0 +1,70 @@
|
||||
# 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||
- 小程序、H5 及其他前端:无需处理
|
||||
|
||||
# 变更背景
|
||||
|
||||
订单改出发日期后,原入住日期已经配置的酒店不能静默平移到新日期。房务需要明确核对并逐条删除旧配房;旧配房未清完时禁止最终确认。
|
||||
|
||||
# 详情接口新增字段
|
||||
|
||||
`GET /admin/house/orders/{orderId}` 顶层新增:
|
||||
|
||||
```json
|
||||
{
|
||||
"pendingRescheduleAssignments": [
|
||||
{
|
||||
"assignmentId": "99001",
|
||||
"originalStayDate": "2026-07-22",
|
||||
"hotelId": "8001",
|
||||
"hotelName": "示例酒店",
|
||||
"roomTypeId": "9001",
|
||||
"roomTypeName": "普通标间",
|
||||
"roomCategory": "STANDARD",
|
||||
"roomCategoryLabel": "标间",
|
||||
"roomCount": 2,
|
||||
"assignmentStage": "FINAL_CONFIRMED",
|
||||
"assignmentStageLabel": "最终确认",
|
||||
"deleteEndpoint": "DELETE /v3/admin/order/assignments/99001"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`assignmentStage` 枚举:
|
||||
|
||||
| 值 | 中文 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `UNCONFIRMED` | 未单日确认 | 改期前仍处于询房/候选阶段 |
|
||||
| `DAY_CONFIRMED` | 单日确认 | 改期前已完成该日确认,但原需求未最终确认 |
|
||||
| `FINAL_CONFIRMED` | 最终确认 | 改期前所属住宿需求已经最终确认 |
|
||||
|
||||
数组为空表示没有改期旧配房待清理。旧配房不会再出现在当前 `itinerary[].assignments`,也不计入当前配房进度。
|
||||
|
||||
# 前端交互要求
|
||||
|
||||
1. 在“订单调整提醒”的改期记录下展示 `pendingRescheduleAssignments`,每行至少显示:原日期、酒店、房型、数量、配房步骤。
|
||||
2. 每行提供“删除旧配房”,调用返回的 `deleteEndpoint`;成功后重新拉取详情。
|
||||
3. 只要数组非空,不允许用户最终确认,并显示后端 `actions.canFinalize.disabledReason`。
|
||||
4. 数组清空后再按后端 `actions.canFinalize.enabled` 决定按钮状态,禁止前端自行推断。
|
||||
5. 删除仍可能因领取归属、房务写权限、并发修改或库存释放链路失败而报错,直接展示后端消息并刷新详情。
|
||||
|
||||
# 最终确认写口门禁
|
||||
|
||||
`POST /admin/house/assignments/requirements/{requirementId}/finalize`
|
||||
|
||||
若仍有旧日期配房,返回业务错误:
|
||||
|
||||
- code:`808183`
|
||||
- message:`改期前旧日期配房尚未清理,请逐条删除后再最终确认`
|
||||
|
||||
该门禁由后端强制执行,前端禁用按钮仅用于交互提示。
|
||||
|
||||
# 兼容说明
|
||||
|
||||
- 字段为 additive;旧页面忽略新增字段不会影响反序列化。
|
||||
- `roomTypeName` 在资源服务降级时可为空,前端回退 `roomCategoryLabel`。
|
||||
- `assignmentId`、`hotelId`、`roomTypeId` 按字符串处理,禁止转 JavaScript `number`。
|
||||
@@ -0,0 +1,64 @@
|
||||
# 作废需求详情冻结作废时配房快照
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||
- 小程序、H5 及其他前端:无需处理
|
||||
|
||||
## 业务规则
|
||||
|
||||
订单调整生成新住宿需求时,旧需求详情必须展示“该需求作废当时”的配房事实,不能复用当前订单的新日期、新行程地点或当前配房。历史数据严格只读,不能恢复或执行任何业务操作。
|
||||
|
||||
## 接口
|
||||
|
||||
```text
|
||||
GET /admin/house/orders/{orderId}?requirementId={作废需求ID}
|
||||
```
|
||||
|
||||
历史详情的 `itinerary[].assignments[]` 明确返回冻结字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"stayDate": "2026-07-28",
|
||||
"assignments": [
|
||||
{
|
||||
"assignmentId": "2079188886726098945",
|
||||
"hotelId": "2023714929877450753",
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"roomTypeId": "2023727403196502017",
|
||||
"roomTypeName": "普通标间",
|
||||
"roomCategory": "STANDARD",
|
||||
"confirmStatus": "CONFIRMED",
|
||||
"confirmStatusLabel": "已确认",
|
||||
"roomCount": 1,
|
||||
"protoPrice": "280.00",
|
||||
"settlementPrice": "279.00",
|
||||
"settleType": "sign",
|
||||
"sellPrice": "280.00",
|
||||
"deductInventory": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`stayDate`、酒店、房型、数量、价格、支付方式、库存口径和确认状态均来自作废时快照。后续删除/修改当前配房、资源酒店改名或价格调整,不影响历史详情。
|
||||
|
||||
## 管理后台处理要求
|
||||
|
||||
1. 作废详情按 `itinerary[]` 展示旧日期;每条配房至少显示酒店、`roomTypeName`、`roomCount` 和 `confirmStatusLabel`。
|
||||
2. 房型名称优先使用 `roomTypeName`;部署前没有可信名称快照的旧数据才允许回退 `roomCategoryLabel`,不得按 ID 或列表位置猜测。
|
||||
3. `historicalRequirement=true` 或 `voided=true` 时保持严格只读:仅允许查看、关闭、查看车务;不得出现联系、转单、配房、删除、清空、驳回、最终确认等房务写操作。
|
||||
4. 禁止用当前订单出发日期推算历史 `stayDate`,禁止调用当前资源结果覆盖后端返回的历史酒店/房型快照。
|
||||
5. ID 字段按字符串处理,禁止转换为 JavaScript `number`。
|
||||
|
||||
## 兼容与验收证据
|
||||
|
||||
- 变更为 additive,当前生效需求的接口结构不变。
|
||||
- 测试订单:`HL20260719151313174`,`orderId=2078739922130243586`。
|
||||
- 作废需求:`requirementId=2079181505287897090`。
|
||||
- 测试环境返回 3 晚旧配房:2026-07-28/29/30,酒店“呼伦贝尔香格里拉大酒店”,房型“普通标间”,数量 1/2/3,状态均为“已确认”。
|
||||
- 浏览器验收使用仓库 CDP 脚本完成,页面无 console error 或 failed request。
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 【前端待处理·管理后台】#4933 车务矩阵图例与空闲格直接派单
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 仓库地址:<https://git.1814.love:8443/mmg/hl-ui.git>
|
||||
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||
- 小程序:无需处理,本问题仅涉及管理后台车务矩阵页面
|
||||
- H5:无需处理,本问题仅涉及管理后台车务矩阵页面
|
||||
|
||||
## 问题与结论
|
||||
|
||||
2026-07-21 在车务管理员角色访问 `/fleet/matrix` 时确认两处前端缺陷:
|
||||
|
||||
1. 页面图例仍显示“绿 海拉尔接/送机、橙 外地接/送机”,把地域误当成衔接状态;这与 #4933 已确认的后端语义不一致。
|
||||
2. 点击车辆空闲格只弹出“请先从未派订单池拖拽或在订单详情发起派单”,阻断了矩阵页直接派单。矩阵页已有 `AssignModal` 和车辆预选能力,应直接进入本页派单流程。
|
||||
3. 订单详情“历史操作”把保险退保结果的内部 JSON 原样拼进业务时间线,业务人员无法阅读。
|
||||
4. 矩阵只解释衔接标记,未解释蓝灰色派车占用条;同车出现重叠时也没有冲突语义。后端 #5109 将统一过滤取消记录,前端仍需区分普通占用与有效派车重叠异常。
|
||||
|
||||
后端 #5109 已统一矩阵统计与占用条口径,并新增有效派车重叠异常字段。前端必须消费后端下发的衔接状态和异常信息,不能按“海拉尔/外地”或占用条颜色自行推导。
|
||||
|
||||
## 图例适配要求
|
||||
|
||||
图例按 `connections[].status/style` 固定展示以下四种业务语义:
|
||||
|
||||
| `status` | `style` | 图例文案 | 展示要求 |
|
||||
| --- | --- | --- | --- |
|
||||
| `SAME_CITY_OK` | `GREEN` | 同城衔接正常 | 绿色实线/标记 |
|
||||
| `DIFFERENT_CITY` | `DARK_RED` | 不同城 | 深红色实线/强提醒 |
|
||||
| `SAME_CITY_TOO_SHORT` | `LIGHT_RED` | 同城间隔不足 | 淡红色实线/提醒 |
|
||||
| `MISSING_TIME` | `RED_DASHED` | 缺少接送信息 | 红色虚线 |
|
||||
|
||||
- 删除“海拉尔接/送机”“外地接/送机”两项旧图例。
|
||||
- 图例颜色、矩阵连线/标记和悬浮详情必须使用同一份状态映射。
|
||||
- 悬浮详情优先展示后端 `label`、`reasonText`、`intervalMinutes`、`minIntervalMinutes`;不得覆盖后端文案或重新计算状态。
|
||||
|
||||
图例应分成两组,避免混淆:
|
||||
|
||||
- 派车占用:说明订单占用条的基础颜色、边框及文字含义。
|
||||
- 订单衔接:继续展示上述四种后端衔接状态。
|
||||
|
||||
若后端返回有效派车重叠异常标记,必须使用独立冲突样式和明确文案,不能复用普通占用色,也不能把两条记录静默叠放。
|
||||
|
||||
## 历史操作展示要求
|
||||
|
||||
- 时间线默认只展示 `opTypeLabel`、`operationStatusLabel`、`summary`、操作时间和业务操作人。
|
||||
- `detailJson` 仅供诊断或折叠的技术明细使用,不得直接拼接到 `content`,不得默认展示 JSON。
|
||||
- 保险退保成功示例应展示为“司机保险退保成功 / 已完成 / 共 1 个服务日,线上成功 1,线下完成 0”,不展示 `resolution`、`refundResult`、日期数组等内部字段名。
|
||||
- `canceled` 且有效派车组为 0 的订单可在详情中查看取消时间线,但不能在矩阵中继续绘制占用条。
|
||||
|
||||
## 空闲格直接派单要求
|
||||
|
||||
用户点击车辆某日的空闲格后,应在当前矩阵页面完成派单:
|
||||
|
||||
1. 打开未派订单选择层(或复用现有选择组件),只列出当前筛选范围内可派订单。
|
||||
2. 选中订单后打开现有 `AssignModal`。
|
||||
3. 自动预选被点击车辆,并将点击日期带入派单日期上下文;仍允许用户在弹窗内调整司机、车辆和合法日期范围。
|
||||
4. 按现有候选、预校验和创建派单接口完成校验与提交,不能绕过冲突、容量、常驻错配确认等后端门禁。
|
||||
5. 成功后关闭弹窗并刷新矩阵、统计和未派订单数量;失败时保留用户已填内容并展示后端错误。
|
||||
6. 当确实没有可派订单时才显示空态“暂无可派订单”,不得再提示用户去订单详情发起派单。
|
||||
|
||||
建议直接修正当前 `@idle-click="onIdleClick"` 分支:现实现只调用 `message.info`,但同页已经挂载 `AssignModal`、`activeOrder`、`preselectVehicle` 和 `assignMode`,应复用现有派单链路。
|
||||
|
||||
## 接口证据
|
||||
|
||||
矩阵响应已提供后端判定结果,核心字段位于车辆相邻订单衔接集合 `connections`:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "SAME_CITY_OK",
|
||||
"label": "同城衔接正常",
|
||||
"style": "GREEN",
|
||||
"reasonCode": "SAME_CITY_INTERVAL_SUFFICIENT",
|
||||
"reasonText": "同城前后订单衔接间隔满足配置阈值",
|
||||
"intervalMinutes": 180,
|
||||
"minIntervalMinutes": 120
|
||||
}
|
||||
```
|
||||
|
||||
后端允许值:
|
||||
|
||||
- `status`:`MISSING_TIME`、`DIFFERENT_CITY`、`SAME_CITY_TOO_SHORT`、`SAME_CITY_OK`
|
||||
- `style`:`RED_DASHED`、`DARK_RED`、`LIGHT_RED`、`GREEN`
|
||||
|
||||
每辆车新增 `overlaps`,仅在两个不同的有效派车组日期重叠时返回;无异常时固定为空数组:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "100",
|
||||
"assignments": [],
|
||||
"overlaps": [
|
||||
{
|
||||
"firstAssignmentGroupId": "1001",
|
||||
"secondAssignmentGroupId": "1002",
|
||||
"overlapStartDate": "2026-05-03",
|
||||
"overlapEndDate": "2026-05-04",
|
||||
"code": "ACTIVE_ASSIGNMENT_OVERLAP",
|
||||
"message": "同一车辆存在有效派车日期重叠"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `canceled` 派车切片不进入 `assignments`、顶部统计、衔接计算或 `overlaps`。
|
||||
- 同一派车组的有效日期被取消日切断时,`assignments` 返回两个不连续日期段,但顶部仍按一个派车组计数。
|
||||
- `unassigned-orders` 条目及 `parallelAssignments[]` 新增 `serviceDateSegments[]`,每项包含 `startDate/endDate`;存在取消日期缺口时返回多个连续有效段。旧 `startDate/endDate` 仅表示该组总体边界,前端绘制或判断逐日有效性必须以 `serviceDateSegments` 为准。
|
||||
- `overlaps` 是明确的数据异常,不是新的普通占用颜色;前端应显示独立冲突提示并允许定位涉及的两个派车组。
|
||||
|
||||
派单继续复用现有车务候选、预校验和创建派单接口,不新增接口。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] `/fleet/matrix` 图例只展示四种后端衔接语义,不再出现地域型图例。
|
||||
- [ ] 构造四种 `status/style` 数据,图例、矩阵标记和悬浮说明三者一致。
|
||||
- [ ] 点击任意车辆空闲格可在当前页面选择未派订单并打开派单弹窗。
|
||||
- [ ] 派单弹窗自动预选点击车辆及日期上下文。
|
||||
- [ ] 正常派单成功后矩阵、统计与未派数刷新。
|
||||
- [ ] 冲突或校验失败时展示后端错误且不产生半成品派单。
|
||||
- [ ] 无可派订单时展示空态,不再引导去订单详情。
|
||||
- [ ] 图例分别说明“派车占用”和“订单衔接”,普通占用、衔接标记与重叠异常不会混淆。
|
||||
- [ ] 历史操作默认不显示或拼接 `detailJson`,保险退保记录使用中文业务摘要。
|
||||
- [ ] 已取消且有效派车组为 0 的订单只保留详情历史,不绘制矩阵占用条。
|
||||
- [ ] `vehicles[].overlaps[]` 非空时展示明确重叠异常;为空时不显示冲突样式。
|
||||
- [ ] 在 <http://192.168.100.160:9527> 以车务管理员角色完成页面、Network/API 响应和截图验收。
|
||||
|
||||
## 现场证据
|
||||
|
||||
- 页面:`/fleet/matrix`
|
||||
- 角色:车务管理员
|
||||
- 现象:错误地域图例;点击车辆空闲格连续出现阻断性提示
|
||||
- 现场截图:由 #4933 验收反馈于 2026-07-21 提供
|
||||
- 当前调试 Chrome 登录态已失效,自动复核跳转登录页;修复后需在上述固定验收环境重新登录并完成验收清单。
|
||||
@@ -0,0 +1,68 @@
|
||||
# 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 前端仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调环境:`http://192.168.100.160:9527`
|
||||
- 其他前端:不需要处理
|
||||
|
||||
# 变更目标
|
||||
|
||||
房务管理员“订单列表”的第五个状态筛选由“异常”替换为“作废”。该筛选必须走后端分页,不能只过滤当前页。
|
||||
|
||||
# 接口变更
|
||||
|
||||
## 我的接单
|
||||
|
||||
`GET /v3/admin/order/grab-pool/my-claims/hotel`
|
||||
|
||||
新增查询参数取值:
|
||||
|
||||
```text
|
||||
status=voided
|
||||
```
|
||||
|
||||
语义:仅返回当前房务曾领取、后因订单调整生成新版本而作废的旧住宿需求。
|
||||
|
||||
响应保持原结构:
|
||||
|
||||
- `list`:本页作废需求;每行 `voided=true`。
|
||||
- `total`:全部命中作废需求数,用于服务端分页。
|
||||
- `stats.voided`:当前房务全部作废需求计数,不随当前状态筛选收窄。
|
||||
- `voidReason`、`voidedAt`:作废原因和时间。
|
||||
- `primaryAction.code=VIEW`:只读查看,不提供配房、最终确认、清空配房、转单等写操作。
|
||||
|
||||
现有 `status=exception` 契约仍保留给其他业务入口,语义不变;本页面不再把它作为第五个筛选项展示。
|
||||
|
||||
# 管理后台适配要求
|
||||
|
||||
房务管理员“订单列表”顶部筛选固定为:
|
||||
|
||||
1. 全部
|
||||
2. 配房中
|
||||
3. 待确认
|
||||
4. 已完成
|
||||
5. 作废
|
||||
|
||||
第五项适配:
|
||||
|
||||
- 文案由“异常”改为“作废”。
|
||||
- value 由 `exception` 改为 `voided`。
|
||||
- 数量徽标读取 `stats.voided`,不要继续读取 `stats.exception`。
|
||||
- 点击后请求 `status=voided`,列表和 `total` 直接使用接口结果,禁止前端当前页二次筛选。
|
||||
- 作废卡片保持红色只读样式,显示作废原因和作废时间。
|
||||
- “查看”必须携带该行自己的住宿需求 ID 作为 `requirementId`,进入作废历史详情。
|
||||
|
||||
# 验收标准
|
||||
|
||||
- 在 `http://192.168.100.160:9527/housekeeper/orders` 不再显示“异常”筛选,显示“作废”。
|
||||
- “作废”徽标数量等于接口 `stats.voided`。
|
||||
- 点击“作废”后 Network 请求包含 `status=voided`。
|
||||
- 多页作废数据的 `total`、分页与列表一致,不发生只过滤当前页的问题。
|
||||
- 作废列表仅包含 `voided=true` 的历史需求;正常配房中的当前需求不混入。
|
||||
- 作废行只保留“查看”,详情为只读状态。
|
||||
|
||||
# 后端交付
|
||||
|
||||
- 后端 Issue:`wx/HL#5103`
|
||||
- 后端分支:`fix/5103-house-voided-filter`
|
||||
- 合入并部署测试环境后,前端再进行 Network 与页面验收。
|
||||
@@ -0,0 +1,107 @@
|
||||
# 房务改期旧配房人工清理
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||
- 其他前端:小程序、H5 无需处理
|
||||
|
||||
> 服务:`hl-order-service-v3`(8086)
|
||||
> PR:#5097
|
||||
> 日期:2026-07-21
|
||||
> 影响范围:房务订单详情弹窗的改期后旧配房清理和重新配房流程
|
||||
|
||||
---
|
||||
|
||||
## 关键变化
|
||||
|
||||
订单改期后,既有配房不再自动迁移到新日期,也不会自动变为新需求的可确认候选。后端将这些配房标记为待人工删除;房务必须逐条删除旧配房,再按新行程重新提交并确认配房。
|
||||
|
||||
当前后端已返回待清理数据和删除接口,但管理后台尚未消费该字段,导致页面无法完成该流程。
|
||||
|
||||
---
|
||||
|
||||
## 接口清单
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 | 用途 |
|
||||
|------|------|------|------|------|
|
||||
| 房务订单详情 | GET | `/admin/house/orders/{orderId}` | 响应新增字段 | 返回改期后待删除旧配房 |
|
||||
| 删除配房 | DELETE | `/v3/admin/order/assignments/{assignmentId}` | 既有接口 | 删除一条待清理旧配房 |
|
||||
|
||||
---
|
||||
|
||||
## 房务订单详情
|
||||
|
||||
### `GET /admin/house/orders/{orderId}`
|
||||
|
||||
响应 `data` 新增 `pendingRescheduleAssignments`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `assignmentId` | String | 待删除配房 ID |
|
||||
| `originalStayDate` | String | 原入住日期,格式 `yyyy-MM-dd` |
|
||||
| `hotelId` | String | 原酒店 ID |
|
||||
| `hotelName` | String | 原酒店名称快照 |
|
||||
| `roomTypeId` | String | 原房型 ID |
|
||||
| `roomTypeName` | String | 原房型名称,资源不可用时可为空 |
|
||||
| `roomCategory` | String | 房型字典 code |
|
||||
| `roomCategoryLabel` | String | 房型中文 |
|
||||
| `roomCount` | Integer | 房间数量 |
|
||||
| `assignmentStage` | String | `UNCONFIRMED`、`DAY_CONFIRMED`、`FINAL_CONFIRMED` |
|
||||
| `assignmentStageLabel` | String | 未单日确认、单日确认、最终确认 |
|
||||
| `deleteEndpoint` | String | 本条配房的删除接口 |
|
||||
|
||||
无待清理配房时该字段返回空数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"pendingRescheduleAssignments": [
|
||||
{
|
||||
"assignmentId": "2079430000000000001",
|
||||
"originalStayDate": "2026-07-23",
|
||||
"hotelId": "2001",
|
||||
"hotelName": "旧日期酒店",
|
||||
"roomTypeId": "3001",
|
||||
"roomTypeName": "标准大床房",
|
||||
"roomCategory": "STANDARD",
|
||||
"roomCategoryLabel": "标准间",
|
||||
"roomCount": 2,
|
||||
"assignmentStage": "DAY_CONFIRMED",
|
||||
"assignmentStageLabel": "单日确认",
|
||||
"deleteEndpoint": "DELETE /v3/admin/order/assignments/2079430000000000001"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端处理规则
|
||||
|
||||
1. `pendingRescheduleAssignments` 非空时,在房务订单详情中显示“改期前待清理配房”区域,逐条展示原入住日期、酒店、房型、房间数和配房阶段。
|
||||
2. 每条记录使用其 `deleteEndpoint` 调用删除配房接口;删除成功后重新获取订单详情。
|
||||
3. 待清理列表非空时,禁用最终确认,并提示“改期前旧日期配房尚未清理,请逐条删除”。
|
||||
4. 删除完成后,由房务按当前行程重新提交配房候选,再执行单日确认。不得直接对旧配房调用确认接口。
|
||||
5. `assignmentStage` 仅作展示,不可通过编辑旧配房绕过删除步骤。
|
||||
|
||||
---
|
||||
|
||||
## 边界行为
|
||||
|
||||
- 直接确认旧配房不会产生候选,接口将返回 `808118`“该天无可确认的询房中候选”。
|
||||
- 旧配房未清理时,最终确认返回 `808183`,不得绕过。
|
||||
- 删除后重新配房仍沿用现有提交和单日确认接口,无新增请求体字段。
|
||||
|
||||
## 不影响范围
|
||||
|
||||
- 抢单池、领取、转单和释放流程不变。
|
||||
- 小程序、H5 无需适配。
|
||||
- 非改期订单的详情和配房流程不变。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- PR:[#5097](https://git.1814.love:8443/wx/HL/pulls/5097)
|
||||
@@ -0,0 +1,73 @@
|
||||
# 房务作废配房视觉区分
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||
- 其他前端:小程序、H5 无需处理
|
||||
|
||||
> 服务:`hl-order-service-v3`(8086)
|
||||
> 关联 PR:#5097
|
||||
> 日期:2026-07-21
|
||||
> 影响范围:房务订单详情中的作废需求及配房快照展示
|
||||
|
||||
---
|
||||
|
||||
## 关键问题
|
||||
|
||||
当前管理后台打开作废住宿需求时,顶部已提示“只读查看”和“已作废”,但“配房行程”仍使用绿色背景以及“已确认/已完成”标签。该视觉语义会让房务误认为这些配房仍然有效。
|
||||
|
||||
作废需求中的配房数据是历史快照,仅用于追溯,不能复用当前有效配房的成功态样式。
|
||||
|
||||
---
|
||||
|
||||
## 前端处理要求
|
||||
|
||||
1. 当详情响应表明当前查看的住宿需求已作废时,配房行程内所有快照行统一进入“作废历史”展示态。
|
||||
2. 行背景使用浅红色警示背景,边框和状态标签使用红色语义;保证文字对比度,不使用高饱和纯红大面积填充。
|
||||
3. 原绿色“已确认”和“已完成”标签统一替换为红色“已作废”。原确认阶段可作为次要文字展示,例如“作废前:已最终确认”,不得继续作为当前状态标签。
|
||||
4. 酒店、房型、入住日期、房间数、价格和支付方式继续展示,数据来源保持后端作废快照,不读取当前酒店或房型主数据覆盖快照。
|
||||
5. 作废详情必须保持全只读:隐藏或禁用配房、改单、删除、确认、最终确认等写操作。
|
||||
6. 非作废需求继续沿用现有绿色确认/完成样式,不得受影响。
|
||||
|
||||
---
|
||||
|
||||
## 推荐展示层级
|
||||
|
||||
- 需求级:顶部保留红色“已作废”状态和只读原因。
|
||||
- 配房行级:浅红背景 + 红色“已作废”标签。
|
||||
- 历史阶段:灰色次要文案“作废前:未确认 / 单日确认 / 最终确认”。
|
||||
- 操作区:不出现任何可写按钮。
|
||||
|
||||
---
|
||||
|
||||
## 验收场景
|
||||
|
||||
| 场景 | 预期 |
|
||||
|------|------|
|
||||
| 作废需求存在两晚已确认配房快照 | 两晚均显示浅红背景和“已作废”,不显示绿色“已完成” |
|
||||
| 作废前处于单日确认 | 主状态“已作废”,次要信息可显示“作废前:单日确认” |
|
||||
| 作废前已最终确认 | 主状态“已作废”,次要信息可显示“作废前:最终确认” |
|
||||
| 作废快照中的房型已被资源侧修改/删除 | 仍展示作废时冻结的房型名称 |
|
||||
| 打开作废详情 | 只能查看,不存在可触发写接口的按钮 |
|
||||
| 打开当前有效需求 | 原绿色确认/完成样式不变 |
|
||||
|
||||
---
|
||||
|
||||
## 接口依据
|
||||
|
||||
- 详情:`GET /admin/house/orders/{orderId}?requirementId={voidedRequirementId}`
|
||||
- 后端已返回作废需求状态、只读原因及作废时配房快照。
|
||||
- 本通知不要求新增或修改后端接口。
|
||||
|
||||
## 不影响范围
|
||||
|
||||
- 选单池、我的订单筛选和抢单流程不变。
|
||||
- 当前有效需求的配房、确认和最终确认流程不变。
|
||||
- 小程序、H5 无需处理。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 后端 PR:[#5097](https://git.1814.love:8443/wx/HL/pulls/5097)
|
||||
- 改期旧配房人工清理:`changelogs-v2/2026-07/75_5097_改期旧配房人工清理-管理后台.md`
|
||||
@@ -0,0 +1,51 @@
|
||||
# 房务最终确认成功后残留全屏遮罩(前端待处理)
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||
- 其他前端:小程序、H5 无需处理
|
||||
|
||||
本问题属于管理后台交互状态清理,不要求修改后端接口。
|
||||
|
||||
## 现象
|
||||
|
||||
房务管理员在订单详情完成最后一晚“单日确认”后点击“最终确认”,业务操作成功,列表状态也已更新为“已完成”,但详情抽屉关闭后页面仍残留覆盖整个视口的 `.n-modal-mask`。
|
||||
|
||||
遮罩使列表变暗并拦截鼠标操作,页面上没有可见对话框或关闭按钮;按 `Escape` 后遮罩消失,页面恢复操作。
|
||||
|
||||
## 复现记录
|
||||
|
||||
- 测试订单:`HL20260721142957939`
|
||||
- 操作角色:房务管理员
|
||||
- 操作步骤:选单 -> 两晚配房 -> 两晚单日确认 -> 最终确认
|
||||
- 配房口径:第 1 晚扣系统库存,第 2 晚不扣系统库存
|
||||
- 页面结果:最终确认成功,列表显示“已完成”和“已配 2 / 共 2 晚”,但全屏遮罩残留
|
||||
- 运行观察:成功后观察 3 秒,无 console error 或 failed request
|
||||
- 截图证据:`D:/work2/HL-v3/.tmp/house-final-200-browser-flow-final-transient-empty.png`
|
||||
|
||||
按 `Escape` 清除遮罩后重新打开该订单,详情正常显示 `2/2`、两晚酒店与库存口径,说明后端状态和配房数据均已正确落库,问题集中在前端弹层/抽屉的关闭清理。
|
||||
|
||||
## 前端处理要求
|
||||
|
||||
1. 最终确认成功后,关闭确认对话框和订单详情抽屉时同步卸载对应 teleport/modal 容器,不能遗留可见或可交互的 `.n-modal-mask`。
|
||||
2. 无论最终确认请求成功、业务失败、网络异常或用户取消,都必须在结束路径中恢复页面滚动和 pointer events。
|
||||
3. 不得依赖用户按 `Escape`、刷新页面或重新进入菜单恢复操作。
|
||||
4. 避免用全局删除所有遮罩的方式修复;只清理本次最终确认流程拥有的弹层状态,不能影响站内信、全局搜索等其他弹层。
|
||||
5. 最终确认成功后若自动关闭详情,列表状态与统计应正常刷新;若保留详情,则应直接展示正确的完成态 `2/2` 数据。
|
||||
|
||||
## 验收
|
||||
|
||||
| 场景 | 预期 |
|
||||
| --- | --- |
|
||||
| 正常完成最终确认 | 成功提示后无残留遮罩,列表可立即点击、筛选和滚动 |
|
||||
| 最终确认业务失败 | 错误提示可关闭,原详情仍可操作,无遮罩残留 |
|
||||
| 最终确认网络失败 | loading 结束,页面恢复交互,可重试 |
|
||||
| 用户取消最终确认 | 对话框关闭,详情与列表交互正常 |
|
||||
| 成功后重开订单 | 显示“已完成”、正确配房进度及全部配房行 |
|
||||
|
||||
## 接口边界
|
||||
|
||||
- 最终确认沿用现有房务接口,无需新增字段或修改响应结构。
|
||||
- 本次浏览器复测确认成功后的列表和详情数据正确,不创建 `wx/HL` 后端 Issue。
|
||||
@@ -0,0 +1,150 @@
|
||||
# 【前端待处理·管理后台】车务派车看板与矩阵 SSE 实时刷新
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 仓库地址:<https://git.1814.love:8443/mmg/hl-ui.git>
|
||||
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||
- 目标角色:当前登录角色为 `VEHICLE_MANAGER`(车务管理员)
|
||||
- 小程序、司机 H5:无需处理
|
||||
|
||||
## 问题与后端结论
|
||||
|
||||
2026-07-21 复现:订单详情新增用车需求后,fleet-service 已生成未派车占位,但已打开的派车看板和矩阵派单不会实时刷新;多个车务管理员同时在线时,其他人的页面也无法感知变化。
|
||||
|
||||
根因是原管理后台 SSE 只接入消息、聊天、在线状态和房务抢单池信令,没有车务派单数据变更事件,也没有看板/矩阵的刷新订阅。
|
||||
|
||||
后端已补充以下链路:
|
||||
|
||||
1. fleet-service 在用车需求展开事务真正提交后发布 `REQUIREMENT_EXPANDED` 事件,避免页面刷新早于未派占位落库。
|
||||
2. fleet-service 调 user-service 内部广播接口。
|
||||
3. user-service 经 Redis Pub/Sub 把信令分发到所有实例。
|
||||
4. 每个实例只向当前连接角色为 `VEHICLE_MANAGER` 的 SSE 连接发送 `fleet-dispatch-changed`。
|
||||
5. 信令不绑定单个 `adminId`,因此多个车务管理员、多个浏览器标签和多个 user-service Pod 均可收到。
|
||||
|
||||
> 后端代码和定向测试已完成;测试环境是否已部署须以前后端发布记录为准。前端不得在后端未部署时把“收不到新事件”误判为页面监听实现失败。
|
||||
|
||||
## SSE 契约
|
||||
|
||||
继续复用现有管理后台 SSE 连接,不新增浏览器请求接口:
|
||||
|
||||
```http
|
||||
GET /ws/admin-msg/stream?token=<accessToken>
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
新增具名事件:
|
||||
|
||||
```text
|
||||
event: fleet-dispatch-changed
|
||||
data: {"type":"FLEET_DISPATCH","targetRoleKey":"VEHICLE_MANAGER","fleetEvent":"REQUIREMENT_EXPANDED","orderId":"2079494135466643457","requirementId":"..."}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 当前值/说明 |
|
||||
| --- | --- | --- |
|
||||
| `type` | String | 固定 `FLEET_DISPATCH` |
|
||||
| `targetRoleKey` | String | 固定 `VEHICLE_MANAGER` |
|
||||
| `fleetEvent` | String | 当前为 `REQUIREMENT_EXPANDED` |
|
||||
| `orderId` | String/Long | 变更涉及的订单 ID;仅作定位提示,按字符串处理 |
|
||||
| `requirementId` | String/Long | 变更涉及的用车需求 ID;仅作定位提示,按字符串处理 |
|
||||
|
||||
- 前端不得对雪花 ID 使用 `Number()`;如需比较,统一 `String(value)` 后比较。
|
||||
- 本事件是“数据已变化”的轻量信令,不携带看板或矩阵业务正文。
|
||||
- 收到事件后必须重拉现有权威查询接口,不能根据信令自行拼装订单、派车组或矩阵占用条。
|
||||
- 后端只向当前角色为 `VEHICLE_MANAGER` 的连接投递。`SUPER_ADMIN` 只有切换并以车务管理员当前角色重新建立 SSE 后才会收到。
|
||||
|
||||
## 前端接入要求
|
||||
|
||||
### 1. 全局 SSE 接收
|
||||
|
||||
在 `src/composables/useAdminMessageSSE.js` 增加具名事件监听:
|
||||
|
||||
```js
|
||||
es.addEventListener('fleet-dispatch-changed', handleFleetDispatchSignal)
|
||||
```
|
||||
|
||||
解析 JSON 后转入独立的车务派单信令总线。不要复用聊天信令或房务 `lastGrabPoolSignal`,避免模块语义互相污染。
|
||||
|
||||
建议在现有总线文件中新增:
|
||||
|
||||
```js
|
||||
export const lastFleetDispatchSignal = ref(null)
|
||||
|
||||
export function pushFleetDispatchSignal(signal) {
|
||||
if (!signal) return
|
||||
lastFleetDispatchSignal.value = { ...signal, _seq: Date.now() }
|
||||
}
|
||||
```
|
||||
|
||||
连续事件必须保证每次都能触发订阅;实现可沿用现有 `_gseq` 自增模式,不强制使用 `Date.now()`。
|
||||
|
||||
### 2. 派车看板刷新
|
||||
|
||||
目标页面:`src/views/fleet/board/index.vue`
|
||||
|
||||
- 订阅 `lastFleetDispatchSignal`。
|
||||
- 页面处于挂载状态并收到 `REQUIREMENT_EXPANDED` 后调用现有 `fetchBoard()`。
|
||||
- 保留当前筛选条件、分页/视图模式和搜索输入,不得重置用户工作区。
|
||||
- 多条短时间信令可做 100~300ms 合并刷新,避免重复并发请求。
|
||||
- 沿用现有请求序号/取消机制,迟到响应不得覆盖较新的看板数据。
|
||||
|
||||
### 3. 矩阵派单刷新
|
||||
|
||||
目标页面:
|
||||
|
||||
- `src/views/fleet/matrix/index.vue`
|
||||
- `src/views/fleet/matrix/solo/index.vue`(如独立挂载数据上下文)
|
||||
- `src/views/fleet/matrix/composables/useFleetMatrixData.js`
|
||||
|
||||
处理要求:
|
||||
|
||||
- 收到信令后调用现有 `fetchMatrix(requestFilters.value)` 或等价的当前筛选刷新入口。
|
||||
- 同步刷新未派订单池、顶部统计和矩阵占用数据;不能只刷新车辆行而保留旧未派数量。
|
||||
- 保留当前年月、车队、车型和其他筛选条件。
|
||||
- 若派单弹窗正在提交,不得关闭弹窗或清空用户输入;提交结束后以最后一次权威查询结果收敛页面。
|
||||
- 矩阵分窗复用主页面数据组件时只订阅一次,避免同一事件发起重复请求。
|
||||
|
||||
### 4. 断线重连对账
|
||||
|
||||
Redis Pub/Sub 和 SSE 均不提供历史事件重放。断线期间可能漏过 `fleet-dispatch-changed`,因此:
|
||||
|
||||
- SSE 重新收到 `connected` 后,若派车看板或矩阵当前已打开,应主动重拉一次当前页面数据。
|
||||
- 不要仅依赖实时事件维持页面正确性。
|
||||
- 仍按现有 SSE 生命周期要求保证全局只有一条连接,不得为看板和矩阵各自新建 `EventSource`。
|
||||
|
||||
## 不影响范围
|
||||
|
||||
- 不修改派车看板、矩阵派单现有查询接口及响应结构。
|
||||
- 不修改现有 `message`、`unread-count`、`im-chat`、`im-chat-read`、`presence`、`grab-pool-changed` 事件。
|
||||
- 不要求前端调用 `/internal/sse/fleet-dispatch/broadcast`;该路径仅供服务间 Feign 使用,管理后台不得直接访问。
|
||||
- 本次只覆盖用车需求展开后实时刷新。后续其他派单动作如扩展新的 `fleetEvent`,将另行补充契约。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 以车务管理员 A 打开 `/fleet/board`,车务管理员 B 或定制师新增用车需求后,A 的看板无需手动刷新即可出现新未派订单。
|
||||
- [ ] 以车务管理员 A 打开 `/fleet/matrix`,新增用车需求后未派订单池、顶部统计和矩阵数据自动更新。
|
||||
- [ ] 两个不同车务管理员同时在线并分别打开看板/矩阵,两边均收到同一变更并刷新。
|
||||
- [ ] 同一车务管理员两个浏览器标签同时在线,两个标签均能刷新且互不关闭 SSE。
|
||||
- [ ] 当前角色为 `SUPER_ADMIN` 且未切换车务角色时不接收本事件。
|
||||
- [ ] `SUPER_ADMIN` 切换为 `VEHICLE_MANAGER` 并重建 SSE 后可以接收。
|
||||
- [ ] 收到信令时保留看板和矩阵当前筛选条件,不跳回默认月份或清空搜索项。
|
||||
- [ ] 短时间连续提交用车需求不会产生请求风暴或旧响应覆盖新数据。
|
||||
- [ ] SSE 断线期间新增需求,连接恢复并收到 `connected` 后页面主动对账并显示最新数据。
|
||||
- [ ] Network 中只存在一条 `/ws/admin-msg/stream` 长连接,没有为车务页面新增独立 SSE。
|
||||
|
||||
## 后端验证记录
|
||||
|
||||
- fleet-service:`AssignmentServiceTest` 239 项通过。
|
||||
- fleet-service:车务派单 AFTER_COMMIT 通知测试 2 项通过。
|
||||
- user-service:`AdminSseServiceTest` 37 项通过,覆盖两个车务同时接收、非车务角色隔离。
|
||||
- user-service:车务广播与内部接口测试 3 项通过。
|
||||
- `hl-fleet-service`、`hl-user-service` 模块级 `mvn -DskipTests package` 均通过。
|
||||
|
||||
## 发布说明
|
||||
|
||||
- 本文是前端接入与联调通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
- 前端完成后应在 `mmg/hl-ui` 走自身 Issue、分支、PR、测试和发布流程。
|
||||
- 联调材料不得包含 access token、带 token 的完整 SSE URL、Cookie 或真实管理员身份信息。
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
ticket: "5132"
|
||||
title: "车务派单详情补全产品、行程节点、出行人与大交通"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T10:35:00+08:00"
|
||||
---
|
||||
|
||||
# 【修改接口·前端待处理·管理后台】车务派单详情补全产品、行程节点与出行人
|
||||
|
||||
> **服务**: hl-order-service-v3 + hl-fleet-service
|
||||
> **日期**: 2026-07-22
|
||||
> **工单**: #5132
|
||||
> **影响范围**: 管理后台车务管理 / 派车看板 / 派单弹窗 Step1「订单详情」
|
||||
|
||||
---
|
||||
|
||||
## 关键变化
|
||||
|
||||
派单弹窗 Step1 不能再只展示人数、日期和每日一句简介。`GET /admin/fleet/board/orders/{orderId}` 现一次返回:
|
||||
|
||||
- `productName`:订单产品名(原字段,前端本次必须展示)。
|
||||
- `tags[]`:订单在 `order_tag` 中真实挂载的标签名称与颜色;无标签返回 `[]`。
|
||||
- `itinerary.days[].nodes[]`:每日真实行程节点,含开始时间、时段、时长、名称和简介。
|
||||
- `travelers[]`:出行人脱敏基本信息,不含生日和任何明文字段。
|
||||
- `transport`:抵达、返程及分批大交通信息(原字段,前端本次必须完整展示时间和班次,不能只显示站点)。
|
||||
|
||||
行程数据仍以订单当前 `order_itinerary_day` 和 `order_itinerary_node` 为权威源,禁止从产品模板反推。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders/{orderId}
|
||||
```
|
||||
|
||||
响应 VO:`BoardOrderDetailVO`
|
||||
|
||||
### 新增字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `tags` | Array | 真实订单标签;无标签返回 `[]` |
|
||||
| `tags[].tagId` | String | 标签雪花 ID,必须按字符串处理 |
|
||||
| `tags[].name` | String/null | 标签名称 |
|
||||
| `tags[].color` | String/null | 标签颜色,如 `#52C41A` |
|
||||
| `itinerary.days[].nodes` | Array | 当日节点,按 `sortOrder` 升序;无节点返回 `[]` |
|
||||
| `itinerary.days[].nodes[].nodeId` | String | 节点雪花 ID,必须按字符串处理 |
|
||||
| `itinerary.days[].nodes[].nodeType` | String/null | 节点类型,如 `SCENIC`、`RESTAURANT`、`ACTIVITY`、`SERVICE`、`CUSTOM` |
|
||||
| `itinerary.days[].nodes[].nodeName` | String/null | 节点展示名;节点名为空时后端回退资源名 |
|
||||
| `itinerary.days[].nodes[].startTime` | String/null | 开始时间,格式 `HH:mm` |
|
||||
| `itinerary.days[].nodes[].timePeriod` | String/null | 时段,如上午、下午、全天 |
|
||||
| `itinerary.days[].nodes[].durationMinutes` | Number/null | 时长,单位分钟 |
|
||||
| `itinerary.days[].nodes[].description` | String/null | 节点简介 |
|
||||
| `itinerary.days[].nodes[].sortOrder` | Number/null | 同天排序 |
|
||||
| `travelers` | Array | 出行人脱敏基本信息;无出行人或下游降级时返回 `[]` |
|
||||
| `travelers[].travelerId` | String | 出行人雪花 ID,必须按字符串处理 |
|
||||
| `travelers[].travelerType` | String/null | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |
|
||||
| `travelers[].travelerTypeName` | String/null | 人员类型中文名,如“成人”“儿童” |
|
||||
| `travelers[].nameMasked` | String/null | 脱敏姓名 |
|
||||
| `travelers[].gender` | String/null | 性别字典值 |
|
||||
| `travelers[].genderName` | String/null | 性别中文名 |
|
||||
| `travelers[].ageAtDeparture` | Number/null | 按订单出发日计算的周岁 |
|
||||
| `travelers[].idType` | String/null | 证件类型字典值 |
|
||||
| `travelers[].idTypeName` | String/null | 证件类型中文名 |
|
||||
| `travelers[].idNoMasked` | String/null | 脱敏证件号 |
|
||||
| `travelers[].phoneMasked` | String/null | 脱敏手机号 |
|
||||
| `travelers[].nationality` | String/null | 国籍 |
|
||||
| `travelers[].race` | String/null | 民族 |
|
||||
| `travelers[].emergencyContactMasked` | String/null | 脱敏紧急联系人姓名 |
|
||||
| `travelers[].emergencyPhoneMasked` | String/null | 脱敏紧急联系人电话 |
|
||||
| `travelers[].roomGroupNo` | Number/null | 同住分组号 |
|
||||
| `travelers[].transportPlanIds` | String[] | 关联大交通批次 ID,必须按字符串处理 |
|
||||
| `travelers[].profileStatus` | String/null | 资料状态:`PENDING` / `COMPLETED` |
|
||||
| `travelers[].profileStatusName` | String/null | 资料状态中文名 |
|
||||
|
||||
`productName` 是已有字段,结构不变;本次页面必须消费,不再只保存在 `normalizeBoardOrder().product` 而不展示。
|
||||
|
||||
### 已有但本次必须完整展示的大交通字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `transport.arrive` | Object/null | 抵达接团段 |
|
||||
| `transport.arrive.transportNo` | String/null | 抵达航班号/车次号 |
|
||||
| `transport.arrive.time` | String/null | 抵达时间,ISO `LocalDateTime` |
|
||||
| `transport.arrive.station` | String/null | 抵达机场/车站 |
|
||||
| `transport.arrive.remark` | String/null | 抵达备注 |
|
||||
| `transport.depart` | Object/null | 返程送站段 |
|
||||
| `transport.depart.transportNo` | String/null | 返程航班号/车次号 |
|
||||
| `transport.depart.time` | String/null | 返程时间,ISO `LocalDateTime` |
|
||||
| `transport.depart.station` | String/null | 返程机场/车站 |
|
||||
| `transport.depart.remark` | String/null | 返程备注 |
|
||||
| `transport.batches` | Array | 分批接送列表 |
|
||||
| `transport.batches[].travelerNames` | String/null | 本批出行人姓名摘要 |
|
||||
| `transport.batches[].transportNo` | String/null | 本批航班号/车次号 |
|
||||
| `transport.batches[].time` | String/null | 本批抵达/返程时间 |
|
||||
| `transport.batches[].station` | String/null | 本批机场/车站 |
|
||||
| `transport.transferTimeHint` | String/null | 无任何大交通时间时的后端提示,当前为“暂无接送机时间” |
|
||||
| `transport.pickupRequired` | Boolean/null | 是否需要平台派车接送 |
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderNo": "HL20260721171011648",
|
||||
"productName": "草原亲子三日游",
|
||||
"tags": [
|
||||
{
|
||||
"tagId": "9001",
|
||||
"name": "亲子家庭",
|
||||
"color": "#52C41A"
|
||||
}
|
||||
],
|
||||
"itinerary": {
|
||||
"theme": "草原亲子三日游",
|
||||
"route": null,
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"date": "2026-07-29",
|
||||
"title": "接机",
|
||||
"detail": "抵达后入住酒店",
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "2001",
|
||||
"nodeType": "SERVICE",
|
||||
"nodeName": "海拉尔机场接机",
|
||||
"startTime": "10:30",
|
||||
"timePeriod": "上午",
|
||||
"durationMinutes": 60,
|
||||
"description": "司机举牌接机",
|
||||
"sortOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"travelers": [
|
||||
{
|
||||
"travelerId": "3001",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人",
|
||||
"nameMasked": "孔**",
|
||||
"gender": "2",
|
||||
"genderName": "女",
|
||||
"ageAtDeparture": 35,
|
||||
"idType": "ID_CARD",
|
||||
"idTypeName": "身份证",
|
||||
"idNoMasked": "150***********1234",
|
||||
"phoneMasked": "138****1234",
|
||||
"nationality": "中国",
|
||||
"race": "蒙古族",
|
||||
"emergencyContactMasked": "王*",
|
||||
"emergencyPhoneMasked": "139****5678",
|
||||
"roomGroupNo": 1,
|
||||
"transportPlanIds": ["4001"],
|
||||
"profileStatus": "COMPLETED",
|
||||
"profileStatusName": "已完善"
|
||||
}
|
||||
],
|
||||
"transport": {
|
||||
"transferTimeHint": null,
|
||||
"arrive": {
|
||||
"transportNo": "CA1234",
|
||||
"time": "2026-07-29T10:30:00",
|
||||
"station": "海拉尔东山国际机场",
|
||||
"remark": "T2 出口举牌接机"
|
||||
},
|
||||
"depart": {
|
||||
"transportNo": "CA5678",
|
||||
"time": "2026-07-31T17:20:00",
|
||||
"station": "海拉尔东山国际机场",
|
||||
"remark": "提前 2 小时送达"
|
||||
},
|
||||
"batches": [],
|
||||
"pickupRequired": true
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端页面调整要求
|
||||
|
||||
目标文件:`src/views/fleet/board/components/Step1OrderDetail.vue`。
|
||||
|
||||
1. 顶部订单摘要展示产品名,读取 `order.productName || order.product`;产品名为空才显示 `—`。
|
||||
- 当前 `Step1OrderDetail.vue` 中的 `order.bookingType || '企业包车'` 是硬编码占位,不是订单标签,必须删除。
|
||||
- 该位置改为遍历详情响应 `tags[]`,使用 `name` 作为文案、`color` 作为颜色;`tags=[]` 时不显示标签,也不回退“企业包车”。
|
||||
2. 在顶部订单摘要下增加“大交通”信息卡,抵达与返程分栏展示 `transportNo + time + station + remark`:
|
||||
- 时间使用完整月日和时分,不只展示日期。
|
||||
- `arrive`、`depart` 独立判空,只有一段时仍正常展示该段。
|
||||
- `batches[]` 非空时增加“分批接送”,展示本批出行人、班次、时间和站点。
|
||||
- 无任何时间时展示 `transport.transferTimeHint`,不得伪造航班或时间。
|
||||
- 当前顶部“接送”统计可保留站点摘要,但不能替代大交通详情卡。
|
||||
3. 左侧“每日安排”保留日标题和 `detail`,并在每一天下面渲染 `nodes[]`:
|
||||
- 时间优先显示 `startTime`,为空时显示 `timePeriod`,两者都有可组合展示。
|
||||
- `startTime` 和 `timePeriod` 都为空时显示“时间待定”,不得根据节点顺序或描述猜测具体时间。
|
||||
- 主文案显示 `nodeName`。
|
||||
- `durationMinutes` 有值时显示易读时长。
|
||||
- `description` 有值且与日简介不重复时显示节点简介。
|
||||
4. 右侧新增“出行人信息”区,默认展示脱敏姓名、人员类型、性别、年龄、国籍/民族、脱敏手机号和资料状态;证件、同住分组、关联大交通批次及紧急联系人可在行内展开或次要信息区展示。
|
||||
- 年龄文案使用自然表达“年龄 29 岁”,不要显示成“出发时 29岁”。
|
||||
- `ageAtDeparture` 的业务口径仍是按订单出发日计算;如需说明,将“按出发日计算”放在字段提示或帮助文案中,不与年龄值拼成标签。
|
||||
5. 禁止为了展示此页面调用明文接口 `POST /admin/fleet/board/orders/{orderId}/travelers/plain`。Step1 只使用详情响应中的脱敏 `travelers[]`。
|
||||
6. 空态明确:无节点显示“暂无行程节点”,无出行人显示“暂未填写出行人信息”;不得生成模拟节点或模拟出行人。
|
||||
7. 雪花 ID 禁止 `Number()` / `parseInt()`,统一按字符串处理。
|
||||
|
||||
推荐布局:顶部摘要下放横向“大交通”卡;左栏继续承载逐日节点时间线;右栏顺序为“出行人信息 → 客人留言 → 特殊要求 → 操作记录”。
|
||||
|
||||
---
|
||||
|
||||
## 兼容与降级
|
||||
|
||||
- 仅新增响应字段,不修改请求参数,不影响旧调用方。
|
||||
- 历史订单无订单标签时 `tags=[]`,禁止使用产品类型、预订类型或固定文案冒充订单标签。
|
||||
- 历史行程没有节点时 `nodes=[]`,每日标题和简介仍照常返回。
|
||||
- order-v3 聚合上下文失败并回退 fleet 本地快照时,`relatedDetailReady=false`,`travelers=[]`,行程节点不可用;前端显示真实空态。
|
||||
- 原独立脱敏接口 `GET /admin/fleet/board/orders/{orderId}/travelers` 保留兼容,但此页面无需再发第二次请求。
|
||||
- 不返回 `birthday`、明文姓名、明文证件号、明文手机号或明文紧急联系人。
|
||||
|
||||
---
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 顶部可看到订单产品名。
|
||||
- [ ] 顶部只展示 `tags[]` 中的真实订单标签;无标签时不显示,“企业包车”硬编码已删除。
|
||||
- [ ] 大交通卡分别展示抵达/返程的班次、完整时间、站点和备注。
|
||||
- [ ] 有分批接送时展示每批出行人、班次、时间和站点;无大交通时间时展示真实空态。
|
||||
- [ ] 每日安排按节点顺序展示时间、节点名、时长和简介。
|
||||
- [ ] 节点无 `startTime` 时可回退显示 `timePeriod`,不会出现 `undefined`。
|
||||
- [ ] 右侧可看到全部出行人的脱敏基本信息。
|
||||
- [ ] 每位出行人以“年龄 N 岁”的自然文案展示年龄,并可查看人员类型、性别、证件、国籍/民族、同住分组、关联大交通及资料状态。
|
||||
- [ ] 无节点/无出行人时展示真实空态,不生成模拟数据。
|
||||
- [ ] 页面 Network 只需现有详情请求,不调用出行人明文接口。
|
||||
- [ ] 现有留言、特殊要求、步骤条和操作记录不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
- `ItineraryServiceTest`:覆盖节点名称、开始时间、时段、时长、简介和排序装配。
|
||||
- `OrderFleetProviderServiceTest`:覆盖节点随当前订单日期对齐且出行人脱敏进入聚合上下文。
|
||||
- `BoardOrderServiceTest`:覆盖 shared DTO 到管理端 VO 的节点和出行人映射。
|
||||
- `BoardControllerTest`:覆盖 `productName`、节点时间、String ID 与脱敏出行人的 JSON 契约。
|
||||
- `OrderFleetProviderServiceTest`、`BoardOrderServiceTest` 与 `BoardControllerTest`:覆盖 `order_tag` 名称/颜色进入详情响应,标签 ID 按字符串序列化。
|
||||
- 测试环境网关实测订单 `HL20260721171011648`:HTTP 200,返回 3 个行程日、14 个真实节点和 5 位出行人;5 位出行人均返回 `ageAtDeparture`,且未出现生日、明文姓名、明文证件号或明文手机号。
|
||||
- 同一实测订单返回 2 个真实订单标签“自动化测试”“房务需求”,均包含颜色,`tagId` 均为字符串;响应不含 `bookingType`,前端无需也不得使用“企业包车”等硬编码兜底。
|
||||
- 同一实测订单已返回抵达大交通的班次、抵达时间和站点;该订单无返程段、无分批接送,接口按真实数据返回空值或空数组。
|
||||
- 该订单 14 个节点的 `startTime` 与 `timePeriod` 在订单行程源数据中均为空,接口如实返回 `null`;前端须展示“时间待定”,若要显示具体钟点需先补录订单行程节点时间。
|
||||
- 网关证据已由 `hl task` 登记,SHA-256:`8ff09cc804fb8d72fde6df3f338125b725c857d2c098b59f6b222f460da844a3`。
|
||||
|
||||
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
ticket: "5139"
|
||||
title: "车务派单候选筛选、分页与任意车辆选择"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T13:25:00+08:00"
|
||||
---
|
||||
|
||||
# 【修改接口·前端待处理·管理后台】车务派单候选筛选、分页与任意车辆选择
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 仓库地址:<https://git.1814.love:8443/mmg/hl-ui.git>
|
||||
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||
- 小程序:无需处理
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **日期**: 2026-07-22
|
||||
> **工单**: #5139
|
||||
> **影响范围**: 订单派车弹窗的车辆候选、司机候选与最终派单校验
|
||||
|
||||
## 关键变化
|
||||
|
||||
`POST /admin/fleet/assignments/candidates` 继续同时返回车辆和司机,但两侧必须按各自分页参数渲染。后端新增动态车队/车型筛选、车型需求匹配、协议参考价、车辆常驻司机、司机历史统计,以及“先选司机时回显常驻车”的契约。
|
||||
|
||||
车型或座位不符合订单需求时,车辆仍允许选择;只有真实档期冲突或资源不可用才禁止。车辆选中态不是强制单选,前端再次点击已选车辆时可把 `selectedVehicleId` 清为 `null` 后重新查询。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| POST | `/admin/fleet/assignments/candidates` | 车辆、司机独立筛选和分页;任一侧可先选 |
|
||||
| POST | `/admin/fleet/assignments/precheck` | 车型/座位不匹配只返回 warning |
|
||||
| POST | `/admin/fleet/assignments` | `strictSeats` 历史字段不再阻断任意车辆派单 |
|
||||
|
||||
## 候选查询入参
|
||||
|
||||
在原请求基础上新增或明确以下字段:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `selectedVehicleId` | String/null | 否 | 当前已选车辆;传 `null` 表示取消车辆选择 |
|
||||
| `selectedDriverId` | String/null | 否 | 当前已选司机;可在未选车辆时先传 |
|
||||
| `fleetTeamId` | String/null | 否 | 独立车队主数据 ID;空为全部 |
|
||||
| `vehicleTypeId` | String/null | 否 | 车型大类 ID;空为全部 |
|
||||
| `requiredVehicleType` | String/null | 否 | 订单需求车型大类 key,只影响匹配标记,不限制选择 |
|
||||
| `vehiclePage` / `vehiclePageSize` | Integer | 是 | 车辆独立分页,页大小 1~100 |
|
||||
| `driverPage` / `driverPageSize` | Integer | 是 | 司机独立分页,页大小 1~100 |
|
||||
| `driverAvailability` | String | 否 | `ALL` / `AVAILABLE`,接口默认 `ALL`;管理后台按原型首屏显式传 `AVAILABLE` |
|
||||
| `driverSort` | String | 否 | `SMART` / `RATING` / `YEARS` / `RECENT_ORDER`,默认 `SMART` |
|
||||
|
||||
雪花 ID 一律按字符串保存和提交,禁止 `Number()`、`parseInt()`。
|
||||
|
||||
取消车辆但保留司机的请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2080000000000000001",
|
||||
"requirementId": "2080000000000000101",
|
||||
"fleetItemIndex": 0,
|
||||
"startDate": "2026-07-29",
|
||||
"endDate": "2026-07-31",
|
||||
"headcount": 5,
|
||||
"requiredVehicleType": "suv",
|
||||
"selectedVehicleId": null,
|
||||
"selectedDriverId": "2080000000000000201",
|
||||
"vehiclePage": 1,
|
||||
"vehiclePageSize": 10,
|
||||
"driverPage": 1,
|
||||
"driverPageSize": 10
|
||||
}
|
||||
```
|
||||
|
||||
## 响应结构
|
||||
|
||||
### 独立分页
|
||||
|
||||
`data.vehicles` 和 `data.drivers` 均返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [],
|
||||
"list": [],
|
||||
"total": 106,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
```
|
||||
|
||||
`records` 与 `list` 内容相同,前端统一使用 `records`。切换车辆筛选只重置 `vehiclePage`,切换司机筛选只重置 `driverPage`,不要一次性把所有候选渲染成长列表。
|
||||
|
||||
### 动态筛选项
|
||||
|
||||
- `fleetTeamFacets[]`: `fleetTeamId/fleetTeamName/fleetType/count`。
|
||||
- `vehicleTypeFacets[]`: `vehicleTypeId/vehicleTypeKey/vehicleTypeName/count`。
|
||||
- 数量按当前车辆关键词统计;“全部”数量可按 facet 求和或使用 `vehicles.total`。
|
||||
- 不再写死“自有车队/合作车队 A/合作车队 B”或固定车型数组。
|
||||
|
||||
### 车辆候选新增字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `vehicleTypeId` | String/null | 车型大类 ID |
|
||||
| `vehicleTypeKey` / `vehicleTypeName` | String/null | 车型大类编码和名称 |
|
||||
| `fleetTeamId/fleetTeamName/fleetType` | String/null | 动态车队信息 |
|
||||
| `primaryDriverId/primaryDriverName` | String/null | 常驻司机;为空显示“无常驻” |
|
||||
| `protocolPrice` | Decimal/null | 用车开始日价格日历协议参考价;为空显示“未设价” |
|
||||
| `passengerCapacity` | Integer | 载客数,已扣除司机座 |
|
||||
| `seatsEnough` | Boolean | 座位是否满足人数,仅用于提示 |
|
||||
| `requirementMatched` | Boolean | 车型和座位是否均符合需求;`false` 只做醒目标记 |
|
||||
| `available` | Boolean | 是否可选的权威值;真实档期冲突时为 `false` |
|
||||
| `selected` | Boolean | 是否为当前已选车辆 |
|
||||
|
||||
前端禁用判断只使用 `available === false`。禁止用 `requirementMatched === false`、`seatsEnough === false` 或车型不一致禁用车辆;这些情况应显示“需求不匹配/座位不足”提示,但允许车务选中。
|
||||
|
||||
需求不匹配必须使用车辆卡片内的显式标签,不能再以黄色外框作为主要提示:
|
||||
|
||||
- `requirementMatched === false`:在车辆名称/状态附近显示橙色 `需求不匹配` 标签。
|
||||
- `seatsEnough === false`:额外显示红色或橙红色 `座位不足` 标签。
|
||||
- 移除需求不匹配专用黄色外框;边框只保留选中态、档期冲突等已有交互语义,避免颜色含义不明。
|
||||
- 标签只负责提醒,不改变 `available`、点击选择或最终派单规则。
|
||||
|
||||
### 先选司机与取消车辆
|
||||
|
||||
- 仅传 `selectedDriverId` 时,`selectedDriverResidentVehicle` 返回该司机常驻车的完整车辆候选;司机无常驻车时为 `null`。
|
||||
- 常驻车即使不在当前车队、车型筛选页内,也会通过该独立字段返回,前端可置顶或单独提示。
|
||||
- 再次点击已选车辆时,前端清空本地车辆 ID,并以 `selectedVehicleId: null` 查询;保留 `selectedDriverId` 时常驻车提示仍存在。
|
||||
- 同时选定跨常驻车组合时,沿用 `selectedRelation.requiresConfirmation` 和候选项 `requiresCrossResidentConfirmation` 的确认流程。
|
||||
|
||||
### 司机候选统计
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `completedOrderCount` | Integer | 司机跨赛季历史完单量,按派车组去重 |
|
||||
| `lastOrderAt` | Date/null | 最近完单日期 |
|
||||
| `rating` | Decimal/null | 真实平均评分;无评价时为 `null` |
|
||||
| `hasRating` | Boolean | 是否存在真实评分 |
|
||||
| `residentVehicleId/residentVehiclePlate` | String/null | 司机常驻车辆 |
|
||||
|
||||
`hasRating=false` 时显示“暂无评价”,不要展示星标和 `0.0/5.0`;不得再用固定 `5.0` 兜底。单量为司机历史累计,不按赛季清零。
|
||||
|
||||
### 原型一致性:司机筛选控件
|
||||
|
||||
司机筛选必须按原型平铺展示,不能用两个下拉框折叠选项。平铺按钮让车务一眼看到当前范围和全部排序方式,并可单击切换:
|
||||
|
||||
- 范围:`仅空闲`(`AVAILABLE`,首屏默认选中)、`全部`(`ALL`)。
|
||||
- 排序:`智能推荐`(`SMART`,首屏默认选中)、`评分`(`RATING`)、`驾龄`(`YEARS`)、`最近接单`(`RECENT_ORDER`)。
|
||||
- 切换范围或排序时只把 `driverPage` 重置为 1,不重置车辆筛选、车辆页码或已选车辆。
|
||||
- “全部司机/智能排序”两个 `NSelect` 不视为原型等价实现;验收以按钮全部可见、选中态明确为准。
|
||||
|
||||
## 最终派单规则
|
||||
|
||||
- 车型或座位不匹配:候选项仍可选,预检返回 warning,最终派单不阻断。
|
||||
- 档期冲突、车辆/司机不可用、黑名单或跨常驻未确认:仍按现有业务守卫阻断。
|
||||
- `strictSeats` 为历史兼容字段,可不再提交;即使提交 `true` 也不会把座位不足变成阻断。
|
||||
|
||||
## 前端处理清单
|
||||
|
||||
- [ ] 车辆和司机列表分别接 `records/total/page/pageSize` 并增加独立分页控件。
|
||||
- [ ] 车队和车型筛选使用 `fleetTeamFacets/vehicleTypeFacets` 动态渲染及计数。
|
||||
- [ ] 车辆行展示车型、常驻司机、协议参考价;需求不匹配改用卡片内显式标签并移除黄色外框,座位不足追加独立标签,均不禁选。
|
||||
- [ ] 支持再次点击已选车辆取消选择,并传 `selectedVehicleId: null`。
|
||||
- [ ] 支持先选司机,并展示/置顶 `selectedDriverResidentVehicle`。
|
||||
- [ ] 司机范围与排序按原型平铺为 2+4 个按钮,默认“仅空闲 + 智能推荐”,不得折叠成两个下拉框。
|
||||
- [ ] 司机无评价显示“暂无评价”,不伪造 `5.0` 或 `0.0`;完成单量读取 `completedOrderCount`。
|
||||
- [ ] 雪花 ID 全程按字符串处理。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- PR [wx/HL#5140](https://git.1814.love:8443/wx/HL/pulls/5140) 已合并到 `dev-v3`。
|
||||
- 派单候选、派单服务、司机统计、可靠投影、价格日历和迁移审计定向测试全部通过。
|
||||
- `spotless:check` 与 `mvn -pl hl-fleet-service -am verify` 通过。
|
||||
- 测试环境 `hl-fleet-service` 8087/8187 双实例滚动部署健康。
|
||||
- 测试网关实测 HTTP/业务码 200:车辆和司机独立分页一致,返回 3 个动态车队、4 个车型大类;协议价非空,车型不匹配车辆仍可选;车辆可清空,先选司机可返回常驻车。
|
||||
- 测试库只读核验:`V20260722.002` 已成功执行,候选 `completedOrderCount/lastOrderAt` 与 `fleet_driver` 投影一致,无评分司机返回 `rating=null`。
|
||||
|
||||
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
ticket: "5141"
|
||||
title: "车务派单司机保险类型与行程保障状态"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T14:07:00+08:00"
|
||||
---
|
||||
|
||||
# 【修改接口·前端待处理·管理后台】车务派单司机保险类型与行程保障状态
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 仓库地址:<https://git.1814.love:8443/mmg/hl-ui.git>
|
||||
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||
- 小程序:无需处理
|
||||
|
||||
## 变更接口
|
||||
|
||||
`POST /admin/fleet/assignments/candidates` 的 `data.drivers.records[]` 新增司机保险字段。数据直接来自司机档案,并按本次请求的 `startDate/endDate` 判断全年保险是否完整覆盖行程。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `insuranceType` | String | `annual` 全年保险、`perTrip` 按行程投保、`none` 无保险 |
|
||||
| `insuranceTypeLabel` | String | `全年保险`、`按行程投保`、`无保险` |
|
||||
| `insuranceAnnualStart` | Date/null | 全年保险起始日;非 `annual` 为空 |
|
||||
| `insuranceAnnualEnd` | Date/null | 全年保险到期日;非 `annual` 为空 |
|
||||
| `insuranceCoverageStatus` | String | 本次行程保障状态,枚举见下表 |
|
||||
| `insuranceCoverageMessage` | String | 后端生成的中文提示,可直接展示 |
|
||||
| `insuranceCovered` | Boolean | 仅全年保险完整覆盖本次服务日期时为 `true` |
|
||||
|
||||
## 保障状态
|
||||
|
||||
| `insuranceCoverageStatus` | `insuranceCoverageMessage` | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `ANNUAL_COVERED` | 全年保险已覆盖 | 年保起止日完整覆盖本次行程 |
|
||||
| `ANNUAL_NOT_COVERED` | 全年保险不覆盖本行程 | 年保缺日期、未生效、已过期或仅覆盖部分行程 |
|
||||
| `PER_TRIP_REQUIRED` | 待按行程投保 | 司机配置为按行程投保,候选阶段尚不代表已经出单 |
|
||||
| `UNINSURED` | 无保险 | 司机档案明确为无保险 |
|
||||
| `UNKNOWN` | 保险状态未知 | 存量异常值兜底,不能当作已保障 |
|
||||
|
||||
## 前端展示规则
|
||||
|
||||
- 在司机卡片姓名或驾龄附近展示保险徽标,文案优先使用 `insuranceCoverageMessage`。
|
||||
- `ANNUAL_COVERED` 可用绿色;`PER_TRIP_REQUIRED` 用橙色;`ANNUAL_NOT_COVERED/UNINSURED/UNKNOWN` 用红色或醒目警示色。
|
||||
- 全年保险可在悬浮提示或次级文案展示 `insuranceAnnualStart ~ insuranceAnnualEnd`。
|
||||
- 保险状态只用于车务判断和提示,不影响司机候选的 `available`,不得因为未投保或待按行程投保禁用司机。
|
||||
- 不要只根据 `insuranceType=annual` 显示“已保障”,必须以 `insuranceCoverageStatus` 或 `insuranceCovered` 为准。
|
||||
|
||||
## 前端处理清单
|
||||
|
||||
- [ ] 司机候选卡片展示保险保障徽标。
|
||||
- [ ] 区分全年已覆盖、全年未覆盖、待按行程投保、无保险及未知状态。
|
||||
- [ ] 年保可查看保障起止日,且不把过期或部分覆盖年保展示为已保障。
|
||||
- [ ] 保险状态不改变司机可选性,候选禁用仍只依据 `available === false`。
|
||||
|
||||
## 编辑司机:无保单时直接线上投保
|
||||
|
||||
司机编辑抽屉选择“全年保险”后,如果“关联保游网保单”没有可选数据,不应只展示空下拉。需要在当前抽屉提供“立即投保”入口,复用保险订单页“投保下单 → 司机”的线上真实投保逻辑。
|
||||
|
||||
目标文件:
|
||||
|
||||
- `src/views/fleet/drivers/components/DriverEditModal.vue`
|
||||
- 可复用 `src/views/insurance/orders/index.vue` 中的司机投保表单和 `src/api/fleet/drivers.js` 的 `purchaseDriverInsurance`。
|
||||
|
||||
交互要求:
|
||||
|
||||
1. 无可关联保单时显示“暂无可关联保单”,并提供“立即投保”按钮。
|
||||
2. 点击后填写保险计划、保障开始、保障结束和可选备注;表单行为与保险订单页的司机投保一致。
|
||||
3. 用户点击“确认投保”后才发起真实线上投保;仅切换到“全年保险”不得自动出单。
|
||||
4. 投保请求必须传 `bindAnnual: true`。受理成功后,后端会自动把新保单绑定为司机档案的全年保险。
|
||||
5. 成功后重新加载司机保单列表和司机详情,回显新 `insuranceOrderId`、保单状态及保障起止;`INSURING` 时显示“出单中”,不能要求用户重复投保。
|
||||
6. 保留“手工录入线下保单”作为独立兜底路径,文案和操作不得与线上投保混用。
|
||||
|
||||
调用示例:
|
||||
|
||||
```http
|
||||
POST /admin/fleet/drivers/{driverId}/insurance/purchase
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"planId": "2080000000000000001",
|
||||
"coverageStartDate": "2026-07-23",
|
||||
"coverageEndDate": "2027-07-22",
|
||||
"bindAnnual": true,
|
||||
"remark": "司机全年保险"
|
||||
}
|
||||
```
|
||||
|
||||
`driverId`、`planId` 和响应中的 `insuranceOrderId` 均为雪花 ID,前端必须按字符串透传。保险计划继续使用 `GET /admin/fleet/drivers/insurance/plan-options`。
|
||||
|
||||
异常处理沿用保险订单页:全局展示后端错误文案;若返回 `600206`,表示可能已经出单但档案绑定失败,必须关闭投保弹窗并刷新保单列表,提示用户勿重复投保。
|
||||
|
||||
追加验收项:
|
||||
|
||||
- [ ] 编辑司机选择全年保险且无已有保单时,可在当前抽屉发起线上真实投保。
|
||||
- [ ] 请求携带 `bindAnnual: true`,投保受理后司机档案自动回显全年保险,无需先保存再关联。
|
||||
- [ ] 出单中、已承保和 `600206` 场景均不会诱导用户重复投保。
|
||||
- [ ] 线上投保与手工录入线下保单入口、文案和数据来源清晰分离。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 后端 PR [wx/HL#5143](https://git.1814.love:8443/wx/HL/pulls/5143) 已合并到 `dev-v3`。
|
||||
- 派单候选与司机域定向测试共 148 项通过。
|
||||
- fleet `spotless:check` 与 `mvn -pl hl-fleet-service -am verify` 通过。
|
||||
- 测试网关真实返回 21 名司机,覆盖全年已覆盖、全年未覆盖、待按行程投保和无保险四类结果;响应与测试库司机保险档案逐条一致,19 名警示状态司机仍可选择。
|
||||
|
||||
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
ticket: "5145"
|
||||
title: "选车后常驻司机默认配对"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T15:30:00+08:00"
|
||||
---
|
||||
|
||||
# 【修改接口·前端待处理·管理后台】选车后常驻司机默认配对
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 仓库地址:<https://git.1814.love:8443/mmg/hl-ui.git>
|
||||
- 目标分支:`v2.1`
|
||||
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||
- 小程序:无需处理
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
>
|
||||
> **后端 PR**: [wx/HL#5148](https://git.1814.love:8443/wx/HL/pulls/5148)
|
||||
>
|
||||
> **工单**: [wx/HL#5145](https://git.1814.love:8443/wx/HL/issues/5145)
|
||||
>
|
||||
> **日期**: 2026-07-22
|
||||
>
|
||||
> **影响范围**: 管理后台订单派车弹窗的车辆/司机联动选择
|
||||
|
||||
## 关键变化
|
||||
|
||||
`POST /admin/fleet/assignments/candidates` 的响应新增 `data.selectedVehicleResidentDriver`。前端选中车辆后,可直接取得该车常驻司机的完整候选快照并按档期决定是否自动选中,不再依赖当前司机页中能否找到该司机。
|
||||
|
||||
这个独立快照不受司机关键词、司机分页、`driverAvailability=AVAILABLE` 或排序条件影响;没有有效常驻司机时为 `null`。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 变更类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| POST | `/admin/fleet/assignments/candidates` | 响应新增字段 | 返回已选车辆的常驻司机候选快照 |
|
||||
|
||||
请求时继续传当前选中车辆:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2080000000000000001",
|
||||
"requirementId": "2080000000000000101",
|
||||
"fleetItemIndex": 0,
|
||||
"startDate": "2026-07-29",
|
||||
"endDate": "2026-07-31",
|
||||
"selectedVehicleId": "2080000000000000201",
|
||||
"selectedDriverId": null,
|
||||
"driverKeyword": "不会命中常驻司机的关键词",
|
||||
"driverAvailability": "AVAILABLE",
|
||||
"driverPage": 3,
|
||||
"driverPageSize": 10
|
||||
}
|
||||
```
|
||||
|
||||
响应新增字段示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"selectedVehicleResidentDriver": {
|
||||
"driverId": "2080000000000000301",
|
||||
"name": "常驻司机",
|
||||
"maskedPhone": "135****5001",
|
||||
"available": true,
|
||||
"availabilityReasonCode": "AVAILABLE",
|
||||
"availabilityReasonMessage": "所选服务日期内可用",
|
||||
"availabilityWindows": [
|
||||
{ "startDate": "2026-07-29", "endDate": "2026-07-31" }
|
||||
],
|
||||
"insuranceCoverageStatus": "ANNUAL_COVERED",
|
||||
"insuranceCoverageMessage": "全年保险已覆盖",
|
||||
"insuranceCovered": true,
|
||||
"residentVehicleId": "2080000000000000201",
|
||||
"residentVehiclePlate": "蒙A-示例",
|
||||
"completedOrderCount": 12,
|
||||
"rating": null,
|
||||
"hasRating": false,
|
||||
"conflicts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
冲突时该字段仍返回,不会被 `driverAvailability=AVAILABLE` 过滤:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"selectedVehicleResidentDriver": {
|
||||
"driverId": "2080000000000000301",
|
||||
"available": false,
|
||||
"availabilityReasonCode": "ASSIGNMENT_CONFLICT",
|
||||
"availabilityReasonMessage": "所选服务日期内存在派单冲突",
|
||||
"availabilityWindows": [],
|
||||
"conflicts": [
|
||||
{
|
||||
"startDate": "2026-07-30",
|
||||
"endDate": "2026-07-31",
|
||||
"blocking": true,
|
||||
"reasonCode": "ASSIGNMENT_CONFLICT"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
雪花 ID 继续按字符串处理,禁止 `Number()` 或 `parseInt()`。
|
||||
|
||||
## 前端交互口径
|
||||
|
||||
### 选车后默认常驻司机
|
||||
|
||||
- 用户选中车辆后重新请求候选接口,并读取 `selectedVehicleResidentDriver`。
|
||||
- 字段非空且 `available === true`:默认选中该司机,并记录本次司机选择来源为“车辆常驻司机自动选中”。
|
||||
- 字段非空且 `available === false`:不要自动选中;在车辆/司机联动区域显示醒目的 `常驻司机档期冲突` 标签,可补充 `availabilityReasonMessage`。
|
||||
- 字段为 `null`:该车辆没有有效常驻司机,不自动选择司机。
|
||||
- 不要在 `drivers.records` 中二次查找常驻司机;它可能因关键词、分页或“仅空闲”条件不在当前列表。
|
||||
|
||||
### 更换自动选中的常驻司机
|
||||
|
||||
- 只有当前司机是本次选车后自动选中的常驻司机时,用户点击其他司机才弹二次确认。
|
||||
- 推荐文案:`该车辆已默认匹配常驻司机「{name}」,确认更换为「{newName}」吗?`
|
||||
- 点击取消:保留原常驻司机,不更新本地 `selectedDriverId`,也不要以新司机重新查询接口。
|
||||
- 点击确认:替换为新司机,再以新 `selectedDriverId` 查询候选接口。
|
||||
- 常驻司机因档期冲突未自动选中时,用户选择其他司机不需要这次二次确认。
|
||||
- 用户主动选择其他司机后的跨常驻关系,仍按已有 `selectedRelation.requiresConfirmation` 做最终派单确认;两种确认不可合并。
|
||||
|
||||
### 车辆取消与切换
|
||||
|
||||
- 再次点击已选车辆取消选择时,同时清除“自动常驻司机”来源标记;是否保留司机沿用当前页面既有取消车辆口径。
|
||||
- 切换到另一辆车后,以上规则按新响应重新执行;不得沿用上一辆车的常驻司机快照。
|
||||
|
||||
## 前端处理清单
|
||||
|
||||
- [ ] 接入 `selectedVehicleResidentDriver`,不依赖司机当前分页定位常驻司机。
|
||||
- [ ] 常驻司机档期可用时默认选中,并记录自动选择来源。
|
||||
- [ ] 常驻司机冲突时不自动选中,展示 `常驻司机档期冲突` 标签。
|
||||
- [ ] 更换自动选中的常驻司机时增加二次确认;取消不产生瞬时切换或接口重查。
|
||||
- [ ] 保留已有跨常驻最终派单确认,两种确认分别处理。
|
||||
- [ ] 雪花 ID 全程按字符串处理。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试覆盖:常驻司机在司机关键词/分页/仅空闲筛选之外仍返回;档期冲突仍返回;车辆无常驻司机返回 `null`。
|
||||
- `mvn -pl hl-fleet-service -am test` 通过。
|
||||
- `mvn -pl hl-fleet-service spotless:check` 通过。
|
||||
- `mvn -pl hl-fleet-service -am verify` 通过。
|
||||
- `hl-fleet-service` 已从 `dev-v3` 滚动部署测试环境,8087/8187 双实例健康。
|
||||
- 测试网关实测 HTTP/业务码 200:常驻司机快照在司机关键词不命中、司机页为空和 `AVAILABLE` 筛选下仍返回,司机 ID 与车辆 `primaryDriverId` 一致;无常驻司机车辆返回 `null`。
|
||||
|
||||
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 草原指南管理:视频素材仅查询当前文件夹
|
||||
|
||||
> **管理端 PR**: [wx/hl-ui#9](https://git.1814.love:8443/wx/hl-ui/pulls/9)
|
||||
> **日期**: 2026-07-15
|
||||
> **影响范围**: 管理后台草原指南新增/编辑页的视频素材选择弹窗
|
||||
> **当前状态**: 已合入 `v2.1` 并部署测试环境
|
||||
|
||||
---
|
||||
|
||||
## 一、业务口径更正
|
||||
|
||||
视频素材选择器继续保留“草原指南”文件夹下拉,但素材列表改为严格按当前所选文件夹查询,不再自动包含子文件夹素材。
|
||||
|
||||
本记录更正 `15_fix_grassland_guide_video_folder_selector_visibility.md` 中关于递归查询的描述;文件夹树显示规则保持不变。
|
||||
|
||||
---
|
||||
|
||||
## 二、最终查询规则
|
||||
|
||||
- 选择“草原指南”根目录:只显示直接归属根目录的视频,不显示任意子文件夹视频。
|
||||
- 选择具体子文件夹:只显示直接归属该文件夹的视频,不显示更深层子文件夹视频。
|
||||
- 关键词搜索、分页和视频类型过滤均在当前文件夹范围内执行。
|
||||
- 文件夹下拉仍锁定在“草原指南”分类树内,不能切换到其他业务分类。
|
||||
|
||||
对应请求规则:
|
||||
|
||||
```http
|
||||
# 选择“草原指南”根目录
|
||||
GET /admin/material/list?categoryCode=grassland_guide&fileType=video
|
||||
|
||||
# 选择具体子文件夹
|
||||
GET /admin/material/list?categoryCode=grassland_guide&subCategoryId={文件夹ID}&fileType=video
|
||||
```
|
||||
|
||||
上述请求均不提交 `includeDescendants=true`。
|
||||
|
||||
---
|
||||
|
||||
## 三、接口与配置影响
|
||||
|
||||
- 不新增或修改 API 字段。
|
||||
- 后端 `includeDescendants` 可选能力保持不变,其他调用方仍可按需显式启用。
|
||||
- 不修改数据库、字典、菜单或 Nacos 配置。
|
||||
- 不影响封面图选择器、素材上传、小程序接口或草原指南保存逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 四、验证记录
|
||||
|
||||
- [x] 根目录请求不包含 `subCategoryId` 和 `includeDescendants`。
|
||||
- [x] 子文件夹请求包含对应 `subCategoryId`,不包含 `includeDescendants`。
|
||||
- [x] 文件夹下拉、根节点和子节点继续显示。
|
||||
- [x] 共享组件显式开启 `includeDescendants=true` 时仍正常传参。
|
||||
- [x] Vitest:1 个测试文件、4 项测试通过。
|
||||
- [x] 相关 ESLint 通过。
|
||||
- [x] Vite 生产构建通过。
|
||||
- [x] 独立代码复核无阻断项。
|
||||
- [x] 测试环境部署任务 `418ff501` 成功。
|
||||
- [x] 测试环境静态包 `VideoEditDrawer-qeoVDIga.js` 已确认保留文件夹下拉且不再传递 `include-descendants`。
|
||||
@@ -0,0 +1,57 @@
|
||||
# 草原指南管理:视频素材选择器显示文件夹树
|
||||
|
||||
> **管理端 PR**: [wx/hl-ui#8](https://git.1814.love:8443/wx/hl-ui/pulls/8)
|
||||
> **关联后端工单**: [wx/HL#4999](https://git.1814.love:8443/wx/HL/issues/4999)
|
||||
> **日期**: 2026-07-15
|
||||
> **影响范围**: 管理后台草原指南新增/编辑页的视频素材选择弹窗
|
||||
> **当前状态**: 已合入 `v2.1` 并部署测试环境
|
||||
|
||||
---
|
||||
|
||||
## 一、修复原因
|
||||
|
||||
封面图素材选择器能够显示分类树,但视频选择器锁定在 `grassland_guide` 后,错误地以“根分类必须存在子节点”作为下拉显示条件。分类接口暂时只返回根节点时,视频弹窗会完全隐藏文件夹下拉。
|
||||
|
||||
该问题只影响管理端显示,不涉及后端接口或数据。
|
||||
|
||||
---
|
||||
|
||||
## 二、最终交互
|
||||
|
||||
- 视频素材弹窗始终显示文件夹下拉,根节点名称为“草原指南”。
|
||||
- 有子文件夹时,以树形层级挂在“草原指南”根节点下,可选择任意层级子文件夹。
|
||||
- 选择“草原指南”根节点时,查询该顶级分类自身及全部后代素材。
|
||||
- 选择子文件夹时,查询该文件夹及其全部后代素材。
|
||||
- 视频选择器继续锁定在草原指南分类内,不能切换到其他业务顶级分类。
|
||||
- 普通素材选择器和封面图选择器行为不变。
|
||||
|
||||
对应请求规则:
|
||||
|
||||
```http
|
||||
# 选择“草原指南”根节点
|
||||
GET /admin/material/list?categoryCode=grassland_guide&fileType=video&includeDescendants=true
|
||||
|
||||
# 选择具体子文件夹
|
||||
GET /admin/material/list?categoryCode=grassland_guide&subCategoryId={文件夹ID}&fileType=video&includeDescendants=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、接口与配置影响
|
||||
|
||||
- 不新增或修改 API 字段。
|
||||
- 不修改数据库、字典、菜单或 Nacos 配置。
|
||||
- 不修改素材上传、小程序接口或草原指南保存逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 四、验证记录
|
||||
|
||||
- [x] Vitest:1 个测试文件、3 项测试通过。
|
||||
- [x] 覆盖根分类没有子文件夹时仍显示下拉。
|
||||
- [x] 覆盖选择子文件夹后继续提交 `subCategoryId`。
|
||||
- [x] 相关 ESLint 通过。
|
||||
- [x] Vite 生产构建通过。
|
||||
- [x] 独立代码复核无阻断项。
|
||||
- [x] 测试环境部署任务 `36ee2a2e` 成功。
|
||||
- [x] 测试环境静态包已确认包含新的根节点与子文件夹树逻辑。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 草原指南管理:今日前端文件夹选择改动已全部回退
|
||||
|
||||
> **日期**:2026-07-15
|
||||
> **影响范围**:`hl-ui` 草原指南视频素材选择器
|
||||
> **当前状态**:今天由 `wx` 合入的相关前端代码已全部撤销
|
||||
|
||||
---
|
||||
|
||||
## 一、回退结论
|
||||
|
||||
今天合入 `hl-ui` 的草原指南素材文件夹选择改动不再作为当前实现,相关 PR 已通过纯 Git revert 全部撤销:
|
||||
|
||||
- `v2.1`:原 PR #7、#8、#9 已由回退 PR [wx/hl-ui#12](https://git.1814.love:8443/wx/hl-ui/pulls/12) 撤销。
|
||||
- `master`:原 PR #10 已由回退 PR [wx/hl-ui#11](https://git.1814.love:8443/wx/hl-ui/pulls/11) 撤销。
|
||||
|
||||
回退后未新增任何替代前端实现。
|
||||
|
||||
## 二、当前代码与部署基线
|
||||
|
||||
| 分支 / 环境 | 当前版本 | 回退校验 |
|
||||
|---|---|---|
|
||||
| `v2.1` / 测试环境 | `30b3ee1f`,任务 `ca6a7df0` | 代码树与改动前 `797beda4` 完全一致 |
|
||||
| `master` / 正式环境 | `f97a7ccb`,deploy `271` | 代码树与改动前 `4d305ca4` 完全一致 |
|
||||
|
||||
测试与正式环境的 `VideoEditDrawer` 静态资源均已复核,不再包含本次新增的 `allow-sub-category-select` 或 `include-descendants` 配置。
|
||||
|
||||
## 三、对早期记录的更正
|
||||
|
||||
以下两份记录仅保留为历史过程,不代表当前 `hl-ui` 代码状态:
|
||||
|
||||
- `15_fix_grassland_guide_video_folder_selector_visibility.md`
|
||||
- `15_fix_grassland_guide_video_current_folder_scope.md`
|
||||
|
||||
前端不得再按上述两份记录继续实现或判断当前页面能力;如后续重新启动该需求,需要重新确认业务范围并另开前端任务。
|
||||
|
||||
## 四、后端接口边界
|
||||
|
||||
- `GET /admin/material/list` 的 `includeDescendants` 后端可选能力仍保留。
|
||||
- 默认不传或传 `false` 时只查当前分类节点;显式传 `true` 时查询所选节点及其后代。
|
||||
- 当前 `hl-ui` 回退后不消费本次新增的文件夹选择能力。
|
||||
- 本次前端回退不修改后端接口、响应结构、数据库、字典、菜单或 Nacos 配置。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 草原指南管理端免登录详情接口
|
||||
|
||||
- 工单:`wx/HL#5005`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 正式环境:已部署 `main@59e2b20c`
|
||||
- 鉴权:不需要登录
|
||||
- 数据范围:只允许查询 `PUBLISHED`
|
||||
|
||||
## 接口
|
||||
|
||||
`GET /admin/grassland-guide/public/videos/{videoId}`
|
||||
|
||||
返回列表字段,并增加:`videoMaterialId`、`videoUrl`、
|
||||
`customCoverMaterialId`、`contentHtml`、`contentImageMaterialIds`、
|
||||
`createdBy`、`updatedBy`、`createdAt`、`updatedAt`。
|
||||
|
||||
说明:管理端免登录预览不受 `loginRequired` 限制;该字段只表达小程序用户是否需要登录。
|
||||
草稿、下架、删除或不存在的视频统一按不存在处理。
|
||||
|
||||
测试网关实测:HTTP 200,业务码 `200`。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 草原指南管理端免登录列表接口
|
||||
|
||||
- 工单:`wx/HL#5005`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 正式环境:已部署 `main@59e2b20c`
|
||||
- 鉴权:不需要登录
|
||||
- 数据范围:仅返回 `PUBLISHED`,不会返回草稿、下架或已删除数据
|
||||
|
||||
## 接口
|
||||
|
||||
`GET /admin/grassland-guide/public/videos`
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| keyword | string | 否 | - | 匹配标题或简介 |
|
||||
| featured | boolean | 否 | - | 是否精选 |
|
||||
| page | integer | 否 | 1 | 最小 1 |
|
||||
| pageSize | integer | 否 | 20 | 1~100 |
|
||||
|
||||
`data` 为分页对象:`records`、`total`、`page`、`pageSize`。列表项包含:
|
||||
`videoId`、`title`、`summary`、`effectiveCoverUrl`、`coverSource`、
|
||||
`durationSeconds`、`featured`、`loginRequired`、`sortWeight`、`status`、
|
||||
`publishTime`、`linkedProductId`。
|
||||
|
||||
测试网关实测:HTTP 200,业务码 `200`。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 草原指南管理端免登录播放接口
|
||||
|
||||
- 工单:`wx/HL#5005`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 正式环境:已部署 `main@59e2b20c`
|
||||
- 鉴权:不需要登录
|
||||
- 数据范围:只允许播放 `PUBLISHED`
|
||||
|
||||
## 接口
|
||||
|
||||
`GET /admin/grassland-guide/public/videos/{videoId}/play`
|
||||
|
||||
`data` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| videoId | string | 视频业务 ID |
|
||||
| title | string | 标题 |
|
||||
| videoUrl | string | OSS MP4 直出地址 |
|
||||
| durationSeconds | integer | 素材自动解析的时长(秒) |
|
||||
| effectiveCoverUrl | string | 自定义封面或 OSS 自动截帧封面 |
|
||||
|
||||
该接口是播放最小响应,不返回正文、创建人或操作信息。管理端免登录预览不受
|
||||
`loginRequired` 限制;小程序端仍按该字段执行登录鉴权。
|
||||
|
||||
测试网关实测:HTTP 200,业务码 `200`,真实素材返回播放地址与时长。
|
||||
@@ -0,0 +1,23 @@
|
||||
# 草原指南管理端免登录推荐接口
|
||||
|
||||
- 工单:`wx/HL#5005`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 正式环境:已部署 `main@59e2b20c`
|
||||
- 鉴权:不需要登录
|
||||
- 数据范围:源视频和推荐结果都必须是 `PUBLISHED`,推荐结果自动排除自身
|
||||
|
||||
## 接口
|
||||
|
||||
`GET /admin/grassland-guide/public/videos/{videoId}/recommendations`
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| page | integer | 否 | 1 | 最小 1 |
|
||||
| pageSize | integer | 否 | 20 | 1~100 |
|
||||
|
||||
`data` 为分页对象:`records`、`total`、`page`、`pageSize`;`records` 字段与列表接口一致。
|
||||
推荐继续使用后端推荐算法,相关性不足时随机兜底,不需要前端拼装。
|
||||
|
||||
测试网关实测:HTTP 200,业务码 `200`。
|
||||
@@ -0,0 +1,17 @@
|
||||
# 草原指南管理列表按排序权重正序
|
||||
|
||||
- 工单:`wx/HL#5011`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 接口:`GET /admin/grassland-guide/videos`
|
||||
|
||||
## 变更
|
||||
|
||||
列表排序改为:
|
||||
|
||||
1. `sortWeight ASC`
|
||||
2. `publishTime DESC`
|
||||
3. `videoId DESC`
|
||||
|
||||
排序权重数字越小越靠前,`0` 排在所有正数权重之前。接口字段和请求参数没有变化,前端不需要改传参。
|
||||
|
||||
测试环境已用 57 条真实数据验证,权重顺序为 `0, 0, 0, 1, 100, 200...`。
|
||||
@@ -0,0 +1,98 @@
|
||||
# 草原指南 MP4 物理交错修复与测试数据回填
|
||||
|
||||
> 日期:2026-07-16
|
||||
>
|
||||
> 后端 Issue:[HL #5016](https://git.1814.love:8443/wx/HL/issues/5016)
|
||||
>
|
||||
> 后端 PR:[HL #5019](https://git.1814.love:8443/wx/HL/pulls/5019)
|
||||
>
|
||||
> 测试分支:`dev-v3`
|
||||
>
|
||||
> 影响服务:`hl-user-service`
|
||||
>
|
||||
> 本次不修改前端仓库,不新增或修改接口字段
|
||||
|
||||
## 1. 问题结论
|
||||
|
||||
草原指南真实 4K MP4 在 `1x`、`2x` 播放时出现固定位置反复缓冲。浏览器 Network 中同一文件产生大量被取消并重新发起的 `206 Partial Content` 请求,实际传输量可以超过文件本身体积。
|
||||
|
||||
该问题包含两个独立因素:
|
||||
|
||||
1. 历史 MP4 虽然已经把 `moov` 移到 `mdat` 前面,但音频、视频 Sample 在 `mdat` 中没有按时间物理交错。播放到同一时间点时,浏览器需要在文件相距很远的位置来回读取音视频数据,造成 Range 请求抖动、取消和重复下载。
|
||||
2. 原片本身约 `357204588` 字节、`71` 秒,平均码率约 `40.25 Mbps`。2026-07-16 当前诊断链路连续读取 OSS Range 仅约 `0.52–1.44 MB/s`(约 `4.14–11.50 Mbps`),低于 `1x` 所需的约 `5.03 MB/s`,因此修复文件结构后仍可能因实际网络吞吐不足发生正常缓冲。
|
||||
|
||||
前端缓存策略不能补足长期吞吐缺口。`preload="auto"` 只能改善起播等待,不能让 `10 Mbps` 链路持续播放 `40 Mbps` 原片。
|
||||
|
||||
## 2. 后端修复
|
||||
|
||||
草原指南视频上传确认阶段新增 MP4 无损重封装:
|
||||
|
||||
- 不重新编码,不改变 H.264/AAC Sample。
|
||||
- 不降低分辨率、帧率或码率。
|
||||
- 保持原视频、音频轨的 Sample 数量、Sample 总字节数和时长。
|
||||
- 将 `ftyp`、`moov` 放在文件前部。
|
||||
- 以约 2 秒为窗口重新排列音频、视频 Chunk,使相同时间点的数据物理相邻。
|
||||
- 重封装前后执行媒体指纹校验;不一致时拒绝替换原对象。
|
||||
- 新对象使用 `.progressive-...mp4` Key,成功后再以 CAS 更新文件记录;旧对象暂不删除,便于回滚。
|
||||
- 单个 Pod 同时只执行一个重封装任务,并检查临时磁盘空间,避免大文件并发耗尽磁盘。
|
||||
|
||||
接口路径和请求体保持不变:
|
||||
|
||||
```http
|
||||
POST /admin/material/upload/confirm
|
||||
```
|
||||
|
||||
前端仍然只提交原有 `materialId`、`description` 和 `tagIds`,不需要增加重封装参数。
|
||||
|
||||
## 3. 历史测试数据回填
|
||||
|
||||
已对测试环境问题视频执行一次性回填:
|
||||
|
||||
| 项目 | 值 |
|
||||
| --- | --- |
|
||||
| 草原指南视频 ID | `2076910159484928002` |
|
||||
| 素材 ID | `2076909130286612482` |
|
||||
| 文件 ID | `2076909128663379970` |
|
||||
| 文件大小 | `357204588` 字节 |
|
||||
| 新时长 | `71` 秒 |
|
||||
| 新文件标识 | OSS Key 包含 `.progressive-2076909128663379970-` |
|
||||
|
||||
回填结果:
|
||||
|
||||
- `file_info` 已切换为新 `.progressive-...mp4`,状态为 `ACTIVE / OSS_MP4_READY_V1`。
|
||||
- `material.oss_url`、自动封面和时长已切换。
|
||||
- `grassland_guide_video` 自动封面和时长已同步,业务记录仍为 `PUBLISHED`。
|
||||
- 资源服务 `8082/8182` 与网关 `8080` 的匿名详情、播放接口均返回新地址。
|
||||
- OSS `HEAD` 返回 `200 video/mp4`、`Content-Length: 357204588`、`Accept-Ranges: bytes`。
|
||||
- `Range: bytes=0-1048575` 返回 `206` 和正确的 `Content-Range`。
|
||||
|
||||
## 4. 部署与验证证据
|
||||
|
||||
- 修复提交:`4529da5e97fea21022ca700390a146944608eadc`
|
||||
- `dev-v3` 合并提交:`65c63a68d903370d07dd80ffea62ac58ccfb31c8`
|
||||
- `hl-user-service` 测试环境滚动部署任务:`f4d670ba`
|
||||
- 两实例 `8081/8181` 均启动健康。
|
||||
- `hl-user-service` 测试:`3226` 个测试,`0` 失败,`0` 错误,`6` 跳过。
|
||||
- 真实 357 MB 文件无损重封装测试通过;重封装前后轨道 Sample 数、Sample 字节数和时长一致。
|
||||
- 原文件约 44 秒处的音频、视频数据物理距离约 `185.7 MB`;重封装后缩短到约 `69.7 KB`。
|
||||
|
||||
## 5. 仍需处理的基础设施边界
|
||||
|
||||
本次后端修复解决“文件内部排列导致重复 Range 请求”的问题,但不能提高用户到 OSS 的实时带宽。
|
||||
|
||||
在“不降低码率、不生成低清档”的前提下:
|
||||
|
||||
- `1x` 需要链路持续高于约 `40.25 Mbps`,还应预留网络波动余量。
|
||||
- `2x` 平均需要约 `80.50 Mbps`,短时峰值可能更高。
|
||||
- 当前测试桶传输加速域名请求返回 `400`,未形成可用的加速播放链路。
|
||||
- 若目标用户链路长期低于原片码率,只能选择 OSS 前置 CDN/边缘缓存、开通并验证 OSS 传输加速,或让用户在播放前基本下载完整文件;单纯调整浏览器缓冲逻辑无法解决。
|
||||
|
||||
接入 CDN 或 OSS 传输加速会改变基础设施和费用,须单独确认后实施。不得为规避该问题把流量代理到 Java 服务,也不得把几百 MB 文件整体读入服务内存。
|
||||
|
||||
## 6. 大文件上传确认超时提醒
|
||||
|
||||
本次真实文件在测试环境完成下载、重封装、上传共耗时约 `410` 秒,超过当前网关约 `60` 秒的请求超时。
|
||||
|
||||
- 服务端任务能够继续完成,但前端可能先收到 `504`。
|
||||
- 在异步媒体处理改造完成前,前端不得因单次 `504` 立即重复提交或重复上传,应重新查询素材状态。
|
||||
- 正式发布前应单独改造为异步处理状态机,或提供明确的后台任务查询接口;不建议简单把网关超时提高到数分钟。
|
||||
@@ -0,0 +1,19 @@
|
||||
# 草原指南小程序列表按排序权重正序
|
||||
|
||||
- 工单:`wx/HL#5011`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 影响接口:
|
||||
- `GET /mp/grassland-guide/home`
|
||||
- `GET /mp/grassland-guide/videos`
|
||||
|
||||
## 变更
|
||||
|
||||
普通视频分页列表排序改为:
|
||||
|
||||
1. `sortWeight ASC`
|
||||
2. `publishTime DESC`
|
||||
3. `videoId DESC`
|
||||
|
||||
排序权重数字越小越靠前,`0` 排在所有正数权重之前。首页精选区原本就是小权重优先,规则保持不变;相关推荐仍使用推荐算法,不改为纯权重排序。
|
||||
|
||||
接口字段和请求参数没有变化。
|
||||
@@ -0,0 +1,108 @@
|
||||
# 草原指南切换 2x 后永久缓冲更正
|
||||
|
||||
> 日期:2026-07-16
|
||||
>
|
||||
> 前端仓库:`mmg/hl-ui`
|
||||
>
|
||||
> 影响组件:`src/views/h5/grassland-guide/components/GuideVideoPlayer.vue`
|
||||
>
|
||||
> 影响辅助逻辑:`src/views/h5/grassland-guide/components/playbackBuffer.js`
|
||||
>
|
||||
> 本次后端接口、字段、OSS 地址和视频文件均无变化
|
||||
|
||||
## 1. 现象
|
||||
|
||||
视频在 `1x` 已经开始播放后切换到 `2x`,页面停在“正在缓冲”,即使等待较长时间也不能恢复。
|
||||
|
||||
Network 中可以看到同一个 MP4 存在多条大小不同的 `206 Partial Content` 请求。这是 Chrome 原生媒体加载器根据 MP4 元数据、当前播放位置和缓存状态发起的 HTTP Range 请求,Range 大小不固定是正常行为,不能据此判断 OSS 分片异常。
|
||||
|
||||
## 2. 已确认根因
|
||||
|
||||
线上播放器当前调用链为:
|
||||
|
||||
```text
|
||||
切换 2x
|
||||
→ 设置 video.playbackRate = 2
|
||||
→ evaluateBuffer({ initial: true })
|
||||
→ 要求 bufferAhead >= 15 秒
|
||||
→ 未达到阈值
|
||||
→ pauseForBuffering()
|
||||
→ video.pause()
|
||||
```
|
||||
|
||||
播放器暂停后,浏览器可以降低甚至停止后续媒体预取。当前代码又只依赖 `progress`、`canplay` 等媒体事件重新执行 `evaluateBuffer()`,没有保证这些事件一定继续产生,因此可能永远达不到 `15` 秒阈值,形成状态机死锁。
|
||||
|
||||
同类问题还存在于:
|
||||
|
||||
- 首次播放前等待固定 `8` 秒。
|
||||
- `timeupdate` 检测到低水位后主动 `pause()`。
|
||||
- `waiting`/`stalled` 事件再次主动 `pause()`。
|
||||
|
||||
这些逻辑把浏览器原生的“缺数据时等待并继续下载”变成了应用层“暂停后等待浏览器继续下载”,两者行为并不等价。
|
||||
|
||||
## 3. 必须修改的前端逻辑
|
||||
|
||||
### 3.1 播放与切换倍速
|
||||
|
||||
```js
|
||||
async function play() {
|
||||
playbackRequested.value = true
|
||||
await videoRef.value?.play()
|
||||
}
|
||||
|
||||
function togglePlaybackRate() {
|
||||
const video = videoRef.value
|
||||
if (!video) return
|
||||
|
||||
playbackRate.value = playbackRate.value === 1 ? 2 : 1
|
||||
video.playbackRate = playbackRate.value
|
||||
|
||||
// 禁止在这里 pause()
|
||||
// 禁止在这里重新执行固定秒数的初始缓冲门槛
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 缓冲状态由原生事件驱动
|
||||
|
||||
```js
|
||||
function handleWaiting() {
|
||||
if (!playbackRequested.value) return
|
||||
buffering.value = true
|
||||
recordPlaybackEvent('waiting')
|
||||
}
|
||||
|
||||
function handleStalled() {
|
||||
const video = videoRef.value
|
||||
if (playbackRequested.value && video?.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
|
||||
buffering.value = true
|
||||
}
|
||||
recordPlaybackEvent('stalled')
|
||||
}
|
||||
|
||||
function handlePlaying() {
|
||||
buffering.value = false
|
||||
recordPlaybackEvent('playing')
|
||||
}
|
||||
```
|
||||
|
||||
事件处理器内不得调用 `video.pause()`。只要用户没有主动暂停,就保留原生播放意图,让浏览器在缺数据时自动等待、继续 Range 取流,并在数据恢复后自行继续播放。
|
||||
|
||||
### 3.3 删除主动低水位暂停
|
||||
|
||||
- 删除 `pauseForBuffering()` 对自动缓冲流程的使用。
|
||||
- `handleTimeUpdate()` 只同步时间、记录 `bufferAhead` 和掉帧指标,不得检测低水位后暂停。
|
||||
- `evaluateBuffer()` 不再阻塞首次播放或倍速切换;如保留该方法,只能用于诊断,不能控制 `play/pause`。
|
||||
- 删除 `BUFFER_POLICIES` 中作为播放硬门槛的 `initial/low/resume`,避免后续重新引入死锁。
|
||||
|
||||
## 4. 验收要求
|
||||
|
||||
- [ ] `1x` 点击播放后能直接进入原生播放流程,不等待固定 `8` 秒。
|
||||
- [ ] 播放中切换 `2x` 不触发 `pause` 事件。
|
||||
- [ ] 切换 `2x` 后,即使触发 `waiting`,后续 Range 请求仍继续。
|
||||
- [ ] 数据恢复后触发 `playing`,缓冲遮罩自动消失,播放继续。
|
||||
- [ ] `1x ↔ 2x` 连续切换 10 次,不出现永久缓冲。
|
||||
- [ ] 拖动进度后仍可重新播放,用户主动暂停不会被自动恢复。
|
||||
- [ ] Console 中不再出现 `ratechange → initial-buffering → pause` 的调用序列。
|
||||
- [ ] 使用 DevTools 测试真实用户表现时关闭 `Disable cache`;需要模拟弱网时单独选择网络限速,不把禁用缓存结果当作正常生产表现。
|
||||
|
||||
修复状态机后,若 `1x` 或 `2x` 仍出现能够自行恢复的短时 `waiting`,再根据 `bufferAhead`、实际下载速度和掉帧数判断是用户网络还是设备解码能力问题。播放器逻辑修复不能提高用户带宽;需要跨地区稳定承载原始 4K 高码率视频时,应另行评估 OSS 前置 CDN Range 缓存。
|
||||
@@ -0,0 +1,15 @@
|
||||
# 草原指南管理端免登录列表按排序权重正序
|
||||
|
||||
- 工单:`wx/HL#5011`
|
||||
- 测试环境:已部署 `dev-v3`
|
||||
- 接口:`GET /admin/grassland-guide/public/videos`
|
||||
|
||||
## 变更
|
||||
|
||||
列表排序改为:
|
||||
|
||||
1. `sortWeight ASC`
|
||||
2. `publishTime DESC`
|
||||
3. `videoId DESC`
|
||||
|
||||
排序权重数字越小越靠前,`0` 排在所有正数权重之前。接口字段、分页方式和免登录规则没有变化。
|
||||
@@ -0,0 +1,128 @@
|
||||
# 草原指南 4K 原片播放缓冲优化通知
|
||||
|
||||
> 日期:2026-07-16
|
||||
>
|
||||
> 前端仓库:`mmg/hl-ui`
|
||||
>
|
||||
> 影响页面:草原指南 H5 详情/播放页
|
||||
>
|
||||
> 本次后端接口、字段和 OSS 地址均无变化
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 2026-07-16 线上验证发现:原通知中的“主动暂停并等待固定缓冲秒数”会与浏览器原生媒体加载策略形成死锁,已经撤销。前端必须按独立更正通知
|
||||
> [`16_fix_grassland_guide_playback_buffer_deadlock.md`](./16_fix_grassland_guide_playback_buffer_deadlock.md)
|
||||
> 处理,不得再以 `8/15` 秒阈值阻塞播放或切换倍速。
|
||||
|
||||
## 1. 问题与诊断结论
|
||||
|
||||
草原指南详情直接播放后端返回的 OSS 原始 MP4。正式环境真实 4K 样本的媒体参数为:
|
||||
|
||||
- 文件约 `451.74 MiB`,时长约 `95` 秒。
|
||||
- 分辨率 `3812 × 2160`,`60 fps`。
|
||||
- H.264 Main Profile,Level `5.2`。
|
||||
- 平均码率约 `40.20 Mbps`。
|
||||
- `1x` 播放时短时峰值约 `72.98 Mbps`。
|
||||
- `2x` 播放时平均网络消耗约 `80.40 Mbps`;短时峰值约 `142.17 Mbps`。
|
||||
|
||||
正式 OSS 已支持 HTTP Range,请求返回 `206 Partial Content`。同一诊断环境连续读取 OSS Range 的平均速度约 `230.52 Mbps`,高于该视频 `2x` 播放的短时峰值。因此目前没有证据表明 Java 服务或 OSS Range 能力是主要瓶颈;但该结果不代表每个用户到 OSS 的实时链路都能达到相同速度。
|
||||
|
||||
现已确认 `1x` 也会偶发卡顿。结合 `1x` 接近 `73 Mbps` 的短时峰值,更可能是用户链路瞬时波动与浏览器前向缓冲较浅共同造成:即使平均网速高于视频平均码率,只要短时间下载速度低于瞬时消耗速度,缓冲仍可能耗尽。因此首次 `1x` 播放和卡顿恢复也必须执行缓冲水位保护,不能只处理 `2x`。
|
||||
|
||||
此外,`4K 60 fps` 在 `2x` 下相当于设备需要承担接近 `120 fps` 的解码节奏。部分设备即使网络充足,也可能因硬件解码能力不足出现掉帧;前端需要把“等待网络缓冲”和“设备解码掉帧”分别记录。
|
||||
|
||||
## 2. 前端处理要求
|
||||
|
||||
### 2.1 保持原生 Range 播放
|
||||
|
||||
- 继续直接使用接口返回的 `videoUrl` 作为 `<video src>`。
|
||||
- 设置 `preload="auto"`,允许浏览器提前加载媒体数据。
|
||||
- 不要使用 `fetch`/`axios` 把完整 MP4 下载为 Blob 后再播放,避免一次性占用数百 MiB 内存并破坏原生 Range 调度。
|
||||
- 不要改写 OSS 域名,不转成 HLS/M3U8,不接直播播放器。
|
||||
- 本轮不降低码率、不转码、不新增清晰度档位。
|
||||
|
||||
### 2.2 计算真实前向缓冲
|
||||
|
||||
不能只读取 `video.buffered.end(video.buffered.length - 1)`。应找到包含 `currentTime` 的 buffered 区间,再计算:
|
||||
|
||||
```js
|
||||
function getBufferAhead(video) {
|
||||
const currentTime = video.currentTime
|
||||
|
||||
for (let index = 0; index < video.buffered.length; index += 1) {
|
||||
const start = video.buffered.start(index)
|
||||
const end = video.buffered.end(index)
|
||||
|
||||
if (currentTime >= start && currentTime <= end) {
|
||||
return Math.max(0, end - currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 `1x` 与倍速播放缓冲策略
|
||||
|
||||
- 用户点击播放时直接调用原生 `video.play()`,不得先暂停等待固定缓冲秒数。
|
||||
- 用户切换 `2x` 时只设置 `video.playbackRate = 2`,不得调用 `pause()`,也不得重新执行“初始缓冲门槛”。
|
||||
- `waiting` 事件只负责显示“正在缓冲”和记录诊断;不得在事件处理器内再次调用 `pause()`。
|
||||
- 保持原生播放请求后,浏览器会继续 Range 取流;数据恢复后通过 `playing` 事件关闭缓冲提示。
|
||||
- `stalled` 用于记录网络加载停滞;仅当仍有播放意图且 `readyState < HTMLMediaElement.HAVE_FUTURE_DATA` 时显示缓冲提示,不主动暂停。
|
||||
- `timeupdate` 只采集缓冲指标,不得因低于人为水位而主动暂停。
|
||||
- 用户主动暂停、拖动进度或离开页面时清除缓冲提示,避免与真实播放意图混淆。
|
||||
- `preload="auto"` 只是浏览器提示,不能强制浏览器预取指定秒数。
|
||||
|
||||
`video.buffered` 用于诊断和观测,不再作为阻塞 `play()` 或切换倍速的硬门槛。简单的原生 `<video>` 无法保证“暂停后一定继续预取到 N 秒”;若业务将来要求确定性分片缓冲,需要另行评估 MSE/HLS 或 CDN,不应在原生 MP4 播放器中模拟。
|
||||
|
||||
### 2.4 记录卡顿证据
|
||||
|
||||
至少监听并记录以下事件和状态:
|
||||
|
||||
- 事件:`loadstart`、`loadedmetadata`、`canplay`、`canplaythrough`、`progress`、`waiting`、`stalled`、`playing`、`seeking`、`seeked`、`error`。
|
||||
- 状态:`currentTime`、`playbackRate`、前向缓冲秒数、`readyState`、`networkState`。
|
||||
- 浏览器支持时记录 `getVideoPlaybackQuality()` 的 `droppedVideoFrames` 和 `totalVideoFrames`。
|
||||
|
||||
日志不得记录完整带签名 URL、Token 或其他凭证。建议只记录 `videoId`、事件时间和上述播放指标。
|
||||
|
||||
判断口径:
|
||||
|
||||
- 出现 `waiting`/`stalled`,同时前向缓冲接近 `0`:网络或缓冲调度不足。
|
||||
- 前向缓冲充足、没有 `waiting`,但 `droppedVideoFrames` 持续上升:设备解码能力不足。
|
||||
|
||||
## 3. 接口契约
|
||||
|
||||
播放接口保持不变:
|
||||
|
||||
```http
|
||||
GET /admin/grassland-guide/public/videos/{videoId}/play
|
||||
```
|
||||
|
||||
前端继续使用:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `videoId` | string | 视频业务 ID |
|
||||
| `videoUrl` | string | OSS 原始 MP4 地址,直接交给原生 `<video>` |
|
||||
| `durationSeconds` | integer | 后端从素材解析的时长 |
|
||||
| `effectiveCoverUrl` | string | 生效封面 |
|
||||
|
||||
雪花 ID 必须按字符串处理。本次不增加缓冲、码率或清晰度字段,前端不得等待后端返回这些字段后才处理播放。
|
||||
|
||||
相关接口说明见:
|
||||
|
||||
- [`16_feat_grassland_guide_public_play.md`](./16_feat_grassland_guide_public_play.md)
|
||||
- [`12_feat_grassland_guide_admin_mp_oss.md`](./12_feat_grassland_guide_admin_mp_oss.md)
|
||||
|
||||
## 4. 前端验收清单
|
||||
|
||||
- [ ] `<video>` 使用接口原始 `videoUrl`,并设置 `preload="auto"`。
|
||||
- [ ] 没有将完整 MP4 下载为 Blob。
|
||||
- [ ] 首次 `1x` 播放不受固定缓冲秒数阻塞。
|
||||
- [ ] 切换 `2x` 不调用 `pause()`,不等待 `15` 秒缓冲。
|
||||
- [ ] `waiting`/`stalled` 只更新提示与日志,不主动暂停;`playing` 能可靠关闭提示。
|
||||
- [ ] 用户暂停、拖动和离开页面时不会被自动恢复播放。
|
||||
- [ ] 能区分并记录缓冲耗尽与设备解码掉帧。
|
||||
- [ ] 使用正式 4K 样本分别连续验证 `1x` 和 `2x`,记录等待次数、累计等待时长、最小前向缓冲和掉帧数。
|
||||
- [ ] Chrome 桌面端和目标移动设备均完成验证。
|
||||
|
||||
若完成上述缓冲策略后,多个地区和设备仍普遍出现“前向缓冲耗尽”,再单独评估 OSS 前置 CDN Range 缓存或 OSS 传输加速;该基础设施调整会新增费用,不属于本次前端通知范围。
|
||||
@@ -0,0 +1,34 @@
|
||||
# 草原指南 MP4 重封装时间轴修复正式发布
|
||||
|
||||
> 日期:2026-07-17
|
||||
>
|
||||
> 后端 Issue:[HL #5021](https://git.1814.love:8443/wx/HL/issues/5021)
|
||||
>
|
||||
> 后端 PR:[HL #5026](https://git.1814.love:8443/wx/HL/pulls/5026)
|
||||
>
|
||||
> 正式分支:`main`
|
||||
>
|
||||
> 影响服务:`hl-user-service`
|
||||
|
||||
## 问题与修复
|
||||
|
||||
旧版 MP4 无损重封装虽然保持了 Sample 数据,但 `mvhd/tkhd/elst` 使用了不一致的时间单位,浏览器可能把完整视频识别成数秒并提前触发 `ended`。
|
||||
|
||||
后端现已统一 presentation timeline 的 movie timescale,并在替换 OSS 对象前校验 `mvhd/tkhd/elst/mdhd/stts` 一致性。视频仍保持原分辨率、帧率、编码和码率,不经过 Java 服务代理播放流量。
|
||||
|
||||
接口路径、请求体及响应字段均保持不变,前端无需适配新字段。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- MP4 核心测试:30/30 通过
|
||||
- `FileServiceTest` 与 `OssServiceTest`:141/141 通过
|
||||
- Maven package:`BUILD SUCCESS`
|
||||
- 测试环境真实媒体 1x/2x 播放已验收不卡顿
|
||||
- 正式部署任务:`#283`
|
||||
- 发布提交/镜像:`009d5ffe`
|
||||
- `hl-user-service`:`2/2 Ready`
|
||||
- 正式列表、详情、推荐、播放接口:HTTP/业务码均为 200
|
||||
- 正式真实 MP4:`Content-Length: 592945035`、`Accept-Ranges: bytes`
|
||||
- `Range: bytes=0-1023`:返回 206,`Content-Range: bytes 0-1023/592945035`
|
||||
|
||||
本次未修改或部署 `hl-ui`,无数据库结构及 Nacos 配置变更。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 草原指南排序权重正序规则正式发布
|
||||
|
||||
> 日期:2026-07-17
|
||||
>
|
||||
> 后端 PR:[HL #5026](https://git.1814.love:8443/wx/HL/pulls/5026)
|
||||
>
|
||||
> 正式分支:`main`
|
||||
>
|
||||
> 影响服务:`hl-resource-service`
|
||||
|
||||
## 变更说明
|
||||
|
||||
草原指南列表排序权重统一按正序处理:数值越小越靠前,`0` 位于 `1` 之前。
|
||||
|
||||
本次不新增或删除接口,不修改请求参数与响应字段。前端继续使用原有列表接口,不需要自行反转结果。
|
||||
|
||||
## 正式验证
|
||||
|
||||
- 正式部署任务:`#282`
|
||||
- 发布提交/镜像:`009d5ffe`
|
||||
- `hl-resource-service`:`2/2 Ready`
|
||||
- 匿名正式列表接口:HTTP 200、业务码 200
|
||||
- 正式数据首屏排序权重:`0,1,2,3,4,5,6,7,8,9`
|
||||
|
||||
本次未修改或部署 `hl-ui`,无数据库结构及 Nacos 配置变更。
|
||||
@@ -0,0 +1,111 @@
|
||||
# 【前端待处理·管理后台】景区季节全量清空与标签同步(#5123)
|
||||
|
||||
## 目标前端
|
||||
|
||||
- 端类型:管理后台(Web)
|
||||
- 目标仓库:`mmg/hl-ui`
|
||||
- 目标分支:按前端仓库当前发布流程执行
|
||||
- 后端工单:`wx/HL #5123`
|
||||
- 小程序:无需改页面,但必须参与接口与缓存回退验收
|
||||
|
||||
## 问题与根因
|
||||
|
||||
`src/views/resource/scenic/SeasonDrawer.vue` 的 `handleSave()` 目前只在
|
||||
`isSeasonConfigured(form)` 为 true 时调用 PUT。已存在的季节被全部清空后,
|
||||
该判断变为 false,前端既不发 PUT,也没有调用后端已有 DELETE,数据库旧记录仍在,
|
||||
因此重新打开抽屉会回显旧内容,页签“已配置”和景区列表季节标签也不会消失。
|
||||
|
||||
后端同时修复了可空字段写入 null、季节素材引用解绑,以及管理后台/小程序相关缓存依赖失效。
|
||||
前端不能继续用“不发请求”表达删除已存在季节。
|
||||
|
||||
## 接口契约
|
||||
|
||||
### 查询季节列表
|
||||
|
||||
```http
|
||||
GET /admin/scenic/spot/{scenicId}/seasons
|
||||
```
|
||||
|
||||
### 保存仍有内容的季节
|
||||
|
||||
```http
|
||||
PUT /admin/scenic/spot/{scenicId}/season/{seasonType}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### 删除已全量清空的季节
|
||||
|
||||
```http
|
||||
DELETE /admin/scenic/spot/{scenicId}/season/{seasonType}
|
||||
```
|
||||
|
||||
`seasonType` 取 `spring`、`summer`、`autumn`、`winter`。DELETE 无请求体,沿用现有管理后台鉴权。
|
||||
|
||||
## 前端改动要求
|
||||
|
||||
### 1. API 封装
|
||||
|
||||
在 `src/api/scenic.js` 新增并导出删除方法,例如:
|
||||
|
||||
```js
|
||||
export function deleteScenicSeason(scenicId, seasonType) {
|
||||
return http.delete(`/scenic/spot/${scenicId}/season/${seasonType}`)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 记录初始已配置季节
|
||||
|
||||
`SeasonDrawer.vue` 每次打开并成功加载季节列表后,记录后端实际返回过的 `seasonType` 集合。
|
||||
|
||||
- 加载前清空该集合,避免切换景区时串数据。
|
||||
- 只以后端列表是否存在记录作为“初始已配置”依据,不要用当前编辑中的
|
||||
`isSeasonConfigured()` 反推。
|
||||
- 查询失败时不得把未知状态当成“从未配置”;应阻止保存或保留错误状态,避免误判删除。
|
||||
|
||||
### 3. 保存判定
|
||||
|
||||
遍历四季时按以下规则处理:
|
||||
|
||||
| 初始状态 | 当前表单 | 请求 |
|
||||
| --- | --- | --- |
|
||||
| 不存在 | 全空 | 不请求 |
|
||||
| 不存在 | 有内容 | PUT |
|
||||
| 已存在 | 有内容 | PUT |
|
||||
| 已存在 | 全空 | DELETE |
|
||||
|
||||
所有文本字段都按 `trim()` 后判断是否为空;素材按有效 `id` 判断,空壳对象不能让季节继续显示为已配置。
|
||||
|
||||
同一次保存中任一季节请求失败时,不得关闭抽屉或显示“全部保存成功”;应保留编辑内容并明确提示失败季节。
|
||||
全部请求成功后重新 GET 季节列表,再触发父级景区列表刷新,确保以下状态以服务端结果收敛:
|
||||
|
||||
- 当前抽屉内容不再回显已删除季节。
|
||||
- 对应页签“已配置”标记消失。
|
||||
- 景区列表对应季节标签消失。
|
||||
- 其他季节和景区基础信息不变。
|
||||
|
||||
## 小程序链路说明
|
||||
|
||||
小程序产品详情会经 product-service 和 resource-service 读取季节亮点及季节媒体。
|
||||
后端已为实时产品详情、订单快照、mp-service 产品聚合和景区详情缓存补齐
|
||||
`table:scenic_season` / `table:scenic_spot` 依赖。
|
||||
|
||||
季节删除后,小程序不得继续显示旧 `seasonHighlights`、封面、轮播图或视频;未命中季节时应回退景区本体媒体和节点快照描述。前端管理后台无需主动清小程序 Redis,也不得新增清缓存接口。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 仅配置描述和亮点的季节,两项全部清空并保存后,重新打开不再回显。
|
||||
- [ ] 对应页签“已配置”标记消失。
|
||||
- [ ] 保存成功并刷新景区列表后,对应季节标签消失。
|
||||
- [ ] 保留其他内容时,可单独清空描述、亮点、封面、轮播图和视频。
|
||||
- [ ] 清空一个季节不影响其他季节及景区基础信息。
|
||||
- [ ] 从未配置且仍为空的季节不发 PUT 或 DELETE。
|
||||
- [ ] 查询季节列表失败时不会误发 DELETE。
|
||||
- [ ] 部分请求失败时抽屉保留,且不会提示全部成功。
|
||||
- [ ] 小程序产品详情不再返回已删除季节的亮点或媒体。
|
||||
- [ ] 小程序在季节未命中时正确回退景区本体媒体和节点快照描述。
|
||||
|
||||
## 发布说明
|
||||
|
||||
- 本文是前端修复与联调通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||
- 前端完成后须创建并指派自身工单,走分支、PR、测试和发布流程,并关联 `wx/HL #5123`。
|
||||
- 测试环境验收必须通过网关使用真实管理员鉴权完成 PUT、DELETE、GET 回读;记录不得包含 token、Cookie 或真实隐私数据。
|
||||
在新工单中引用
屏蔽一个用户