比较提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
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 |
@@ -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",
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
# 【新增接口·管理后台】核团详情接入真实司机车辆连续服务区间(#5037)
|
||||
|
||||
> **Issue**: [wx/HL#5037](https://git.1814.love:8443/wx/HL/issues/5037)
|
||||
>
|
||||
> **PR**: [wx/HL#5048](https://git.1814.love:8443/wx/HL/pulls/5048)
|
||||
>
|
||||
> **服务**: `hl-order-service-v3` / `hl-fleet-service`
|
||||
>
|
||||
> **日期**: 2026-07-18
|
||||
>
|
||||
> **影响范围**: 管理后台订单中心核团详情中的司机车辆展示
|
||||
|
||||
> **契约边界修正**:本文只面向前端保留管理端公开接口;Fleet 与 Order v3 之间的
|
||||
> internal Feign 契约已迁至 `hl-backend-changelog`,不再作为前端对接内容发布。
|
||||
|
||||
## 一、前端对接结论
|
||||
|
||||
1. 核团详情新增正式接口 `GET /v3/admin/order/{orderId}/settlement/return-detail`。
|
||||
2. 前端只调用 Order v3 管理端接口;请求只有 Path 参数 `orderId`,无 Query 参数、无请求体。
|
||||
3. 司机车辆数据来自 Fleet 真实派单,不再生成 mock/占位数据。
|
||||
4. `driverVehicles` 永远是数组:无有效派车时返回 `[]`,不会返回 `null`。
|
||||
5. Fleet 不可用时返回业务码 `584072`,前端应显示“暂时不可用/重试”,不得当成“没有派车”。
|
||||
6. 同一 `vehicleId + driverId` 的连续自然日合并为一个闭区间;换车、换司机或日期断档会拆成不同项。
|
||||
7. 同一订单允许多车、多司机并行,前端必须遍历完整数组,不能只展示第一项。
|
||||
8. `driverId`、`vehicleId` 按字符串处理,禁止转 JavaScript `Number`。
|
||||
9. `driverPhone` 已在 Fleet 出域前脱敏;前端不得尝试补全、缓存或日志打印明文手机号。
|
||||
10. 房务管理员、房务组长无订单详情查看权限,调用会返回 `581045`;该入口面向有订单查看权限的管理端角色。
|
||||
|
||||
## 二、接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 调用方 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 核团详情 | GET | `/v3/admin/order/{orderId}/settlement/return-detail` | 管理后台 | 前端正式入口,返回当前有效司机车辆区间 |
|
||||
|
||||
## 三、管理端正式接口
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2000000000000000001/settlement/return-detail HTTP/1.1
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
### 3.2 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `orderId` | Path | String | 是 | 正整数 | 订单雪花 ID;按字符串传递 |
|
||||
|
||||
无 Query 参数、无请求体。当前有效用车需求由 Order v3 在服务端解析,前端不得缓存或拼接需求版本。
|
||||
|
||||
### 3.3 出参 `Result<SettlementReturnDetailRespVO>`
|
||||
|
||||
| 字段 | 类型 | 必定存在 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `data.driverVehicles` | Array | 是 | 当前有效需求下的司机车辆连续服务区间;无数据固定 `[]` |
|
||||
|
||||
`driverVehicles[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `driverId` | String / null | 司机 ID;正常最终态有值,异常历史缺档案记录可能为 `null` |
|
||||
| `driverName` | String / null | 司机姓名;档案归档时回退派单快照 |
|
||||
| `driverPhone` | String / null | 脱敏手机号,例如 `138****0000` |
|
||||
| `vehicleId` | String / null | 车辆 ID;按字符串处理 |
|
||||
| `vehiclePlateNo` | String / null | 派单车牌快照 |
|
||||
| `vehicleModelName` | String / null | 当前车型名,档案归档时回退派单快照 |
|
||||
| `seatCount` | Integer / null | 当前车辆座位数 |
|
||||
| `startDate` | String | 闭区间开始日期,格式 `yyyy-MM-dd` |
|
||||
| `endDate` | String | 闭区间结束日期,格式 `yyyy-MM-dd` |
|
||||
|
||||
### 3.4 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"driverVehicles": [
|
||||
{
|
||||
"driverId": "2000000000000000101",
|
||||
"driverName": "测试司机",
|
||||
"driverPhone": "138****0000",
|
||||
"vehicleId": "2000000000000000201",
|
||||
"vehiclePlateNo": "蒙A·TEST1",
|
||||
"vehicleModelName": "测试七座车",
|
||||
"seatCount": 7,
|
||||
"startDate": "2026-07-23",
|
||||
"endDate": "2026-07-28"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 空业务结果
|
||||
|
||||
订单存在但没有当前有效用车需求、订单已取消,或当前需求没有 `assigned/completed` 最终派车时:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"driverVehicles": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端空态判断只能使用 `driverVehicles.length === 0`,不要判断 `data == null`。
|
||||
|
||||
## 四、聚合口径
|
||||
|
||||
### 4.1 纳入与排除
|
||||
|
||||
| Fleet 派单状态 | 是否展示 | 说明 |
|
||||
|---|---|---|
|
||||
| `assigned` | 是 | 已形成最终司机车辆关系 |
|
||||
| `completed` | 是 | 已形成并完成的最终关系 |
|
||||
| `unassigned` | 否 | 尚未派车 |
|
||||
| `holding` | 否 | 排车/司机确认链路未最终完成 |
|
||||
| `canceled` | 否 | 关系已失效 |
|
||||
|
||||
仅查询 Order v3 当前有效 `requirementId`。历史需求即使保留 `assigned/completed` 数据,也不会进入当前核团详情。
|
||||
|
||||
### 4.2 连续区间
|
||||
|
||||
```text
|
||||
同车同司机:07-23、07-24、07-25 → 07-23 ~ 07-25(一项)
|
||||
同车同司机:07-23、07-25 → 两项(日期断档)
|
||||
同车换司机或同司机换车 → 分项
|
||||
同日多车并行 → 全部返回
|
||||
```
|
||||
|
||||
`startDate/endDate` 都包含当天。前端不得自行补日期、重算关系或按姓名/车牌合并。
|
||||
|
||||
## 五、错误码与前端行为
|
||||
|
||||
| code | 含义 | 前端处理 |
|
||||
|---|---|---|
|
||||
| `200` | 查询成功 | 渲染完整 `driverVehicles`;空数组展示空态 |
|
||||
| `400` | `orderId` 非正数/参数非法 | 提示参数错误,不发起重试风暴 |
|
||||
| `401` | 未登录或登录失效 | 走统一登录失效处理 |
|
||||
| `581007` | 订单不存在 | 提示订单不存在/已删除 |
|
||||
| `581045` | 房务角色无权查看订单详情 | 隐藏入口并走统一无权限提示 |
|
||||
| `584072` | Fleet 司机车辆信息暂时不可用 | 保留页面上下文,显示错误与重试;禁止渲染空态 |
|
||||
|
||||
`584072` 与成功空数组含义不同:
|
||||
|
||||
```text
|
||||
code=200 + driverVehicles=[] → 业务上确实没有有效司机车辆
|
||||
code=584072 → 跨服务查询失败,当前状态未知
|
||||
```
|
||||
|
||||
## 六、前端接入清单
|
||||
|
||||
- [ ] 核团详情改调 `GET /v3/admin/order/{orderId}/settlement/return-detail`。
|
||||
- [ ] 遍历 `data.driverVehicles`,支持多车、多司机、多区间。
|
||||
- [ ] 所有 ID 保持 String,不经过 `Number()`、`parseInt()`。
|
||||
- [ ] 日期按后端闭区间直接展示,不自行合并或补齐断档。
|
||||
- [ ] 空数组显示“暂无有效司机车辆”,不得生成 mock 卡片。
|
||||
- [ ] `584072` 显示加载失败与重试,不显示空态。
|
||||
- [ ] 房务角色不展示该入口。
|
||||
|
||||
## 七、兼容性与不影响范围
|
||||
|
||||
- 新增只读接口,不修改既有核单 Step1~Step6、汇总、日志或提交接口。
|
||||
- 不修改派单写入、司机确认、改派、取消和完结状态机。
|
||||
- 不涉及 DDL、Redis Key、MQ Topic 或网关顶级路由变更。
|
||||
- 既有 `MockVehicleProvider` 仍只服务终止行程/退款金额计算,不参与本接口;金融计算链路不在本次变更范围。
|
||||
- 本次未修改 `hl-ui`,需前端按本文完成接入。
|
||||
|
||||
## 八、测试环境验证
|
||||
|
||||
### 8.1 部署
|
||||
|
||||
```text
|
||||
PR #5048 已合并:merge commit 736659cd4
|
||||
Fleet:Deploy Panel 任务 9d0a2ada,8087/8187 滚动部署成功
|
||||
Order v3:Deploy Panel 任务 3e4c2c49,8086/8186 滚动部署成功
|
||||
测试环境随后再次滚动发布同一 dev-v3,16:00:37 完成;当前四端口均监听且 Nacos healthy
|
||||
```
|
||||
|
||||
### 8.2 OpenAPI
|
||||
|
||||
```text
|
||||
Order v3 /v2/api-docs?group=default:
|
||||
/v3/admin/order/{orderId}/settlement/return-detail 存在
|
||||
operation summary 存在,description 明确包含 584072
|
||||
```
|
||||
|
||||
### 8.3 真实 API、DB 与日志
|
||||
|
||||
使用测试账号新获取的 `CUSTOMIZER` token,经 `https://api.test.1814.love:9443` 验证:
|
||||
|
||||
```text
|
||||
主样本:Fleet DB 6 条连续日切片(2026-07-23 ~ 2026-07-28)
|
||||
Fleet 8087/8187:均返回 1 个闭区间,与只读 DB 精确一致,手机号已脱敏
|
||||
Order v3 网关:HTTP 200 / code 200,返回同一 1 个区间
|
||||
Long ID:driverId/vehicleId 均为 JSON String
|
||||
空样本:Fleet 最终态 0 行,driverVehicles=[] 且非 null
|
||||
无 token:code 401
|
||||
orderId=0:code 400
|
||||
最终业务探测窗口:Order v3 8086/8186 均 0 ERROR/异常栈;Fleet 双实例 0 ERROR/异常栈
|
||||
```
|
||||
|
||||
Order v3 全组 OpenAPI 生成仍会记录一条既有 Springfox 超长数字 example 的 `NumberFormatException` 栈;本次新增 operation 可正常读取,且业务 API 干净窗口无异常。该日志来自既有文档模型,不由 #5037 数据流触发。
|
||||
|
||||
## 九、后端验证证据
|
||||
|
||||
```text
|
||||
最新 dev-v3 rebase 后目标回归:120/120 通过(Order v3 65、Fleet 55)
|
||||
Fleet 全量 clean verify:1815/1815 通过
|
||||
Fleet Spotless:502 个生产 Java 文件,0 违规
|
||||
Order v3 全量:5660 tests;5 项失败均在纯上游基线独立复现,#5037 无新增失败
|
||||
git diff --check、secret scan、数据流 gate_check:通过
|
||||
独立盲审、API 契约审计、最终复审:无阻断项
|
||||
```
|
||||
|
||||
## 十、回滚
|
||||
|
||||
- 无 DDL,代码回滚即可。
|
||||
- 回滚顺序:先 Order v3,后 Fleet,避免消费者依赖不存在的提供方契约。
|
||||
- 回滚后前端应兼容接口不可用,不得回退到本地 mock。
|
||||
|
||||
## 十一、关联链接
|
||||
|
||||
- Issue: [#5037](https://git.1814.love:8443/wx/HL/issues/5037)
|
||||
- PR: [#5048](https://git.1814.love:8443/wx/HL/pulls/5048)
|
||||
- Merge commit: [736659cd4](https://git.1814.love:8443/wx/HL/commit/736659cd4)
|
||||
- 车务读模型统一说明: `60_4936_车务看板详情候选与矩阵读模型统一-管理后台.md`
|
||||
@@ -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`。
|
||||
@@ -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,158 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
# 【修改接口·前端待处理·管理后台】车务派单候选筛选、分页与任意车辆选择
|
||||
|
||||
> **服务**: 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` |
|
||||
| `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` 或车型不一致禁用车辆;这些情况应显示“需求不匹配/座位不足”提示,但允许车务选中。
|
||||
|
||||
### 先选司机与取消车辆
|
||||
|
||||
- 仅传 `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` 兜底。单量为司机历史累计,不按赛季清零。
|
||||
|
||||
## 最终派单规则
|
||||
|
||||
- 车型或座位不匹配:候选项仍可选,预检返回 warning,最终派单不阻断。
|
||||
- 档期冲突、车辆/司机不可用、黑名单或跨常驻未确认:仍按现有业务守卫阻断。
|
||||
- `strictSeats` 为历史兼容字段,可不再提交;即使提交 `true` 也不会把座位不足变成阻断。
|
||||
|
||||
## 前端处理清单
|
||||
|
||||
- [ ] 车辆和司机列表分别接 `records/total/page/pageSize` 并增加独立分页控件。
|
||||
- [ ] 车队和车型筛选使用 `fleetTeamFacets/vehicleTypeFacets` 动态渲染及计数。
|
||||
- [ ] 车辆行展示车型、常驻司机、协议参考价;需求车型或座位不匹配只醒目标记,不禁选。
|
||||
- [ ] 支持再次点击已选车辆取消选择,并传 `selectedVehicleId: null`。
|
||||
- [ ] 支持先选司机,并展示/置顶 `selectedDriverResidentVehicle`。
|
||||
- [ ] 司机无评价显示“暂无评价”,不伪造 `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,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 或真实隐私数据。
|
||||
在新工单中引用
屏蔽一个用户