比较提交
183
次代码提交
f3e200bc45
...
main
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
c7d7b4c61e | ||
|
|
aa11cd2610 | ||
|
|
effa929648 | ||
|
|
a6dfbd4719 | ||
|
|
dcd914c8a5 | ||
|
|
5409e499cd | ||
|
|
12d25a7f35 | ||
|
|
c55453fb89 | ||
|
|
aeb5106d90 | ||
|
|
c87e079286 | ||
|
|
90b18a1594 | ||
|
|
3bf207769a | ||
|
|
d2b34953bc | ||
|
|
dd6794e4b8 | ||
|
|
c36c051667 | ||
|
|
a0dde3e6ad | ||
|
|
ed192a98a4 | ||
|
|
fe4fa3b70b | ||
|
|
a785dcbae6 | ||
|
|
9ab4794549 | ||
|
|
f8fb2f34b7 | ||
|
|
575292337b | ||
|
|
c96526f098 | ||
|
|
0b6467b2c3 | ||
|
|
3ce7ef580a | ||
|
|
1029581523 | ||
|
|
068e6ef2d7 | ||
|
|
6005b617a7 | ||
|
|
9571ad0ceb | ||
|
|
8835683a40 | ||
|
|
060d71bb90 | ||
|
|
ed0e5bda03 | ||
|
|
22e05ddeaf | ||
|
|
d35be59c3b | ||
|
|
f8ac9edf78 | ||
|
|
053276cfdd | ||
|
|
86da96e23e | ||
|
|
b2ca08e95e | ||
|
|
e08991a588 | ||
|
|
b4f2e3da14 | ||
|
|
58a9fc7211 | ||
|
|
9537a84303 | ||
|
|
d481025d6b | ||
|
|
8b683169ea | ||
|
|
f9b49d2852 | ||
|
|
737e20824a | ||
|
|
bb6df1469a | ||
|
|
21b7836c2b | ||
|
|
8b56ce67c6 | ||
|
|
2719ef1839 | ||
|
|
0c0d1ae396 | ||
|
|
2f5013a76a | ||
|
|
944c09775e | ||
|
|
d47e68a451 | ||
|
|
194f5df83d | ||
|
|
97056b0370 | ||
|
|
441a01d6ea | ||
|
|
c200920a76 | ||
|
|
096398a6f8 | ||
|
|
cc8292ea1e | ||
|
|
93027c27eb | ||
|
|
5e25e03897 | ||
|
|
77ab7a9a67 | ||
|
|
5d34813960 | ||
|
|
b221b2317a | ||
|
|
5203c4a599 | ||
|
|
7010d52c60 | ||
|
|
5995966e1d | ||
|
|
5b3764085a | ||
|
|
a7c93679f8 | ||
|
|
11195659e1 | ||
|
|
1c59fb716a | ||
|
|
358be6ddb2 | ||
|
|
5442f80523 | ||
|
|
27ba364e60 | ||
|
|
b54834b3f5 | ||
|
|
e66936fc5a | ||
|
|
3d0a3d7757 | ||
|
|
1e71734160 | ||
|
|
e3fe98b1f2 | ||
|
|
92c9bb51c1 | ||
|
|
ccc6c90e6f | ||
|
|
d221505375 | ||
|
|
48ddf1194e | ||
|
|
26c43ee57c | ||
|
|
cb0ec7f47d | ||
|
|
b41e3df62d | ||
|
|
9fe70242a6 | ||
|
|
9ff97c8e4c | ||
|
|
cdcd07d8e5 | ||
|
|
5c13bfd8b4 | ||
|
|
4123a6a59c | ||
|
|
7e0cdffd7d | ||
|
|
fbd759f003 | ||
|
|
df02512101 | ||
|
|
a8147a3535 | ||
|
|
ba729d6623 | ||
|
|
9c6c034e6f | ||
|
|
e1c529531c | ||
|
|
8d426a4dd1 | ||
|
|
3b93fa2606 | ||
|
|
62e9df3879 | ||
|
|
bed946723f | ||
|
|
eb2dc899f2 | ||
|
|
8b8c147a1c | ||
|
|
b82d4f4e6e | ||
|
|
86c8752549 | ||
|
|
b5c37d3724 | ||
|
|
767532e2ea | ||
|
|
79aff0aa3d | ||
|
|
c98b11fa44 | ||
|
|
95fdc98cdc | ||
|
|
b458800218 | ||
|
|
fa3213d309 | ||
|
|
8f3526b357 | ||
|
|
d3b6d38f39 | ||
|
|
ca207b14dc | ||
|
|
38164c6439 | ||
|
|
5dc79ef2e6 | ||
|
|
a29c0873e3 | ||
|
|
0e8500103a | ||
|
|
be4e9e76fa | ||
|
|
6be4c04bea | ||
|
|
653936db83 | ||
|
|
514303e28e | ||
|
|
78361b8cde | ||
|
|
69c0b76bb9 | ||
|
|
903e30b4d1 | ||
|
|
7242ab4108 | ||
|
|
d25ff94370 | ||
|
|
f40c50f1bc | ||
|
|
e1ae777695 | ||
|
|
dc080e389e | ||
|
|
78eeac7a4f | ||
|
|
5933096d07 | ||
|
|
b3de329869 | ||
|
|
877d69e651 | ||
|
|
687999963b | ||
|
|
92fb65fe68 | ||
|
|
42754367de | ||
|
|
4445618696 | ||
|
|
77092f1ae4 | ||
|
|
d1ab03e7c0 | ||
|
|
3f3f5e7558 | ||
|
|
315e9dbb2e | ||
|
|
ca26be400d | ||
|
|
0f9b98f084 | ||
|
|
a3dcb89d6e | ||
|
|
febdcfe063 | ||
|
|
0491f00fa6 | ||
|
|
c2f04d7b32 | ||
|
|
a60a791132 | ||
|
|
94fb72d79f | ||
|
|
da562f36d4 | ||
|
|
487f5fb399 | ||
|
|
52548a02a1 | ||
|
|
14d84237e7 | ||
|
|
2bce6cb8ea | ||
|
|
aeedf41639 | ||
|
|
447b3294a9 | ||
|
|
1ecfa85f54 | ||
|
|
14a332f2cb | ||
|
|
8302fb0b10 | ||
|
|
147e1b34a7 | ||
|
|
c0bf66a4e8 | ||
|
|
bb58d34367 | ||
|
|
397bac4fbc | ||
|
|
734d06d7b5 | ||
|
|
6fce073bcb | ||
|
|
76f01e1162 | ||
|
|
449caae176 | ||
|
|
46f2f0b583 | ||
|
|
b00027d107 | ||
|
|
da2ea29e67 | ||
|
|
e80d159fbb | ||
|
|
9806253b4a | ||
|
|
6bb0506f37 | ||
|
|
d7bec334e5 | ||
|
|
7c7ece465c | ||
|
|
9bcfc55fb8 | ||
|
|
fb3129af24 | ||
|
|
85e97677d8 | ||
|
|
6901219b95 |
@@ -0,0 +1,278 @@
|
||||
---
|
||||
schema: hl-changelog/v2
|
||||
ticket: "8739"
|
||||
title: "小程序团期下单撞上改期联动时最多等 5 秒,超时返回 100503,不再误报请勿重复提交"
|
||||
consumer: mp
|
||||
author: wx(GIT)
|
||||
change_type: 修改接口
|
||||
backend_status: deployed
|
||||
gateway_status: not_required
|
||||
frontend_status: pending
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-03"
|
||||
base: dev-v3
|
||||
---
|
||||
|
||||
# 小程序团期下单撞上改期联动时最多等 5 秒,超时返回 100503,不再误报请勿重复提交
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 场景:`POST /mp/order/create` 带 `groupBatchId` 下团期单,而管理端此刻正在给该团期「改出发日」并联动订单(#8666 引入的联动)。
|
||||
- 下单会先等联动结束,最多等 5 秒。
|
||||
- 5 秒内联动结束:按改后的日期正常成单,响应比平时慢几秒。
|
||||
- 5 秒后联动仍未结束:返回 `100503`「资源被占用,请稍后重试」,不成单,可以直接重提。
|
||||
- 改前(#8666 上线后、本次之前),同一场景下小程序约 3.5 秒就收到 `100502`「请勿重复提交订单」。测试服实测:
|
||||
- 联动在 5 秒内结束时,后台其实**已经成单**;
|
||||
- 联动占用超过 5 秒时,**没有成单**。
|
||||
|
||||
也就是说,这条路径上的 `100502` 既可能是「成了」,也可能是「没成」。本次起这条路径不再返回 `100502`。
|
||||
- 小程序服务等订单服务的上限由 2 秒放宽到 10 秒,超时后也不再自动重发下单请求。
|
||||
- 超过 10 秒仍无结果时,返回 `500`「服务暂时不可用,请稍后重试」。
|
||||
- 此时订单服务端的那次创单不会因此中止,订单**可能已经生成**。
|
||||
- 不带 `groupBatchId` 的下单不等联动、不会多等。10 秒上限和「不自动重发」对它同样适用。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 创建订单 | POST | `/mp/order/create` | 错误响应语义变更 | 团期下单撞上改期联动时最多等 5s,超时返 100503,该路径不再返 100502;服务端超过 10s 返 500,不再自动重发 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 创建订单 `POST /mp/order/create`
|
||||
|
||||
**VO**: `MpCreateOrderRequest → MpOrderDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
用户在产品详情页选好档位、人数并填好联系人后提交下单。
|
||||
|
||||
- 本次只改变下单失败和变慢时返回什么。
|
||||
- 对带 `groupBatchId` 的团期单影响最大:该团期正被管理端改出发日时,下单会先等联动结束。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| productId | Body | String | 是 | 不能为空;必须是数字字符串,否则返回 600002「产品ID格式错误」 | 产品 ID(对外收字符串,防止 JS 精度丢失) |
|
||||
| groupBatchId | Body | String | 否 | GROUP 产品传;null 或空串视为未指定;非数字返回 600003「团期ID格式错误」;≤0 视为未指定 | 团期 ID。有效时下单与该团期的改期联动互斥 |
|
||||
| departureDate | Body | LocalDate | 否 | `yyyy-MM-dd` | 出发日期 |
|
||||
| adultCount | Body | Integer | 否 | ≥1,缺省 1 | 成人数 |
|
||||
| childCount | Body | Integer | 否 | ≥0,缺省 0 | 儿童数 |
|
||||
| youngChildCount | Body | Integer | 否 | ≥0,缺省 0 | 小童数 |
|
||||
| babyCount | Body | Integer | 否 | ≥0,缺省 0 | 幼童数 |
|
||||
| childNeedBed | Body | Boolean | 否 | 缺省 false | 儿童是否需要床位 |
|
||||
| tierSeq | Body | Integer | 是 | ≥1 | 档位序号 |
|
||||
| roomCount | Body | Integer | 否 | 传则 ≥1 | 房间数 |
|
||||
| sharerOpenid | Body | String | 否 | ≤64;可传空串;非空时只能含字母、数字、下划线、短横线 | 分享人 OpenID |
|
||||
| customizerId | Body | Long | 否 | ≥1 | 分享人定制师 ID |
|
||||
| contactName | Body | String | 是 | 非空白,≤50 | 联系人姓名 |
|
||||
| contactPhone | Body | String | 是 | 非空白,≤20 | 联系人电话 |
|
||||
| remark | Body | String | 否 | ≤500 | 订单备注 |
|
||||
|
||||
本次请求字段零变更。上表为完整字段表,与改前一致。
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| orderId | String | 订单 ID。后端为 Long,超出 JS 安全整数范围时序列化为字符串,一律按字符串处理 |
|
||||
| orderNo | String | 订单编号,格式为 `HL` + 年月日时分秒 + 3 位毫秒 |
|
||||
| groupCode | String | 4 位团号,订金支付成功时生成;未生成时该字段不出现 |
|
||||
| groupBatchId | String | 团期 ID(序列化同 orderId),只有团期订单才出现 |
|
||||
| productId | String | 产品 ID(序列化同 orderId) |
|
||||
| departureDate | LocalDate | 出发日期 |
|
||||
| returnDate | LocalDate | 返程日期 |
|
||||
|
||||
- 响应对象带 `@JsonInclude(NON_NULL)`:值为空的字段(如订金支付前的 `groupCode`)直接不出现,不会以 `null` 返回。前端按「字段缺失」判空。
|
||||
- 本次响应字段零变更。出参与 `GET /mp/order/{orderId}` 详情接口同构;上表只列标识字段,完整字段见既有详情接口文档。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"productId": "1900123456789000001",
|
||||
"groupBatchId": "1900123456789000002",
|
||||
"departureDate": "2027-01-27",
|
||||
"adultCount": 2,
|
||||
"childCount": 1,
|
||||
"tierSeq": 1,
|
||||
"contactName": "林晓梅",
|
||||
"contactPhone": "13900001234",
|
||||
"remark": "老人同行,希望安排低楼层"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"orderId": "1900123456789000003",
|
||||
"orderNo": "HL20270127143025001",
|
||||
"groupBatchId": "1900123456789000002",
|
||||
"productId": "1900123456789000001",
|
||||
"departureDate": "2027-01-27",
|
||||
"returnDate": "2027-02-01"
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口没有空数据形态。
|
||||
|
||||
- 订单服务等待超过 10 秒,或暂时不可达时,返回下方错误响应里的 5xx(文案「服务暂时不可用,请稍后重试」),不会返回半成品数据。
|
||||
- 上述错误响应的 HTTP 状态码都是 200,结果看 `code`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
团期下单撞上改期联动,等满 5 秒联动仍未结束(不成单,可以直接重提):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100503,
|
||||
"message": "资源被占用,请稍后重试",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
订单服务超过 10 秒没给出结果(订单可能已经生成,先查订单列表再决定是否重提):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"message": "服务暂时不可用,请稍后重试",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
真的重复提交(上一次下单请求已被受理或仍在处理,不要自动重提):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100502,
|
||||
"message": "请勿重复提交订单",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **什么时候会等**:只有 `groupBatchId` 解析为正数时,下单才与该团期的改期联动互斥。不带团期的下单不等。
|
||||
- **等多久**:最多 5 秒,后端常量,前端不可调整。测试服实测一次改期联动占用 41–688 毫秒(12 次),所以多数情况是「慢一点但成单」,`100503` 只在联动占用超过 5 秒时出现。
|
||||
- **`100503`**:不成单,因为等锁失败时创单业务还没开始执行。可以直接重提,不需要任何清理。
|
||||
- 重提不会撞 `100502`。小程序层的防重窗口 5 秒,`100503` 返回时已过;订单服务层的防重键在 `100503` 时随失败释放。
|
||||
- **`500` 等 5xx(等待超过 10 秒或订单服务不可达)**:小程序服务不再自动重发,但订单服务端已开始的那次创单不会中止,**可能已经成单**。
|
||||
- 此时直接重提可能产生两张订单:订单服务层同用户同产品的防重窗口是 10 秒,从原请求开始计时,到 `500` 返回时已基本过期。(按代码机制推导,测试服未构造过超过 10 秒的场景。)
|
||||
- 建议提示「网络繁忙,请到我的订单查看」,先刷新订单列表,确认没有新订单再让用户重提。
|
||||
- **`100502` 现在只表示真的重复提交**,有两层窗口:
|
||||
- 小程序层:同一用户 5 秒内第二次下单,不分产品。成功下单后 5 秒内再下也算。
|
||||
- 订单服务层:同一用户同一产品 10 秒内第二次下单。
|
||||
|
||||
收到时说明前一次请求已被受理或仍在处理,引导用户到订单列表查看,不要自动重提。
|
||||
- **`100501`「下单过于频繁,请稍后再试」**:订单服务层按用户限流,每个用户 60 秒内最多 5 次下单请求。既有行为,未变。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 按 `code` 区分错误,不要按 `message` 判断。`100502`、`100503` 和 5xx 的文案相近,处理方式完全不同。
|
||||
2. 下单请求的前端超时不要短于 15 秒:服务端最长 10 秒给出结果,另有网关与网络开销。前端先放弃时,用户看不到 `100503`,后台却可能已成单。
|
||||
3. 提交期间禁用提交按钮,直到收到响应。团期下单撞上改期联动时,正常响应也可能慢约 5 秒。
|
||||
4. 收到 `100503` 可以直接重提原请求。
|
||||
5. 收到 5xx(`code` ≥ 500)先刷新订单列表,确认没成单再重提。
|
||||
6. 收到 `100502` 不要自动重提,引导用户到订单列表查看。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 结果 | 数据库写入 |
|
||||
|------|------------|
|
||||
| `100503` | 零写入:等锁失败发生在创单业务开始之前 |
|
||||
| `100502` / `100501` | 零写入:在进入创单业务前就被拦截 |
|
||||
| 5xx(等待超过 10 秒) | 订单服务端那次创单照常提交或失败,与小程序有没有收到结果无关 |
|
||||
| 成功 | 写入与改前完全一致;本次不涉及任何表结构变更 |
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 两个时限(等锁 5 秒、小程序服务调订单服务超时 10 秒)都是服务端固定值,请求参数无法调整。
|
||||
- 本接口与管理端创单复用同一套创单内核,锁判断完全一致。
|
||||
- 管理端用 `productBatchId` 数字。
|
||||
- 小程序用 `groupBatchId` 字符串,两者是同一个 ID。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本接口请求、响应字段均**零变更**,变的只是失败和变慢时的返回。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前(#8666 上线后、本次之前) | 改后 |
|
||||
|------|------|------|
|
||||
| 团期下单,没撞上改期联动 | 正常成单 | 正常成单,无差异 |
|
||||
| 团期下单,撞上改期联动且联动在 5 秒内结束 | 约 3.5 秒返回 `100502`,后台其实已成单 | 等联动结束后正常成单,响应最多慢约 5 秒 |
|
||||
| 团期下单,联动占用超过 5 秒 | 约 3.5 秒返回 `100502`,没有成单 | 约 5 秒返回 `100503`,没有成单,可直接重提 |
|
||||
| 任意下单,订单服务处理超过 2 秒 | 小程序服务约 2 秒超时后自动重发同一请求,用户可能收到 `100502` 而后台已成单 | 等到 10 秒;超过 10 秒返回 `500`,不重发 |
|
||||
|
||||
在 #8666 之前,下单完全不与改期联动互斥。撞上改期时可能按旧出发日成单,且日期与团期永久错位、无人察觉。#8666 堵住了这个窗口,本次修正的是它在小程序链路上的错误码表现。
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。请求、响应字段零变更,正常成单路径不变。
|
||||
- **前端是否必须同步上线**:否。未识别 `100503` 时展示原始 `message` 不会白屏。但若前端下单请求的超时短于 15 秒,需要调整,见第四节第 2 条。
|
||||
- **前端 workaround 清理点**:无。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 小程序其他订单接口(列表、详情、取消、改单等)调订单服务的等待上限与重试策略**未变**。
|
||||
- 请求、响应字段零变更。
|
||||
- 网关路由零变更,无数据库结构变更。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-10-03 测试服部署 hl-mp-service(dev-v3 `f4e45c2f1`)后实测。做法:用测试 C 端用户直连小程序服务,在自建团期上人为占住改期联动锁。
|
||||
|
||||
| 场景 | 响应 | 耗时 | 成单 | 订单服务收到的请求 |
|
||||
|------|------|------|------|------|
|
||||
| 联动占用约 3.5 秒 | 成功,`data` 为完整订单详情 | 约 3.5 秒 | 1 单 | 1 次 |
|
||||
| 联动占用约 6 秒 | `100503`「资源被占用,请稍后重试」 | 约 5 秒 | 0 | 1 次 |
|
||||
| 上一行锁释放后间隔 ≥5 秒重提 | 成功 | 正常 | 1 单 | 1 次 |
|
||||
| 收到 `100503` 后 0.3 秒内立即重提(锁剩余不足 1 秒) | 未返回 `100502`,等联动结束后成功 | 未单独计时 | 1 单 | 2 次,即两次真实提交,无自动重发 |
|
||||
| 不占锁的常规下单 | 成功 | 正常 | 1 单 | 1 次 |
|
||||
|
||||
- 常规下单的响应 `data` 与 `GET /mp/order/{orderId}` 的 `data` 做了字段集比对:61 个字段路径,零差异,二者同为 `MpOrderDetailVO`。
|
||||
- 改前基线是部署前用同一夹具、同一方法测的:
|
||||
- 联动占用约 3.5 秒:约 3.5 秒返回 `100502`,但后台已成单,订单服务收到 2 次。
|
||||
- 联动占用约 6 秒:同样返回 `100502`,未成单。
|
||||
- 本轮未构造订单服务超过 10 秒才返回的场景,所以 `500` 之后可能已成单这一点仍是按代码推导,见业务边界。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 管理端同源改动:`changelogs-v2/2026-10/03_8666_产品班期改出发日联动团期与子单日期并新增改期拒绝码-修改接口-管理后台.md`(#8666)。改期联动本身的事件与拒绝码以该条为准。
|
||||
- 内部转发路径:`POST /mp/order/create`(hl-mp-service)→ `POST /v3/internal/mp/order/create`(order-v3)。`/v3/internal/**` 只供服务间调用,不经网关,前端不可达。
|
||||
- `100503` 是全仓统一的锁竞争可重试错误码。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8739](https://git.1814.love/wx/HL/issues/8739)(本次)、[#8666](https://git.1814.love/wx/HL/issues/8666)(引入改期联动锁)
|
||||
- **PR**: [#8740](https://git.1814.love/wx/HL/pulls/8740)、[#8738](https://git.1814.love/wx/HL/pulls/8738)
|
||||
- **Merge commit**: `f4e45c2f147eb5e0deec4dbd920e6c82c496ec7b`(#8740)、`fad5d7814b2d1fb940d740457dbed754e46d03f9`(#8738)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5186"
|
||||
title: "排车中订单恢复派车派人入口并补齐改派上下文"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "158f728ce9110ca6a9e0185c137969f84b522c6f"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-23 交付(恢复排车中订单派车派人入口,158f728ce 起含 2f4d278a6/54c007856 后续修正),证据为交付 commit 正文标注 #5186"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-23T16:10:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5194"
|
||||
title: "待确认详情补全多车多司机与按槽位改派"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "bfb4e3cde018d14b2b5dedaacacb83128cc7714d"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-23 交付(多槽位改派与司机确认,bfb4e3cde),证据为交付 commit 正文标注 #5194"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-23T18:02:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5199"
|
||||
title: "车务首页订单去重与字段补全"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "3c4f8619bbc569d72e73cedba9a74b2cefc67402"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-24 交付(车务首页订单聚合契约对齐,3c4f8619b),证据为交付 commit 正文标注 #5199"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-24T09:46:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5200"
|
||||
title: "用车需求驳回历史与重新提交"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "d7afc25146ed1bceea3e3722c521babd85c1345e"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-24 交付(用车需求驳回历史与重新提交,d7afc2514),证据为交付 commit 正文标注 #5200"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-24T11:05:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5205"
|
||||
title: "车务首页汇总与看板人员类型展示"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "78cbb1565b6555788607b34f591f0291a87af96d"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-24 交付(车务汇总与人员类型展示对齐,78cbb1565),证据为交付 commit 正文标注 #5205"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-24T10:45:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5132"
|
||||
title: "车务派单详情补全产品、行程节点、出行人与大交通"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "524cfb21d24074e8e22c5ca7a89cbbd693f59abb"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(派单详情新增字段展示/出行人类型区分,524cfb21d 起 3 个 commit),证据为交付 commit 正文标注 #5132"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T10:35:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5139"
|
||||
title: "车务派单候选筛选、分页与任意车辆选择"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "a0e8a6ef15a5a1983ddab3ca2958444e7971ae77"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(派单候选筛选与任意车辆选择接入,a0e8a6ef1),证据为交付 commit 正文标注 #5139"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T13:25:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5141"
|
||||
title: "车务派单司机保险类型与行程保障状态"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "29ab0d6562413be6ab715d8b1e1eb2d4521a9eff"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(司机保险保障状态与线上投保接入,29ab0d656),证据为交付 commit 正文标注 #5141"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T14:07:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5145"
|
||||
title: "选车后常驻司机默认配对"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "b9994da9308ae84d361e79e86f74200813ec06cc"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(选车后常驻司机默认配对接入,b9994da93),证据为交付 commit 正文标注 #5145"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T15:30:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5146"
|
||||
title: "司机待确认通知与可选确认凭证"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "089e55ffb6f4336c19ffd2ed2eade131bdb35bf7"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(司机确认登记与可选凭证接入,089e55ffb),证据为交付 commit 正文标注 #5146"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T15:20:00+08:00"
|
||||
---
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
schema: "hl-changelog/v1"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5149"
|
||||
title: "派单详情补充团号与产品类型"
|
||||
consumer: "admin"
|
||||
backend: "verified"
|
||||
gateway: "verified"
|
||||
frontend: "pending"
|
||||
change_type: "修改接口"
|
||||
backend_status: "verified"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "ccbdd0bd1b7c903846c8ae9e4e9e15d80266b9a4"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-09"
|
||||
status_note: "7 月历史补标:已于 2026-07-22 交付(派单详情补充团号与产品类型,ccbdd0bd1),证据为交付 commit 正文标注 #5149"
|
||||
updated_at: "2026-10-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-07-22T16:22:00+08:00"
|
||||
---
|
||||
|
||||
@@ -9,11 +9,11 @@ backend_status: "merged"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "1c555f544f449b27e6044c6e70e520252f2825be"
|
||||
frontend_ref: "9a28a7a8dc12a0c041c616ac3b1000c53847b8a6"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-27"
|
||||
status_note: "Epic #8361 五 PR(#8366/#8367/#8368/#8400/#8406)已全部合并 dev-v3,尚未部署测试服、网关未实测。前端需动三处:①应收台账行按 rowType 分流(GROUP_BATCH 行 id 是团期批次 ID,按订单详情跳转必 404);②报账单列表按 bizType 分流展示(GROUP_BATCH 行展示 bizNo 团号);③新增团期核单页(finalize/confirm/reports 三端点 + settlementStatus 新增 SETTLED 态)。【2026-09-27 mmg】①应收台账 rowType 分流 + ②报账单 bizType 分流已交付(276de5f1):GROUP_BATCH 行跳团期详情不跳订单详情、详情「团号」取 bizNo(行快照兜底)、搜索兼容团号文案对齐;receivable/reimburse 两 spec 18 例全绿。③团期核单页 defer——原型快照 Sep 17 早于本 Epic 无其设计(prototype fidelity 不可无原型建财务新页),后端 §11 明说可随后迭代不阻塞,待原型刷新后续做,故 frontend_status 保持 pending。【2026-09-27 mmg ③】团期核单页已交付(1c555f54):用户拍板不算财务域不等原型,详情页「核单结算」后新增「团期核单」tab——reports/group 空壳范式先判 finalized、finalize 节点门 TRIP_FINISHED/REVIEWING 置灰内联、confirm 仅「FINALIZED+待复核」、SETTLED 无人工结清口、金额/ID 全字符串 null 显 —;新建 api/order-v3/groupSettlement.js+GroupSettlementTab.vue,API spec 4 例+组件 spec 10 例+详情页 spec 共 28 例全绿,checkpoint 全绿。Epic ①②③全部闭环,翻 verified。"
|
||||
updated_at: "2026-09-27"
|
||||
status_note: "Epic #8361 五 PR(#8366/#8367/#8368/#8400/#8406)已全部合并 dev-v3,尚未部署测试服、网关未实测。前端需动三处:①应收台账行按 rowType 分流(GROUP_BATCH 行 id 是团期批次 ID,按订单详情跳转必 404);②报账单列表按 bizType 分流展示(GROUP_BATCH 行展示 bizNo 团号);③新增团期核单页(finalize/confirm/reports 三端点 + settlementStatus 新增 SETTLED 态)。【2026-09-27 mmg】①应收台账 rowType 分流 + ②报账单 bizType 分流已交付(276de5f1):GROUP_BATCH 行跳团期详情不跳订单详情、详情「团号」取 bizNo(行快照兜底)、搜索兼容团号文案对齐;receivable/reimburse 两 spec 18 例全绿。③团期核单页 defer——原型快照 Sep 17 早于本 Epic 无其设计(prototype fidelity 不可无原型建财务新页),后端 §11 明说可随后迭代不阻塞,待原型刷新后续做,故 frontend_status 保持 pending。【2026-09-27 mmg ③】团期核单页已交付(1c555f54):用户拍板不算财务域不等原型,详情页「核单结算」后新增「团期核单」tab——reports/group 空壳范式先判 finalized、finalize 节点门 TRIP_FINISHED/REVIEWING 置灰内联、confirm 仅「FINALIZED+待复核」、SETTLED 无人工结清口、金额/ID 全字符串 null 显 —;新建 api/order-v3/groupSettlement.js+GroupSettlementTab.vue,API spec 4 例+组件 spec 10 例+详情页 spec 共 28 例全绿,checkpoint 全绿。Epic ①②③全部闭环,翻 verified。【2026-09-28 mmg ③迁】用户改拍「团期核单归财务域(免原型对齐),finance/settlement 团期产品 tab 即预留位」:先把 order-v2/batch/detail 的「团期核单」Tab 下线(081966ff),再落地财务域(9a28a7a8)——团期产品 tab 数据源由空调常规 /v3/admin/order-settlement/tasks(仅常规 CORE 无团期批次,即"接口不对"根因)改走 GB-ADM-001 /v3/admin/order/group-batch(订单域接口财务只读,pageNo/departFrom/departTo/opsStage 七桶),行弹层挂 GroupSettlementPanel(原 GroupSettlementTab 迁入)读 reports/group+finalize/confirm;应收/已收权威字段直显不反算,未建团行(groupBatchId=null)不放入口。恢复 api/order-v3/groupSettlement.js,新增 index.spec 锁双 tab 数据源+参数名+列 render+弹层入参,三 spec 22 例全绿,checkpoint 全绿。ref 由 1c555f54 改指 9a28a7a8。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "yst"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "577683cf59a5ce3bf74bc48e6682be745de638c1"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "前端已交付(团期子订单隐藏订单级预支按钮,589558 拦截器透 message 兜底),详见 hl-admin v2.1 提交 577683cf。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "6b0198595a1debda7e35b86954faa8d5dd1823ab"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-28"
|
||||
status_note: "前端已交付(车务首页待确认接送变更状态卡),详见 hl-admin v2.1 提交 6b019859。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "d7e932ac5b0291cc260f3c04fb74eedc653e892a"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-28"
|
||||
status_note: "前端已交付(派单看板订单归属页签+团期配车模块删除),详见 hl-admin v2.1 提交 d7e932ac。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8475(bf65803b7)与 #8476(623c932bd)已合入 dev-v3,TEST 的 hl-resource-service 与 hl-order-service-v3 均运行 dev-v3 623c932bd;经网关 api.test.1814.love 用真实 admin token 实测 AC-1、3、6~13、15、16 主路径全部通过,金额字段字符串形态已复验。前端需接:选人弹窗展示等级、服务区域、擅长、基础日薪与占用标签,占用明细弹层,保存弹窗编辑服务日期,服务人员管理基础日薪回显与列表列,导摄页签与看板芯片弹层去掉逐户人员表(见第四节交接清单),故 frontend_status 记 pending。"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "838b80d69dd1ff5a96e6a95e886d979dac5f452c"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-28"
|
||||
status_note: "PR #8475(bf65803b7)与 #8476(623c932bd)已合入 dev-v3,TEST 的 hl-resource-service 与 hl-order-service-v3 均运行 dev-v3 623c932bd;经网关 api.test.1814.love 用真实 admin token 实测 AC-1、3、6~13、15、16 主路径全部通过,金额字段字符串形态已复验。前端需接:选人弹窗展示等级、服务区域、擅长、基础日薪与占用标签,占用明细弹层,保存弹窗编辑服务日期,服务人员管理基础日薪回显与列表列,导摄页签与看板芯片弹层去掉逐户人员表(见第四节交接清单),故 frontend_status 记 pending。前端已交付(人员画像/占用标签/服务日期三态+导摄芯片去逐户人员),详见 hl-admin v2.1 提交 838b80d6。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8489 已合入 dev-v3(2c87b0618),TEST 的 hl-order-service-v3 运行 dev-v3 2c87b0618;经网关 api.test.1814.love 用真实 admin token 实测 AC-1~AC-6、AC-9 通过(验收脚本 22/22)。5 个接口结构、字段名、枚举值集不变,颜色渲染逻辑不用改;导/摄计数单位由户变为名册位,看板悬停 0/0 与导/摄页签、芯片弹层的按户文案需前端核对(见第四节交接清单),故 frontend_status 记 pending。"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "17afce4236ba8157f66f0aa0ffb181e9da60ae35"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "PR #8489 已合入 dev-v3(2c87b0618),TEST 的 hl-order-service-v3 运行 dev-v3 2c87b0618;经网关 api.test.1814.love 用真实 admin token 实测 AC-1~AC-6、AC-9 通过(验收脚本 22/22)。5 个接口结构、字段名、枚举值集不变,颜色渲染逻辑不用改;导/摄计数单位由户变为名册位,看板悬停 0/0 与导/摄页签、芯片弹层的按户文案需前端核对(见第四节交接清单),故 frontend_status 记 pending。前端已交付(导/摄汇总行随 #8468 已删;chipStatsTip total=0 改显「无需」),详见 hl-admin v2.1 提交 17afce42。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,7 +7,7 @@ author: "wx(GIT)"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8480 已合入 dev-v3(49777861b),TEST 的 hl-order-service-v3 运行 dev-v3 49777861b;经网关 api.test.1814.love 用真实 admin token 实测招募、配置、确认、出行三态、核单、结算、已流团各状态,以及整团免车与无需导摄,全部通过。前端需接:详情页头用 progressStepper 画分叉进度条替换直线步骤条,页头加「未设置主报账人」标签,去掉「团期尚不满足确认条件」横幅、查看需求「仍有子订单需求缺失」提示块、总览「资源与单据」卡片(见第四节前端交接清单),故 frontend_status 记 pending。"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "59cc3bf27685735545025d88981a6d066bc9d7a6"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-28"
|
||||
status_note: "PR #8480 已合入 dev-v3(49777861b),TEST 的 hl-order-service-v3 运行 dev-v3 49777861b;经网关 api.test.1814.love 用真实 admin token 实测招募、配置、确认、出行三态、核单、结算、已流团各状态,以及整团免车与无需导摄,全部通过。前端需接:详情页头用 progressStepper 画分叉进度条替换直线步骤条,页头加「未设置主报账人」标签,去掉「团期尚不满足确认条件」横幅、查看需求「仍有子订单需求缺失」提示块、总览「资源与单据」卡片(见第四节前端交接清单),故 frontend_status 记 pending。前端已交付(团期详情分叉进度条替代确认缺项横幅),详见 hl-admin v2.1 提交 59cc3bf2。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8487 已合入 dev-v3(22fce5985),TEST 的 hl-order-service-v3 运行 dev-v3 22fce5985(面板任务 45bfb475,两实例经运行字节探针确认)。经网关 api.test.1814.love 用 admin token 实测新增 / 改数量 / 删除后物资确认失效、改成原值不失效、失效后团期确认 589556、重新确认后放行、团期确认后三写口 589598、确认物资流水带清单快照、进入待出发记 BATCH_DEPARTURE_ADMIT,全部通过。前端需接:物资面板改数量 / 删除后要刷新团期详情(现在只有确认物资后才刷新),时间线识别三个新事件码(见第四节),故 frontend_status 记 pending。"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "93a784cd0b1b69c00ff112675b44c083496a9593"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "PR #8487 已合入 dev-v3(22fce5985),TEST 的 hl-order-service-v3 运行 dev-v3 22fce5985(面板任务 45bfb475,两实例经运行字节探针确认)。经网关 api.test.1814.love 用 admin token 实测新增 / 改数量 / 删除后物资确认失效、改成原值不失效、失效后团期确认 589556、重新确认后放行、团期确认后三写口 589598、确认物资流水带清单快照、进入待出发记 BATCH_DEPARTURE_ADMIT,全部通过。前端需接:物资面板改数量 / 删除后要刷新团期详情(现在只有确认物资后才刷新),时间线识别三个新事件码(见第四节),故 frontend_status 记 pending。前端已交付(物资三能力成功补 emit changed+配置节点重检预检),详见 hl-admin v2.1 提交 93a784cd。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "wx(GIT)"
|
||||
change_type: "前端优化"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
frontend_ref: "df75f2510f9599d95ed999d31e5cff64fc1626bd"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-28"
|
||||
status_note: "前端已交付(出团管理删导出+团期管理员隐藏新增子订单),详见 hl-admin v2.1 提交 df75f251。"
|
||||
updated_at: "2026-09-28"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -7,12 +7,12 @@ author: "jw(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "2026-09-28 jw 在 TEST(web.test.1814.love)从团期列表「进入团期」打开 jw测试产品 第1期(状态:配置),点浏览器返回,回到列表页即弹 400「参数 groupBatchId 格式错误,请检查后重试」。根因在 hl-ui:#8410(c37fd8ad)在团期详情页新增的 watch([canConfirmBatch, groupBatchId]) 没有照同页其它监听器写 isDetailActive 与空 id 两道守卫;离开详情页时路由先变、onDeactivated 后执行,中间这一拍 groupBatchId 塌缩为空串而页面仍算激活,该监听器随即调 loadConfirmCheck(),拼出 GET /v3/admin/order/group-batch//confirm-check,连续斜杠被合并后落进团期详情接口的 groupBatchId 位,confirm-check 转 Long 失败返 400。后端接口与数据均正常,契约不变。修法:该监听器补两道守卫(守卫放在清空 confirmCheck 之前),loadConfirmCheck 入口补空 id 兜底;与 28_8478 前端交接清单(保留 loadConfirmCheck)不冲突,可同批改。"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "41e42d0c6c8f323e65add238596f59fd7b433804"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "2026-09-28 jw 在 TEST(web.test.1814.love)从团期列表「进入团期」打开 jw测试产品 第1期(状态:配置),点浏览器返回,回到列表页即弹 400「参数 groupBatchId 格式错误,请检查后重试」。根因在 hl-ui:#8410(c37fd8ad)在团期详情页新增的 watch([canConfirmBatch, groupBatchId]) 没有照同页其它监听器写 isDetailActive 与空 id 两道守卫;离开详情页时路由先变、onDeactivated 后执行,中间这一拍 groupBatchId 塌缩为空串而页面仍算激活,该监听器随即调 loadConfirmCheck(),拼出 GET /v3/admin/order/group-batch//confirm-check,连续斜杠被合并后落进团期详情接口的 groupBatchId 位,confirm-check 转 Long 失败返 400。后端接口与数据均正常,契约不变。修法:该监听器补两道守卫(守卫放在清空 confirmCheck 之前),loadConfirmCheck 入口补空 id 兜底;与 28_8478 前端交接清单(保留 loadConfirmCheck)不冲突,可同批改。前端已交付(预检 watch 补 isDetailActive+空 id 双守卫+loadConfirmCheck 入口兜底),详见 hl-admin v2.1 提交 41e42d0c。"
|
||||
updated_at: "2026-09-28"
|
||||
base: dev-v3
|
||||
---
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8497"
|
||||
title: "团期团号改为 T+出发年份后两位-4位混淆号,新增存量换号的两个内部端点(产品回填 / order-v3 同步)"
|
||||
consumer: "internal"
|
||||
author: "jw(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "两个新增接口都是 internal(网关不暴露、X-Internal-Token 校验),只给运维一次性执行存量换号用,前端无需对接。TEST 已于 2026-09-29 执行完毕:297 个在册班期换号、13 个团期与 61 行应付快照同步,重跑零变更。附带说明:团期团号 batchNo 的取值格式由 28 位旧号改为形如 T26-8867 的新号,所有返回 batchNo 的接口字段名与类型不变,仅取值变短,前端列宽可收窄。"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期:团号改为 T26-8867 形态,新增存量换号的两个内部端点
|
||||
|
||||
> **服务**: hl-product-service-v2(端口 8083)、hl-order-service-v3(端口 8086,含 finance 模块)
|
||||
> **PR**: #8514(新规则)、#8532(存量换号)
|
||||
> **Issue**: #8497
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 团期团号 batchNo 的取值格式;两个一次性内部端点
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 团期团号(产品侧 `group_tour_batch.batch_no`,建团时原样抄进订单侧团期)由 28 位旧号 `Q + yyyyMMdd + 19 位雪花 ID`(如 `Q202611102104491287904428034`)改为 **`T` + 出发年份后两位 + `-` + 4 位混淆号**(如 `T26-8867`;一年超过 9999 个后为 5 位)。
|
||||
- 所有返回 `batchNo` 的既有接口(团期列表 / 详情、房务看板、车务派单、财务应收台账团行的 `orderNo` / `teamNo` 等)**字段名、类型、位置都不变**,只是取值变成新格式;存量在册班期已统一换成新号。
|
||||
- 团期子订单自己的订单号(`HL` + 年月日时分秒 + 3 位毫秒)与团号(`yy-NNNN`,如 `26-3821`)**不变**。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
jw 2026-09-29 定:团号要能念、能记,参照订单团号 `26-8867` 的思路改为 `T26-8867`(T=团),年份取出发日期。新建班期由 PR-1 起按新规则取号;存量在册班期的旧号由本次新增的两个内部端点一次性换掉,旧号存进产品库 `legacy_batch_no` 列备查(不进任何接口、不做按旧号搜索的兼容)。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期存量团号回填 | POST | `/internal/product/group-tour-batch/batch-no/backfill` | 新增 | 产品服务:在册班期旧号换成新号,dryRun 默认 true |
|
||||
| 2 | 团期团号同步 | POST | `/v3/internal/order/group-batch/batch-no/sync` | 新增 | order-v3:订单侧团期与财务应付快照跟上产品侧新号,dryRun 默认 true |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期存量团号回填 `POST /internal/product/group-tour-batch/batch-no/backfill`
|
||||
|
||||
**VO**: `GroupTourBatchNoBackfillRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
运维在部署后一次性执行:把产品库在册班期里不是新格式的团号逐行换成新号(与新建班期共用同一个取号器、同一张按年序号表),旧号写进 `legacy_batch_no`;班期名等于旧号的一并换成新号;班期名长得像旧号(复制产品抄来的)的改回自身团号。先 dryRun 看数,再 `dryRun=false` 正式执行,紧接着调用接口 2。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| X-Internal-Token | header | string | 是 | 服务间内部令牌 | 缺失或错误返回 403 |
|
||||
| dryRun | query | boolean | 否 | 默认 true | true 只统计不修改;false 正式换号 |
|
||||
| limit | query | integer | 否 | 大于等于 0;不传为不限 | 本轮最多换号行数,只限制换号,不限制改名 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| dryRun | boolean | 是否只统计 |
|
||||
| limit | integer | 本轮换号上限,null 为不限 |
|
||||
| scanned | integer | 扫描的在册班期数 |
|
||||
| legacyCandidates | integer | 在册班期中不是新格式的行数 |
|
||||
| converted | integer | 换号成功行数(dryRun 时为将会换号的行数) |
|
||||
| renamed | integer | 名字改回自身团号的行数 |
|
||||
| skipped | integer | 跳过行数(读后被并发改动 / 已删除,本行已回滚) |
|
||||
| mappingTotal | integer | 换号映射总数 |
|
||||
| mappings | array | 换号映射(最多返回前 N 条):batchId(string)、oldBatchNo、newBatchNo(dryRun 时为 null) |
|
||||
| skippedItems | array | 跳过明细:batchId、phase(CONVERT / RENAME)、reason |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /internal/product/group-tour-batch/batch-no/backfill?dryRun=false HTTP/1.1
|
||||
Host: 192.168.100.236:8083
|
||||
X-Internal-Token: <内部令牌>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"dryRun": false,
|
||||
"limit": null,
|
||||
"scanned": 301,
|
||||
"legacyCandidates": 297,
|
||||
"converted": 297,
|
||||
"renamed": 5,
|
||||
"skipped": 0,
|
||||
"mappingTotal": 297,
|
||||
"mappings": [
|
||||
{ "batchId": "2046126055898431490", "oldBatchNo": "Q202602262046126055885848578", "newBatchNo": "T26-0906" }
|
||||
],
|
||||
"skippedItems": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有需要换号的班期时(例如重跑)返回 `converted=0`、`renamed=0`、`mappings=[]`,HTTP 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": { "dryRun": false, "limit": null, "scanned": 306, "legacyCandidates": 0, "converted": 0, "renamed": 0, "skipped": 0, "mappingTotal": 0, "mappings": [], "skippedItems": [] },
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少或错误的内部令牌:
|
||||
|
||||
```json
|
||||
{ "code": 403, "msg": "内部接口禁止外部访问" }
|
||||
```
|
||||
|
||||
`limit` 为负数:
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "数值超出允许范围,请修改后重试", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只处理在册班期,已删除的班期保留原号。
|
||||
- 每行一个独立事务;读后被并发改动的行回滚并计入 skipped,不影响后续行,序号不留空洞。
|
||||
- 回填过程中同时新建班期不会撞号(共用同一张按年序号表)。
|
||||
- 可重跑,已是新号的行不再处理。
|
||||
|
||||
### 2. 团期团号同步 `POST /v3/internal/order/group-batch/batch-no/sync`
|
||||
|
||||
**VO**: `GroupBatchNoSyncRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
紧接接口 1 执行:逐个团期拉产品侧当前团号(在事务外调用产品服务),与订单侧团期不一致时,在同一事务里回写订单侧团期团号(名字等于旧号时一并改),并把财务应付明细与应付团头里等于旧号的团号换成新号。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| X-Internal-Token | header | string | 是 | 服务间内部令牌 | 缺失或错误返回 403 |
|
||||
| dryRun | query | boolean | 否 | 默认 true | true 只统计不修改;false 正式同步 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| dryRun | boolean | 是否只统计 |
|
||||
| scanned | integer | 扫描的未删除团期数 |
|
||||
| synced | integer | 已同步团期数(dryRun 时为将会同步的数量) |
|
||||
| skipped | integer | 与产品侧已一致而跳过的团期数 |
|
||||
| failedCount | integer | 失败团期数(拉产品侧失败 / 产品侧团号为空 / 事务失败,均已回滚且不中断) |
|
||||
| payableLineRows | integer | 应付明细改号影响行数合计(只作留痕) |
|
||||
| payableTeamRows | integer | 应付团头改号影响行数合计(只作留痕) |
|
||||
| syncedItems | array | 同步明细:groupBatchId(string)、oldBatchNo、newBatchNo、batchNameChanged |
|
||||
| failed | array | 失败明细:groupBatchId、reason |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/internal/order/group-batch/batch-no/sync?dryRun=false HTTP/1.1
|
||||
Host: 192.168.100.236:8086
|
||||
X-Internal-Token: <内部令牌>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"dryRun": false,
|
||||
"scanned": 17,
|
||||
"synced": 13,
|
||||
"skipped": 4,
|
||||
"failedCount": 0,
|
||||
"payableLineRows": 60,
|
||||
"payableTeamRows": 1,
|
||||
"syncedItems": [
|
||||
{ "groupBatchId": "2101506167098511362", "oldBatchNo": "Q202609272101502082564407298", "newBatchNo": "T26-9802", "batchNameChanged": false }
|
||||
],
|
||||
"failed": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
全部团期已与产品侧一致时返回 `synced=0`、`syncedItems=[]`;单个团期拉取产品侧失败时计入 `failedCount` 与 `failed`,其余团期照常处理:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": { "dryRun": false, "scanned": 17, "synced": 0, "skipped": 17, "failedCount": 0, "payableLineRows": 0, "payableTeamRows": 0, "syncedItems": [], "failed": [] },
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少或错误的内部令牌:
|
||||
|
||||
```json
|
||||
{ "code": 403, "msg": "内部接口禁止外部访问" }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 不改团期子订单的订单号与团号。
|
||||
- 同一团期的团期表与应付快照在同一事务内一起改或一起回滚。
|
||||
- 新号若与已有应付团头冲突,该团期回滚并计入 failed,需人工处理。
|
||||
- 可重跑,已一致的团期跳过。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 两个接口只供运维一次性执行,不经网关(网关对 `/internal/**`、`/v3/internal/**` 返回 403),需直连服务端口并带 `X-Internal-Token`。
|
||||
- 执行顺序固定:接口 1 dryRun → 接口 1 正式 → **紧接着**接口 2 dryRun → 接口 2 正式。两者之间有新订单进来不影响结果(下单时不改团号)。
|
||||
- 不传 `dryRun` 等同 dryRun=true,不会误改数据。
|
||||
- 既有接口里的 `batchNo` 字段名、类型不变,取值格式变为新号;调用方不要按 28 位长度或 `Q` 前缀解析团号。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 接口 1:产品库班期的团号换成新号,旧号写入备查列;占用按出发年份计数的团号序号;不改已删除班期。
|
||||
- 接口 2:订单侧团期的团号(及等于旧号的班期名)与财务应付明细、应付团头上的团号改为新号;金额不变;不改订单主表。
|
||||
- 两个接口 dryRun 时只读不写。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 同一序号在不同年份后四位相同(如 `T26-0906`、`T27-0906`),靠年份段区分,属公式特性。
|
||||
- 号一经生成,出发日期改到别的年份也不改号。
|
||||
- 快照还原遇到旧格式的号会重新取号,避免换号后旧号回流。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 团期子订单的订单号(`HL…`)与团号(`yy-NNNN`)。
|
||||
- 所有既有接口的路径、入参、出参字段与类型。
|
||||
- 前端:无需对接这两个内部接口;`batchNo` 列宽可按新格式收窄(可选)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-29 在 TEST 直连服务端口执行(换号前后各存全量快照逐行比对):
|
||||
|
||||
- 接口 1 dryRun:在册 301、旧格式 297、名字像旧号 5,序号表不变;正式:297 换号、5 改名、0 跳过,3.6 秒;执行期间并发新建 5 个班期全部成功且不撞号。
|
||||
- 接口 2 dryRun → 正式:扫描 17、同步 13、跳过 4、失败 0,应付明细改 60 行、应付团头改 1 行。
|
||||
- 重跑两接口零变更;无令牌 403;`limit=-1` 返回 400。
|
||||
- 换号后:在册班期无旧号、`legacy_batch_no` 与原号逐行一致;订单侧团期与产品侧逐行一致;全库团号列普查旧号 0;团期子订单订单号与团号 46 行逐行一致;应付台账按新号 `T26-9802` 查得应付 3120.00,与换号前一致。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #8497(含两轮 TEST 验收证据评论)
|
||||
- PR #8514(新规则)、PR #8532(存量换号)
|
||||
- 团期文档 `docs/group/数据模型.html`「新增事实三」已按新规则收口
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 工单:https://git.1814.love/wx/HL/issues/8497
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8501"
|
||||
title: "应收往来对象分类/应收性质两组下拉字典化,新增 /receipt/options 接口供前端动态读取"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "49a3bb6c5c92e405aee7c4a4ceb275d10b2f6d63"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "应收页「往来对象分类」「应收性质」两组下拉此前前端写死常量、后端无字典无接口,加值须改前端发版。本次纯字典化(不落库不加列):新增 2 个财务字典 + GET /admin/finance/receipt/options 接口,测试环境网关已实测返回两组 ACTIVE 下拉。前端需把两组写死常量改读本接口,码值从中文 value 迁英文码。;前端 2026-09-29 已交付:grep 实证两写死中文常量零消费点(应收新增表单尚未建),删死常量留防回加注释,新增 getReceiptOptions 客户端(英文码 value/ACTIVE 升序/5 分钟缓存/字典失败降级空数组),receipt api spec 3 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:应收往来对象分类 / 应收性质两组下拉字典化 + 新增 /receipt/options(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: #8503
|
||||
**Issue**: #8501
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
财务应收页(应收台账等)有两组下拉:
|
||||
|
||||
- **往来对象分类**:个人客户 / 企业客户 / 同行客户
|
||||
- **应收性质**:押金退还 / 赔偿款 / 口车费 / 其他应收
|
||||
|
||||
此前这两组**前端写死常量**,后端没有对应数据字典、也没有下拉接口,运营要加值必须改前端代码发版。本次排查三方支付渠道字典化问题(前端写死导致新渠道 `webchat` 取不到)时,顺势把应收这两组也拉齐为「字典事实源 + 下拉接口」的统一做法,与资金账户三字段(#7141 accountType/nature/channel)一致。
|
||||
|
||||
**拍板方案**:纯字典化,**不落库、不在应收/往来表加列**——仅作前端展示/筛选下拉的事实源。
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| 数据字典 | 新增 2 个:`fin_recv_customer_ptype`、`fin_recv_nature` |
|
||||
| 接口 | 新增 `GET /admin/finance/receipt/options` |
|
||||
| 数据库表 | 零 DDL、零加列(纯字典化) |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
- **路径**:`GET /admin/finance/receipt/options`
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **数据源**:平台字典 `fin_recv_customer_ptype` / `fin_recv_nature`,仅下放 **ACTIVE** 行、按 `sortOrder` 升序
|
||||
- **缓存**:后端 5 分钟本地缓存,运营改字典最多 5 分钟生效
|
||||
- **降级**:字典服务不可用时对应组返回空 List,**不报错**
|
||||
|
||||
## 四、入参
|
||||
|
||||
无(Query / Body 均无参数)。
|
||||
|
||||
## 五、出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `customerPTypes` | `List<OptionItem>` | 应收往来对象分类下拉(字典 `fin_recv_customer_ptype`) |
|
||||
| `recvNatures` | `List<OptionItem>` | 应收性质下拉(字典 `fin_recv_nature`) |
|
||||
|
||||
`OptionItem`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `value` | String | 字典值(英文码,如 `PERSONAL` / `DEPOSIT_REFUND`) |
|
||||
| `label` | String | 中文标签(如 `个人客户` / `押金退还`) |
|
||||
| `sortOrder` | Integer | 排序(越小越靠前) |
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
`fin_recv_customer_ptype`(应收往来对象分类):
|
||||
|
||||
| dict_value | label | sortOrder |
|
||||
|---|---|---|
|
||||
| `PERSONAL` | 个人客户 | 10 |
|
||||
| `COMPANY` | 企业客户 | 20 |
|
||||
| `PEER` | 同行客户 | 30 |
|
||||
|
||||
`fin_recv_nature`(应收性质):
|
||||
|
||||
| dict_value | label | sortOrder |
|
||||
|---|---|---|
|
||||
| `DEPOSIT_REFUND` | 押金退还 | 10 |
|
||||
| `COMPENSATION` | 赔偿款 | 20 |
|
||||
| `CAR_FEE` | 口车费 | 30 |
|
||||
| `OTHER` | 其他应收 | 40 |
|
||||
|
||||
> 字典加值(如新增一种应收性质)零发版生效,前端读接口即可拿到。
|
||||
|
||||
## 七、错误码
|
||||
|
||||
无业务错误码。字典加载失败降级为空 List(HTTP 200,对应组 `[]`),不抛错。
|
||||
|
||||
## 八、示例
|
||||
|
||||
**典型**:
|
||||
|
||||
```http
|
||||
GET /admin/finance/receipt/options
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"customerPTypes": [
|
||||
{ "value": "PERSONAL", "label": "个人客户", "sortOrder": 10 },
|
||||
{ "value": "COMPANY", "label": "企业客户", "sortOrder": 20 },
|
||||
{ "value": "PEER", "label": "同行客户", "sortOrder": 30 }
|
||||
],
|
||||
"recvNatures": [
|
||||
{ "value": "DEPOSIT_REFUND", "label": "押金退还", "sortOrder": 10 },
|
||||
{ "value": "COMPENSATION", "label": "赔偿款", "sortOrder": 20 },
|
||||
{ "value": "CAR_FEE", "label": "口车费", "sortOrder": 30 },
|
||||
{ "value": "OTHER", "label": "其他应收", "sortOrder": 40 }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**边界(字典某组全部停用 / 加载失败)**:对应组返回空数组,前端按空下拉处理:
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "customerPTypes": [], "recvNatures": [] }, "success": true }
|
||||
```
|
||||
|
||||
**异常(未带 / 过期 token)**:网关 401。
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- 本接口**只提供下拉选项**,不涉及应收数据的落库、查询、统计——两组值当前不存任何表字段。
|
||||
- 仅 ACTIVE 字典行下放;停用行不出现,但历史若有引用不受影响(本期无落库引用)。
|
||||
- 后端 5 分钟缓存:运营改字典后最多 5 分钟各实例口径一致。
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
本接口为**新增**,无旧版本。前端的对应变化:
|
||||
|
||||
| 项 | 改前(前端写死) | 改后(读接口) |
|
||||
|---|---|---|
|
||||
| 往来对象分类 | `CUSTOMER_PTYPE_OPTIONS` 中文 value(`'个人客户'`…) | 读 `customerPTypes`,英文码 value(`PERSONAL`…) |
|
||||
| 应收性质 | `RECV_NATURE_OPTIONS` 中文 value(`'押金退还'`…) | 读 `recvNatures`,英文码 value(`DEPOSIT_REFUND`…) |
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- **影响面**:纯新增接口 + 新增字典,零存量接口改动、零 DDL、零数据迁移。旧逻辑不受影响。
|
||||
- **前端**:本次接口新增不破坏现有页面;但下拉要动态化需前端配套改(见下)。
|
||||
- **回滚**:回退 PR #8503 即可;新增字典行可保留(无引用不碍事)或由 DBA 清理。
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
- ⚠️ **码值是英文码**(`PERSONAL`/`DEPOSIT_REFUND`…),不是中文。前端若沿用旧写死常量的中文 value 需迁移。
|
||||
- ⚠️ 部署后需清字典缓存(迁移脚本注释已含 redis-cli DEL 命令),否则可能读到旧缓存。
|
||||
- 字典缓存 5 分钟:前端联调时若刚改字典没看到新值,先等缓存过期或重启服务。
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8501
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8503
|
||||
- 合并 commit:https://git.1814.love/wx/HL/commit/8f278ec57485f896bf89372cb084fc165a7ac302
|
||||
- 负责人:yst(后端)
|
||||
|
||||
**前端配套动作**:应收页两组写死常量(`CUSTOMER_PTYPE_OPTIONS` / `RECV_NATURE_OPTIONS`)改为调用 `GET /admin/finance/receipt/options` 动态渲染,码值从中文迁到英文码。
|
||||
@@ -0,0 +1,342 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "应收台账新增 rowType 入参(全部/散客订单/团期切换)+ 查看按钮对接指引"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "e696593015e273f5b63271183cbe527fa8275e12"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "应收台账分页接口新增可选入参 rowType(空=全部 / ORDER=散客订单 / GROUP_BATCH=团期整团聚合行),后端过滤后 total 正确,前端需加顶部切换;另每行需加「查看」按钮,按 rowType 分流跳订单详情 / 出团详情(路由规则见 §十二)。;前端 2026-09-29 已随 29_frontend 一并交付(同一 commit e6965930):页签切换走后端 rowType 过滤,行内查看按 rowType 分流钻取"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:应收台账新增 rowType 入参 + 查看按钮对接(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: https://git.1814.love/wx/HL/pulls/8505
|
||||
**Issue**: https://git.1814.love/wx/HL/issues/8504
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
管理后台「应收台账」页面调用 `GET /admin/finance/receipt/receivable/page`(#8191 交付的只读台账页)。台账行是**双轨**的:
|
||||
|
||||
- `rowType = ORDER`:散客订单行,一行 = 一个订单
|
||||
- `rowType = GROUP_BATCH`:团期整团聚合行,一行 = 一个出团批次(整团应收汇总)
|
||||
|
||||
本次变更给分页接口**新增一个可选入参 `rowType`**,支持页面顶部「全部 / 散客订单 / 团期」三态切换,由后端过滤(此前前端只能本地过滤,分页 total 会错)。
|
||||
|
||||
另财务要求在每行加「查看」按钮点行进详情。**查看按钮是纯前端动作**——行出参字段已够用(`rowType` / `id` / `groupBatchId` 都有),不依赖本次后端发版,但跳转路由规则必须按 §十二 执行,跳错必 404。
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| `GET /admin/finance/receipt/receivable/page` 入参 | **新增可选 Query 参数 `rowType`**(空/不传 = 全部,`ORDER` = 只散客订单行,`GROUP_BATCH` = 只团期整团聚合行) |
|
||||
| 出参字段 | **不变**(行 VO 字段同 #8191) |
|
||||
| 数据库表 | 零 DDL |
|
||||
| 前端页面 | 需加顶部「全部/散客订单/团期」切换(把 `rowType` 传给后端)+ 每行加「查看」按钮(按 §十二 分流跳转) |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
- **路径**:`GET /admin/finance/receipt/receivable/page`
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **幂等性**:是(只读查询,可重复调用无副作用)
|
||||
- **限流**:无特殊限流
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `pageNo` | Integer | ✅ | 页码,从 1 开始 |
|
||||
| `pageSize` | Integer | ✅ | 每页条数 |
|
||||
| `keyword` | String | ❌ | 关键字模糊搜索(订单号 / 团号 / 产品名 / 客户名等),语义不变 |
|
||||
| `receivableStatus` | String | ❌ | 收款状态过滤,语义不变(枚举见 §六) |
|
||||
| `rowType` | String | ❌ | **本次新增**。行类型过滤:`ORDER` = 只返回散客订单行;`GROUP_BATCH` = 只返回团期整团聚合行;**空或不传 = 全部**(向后兼容) |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无(GET 请求)。
|
||||
|
||||
## 五、出参
|
||||
|
||||
外层为标准分页响应包:`code` / `data` / `message` / `success`,其中 `data` 含 `list`(行数组)/ `total` / `pageNo` / `pageSize`。
|
||||
|
||||
行 VO 字段(与 #8191 一致,本次未变):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `rowType` | String | 行类型:`ORDER` / `GROUP_BATCH` |
|
||||
| `id` | String | 行主键。`ORDER` 行 = 订单 ID;`GROUP_BATCH` 行 = 团期批次 ID(⚠️ 见 §十二,跳转时不能混用) |
|
||||
| `groupBatchId` | String | 团期批次 ID。**仅 `GROUP_BATCH` 行有值**,`ORDER` 行为 null |
|
||||
| `orderNo` | String | 订单号(`GROUP_BATCH` 行为团号口径,按行展示即可) |
|
||||
| `teamNo` | String | 团号 |
|
||||
| `productName` | String | 产品名称 |
|
||||
| `customerName` | String | 客户姓名。**`GROUP_BATCH` 行恒为 null**,前端显示「—」 |
|
||||
| `customerPhone` | String | 客户电话。**`GROUP_BATCH` 行恒为 null**,前端显示「—」 |
|
||||
| `orderStatus` | String | 订单状态码 |
|
||||
| `orderStatusName` | String | 订单状态中文名 |
|
||||
| `receivableStatus` | String | 收款状态码(枚举见 §六) |
|
||||
| `receivableStatusName` | String | 收款状态中文名 |
|
||||
| `receivableAmount` | Number | 应收金额(元) |
|
||||
| `paidAmount` | Number | 已付金额(元) |
|
||||
| `collectedAmount` | Number | 已收金额(元) |
|
||||
| `balanceAmount` | Number | 待收尾款(元) |
|
||||
|
||||
> ⚠️ `id` / `groupBatchId` 为 Long 大数(雪花 ID)序列化的**字符串**;各金额字段如以字符串下发同样**当字符串处理**。JS 全程禁止 `Number()` 转换 ID 类字段,防精度丢失。
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### 6.1 rowType(行类型)
|
||||
|
||||
**所属字段**:Query 入参 `rowType` / 出参行 `rowType` | **类型**:`String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `ORDER` | 散客订单 | 一行 = 一个订单 |
|
||||
| `GROUP_BATCH` | 团期整团 | 一行 = 一个出团批次(整团应收汇总聚合行) |
|
||||
|
||||
> 入参侧:空 / 不传 = 全部;非法值后端按「全部」处理(不过滤),不报错。
|
||||
|
||||
### 6.2 receivableStatus(收款状态)
|
||||
|
||||
**所属字段**:Query 入参 `receivableStatus` / 出参行 `receivableStatus` | **类型**:`String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `UNPAID` | 未收款 | 已收为 0 |
|
||||
| `PARTIAL` | 部分收款 | 已收 > 0 但未收齐 |
|
||||
| `DONE` | 已收齐 | 已收 = 应收 |
|
||||
|
||||
## 七、错误码
|
||||
|
||||
本接口为只读分页查询,**无业务错误码**。仅标准网关层错误:
|
||||
|
||||
| HTTP | 场景 |
|
||||
|------|------|
|
||||
| 401 | 未登录 / token 失效 |
|
||||
| 403 | 无权限访问 |
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功(rowType=ORDER,只查散客订单行)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /admin/finance/receipt/receivable/page?pageNo=1&pageSize=10&rowType=ORDER&receivableStatus=UNPAID
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"rowType": "ORDER",
|
||||
"id": "1964123456789012345",
|
||||
"groupBatchId": null,
|
||||
"orderNo": "O20260928123001",
|
||||
"teamNo": "T20261005-01",
|
||||
"productName": "呼伦贝尔草原五日游",
|
||||
"customerName": "张三",
|
||||
"customerPhone": "138****1234",
|
||||
"orderStatus": "CONFIRMED",
|
||||
"orderStatusName": "已确认",
|
||||
"receivableStatus": "UNPAID",
|
||||
"receivableStatusName": "未收款",
|
||||
"receivableAmount": 5980.00,
|
||||
"paidAmount": 0.00,
|
||||
"collectedAmount": 0.00,
|
||||
"balanceAmount": 5980.00
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"pageNo": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(rowType=GROUP_BATCH,团行 customerName/customerPhone 为 null)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /admin/finance/receipt/receivable/page?pageNo=1&pageSize=10&rowType=GROUP_BATCH
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**(注意 `customerName` / `customerPhone` 恒为 null,前端显示「—」):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"rowType": "GROUP_BATCH",
|
||||
"id": "1964999888777666555",
|
||||
"groupBatchId": "1964999888777666555",
|
||||
"orderNo": "GB20261005-01",
|
||||
"teamNo": "T20261005-01",
|
||||
"productName": "阿尔山秋色摄影团",
|
||||
"customerName": null,
|
||||
"customerPhone": null,
|
||||
"orderStatus": "CONFIRMED",
|
||||
"orderStatusName": "已确认",
|
||||
"receivableStatus": "PARTIAL",
|
||||
"receivableStatusName": "部分收款",
|
||||
"receivableAmount": 88000.00,
|
||||
"paidAmount": 50000.00,
|
||||
"collectedAmount": 50000.00,
|
||||
"balanceAmount": 38000.00
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"pageNo": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 不传 rowType(全部,ORDER + GROUP_BATCH 行混合返回)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /admin/finance/receipt/receivable/page?pageNo=1&pageSize=10
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**(两种行混排,前端按每行 `rowType` 自行决定展示与跳转):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"rowType": "ORDER",
|
||||
"id": "1964123456789012345",
|
||||
"groupBatchId": null,
|
||||
"orderNo": "O20260928123001",
|
||||
"teamNo": "T20261005-01",
|
||||
"productName": "呼伦贝尔草原五日游",
|
||||
"customerName": "张三",
|
||||
"customerPhone": "138****1234",
|
||||
"orderStatus": "CONFIRMED",
|
||||
"orderStatusName": "已确认",
|
||||
"receivableStatus": "DONE",
|
||||
"receivableStatusName": "已收齐",
|
||||
"receivableAmount": 5980.00,
|
||||
"paidAmount": 5980.00,
|
||||
"collectedAmount": 5980.00,
|
||||
"balanceAmount": 0.00
|
||||
},
|
||||
{
|
||||
"rowType": "GROUP_BATCH",
|
||||
"id": "1964999888777666555",
|
||||
"groupBatchId": "1964999888777666555",
|
||||
"orderNo": "GB20261005-01",
|
||||
"teamNo": "T20261005-01",
|
||||
"productName": "阿尔山秋色摄影团",
|
||||
"customerName": null,
|
||||
"customerPhone": null,
|
||||
"orderStatus": "CONFIRMED",
|
||||
"orderStatusName": "已确认",
|
||||
"receivableStatus": "UNPAID",
|
||||
"receivableStatusName": "未收款",
|
||||
"receivableAmount": 88000.00,
|
||||
"paidAmount": 0.00,
|
||||
"collectedAmount": 0.00,
|
||||
"balanceAmount": 88000.00
|
||||
}
|
||||
],
|
||||
"total": 2,
|
||||
"pageNo": 1,
|
||||
"pageSize": 10
|
||||
},
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- ✅ **适用场景**:应收台账分页查询,支持全部 / 只看散客订单 / 只看团期三种过滤。
|
||||
- ⚠️ **不传 `rowType` = 全部**,与旧版行为完全一致(向后兼容)。
|
||||
- ⚠️ `GROUP_BATCH` 行是分页折叠的**聚合行**,整团一页内只占一行;`total` 为近似口径(既有行为,本次未变)。
|
||||
- ⚠️ `rowType` 传非法值(非 `ORDER` / `GROUP_BATCH`)后端按「全部」处理,不过滤也不报错,前端不要做非法值兜底展示。
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
### 10.1 入参级对比
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| Query 参数 | pageNo / pageSize / keyword / receivableStatus | **+ rowType**(可选,空=全部) |
|
||||
| 「只看散客订单 / 只看团期」实现方式 | 前端拿全部分页结果本地过滤,**total 与实际行数对不上**,翻页错乱 | 后端按 `rowType` 过滤,**total 正确**,翻页正常 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 不传 rowType | 全部行(ORDER + GROUP_BATCH 混排) | 同左(不变) |
|
||||
| 切「散客订单」 | 无此能力(只能本地过滤) | `rowType=ORDER`,后端只返回 ORDER 行 |
|
||||
| 切「团期」 | 无此能力 | `rowType=GROUP_BATCH`,后端只返回团行 |
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:否。`rowType` 为可选入参,不传时行为与旧版完全一致。
|
||||
- **前端是否必须同步上线**:切换功能(顶部三态)建议改用后端过滤以修正 total;「查看」按钮不依赖本次后端发版(行出参字段 #8191 已齐备),可先行上线。
|
||||
- **影响已有数据**:无(零 DDL、零数据迁移)。
|
||||
- **回滚方案**:后端回退本 PR 即恢复旧行为(前端不传 rowType 不受影响);前端回退页面版本即可。
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
**本节是重点:「查看」按钮的路由分流规则。**
|
||||
|
||||
每行加「查看」按钮时,**必须先读该行的 `rowType` 再决定跳哪个详情页**:
|
||||
|
||||
| 行 `rowType` | 跳转前端路由 | 路由参数取哪个字段 | 后端详情接口 |
|
||||
|---|---|---|---|
|
||||
| `ORDER` | `/order-v2/detail/{id}` | 行的 **`id`**(= 订单 ID) | `GET /v3/admin/order/{id}` |
|
||||
| `GROUP_BATCH` | `/order-v2/batch/detail/{code}` | 行的 **`groupBatchId`**(= 团期批次 ID) | `GET /v3/admin/order/group-batch/{groupBatchId}` |
|
||||
|
||||
逐条红线:
|
||||
|
||||
- ⚠️ **GROUP_BATCH 行绝不能拿 `id` 跳订单详情页**(`/order-v2/detail/{id}`)——团行的 `id` 里装的是**批次 ID**,按订单详情跳**必 404**(后端契约写死,订单详情只认订单 ID)。
|
||||
- ⚠️ 团行跳转用的是独立的 **`groupBatchId`** 字段,不要用 `id`(虽然当前团行 `id == groupBatchId`,但契约上以 `groupBatchId` 为准)。
|
||||
- ⚠️ 团行跳转的前端路由参数名是 **`:code`** 不是 `:id`,对应页面叫「**出团详情**」(`/order-v2/batch/detail/:code`)。
|
||||
- ⚠️ `id` / `groupBatchId` 是 Long 大数序列化的字符串,拼接路由时**原样字符串拼接**,禁止 `Number()` 转换。
|
||||
- ⚠️ `GROUP_BATCH` 行 `customerName` / `customerPhone` 恒为 null,列表显示「—」,不要显示「null」字样。
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8504
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8505
|
||||
- Merge commit:https://git.1814.love/wx/HL/commit/cf140ebed1
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- 后端负责人:yst
|
||||
@@ -0,0 +1,353 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8507"
|
||||
title: "往来期初 ledgerType 新增 SUPPLIER_RECV 供应商应收账套,支撑财务初始化「应收初始化」tab"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "cdfaf7932a9bf3008760a4b728f2d9fe0ffff421"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "POST /admin/finance/opening-balances 的 ledgerType 枚举新增 SUPPLIER_RECV(供应商应收:他欠我们的杂项应收,如押金退还/赔偿款/口车费),配套应收性质字典 fin_recv_nature(DEPOSIT_REFUND/COMPENSATION/CAR_FEE/OTHER)+ 错误码 598409/598410;同时收紧金额方向:SUPPLIER 只认 openingPayable>0,CUSTOMER/SUPPLIER_RECV 只认 openingReceivable>0。测试环境已部署并行为验证通过。前端需新增财务初始化「应收初始化」tab(7 字段表单,字段映射见本文档 §三/§四)。;前端 2026-09-29 已交付:fin-init 新增「供应商应收」tab(7 元素表单,应收性质走 recvNatures 字典不写死,isPrimary=1 默认选中,记账日期只读不传参),CUSTOMER 同步收紧为单应收框,STAFF 维持双向;fin-init spec 21 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:往来期初新增供应商应收账套 SUPPLIER_RECV(应收初始化 tab 对接)(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: [#8509](https://git.1814.love/wx/HL/pulls/8509)、[#8513](https://git.1814.love/wx/HL/pulls/8513)
|
||||
**Issue**: [#8507](https://git.1814.love/wx/HL/issues/8507)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
管理后台「财务初始化」要新增一个**「应收初始化」tab**:记录**供应商欠我们**的杂项应收(押金退还、赔偿款、口车费等,不走订单流水的零散应收)。
|
||||
|
||||
支撑这个 tab,往来期初接口 `POST /admin/finance/opening-balances` 的账套枚举 `ledgerType` 新增第三个值 **`SUPPLIER_RECV`**(供应商应收)。
|
||||
|
||||
原有账套语义不变:
|
||||
|
||||
| 账套值 | 中文 | 方向 | 谁欠谁 |
|
||||
|---|---|---|---|
|
||||
| `SUPPLIER` | 供应商应付 | 应付 | 我们欠供应商 |
|
||||
| `CUSTOMER` | 客户应收 | 应收 | 客户欠我们 |
|
||||
| `SUPPLIER_RECV` ✨ | 供应商应收 | 应收 | 供应商欠我们(本次新增) |
|
||||
|
||||
> ⚠️ 枚举值是 `SUPPLIER_RECV`(13 字符),**不是** `SUPPLIER_RECEIVABLE`(18 字符超列宽,#8511 已改短)。前端写常量时别用长名。
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 往来期初一次录入 | POST | `/admin/finance/opening-balances` | 修改接口 | `ledgerType` 新增 `SUPPLIER_RECV`;金额方向校验收紧(见 §十) |
|
||||
|
||||
配套数据源接口(均**早已上线**,无变更,仅列出供新 tab 对接):供应商下拉 `GET /admin/supplier/items/list`、应收性质下拉 `GET /admin/finance/receipt/options`、所属公司下拉 `GET /v3/admin/travel-agency/enabled`,详见 §4.3 ~ §4.5。
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
- **路径**:`POST /admin/finance/opening-balances`
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **幂等性**:否。但同一往来对象同一账套**仅可录一次**初始期初,重复提交被 598401 拦截(要改金额走「期初调整」),不会产生脏数据
|
||||
- **限流**:无特殊限流
|
||||
|
||||
**应收初始化(SUPPLIER_RECV)表单最终形态(7 个元素)**:
|
||||
|
||||
| # | 表单元素 | 控件 | 必填 | 提交字段 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 供应商 | 下拉(可搜索) | ✅ | `refId` + `refName` |
|
||||
| 2 | 应收性质 | 下拉 | ✅ | `customerCategory`(复用此字段存应收性质码值) |
|
||||
| 3 | 应收金额 | 数字输入 | ✅ | `openingReceivable` |
|
||||
| 4 | 所属公司 | 下拉 | ✅ | `companyId` |
|
||||
| 5 | 记账日期 | 只读展示 | — | **后端取,前端不传** |
|
||||
| 6 | 佐证材料 | 图片上传 | ❌ 选填 | `evidenceUrl` |
|
||||
| 7 | 备注 | 文本域 | ❌ 选填 | `remark`(≤ 200 字) |
|
||||
|
||||
**本 tab 表单上不要出现的元素**:
|
||||
|
||||
| 不要出现 | 原因 |
|
||||
|---|---|
|
||||
| 应付金额框 | 供应商应收账套只有应收方向,`openingPayable` 不传 |
|
||||
| 供应商 ID / 名称手填框 | 必须走下拉,ID 与名称由选项带出 |
|
||||
| 记账日期输入框 | 只读展示,后端取当前未封账账期 startDate 落库 |
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
无。
|
||||
|
||||
### 4.2 请求体字段(SUPPLIER_RECV 账套)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| `ledgerType` | String | ✅ | 账套类型。本 tab **固定传 `SUPPLIER_RECV`** | 枚举,见 §6.1 |
|
||||
| `refId` | String | ✅ | 供应商 ID(取下拉项的 `supplierId`) | 必须是存在的供应商 |
|
||||
| `refName` | String | ✅ | 供应商名称(取下拉项的 `fullName`) | ≤ 128 字 |
|
||||
| `customerCategory` | String | ✅ | 应收性质码值(复用该字段,取应收性质下拉的 `value`) | 必须是应收性质字典 `fin_recv_nature` 的值,见 §6.2;缺失 → 598409,取值非法 → 598410 |
|
||||
| `companyId` | String | ✅ | 所属公司主体 ID(取下拉项的 `agencyId`) | 必须是启用中的公司主体,否则 598407 |
|
||||
| `openingReceivable` | Number | ✅ | 应收期初金额(元) | 必填且 **> 0**,否则 598405 |
|
||||
| `evidenceUrl` | String | ❌ | 佐证材料图片 URL | 可空 / 不传 |
|
||||
| `remark` | String | ❌ | 备注 | ≤ 200 字 |
|
||||
|
||||
**不要传的字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `openingPayable` | 应付期初金额,应收账套(CUSTOMER / SUPPLIER_RECV)不传 |
|
||||
| 记账日期相关字段 | 请求体里没有记账日期字段,后端自动取当前未封账账期 startDate |
|
||||
|
||||
> ⚠️ `refId` / `companyId` 后端是 Long 大数(雪花 ID),JSON 序列化为字符串下发;前端 JS 一律**当字符串处理**,不要 `Number()` 转换,防精度丢失。
|
||||
|
||||
### 4.3 供应商下拉数据源
|
||||
|
||||
- **路径**:`GET /admin/supplier/items/list`(资源服务,已上线)
|
||||
- **认证**:网关 JWT(admin)
|
||||
|
||||
Query 参数(全部选填):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `status` | String | ❌ | 本表单**固定传 `ACTIVE`**(只在用供应商可录期初) |
|
||||
| `keyword` | String | ❌ | 名称 / 编号模糊搜索 |
|
||||
| `limit` | Integer | ❌ | 默认 50,最大 200 |
|
||||
|
||||
响应 `data` 为数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `supplierId` | String | 供应商 ID(Long 序列化字符串) |
|
||||
| `fullName` | String | 供应商全称(下拉显示用) |
|
||||
| `shortName` | String | 简称 |
|
||||
| `supplierNo` | String | 供应商编号 |
|
||||
| `statusName` | String | 状态中文名 |
|
||||
|
||||
**取值映射**:下拉显示 `fullName`;选中后提交 `refId = supplierId`、`refName = fullName`。
|
||||
|
||||
### 4.4 应收性质下拉数据源
|
||||
|
||||
- **路径**:`GET /admin/finance/receipt/options`(#8501 已上线)
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **入参**:无
|
||||
- **取数**:取响应 `data.recvNatures` 数组(每项 `value` / `label`)
|
||||
- **取值映射**:下拉显示 `label`;选中后提交 `customerCategory = value`。字典 `fin_recv_nature` 值见 §6.2
|
||||
|
||||
### 4.5 所属公司下拉数据源
|
||||
|
||||
- **路径**:`GET /v3/admin/travel-agency/enabled`(order-v3,已上线)
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **入参**:无
|
||||
|
||||
响应 `data` 为数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `agencyId` | String | 公司主体 ID(Long 序列化字符串) |
|
||||
| `agencyName` | String | 公司名称(下拉显示用) |
|
||||
| `isPrimary` | Integer | 是否主体公司:1 = 是,0 = 否 |
|
||||
|
||||
**取值映射**:下拉显示 `agencyName`;`isPrimary = 1` 的主体公司**默认选中**;选中后提交 `companyId = agencyId`。公司名称由后端自取快照,前端不用传。
|
||||
|
||||
### 4.6 记账日期展示值来源(只读,不入参)
|
||||
|
||||
记账日期 = 当前**未封账**账期的 `startDate`,后端落库时自动取,前端不传。若表单上要展示该值:
|
||||
|
||||
- **路径**:`GET /admin/finance/account-periods/page`
|
||||
- **取数**:取返回列表中 `isClosed = 0`(未封账)那一行的 `startDate` 展示即可
|
||||
|
||||
## 五、出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data` | String | 新建期初行 ID(Long 序列化字符串,JS 当字符串处理) |
|
||||
|
||||
外层为标准响应包:`code` / `data` / `message` / `success`。
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### 6.1 ledgerType(账套类型)
|
||||
|
||||
**所属字段**:请求体 `ledgerType` | **类型**:`String` | **必填**:✅
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SUPPLIER` | 供应商应付 | 我们欠供应商;只传 `openingPayable` |
|
||||
| `CUSTOMER` | 客户应收 | 客户欠我们;只传 `openingReceivable` |
|
||||
| `SUPPLIER_RECV` ✨ | 供应商应收 | 供应商欠我们(杂项应收);只传 `openingReceivable`,且 `customerCategory` 必填 |
|
||||
|
||||
### 6.2 customerCategory(应收性质,字典 fin_recv_nature)
|
||||
|
||||
**所属字段**:请求体 `customerCategory` | **类型**:`String` | **必填**:✅(SUPPLIER_RECV 账套必填)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `DEPOSIT_REFUND` | 押金退还 | 供应商应退未退的押金 |
|
||||
| `COMPENSATION` | 赔偿款 | 供应商应赔付的款项 |
|
||||
| `CAR_FEE` | 口车费 | 供应商应付的口车费用 |
|
||||
| `OTHER` | 其他应收 | 其他杂项应收 |
|
||||
|
||||
> 数据源是 §4.4 的 `/admin/finance/receipt/options`(`recvNatures` 数组),**前端不要写死这 4 个值**,后端字典加值时下拉自动多一项。
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| code | 含义 | 触发场景 | 前端 toast 建议文案 |
|
||||
|------|------|----------|---------------------|
|
||||
| 598409 | 应收性质必填 | SUPPLIER_RECV 账套未传 `customerCategory` | 请选择应收性质 |
|
||||
| 598410 | 应收性质取值非法 | `customerCategory` 不在应收性质字典内 | 应收性质无效 |
|
||||
| 598405 | 期初金额未填或 ≤ 0 | `openingReceivable` 缺失 / 为 0 / 负数 | 请填写正确的应收金额 |
|
||||
| 598401 | 该往来对象期初已录过 | 同一供应商同账套重复提交(要改走「期初调整」) | 该供应商期初已录入,要改走「期初调整」 |
|
||||
| 598407 | 所属公司非法或已停用 | `companyId` 不存在或公司主体已停用 | 所属公司无效 |
|
||||
| 598403 | 无未封账账期 | 当前没有 `isClosed = 0` 的账期,无法落记账日期 | 当前无进行中账期 |
|
||||
| 598406 | 账套非法 | `ledgerType` 不是 SUPPLIER / CUSTOMER / SUPPLIER_RECV 之一 | 账套须为 SUPPLIER/CUSTOMER/SUPPLIER_RECV |
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /admin/finance/opening-balances
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER_RECV",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某车队有限公司",
|
||||
"customerCategory": "DEPOSIT_REFUND",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingReceivable": 1500.00,
|
||||
"evidenceUrl": "https://oss.example.com/finance/evidence/x.jpg",
|
||||
"remark": "2025 年度押金应退未退"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": "1956789012345678901",
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(佐证、备注均不传,金额最小值)
|
||||
|
||||
**场景说明**:`evidenceUrl` / `remark` 选填,最小合法请求 6 个字段;金额边界 0.01。
|
||||
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER_RECV",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某车队有限公司",
|
||||
"customerCategory": "OTHER",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingReceivable": 0.01
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": "1956789012345678902",
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(应收性质未传,触发 598409)
|
||||
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER_RECV",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某车队有限公司",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingReceivable": 1500.00
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 598409,
|
||||
"data": null,
|
||||
"message": "应收性质必填",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
> 其他常见失败:重复提交同一供应商 → `598401`;`ledgerType` 拼错(如 `SUPPLIER_RECEIVABLE` 长名)→ `598406`。
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- ✅ **适用场景**:供应商存在杂项应收(押金退还 / 赔偿款 / 口车费 / 其他)首次录期初,且当前存在未封账账期、所属公司主体启用中。
|
||||
- ❌ **不适用场景**:
|
||||
- 该供应商已录过本账套期初 → 598401,改金额请走「期初调整」,不要重复提交;
|
||||
- 无未封账账期 → 598403;
|
||||
- 所属公司已停用 → 598407。
|
||||
- ⚠️ **特殊边界**:
|
||||
- 记账日期恒等于当前未封账账期 startDate(后端落库),不可指定历史 / 未来日期;
|
||||
- 一账套一方向:SUPPLIER_RECV 只录应收,录应付请切「应付初始化」tab(`SUPPLIER` 账套)。
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
### 10.1 字段 / 枚举级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `ledgerType` | 2 个值:`SUPPLIER` / `CUSTOMER` | 3 个值:新增 **`SUPPLIER_RECV`**(供应商应收) |
|
||||
| `customerCategory` | 仅 CUSTOMER 账套使用 | SUPPLIER_RECV 账套**复用此字段**存应收性质码值(必填,字典 `fin_recv_nature`) |
|
||||
|
||||
### 10.2 行为级对比(金额方向收紧,本次同步生效)
|
||||
|
||||
| 账套 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `SUPPLIER` | 应收 / 应付金额校验相对宽松 | **只认 `openingPayable` > 0**,应收方向不传 |
|
||||
| `CUSTOMER` | 同上 | **只认 `openingReceivable` > 0**,应付方向不传 |
|
||||
| `SUPPLIER_RECV` | (不存在) | **只认 `openingReceivable` > 0**,且 `customerCategory` 必填 |
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:基本否。`SUPPLIER` / `CUSTOMER` 已有表单若本来就按「一账套一方向」正确提交(只传一个方向金额),不受金额方向收紧影响;`SUPPLIER_RECV` 是纯新增枚举值,老代码不传它即可。
|
||||
- **前端是否必须同步上线**:是(针对「应收初始化」tab)。该 tab 未上线前后端能力空转无影响;tab 上线时必须按本文档字段映射对接。
|
||||
- **影响已有数据**:无(无需前端配合的数据迁移)。
|
||||
- **回滚方案**:后端 revert 两个 PR 即可;前端 tab 未上线则无需回滚。
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
- ⚠️ 枚举值是 **`SUPPLIER_RECV`(13 字符)**,不是 `SUPPLIER_RECEIVABLE`(18 字符超列宽,#8511 已改短)。拼错会触发 598406。
|
||||
- ⚠️ `refId` / `companyId` / 响应 `data` 均为 Long 大数序列化的字符串,JS 全程当字符串处理,禁止 `Number()` 转换。
|
||||
- ⚠️ `customerCategory` 字段在 SUPPLIER_RECV 账套下语义是「应收性质」,值必须来自 §4.4 下拉(字典 `fin_recv_nature`),**不要写死 4 个码值**。
|
||||
- ⚠️ 记账日期前端不传;如需展示,查 `GET /admin/finance/account-periods/page` 取 `isClosed = 0` 行的 `startDate`。
|
||||
- ⚠️ 佐证材料 `evidenceUrl` 是**选填**,不要加必填校验拦截提交。
|
||||
- 本次已部署测试服并行为验证通过,前端可立即联调。
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8507](https://git.1814.love/wx/HL/issues/8507)
|
||||
- **PR**: [#8509](https://git.1814.love/wx/HL/pulls/8509)、[#8513](https://git.1814.love/wx/HL/pulls/8513)
|
||||
- **前置依赖**: [#8501](https://git.1814.love/wx/HL/issues/8501)(应收性质字典 + `/receipt/options` 接口)、[#8511](https://git.1814.love/wx/HL/issues/8511)(枚举值改短 SUPPLIER_RECEIVABLE → SUPPLIER_RECV)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: yst
|
||||
@@ -0,0 +1,244 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8510"
|
||||
title: "团期核单聚合复核 reports/group 出参新增 21 字段,对齐常规订单核单财务总览"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "8f1c982b831ae11b0bd9783f812ca0ecf4dcb90d"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-09-30"
|
||||
status_note: "团期核单「聚合复核」GET /v3/admin/order/group-batch/{groupBatchId}/settlement/reports/group 出参 GroupSettlementRespVO 新增 21 个字段(11 金额字段 + 4 个 mirrorMatched + customers 客户合并列表 + 6 个人数汇总字段),结构与常规订单核单财务总览完全对齐,唯一区别是数据从单订单换成全团在团子订单合并(排除已取消)。关键:待收尾款 outstandingAmount 与 /finance 应收台账同源,前端不要再自行用「应收−已付」硬减;primaryReporterCollectedAmount 已含在 offlinePaidAmount 内勿重复加总。后端已合并待部署,部署后行为验证。前端可按 §5 字段表接入团期核单详情财务总览区(与常规订单核单同一套渲染)。前端已交付:收款总览(实时)11 金额+客户与人数 6 档+customers 一户一行,未部署 key 缺失整块隐藏;2026-09-30 拍板改独立详情页 /finance/settlement/group/:id(骨架对齐常规核单详情两步流程,核单录入 AuditTab+聚合复核 finalize/confirm),行弹层与 GroupSettlementPanel 已删。"
|
||||
---
|
||||
|
||||
# 团期核单详情对齐常规订单核单 —— 修改接口(管理后台)
|
||||
|
||||
> Issue: https://git.1814.love/wx/HL/issues/8510
|
||||
> PR: https://git.1814.love/wx/HL/pulls/8546
|
||||
> Commit: https://git.1814.love/wx/HL/commit/306f9873d30a567331afed12b91c45af176418bf
|
||||
> 负责人:腰苏图
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
团期核单(一团一核单)的「聚合复核」接口此前只返回团级汇总快照(订单总额/已收/欠收),与常规订单核单详情的财务总览结构不一致,缺线上/线下拆分、主报账人代收、客户合并信息、人数分档等字段,前端无法像常规订单核单一样渲染完整详情。
|
||||
|
||||
本次把团期核单的财务总览**完全对齐常规订单核单**:字段平铺结构与常规订单 `SettlementFinancialOverviewRespVO` 一致,唯一区别是**数据来源从单个订单换成全团所有在团子订单合并**(排除已取消子订单)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| 修改 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/reports/group` | 出参 `GroupSettlementRespVO` 新增 21 个字段(11 金额字段 + 4 mirrorMatched + customers 客户合并列表 + 6 人数汇总字段) |
|
||||
|
||||
入参无变化;无字段删除、无类型变更、无枚举变更。纯**出参新增字段**,前端旧逻辑可继续按原字段渲染,新字段为增强。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **方法/路径**:`GET /v3/admin/order/group-batch/{groupBatchId}/settlement/reports/group`
|
||||
- **鉴权**:管理后台登录(与团期核单录入同权限)
|
||||
- **说明**:团期核单聚合复核详情。`finalized=false`(未结算)时同样实时装配财务总览,字段照常返回。
|
||||
|
||||
---
|
||||
|
||||
## 4. 入参
|
||||
|
||||
无变化。路径参数 `groupBatchId`(团期 ID,Long)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参
|
||||
|
||||
在原有团级汇总字段基础上,**新增**以下字段(与常规订单核单财务总览同口径、同平铺风格)。所有金额为 `BigDecimal`,货币单位元;缺省时返回 `0`(不返回 null)。
|
||||
|
||||
### 5.1 金额字段(11 个,全团在团子订单合并求和)
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `baseOrderAmount` | BigDecimal | 订单基础金额合计(Σ 各户 order_amount) |
|
||||
| `otherIncomeAmount` | BigDecimal | 有效增费合计(Σ 各户 surcharge_amount) |
|
||||
| `discountAmount` | BigDecimal | 有效优惠合计(Σ 各户 discount_amount) |
|
||||
| `adjustedReceivableAmount` | BigDecimal | 调整后应收 = Σ 各户 calcPayable = order + surcharge − discount |
|
||||
| `onlinePaidAmount` | BigDecimal | 成功线上支付合计(权威源,Σ 各户成功线上交易) |
|
||||
| `offlinePaidAmount` | BigDecimal | 有效线下收款合计(权威源,Σ 各户有效手工收款) |
|
||||
| `primaryReporterCollectedAmount` | BigDecimal | 各户主报账人(DRIVER_CASH 渠道)代收合计;仅展示,已包含在 `offlinePaidAmount` 内 |
|
||||
| `paidAmount` | BigDecimal | 已收合计(镜像口径 Σ 各户 paid_amount,与 outstanding 同源) |
|
||||
| `actualRefundedAmount` | BigDecimal | 实际退款合计(镜像口径 Σ 各户 refunded_amount) |
|
||||
| `netPaidAmount` | BigDecimal | 净已收 = paidAmount − actualRefundedAmount |
|
||||
| `outstandingAmount` | BigDecimal | **待收尾款** = Σ 各户 calcBalance(= calcPayable − refunded − paid,已取消恒 0、下限 0),与 `/finance` 应收台账同源 |
|
||||
|
||||
### 5.2 镜像校验位(4 个)
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `surchargeMirrorMatched` | Boolean | 增费镜像是否匹配权威明细(数据质量信号,仅供内部核查) |
|
||||
| `discountMirrorMatched` | Boolean | 优惠镜像是否匹配权威明细 |
|
||||
| `paidMirrorMatched` | Boolean | 已收镜像是否匹配权威明细 |
|
||||
| `refundedMirrorMatched` | Boolean | 退款镜像是否匹配权威明细(**仅对拍已退完成 REFUNDED 口径**,在途退款不计,避免在途期间恒 false) |
|
||||
|
||||
> 前端一般不需要展示这 4 个字段,它们是给后端/排查用的数据质量信号。
|
||||
|
||||
### 5.3 客户合并信息 `customers`
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `customers` | `List<GroupSettlementCustomerItemVO>` | 全团在团子订单的客户信息,一户一行 |
|
||||
|
||||
**GroupSettlementCustomerItemVO**(一户一行):
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `orderId` | String | 子订单 ID(Long 序列化为字符串,防 JS 精度丢失) |
|
||||
| `orderNo` | String | 子订单号 |
|
||||
| `teamNo` | String | 子订单团内编号(如有) |
|
||||
| `customerName` | String | 联系人姓名(**明文**,与团期财务列表现状一致) |
|
||||
| `customerPhone` | String | 联系人手机号(**已脱敏**,如 `138****5678`) |
|
||||
| `travelerCount` | Integer | 该户人数(含婴儿) |
|
||||
|
||||
### 5.4 人数汇总(6 个,含婴儿统一口径)
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `householdCount` | Integer | 在团户数(排除已取消子订单) |
|
||||
| `travelerCount` | Integer | 总人数 = adult + child + youngChild + **baby** |
|
||||
| `adultCount` | Integer | 成人数 |
|
||||
| `childCount` | Integer | 儿童数 |
|
||||
| `youngChildCount` | Integer | 幼儿数 |
|
||||
| `babyCount` | Integer | 婴儿数 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
无新增枚举。本接口不复用订单状态枚举,全部为金额/客户/人数字段。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
无新增错误码。复用团期域既有错误码(如团期不存在返回对应业务错误)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型(已结算团,finalized=true)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"groupBatchId": "123",
|
||||
"finalized": true,
|
||||
"baseOrderAmount": 12800.00,
|
||||
"otherIncomeAmount": 600.00,
|
||||
"discountAmount": 300.00,
|
||||
"adjustedReceivableAmount": 13100.00,
|
||||
"onlinePaidAmount": 8000.00,
|
||||
"offlinePaidAmount": 2600.00,
|
||||
"primaryReporterCollectedAmount": 1200.00,
|
||||
"paidAmount": 10600.00,
|
||||
"actualRefundedAmount": 0.00,
|
||||
"netPaidAmount": 10600.00,
|
||||
"outstandingAmount": 2500.00,
|
||||
"surchargeMirrorMatched": true,
|
||||
"discountMirrorMatched": true,
|
||||
"paidMirrorMatched": true,
|
||||
"refundedMirrorMatched": true,
|
||||
"householdCount": 2,
|
||||
"travelerCount": 6,
|
||||
"adultCount": 4,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 1,
|
||||
"customers": [
|
||||
{
|
||||
"orderId": "9001",
|
||||
"orderNo": "HL20260901001",
|
||||
"teamNo": "A1",
|
||||
"customerName": "张三",
|
||||
"customerPhone": "138****5678",
|
||||
"travelerCount": 3
|
||||
},
|
||||
{
|
||||
"orderId": "9002",
|
||||
"orderNo": "HL20260901002",
|
||||
"teamNo": "A2",
|
||||
"customerName": "李四",
|
||||
"customerPhone": "139****1234",
|
||||
"travelerCount": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(未结算团,finalized=false,字段照常返回)
|
||||
|
||||
未结算(团期未 finalize)时,财务总览字段仍**实时装配**返回(金额字段/客户列表/人数照常),`finalized=false`,无快照段。前端可直接用同一套字段渲染,无需区分。
|
||||
|
||||
### 8.3 异常(团期不存在)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 5xxxxx,
|
||||
"msg": "团期不存在"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **数据范围**:金额与客户合并**只统计在团子订单**,**排除已取消(CANCELLED)子订单**。已取消订单的金额不计入任何合计。
|
||||
- **待收尾款口径**:`outstandingAmount` 与 `/finance` 应收台账 `totalPendingBalance` 同源(都是 Σ calcBalance,含实退、下限 0),两端数值可对拍一致。
|
||||
- **主报账人代收**:`primaryReporterCollectedAmount` 仅展示用,其金额已包含在 `offlinePaidAmount` 中,**不要重复加总**。
|
||||
- **手机号**:`customerPhone` 已脱敏;`customerName` 明文(管理后台财务查看权限内,与团期财务列表现状一致)。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比(修改类)
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 出参结构 | 仅团级汇总快照(订单总额/已收/欠收等粗粒度) | 新增 21 字段:11 金额 + 4 mirrorMatched + customers + 6 人数,与常规订单核单财务总览同结构 |
|
||||
| 线上/线下拆分 | 无 | 有(onlinePaidAmount / offlinePaidAmount / primaryReporterCollectedAmount) |
|
||||
| 待收尾款 | 无(前端需自行用「应收−已付」硬减,未扣实退口径错误) | 有(outstandingAmount,与 /finance 同源,口径正确) |
|
||||
| 客户信息 | 无 | 有(customers 一户一行,含脱敏手机号) |
|
||||
| 人数 | 无 | 有(含婴儿 6 个分档汇总) |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚(修改类)
|
||||
|
||||
- **兼容性**:纯出参新增字段,前端按原字段渲染不受影响;新字段为增强,可渐进接入。
|
||||
- **性能**:单团装配固定 9 次查询封顶(无 N+1),空团短路;金额内存聚合,性能可控。
|
||||
- **回滚**:回退本次 merge commit 即可恢复原出参;新字段无持久化、无 DDL,回滚无数据迁移成本。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 新字段**实时装配**,不读快照表;`finalized=false` 时也照常返回。
|
||||
2. `paidAmount` / `actualRefundedAmount` 是 order_main 镜像口径(用于与 outstanding 同源自洽),线上/线下拆分走权威源——两者求和理论上应等于 `netPaidAmount` 相关口径,差异会体现在 `paidMirrorMatched` 校验位。
|
||||
3. 本 PR 是团期核单详情对齐的 **PR-1(财务总览 + 客户合并 + 人数口径)**;后续 **PR-2** 还会补多 tab 明细(step1 住宿/step2 门票/step3 车辆/导游/摄影/餐食/其他收入/其他支出/返还),届时另行推 changelog。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue: https://git.1814.love/wx/HL/issues/8510
|
||||
- PR: https://git.1814.love/wx/HL/pulls/8546
|
||||
- Commit: https://git.1814.love/wx/HL/commit/306f9873d30a567331afed12b91c45af176418bf
|
||||
- 负责人:腰苏图
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8515"
|
||||
title: "供应商注册提交:未绑定企业微信的管理员立即返回 395020"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "只新增一种失败返回,前端按现有方式展示 message 即可"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商: 注册提交未绑定企业微信时立即返回失败
|
||||
|
||||
> **服务**: hl-resource-service
|
||||
> **PR**: #8535
|
||||
> **Issue**: #8515
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 管理端供应商「提交审批」(注册审批)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 以前:未绑定企业微信的管理员提交注册审批,接口先返回成功、供应商转「注册审核中」,随后后台建企业微信审批单失败,审批记录落「建单失败」,自动重提 4 次后才退回草稿。
|
||||
- 现在:注册审批走企业微信时,接口当场返回 `395020`「当前账号未绑定企业微信,请先绑定企业微信后再提交」,供应商保持提交前状态,不产生审批记录,也不自动重提。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 提交供应商注册审批 | POST | `/admin/supplier/items/{supplierId}/submit` | 新增一种失败返回 | 提交人未绑定企业微信时返回 395020 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 提交供应商注册审批 `POST /admin/supplier/items/{supplierId}/submit`
|
||||
|
||||
**VO**: `SupplierSubmitReqVO` → `SupplierApprovalCommandRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理员把草稿供应商提交注册审批。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| supplierId | Path | Long | ✅ | 正整数 | 草稿供应商 ID |
|
||||
| expectedUpdateTime | Body | String | ✅ | `yyyy-MM-dd HH:mm:ss` | 乐观锁,取详情里的 updateTime |
|
||||
| 其余注册资料字段 | Body | - | - | 同现有提交接口 | 本次未改 |
|
||||
|
||||
#### 出参 `Result<SupplierApprovalCommandRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| approvalLogId | String | 审批记录 ID |
|
||||
| requestNo | String | 幂等请求号 |
|
||||
| provider | String | 审批方式,企业微信为 `WECOM` |
|
||||
| approvalStatus | String | 审批状态,提交后为 `PENDING` |
|
||||
| approvalStatusName | String | 审批状态中文名 |
|
||||
| spNo | String | 企业微信审批单号,异步建单完成前为空 |
|
||||
| syncStatus | String | 同步状态 |
|
||||
| submittedAt | String | 提交时间 |
|
||||
|
||||
本次出参不变。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedUpdateTime": "2026-09-29 17:30:00",
|
||||
"fullName": "额尔古纳市室韦镇界河木刻楞民宿有限公司"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalLogId": "2104890000000000001",
|
||||
"provider": "WECOM",
|
||||
"approvalStatus": "PENDING",
|
||||
"approvalStatusName": "审核中",
|
||||
"syncStatus": "REQUESTING"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口没有空数据。用户服务暂不可用、查不到提交人时不拦截,照原流程进入审批,由后台建单时再检查绑定。
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "provider": "WECOM", "approvalStatus": "PENDING" }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395020,
|
||||
"message": "当前账号未绑定企业微信,请先绑定企业微信后再提交",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 校验顺序:登录与写权限 → 企业微信绑定 → 信用定级权限(请求带信用定级时)→ 资料校验 → 重名确认 → 进入审批。没有基础写权限时仍先返回原有权限错误;带信用定级但没有该权限、又未绑定企业微信时,先返回 395020。
|
||||
- 「未绑定」指提交人管理员账号上的企业微信 ID 为空。
|
||||
- 只在资源服务启动时按企业微信审批装配(`supplier.approval.provider=WECOM`)时校验;本地自动审批模式不校验。
|
||||
- 查不到管理员或用户服务暂不可用时不拦截,照原流程进入审批,由后台建单时再检查绑定。
|
||||
- 返回 395020 时零写入:供应商状态、资料、审批记录、变更记录都不变,也不会自动重提。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误调用对照
|
||||
|
||||
| 场景 | 结果 |
|
||||
|------|------|
|
||||
| ✅ 已绑定企业微信的管理员提交 | 200,进入注册审核中 |
|
||||
| ❌ 未绑定企业微信的管理员提交 | 395020,供应商保持原状态 |
|
||||
|
||||
请求体不变,没有新增或删除字段。管理员先在账号上绑定企业微信,再重新提交即可。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 场景 | 供应商状态 | 审批记录 | 变更记录 |
|
||||
|------|-----------|----------|----------|
|
||||
| 未绑定,返回 395020 | 不变 | 不新增 | 不新增 |
|
||||
| 已绑定,提交成功 | 注册审核中 | 新增一条 | 同改前 |
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 已绑定企业微信的管理员:流程不变,照常进入注册审核中并创建企业微信审批单。
|
||||
- 提交后才解绑的:仍由后台建单时的检查拦下(原有「建单失败」逻辑不变)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 未绑定企业微信的管理员提交注册审批 | 返回成功,随后落「建单失败」并自动重提,约 2 分钟后退回草稿 | 当场返回 395020,供应商保持原状态 |
|
||||
| 已绑定企业微信的管理员提交注册审批 | 进入注册审核中 | 不变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否(只新增一种失败返回)
|
||||
- **前端是否必须同步上线**: 否
|
||||
- **前端 workaround 清理点**: 无
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 供应商注册「提交审批」接口
|
||||
- **零影响**:
|
||||
- 供应商状态变更审批(暂停、拉黑等)、合作中资料变更审批
|
||||
- 用户服务建企业微信审批单的原有检查
|
||||
- 395020 在状态变更审批中的原有文案「当前账号未绑定企业微信」
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
TEST 已部署合并提交 `41bc1be10`,经 Gateway 实测:
|
||||
|
||||
```
|
||||
管理员未绑定企业微信 → POST /admin/supplier/items/{SUP260106}/submit → 395020「当前账号未绑定企业微信,请先绑定企业微信后再提交」,约 3 秒返回 ✓
|
||||
被拦后:供应商仍为草稿、资料未改写、审批记录和变更记录都不新增,130 秒后仍无自动重提 ✓
|
||||
管理员已绑定企业微信 → POST /admin/supplier/items/{SUP260103}/submit → 200,provider=WECOM,进入注册审核中并生成企业微信审批单 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8515](https://git.1814.love/wx/HL/issues/8515)
|
||||
- 关联 PR: [wx/HL#8535](https://git.1814.love/wx/HL/pulls/8535)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8515](https://git.1814.love/wx/HL/issues/8515)
|
||||
- **PR**: [#8535](https://git.1814.love/wx/HL/pulls/8535)
|
||||
- **Merge commit**: [41bc1be10](https://git.1814.love/wx/HL/commit/41bc1be10)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @lc
|
||||
@@ -0,0 +1,475 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8517"
|
||||
title: "预支审批中心列表与审批 / 驳回只放行财务与管理员(585008),撤回只放行申请人本人与管理员(585009),带 nacos 回滚开关"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "已合并 dev-v3(e51832e8d)并部署 TEST,自签 token 经网关实测:列表 11 角色矩阵、不存在 advanceId 的三个写接口、真实预支的审批 / 驳回 / 撤回正反路径、nacos 开关关闭→还原往返(md5 逐字节还原)与服务端 ADVANCE_ACL_DENY 日志正反两面。前端判 not_required:审批中心菜单与通过 / 驳回按钮本来只授超管 / 管理员 / 财务;新错误码由 request.js 拦截器统一弹 message;撤回按钮对非申请人仍显示,点了会提示 585009,出参里没有申请人 id 供前端判断显隐(本单不改出参)。"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 预支审批中心列表与审批 / 驳回 / 撤回补角色守卫
|
||||
|
||||
**服务**: hl-order-service-v3
|
||||
**PR**: `#8531`(已合入 `dev-v3`,合并提交 `e51832e8d`)
|
||||
**Issue**: #8517
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
🔴 **审批中心列表、审批通过、驳回:只放行超管、管理员、财务,其余角色返回 `585008`。** 改前任意后台账号都能看全公司预支、能审批(订单级预支审批通过会生成出纳待付款),只有团期管理员被挡。
|
||||
|
||||
🔴 **撤回:只放行申请人本人和超管、管理员,其余返回 `585009`。** 财务也不能撤别人的预支,要拦应走驳回并填原因。
|
||||
|
||||
🟢 **四个接口的入参、出参、成功码全部不变。** 有权限的调用方行为与改前一致。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
预支按设计由财务审批:管理后台「预支审批」菜单和通过 / 驳回按钮只授给超管、管理员、财务。但这四个接口此前只拒团期管理员,界面上进不去的角色直接调接口就能审批、查看全量预支、撤掉别人待审批的预支。本单补上接口层的判权。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 修改 | **新增判权**:只放行 `SUPER_ADMIN` / `ADMIN` / `FINANCE`,其余 `585008`;出参不变 |
|
||||
| 2 | 预支审批通过 | PUT | `/v3/admin/order/advance/:advanceId/approve` | 修改 | **新增判权**:同上,其余 `585008`;出参不变 |
|
||||
| 3 | 预支审批驳回 | PUT | `/v3/admin/order/advance/:advanceId/reject` | 修改 | **新增判权**:同上,其余 `585008`;出参不变 |
|
||||
| 4 | 撤回待审批预支 | DELETE | `/v3/admin/order/advance/:advanceId` | 修改 | **新增判权**:只放行申请人本人与 `SUPER_ADMIN` / `ADMIN`,其余 `585009`;出参不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 预支审批列表 `GET /v3/admin/order/advance-approvals/page`
|
||||
|
||||
**VO**: `AdvanceApprovalPageReqVO` → `Result<PageResult<AdvanceApprovalPageItemRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台「财务管理 → 预支审批」页的列表。**本单起只有超管、管理员、财务能查。**
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| page | Query | Integer | ❌ | ≥1 | 页码。**行为不变** |
|
||||
| pageSize | Query | Integer | ❌ | ≥1 | 每页条数。**行为不变** |
|
||||
| status | Query | String | ❌ | `SUBMITTED` / `APPROVED` / `REJECTED` / `PAID` | 缺省 `SUBMITTED`。**行为不变** |
|
||||
| 其余筛选项 | Query | — | ❌ | — | orderId / keyword / scope / payeeName / createdByName / 提交时间区间。**行为不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | `PageResult<AdvanceApprovalPageItemRespVO>` | **结构完全不变**(`records` / `total` / `page` / `pageSize`) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=20&status=SUBMITTED HTTP/1.1
|
||||
Authorization: Bearer <财务账号 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无数据时 `records` 为空数组、`total` 为 0(行为不变)。**降级**:nacos `advance.acl.enforce.role-guard` 置 `false` 时回到改前行为(任意后台角色可查),但服务端每次仍留一条 `ADVANCE_ACL_DENY` 日志(`enforced=false`)。开关默认 `true`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 本单 |
|
||||
|----|------|------|------|
|
||||
| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务(含缺角色的 token) | 🆕 新增 |
|
||||
| 585005 | `ADVANCE_STATUS_ILLEGAL` | `status` 不是合法取值 | 不变 |
|
||||
|
||||
`585008` 实打响应体(TEST,2026-09-29,定制师):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585008,
|
||||
"message": "仅财务或管理员可查看预支审批、审批或驳回预支",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 判权在查询之前,被拒时不返回任何数据。
|
||||
- 团期管理员也返回 `585008`(本接口改前对它不设防)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 预支审批通过 `PUT /v3/admin/order/advance/:advanceId/approve`
|
||||
|
||||
**VO**: `OrderAdvanceRespVO`(无请求体,出参 `Result<OrderAdvanceRespVO>`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
预支审批页「通过」按钮:待审批(`SUBMITTED`)→ 已审批(`APPROVED`);订单级预支同时生成出纳待付款。**本单起只有超管、管理员、财务能批。**
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | `OrderAdvanceRespVO` | **结构完全不变**;`status=APPROVED`,`approvedBy` 为审批人姓名 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/advance/2104861625016287233/approve HTTP/1.1
|
||||
Authorization: Bearer <财务账号 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2104861625016287233",
|
||||
"status": "APPROVED",
|
||||
"approvedBy": "jw",
|
||||
"rejectReason": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据语义。**降级**:nacos 开关置 `false` 时回到改前行为(除团期管理员外任意后台角色可批),仍留 `ADVANCE_ACL_DENY` 日志。团期管理员的 `581008` 不读该开关,关掉也照拒。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 本单 |
|
||||
|----|------|------|------|
|
||||
| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务。**先于预支存在性校验**,预支不存在也返回本码 | 🆕 新增 |
|
||||
| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员(排在最前) | 不变 |
|
||||
| 585000 | `ADVANCE_NOT_FOUND` | 有权角色操作不存在的预支 | 不变 |
|
||||
| 585005 | `ADVANCE_STATUS_ILLEGAL` | 预支不是待审批 | 不变 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585008,
|
||||
"message": "仅财务或管理员可查看预支审批、审批或驳回预支",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 被拒时零写入:预支状态不变,也不会生成出纳待付款。
|
||||
- 定制师审批自己申请的预支同样返回 `585008`。
|
||||
- 本单不限制「管理员 / 财务审批自己申请的预支」(职责分离不在本单范围)。
|
||||
|
||||
---
|
||||
|
||||
### 3. 预支审批驳回 `PUT /v3/admin/order/advance/:advanceId/reject`
|
||||
|
||||
**VO**: `RejectAdvanceReqVO` → `Result<OrderAdvanceRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
预支审批页「驳回」按钮:待审批(`SUBMITTED`)→ 已驳回(`REJECTED`),须填驳回原因。**本单起只有超管、管理员、财务能驳。**
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** |
|
||||
| reason | Body | String | ✅ | 非空 | 驳回原因。**行为不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | `OrderAdvanceRespVO` | **结构完全不变**;`status=REJECTED`,`rejectReason` 为驳回原因 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/advance/2104861622562627585/reject HTTP/1.1
|
||||
Authorization: Bearer <财务账号 token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"reason": "门票已由地接社统一采购,无需个人垫付"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2104861622562627585",
|
||||
"status": "REJECTED",
|
||||
"approvedBy": "jw",
|
||||
"rejectReason": "门票已由地接社统一采购,无需个人垫付"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据语义。降级口径同「审批通过」。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 本单 |
|
||||
|----|------|------|------|
|
||||
| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务;先于存在性校验 | 🆕 新增 |
|
||||
| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员 | 不变 |
|
||||
| 585000 / 585005 | — | 预支不存在 / 不是待审批 | 不变 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585008,
|
||||
"message": "仅财务或管理员可查看预支审批、审批或驳回预支",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 被拒时零写入:状态与驳回原因都不变。
|
||||
|
||||
---
|
||||
|
||||
### 4. 撤回待审批预支 `DELETE /v3/admin/order/advance/:advanceId`
|
||||
|
||||
**VO**: `Result<Void>`(无请求体)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
订单详情「预支」弹窗、团期详情「财务」页签里的「撤回」按钮:撤掉一笔还没审批的预支。**本单起只有申请人本人和超管、管理员能撤。**
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | `null` | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
DELETE /v3/admin/order/advance/2104861626131980289 HTTP/1.1
|
||||
Authorization: Bearer <申请人本人 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据语义。降级:nacos 开关置 `false` 时回到改前行为(除团期管理员外任意后台角色可撤),仍留日志。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
| 码 | 符号 | 触发 | 本单 |
|
||||
|----|------|------|------|
|
||||
| **585009** | `ADVANCE_REVOKE_FORBIDDEN` | 既不是这笔预支的申请人,也不是超管 / 管理员(**财务也返回本码**)。非管理员撤不存在的预支也返回本码 | 🆕 新增 |
|
||||
| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员 | 不变 |
|
||||
| 585000 | `ADVANCE_NOT_FOUND` | 超管 / 管理员撤不存在的预支 | 不变 |
|
||||
| 585005 | `ADVANCE_STATUS_ILLEGAL` | 预支已审批 / 已驳回 | 不变 |
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585009,
|
||||
"message": "仅申请人本人或管理员可撤回该预支",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 「申请人」指提交这笔预支的后台账号,按账号 ID 判定,不按姓名。
|
||||
- 被拒时零写入:预支不删。
|
||||
- 期初结转等没有申请人记录的存量预支,只有超管、管理员能撤。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 新错误码 `585008` / `585009` 直接透出 `message` 即可。
|
||||
- 撤回按钮目前对所有能看到待审批预支的人显示;非申请人点击会收到 `585009`。出参里没有申请人账号 ID,本单不改出参,按钮显隐暂不能按申请人精确控制。
|
||||
- 同时挂财务与其它角色的账号,要切到财务角色才能进审批中心(后端按当前角色判定)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 零 DDL、零数据迁移。
|
||||
- 撤回的「申请人」取预支记录既有的创建人字段(提交预支时自动写入);TEST 现存预支该字段全部有值。
|
||||
- 被拒绝的请求不写库。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 有权限的调用方:与改前完全一致。
|
||||
- 缺角色的 token:列表 / 审批 / 驳回返回 `585008`;撤回只要是申请人本人仍可撤。
|
||||
- 系统内部调用(定时任务、消息消费等无请求的场景)不受限制。
|
||||
- 团期管理员:审批 / 驳回 / 撤回仍先返回 `581008`;列表返回 `585008`。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 角色 | 列表 | 审批 / 驳回 | 撤回别人的预支 | 撤回自己的预支 |
|
||||
|------|------|------|------|------|
|
||||
| `SUPER_ADMIN` / `ADMIN` | 正常 → 正常 | 正常 → 正常 | 正常 → 正常 | 正常 → 正常 |
|
||||
| `FINANCE` | 正常 → 正常 | 正常 → 正常 | **正常 → 585009** | 正常 → 正常 |
|
||||
| `CUSTOMIZER` | **正常 → 585008** | **正常 → 585008** | **正常 → 585009** | 正常 → 正常 |
|
||||
| 车务 / 房务 / 房务组长 / 运营 / 客服 | **正常 → 585008** | **正常 → 585008** | **正常 → 585009** | 正常 → 正常 |
|
||||
| `GROUP_BATCH_MANAGER` | **正常 → 585008** | 581008 → 不变 | 581008 → 不变 | 581008 → 不变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:对有权限的调用方否;对无权限的调用方,原来能成功的请求改为返回新错误码。
|
||||
- **前端是否必须同步上线**:否。
|
||||
- **回滚**:nacos `advance.acl.enforce.role-guard` 置 `false`,TEST 实测 5.6 秒内生效,不需重发服务。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 创建预支 `POST /v3/admin/order/:orderId/advance`、借款对象候选、本单预支列表 `GET /v3/admin/order/:orderId/advances`:判权不变。
|
||||
- 团期发起预支、团期财务页签的预支列表:判权不变。
|
||||
- 财务出纳付款:本单未改。
|
||||
- 小程序端:无影响。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**环境**:TEST(`https://api.test.1814.love`) **验证时间**:2026-09-29 17:08~17:15
|
||||
**构建身份**:order-v3 部署 `dev-v3 @ e51832e8d`(本单合并提交),17:08 完成。零写入判据:旧字节里定制师查审批列表返回 200 和数据,新字节才会返回 `585008`;部署后连打 6 次全部 `585008`,同时管理员 200。
|
||||
**身份**:自签 token 直打网关,未只用超管;TEST 上没有持财务角色的账号,财务身份用 `role=FINANCE` 的自签 token。
|
||||
|
||||
### 8.1 列表角色矩阵
|
||||
|
||||
| 角色 | 结果 |
|
||||
|---|---|
|
||||
| SUPER_ADMIN / ADMIN / FINANCE | `200`,`status=PAID` 返回 2 条 |
|
||||
| CUSTOMIZER / VEHICLE_MANAGER / ROOM_MANAGER / house_keeper_lead / GROUP_BATCH_MANAGER / OPERATOR / CUSTOMER_SERVICE / 缺角色 | **`585008`**,`data` 为 `null` |
|
||||
|
||||
### 8.2 不存在的预支 ID(零写入)
|
||||
|
||||
| 角色 | 审批 | 驳回 | 撤回 |
|
||||
|---|---|---|---|
|
||||
| CUSTOMIZER / VEHICLE_MANAGER | **585008** | **585008** | **585009** |
|
||||
| FINANCE | 585000(过守卫) | 585000(过守卫) | **585009** |
|
||||
| ADMIN | 585000 | 585000 | 585000 |
|
||||
| GROUP_BATCH_MANAGER | 581008 | 581008 | 581008 |
|
||||
|
||||
前后预支表、出纳执行单表行数不变。
|
||||
|
||||
### 8.3 真实预支的正反路径
|
||||
|
||||
在待出发订单 `HL20260918082629372` 上以定制师身份提交 4 笔预支(门票 380、餐费 260、门票 150、餐费 120):
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| 其他定制师 / 车务管理员 / 申请人本人审批 380 那笔 | 均 **585008**,仍待审批,出纳执行单 0 行 |
|
||||
| 其他定制师驳回 | **585008**,驳回原因为空 |
|
||||
| 财务、其他定制师撤回 | 均 **585009**,未删除 |
|
||||
| 财务驳回 | `200`,已驳回,出纳执行单 0 行 |
|
||||
| 其他定制师审批 260 那笔 | **585008**;随后财务审批 `200`,已审批,出纳执行单 1 行 |
|
||||
| 申请人本人撤回 150 那笔 | `200`,已软删 |
|
||||
| 管理员撤回 120 那笔(别人申请的) | `200`,已软删 |
|
||||
|
||||
### 8.4 nacos 回滚开关往返
|
||||
|
||||
配置 `hl-order-service-v3-test.yml`(`tenant=test`)原本不含 `advance.*` 键,即默认 `true`。
|
||||
|
||||
| 态 | 配置 | 定制师查列表 | 定制师审批不存在的预支 | 定制师撤不存在的预支 | 团期管理员审批 |
|
||||
|---|---|---|---|---|---|
|
||||
| A | 无键(默认 `true`) | `585008` | `585008` | `585009` | `581008` |
|
||||
| B | 追加 `role-guard: false` | `200`(5.6 秒内生效) | `585000`(回到改前) | `585000`(回到改前) | `581008`(不受开关影响) |
|
||||
| C | 逐字节还原 | `585008`(9.3 秒内生效) | — | — | — |
|
||||
|
||||
- 发布带 `casMd5`,还原写在 `finally` 里;还原后 md5 与原值同为 `c2206934960057f70b7173159046dc54`。
|
||||
- 服务端日志(两个实例合计):`ADVANCE_ACL_DENY ... enforced=false` 只出现在 B 态的 17:14:37~43,共 9 条,与 B 态调用次数一致;A、C 两态只有 `enforced=true`。
|
||||
|
||||
### 本地证据
|
||||
|
||||
| 项 | 读数 |
|
||||
|---|---|
|
||||
| 新增 4 个测试类 | 51 例全绿(守卫 29、绑定器 6、Controller 7、撤回 9) |
|
||||
| 预支包 + ArchTest + 错误码门禁 + 切片清单 | 249/0/0/0 |
|
||||
| order-v3 全量(有 Docker) | 14519 例,9F / 8E;红的 7 个类在基底 `48e66bd06` 上逐类、逐用例名一致,本单零新增失败 |
|
||||
| 变异 | 删掉审批端点守卫 → 1 例红;开关判定上提到方法头 → 12 例红;已还原 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue `#8517`;PR `#8531`
|
||||
- `docs/finance/api/API-SPEC-FINANCE-V1.0.html` §2.4.4(补判权口径)
|
||||
- `docs/group/团期模块接口文档-v2.0.html` §0B.9、`docs/group/数据模型.html` §A.11.12、`docs/group/实施单/11-财务与预支.html`(错误码表补 585008 / 585009)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8517](https://git.1814.love/wx/HL/issues/8517)
|
||||
- **PR**: [#8531](https://git.1814.love/wx/HL/pulls/8531)
|
||||
- **Merge commit**: [e51832e8d](https://git.1814.love/wx/HL/commit/e51832e8d)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @jw
|
||||
@@ -0,0 +1,379 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8518"
|
||||
title: "派单看板列表与汇总下发用车需求类别 requirementKind,支持按类别筛选"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "2f65d1f756f98453acf91cd32d1866eed3f01f5c"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8534(fix(fleet): 派单看板列表下发用车需求类别并支持按类别筛,关联 #8518)已合并 dev-v3,滚动部署测试服 hl-fleet-service @ dfb5db832(2026-09-29 17:48:30)。requirementKind/requirementKindLabel 两字段与同名可选筛选参数已实测:基线 47 条,TRAVEL 37/TRANSFER 10,两档相加等于基线且交集为空,requirementId 集合与基线一致;非法值返 100001。前端 2026-09-30 已交付:看板列表加「类别」列直显 requirementKindLabel(NTag info/warning 读英文码,不自译),orderKind 页签旁加类别页签且列表+汇总两接口同传(空=不过滤),与 orderKind 可同传 AND;586 例全绿。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 派单看板下发用车需求类别 `requirementKind`
|
||||
|
||||
**服务**: hl-fleet-service
|
||||
**PR**: #8534(已合入 `dev-v3`,squash `dfb5db832`)
|
||||
**Issue**: #8518
|
||||
**日期**: 2026-09-29
|
||||
**影响范围**: 管理后台车务「派单看板」的列表与汇总两个读口
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 派单看板「团期订单 / 全部订单」列表上,同一订单若同时存在行程用车与接送机两条用车需求,会出**两张卡**——两张卡的订单号、团号、客户、定制师、行程日期、人数**逐字相同**,此前没有任何字段能分辨哪张是接送机。
|
||||
- `GET /admin/fleet/board/orders` 的 `data.records[]` **新增 `requirementKind` / `requirementKindLabel` 两个字段**,恒成对非空:`requirementKind` 取值 `TRAVEL`(行程用车)/ `TRANSFER`(接送机),`requirementKindLabel` 是对应中文标签,由后端下发,前端不要自己做 `kind → 中文` 的映射。
|
||||
- `GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` **新增同名可选查询参数 `requirementKind`**,两个接口共用同一入参 VO。不传或传空串 = 不过滤,两类都返。
|
||||
- `requirementKind` 与既有的 `orderKind` 是**两个互不相交的维度**:`orderKind` 分订单归属(`ALL`/`NORMAL`/`GROUP`),`requirementKind` 分需求类别。两者可同传按 AND 组合,串用不会报错,只会筛出错误的行数,详见「四、契约约束与正确调用方式」。
|
||||
- **非法取值不被静默容忍**:非 `TRAVEL`/`TRANSFER` 且非空一律 **HTTP 200 + body `code=100001`**,`data=null`、`success=false`。
|
||||
- `GET /admin/fleet/board/summary` 的 `statusCounts` 与 `statusOptions[].count` 随 `requirementKind` **一起收窄**;`idleVehicleCount` / `idleDriverCount` 是全局物理资源指标,不受该筛选影响。
|
||||
- **`statusCounts` 里的 `unassignedUrgent` / `holdingUrgent` 是 `unassigned` / `holding` 的子集**(`statusOptions` 里以 `urgentCount` 形式出现),不是独立状态桶,前端加总时不要重复计入。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
车务派单看板存在同订单出两张卡、字段完全相同、无法分辨哪张是接送机的问题(wx/HL#8518)。本次为每条 record 补充需求类别下发(`requirementKind`/`requirementKindLabel`),并给列表与汇总两个读口各加一个同名可选筛选参数,用于把两类需求分列展示或过滤。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 派单看板列表 | GET | `/admin/fleet/board/orders` | 新增出参字段 + 可选入参 | 新增 `requirementKind`/`requirementKindLabel` 出参字段;新增可选筛选参数 `requirementKind`,非法值返 `100001` |
|
||||
| 2 | 派单看板汇总 | GET | `/admin/fleet/board/summary` | 新增可选入参 | 新增可选筛选参数 `requirementKind`(与列表共用同一入参 VO),`statusCounts`/`statusOptions[].count` 随之收窄 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 派单看板列表 `GET /admin/fleet/board/orders`
|
||||
|
||||
**VO**: `BoardOrderPageReqVO → BoardOrderRecordVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务「派单看板」主列表。同一订单同时存在行程用车与接送机两条用车需求时会各出一张卡,此前两卡逐字段相同、无法分辨。本次每条 record 补充需求类别,前端可据此区分两张卡,或用新增的 `requirementKind` 查询参数直接按类别筛选。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| requirementKind | Query | String | 否 | `TRAVEL` / `TRANSFER`;其余非空值返 `100001` | 🆕 本次新增。用车需求类别筛选:`TRAVEL`=只看行程用车,`TRANSFER`=只看接送机。不传或传空串=不过滤,两类都返。与既有 `orderKind`(订单归属维度)互不相交,可同传按 AND 组合 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.records[].orderId | String | 订单 ID |
|
||||
| data.records[].requirementId | String | 用车需求 ID |
|
||||
| data.records[].teamNo | String | 团号 |
|
||||
| data.records[].virtualPending | Boolean | 是否为还没有任何派车行的虚拟待派卡片 |
|
||||
| data.records[].requirementKind | String | 🆕 本次新增。用车需求类别:`TRAVEL` 行程用车 / `TRANSFER` 接送机。取本条记录**所属需求自身**的类别,恒非空 |
|
||||
| data.records[].requirementKindLabel | String | 🆕 本次新增。类别中文标签:`行程用车` / `接送机`。由后端下发,前端不要自己做 `kind→中文` 的映射,与 `requirementKind` 恒成对非空 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRANSFER HTTP/1.1
|
||||
Host: <测试服网关>
|
||||
Authorization: Bearer <token>
|
||||
|
||||
(GET 无请求体)
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为按 `requirementKind=TRANSFER` 过滤后(实测命中 10 条)摘录其中 1 条,仅列本次相关字段与几个已知存在的字段(完整响应还含既有其余字段,本文档未逐一核对不重复列出);`orderId` 为 2026-09-29 17:48 测试服实测命中的真实并存订单之一(该订单同时存在 TRAVEL、TRANSFER 两条记录),`requirementId`/`teamNo` 为示意值:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"orderId": "2101566624467419137",
|
||||
"requirementId": "<示意值,真实用车需求 ID>",
|
||||
"teamNo": "<示意值,真实团号>",
|
||||
"virtualPending": false,
|
||||
"requirementKind": "TRANSFER",
|
||||
"requirementKindLabel": "接送机"
|
||||
}
|
||||
],
|
||||
"total": 10,
|
||||
"page": 1,
|
||||
"pageSize": 100
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本次测试窗口内 `requirementKind=TRAVEL`(37 条)与 `requirementKind=TRANSFER`(10 条)均非空,未专门验证 0 命中场景。`requirementKind` 非法取值走「错误响应」,不属于本节的空数据场景。
|
||||
|
||||
order-v3 整体不可达、取不到需求身份时的降级口径:`requirementKind` 回退 `TRAVEL`,与该场景下特殊诉求/备注回退快照同属既有降级口径。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`requirementKind` 非法(非 `TRAVEL`/`TRANSFER` 且非空),2026-09-29 测试服实测原文:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100001,
|
||||
"message": "参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:BOGUS",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
HTTP 状态行仍是 **200**,判据在 body 的 `code` / `success`,不要只看状态码。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 真实卡与**虚拟待派卡**(`virtualPending=true`)一律非空——实测 47 条里 10 条虚拟待派卡两字段全部有值。
|
||||
- **纯接送机订单**(没有 active 行程用车需求)如实返 `TRANSFER`,不受「顶层 `requirementId` 恒指 `TRAVEL`」那条既有契约影响。
|
||||
- 类别取自卡片自身归属的那条需求,不读订单级单值字段——单值恒取身份列表首项(两类并存时是 `TRAVEL`)。
|
||||
- order-v3 降级取不到需求身份时回退 `TRAVEL`(与该场景下特殊诉求/备注回退快照同属既有降级口径)。
|
||||
- `requirementKind` 与 `orderKind` 是两个互不相交的维度,串用不会报错,只会筛出错误的行数,详见「四、契约约束与正确调用方式」。
|
||||
|
||||
---
|
||||
|
||||
### 2. 派单看板汇总 `GET /admin/fleet/board/summary`
|
||||
|
||||
**VO**: `BoardOrderPageReqVO → BoardSummaryVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
派单看板顶部的状态页签计数来源,与列表接口共用同一套筛选参数(同一入参 VO)。切换需求类别筛选时要和列表接口同步传同一个 `requirementKind`,否则会出现「列表条数与状态页签计数对不上」的界面表现。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| requirementKind | Query | String | 否 | `TRAVEL` / `TRANSFER`;其余非空值返 `100001` | 🆕 本次新增,与列表接口同名同取值、同缺省语义,两接口共用同一入参 VO `BoardOrderPageReqVO` |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.statusCounts | Object | 各状态桶计数,随 `requirementKind` 一起收窄 |
|
||||
| data.statusCounts.unassigned | Integer | 待派车状态桶计数,随 `requirementKind` 收窄 |
|
||||
| data.statusCounts.unassignedUrgent | Integer | `unassigned` 的**子集**(加急),不是独立状态桶 |
|
||||
| data.statusCounts.holding | Integer | 排车中状态桶计数,随 `requirementKind` 收窄 |
|
||||
| data.statusCounts.holdingUrgent | Integer | `holding` 的**子集**(加急),不是独立状态桶 |
|
||||
| data.statusOptions[].count | Integer | 状态下拉选项计数,随 `requirementKind` 一起收窄,与 `statusCounts` 同口径 |
|
||||
| data.statusOptions[].urgentCount | Integer | 该状态下的加急子集计数 |
|
||||
| data.idleVehicleCount | Integer | 空闲车辆数:全局物理资源指标,**不受** `requirementKind` 影响 |
|
||||
| data.idleDriverCount | Integer | 空闲司机数:全局物理资源指标,**不受** `requirementKind` 影响 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRAVEL HTTP/1.1
|
||||
Host: <测试服网关>
|
||||
Authorization: Bearer <token>
|
||||
|
||||
(GET 无请求体)
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为字段结构示意,`statusCounts` 内部逐桶数值为**示意拆分**(本次仅验证 `requirementKind=TRAVEL` 六桶之和 = 37,与列表 `total=37` 对齐,未逐桶记录具体读数;真实数值见「八、测试环境已验证」):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"statusCounts": {
|
||||
"unassigned": "<示意值,六桶之和已实测=37>",
|
||||
"unassignedUrgent": "<示意值,unassigned 的子集>",
|
||||
"holding": "<示意值>",
|
||||
"holdingUrgent": "<示意值,holding 的子集>"
|
||||
},
|
||||
"statusOptions": [
|
||||
{ "count": "<示意值>", "urgentCount": "<示意值>" }
|
||||
],
|
||||
"idleVehicleCount": "<全局值,不随 requirementKind 变化>",
|
||||
"idleDriverCount": "<全局值,不随 requirementKind 变化>"
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本次测试窗口内 `requirementKind=TRAVEL`/`TRANSFER` 两档六桶合计均非空(37/10)。降级口径与列表接口相同:order-v3 整体不可达时 `requirementKind` 判定回退 `TRAVEL`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`requirementKind` 非法取值时与列表接口同一错误码,2026-09-29 测试服实测原文:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100001,
|
||||
"message": "参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:BOGUS",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **`statusCounts` 的 `unassignedUrgent` / `holdingUrgent` 是 `unassigned` / `holding` 的子集**(`statusOptions` 里以 `urgentCount` 出现),**不是独立状态桶**,前端加总统计时不要重复计入。
|
||||
- `idleVehicleCount` / `idleDriverCount` 是全局物理资源指标,切换 `requirementKind` 时这两个数字不受影响。
|
||||
- 切换需求类别筛选页签时,务必与列表接口同步传同一个 `requirementKind`,否则会出现列表条数与状态页签计数对不上的情况。
|
||||
- `requirementKind` 非法取值的错误码、报文格式与列表接口完全一致。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### `requirementKind` 与 `orderKind` 是两个互不相交的维度
|
||||
|
||||
| 维度 | `orderKind` | `requirementKind` |
|
||||
|------|-------------|--------------------|
|
||||
| 问题 | 这个订单当前属不属于某个运营团期 | 这条用车需求本身是行程用车还是接送机 |
|
||||
| 取值 | `ALL` / `NORMAL` / `GROUP` | `TRAVEL` / `TRANSFER` |
|
||||
| 缺省 | 不传或空串 = `ALL`(不过滤) | 不传或空串 = 不过滤(两类都返) |
|
||||
| 组合方式 | 与 `requirementKind` 按 AND 组合 | 同左 |
|
||||
|
||||
两者语义完全独立,**串用不会报错,只会筛出错误的行数**——例如把 `requirementKind` 误传成了 `orderKind` 的取值(如 `orderKind=TRANSFER`),不会命中任何非法校验(`TRANSFER` 不在 `orderKind` 枚举内,会被 `orderKind` 自己的校验拦成 `100001`),但如果误把 `orderKind` 的取值传给 `requirementKind`(如 `requirementKind=GROUP`),同样会被 `requirementKind` 自己的校验拦截,报文里的字段名与传入值都能定位到问题,不会静默放行成一个"看似合理"的过滤结果。
|
||||
|
||||
### 非法取值处理
|
||||
|
||||
`requirementKind` 非 `TRAVEL`/`TRANSFER` 且非空 → `code=100001`,报文格式固定为 `参数非法: requirementKind 仅支持 TRAVEL/TRANSFER,传入非法值:<原始传入值>`,HTTP 状态行仍是 200,判据在 body。
|
||||
|
||||
### 两接口需同步传参
|
||||
|
||||
`GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` 共用同一入参 VO(`BoardOrderPageReqVO`)。切换需求类别页签时必须把 `requirementKind` 同时传给两个接口,否则会出现「列表 10 条、状态页签写着 47 条」这类界面对不上的情况。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本次变更的两个接口都是只读 `GET`,**零数据库写入**,不产生任何落库副作用。`requirementKind` 只影响查询结果的过滤范围,不改写任何行。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 真实卡与虚拟待派卡(`virtualPending=true`)在 `requirementKind`/`requirementKindLabel` 两个新字段上**一律非空**——实测 47 条里 10 条虚拟待派卡两字段全部有值。
|
||||
- 纯接送机订单(没有 active 行程用车需求)如实返 `TRANSFER`,不受「顶层 `requirementId` 恒指 `TRAVEL`」那条既有契约影响。
|
||||
- 需求类别取自卡片自身归属的那条需求,不读订单级单值字段——订单级单值字段恒取身份列表首项(两类并存时是 `TRAVEL`)。
|
||||
- order-v3 整体不可达、取不到需求身份时,`requirementKind` 回退 `TRAVEL`,与该场景下特殊诉求/备注回退快照同属既有降级口径。
|
||||
- `requirementKind` 大小写敏感:只有精确的 `TRAVEL`/`TRANSFER` 合法。
|
||||
- `requirementKind` 与既有全部筛选条件(含 `orderKind`、`groupBatchId`、`statuses`、日期、车型、`consultantId`、`keyword`)都是 AND 组合。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### requirementKind
|
||||
|
||||
**所属字段**: `requirementKind`(两个接口共用的查询参数,同名出现在列表响应的 `data.records[].requirementKind`) | **类型**: `String`
|
||||
|
||||
| 值 | 中文标签(`requirementKindLabel`) | 说明 |
|
||||
|----|-----------------------------------|------|
|
||||
| `TRAVEL` | 行程用车 | 常规行程用车需求 |
|
||||
| `TRANSFER` | 接送机 | 接送机用车需求 |
|
||||
|
||||
不传、传空串 = 不过滤,两类都返。其余任何取值(含大小写不符)返 `100001`。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `requirementKind`(两个接口的 query) | 不存在,传了被忽略 | 可选参数,`TRAVEL`/`TRANSFER`,缺省不过滤,非法值返 `100001` |
|
||||
| `data.records[].requirementKind`(列表响应) | 不存在 | 🆕 新增字段,恒非空,取该卡片自身归属需求的类别 |
|
||||
| `data.records[].requirementKindLabel`(列表响应) | 不存在 | 🆕 新增字段,中文标签,与 `requirementKind` 恒成对非空 |
|
||||
| `statusCounts` / `statusOptions[].count`(汇总响应) | 不随需求类别过滤 | 随 `requirementKind` 一起收窄 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 同订单行程用车+接送机并存 | 两张卡逐字段相同,前端无法分辨哪张是接送机 | 两张卡各自携带 `requirementKind`/`requirementKindLabel`,可据此区分 |
|
||||
| 按需求类别筛选看板 | 不支持 | 支持 `requirementKind=TRAVEL`/`TRANSFER` 直接筛 |
|
||||
| 传了不识别的 `requirementKind` | 参数不存在,被忽略 | 返 `code=100001`,不静默放行 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。不传 `requirementKind` 的旧调用行为与改前完全一致(不过滤,两类都返),响应只新增字段,不删改任何既有字段。
|
||||
- **前端是否必须同步上线**: 视需求而定——若要解决「同订单两张卡无法区分接送机」这个问题,需要前端读取新字段渲染区分,或使用新参数筛选;不读取新字段时界面行为与改动前完全一致,不会报错。
|
||||
- **前端 workaround 清理点**: 此前前端没有任何字段可用于区分两类需求;若曾用行程备注、行程日期或别的间接线索猜测哪张卡是接送机,可以改用 `requirementKind` 精确判断。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `GET /admin/fleet/board/orders` 与 `GET /admin/fleet/board/summary` 两个读口。
|
||||
- **零影响**:
|
||||
- `orderKind` 维度及其既有筛选行为(本次未改动该维度任何逻辑);
|
||||
- 派车、改派、取消等所有写口(本次改动只涉及看板列表与汇总两个读口);
|
||||
- `idleVehicleCount` / `idleDriverCount` 全局物理资源指标;
|
||||
- 小程序端全部接口(派单看板为管理后台专属能力)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服 hl-fleet-service 已部署 `dfb5db832`(2026-09-29 17:48:30)。窗口 `orderKind=ALL&pageSize=100` 实测:
|
||||
|
||||
```
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100 → 200,基线 47 条
|
||||
requirementKind 分布 {TRAVEL: 37, TRANSFER: 10}
|
||||
requirementKindLabel 分布 {行程用车: 37, 接送机: 10}
|
||||
两字段 47 条全部非空(含 10 条虚拟待派卡)
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRAVEL → 200,返回 37 条,全为 TRAVEL
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=TRANSFER → 200,返回 10 条,全为 TRANSFER
|
||||
37 + 10 = 47(两档相加等于基线,requirementId 集合与基线完全一致)
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind= → 200,返回 47 条,与不传一致
|
||||
GET /admin/fleet/board/orders?orderKind=ALL&pageSize=100&requirementKind=BOGUS → 200 + code 100001
|
||||
GET /admin/fleet/board/summary?orderKind=ALL → 六个状态桶合计 47
|
||||
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRAVEL → 六个状态桶合计 37
|
||||
GET /admin/fleet/board/summary?orderKind=ALL&requirementKind=TRANSFER → 六个状态桶合计 10
|
||||
三档与列表 total 逐一对齐(47/37/10)
|
||||
```
|
||||
|
||||
同一订单出两张卡(TRAVEL + TRANSFER 并存)的订单实测 4 个,例如 `2101566624467419137`、`2101146798373339137`。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8518](https://git.1814.love/wx/HL/issues/8518)
|
||||
- 关联 PR: [wx/HL#8534](https://git.1814.love/wx/HL/pulls/8534)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8518](https://git.1814.love/wx/HL/issues/8518)
|
||||
- **PR**: [#8534](https://git.1814.love/wx/HL/pulls/8534)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端责任人**: wx(GIT)
|
||||
- **问题反馈**: wx/HL Issue #8518 评论区
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8528"
|
||||
title: "团期配车重排新增资源态硬校验,响应回填被 clearAll 忽略的行程日"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8553 合并 dev-v3(7b702f5c3f);hl-fleet-service dev-v3 分支部署测试网关 @ e2982739e8(含 7b702f5c3f)并实测:排入 rest 司机返 605038、排入 DISABLED 车辆返 605037(均一行未落库);正常 ACTIVE 车辆 7 天全排返 addedCount=7;clearAll=true 场景返 ignoredDemandDays 回填生效。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期配车重排:新增资源态硬校验,响应回填被 clearAll 忽略的行程日
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8087)
|
||||
> **PR**: #8553
|
||||
> **Issue**: #8528 #8529
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 管理后台团期配车页「整团逐日配车提交」
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 团期配车重排提交时,本次新增或就地改动的配车行,其车辆与司机的当前状态(是否维保/停用/休假/待激活/黑名单/非在册赛季)现在会被硬校验,不可派即整批提交回滚(工单 #8528)。
|
||||
- 响应新增字段 `ignoredDemandDays`:`clearAll=true` 时把未被写入的行程日回填给前端(工单 #8529)。此前 `clearAll=true` 提交后无法区分「清完并按新计划重排」与「只清空」,两者响应里 `addedCount` 都是 0。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 整团逐日配车提交 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` | 新增错误码 + 新增响应字段 | 605037/605038 新增;605006/605013 文案带占位符;响应新增 `ignoredDemandDays` |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 整团逐日配车提交 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure`
|
||||
|
||||
**VO**: `GroupDispatchReconfigureReqVO` → `GroupDispatchReconfigureRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务在团期配车页提交/重排整团逐日配车计划:服务端按乘车分组与现状差量比对,多删少补,旧记录软删留痕。本次改动新增两类内容:①提交时对新增/就地改的配车行做车辆与司机的当前可派性硬校验;②`clearAll=true` 时把未写入的行程日回填进响应。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| requirementId | Body | Long | ✅ | - | 正式团级用车需求 ID;与基线不一致抛 602005 |
|
||||
| requirementVersion | Body | Integer | ✅ | - | 需求版本;落后于基线当前版本抛 602005 |
|
||||
| clearAll | Body | Boolean | 否 | 默认 false | true=整团清零,`demands` 仅当待清日用,不写入任何配车行 |
|
||||
| survivorPolicy | Body | String | 条件必填 | `KEEP_LEGAL`/`REASSIGN`/`RELEASE` | 仅 `clearAll=true` 且该团存在 active 共用关系时必填,缺失抛 602110 |
|
||||
| demands | Body | List<GroupDispatchDayDemandReqVO> | 条件必填 | - | `clearAll=false` 时必填且非空;每项含 `tripDate` + `assignments`(车辆/司机/分组),本次不变 |
|
||||
| reconfigureWindowToken | Body | String | 条件必填 | - | 团期过资源准备阶段后必填,本次不变 |
|
||||
|
||||
#### 出参 `Result<GroupDispatchReconfigureRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ignoredDemandDays | List<LocalDate> | **新增(#8529)**:因 `clearAll=true` 未被写入的行程日清单,格式 `yyyy-MM-dd`;`clearAll=false` 时恒为空列表,不会是 null |
|
||||
| addedCount / removedCount / keptCount / updatedCount / aliveCount | Integer | 结构不变 |
|
||||
| coverage | GroupDispatchCoverageRespVO | 结构不变,含 `wholeBatchSatisfied`(全团行程日整体覆盖是否成立)等字段 |
|
||||
| 其余字段 | - | 结构不变,与既有契约一致(本次不变) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
正常提交(7 天全排 ACTIVE 车辆场景,节选一天):
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementId": "5501",
|
||||
"requirementVersion": 3,
|
||||
"clearAll": false,
|
||||
"demands": [
|
||||
{ "tripDate": "2026-09-12", "assignments": [ { "groupId": "BUS", "vehicleId": "1001", "driverId": "2001" } ] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
clearAll 场景:
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementId": "5501",
|
||||
"requirementVersion": 3,
|
||||
"clearAll": true,
|
||||
"survivorPolicy": "RELEASE",
|
||||
"demands": [
|
||||
{ "tripDate": "2026-11-10", "assignments": [] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
7 天全排成功:
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "addedCount": 7, "coverage": { "wholeBatchSatisfied": true } }, "success": true }
|
||||
```
|
||||
|
||||
clearAll 场景(行程日被回填进 `ignoredDemandDays`):
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "addedCount": 0, "ignoredDemandDays": ["2026-11-10"] }, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据形态;命中资源态硬校验或既有校验失败时 `data=null`,见错误响应。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 605038, "message": "司机处于休假或待激活状态,不能派车:苏和巴特尔", "data": null, "success": false }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 605037, "message": "车辆处于维保或停用状态,不能派车:蒙C02E02", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 605037/605038/605006/605013 四个码新增的占位符文案同样出现在逐户派单写路径(`POST /admin/fleet/assignments` 及改派端点),二者共用同一个资源态校验组件;前端若对这 4 个码有硬编码文案匹配,两条路径都要一起改。
|
||||
- 资源态校验是整批拒绝:任意一天新增/就地改的车辆或司机不可派,会回滚本次整团提交,不是部分成功。
|
||||
- 本次未改动的存量配车行不重判——车辆/司机事后状态变化不会把整团重配卡死,只挡本次新增/就地改的行。
|
||||
- 车辆/司机已被删除时,占位符退回请求里携带的主键(`ID=车辆ID` / `ID=司机ID`),不是报 500。
|
||||
- `ignoredDemandDays` 在 `clearAll=false` 时恒为空列表(不是 null),前端可无条件取其长度判断有无回填项。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ✅ 排入正常 ACTIVE 车辆/司机 | 正常提交,返回 `code=200` |
|
||||
| ❌ 排入 DISABLED/维保车辆 | 任意排车项使用该车辆 → `605037` |
|
||||
| ❌ 排入休假/待激活司机 | 任意排车项使用该司机 → `605038` |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
收到 605037/605038/605006/605013 直接把 `message` 展示给车务,引导其在该排车项上更换车辆或司机后重新提交;本端点无独立幂等键字段,防重仅靠既有 10 秒窗口,修正后正常重提即可。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
本次未新增表、未新增列。资源态硬校验发生在写入前(校验车辆/司机当前状态),校验不通过时整批回滚、不产生任何 `fleet_group_dispatch` 写入;`ignoredDemandDays` 是内存计算结果、不落库。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 车辆/司机已被删除 → 错误码占位符退回请求里携带的主键(`ID=车辆ID`/`ID=司机ID`),不是 500
|
||||
- 本次未改动的存量配车行不参与资源态重判
|
||||
- `clearAll=false` 时 `ignoredDemandDays` 恒为空列表,不是 null
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
本次未新增或变更任何枚举取值;`survivorPolicy`(`KEEP_LEGAL`/`REASSIGN`/`RELEASE`)沿用既有契约,未变化。
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.ignoredDemandDays` | 不存在 | 新增,`clearAll=true` 时回填未被写入的行程日 |
|
||||
| 605006 `message` | `司机已黑名单,不能派车` | `司机已黑名单,不能派车:{司机姓名}`(缺姓名退回 `ID=司机ID`) |
|
||||
| 605013 `message` | `司机非在册赛季不可派单` | `司机非在册赛季不可派单:{司机姓名}`(缺姓名退回 `ID=司机ID`) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 排入维保/停用车辆 | 无该项硬校验,可能带着不可派车辆落库 | 605037 拒绝,整批回滚 |
|
||||
| 排入休假/待激活司机 | 无该项硬校验,可能带着不可派司机落库 | 605038 拒绝,整批回滚 |
|
||||
| `clearAll=true` 提交 | 响应无法区分「清完重排」与「只清空」 | `ignoredDemandDays` 回填未写入日期 |
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
- **是否破坏向后兼容**: 否——605037/605038 是新增错误码,605006/605013 只在文案末尾追加占位符文本(前端如做精确字符串匹配需要更新);`ignoredDemandDays` 是新增字段,旧前端忽略它不受影响。
|
||||
- **前端是否必须同步上线**: 否——新增字段/错误码是可选适配,未处理时行为退化为"看不到具体车牌/司机名,只看到通用错误码提示",不影响提交本身的成败判定。
|
||||
- **前端 workaround 清理点**: 若此前靠 `addedCount===0` 猜测「clearAll 是否清空后又重排」,可以换成直接读 `ignoredDemandDays`。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 管理后台团期配车页「整团逐日配车提交」(`POST reconfigure`)
|
||||
- **零影响**:
|
||||
- 确认整团配车端点(`POST confirm`)本次未改动响应结构(其资源态硬校验为工单 #8528 同批改动,但不在本 changelog 覆盖范围内)
|
||||
- 团期配车四个读口(总览/就绪/共用关系/共用候选,见另一份 changelog)
|
||||
- 既有错误码(600003-600011、602005-602012 等)语义与格式不变
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-fleet-service`,dev-v3 分支部署测试网关 @ `e2982739e8`(含 #8528/#8529 所在提交 `7b702f5c3f`),测试网关 `https://api.test.1814.love`:
|
||||
|
||||
```
|
||||
✓ 排入 driverStatus=rest 的司机 → code=605038, message="司机处于休假或待激活状态,不能派车:苏和巴特尔",一行未落库
|
||||
✓ 排入 DISABLED 车辆(车牌 蒙C02E02)→ code=605037, message="车辆处于维保或停用状态,不能派车:蒙C02E02",一行未落库
|
||||
✓ 正常 ACTIVE 车辆 7 天全排 → code=200, addedCount=7, coverage.wholeBatchSatisfied=true(回归未破坏)
|
||||
✓ clearAll=true 场景 → code=200, data.addedCount=0, data.ignoredDemandDays=["2026-11-10"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8528](https://git.1814.love/wx/HL/issues/8528)、[wx/HL#8529](https://git.1814.love/wx/HL/issues/8529)
|
||||
- 关联 PR: [wx/HL#8553](https://git.1814.love/wx/HL/pulls/8553)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8528](https://git.1814.love/wx/HL/issues/8528)、[#8529](https://git.1814.love/wx/HL/issues/8529)
|
||||
- **PR**: [#8553](https://git.1814.love/wx/HL/pulls/8553)
|
||||
- **Merge commit**: [7b702f5c3f](https://git.1814.love/wx/HL/commit/7b702f5c3f)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,332 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8530"
|
||||
title: "团期用车需求换组时把旧版已排车行整槽平移,响应新增 migratedAssignmentCount 平移下界字段"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "38bcab548c5d71317d0f56c6d0051c7fe7a0d4fd"
|
||||
target_release: ""
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8563 合并 dev-v3(cb876bf780);hl-order-service-v3 + hl-fleet-service 已随该提交部署测试网关;order-v3 新增单测 6 条(RequirementServiceTest)+ fleet 新增单测 6 条(AssignmentServiceTest)+ mapper 层 2 条 + 跨服务常量 1 条,共 15 条,均为换组迁移/无配车迁移/同内容重放不迁移/非团期恒 0/订单终态不迁移等场景的定向用例。;前端已交付:FunItemAdjustModal 成功提示附换组平移基数(>0 显「旧版 N 行配车将平移至新需求」,0/统一提交路径原提示),spec 3 例,checkpoint 全绿(hl-admin 38bcab54)"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期用车需求换组:响应新增 migratedAssignmentCount 平移下界字段
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3(响应端点所在服务)+ hl-fleet-service(旧版配车整槽平移的执行方,经 Outbox/Feign 异步处理,前端不直接调用它)
|
||||
> **PR**: #8563
|
||||
> **Issue**: #8530
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 管理后台订单详情页「提交/修改/调整用车需求」接口的响应体(仅团期子订单换组场景新增字段值有意义)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 团期子订单在「改提/换组」用车需求时,旧版本名下已经排好的车行,此前会原地留在已失活的旧 `requirementId` 下,车务侧对这些行的任何后续推进都会撞错误码 605905/605913 且没有任何提示;现在后端会在换组的同一时刻把这些行整槽平移到新需求。
|
||||
- 响应 VO `VehicleRequirementRespVO` 新增字段 `migratedAssignmentCount`(`Integer`,恒非 `null`):本次换版交给车务平移的配车基数(下界),**不是**真实迁移行数。它在所有分支(首提/改提/完成后调整/幂等重放)都会回填,取 0 或正整数,从不为 `null`。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 提交/修改/调整用车需求 | PUT | `/v3/admin/order/{id}/vehicle-requirement` | 响应新增字段 | `data.migratedAssignmentCount` |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 提交/修改/调整用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`
|
||||
|
||||
**VO**: `VehicleRequirementReqVO` → `VehicleRequirementRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
定制师/团期管理员在订单详情页提交、修改或调整用车需求。后端按 `order_vehicle_requirement` 表当前 active 行是否存在及其状态自动判三分支:无 active → `INIT_SUBMIT` 首提;`PENDING` → `PENDING_EDIT` 改提;`DONE` → `DONE_ADJUST` 完成后调整。同内容重放(与当前生效版本完全一致)额外命中 `IDEMPOTENT_NOOP`,不换版。`consultantId` 由后端从 JWT 解析,不接受前端传入。
|
||||
|
||||
团期子订单每次「改提/换组」(即 `PENDING_EDIT` 分支且确实发生换版)都会触发本次改动:旧版本名下已经排好的车行会被整槽平移到新需求,响应回填 `migratedAssignmentCount` 说明本次交给车务平移的基数。非团期(核心)订单、首次提交、同内容重放三种情况都不触发平移,字段恒为 0。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `id` | Path | Long | 是 | 正整数 ID | 订单 ID |
|
||||
| `kind` | Body | String | 否 | `TRAVEL`/`TRANSFER`,不传按 `TRAVEL` | 需求类别:`TRAVEL`=团期行程用车(服务日冻结为行程日),`TRANSFER`=接送机(服务日由大交通派生) |
|
||||
| `fleet` | Body | List\<FleetItem\> | 是 | 至少 1 项 | 车型组合 |
|
||||
| `fleet[].vehicleType` | Body | String | 是 | `suv`/`mpv`/`bus`/`sedan` | 车型大类 key(只选大类,不选具体车型) |
|
||||
| `fleet[].seats` | Body | Integer | 是 | 需在该大类座位选项内 | 座位数 |
|
||||
| `fleet[].count` | Body | Integer | 是 | >0 | 辆数 |
|
||||
| `specialTags` | Body | List\<String\> | 否 | 需在字典 `vehicle_special_demand` 内 | 通用特殊诉求标签,应用到全部车辆 |
|
||||
| `pickupRequired` | Body | Boolean | 否 | - | 兼容字段,接机/接站以实时大交通批次为准 |
|
||||
| `dropoffRequired` | Body | Boolean | 否 | - | 兼容字段,送机/送站以实时大交通批次为准 |
|
||||
| `remark` | Body | String | 否 | ≤500 字符 | 备注 |
|
||||
|
||||
本次改动**未新增或修改任何入参字段**,上表为该端点既有契约,供本节自包含阅读。
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data.id` | Long | 需求行 ID |
|
||||
| `data.kind` | String | 需求类别:`TRAVEL`/`TRANSFER` |
|
||||
| `data.serviceDates` | List\<LocalDate\> | 本版冻结的服务日期,升序去重 |
|
||||
| `data.version` | Integer | 版本号 |
|
||||
| `data.isActive` | Boolean | 是否为当前生效版本 |
|
||||
| `data.status` | String | 需求状态(`PENDING`/`PENDING_REVIEW`/`PROCESSING`/`DONE` 等) |
|
||||
| `data.branchTaken` | String | 实际走的分支:`INIT_SUBMIT`/`PENDING_EDIT`/`DONE_ADJUST`/`IDEMPOTENT_NOOP` |
|
||||
| `data.previousVersion` | Integer/null | `DONE_ADJUST` 分支回填上一版本号;其余分支为 `null` |
|
||||
| `data.assignmentDeletedCount` | Integer/null | `DONE_ADJUST` 分支回填软删旧配车行数;其余分支为 `null` |
|
||||
| `data.migratedAssignmentCount` | Integer | **【新增】** 本次换版交给车务平移的配车基数(下界),恒非 `null`;语义见下方业务边界 |
|
||||
| `data.passengerCount` | Integer | 订单乘车人数(成人+儿童+幼童+婴儿),结构不变 |
|
||||
| `data.vehicleCount` | Integer | 车辆总数,结构不变 |
|
||||
| `data.totalSeatCount` | Integer | 车辆座位总数(含司机座),结构不变 |
|
||||
| `data.driverSeatCount` | Integer | 司机占用座位数,结构不变 |
|
||||
| `data.passengerSeatCapacity` | Integer | 可载客座位数,结构不变 |
|
||||
| `data.remainingPassengerSeats` | Integer | 剩余可载客座位数,结构不变 |
|
||||
| `data.pickupRequired` / `data.dropoffRequired` | Boolean | 兼容回显字段,结构不变 |
|
||||
| `data.submittedAt` / `data.claimerId` / `data.claimerName` / `data.claimedAt` | - | 结构不变 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "TRAVEL",
|
||||
"fleet": [
|
||||
{ "vehicleType": "mpv", "seats": 7, "count": 1 }
|
||||
],
|
||||
"specialTags": ["中文司机"],
|
||||
"remark": "客户要求中文司机"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
团期子订单换组,旧版名下有 3 行在途配车镜像(`branchTaken=PENDING_EDIT` 且触发平移):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "2810002",
|
||||
"kind": "TRAVEL",
|
||||
"serviceDates": ["2026-07-19", "2026-07-23"],
|
||||
"version": 2,
|
||||
"isActive": true,
|
||||
"status": "PENDING_REVIEW",
|
||||
"branchTaken": "PENDING_EDIT",
|
||||
"previousVersion": null,
|
||||
"assignmentDeletedCount": null,
|
||||
"migratedAssignmentCount": 3,
|
||||
"passengerCount": 2,
|
||||
"vehicleCount": 1,
|
||||
"totalSeatCount": 7,
|
||||
"driverSeatCount": 1,
|
||||
"passengerSeatCapacity": 6,
|
||||
"remainingPassengerSeats": 4,
|
||||
"pickupRequired": true,
|
||||
"dropoffRequired": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
首次提交 / 同内容重放(`IDEMPOTENT_NOOP`),没有旧版可迁移:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "2812001",
|
||||
"branchTaken": "IDEMPOTENT_NOOP",
|
||||
"migratedAssignmentCount": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口不存在空数据形态:请求参数合法时恒返回单条需求行;下游依赖(车队字典等)不可用时走错误响应,不降级为空对象。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 582021,
|
||||
"message": "用车需求数组不能为空",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 582030,
|
||||
"message": "当前需求状态不可修改(处理中)",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
本次改动**未新增任何错误码**,以上两例是该端点既有校验错误码,供本节自包含阅读。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `migratedAssignmentCount` 是「下界」不是真实迁移行数:取值 = 换组前那一版 requirement 上挂着的已排车行数(按上一版 `requirementId` 统计 `order_vehicle_assignment`);真实平移由车务侧异步幂等执行,只搬活跃非取消行,可能小于等于该下界。
|
||||
- 连续换组(A→B 再 B→C)第二次读到的恒为 0,但迁移确实发生了:A→B 那一次已经把行迁到 B 名下,B→C 这一次按「上一版」(B)统计,B 名下此时还没有新快照(新快照要等车务重新确认后才按新需求落),统计结果恒为 0。**前端不能把 0 解读成"这条链路从未发生过迁移",只能解读成"本次调用没有新的迁移基数"。**
|
||||
- 首次提交(`INIT_SUBMIT`)与同内容重放的幂等分支(`IDEMPOTENT_NOOP`):字段恒为 0,且不触发平移信号。
|
||||
- 非团期(核心)订单:字段恒为 0,且不额外查询配车镜像表(不新增一次 DB 往返),行为与本次改动之前完全一致。
|
||||
- 订单处于终态(`COMPLETED`/`CANCELLED`)时不触发平移信号,字段为 0——即使是经由"订单调整"入口提交(该入口本身绕过普通提交闸),终态订单同样不发平移。
|
||||
- 该字段在全部四个分支(`INIT_SUBMIT`/`PENDING_EDIT`/`DONE_ADJUST`/`IDEMPOTENT_NOOP`)都会回填,恒非 `null`。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端字段语义与正确/错误解读,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误解读对照
|
||||
|
||||
| 场景 | 解读 |
|
||||
|------|------|
|
||||
| ✅ `migratedAssignmentCount > 0` | 本次换版确有旧版配车行被交给车务侧平移 |
|
||||
| ✅ `migratedAssignmentCount = 0` 且 `branchTaken = INIT_SUBMIT` / `IDEMPOTENT_NOOP` | 本来就没有触发换版,字段恒为 0,属正常值 |
|
||||
| ✅ `migratedAssignmentCount = 0` 且 `branchTaken = PENDING_EDIT` | 旧版名下当时没有在途配车镜像,或本次读到的是连续换组链路中的中间一跳 |
|
||||
| ❌ 用单次 `= 0` 断言"这条订单从未发生过配车平移" | 连续换组 A→B→C 时,B→C 这一次恒读 0,但 A→B 时已经迁移过;单次响应不是整条历史的累计值 |
|
||||
|
||||
### 不需要的前置动作
|
||||
|
||||
该字段只出现在响应里,不改变入参契约:请求体字段零变化,无需为这个字段额外传参或做请求前置。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
`migratedAssignmentCount` **不落库、不是新增列**:它是响应组装时的即时统计结果,等价于:
|
||||
|
||||
```
|
||||
SELECT COUNT(*) FROM order_vehicle_assignment
|
||||
WHERE requirement_id = <换组前的旧 requirementId> AND deleted = 0
|
||||
```
|
||||
|
||||
需求行本身仍按既有逻辑落库(旧版 `deactivate` + 新版 `insert`),本次未新增、未变更任何表结构或列。真实的配车行迁移(把 `order_vehicle_assignment.requirement_id` 从旧值改写为新值)发生在车务侧(`hl-fleet-service`),经异步 Outbox/Feign 通道执行,与本端点的同步响应解耦。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 入参校验失败(`fleet` 为空、车型非法等)→ 既有错误码,HTTP 200,`data=null`。
|
||||
- 非团期(核心)订单:`migratedAssignmentCount` 恒为 0,且不额外查询 `order_vehicle_assignment`,行为与本次改动之前一字不变。
|
||||
- 订单终态(`COMPLETED`/`CANCELLED`):不触发平移信号,`migratedAssignmentCount=0`。
|
||||
- 业务失败仍可能是 HTTP 200,需同时检查 `code`、`success` 和 `message`。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
本次未新增或变更任何枚举取值。`branchTaken` 沿用既有四个取值,未变化:
|
||||
|
||||
### branchTaken(响应字段)
|
||||
|
||||
**所属字段**: `data.branchTaken` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `INIT_SUBMIT` | 首次提交 | 订单无 active 需求行时的分支 |
|
||||
| `PENDING_EDIT` | 改提/换组 | 当前 active 需求为 `PENDING` 时的分支;团期订单在此分支触发配车平移 |
|
||||
| `DONE_ADJUST` | 完成后调整 | 当前 active 需求为 `DONE` 时的分支 |
|
||||
| `IDEMPOTENT_NOOP` | 幂等重放 | 提交内容与当前生效版本完全一致,不换版、不触发平移 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.migratedAssignmentCount` | 不存在 | 新增,`Integer`,恒非 `null`,团期换组场景回填平移基数,其余场景为 0 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 团期子订单改提/换组时,旧版名下已排的车行 | 留在已失活的旧 `requirementId` 下,车务对其任何后续推进都会撞 605905/605913,且没有任何信号 | 换组的同一时刻整槽平移到新需求,响应回填平移基数 |
|
||||
| 团期换组是否发布逐日配车快照 | 不适用(旧版本就是孤儿状态,不发快照) | 平移这一档只做整槽平移,仍不发布逐日快照(新需求停在 `PENDING_REVIEW`,须经管理员审核 + C2 提交车务后才放行) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。新增响应字段,旧前端忽略它不受影响;入参契约与既有错误码零变化。
|
||||
- **前端是否必须同步上线**: 否。字段是可选适配;若前端不读取该字段,端点行为(换组成功、旧配车被平移)照常生效,只是前端看不到"本次平移了几行"这个信息。
|
||||
- **前端 workaround 清理点**: 无。此前该场景下前端没有任何字段可用于感知"旧配车是否被平移",因此没有需要清理的旧逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 团期子订单在订单详情页「改提/换组」用车需求后的响应体,以及旧版已排车行是否被后端平移这一行为。
|
||||
- **零影响**:
|
||||
- 请求参数(`VehicleRequirementReqVO` 零变化)
|
||||
- 首次提交(`INIT_SUBMIT`)与完成后调整(`DONE_ADJUST`)两个分支的既有字段语义(`previousVersion`/`assignmentDeletedCount` 等)
|
||||
- 非团期(核心)订单提交用车需求的行为——该路径 `migratedAssignmentCount` 恒 0 且不新增 DB 查询,与改动前完全一致
|
||||
- 该端点既有错误码(零新增)
|
||||
- 派单看板、逐日配车快照发布等下游读接口的响应结构(本次不改写它们)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-order-service-v3` + `hl-fleet-service`,合并提交 `cb876bf780`(PR #8563)已合入 `dev-v3` 并部署测试网关。
|
||||
|
||||
新增单测(均为源码可核实的真实用例,覆盖以下场景):
|
||||
|
||||
`hl-order-service-v3` `RequirementServiceTest`(6 条):
|
||||
- `upsertVehicle_groupOrderPendingEdit_migratesPreviousRequirement`:团期改提换版 → 登记平移信号,`migratedAssignmentCount=3`(按旧需求 ID 精确统计,非按新需求 ID 误统计)
|
||||
- `upsertVehicle_groupOrderPendingEditNoAssignment_migratedCountZero`:旧需求名下无在途配车 → 仍登记平移信号,`migratedAssignmentCount=0`
|
||||
- `upsertVehicle_groupOrderSameContent_idempotentNoopNoMigrate`:同内容重放(`IDEMPOTENT_NOOP`)→ 不登记平移信号,不查询镜像表,`migratedAssignmentCount=0`
|
||||
- `upsertVehicle_coreOrderPendingEdit_migratedCountZero`:非团期订单改提 → `migratedAssignmentCount` 恒 0,不新增 DB 查询
|
||||
- `upsertVehicleForOrderAdjustment_groupOrderCompleted_noMigrate`:订单已完成(`COMPLETED`)→ 不发平移信号
|
||||
- `upsertVehicleForOrderAdjustment_groupOrderNotTerminal_stillMigrates`:同订单调整入口、订单非终态 → 照常发平移信号(上一条的阳性对照)
|
||||
|
||||
`hl-fleet-service` `AssignmentServiceTest`(6 条,验证可派性闸放宽范围不外溢):
|
||||
- `expand_团期改提新需求待审核_放行整槽平移`
|
||||
- `expand_非待审核需求换版平移_仍按既有出口发布逐日快照`(阳性对照)
|
||||
- `expand_不可派且非待审核需求_仍然拒绝`
|
||||
- `expand_待审核需求但无前序需求_仍然拒绝`
|
||||
- `create_当前需求待审核_仍抛605906`(放宽不外溢到一步派定)
|
||||
- `restoreCancel_当前需求待审核_仍拒绝恢复`(放宽不外溢到撤销取消)
|
||||
|
||||
另有 mapper 层 `OrderVehicleAssignmentMapperTest` 新增 2 条、跨服务状态字面量对齐 `RequirementStatusTest` 新增 1 条。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8530](https://git.1814.love/wx/HL/issues/8530)
|
||||
- 关联 PR: [wx/HL#8563](https://git.1814.love/wx/HL/pulls/8563)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8530](https://git.1814.love/wx/HL/issues/8530)
|
||||
- **PR**: [#8563](https://git.1814.love/wx/HL/pulls/8563)
|
||||
- **Merge commit**: [cb876bf780](https://git.1814.love/wx/HL/commit/cb876bf780)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8533"
|
||||
title: "团期详情(A2)出参 advanceAmount(已预支)改为与财务 Tab advanceApproved 同源——不再读 #7154 起已停写的旧列"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "A2 团期详情的 advanceAmount(已预支金额·累计)原先直读 order_group_batch.advance_amount。该列自 #7154(团期预支改为 order_advance 逐笔台账 + 实时聚合)起已无写入点:新团期永远 0;#7154 之前就有预支的老团期停在 V20260906_004 期初结转那一刻;且旧列只累加团期级、从不含子订单级。于是同一团期 A2 与财务 Tab(GB-ADM-040)advanceApproved 可以是两个数。本次 A2 改为与财务 Tab 调同一对方法(GroupBatchAdvanceQueryService.listActiveSubOrderIds + sumApproved),两位小数,字符串逐字一致。路径、入参、出参字段名与类型零变化;变的是 advanceAmount 的取值口径(旧列快照 → 团期级 ∪ 子订单级 APPROVED 实时聚合),属「语义取值」变化,按 jw 2026-09-18 口径推送。已合并 dev-v3(PR #8539,merge commit 27409a3f1)并部署测试服。hl-ui v2.1 团期详情页未渲染 advanceAmount(已预支卡在 FinanceTab 读 advanceApproved),前端零改动,frontend_status 记 not_required。PAID 不计入是 g-081 的既有口径(09-28 定暂不处理),本次只对齐来源、不改口径。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期详情(A2)已预支改为与财务 Tab 同源(管理后台)
|
||||
|
||||
> **服务**: hl-order-service-v3(端口 8086/8186)
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
团期详情(A2)的 `advanceAmount`「已预支金额(累计)」与财务 Tab(GB-ADM-040)的 `advanceApproved`「已预支」本应是同一个数,
|
||||
但两者读的来源不同:A2 直读 `order_group_batch.advance_amount` 列,财务 Tab 实时汇总 `order_advance`。
|
||||
|
||||
`advance_amount` 列从 #7154 起就没有写入点了(团期预支改为逐笔台账,唯一写入点 `addAdvance` 已删除)。
|
||||
所以新团期的 A2 永远返回 `0.00`,老团期停在期初结转那一刻,而且旧列从来不含子订单级预支。
|
||||
|
||||
本次 A2 改为与财务 Tab 调同一对方法取数,两个接口对同一团期必然同值。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 修改接口 | 出参 `advanceAmount` 取值口径改为与 GB-ADM-040 `advanceApproved` 同源;字段名、类型与其余字段零变化 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. A2 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`
|
||||
|
||||
**VO**: `GroupBatchDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情页进入时拉取整团概览。本次只改其中 `advanceAmount` 的取值来源;需要「已预支 / 待审批 / 可支取」三个数时,
|
||||
仍以财务 Tab `GET /v3/admin/order/group-batch/{groupBatchId}/finance` 为准。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 团期 ID,不存在返 589501 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| advanceAmount | BigDecimal(字符串序列化) | **本次改口径**。已预支 = Σ 本团期 `status=APPROVED` 且未软删的预支,范围是团期级(`group_batch_id` = 本团期)∪ 子订单级(`order_id` ∈ 本团活跃子订单);两位小数。与 GB-ADM-040 `advanceApproved` 同一方法、同一格式,逐字一致。SUBMITTED、REJECTED、已撤回、PAID 均不计入 |
|
||||
| receivableAmount | BigDecimal | 整团应收(既有字段,本次未改) |
|
||||
| receivedAmount | BigDecimal | 整团已收(既有字段,本次未改) |
|
||||
| unpaidAmount | BigDecimal | 整团待收(既有字段,本次未改) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104885714862899201 HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <admin token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104885714862899201",
|
||||
"batchStatus": "RECRUITING",
|
||||
"advanceAmount": "1450.00",
|
||||
"receivableAmount": "5960.00",
|
||||
"receivedAmount": "0.00",
|
||||
"unpaidAmount": "5960.00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期没有任何 APPROVED 预支时返回 `"0.00"`,不返回 null、不缺字段,与财务 Tab 一致。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104885713021587458",
|
||||
"advanceAmount": "0.00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589501,
|
||||
"message": "团期不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589501 | groupBatchId 不存在或已软删 |
|
||||
| 589507 | 缺 `group-batch:view` 权限码 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 取数与财务 Tab 共用 `GroupBatchAdvanceQueryService.listActiveSubOrderIds` + `sumApproved`,不另写 SQL。
|
||||
- 子订单全部退团后,团期级预支仍然计入(子订单集为空时照样按 `group_batch_id` 汇总)。
|
||||
- PAID 不计入是 g-081 的既有口径,本次未改;若日后 `sumApproved` 改口径,A2 与财务 Tab 会一起变。
|
||||
- `order_group_batch.advance_amount` 列保留不删,只是 A2 不再读它。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 页面展示「已预支」请继续取财务 Tab 的 `advanceApproved`;A2 的 `advanceAmount` 现在与它同值,可作为详情页概览的冗余读数。
|
||||
- 不要把 `advanceAmount` 与「待审批」「可支取」相加或互推,三个数请都取财务 Tab。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
零数据库变更。本次不新增表、列或索引,不写任何数据。`advanceAmount` 改为只读聚合 `order_advance`(与财务 Tab 同一查询),
|
||||
详情装配多两次只读查询(取本团活跃子订单 ID + 一次金额聚合),单团期无 N+1,落在既有 `@Transactional(readOnly = true)` 段内,不涉及 Feign。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 团期不存在 → 589501。
|
||||
- 团期无任何预支 → `"0.00"`。
|
||||
- 只有 SUBMITTED / REJECTED / 已撤回的预支 → `"0.00"`(与财务 Tab 一致)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `advanceAmount` 来源 | `order_group_batch.advance_amount`(#7154 起停写) | `order_advance` 实时聚合,与 GB-ADM-040 `advanceApproved` 同一方法 |
|
||||
| 新团期 | 永远 `0.00` | 实际通过的预支合计 |
|
||||
| 子订单级预支 | 不含 | 含 |
|
||||
| 字段名 / 类型 / 其余出参 | — | 逐字未变 |
|
||||
| 路径 / 入参 / 权限码 | — | 逐字未变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **兼容性**:字段名与类型不变;数值会从 0 或过期值变为实际值。hl-ui v2.1 团期详情页未渲染该字段,界面无变化。
|
||||
- **性能**:单团期多两次只读查询,无 N+1。
|
||||
- **回滚**:撤销 PR #8539 的合并提交后重新部署 order-v3,无数据与配置残留。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 财务 Tab(GB-ADM-040)、预支记录(GB-ADM-043)、预支上限校验:口径与字段零改动。
|
||||
- 团期列表、看板:不含 `advanceAmount`,未涉及。
|
||||
- 网关:路径未变、无新增路由与权限码。
|
||||
- 小程序端:本接口仅管理后台使用。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-29 18:44–19:17 测试服(`api.test.1814.love`),部署 `dev-v3 @ 27409a3f1`(order-v3 双实例 8086/8186)。
|
||||
自建两个团期、真实订单与主报账人后造预支;每组同一时刻读 A2 与 GB-ADM-040,并用 SQL 按同一口径算期望值作第三方对照。
|
||||
旧列 `order_group_batch.advance_amount` 在全部读数时刻都是 `0.00`,证明新值不来自旧列。
|
||||
|
||||
| 场景 | 造数 | A2 `advanceAmount` | GB-ADM-040 `advanceApproved` | SQL 期望 |
|
||||
|---|---|---|---|---|
|
||||
| 无预支(自建 2 团 + 现存 17 团只读) | 无 | `"0.00"` | `"0.00"` | 0.00 |
|
||||
| 只有团期级 APPROVED | 团期级 APPROVED 1200 + 待审 350 + 驳回 480 + 撤回 260 | `"1200.00"` | `"1200.00"`(`advancePending="350.00"`) | 1200.00 |
|
||||
| 团期级 + 子订单级 | 团期级 APPROVED 800 + 子订单级 APPROVED 650 + 子订单级待审 420 | `"1450.00"` | `"1450.00"`(`advancePending="420.00"`) | 1450.00 |
|
||||
|
||||
- 部署身份:在「只有团期级 APPROVED」团期上经网关连打 A2 8 次,全是 `"1200.00"`(旧代码会返回 `"0.00"`);直连 8086、8186 两实例各读,同为新值。
|
||||
- 验收造数已全部回收,回读零残留。
|
||||
|
||||
单元测试:定向 35 类 487 例 0 失败(含新增 4 条,经 surefire XML 核实真执行);团期整包 + 全部 ArchTest 3118 例中 11 例红,基底 `dfb5db832` 同样复现,属既有失败。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #8533、PR #8539(merge commit `27409a3f1`)
|
||||
- 团期预支改为逐笔台账 + 实时聚合:#7154
|
||||
- 同写法先例(详情字段与兄弟接口同源回填):#8215
|
||||
- PAID 口径遗留:台账 g-081(09-28 定暂不处理)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端:不涉及(`frontend_status: not_required`)
|
||||
@@ -0,0 +1,418 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8536"
|
||||
title: "团期配车四个读口团期不存在统一收敛为 600015;就绪判定零配车行文案调整"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8554 合并 dev-v3(e2982739e8);hl-fleet-service dev-v3 分支部署测试网关 @ e2982739e8 并实测:四个读口对不存在团期均返回 600015;有效团期零共用关系仍返回 200+data=[];就绪判定零配车行场景文案已改为「本团尚未创建任何配车行」。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期配车四个读口:团期不存在统一收敛为 600015;就绪判定零配车行文案调整
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8087)
|
||||
> **PR**: #8554
|
||||
> **Issue**: #8536 #8537
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 管理后台团期配车页四个只读端点(总览/就绪判定/共用关系查询/共用成员候选)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **破坏性变更(共用关系查询)**:此前对**不存在的团期**调用 `GET share-groups` 会返回 `HTTP 200 + code=200 + data=[]`,与"团期确有效存在但零共用关系"完全同形,前端无法区分。**现在改为返回 `code=600015`**(团期不存在)。有效团期确实零关系时仍然是 `code=200 + data=[]`,未变化。
|
||||
- 四个读口(总览/就绪判定/共用关系查询/共用成员候选)对"团期不存在"场景**统一改用 600015** 作为失败关闭码,与各自原先分散的、语义偏"可重试"的码区分开:600015 是永久性否定,前端不应引导重试。
|
||||
- 就绪判定端点在"该团尚无任何配车行"这一具体场景下,`blockers[].message` 文案从 `本团有 0 条配车行尚未确认` 改为 `本团尚未创建任何配车行`;`code` 仍是 `BLOCK_NOT_CONFIRMED`,未拆分新码。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期配车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 错误码语义收敛 | 团期不存在统一改返 600015 |
|
||||
| 2 | 团期配车就绪判定 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/readiness` | 错误码语义收敛 + 文案调整 | 团期不存在统一改返 600015;零配车行 blocker 文案调整 |
|
||||
| 3 | 查询团期车辆共用关系 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups` | 🔴 破坏性变更 | 团期不存在从 `200+data=[]` 改为 `600015` |
|
||||
| 4 | 查询共用成员候选清单 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/share-member-candidates` | 错误码语义收敛 | 团期不存在统一改返 600015 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期配车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `GroupDispatchOverviewRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务打开团期配车页时调用,取按权威服务日逐日铺开的已排车、空洞日与逐户接送机缺口。本次改动只影响"团期不存在"时的响应,成功路径字段结构未变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
|
||||
#### 出参 `Result<GroupDispatchOverviewRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | Long→String | 团期主订单 ID |
|
||||
| batchNo | String | 团号 |
|
||||
| departDate / endDate | LocalDate | 出团日 / 返团日 |
|
||||
| serviceDates | List<LocalDate> | 权威服务日集合 |
|
||||
| requirementConfirmed | Boolean | 整团需求是否已确认 |
|
||||
| vehicleReady | Boolean | 配车是否已就绪 |
|
||||
| days | List | 逐日行,按 serviceDates 铺满 |
|
||||
| missingDates | List<LocalDate> | 空洞日 |
|
||||
| orders | List | 逐户行 |
|
||||
| transferPendingTotal | Integer | 全团接送机未配计数 |
|
||||
| conversationKey | String | 团期车务会话键 |
|
||||
|
||||
以上字段结构本次均未改动。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/1934567890123456789/overview
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "groupBatchId": "1934567890123456789", "batchNo": "T26-8867", "departDate": "2026-09-12", "endDate": "2026-09-16", "requirementConfirmed": true, "vehicleReady": false, "transferPendingTotal": 3, "conversationKey": "GROUP_FLEET:1934567890123456789" }, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空对象/空 200 形态:团期不存在时不再返回任何形式的空数据,而是抛 600015,见错误响应;上游确实不可达(非团期不存在)时仍返回原有的可重试码 600012。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 600015, "message": "团期不存在: 9107777777777777777", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 团期不存在时统一改抛 600015(永久性否定),前端不应引导用户重试;此前该场景返回的是可重试语义的 600012,含义已变化。
|
||||
- 上游确实不可达(超时/熔断/降级,而非团期不存在)时仍返回原有的 600012,含义不变。
|
||||
- 600015 的判定统一由服务端集中完成,不因具体是哪个下游 Feign 端点而有条件遗漏。
|
||||
|
||||
### 2. 团期配车就绪判定 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/readiness`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `GroupDispatchReadinessRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务打开团期配车页时调用,判定"硬拦三项"是否全过(`ready`)与"只提醒两项"是否有黄牌(`warned`)。本次改动:①团期不存在统一改返 600015;②该团尚无任何配车行时的 blocker 文案调整。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
|
||||
#### 出参 `Result<GroupDispatchReadinessRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId / requirementId | Long→String | 团期主订单 ID / 判定所依据的正式需求 ID |
|
||||
| requirementVersion | Integer | 判定所依据的需求版本 |
|
||||
| planVersion | Long | fleet 侧当前计划版本 |
|
||||
| ready | Boolean | `= blockers.isEmpty()`,硬拦三项是否全过 |
|
||||
| warned | Boolean | `= !warnings.isEmpty()`,是否有只提醒项,与 ready 互相独立 |
|
||||
| blockers / warnings | List | 硬拦未过项 / 只提醒项,每项含 `code` + `message` |
|
||||
| groups | List | 逐组覆盖明细,与配车写口 `coverage` 同源 |
|
||||
| shareGroupCount | Integer | 本团 active 共用关系数 |
|
||||
|
||||
以上字段结构本次均未改动,仅 `blockers[].message` 在特定场景下文案调整(见下)。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/2104838272570245121/readiness
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
该团级需求已确认、但尚无任何物理配车行(真实实测取值):
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "groupBatchId": "2104838272570245121", "ready": false, "blockers": [ { "code": "BLOCK_NOT_CONFIRMED", "message": "本团尚未创建任何配车行" } ] }, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空对象/空 200 形态:团期不存在时不再返回默认就绪对象,而是抛 600015,见错误响应;`blockers`/`warnings` 均可以是空数组(表示该维度全部通过/无提醒),空数组不是错误也不是降级。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 600015, "message": "团期不存在: 9107777777777777777", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `BLOCK_NOT_CONFIRMED` 这一个 `code` 现在对应两种不同的 `message` 文案(该团尚无任何配车行 / 该团有 N 条配车行尚未确认);两种场景下前端的处置动作相同(引导去配车页排车/确认),如果此前是按 `message` 文本内容做分支判断,请改成按 `code` 判断,不要再解析 `message` 里的具体文案或数字。
|
||||
- 团期不存在时统一改抛 600015(永久性否定),不应引导重试;`602113`(基线不可用或该团未声明任何乘车分组)含义不变,仍是可重试语义。
|
||||
- `ready` 与 `warned` 互相独立,`ready=true && warned=true` 是合法组合,本次改动未影响这一既有语义。
|
||||
|
||||
### 3. 查询团期车辆共用关系 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups`
|
||||
|
||||
**VO**: `ShareGroupQueryReqVO` → `List<ShareGroupRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务在团期配车页查看本团当前(及可选的历史)车辆共用关系。🔴 本次改动是**破坏性变更**:团期不存在时的响应形状发生变化。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| serviceDate | Query | LocalDate | 否 | `yyyy-MM-dd` | 不传=全部服务日 |
|
||||
| resourceType | Query | String | 否 | `VEHICLE`/`DRIVER` | 不传=两者;非法枚举按入参非法拒绝 |
|
||||
| includeReleased | Query | Boolean | 否 | 默认 `false` | 是否一并返回已解除的关系与变更历史 |
|
||||
|
||||
#### 出参 `Result<List<ShareGroupRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| shareGroupId / groupBatchId | Long→String | 共用关系 ID / 运营团期 ID |
|
||||
| serviceDate | LocalDate | 共用发生的服务日 |
|
||||
| resourceType | String | `VEHICLE` / `DRIVER` |
|
||||
| resourceId | Long→String | 车辆或司机 ID |
|
||||
| status | String | `ACTIVE` / `RELEASED` |
|
||||
| costBearer | String | `GROUP` / `ORDER` |
|
||||
| costBearerOrderId | Long→String | `costBearer=ORDER` 时的承担订单 ID |
|
||||
| costBearerTeamNo | String | 承担订单的团号 |
|
||||
| costSourceRefNo | String | 车费来源引用,格式 `SHARE-{shareGroupId}` |
|
||||
| members | List | 成员全集 |
|
||||
| confirmedBy / confirmedAt | Long→String / LocalDateTime | 确认人 / 确认时间 |
|
||||
| version | Integer | 乐观锁版本号 |
|
||||
| history | List | 变更历史;仅 `includeReleased=true` 时返回 |
|
||||
|
||||
以上字段结构本次未改动,仅"团期不存在"场景的响应形状变化,见下方请求/响应示例。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/2104840641651556353/share-groups
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测:团期 `2104840641651556353` 存在且零共用关系:
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": [], "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期确有效存在但零共用关系时,返回 `HTTP 200 + code=200 + data=[]`(上方响应示例即为此场景的真实实测结果)——这与"团期不存在"的 `600015` 是两个不同的信号,前端必须能区分两者,不能再把"拿到 600015 错误"当成"data 是空数组"来处理。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 600015, "message": "团期不存在: 9107777777777777777", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 **此前**对不存在的团期调用本接口返回 `code=200 + data=[]`,与"团期确有效存在但零共用关系"完全同形,无法区分;**现在**不存在的团期改为返回 `code=600015`。
|
||||
- 有效团期确实零共用关系时的响应**未变化**,仍是 `code=200 + data=[]`(见响应示例,真实实测)。
|
||||
- 如果此前把 `data.length===0` 当作"该团无共用关系"的唯一判据,现在必须新增对 `600015` 的分支处理,否则遇到不存在的团期会因为拿到错误响应、取不到 `data` 数组而报错或白屏,而不是正确显示"查无此团"。
|
||||
- `includeReleased=true` 时才返回非空的 `history` 字段,默认 `false` 时行为不变。
|
||||
|
||||
### 4. 查询共用成员候选清单 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/share-member-candidates`
|
||||
|
||||
**VO**: `ShareMemberCandidateQueryReqVO` → `List<ShareMemberCandidateRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务在团期配车页勾选共用关系成员时调用,返回的 `sourceType` + `sourceId` 直接喂给确认写口 `POST share-groups` 的 `members[]`。本次改动只影响"团期不存在"时的响应,候选行结构未变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| serviceDate | Query | LocalDate | ✅ | `yyyy-MM-dd` | 必须落在该团基线 serviceDates 内,窗外抛 602104 |
|
||||
| resourceType | Query | String | ✅ | `VEHICLE`/`DRIVER` | 非法字面量抛 `INVALID_PARAM` |
|
||||
| resourceId | Query | Long | 否 | - | 不传=浏览态,此时 `occupying`/`selectable` 恒为 `null`(未判定) |
|
||||
|
||||
#### 出参 `Result<List<ShareMemberCandidateRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| sourceType | String | `ASSIGNMENT`(逐户接送派单)/ `GROUP_DISPATCH`(团级配车行) |
|
||||
| sourceId | Long→String | 成员来源 ID,确认写口 `members[].sourceId` 直接用它 |
|
||||
| requirementId / orderId | Long→String | 用车需求 ID / 订单 ID(团级配车行为空) |
|
||||
| orderNo / teamNo / customerName | String | 订单号 / 团号(GROUP_DISPATCH 行与无团号为 null)/ 客户名 |
|
||||
| headcount | Integer | 人数 |
|
||||
| pickupAt / dropoffAt | String | 接客地 / 送客地 |
|
||||
| pickupParticipant / dropoffParticipant | Integer | 当日是否参与接机/送机,1=是 |
|
||||
| groupCode | String | 车务分组编码(非团号) |
|
||||
| vehicleModel | String | 车型,仅 ASSIGNMENT 行有值 |
|
||||
| occupiedVehicleId / occupiedVehiclePlate | Long→String / String | 当前占用车辆 ID / 车牌 |
|
||||
| occupiedDriverId / occupiedDriverName | Long→String / String | 当前占用司机 ID / 姓名 |
|
||||
| occupying | Boolean | 三态:`null`=未判定(`resourceId` 未传时) |
|
||||
| shareGroupId | Long→String | 本行已属的 ACTIVE 共用关系 ID,不属任何关系为 null |
|
||||
| selectable | Boolean | 三态:`true`=可选 / `false`=不可选 / `null`=未判定 |
|
||||
| unselectableReason / unselectableDetail | String | 不可选原因(机器可读)/ 人读补充 |
|
||||
|
||||
以上字段结构本次未改动,仅"团期不存在"场景的响应形状变化。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/8801/share-member-candidates?serviceDate=2026-09-12&resourceType=VEHICLE
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下取值取自该 VO 源码 `@ApiModelProperty` 声明的示例值(非本轮实测输出,实测仅覆盖下方"错误响应"的团期不存在场景),用于说明字段形状:
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": [ { "sourceType": "ASSIGNMENT", "sourceId": "88001", "requirementId": "5501", "orderId": "70123", "orderNo": "26-0503", "teamNo": "26-0480", "customerName": "赵先生", "headcount": 3, "pickupAt": "海拉尔机场", "dropoffAt": "满洲里口岸", "pickupParticipant": 1, "dropoffParticipant": 0, "groupCode": "G1", "vehicleModel": "别克GL8", "occupiedVehicleId": "2001", "occupiedVehiclePlate": "京A·····", "occupiedDriverId": "3001", "occupiedDriverName": "王师傅", "occupying": true, "shareGroupId": "360462416850587648", "selectable": true } ], "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无匹配候选时返回 `data=[]`,这是正常结果,不是错误;与"团期不存在"的 `600015` 不同。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 600015, "message": "团期不存在: 9107777777777777777", "data": null, "success": false }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 团期不存在时统一改抛 600015,与另外三个读口一致。
|
||||
- `occupying` / `selectable` 三态字段语义本次不变:不传 `resourceId` 时恒为 `null`(未判定),不会误报 `false`。
|
||||
- `shareGroupId` 非空表示该行已属某个共用关系;本读口不知道调用方正在编辑哪一个关系——若在追加同一关系的成员,前端仍需把该关系已有成员一并带上(成员是全集不是增量),这一点本次未变化。
|
||||
- `selectable=true` 不是提交必成功的承诺,候选清单是时点快照、不加锁,提交时仍可能撞 602106,本次未变化。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 响应 |
|
||||
|------|-----------------|
|
||||
| ✅ 查询存在的团期 | 四个读口均正常返回 `code=200` |
|
||||
| ✅ 查询存在但零共用关系的团期(share-groups) | `code=200 + data=[]` |
|
||||
| ❌ 查询不存在的团期(四个读口) | `code=600015` |
|
||||
| ❌ 继续把 `data.length===0` 当作"团期不存在"的判据 | 遇到 600015 时拿不到 `data` 数组,需新增 600015 分支 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端拦到 `code=600015` 时应提示"团期不存在"类文案并阻断当前页面的后续操作(如返回列表页重新选择团期),不要自动重试;600012/602113/600009 等其余错误码仍按原"可重试"逻辑处理,含义未变。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
本次涉及的四个接口均为只读查询,无任何数据库写操作。改动只影响 Feign 出向调用失败时的错误码分流逻辑与部分错误/提示文案,不涉及任何表结构或存量数据变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 团期不存在 → 600015(四个读口统一,本次新行为)
|
||||
- 上游确实不可达(非团期不存在,如超时/熔断降级)→ 仍返回各读口原有的可重试码(overview=600012,readiness=602113,share-groups 查询=600009,share-member-candidates=600009),含义未变
|
||||
- share-groups / share-member-candidates 无匹配数据 → `data=[]`,不是错误
|
||||
- readiness 的 `blockers`/`warnings` 为空数组 → 表示该维度全部通过/无提醒,不是错误
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### 团期不存在错误码(`GroupDispatchErrorCode`)
|
||||
|
||||
**所属字段**: 无(HTTP 响应顶层 `code`) | **类型**: `Integer`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `600015` | 团期不存在 | 上游 order-v3 明确回"团期不存在"(含已软删)时的失败关闭码;本 changelog 覆盖的四个读口统一适用;永久性否定,不应自动重试 |
|
||||
|
||||
`BLOCK_NOT_CONFIRMED` 不是新增枚举值,本次只是其 `message` 文案按"零配车行/有配车行未确认"两种子场景分化,见六.6。
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本次无响应字段新增或删除,四个接口的成功路径字段结构均未改动。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 四个读口对不存在团期的响应 | overview / readiness / share-member-candidates 返回各自原有的可重试码;share-groups 返回 `code=200 + data=[]` | 统一返回 `code=600015`(永久性否定) |
|
||||
| readiness 该团尚无任何配车行 | `blockers[].message` = "本团有 0 条配车行尚未确认" | `blockers[].message` = "本团尚未创建任何配车行"(`code` 仍是 `BLOCK_NOT_CONFIRMED`) |
|
||||
| readiness 该团有配车行但部分未确认 | `blockers[].message` = "本团有 N 条配车行尚未确认" | 不变,仍是 "本团有 N 条配车行尚未确认" |
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
- **是否破坏向后兼容**: 是——`share-groups` 对不存在团期的响应形状变化(`200+data=[]` → `600015`)是唯一的结构性破坏点;`overview`/`readiness`/`share-member-candidates` 原本就是错误响应分支,只是错误码数值变了(code 判断逻辑需同步更新,但不是从"成功"变"失败")。
|
||||
- **前端是否必须同步上线**: 是——针对 `share-groups`,若继续沿用旧的 `data.length===0` 判断"无共用关系",遇到不存在的团期会因为拿到 `600015` 错误响应、取不到 `data` 数组而报错,而不是正确显示"查无此团";针对 readiness 若有按 `message` 文本内容做分支判断的逻辑需要改成按 `code` 判断。
|
||||
- **前端 workaround 清理点**: 若此前为"team not found 但 share-groups 返回空数组"这类情况写过特殊兼容逻辑,可以确认改造后不再需要,因为现在有独立的 600015 信号可用。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 管理后台团期配车页四个只读端点在"团期不存在"场景下的响应;就绪判定端点"该团尚无任何配车行"这一特定场景的提示文案。
|
||||
- **零影响**:
|
||||
- 四个读口成功路径的响应字段结构(除本 changelog 描述的错误码分流与 readiness 文案外,无字段增删)
|
||||
- 其余可重试错误码(600009 / 600012 / 602113 等)的含义与返回条件
|
||||
- readiness 端点"该团有配车行但部分未确认"场景的提示文案
|
||||
- 团期配车写口(`reconfigure`/`confirm`)与共用关系写口(`confirm`/`release`),本次改动仅涉及只读端点
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-fleet-service`,dev-v3 分支部署测试网关 @ `e2982739e8`(含 #8536/#8537 所在提交),测试网关 `https://api.test.1814.love`:
|
||||
|
||||
```
|
||||
✓ 不存在团期 groupBatchId=9107777777777777777 → 总览/就绪判定/共用关系查询/共用成员候选 四个读口均返回 code=600015, message="团期不存在: 9107777777777777777"
|
||||
✓ 团期 groupBatchId=2104840641651556353(存在且零共用关系)→ GET share-groups 返回 code=200, data=[](与不存在团期的 600015 可区分)
|
||||
✓ 团期 groupBatchId=2104838272570245121(团级需求已确认、零物理配车行)→ GET readiness 返回 blockers=[{"code":"BLOCK_NOT_CONFIRMED","message":"本团尚未创建任何配车行"}]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8536](https://git.1814.love/wx/HL/issues/8536)、[wx/HL#8537](https://git.1814.love/wx/HL/issues/8537)
|
||||
- 关联 PR: [wx/HL#8554](https://git.1814.love/wx/HL/pulls/8554)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8536](https://git.1814.love/wx/HL/issues/8536)、[#8537](https://git.1814.love/wx/HL/issues/8537)
|
||||
- **PR**: [#8554](https://git.1814.love/wx/HL/pulls/8554)
|
||||
- **Merge commit**: [e2982739e8](https://git.1814.love/wx/HL/commit/e2982739e8)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,165 @@
|
||||
---
|
||||
schema: hl-changelog/v2
|
||||
ticket: "frontend"
|
||||
title: "团期详情「用车汇总」块,行程用车与接送机从按车型聚合改逐条列出,调用既有 /requirement/vehicle-households 接口"
|
||||
consumer: admin
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端优化"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a73ef932b4b230d70c7fab793da5c08c910d408b"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "前端 2026-09-29 已交付:两板块改逐条列表(团号/客户/车型/单车座位/台数/状态),复用 vehicle-households 经父级转发零新请求,按 kind 过滤不按状态/不用 countedInSummary,teamNo null 显—,statusName 原文直显,表尾合计=列表加总(用户拍板保留),统计摘要保留;spec 5 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: dev-v3
|
||||
---
|
||||
|
||||
# 团期详情用车汇总改逐条列出
|
||||
|
||||
> **服务**: hl-order-service-v3(**后端无改动,复用既有接口**)
|
||||
> **页面**: 管理后台 → 团期订单 → 进入团期(团期详情)→「查看需求」Tab →「用车 · 汇总」块
|
||||
> **数据源接口**: `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households`(既有,VehicleHouseholdsSection 已在调)
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 前端页面展示逻辑。行程用车与接送机块从按车型聚合的 N 行改为逐户逐行的完整列表。
|
||||
|
||||
---
|
||||
|
||||
## 一、需求
|
||||
|
||||
改变「用车 · 汇总」块的行程用车与接送机两个板块展示形态——从当前按车型聚合(显示「车型 / 合计座位 / 台数」)改为**逐条列出**(显示「团号 / 客户 / 车型 / 单车座位 / 台数」),让信息粒度与「用车 · 子订单用车需求记录」块对齐。
|
||||
|
||||
---
|
||||
|
||||
## 二、前提订正
|
||||
|
||||
当前「接送机」块**其实也是按车型汇总的**,而非逐条列表——只是恰好本次测试团的两户选了不同车型(suv / mpv),所以看起来像列表。后端 `requirement-summary` 接口的 `vehicleSeatSummary`(行程)与 `transferSummary.vehicleSeatSummary`(接送机)都用同一个聚合方法(hl-order-service-v3 `GroupBatchRequirementService.java` 第 343、346 行都调 `aggregateVehicleSeats`)。
|
||||
|
||||
wx 的原话是「行程用车也不需要汇总,list 列出来就行,和接送机需求一样就行」。既然接送机现状也是汇总,按「逐条列出」的本意,**行程用车与接送机两块都改成逐条列表**。
|
||||
|
||||
---
|
||||
|
||||
## 三、数据源
|
||||
|
||||
使用既有接口 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households`,契约不变:
|
||||
|
||||
- 响应字段见同目录 `22_8152_团期用车需求补车辆规格与接送机汇总-修改接口-管理后台.md` 与 `22_8195_团期查看需求页五处缺口-修改接口-管理后台.md`。
|
||||
- query 参数 `kind` 可选,只收单个值 `TRAVEL` 或 `TRANSFER`;不传或传空时两类都返回。**不支持逗号多值**(`kind=TRAVEL,TRANSFER` 会被判为非法取值,返错误码 809000)。本需求建议不传 `kind`,一次拿两类。
|
||||
|
||||
**关键字段**:
|
||||
- `households[n].teamNo` —— 团号(可为 null,显示「—」,**禁用 orderNo 顶替**)
|
||||
- `households[n].customerName` —— 客户名
|
||||
- `households[n].requirements[m].kind` —— 类型(`TRAVEL` 或 `TRANSFER`)
|
||||
- `households[n].requirements[m].fleet[p]` —— 车型数组,其中:
|
||||
- `vehicleTypeName` —— 车型名称(为空时退回 `vehicleType`)
|
||||
- `seats` —— 单车座位数
|
||||
- `count` —— 台数
|
||||
|
||||
---
|
||||
|
||||
## 四、数据处理规则
|
||||
|
||||
### ① 按 kind 过滤
|
||||
|
||||
行程用车:取所有 `kind='TRAVEL'` 的行,不按 `countedInSummary` 过滤。
|
||||
|
||||
接送机:取所有 `kind='TRANSFER'` 的行,不按 `countedInSummary` 过滤。
|
||||
|
||||
**禁用 `countedInSummary` 作为过滤条件**。后端 `countedInSummary` 的定义是「该户有活跃 TRAVEL 行」(hl-order-service-v3 `GroupBatchVehicleHouseholdService.java` 第 233 行),用它筛接送机会漏掉只报了接送机的户。聚合数已经按 kind 隔离,前端只需按 kind 对应过滤。
|
||||
|
||||
### ② 按状态
|
||||
|
||||
**不按状态过滤**。汇总的车侧口径不看状态(待审核的也计入),列表要与汇总对得上就不能自行二次筛选。
|
||||
|
||||
### ③ 逐行展开
|
||||
|
||||
对每个 `households[n].requirements[m].fleet` 数组,**每个元素出一行**。行组成:
|
||||
|
||||
```
|
||||
[团号] | [客户名] | [车型] | [单车座位] | [台数] | [状态](可选)
|
||||
```
|
||||
|
||||
具体列如下:
|
||||
|
||||
| 列 | 来源字段 | 说明 |
|
||||
|---|---|---|
|
||||
| 团号 | `teamNo` | 为 null 显「—」;禁用 orderNo 顶替 |
|
||||
| 客户 | `customerName` | — |
|
||||
| 车型 | `vehicleTypeName` 或 `vehicleType` | 优先用 `vehicleTypeName`;空时退回 `vehicleType` |
|
||||
| 单车座位 | `seats` | — |
|
||||
| 台数 | `count` | — |
|
||||
| 状态 | `statusName`(可选) | 仅供展示,不作过滤依据。直接用后端给的 `statusName`,不要按 `status` 自己翻译:同为 `PENDING_REVIEW`,行程用车返「待提交车务」、接送机返「待审核」 |
|
||||
|
||||
---
|
||||
|
||||
## 五、页面形态
|
||||
|
||||
**VehicleSummarySection.vue** 修改位置(现约第 14-33 行行程用车块 + 第 35-61 行接送机块):
|
||||
|
||||
### 行程用车块
|
||||
- 小标题保持「行程用车」。
|
||||
- **替代当前汇总表**:用逐行列表替代按车型聚合的表。
|
||||
- **表头**:`团号 | 客户 | 车型 | 单车座位 | 台数`。
|
||||
- **合计行保留**(可选):表尾显「合计 X 座」「合计 Y 辆」,取 `requirement-summary.vehicleSeatSummary` 或由前端加总。
|
||||
- **空态**:列表为空时显「暂无行程用车需求」。
|
||||
|
||||
### 接送机块
|
||||
- 小标题保持「接送机」。
|
||||
- **分为两部分**:
|
||||
1. **逐行列表**(同行程用车的结构),但 `kind='TRANSFER'` 过滤。
|
||||
2. **既有统计摘要保留**(取 `requirement-summary.transferSummary`):接机 N 户、送机 N 户、合计 N 人、服务日期。
|
||||
- **空态**:接送机列表为空时显「暂无接送机需求」。
|
||||
|
||||
---
|
||||
|
||||
## 六、已实测对账
|
||||
|
||||
测试团期:`2100856430494973953`(王晓测试团期产品·第3期 10月8日出发团)。两户样本数据:
|
||||
|
||||
**行程用车(TRAVEL)**:
|
||||
- 户 1(王有亿,teamNo=`26-7060`):大巴系列(bus)19 座 ×1
|
||||
- 户 2(王二麻子,teamNo=`26-2355`):大巴系列(bus)19 座 ×1
|
||||
- **列表加总**:bus 合计 38 座,2 辆
|
||||
- **汇总返回**(`requirement-summary.vehicleSeatSummary`):bus 38 座,2 辆
|
||||
- **一致性**:✓ 一致
|
||||
|
||||
**接送机(TRANSFER)**:
|
||||
- 户 1(王有亿):SUV系列(suv)5 座 ×1
|
||||
- 户 2(王二麻子):商务车(mpv)7 座 ×1
|
||||
- **列表加总**:suv 5/1,mpv 7/1
|
||||
- **汇总返回**(`requirement-summary.transferSummary.vehicleSeatSummary`):suv 5 座 1 辆,mpv 7 座 1 辆
|
||||
- **一致性**:✓ 一致
|
||||
|
||||
列表逐条数据已与取数接口(`GET /v3/admin/order/group-batch/2100856430494973953/requirement/vehicle-households`)的返回原文核对。
|
||||
|
||||
**覆盖边界声明**:本次对账团两户都有 TRAVEL 行,所以「只报接送机、无 TRAVEL 行的户」分支未被实测覆盖;结论依据后端源码逻辑推出(`countedInSummary` 定义来自源码第 233 行)。
|
||||
|
||||
---
|
||||
|
||||
## 七、业务边界
|
||||
|
||||
- `vehicle-households` 单次最多返 500 户(超出截断并记后端日志);超限时列表加总会小于汇总数。
|
||||
- 户可能 `requirements` 为空数组(还未提交需求)——该户不出行。
|
||||
- 某行 `fleet` 若为空数组,该行不出列表行(汇总加总同样不计它)。
|
||||
- 列表为空时各块显示对应的「暂无」文案。
|
||||
- 与「用车 · 子订单用车需求记录」块共用同一接口,建议前端在父组件缓存一份响应以避免重复请求(非硬需求)。
|
||||
|
||||
---
|
||||
|
||||
## 八、后端接口(无改动)
|
||||
|
||||
- 接口:`GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households`
|
||||
- 路由、权限、响应字段、错误码均不变。
|
||||
- 测试服 2026-09-29 实调可用(HTTP 200 / code=200)。
|
||||
|
||||
---
|
||||
|
||||
## 九、验收
|
||||
|
||||
1. 在测试团期 `2100856430494973953` 打开团期详情「查看需求」Tab。
|
||||
2. 「用车 · 汇总」块的行程用车板显示 2 行(两户 bus 各一行),接送机板显示 2 行(一户 suv、一户 mpv)。
|
||||
3. 逐条列表核对字段完整性:每行都有团号、客户名、车型、座位、台数。
|
||||
4. 将列表按车型加总,与汇总摘要中的合计座位数、台数逐项核对,**完全相等**。
|
||||
5. 接送机块保留既有的「接机 N 户、送机 N 户」等统计行。
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
schema: hl-changelog/v2
|
||||
ticket: "frontend"
|
||||
title: "团期详情「用车」块按户确认接送机弹窗,点「确认」关窗不发请求:弹窗键名写错(onPositive vs onPositiveClick)"
|
||||
consumer: admin
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "01acf7734a778e6cb05b8ace8503f73e04f87ac7"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "前端 2026-09-29 已修复:3 处 onPositive 全改 onPositiveClick(VehicleHouseholdsSection 按户批量/单户接送机、RequirementTab 整团确认),两 spec 的 dialog mock 改只调 onPositiveClick(键名回退即红),49 例全绿,全局 grep 零残留"
|
||||
updated_at: "2026-09-29"
|
||||
base: dev-v3
|
||||
---
|
||||
|
||||
# 团期详情用车需求确认弹窗,点确认关窗不发请求
|
||||
|
||||
> **服务**: hl-order-service-v3(**后端无改动,接口正常**)
|
||||
> **页面**: 管理后台 → 团期订单 → 进入团期(团期详情)→「查看需求」Tab →「用车 · 子订单用车需求记录」块
|
||||
> **接口**: `POST /v3/admin/order/{orderId}/vehicle-requirement/dispatch?kind=TRANSFER`
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 前端。同一写法共 3 处:单户确认接送机(wx 实测复现)、按户批量确认、整团确认(后两处为同一代码形态的静态推断,未实点)。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
1. 进入团期详情,切到「查看需求」Tab。
|
||||
2. 在「用车 · 子订单用车需求记录」块找某户的接送机行,点「确认」按钮。
|
||||
3. 弹出「确认接送机需求 / 确认放行「[客户名]」的接送机需求至车务处理?」。
|
||||
4. 点弹窗中的「确认」按钮。
|
||||
5. **弹窗关闭,但 Network 标签页无任何新请求**。
|
||||
|
||||
实测团期:`2100856430494973953`「王晓测试团期产品·第3期 10月8日出发团」,客户王有亿的接送机行(requirementId=`2102202475291549698`,状态待审核 PENDING_REVIEW)。
|
||||
|
||||
---
|
||||
|
||||
## 二、根因
|
||||
|
||||
hl-ui(`origin/v2.1` @ `4b800a29`)`src/views/order-v2/batch/detail/components/` 下两个组件的确认弹窗用了错误的回调键名 `onPositive`。
|
||||
|
||||
naive-ui(hl-ui 装的 2.44.1)`useDialog().warning({...})` 只认 `onPositiveClick` 作为"确认"按钮回调;写成 `onPositive` 时被静默忽略。来自 `node_modules/naive-ui/es/dialog/src/DialogEnvironment.mjs` 第 62-73 行:
|
||||
|
||||
```
|
||||
handlePositiveClick() 只调 props.onPositiveClick,若不存在直接 hide() 关窗
|
||||
```
|
||||
|
||||
**三处错误的键名**:
|
||||
|
||||
1. `VehicleHouseholdsSection.vue:387` —— 单户接送机确认 `confirmTransfer` 函数内弹窗(本次复现的那处;引入提交 `91d476c9`,2026-09-22)
|
||||
2. `VehicleHouseholdsSection.vue:334` —— 按户批量确认 `openBatchConfirm` 函数内弹窗(同一写法,静态推断同样不发请求,未实点)
|
||||
3. `RequirementTab.vue:588` —— 整团确认弹窗(`onPositive: () => doConfirm()`;引入提交 `96ef0c07`,2026-09-08;静态推断,未实点)
|
||||
|
||||
全 hl-ui 有 76 个文件用的是 `onPositiveClick`;写成 `onPositive:` 的只有上面 2 个文件 3 处。
|
||||
|
||||
---
|
||||
|
||||
## 三、后端验证(均正常)
|
||||
|
||||
测试服对后端接口直接探测(2026-09-29):
|
||||
|
||||
| 请求 | 结果 |
|
||||
|---|---|
|
||||
| `POST /v3/admin/order/1/vehicle-requirement/dispatch?kind=TRANSFER`(不存在订单) | HTTP 200,`code=581007`「订单不存在」,说明路由与端点都可用 |
|
||||
|
||||
探测用的是不存在的订单,对任何订单零写入。王有亿这行在 `GET /v3/admin/order/group-batch/2100856430494973953/requirement/vehicle-households` 里为 `orderId=2100856430239121409`、`requirementId=2102202475291549698`、`kind=TRANSFER`、`status=PENDING_REVIEW`,满足按钮显示与可确认的前置条件。
|
||||
|
||||
后端契约不变,无改动需求。
|
||||
|
||||
---
|
||||
|
||||
## 四、修复方案
|
||||
|
||||
**VehicleHouseholdsSection.vue**
|
||||
|
||||
第 334 行:
|
||||
```js
|
||||
// 当前(错):
|
||||
onPositive: () => doBatchConfirm(),
|
||||
// 改为(正):
|
||||
onPositiveClick: () => doBatchConfirm(),
|
||||
```
|
||||
|
||||
第 387 行:
|
||||
```js
|
||||
// 当前(错):
|
||||
onPositive: () => doConfirmTransfer(household, req),
|
||||
// 改为(正):
|
||||
onPositiveClick: () => doConfirmTransfer(household, req),
|
||||
```
|
||||
|
||||
**RequirementTab.vue**
|
||||
|
||||
第 588 行附近(整团确认弹窗)同样改键名为 `onPositiveClick:`。
|
||||
|
||||
---
|
||||
|
||||
## 五、单测同步修正
|
||||
|
||||
两个规格文件的 dialog.warning mock 中,当前直接调用 `opts.onPositive()`,验不出键名错误。需同步修正:
|
||||
|
||||
- `__tests__/VehicleHouseholdsSection.spec.js` —— 第 257、288、346、395、441 行的 mock implementation
|
||||
- `__tests__/RequirementTab.spec.js` —— 第 169、204、423、447 行直接调 `onPositive()`(第 144、167 行是用例名与注释里的字样,一并改)
|
||||
|
||||
修正方法:确保 mock 只调 `onPositiveClick`,不调 `onPositive`。这样键名一旦写错,用例会红。
|
||||
|
||||
---
|
||||
|
||||
## 六、业务边界
|
||||
|
||||
- 确认按钮调的是 `POST /v3/admin/order/{orderId}/vehicle-requirement/dispatch?kind=TRANSFER`。
|
||||
- 「确认」按钮只在 `kind=TRANSFER` 且 `status=PENDING_REVIEW` 的行上出现(`VehicleHouseholdsSection.vue:291` `canConfirmReq`)。
|
||||
- 修复后,按钮点击会正常发出请求,后端返回成功后列表刷新并同步父级预检状态。
|
||||
- 按户批量确认(同一块内)与整团确认(Tab 顶部)是同一写法,一并修;此修复只涉及弹窗回调键名,无新增端点或参数。
|
||||
|
||||
---
|
||||
|
||||
## 七、验收
|
||||
|
||||
1. 在测试团期 `2100856430494973953` 点王有亿接送机行的「确认」按钮。
|
||||
2. 弹窗确认后,Network 显示新请求 `POST /v3/admin/order/2100856430239121409/vehicle-requirement/dispatch?kind=TRANSFER`。
|
||||
3. 接口返回成功(HTTP 200),列表自动刷新,该行状态更新为接口返回值。
|
||||
4. 批量确认、整团确认各点一遍,同样看到请求发出。
|
||||
5. 两个 spec 文件改 mock 为 `onPositiveClick` 后全绿;临时把源码改回 `onPositive` 时相关用例变红。
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "应收台账:加「全部 / 散客订单 / 团期」页签 + 行内「查看」按钮(按 rowType 分流跳转)"
|
||||
consumer: "admin"
|
||||
author: "yst"
|
||||
change_type: "前端优化"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "e696593015e273f5b63271183cbe527fa8275e12"
|
||||
verified_at: "2026-09-29"
|
||||
target_release: "v2.1"
|
||||
status_note: "应收台账页加「全部/散客订单/团期」页签 + 行内查看按钮,后端 rowType 入参与跳转字段已就绪并部署测试服;前端 2026-09-29 已交付:segment 页签带 rowType 走后端过滤(弃本地过滤防 total 错)、切页签回第 1 页,行内查看按 rowType 分流(ORDER→订单详情/GROUP_BATCH→团期详情用 groupBatchId),receivable spec 7 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 应收台账:加「全部 / 散客订单 / 团期」页签 + 查看按钮(管理后台)
|
||||
|
||||
> 本条是**前端动作指引**:告诉你要改哪个页面、加什么、怎么跳。接口字段级契约
|
||||
> (请求参数 / 响应字段 / 枚举值 / 示例)见 #8504 接口 changelog
|
||||
> `changelogs-v2/2026-09/29_8504_应收台账加rowType入参-修改接口-管理后台.md`,本条不重复抄字段表。
|
||||
> 后端已部署测试服,**可直接联调**。
|
||||
|
||||
## 一、背景
|
||||
|
||||
应收台账页 `src/views/finance/receipt/receivable/index.vue`(菜单「收款管理 / 应收台账」,#8369 已挂)
|
||||
当前是**单列混合列表**(订单行与团行混在一起)且**没有查看入口**。财务提了两个诉求:
|
||||
|
||||
1. 顶部按行类型分流:能只看散客订单、或只看团期;
|
||||
2. 点行能直接跳进对应详情页,不用再手抄单号去别的页面查。
|
||||
|
||||
后端已就绪:`GET /admin/finance/receipt/receivable/page` 新增可选入参 `rowType`,
|
||||
且每行都带了 `rowType` / `id` / `groupBatchId` 三个跳转所需字段。**本次是纯前端改动,等前端实施。**
|
||||
|
||||
## 二、动作 1:顶部加页签「全部 / 散客订单 / 团期」
|
||||
|
||||
- 数据源:`GET /admin/finance/receipt/receivable/page` 新增**可选**入参 `rowType`
|
||||
- 页签与传参映射:
|
||||
|
||||
| 页签 | `rowType` 传值 | 含义 |
|
||||
|---|---|---|
|
||||
| 全部 | **不传**(默认页签) | 订单行 + 团行混合 |
|
||||
| 散客订单 | `rowType=ORDER` | 只返回订单行 |
|
||||
| 团期 | `rowType=GROUP_BATCH` | 只返回团行 |
|
||||
|
||||
- ⚠️ **必须走后端过滤**:切页签时带 `rowType` 重新请求接口,**不要**拿到全量数据后在前端本地过滤——
|
||||
本地过滤会让分页 `total` 算错,翻页直接乱。
|
||||
- 页签切换时**重置到第 1 页**再请求。
|
||||
|
||||
## 三、动作 2:行内加「查看」按钮,按 `rowType` 分流跳转
|
||||
|
||||
先看这一行的 `rowType`,再决定跳哪里、用哪个字段:
|
||||
|
||||
| `rowType` | 跳前端路由 | 用行内哪个字段 | 该路由对应的后端接口 |
|
||||
|---|---|---|---|
|
||||
| `ORDER` | `/order-v2/detail/{id}` | 行的 `id`(= 订单 ID) | `GET /v3/admin/order/{id}` |
|
||||
| `GROUP_BATCH` | `/order-v2/batch/detail/{code}` | 行的 **`groupBatchId`**(= 团期批次 ID) | `GET /v3/admin/order/group-batch/{groupBatchId}` |
|
||||
|
||||
### 🔴 三条红线(踩了必 404)
|
||||
|
||||
1. 🚫 **必须先读 `rowType` 再决定跳哪**——`GROUP_BATCH` 行**绝不能拿 `id` 去跳订单详情**:
|
||||
团行的 `id` 里装的是批次 ID 不是订单 ID,拿去查订单详情必 404。
|
||||
2. 团行跳转参数用**独立的 `groupBatchId` 字段**,别复用 `id`。
|
||||
3. 团期详情路由的参数名是 **`:code`** 不是 `:id`(页面叫「出团详情」)。
|
||||
|
||||
## 四、行内差异渲染
|
||||
|
||||
- `GROUP_BATCH` 行的 `customerName` / `customerPhone` 恒为 `null`(团行没有单一客户概念)→ 显示「—」,不要渲染成「null」或空白。
|
||||
- `orderStatusName` / `receivableStatusName` 后端已翻译成中文,**直接展示**,不用前端再映射。
|
||||
|
||||
## 五、测试服联调
|
||||
|
||||
- 接口已部署测试服,网关地址 `http://192.168.100.236:8080`。
|
||||
- `rowType` 三态过滤(不传 / ORDER / GROUP_BATCH)与行内跳转字段已实测通过:
|
||||
ORDER 纯订单行、GROUP_BATCH 纯团行、分页 `total` 正确。
|
||||
- 可直接联调,无需等后端部署。
|
||||
|
||||
## 六、验证检查清单
|
||||
|
||||
- [ ] 页签「全部 / 散客订单 / 团期」三态切换,请求分别不带 rowType / `rowType=ORDER` / `rowType=GROUP_BATCH`(Network 确认)
|
||||
- [ ] 切页签时重置到第 1 页,分页 `total` 与当前页签数据一致(不是前端本地过滤)
|
||||
- [ ] 「散客订单」页签点「查看」→ 跳 `/order-v2/detail/{id}`,`id` 为行内订单 ID,详情正常打开
|
||||
- [ ] 「团期」页签点「查看」→ 跳 `/order-v2/batch/detail/{code}`,参数取行内 `groupBatchId`,出团详情正常打开
|
||||
- [ ] 团行的客户姓名 / 手机号显示「—」,不出现 null 字样
|
||||
- [ ] 状态列直接展示后端返回的中文名
|
||||
|
||||
## 七、关联
|
||||
|
||||
- 接口契约(入参 / 出参 / 枚举 / 示例,字段级):#8504 changelog
|
||||
`changelogs-v2/2026-09/29_8504_应收台账加rowType入参-修改接口-管理后台.md`
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8504
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8505
|
||||
|
||||
## 联系人
|
||||
|
||||
后端:yst | 前端:mmg
|
||||
@@ -0,0 +1,249 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "proto-align"
|
||||
title: "进项发票登记表单原型对齐:以既有后端契约为准,附前端实现自查清单"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "463082a33a5cbe8b091c210c98b5400b9899c81f"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "后端收票(进项发票)接口(/admin/finance/invoice-in/*,#7857 已于 2026-09-17 上线并部署)入参/出参/枚举/错误码零改动。本文档非接口变更,是【原型稿纠偏 + 前端实现自查单】:财务域原型稿「登记进项发票」弹窗此前画成简化版(关联团号自由文本 + 缺发票类型/发票代码/收票日期/税率/发票影像),与真实后端契约不符,已修订原型对齐。请前端对照第五节自查清单核对当前已实现表单是否逐项覆盖;前端 2026-09-17 已按契约对接(见 #7857),本单主要用于留档防回归 + 原型稿同步,预计前端零改动。;前端 2026-09-29 自查完毕:清单 1/2/3/6/8/9 已落实,5(税额反算)为可选项不做;发现 #7 发票影像手填 URL 与 #4 标签缺「(元)」两处缺口已补齐(FileUpload OSS 上传+「价税合计(元)」),invoice-in spec 17 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:进项发票登记表单原型对齐(后端接口零改动)(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: 无(原型稿纠偏 + 前端自查单,无后端改动)
|
||||
**Issue**: 无(原型稿纠偏,无后端改动)
|
||||
**关联上线单**: #7857 收票(进项发票)域上线(2026-09-17,前端已对接 verified)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
管理后台「发票管理 → 收票(进项)」里,「**登记进项发票**」弹窗用于:已收到供应商发票时直接登记为已收票,挂供应商 + 关联应付/预付单,与已付款勾稽消除进项缺口。
|
||||
|
||||
后端接口 `POST /admin/finance/invoice-in/create`(及配套 `/biz-candidates`、`/update`)**自 2026-09-17 上线后未曾变更**。
|
||||
|
||||
本次起因:财务域**原型稿**(`finance-prototype.html`)里的「登记进项发票」弹窗画的是**简化版**——只有一个「关联团号」自由文本框,且缺发票类型 / 发票代码 / 收票日期 / 税率 / 发票影像 5 个字段,与真实后端契约不一致。**前端实际实现并未照此错误原型**(mmg 2026-09-17 已按契约正确对接),但原型稿滞后易误导后续维护 / 新人。现已修订原型对齐契约,并出本单请前端对照自查、留档防回归。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| 提交接口 `POST /admin/finance/invoice-in/create` | **无变更**(签名 / 字段 / 枚举 / 错误码均未变) |
|
||||
| `GET /biz-candidates` / `PUT /update` 等其余端点 | **无变更** |
|
||||
| 原型稿「登记进项发票」弹窗 | **已纠偏**:删「关联团号」自由文本 → 改「关联应付单」多选;补齐发票类型 / 发票代码 / 收票日期 / 税率 / 发票影像;「金额」改标签「价税合计」 |
|
||||
| 前端实现 | **预期零改动**:本单为自查确认单,请按第五节逐项核对已实现表单 |
|
||||
| 数据库表 | 零 DDL |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|---|---|---|
|
||||
| `/admin/finance/invoice-in/create` | POST | 登记进项发票(落 RECEIVED 已收票;可挂多笔应付/预付/费用报销,也可纯挂供应商) |
|
||||
| `/admin/finance/invoice-in/biz-candidates?supplierId=` | GET | 按供应商捞可勾选业务单据(应付/预付中已生效且未收齐票的,供登记关联勾选) |
|
||||
| `/admin/finance/invoice-in/update` | PUT | 编辑进项发票(仅 RECEIVED 态;关联走全删重插) |
|
||||
| `/admin/file/upload/token` + `/admin/file/confirm` | POST | 发票影像上传(OSS 两步:取预签名凭证 → 直传 → 确认,拿 URL 回填 `voucherUrl`) |
|
||||
|
||||
> 完整出入参加 #7857 上线单。本单只列与「登记表单字段对齐」直接相关的入参。
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 `POST /admin/finance/invoice-in/create` 请求体(`InvoiceInCreateReqVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `invoiceNo` | string | ✅ | 发票号码(≤40;查重锚点,撞号 599401) |
|
||||
| `invoiceCode` | string | ❌ | 发票代码(纸质票填,电子票可空;≤20) |
|
||||
| `invoiceType` | string | ✅ | 发票类型(字典 `fin_invoice_in_type`:SPECIAL 专票 / NORMAL 普票 / ELECTRONIC 电子票;≤20) |
|
||||
| `supplierId` | long | ✅ | 开票供应商 ID(服务端反查落 `supplierName` 快照,前端不传名称) |
|
||||
| `invoiceAmount` | number | ✅ | **价税合计**(>0,否则 599405) |
|
||||
| `taxRate` | number | ❌ | 税率%(仅记录,不管进项抵扣) |
|
||||
| `taxAmount` | number | ❌ | 税额(仅记录) |
|
||||
| `invoiceDate` | date | ❌ | 开票日期 |
|
||||
| `receiveDate` | date | ❌ | 收票日期 |
|
||||
| `voucherUrl` | string | ❌ | 发票影像 URL(OSS 上传后回填;≤500) |
|
||||
| `remark` | string | ❌ | 备注(≤500) |
|
||||
| `relations` | array | ❌ | 关联业务源列表(可空=纯挂供应商;见 4.2) |
|
||||
|
||||
### 4.2 `relations[]` 元素(`InvoiceInRelReqVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `bizType` | string | ✅ | PAYMENT 应付款 / PREPAY 预付款 / EXPENSE 费用报销 |
|
||||
| `bizId` | long | ✅ | 业务单据 ID(PAYMENT/PREPAY 校验归属本供应商 599407 + 须 APPROVED/PAID 生效态 599412;EXPENSE 仅验存在) |
|
||||
| `matchAmount` | number | ✅ | 本票对该笔的匹配金额(>0;ΣmatchAmount ≤ invoiceAmount 否则 599406) |
|
||||
|
||||
### 4.3 `GET /biz-candidates?supplierId=` 出参元素(`InvoiceInBizCandidateRespVO`,返回 `List` 非分页)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `bizType` / `bizTypeName` | 业务类型 + 中文名(PAYMENT 应付款 / PREPAY 预付款;**EXPENSE 不进候选**) |
|
||||
| `bizId` | 业务单据 ID |
|
||||
| `bizNo` | 业务单号(如 FK-20260817-008) |
|
||||
| `amount` | 单据金额 |
|
||||
| `matchedAmount` | 已被有效发票匹配金额合计 |
|
||||
| `remainingAmount` | 剩余可收票金额 = amount − matchedAmount(>0 才返回) |
|
||||
|
||||
> ⚠️ **`biz-candidates` 不返回团号**。候选行只能展示「业务类型 + 单号 + 金额 + 剩余可收票额」;团信息由被挂应付单间接带出,不在进项票上单独录团号。
|
||||
|
||||
---
|
||||
|
||||
## 五、前端实现自查清单(请逐项核对已实现表单)
|
||||
|
||||
| # | 契约关键点 | 期望前端行为 |
|
||||
|---|---|---|
|
||||
| 1 | **关联维度是「关联业务单据」不是「团号」** | 表单**无团号输入框**;改为「关联应付/预付单」多选:选定 `supplierId` 后调 `/biz-candidates` 拉候选,勾选得 `relations[]`(每笔带 `matchAmount`);换供应商重拉;可不勾 = 纯挂供应商(create 不带 `relations`) |
|
||||
| 2 | **发票类型必填下拉** | `invoiceType` 走字典 `fin_invoice_in_type`,勿硬编码 |
|
||||
| 3 | **发票代码可空** | `invoiceCode` 输入框,电子票可空 |
|
||||
| 4 | **金额字段叫「价税合计」** | 标签为「价税合计(元)」,必填 >0 |
|
||||
| 5 | **税率 + 税额(金额三角)** | `taxRate` + `taxAmount` 均可空;可在录入层做「价税合计+税率 → 自动反算税额 = 价税合计÷(1+税率)×税率」,税额允许手填覆盖(后端只记录不校验) |
|
||||
| 6 | **收票日期独立字段** | `receiveDate` 与 `invoiceDate` 分开 |
|
||||
| 7 | **发票影像走附件上传** | `voucherUrl` 由 OSS 上传(`/admin/file/upload/token` → 直传 → `/admin/file/confirm`)回填 URL,**非手填文本** |
|
||||
| 8 | **ΣmatchAmount ≤ invoiceAmount** | NForm 预校验,触发后端 599406 时透 message |
|
||||
| 9 | **编辑态关联全删重插** | update 恒带整包 `relations`;EXPENSE 已挂行只读保留(仅可移除),防全删重插静默清掉 |
|
||||
|
||||
> 前端 2026-09-17 交付记录显示第 1/2/5/8/9 条已落实(详见 #7857 status_note)。请重点复核第 3/4/6/7 条在「登记」「编辑」两个弹窗里是否都已覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### status 单据状态(状态机枚举)
|
||||
`RECEIVED` 已收票 → `VERIFIED` 已核对;`RECEIVED`/`VERIFIED` → `VOIDED` 已作废(终态,作废原因必填 599408)
|
||||
|
||||
### bizType 业务类型(FinInvoiceInBizTypeEnum)
|
||||
`PAYMENT` 应付款 / `PREPAY` 预付款 / `EXPENSE` 费用报销
|
||||
|
||||
### invoiceType 发票类型(数据字典 `fin_invoice_in_type`)
|
||||
`SPECIAL` 增值税专用发票 / `NORMAL` 增值税普通发票 / `ELECTRONIC` 电子发票(数电票)
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 码 | 含义 |
|
||||
|---|---|
|
||||
| 599400 | 进项发票不存在 |
|
||||
| 599401 | 发票号码重复 |
|
||||
| 599402/599403/599404 | 非 RECEIVED 态不可编辑 / VERIFIED 不可编辑 / VOIDED 不可编辑 |
|
||||
| 599405 | 金额非法(invoiceAmount/matchAmount 须 >0) |
|
||||
| 599406 | ΣmatchAmount 超过价税合计 |
|
||||
| 599407 | 关联单据不存在或不归属本供应商 |
|
||||
| 599408 | 作废原因必填 |
|
||||
| 599409 | 发票类型非法(不在字典取值域) |
|
||||
| 599410 | 供应商反查失败 |
|
||||
| 599411 | 同票重复关联同一单据 |
|
||||
| 599412 | 关联单据非 APPROVED/PAID 生效态 |
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 登记(关联两笔应付单)
|
||||
|
||||
```json
|
||||
POST /admin/finance/invoice-in/create
|
||||
{
|
||||
"invoiceNo": "032002600199",
|
||||
"invoiceCode": "",
|
||||
"invoiceType": "ELECTRONIC",
|
||||
"supplierId": 1001,
|
||||
"invoiceAmount": 8960.00,
|
||||
"taxRate": 6,
|
||||
"taxAmount": 507.17,
|
||||
"invoiceDate": "2026-08-19",
|
||||
"receiveDate": "2026-08-19",
|
||||
"voucherUrl": "https://files.example.com/uploads/inv_032002600199.pdf",
|
||||
"remark": "",
|
||||
"relations": [
|
||||
{ "bizType": "PAYMENT", "bizId": 9001, "matchAmount": 8960.00 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(纯挂供应商,不关联任何单据)
|
||||
|
||||
```json
|
||||
POST /admin/finance/invoice-in/create
|
||||
{
|
||||
"invoiceNo": "032002600200",
|
||||
"invoiceType": "NORMAL",
|
||||
"supplierId": 1001,
|
||||
"invoiceAmount": 1200.00
|
||||
}
|
||||
```
|
||||
|
||||
> 不带 `relations`(或空数组)= 纯挂供应商,不进单据维度勾稽。
|
||||
|
||||
### 8.3 业务失败(ΣmatchAmount 超票面 → 599406)
|
||||
|
||||
```json
|
||||
{ "code": 599406, "message": "关联匹配金额合计超过价税合计" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- 进项发票只管**票据勾稽**(钱先付、票后到,消除进项缺口),**不管进项税抵扣、不接税务查验**——`taxRate`/`taxAmount` 仅记录,后端不做税额反算与校验(反算若有,纯前端录入体验)。
|
||||
- 一张票可挂多笔业务(N:N),允许部分匹配(ΣmatchAmount ≤ invoiceAmount)。
|
||||
- EXPENSE(费用报销)无供应商锚点,不进 `/biz-candidates` 候选,但可被 `relations` 挂接(仅验存在)。
|
||||
- 作废(VOIDED)为终态不可恢复,作废票匹配额自动释放回可收票池。
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比(原型稿)
|
||||
|
||||
| 维度 | 旧原型稿(误) | 修订后原型 / 真实契约(正) |
|
||||
|---|---|---|
|
||||
| 关联维度 | 「关联团号」自由文本(如 26-8875) | 「关联应付/预付单」多选(biz-candidates),无团号 |
|
||||
| 发票类型 | 无 | 必填下拉(字典 fin_invoice_in_type) |
|
||||
| 发票代码 | 无 | 可空输入框 |
|
||||
| 金额字段 | 「金额(元)」 | 「价税合计(元)」 |
|
||||
| 税率 | 无 | 可空,参与税额反算 |
|
||||
| 收票日期 | 无 | 独立日期字段 |
|
||||
| 发票影像 | 无 | OSS 附件上传回填 voucherUrl |
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- 后端接口 / 数据库 / 网关:**零改动**,无需发版、无需回滚。
|
||||
- 前端:预期零改动(已按契约对接)。本单为自查 + 留档,若自查发现某弹窗漏了第 3/4/6/7 条字段,补齐即可,不影响已登记数据。
|
||||
- 原型稿:已修订对齐,仅文档层面。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
- `supplierName` / `bizNo` 由服务端反查 / 回链落快照,前端**不传**名称类字段,传 ID 即可。
|
||||
- 长整型 ID(`id`/`supplierId`/`bizId`/`invoiceInId`)JSON 序列化为字符串,前端按字符串处理防精度丢失。
|
||||
- 发票影像预览 / 下载走 `/admin/file/{fileId}/preview`、`/admin/file/preview-by-url`(网关已白名单)。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- 关联上线单:`https://git.1814.love/wx/HL/issues/7857`(收票(进项发票)域上线,2026-09-17 前端已对接 verified)
|
||||
- 本单:无独立 Issue / PR(原型稿纠偏 + 前端自查单)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- 后端 / 财务域:yst(腰苏图)
|
||||
@@ -0,0 +1,321 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "供应商应付期初表单对接纠偏:后端接口零改动,纠正表单形态与字段映射"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "151cf98066d5c41b8e85366a52d5514f20d875e2"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-29"
|
||||
status_note: "后端 POST /admin/finance/opening-balances 入参/出参/枚举零改动。本文档为前端对接纠偏指引:财务初始化·供应商应付期初表单当前画错(手填雪花ID + 应收/应付双金额 + 必填佐证),正确形态为 6 字段(供应商下拉 / 单一应付金额 / 所属公司下拉 / 记账日期只读 / 佐证选填 / 备注选填),前端需按本文档修正表单。;前端 2026-09-29 已交付:供应商账套新建表单收敛为 6 元素(供应商下拉/应付金额/所属公司 isPrimary=1 默认选中/记账日期只读不传参/佐证选填/备注),报文不传 openingReceivable/customerCategory/记账日期,CUSTOMER/STAFF 原形态不动,fin-init spec 17 例全绿"
|
||||
updated_at: "2026-09-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:供应商应付期初表单对接纠偏(后端接口零改动)(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: 无(前端对接纠偏,无后端改动)
|
||||
**Issue**: 无(前端对接纠偏,无后端改动)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
管理后台「财务初始化」里有一组**期初余额录入**表单,按账套区分(供应商应付 / 客户应收等)。本文档只针对**「供应商应付期初」**这一张表单。
|
||||
|
||||
联调发现前端把这张表单画错了:画成了「手填往来对象雪花 ID + 应收/应付两个金额框 + 佐证材料必填」。后端接口 `POST /admin/finance/opening-balances` **自始至终没有变过**(入参 / 出参 / 枚举零改动),是前端表单形态与字段映射理解偏差。本文档给出正确表单形态和逐字段映射,前端按此修正即可,不需要等任何后端发版。
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| 提交接口 `POST /admin/finance/opening-balances` | **无变更**(签名 / 字段 / 枚举 / 错误码均未变) |
|
||||
| 前端表单 | **需纠偏**:表单元素从「手填 ID + 双金额 + 必填佐证」改为「下拉 + 单一应付金额 + 佐证选填」6 字段形态 |
|
||||
| 数据库表 | 零 DDL |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
- **路径**:`POST /admin/finance/opening-balances`
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **幂等性**:否。但同一供应商重复录入会被业务唯一性拦截,返回 598401(见 §七),不会产生脏数据
|
||||
- **限流**:无特殊限流
|
||||
|
||||
**表单最终形态(6 个元素,多了少了都是错)**:
|
||||
|
||||
| # | 表单元素 | 控件 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 供应商 | 下拉选择 | ✅ | 数据源见 §4.3;选中后提交 `refId` + `refName` |
|
||||
| 2 | 应付金额 | 数字输入 | ✅ | label 写「应付金额(我们欠供应商)」。**只有这一个金额框,不要画应收框** |
|
||||
| 3 | 所属公司 | 下拉选择 | ✅ | 数据源见 §4.4;提交 `companyId` |
|
||||
| 4 | 记账日期 | 只读展示 | — | 后端自动取当前未封账账期的 startDate 落库,**前端不传**;展示值来源见 §4.5 |
|
||||
| 5 | 佐证材料 | 图片上传 | ❌ 选填 | 上传后把 URL 填 `evidenceUrl`;不传则该字段不出现或为 null |
|
||||
| 6 | 备注 | 文本域 | ❌ 选填 | ≤ 200 字 |
|
||||
|
||||
**表单上不要出现的元素**:
|
||||
|
||||
| 不要出现 | 原因 |
|
||||
|---|---|
|
||||
| 往来对象 ID 手填框 | 供应商必须走下拉选择,ID 由选项带出,不允许手填雪花 ID |
|
||||
| 期初应收金额框 | 供应商账套只有应付,没有应收 |
|
||||
| 供应商名称手填框 | 名称由下拉选项自动带出(`refName`),不手填 |
|
||||
| 客户分类 | 那是应收账套(客户侧)的字段,供应商账套不传 |
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
无。
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| `ledgerType` | String | ✅ | 账套类型。本表单**固定传 `SUPPLIER`** | 枚举,见 §六 |
|
||||
| `refId` | String | ✅ | 供应商 ID(取下拉项的 `supplierId`) | 必须是存在的供应商 |
|
||||
| `refName` | String | ✅ | 供应商名称(取下拉项的 `fullName`) | ≤ 128 字 |
|
||||
| `companyId` | String | ✅ | 所属公司主体 ID(取下拉项的 `agencyId`) | 必须是启用中的公司主体,否则 598407 |
|
||||
| `openingPayable` | Number | ✅ | 应付期初金额(元) | 必填且 **> 0**,否则 598405 |
|
||||
| `evidenceUrl` | String | ❌ | 佐证材料图片 URL | 可空 / 不传 |
|
||||
| `remark` | String | ❌ | 备注 | ≤ 200 字 |
|
||||
|
||||
**不要传的字段**(传了属于画错账套):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `openingReceivable` | 应收期初金额,供应商账套不传 |
|
||||
| `customerCategory` | 客户分类,应收账套(客户侧)字段,供应商账套不传 |
|
||||
| 记账日期相关字段 | 请求体里**没有**记账日期字段,后端自动取当前未封账账期 startDate,前端不传 |
|
||||
|
||||
> ⚠️ `refId` / `companyId` 后端是 Long 大数(雪花 ID),JSON 序列化为字符串下发;前端 JS 一律**当字符串处理**,不要 `Number()` 转换,防精度丢失。
|
||||
|
||||
### 4.3 供应商下拉数据源
|
||||
|
||||
- **路径**:`GET /admin/supplier/items/list`(资源服务)
|
||||
- **认证**:网关 JWT(admin)
|
||||
|
||||
Query 参数(全部选填):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `status` | String | ❌ | 本表单**固定传 `ACTIVE`**(只在用供应商可录期初) |
|
||||
| `keyword` | String | ❌ | 名称 / 编号模糊搜索 |
|
||||
| `limit` | Integer | ❌ | 默认 50,最大 200 |
|
||||
|
||||
响应 `data` 为数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `supplierId` | String | 供应商 ID(Long 序列化字符串) |
|
||||
| `fullName` | String | 供应商全称(下拉显示用) |
|
||||
| `shortName` | String | 简称 |
|
||||
| `supplierNo` | String | 供应商编号 |
|
||||
| `statusName` | String | 状态中文名 |
|
||||
|
||||
**取值映射**:下拉显示 `fullName`;选中后提交 `refId = supplierId`、`refName = fullName`。
|
||||
|
||||
### 4.4 所属公司下拉数据源
|
||||
|
||||
- **路径**:`GET /v3/admin/travel-agency/enabled`(order-v3)
|
||||
- **认证**:网关 JWT(admin)
|
||||
- **入参**:无
|
||||
|
||||
响应 `data` 为数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `agencyId` | String | 公司主体 ID(Long 序列化字符串) |
|
||||
| `agencyName` | String | 公司名称(下拉显示用) |
|
||||
| `isPrimary` | Integer | 是否主体公司:1 = 是,0 = 否 |
|
||||
|
||||
**取值映射**:下拉显示 `agencyName`;`isPrimary = 1` 的主体公司**默认选中**;选中后提交 `companyId = agencyId`。公司名称由后端自取快照,前端不用传。
|
||||
|
||||
### 4.5 记账日期展示值来源(只读,不入参)
|
||||
|
||||
记账日期 = 当前**未封账**账期的 `startDate`,后端落库时自动取,前端不传。若表单上要展示该值:
|
||||
|
||||
- **路径**:`GET /admin/finance/account-periods/page`
|
||||
- **取数**:取返回列表中 `isClosed = 0`(未封账)那一行的 `startDate` 展示即可
|
||||
|
||||
## 五、出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data` | String | 新建期初行 ID(Long 序列化字符串,JS 当字符串处理) |
|
||||
|
||||
外层为标准响应包:`code` / `data` / `message` / `success`。
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### 6.1 ledgerType(账套类型)
|
||||
|
||||
**所属字段**:请求体 `ledgerType` | **类型**:`String` | **必填**:✅
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SUPPLIER` | 供应商 | 本表单固定传此值(供应商应付期初) |
|
||||
|
||||
> 接口本身支持其他账套值(应收侧等),但**本表单只涉及 `SUPPLIER`**,其他账套的表单形态不在本文档范围。
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 598405 | 期初金额未填或 ≤ 0 | `openingPayable` 缺失 / 为 0 / 负数 |
|
||||
| 598401 | 该供应商期初已录过 | 同一供应商重复提交期初(已有期初的供应商要改金额走「期初调整」,不要重复录) |
|
||||
| 598407 | 所属公司非法或已停用 | `companyId` 不存在或该公司主体已停用 |
|
||||
| 598403 | 无未封账账期 | 当前没有 `isClosed = 0` 的账期,无法落记账日期 |
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /admin/finance/opening-balances
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某景区门票有限公司",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingPayable": 1000.00,
|
||||
"evidenceUrl": "https://oss.example.com/finance/evidence/x.jpg",
|
||||
"remark": "2026 年度合作期初应付"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": "1956789012345678901",
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(佐证材料、备注均不传)
|
||||
|
||||
**场景说明**:`evidenceUrl` / `remark` 均为选填,最小合法请求只有 5 个字段。
|
||||
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某景区门票有限公司",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingPayable": 0.01
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": "1956789012345678902",
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(金额未填 / ≤ 0,触发 598405)
|
||||
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER",
|
||||
"refId": "1834567890123456789",
|
||||
"refName": "内蒙古某景区门票有限公司",
|
||||
"companyId": "1723456789012345678",
|
||||
"openingPayable": 0
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 598405,
|
||||
"data": null,
|
||||
"message": "期初金额未填或必须大于 0",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
> 重复提交同一供应商则返回 `598401`(该供应商期初已录过,请走期初调整)。
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- ✅ **适用场景**:供应商首次录入应付期初,且当前存在未封账账期、所属公司主体启用中。
|
||||
- ❌ **不适用场景**:
|
||||
- 该供应商已录过期初 → 598401,改金额请走「期初调整」功能,不要重复提交本接口;
|
||||
- 无未封账账期 → 598403;
|
||||
- 所属公司已停用 → 598407。
|
||||
- ⚠️ **特殊边界**:记账日期不可指定历史 / 未来日期,恒等于当前未封账账期 startDate(后端落库)。
|
||||
|
||||
## 十、修改前后对比(前端错误实现 vs 正确实现)
|
||||
|
||||
> 后端接口**零改动**,本节对比的是前端表单的「当前错误实现」与「正确实现」。
|
||||
|
||||
### 10.1 表单元素级对比
|
||||
|
||||
| 表单元素 | 错误实现(当前) | 正确实现 |
|
||||
|---|---|---|
|
||||
| 供应商 | 手填往来对象 ID(雪花 ID)输入框 | 下拉选择(数据源 `GET /admin/supplier/items/list?status=ACTIVE`),选中后自动带 `refId` + `refName` |
|
||||
| 供应商名称 | 手填输入框 | **不出现**,由下拉选项带出(`fullName` → `refName`) |
|
||||
| 金额 | 应收 + 应付**两个**金额框 | **只有一个**金额框:「应付金额(我们欠供应商)」,必填 > 0 |
|
||||
| 客户分类 | 出现下拉 | **不出现**(应收账套字段,供应商账套不传 `customerCategory`) |
|
||||
| 佐证材料 | 必填 | **选填**(`evidenceUrl` 可空) |
|
||||
| 记账日期 | 前端手填 / 传参 | **只读展示、不传参**,后端自动取当前未封账账期 startDate |
|
||||
|
||||
### 10.2 提交报文级对比
|
||||
|
||||
| 项 | 错误实现(当前) | 正确实现 |
|
||||
|---|---|---|
|
||||
| `refId` | 手填数字 / Number 类型 | 下拉带出,字符串原样提交 |
|
||||
| `openingReceivable` | 出现在 body | **不传** |
|
||||
| `customerCategory` | 出现在 body | **不传** |
|
||||
| `evidenceUrl` | 必填校验拦截提交 | 可空 |
|
||||
| 记账日期字段 | 出现在 body | **不传**(body 里本就没这个字段) |
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:否。后端接口零改动,已按正确形态对接的调用不受影响。
|
||||
- **前端是否必须同步上线**:是。当前错误表单提交必被 598405 / 598407 等拦截或录错账套,需尽快按本文档修正。
|
||||
- **影响已有数据**:无(无 DDL、无数据迁移)。
|
||||
- **回滚方案**:后端无动作;前端如需回退,回退表单版本即可。
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
- ⚠️ `refId` / `companyId` / 响应 `data` 均为 Long 大数序列化的字符串,JS 全程当字符串处理,禁止 `Number()` 转换(精度丢失)。
|
||||
- ⚠️ 供应商账套**只有一个金额框**(应付)。出现应收框 / 客户分类 = 套错了应收账套的表单。
|
||||
- ⚠️ 佐证材料是**选填**,不要加必填校验拦截提交。
|
||||
- ⚠️ 记账日期前端不传;如需展示,查 `GET /admin/finance/account-periods/page` 取 `isClosed = 0` 行的 `startDate`。
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- Issue:无(前端对接纠偏,无后端改动)
|
||||
- PR:无(前端对接纠偏,无后端改动)
|
||||
- Merge commit:无
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- 后端负责人:yst
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,614 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8491"
|
||||
title: "房务旧列表接口下线:抢单池列表、我的接单、组长全部已抢订单、我的团"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务旧列表: 下线 4 个已废弃的管理端列表接口(抢单池列表 / 我的接单 / 组长全部已抢订单 / 我的团)
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8491
|
||||
> **日期**: 2026-09-29
|
||||
> **影响范围**: 管理后台房务旧列表页(抢单池、我的接单、组长监督视图、我的团)的数据来源
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
|
||||
|
||||
- 以下 4 个 GET 接口从服务端删除,服务端已无这些路由映射,删除后返回形态本文不作约定,调用点一律移除:
|
||||
- `GET /v3/admin/order/grab-pool/hotel-requirements`
|
||||
- `GET /v3/admin/order/grab-pool/my-claims/hotel`
|
||||
- `GET /v3/admin/order/grab-pool/all-claims/hotel`
|
||||
- `GET /v3/admin/order/grab-pool/my-claims/group-batches`
|
||||
- 替代接口:常规单统一走 `GET /v3/admin/order/house-allocation/households`,团期统一走 `GET /v3/admin/order/house-allocation/group-batches`(两者本次的字段调整见同目录修改接口文件)。
|
||||
- 「房务组长」角色同步取消:原「全部已抢订单」监督视图与「我的团 scope=all」不再存在,全体房务改用替代接口的 `scope=all` 查看全部。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
这 4 个接口在 #8491 之前已标注「已废弃,改用 house-allocation 列表」,房务控制台上线两条统一列表后删除。它们的请求 / 响应 VO(`HouseMyOrderPageReqVO`、`HouseMyOrderPageRespVO`、`HouseMyOrderItemRespVO`、`HouseMyOrderStatsVO`、`HouseMyGroupPageReqVO`、`HouseMyGroupPageRespVO`、`HouseMyGroupItemRespVO`、`HouseMyGroupStatsVO`)随之删除。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 抢单池列表(常规单) | GET | `/v3/admin/order/grab-pool/hotel-requirements` | 删除 | 改用 households 列表 status=pendingClaim |
|
||||
| 2 | 我的接单(常规单) | GET | `/v3/admin/order/grab-pool/my-claims/hotel` | 删除 | 改用 households 列表 scope=mine |
|
||||
| 3 | 组长全部已抢订单(常规单) | GET | `/v3/admin/order/grab-pool/all-claims/hotel` | 删除 | 改用 households 列表 scope=all |
|
||||
| 4 | 我的团(团期) | GET | `/v3/admin/order/grab-pool/my-claims/group-batches` | 删除 | 改用 group-batches 列表 status=claimed |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
本节 4 个接口均已删除。入参 / 出参表与示例记录的是**删除前**的契约,仅供前端定位与清理调用点;字段名、类型、校验文案逐一取自删除前源码,ID、日期、姓名等取值为说明用的构造值。服务端已无这些路由映射,删除后返回形态本文不作约定。
|
||||
|
||||
### 1. 抢单池列表(常规单) `GET /v3/admin/order/grab-pool/hotel-requirements`
|
||||
|
||||
**VO**: `HouseGrabPageReqVO → PageResult<HouseGrabPageItemRespVO>`(已删除接口,VO 类仍在代码中但无接口引用)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:房务在抢单池页查看待认领的常规单需求。现在改为 `GET /v3/admin/order/house-allocation/households?status=pendingClaim`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| keyword | Query | String | ❌ | ≤32 字 | 删除前:关键词 |
|
||||
| productType | Query | String | ❌ | - | 删除前:产品类型 |
|
||||
| productName | Query | String | ❌ | - | 删除前:产品名 |
|
||||
| consultantId | Query | Long | ❌ | - | 删除前:定制师 |
|
||||
| guestName | Query | String | ❌ | - | 删除前:客人姓名 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | ❌ | - | 删除前:出行日期区间 |
|
||||
| page | Query | Integer | ❌ | ≥1,默认 1 | 删除前:页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 删除前:每页条数 |
|
||||
| sortBy | Query | String | ❌ | 默认 createTime,desc | 删除前:排序 |
|
||||
|
||||
#### 出参 `Result<PageResult<HouseGrabPageItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | List<HouseGrabPageItemRespVO> | 删除前:行列表 |
|
||||
| total | int | 删除前:总数 |
|
||||
| records[].id / orderId | Long(String) | 删除前:需求 ID / 订单 ID |
|
||||
| records[].orderNo / teamNo / guestName / personsDesc | String | 删除前:订单号 / 团号 / 客人 / 人数描述 |
|
||||
| records[].productType / productName / productNo / route | String | 删除前:产品信息 |
|
||||
| records[].departDate / nights / cities | LocalDate / Integer / List<String> | 删除前:出行日期 / 夜数 / 城市 |
|
||||
| records[].totalAmount | BigDecimal(String) | 删除前:订单总额 |
|
||||
| records[].consultantName / consultantId / consultantRemark | String / Long(String) / String | 删除前:定制师信息 |
|
||||
| records[].requirementNote / dispatchRemark / special | String / String / List<String> | 删除前:需求备注 / 派单备注 / 特殊要求 |
|
||||
| records[].requirementVersion | Integer | 删除前:需求版本 |
|
||||
| records[].urgencyLevel / urgencyLabel / daysToDepart / manualUrgent | String / String / Integer / Boolean | 删除前:紧急度 |
|
||||
| records[].createTime | LocalDateTime | 删除前:入池时间 |
|
||||
| records[].isRework / reworkPrevClaimerName | Boolean / String | 删除前:返工标识 / 上一任认领人 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/grab-pool/hotel-requirements?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "1940000000000000011",
|
||||
"orderId": "1930000000000000021",
|
||||
"orderNo": "26-0915",
|
||||
"teamNo": "26-0920",
|
||||
"guestName": "李女士一家",
|
||||
"productName": "呼伦贝尔秋色 6 日",
|
||||
"departDate": "2026-10-05",
|
||||
"isRework": false
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定;前端移除调用点,空列表展示改由替代接口 `records` 为空时处理。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "keyword 长度不能超过 32 字",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | keyword 长度不能超过 32 字 / page 必须 ≥ 1 / pageSize 最大 100 | 删除前的入参校验 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,删除后返回形态本文不作约定,调用点一律移除。
|
||||
- 替代:`GET /v3/admin/order/house-allocation/households?status=pendingClaim`,排序可传 `sortBy=createTime,desc` 保持原默认顺序;替代接口的 `list` 字段名与本接口的 `records` 不同。
|
||||
|
||||
### 2. 我的接单(常规单) `GET /v3/admin/order/grab-pool/my-claims/hotel`
|
||||
|
||||
**VO**: `HouseMyOrderPageReqVO → HouseMyOrderPageRespVO`(均已删除)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:房务查看本人已认领的常规单及状态统计。现在改为 `GET /v3/admin/order/house-allocation/households?scope=mine`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| keyword | Query | String | ❌ | ≤32 字 | 删除前:关键词 |
|
||||
| status | Query | String | ❌ | `unfinished` / `allUnfinished` / `todo` / `inProgress` / `claiming` / `pendingConfirm` / `confirmed` / `exception` / `voided` / `inInquiry` | 删除前:跟单状态(前三个都表示全部未完成) |
|
||||
| productType | Query | String | ❌ | CORE / ROUTE / CUSTOM / GROUP | 删除前:产品类型 |
|
||||
| productName / guestName | Query | String | ❌ | - | 删除前:模糊搜 |
|
||||
| consultantId | Query | Long | ❌ | - | 删除前:定制师 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | ❌ | - | 删除前:出行日期区间 |
|
||||
| stayDate | Query | LocalDate | ❌ | - | 删除前:入住晚下钻 |
|
||||
| claimedAtFrom / claimedAtTo | Query | LocalDateTime | ❌ | - | 删除前:认领时间区间 |
|
||||
| city / hasException / hasTodo / hasUnreadMessage | Query | String / Boolean | ❌ | - | 删除前:预留字段,未实现 |
|
||||
| page | Query | Integer | ❌ | ≥1,默认 1 | 删除前:页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 删除前:每页条数 |
|
||||
| sortBy | Query | String | ❌ | 默认 claimedAt,desc | 删除前:排序 |
|
||||
|
||||
#### 出参 `Result<HouseMyOrderPageRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| list | List<HouseMyOrderItemRespVO> | 删除前:行列表 |
|
||||
| total | Long | 删除前:总数 |
|
||||
| stats | HouseMyOrderStatsVO | 删除前:inProgress / claiming / inInquiry(恒 0)/ pendingConfirm / confirmed / exception / voided |
|
||||
| list[].id / orderId / consultantId / claimerId | Long(String) | 删除前:需求 / 订单 / 定制师 / 持有人 ID |
|
||||
| list[].orderNo / teamNo / guestName / personsDesc / productType / productName / route | String | 删除前:订单与产品信息 |
|
||||
| list[].departDate / nights / cities | LocalDate / Integer / List<String> | 删除前:出行信息 |
|
||||
| list[].totalAmount | BigDecimal(String) | 删除前:订单总额 |
|
||||
| list[].consultantName / claimerName / claimedAt | String / String / LocalDateTime | 删除前:定制师 / 持有人 / 认领时间 |
|
||||
| list[].houseStatus | String | 删除前:**中文状态**(与 houseStatusLabel 同值) |
|
||||
| list[].houseStatusLabel | String | 删除前:中文状态 |
|
||||
| list[].progressDesc / hotelSummary / lastAction | String | 删除前:进度文字 / 已配酒店摘要 / 最近动作 |
|
||||
| list[].exceptionCount / todoCount / unreadMessageCount | Integer | 删除前:异常数 / 待办数 / 未读留言数 |
|
||||
| list[].primaryAction | HousePrimaryActionVO | 删除前:code / label / url |
|
||||
| list[].voided / requirementVersion / voidReason / voidedAt | Boolean / Integer / String / LocalDateTime | 删除前:作废信息 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/grab-pool/my-claims/hotel?status=unfinished&page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": "1940000000000000011",
|
||||
"orderId": "1930000000000000021",
|
||||
"orderNo": "26-0915",
|
||||
"guestName": "李女士一家",
|
||||
"claimerName": "张敏",
|
||||
"houseStatus": "配房中",
|
||||
"houseStatusLabel": "配房中",
|
||||
"progressDesc": "5晚已配3晚",
|
||||
"unreadMessageCount": 2,
|
||||
"voided": false
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"stats": {
|
||||
"inProgress": 1,
|
||||
"claiming": 1,
|
||||
"inInquiry": 0,
|
||||
"pendingConfirm": 0,
|
||||
"confirmed": 0,
|
||||
"exception": 0,
|
||||
"voided": 0
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定;前端移除调用点。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "pageSize 最大 100",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | keyword 长度不能超过 32 字 / page 必须 ≥ 1 / pageSize 最大 100 | 删除前的入参校验 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,删除后返回形态本文不作约定,调用点一律移除。
|
||||
- 替代接口的 `houseStatus` 是枚举码(PENDING_CLAIM / CLAIMING / PENDING_FINALIZE / CONFIRMED / EXCEPTION),中文取 `houseStatusLabel`;按本接口 `houseStatus` 中文做判断的代码要改。
|
||||
- 替代接口 `scope` 默认 `all`,查本人认领的单必须显式传 `scope=mine`。
|
||||
- 本接口的 `voided` / `voidReason` / `voidedAt` / `progressDesc` / `hotelSummary` / `lastAction` 在替代列表中没有对应字段。
|
||||
|
||||
### 3. 组长全部已抢订单(常规单) `GET /v3/admin/order/grab-pool/all-claims/hotel`
|
||||
|
||||
**VO**: `HouseMyOrderPageReqVO → HouseMyOrderPageRespVO`(均已删除)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:房务组长 / 超管的只读监督视图,查看全部已认领常规单。「房务组长」角色已取消,全体房务改用 `GET /v3/admin/order/house-allocation/households?scope=all`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| keyword | Query | String | ❌ | ≤32 字 | 删除前:同接口 2 |
|
||||
| status | Query | String | ❌ | 同接口 2 | 删除前:跟单状态 |
|
||||
| page | Query | Integer | ❌ | ≥1,默认 1 | 删除前:页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 删除前:每页条数 |
|
||||
| 其余筛选 | Query | - | ❌ | 同接口 2 | 删除前:与接口 2 共用同一请求 VO |
|
||||
|
||||
#### 出参 `Result<HouseMyOrderPageRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| list | List<HouseMyOrderItemRespVO> | 删除前:同接口 2,行内 claimerId / claimerName 标该单属哪个房务 |
|
||||
| total | Long | 删除前:总数 |
|
||||
| stats | HouseMyOrderStatsVO | 删除前:同接口 2 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/grab-pool/all-claims/hotel?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": "1940000000000000012",
|
||||
"orderId": "1930000000000000022",
|
||||
"orderNo": "26-0916",
|
||||
"claimerId": "30002",
|
||||
"claimerName": "王芳",
|
||||
"houseStatus": "待最终确认",
|
||||
"houseStatusLabel": "待最终确认"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"stats": {
|
||||
"inProgress": 0,
|
||||
"claiming": 0,
|
||||
"inInquiry": 0,
|
||||
"pendingConfirm": 1,
|
||||
"confirmed": 0,
|
||||
"exception": 0,
|
||||
"voided": 0
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定;前端移除调用点。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808092,
|
||||
"message": "无权查看全部房务订单(仅房务组长或超管可查看)",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 808092 | 无权查看全部房务订单(仅房务组长或超管可查看) | 删除前:非组长 / 超管访问;该码已删除,不复用 |
|
||||
| 400 | keyword 长度不能超过 32 字 / page 必须 ≥ 1 / pageSize 最大 100 | 删除前的入参校验 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,删除后返回形态本文不作约定,调用点一律移除。
|
||||
- 替代接口 `scope=all` 对全体房务开放;行内 `claimerName` 与 `readOnly` / `readOnlyReason` 标出该单由谁处理。
|
||||
|
||||
### 4. 我的团(团期) `GET /v3/admin/order/grab-pool/my-claims/group-batches`
|
||||
|
||||
**VO**: `HouseMyGroupPageReqVO → HouseMyGroupPageRespVO`(均已删除)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:房务查看本人整团认领的团期(scope=mine),组长 / 超管可看全部已认领团(scope=all)。现在改为 `GET /v3/admin/order/house-allocation/group-batches?status=claimed`,配合 `scope=mine` 或 `scope=all`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| scope | Query | String | ❌ | mine / all,默认 mine | 删除前:all 仅组长 / 超管 |
|
||||
| keyword | Query | String | ❌ | ≤32 字 | 删除前:团期号 / 产品名 |
|
||||
| batchStatus | Query | String | ❌ | 团期九态之一 | 删除前:团期状态 |
|
||||
| needsReconfirm | Query | Boolean | ❌ | - | 删除前:只看需求待重新确认的团 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | ❌ | - | 删除前:出发日区间 |
|
||||
| claimedAtFrom / claimedAtTo | Query | LocalDateTime | ❌ | - | 删除前:认领时间区间 |
|
||||
| page | Query | Integer | ❌ | ≥1,默认 1 | 删除前:页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 删除前:每页条数 |
|
||||
| sortBy | Query | String | ❌ | 默认 claimedAt,desc,可切 departDate,asc | 删除前:排序 |
|
||||
|
||||
#### 出参 `Result<HouseMyGroupPageRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| total | Long | 删除前:总数 |
|
||||
| stats | HouseMyGroupStatsVO | 删除前:total / needsReconfirm / cancelled(后两项在 total 超过 500 时为 null) |
|
||||
| list | List<HouseMyGroupItemRespVO> | 删除前:行列表 |
|
||||
| list[].groupBatchId / productId | Long(String) | 删除前:团期 / 产品 ID |
|
||||
| list[].batchNo / productName / batchName / batchLabel / batchStatus / batchStatusLabel | String | 删除前:团期信息 |
|
||||
| list[].departDate / endDate / enrollDeadline | LocalDate | 删除前:日期 |
|
||||
| list[].enrolledRooms / enrolledPeople / activeOrderCount / hotelOrderCount / daysToDepart | Integer | 删除前:计数 |
|
||||
| list[].hotelReady | Boolean | 删除前:酒店是否就绪 |
|
||||
| list[].urgencyLevel / urgencyLabel | String | 删除前:紧急度 |
|
||||
| list[].createTime | LocalDateTime | 删除前:创建时间 |
|
||||
| list[].houseClaimerId / houseClaimerName / houseClaimedAt | Long(String) / String / LocalDateTime | 删除前:整团认领人与时间 |
|
||||
| list[].requirementConfirmed | Boolean | 删除前:需求整体确认标记 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/grab-pool/my-claims/group-batches?scope=mine&page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"total": 1,
|
||||
"stats": {
|
||||
"total": 1,
|
||||
"needsReconfirm": 0,
|
||||
"cancelled": 0
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"groupBatchId": "1950000000000000031",
|
||||
"batchNo": "GB261005",
|
||||
"batchName": "呼伦贝尔秋色 6 日",
|
||||
"batchStatus": "PENDING_DEPARTURE",
|
||||
"departDate": "2026-10-05",
|
||||
"houseClaimerId": "30001",
|
||||
"houseClaimerName": "张敏",
|
||||
"houseClaimedAt": "2026-09-20 10:00:00",
|
||||
"requirementConfirmed": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定;前端移除调用点。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
删除前(仅供清理对照):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808092,
|
||||
"message": "无权查看全部房务订单(仅房务组长或超管可查看)",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 808092 | 无权查看全部房务订单(仅房务组长或超管可查看) | 删除前:普通房务传 scope=all;该码已删除,不复用 |
|
||||
| 400 | keyword 长度不能超过 32 字 / page 必须大于等于 1 / pageSize 最大 100 | 删除前的入参校验 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,删除后返回形态本文不作约定,调用点一律移除。
|
||||
- 替代接口没有 `needsReconfirm` 筛选;行字段 `requirementConfirmed` 仍在,可在前端按它标记。
|
||||
- 替代接口的统计为 `pendingClaim` / `claimed` / `all`,没有 needsReconfirm / cancelled 计数。
|
||||
- 替代接口 `scope` 默认 `all`,查本人整团认领的团必须显式传 `scope=mine`。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ❌ 抢单池列表 | `GET /v3/admin/order/grab-pool/hotel-requirements` → 路由已删除 |
|
||||
| ✅ 抢单池列表 | `GET /v3/admin/order/house-allocation/households?status=pendingClaim&sortBy=createTime,desc` |
|
||||
| ❌ 我的接单 | `GET /v3/admin/order/grab-pool/my-claims/hotel` → 路由已删除 |
|
||||
| ✅ 我的接单 | `GET /v3/admin/order/house-allocation/households?scope=mine&status=unfinished` |
|
||||
| ❌ 全部已抢订单 | `GET /v3/admin/order/grab-pool/all-claims/hotel` → 路由已删除 |
|
||||
| ✅ 全部已抢订单 | `GET /v3/admin/order/house-allocation/households?scope=all&status=unfinished` |
|
||||
| ❌ 我的团 | `GET /v3/admin/order/grab-pool/my-claims/group-batches` → 路由已删除 |
|
||||
| ✅ 我的团 | `GET /v3/admin/order/house-allocation/group-batches?scope=mine&status=claimed` |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
- 替代接口的状态筛选取值与旧接口不同:常规单 `status` 为 pendingClaim / unfinished(默认)/ claiming / pendingConfirm / confirmed / exception,空串=全部;团期 `status` 为 pendingClaim / claimed,不传=两者都要。旧值(`allUnfinished` / `todo` / `inProgress` / `voided` / `inInquiry`)传给替代接口会被入参校验拒绝(400)。
|
||||
- 两个替代接口的 `scope` 默认都是 `all`;原「我的接单」「我的团」对应的调用必须显式带 `scope=mine`。
|
||||
- 常规单替代接口 `sortBy` 只接受 departDate,asc(默认)/ createTime,desc / claimedAt,desc;团期替代接口只接受 departDate,asc(默认)/ claimedAt,desc / createTime,desc。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
| 前端提交 | 写入位置 | 行为 |
|
||||
|----------|----------|------|
|
||||
| 4 个已删除接口均为只读 GET | 无 | 不涉及写入 |
|
||||
|
||||
**显式 SET NULL 说明**: 不涉及。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 4 条路由已从服务端删除,删除后返回形态本文不作约定,前端不得依赖任何返回来判断,调用点一律移除。
|
||||
- 替代接口的读权限:全体房务(ROOM_MANAGER / SUPER_ADMIN)可用 `scope=all`;非房务角色返回 808090「未登录或非房务角色,无权操作」。
|
||||
- 808091、808092 随组长角色一并删除,不复用。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### 替代接口 houseStatus(HouseStateEnum)
|
||||
|
||||
**所属字段**: `HouseAllocationHouseholdRespVO.houseStatus`(替代旧接口 `HouseMyOrderItemRespVO.houseStatus` 的中文值) / **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING_CLAIM` | 待配房 | 尚未认领 |
|
||||
| `CLAIMING` | 配房中 | 已认领、配房进行中 |
|
||||
| `PENDING_FINALIZE` | 待最终确认 | 配房待最终确认 |
|
||||
| `CONFIRMED` | 已完成 | 配房完成 |
|
||||
| `EXCEPTION` | 异常 | 异常 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比(修改/删除接口必写)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 我的接单 `list[].houseStatus` | 中文状态 | 替代接口为枚举码,中文取 `houseStatusLabel` |
|
||||
| 我的接单 `list[].unreadMessageCount` | 未读留言数 | 替代接口 `unreadCount`(房务会话未读数) |
|
||||
| 我的接单 `stats` | inProgress / claiming / inInquiry / pendingConfirm / confirmed / exception / voided | 替代接口 pendingClaim / claiming / pendingConfirm / confirmed / exception / unfinished / all |
|
||||
| 我的接单 `progressDesc` / `hotelSummary` / `lastAction` / `voided` / `voidReason` / `voidedAt` | 有 | 替代列表无对应字段 |
|
||||
| 抢单池列表 `records` | 行列表字段名 | 替代接口为 `list` |
|
||||
| 我的团 `stats` | total / needsReconfirm / cancelled | 替代接口 pendingClaim / claimed / all |
|
||||
| 我的团入参 `needsReconfirm` | 有 | 替代接口无;行字段 `requirementConfirmed` 保留 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 调用 4 个旧列表接口 | 返回列表 | 路由已删除,返回形态本文不作约定 |
|
||||
| 普通房务查看全部已认领常规单 | 808091 | 替代接口 scope=all 放行 |
|
||||
| 普通房务查看全部已认领团期 | 808092 | 替代接口 scope=all 放行 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是。4 条路由删除,仍调用的页面拿不到数据。
|
||||
- **前端是否必须同步上线**: 是。服务端已无这 4 条路由,仍在调用的页面需改为替代接口。
|
||||
- **前端 workaround 清理点**: 旧抢单池页、我的接单页、组长监督视图、我的团页对这 4 个路径的调用;按 `houseStatus` 中文值、`unreadMessageCount`、`inInquiry` / `voided` 统计、808091 / 808092 做的分支。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 管理后台调用上述 4 个路径的页面。
|
||||
- **零影响**:
|
||||
- 团期抢单池列表 `GET /v3/admin/order/grab-pool/group-batches` 保留(仍为废弃标注)
|
||||
- 同一控制器的转单 `POST /v3/admin/order/hotel-requirements/{requirementId}/transfer` 保留(本次行为变化见同目录修改接口文件)
|
||||
- 小程序与 H5
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
四个旧端点都用房务 A 身份经测试服网关调用,共三轮(00:30、00:31、01:18)。HTTP 状态恒为 200,下面的 `code` 指响应 body 里的 `code`,带 ✓ 标记。行尾 `@` 后面是当时测试服 order-v3 的部署提交,两个提交都包含本单合并提交 `7c21cf0e40`。
|
||||
|
||||
```
|
||||
GET /v3/admin/order/grab-pool/hotel-requirements 抢单池列表(常规单) → code=404 ✓ @bdde64a3a,复测 ✓ @9c7ac9382
|
||||
GET /v3/admin/order/grab-pool/my-claims/hotel 我的接单(常规单) → code=404 ✓ @bdde64a3a,复测 ✓ @9c7ac9382
|
||||
GET /v3/admin/order/grab-pool/all-claims/hotel 组长全部已抢订单(常规单) → code=404 ✓ @bdde64a3a,复测 ✓ @9c7ac9382
|
||||
GET /v3/admin/order/grab-pool/my-claims/group-batches 我的团(团期) → code=404 ✓ @bdde64a3a,复测 ✓ @9c7ac9382
|
||||
```
|
||||
|
||||
验证身份:房务 A,测试专用账号。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8491](https://git.1814.love:8443/wx/HL/issues/8491)
|
||||
- 契约文档: `docs/order-v3/api/API-SPEC-HOUSE-V1.1.html` §12 房务控制台
|
||||
- 同批变更: 同目录 `30_8491_房务控制台接口-新增接口-管理后台.md`、`30_8491_房务配房接口字段与口径调整-修改接口-管理后台.md`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8491](https://git.1814.love:8443/wx/HL/issues/8491)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,245 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8493"
|
||||
title: "下线房务组长会话入口 POST /admin/message/chat/open-house-lead"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "8c9f7bc5174f379f630b4b7530ffd82a4bc1a80c"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "测试服 hl-user-service 已部署 eee6d17ef4(PR #8569)。网关实测:open-house-lead 返回 code=404,open-house 与 conversations 均返回 code=200。历史 HOUSE_LEAD 会话数据只读保留,会话列表、消息分页、标记已读三个既有接口按通用逻辑处理,不对 HOUSE_LEAD 做特殊拦截。前端已交付:chat.js 删 openHouseLead 封装;ChatDrawer HOUSE_LEAD 分支改 conversationKey 直连 messages/read/发消息,对端名片由 conversations 行 peerName/peerRoleLabel 补;housekeeper/orders 移除「联系房务」入口,历史会话从「我的消息」行直读。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-user-service:下线房务组长会话入口 `POST /admin/message/chat/open-house-lead`
|
||||
|
||||
> **服务**: hl-user-service (端口 8081)
|
||||
> **PR**: #8569(dev-v3 `eee6d17ef4`)
|
||||
> **Issue**: #8493
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台「房务组长联系房务」会话入口
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
🔴 **`POST /admin/message/chat/open-house-lead` 已整体下线**,不再接受任何调用,经网关返回业务码 `404`。这不是临时故障,是本单的预期结果。
|
||||
|
||||
🔴 **历史 HOUSE_LEAD 会话不受影响,仍可正常读写。** 只是下线了"新开一条组长会话"的入口;已存在的 `HOUSE_LEAD:{orderId}` 会话在会话列表(`GET conversations`)、消息分页(`GET {conversationKey}/messages`)、标记已读(`POST {conversationKey}/read`)、发消息(`POST {conversationKey}/messages`)四个既有接口上都按通用逻辑处理,后端不做任何额外拦截。前端对这类历史行不要再调 `open-house-lead`(也不要改调其它 `open-*`,那会新建一条会话、找不回历史那条),直接用行上的 `conversationKey` 调上述四个既有接口即可。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`open-house-lead` 原本的用法:房务组长在 `all-claims` 列表里选中一个订单,把该单房务(claimer)的 `adminId` 作为 `peerAdminId` 传入,开一个键为 `HOUSE_LEAD:{orderId}` 的组长↔房务独立会话。#8491 取消了"房务组长"角色、同时删除了 `all-claims` 读口,这个入口从此既没有使用者也没有数据来源,本单据此下线 Controller 方法、Manager 方法、专用请求 VO(`ChatOpenHouseLeadReqVO`)与专用常量(`ChatConstants.BIZ_MODULE_HOUSE_LEAD`、`ROLE_HOUSE_LEAD`、`ConversationKeyUtil.buildHouseOrderLead`)。
|
||||
|
||||
`ChatRoleEnum.HOUSE_LEAD`(值「房务组长」)本单**没有**删除:测试服 `peer_role = 'HOUSE_LEAD'` 的历史成员行不为 0,删掉枚举值会让这些历史行的角色徽章从「房务组长」退化成裸码 `HOUSE_LEAD`,故保留该枚举值只用于历史数据回显。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 打开/找回房务组长会话 | POST | `/admin/message/chat/open-house-lead` | 删除 | 接口整体下线;下线前用于开一条组长↔房务订单维度会话 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 打开/找回房务组长会话(已删除) `POST /admin/message/chat/open-house-lead`
|
||||
|
||||
**VO**: `ChatOpenHouseLeadReqVO → Result<ChatOpenFullRespVO>`(请求 VO 已随本单删除;响应类型下线前复用现仍在用的 `ChatOpenFullRespVO`——该类当前的类头注释已注明"组长会话入口 open-house-lead 已由 #8493 下线")
|
||||
|
||||
#### 使用场景
|
||||
|
||||
**已下线,不再有使用场景。** 下线前用于「房务组长在 all-claims 列表选中一个订单、为该单房务(claimer)开一条独立组长会话」。#8491 取消组长角色并删除 all-claims 读口后,这个场景已不存在。
|
||||
|
||||
#### 入参
|
||||
|
||||
**已下线,不再接受任何入参。** 以下是下线前的参数语义(工单 #8493「现状事实」节口径;字段命名与序列化方式对照同结构的 `open-house` 请求 VO——两者均为"雪花 id、JSON 按 String 透传",仅供核对旧调用代码,不代表下线前 VO 的逐字段校验注解):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Body | String | 是 | 雪花id,JSON 按 String 透传 | **已下线**。会话维度键 `HOUSE_LEAD:{orderId}` |
|
||||
| peerAdminId | Body | String | 是 | 雪花id,JSON 按 String 透传 | **已下线**。该订单房务(claimer)员工id,原由前端从 all-claims 列表传入 |
|
||||
|
||||
#### 出参
|
||||
|
||||
**已下线,不再有响应体。** 下线前复用现仍在用的 `ChatOpenFullRespVO`(继承 `ChatOpenRespVO` 基类字段):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| conversationKey | String | **已下线**。规范化会话键,形如 `HOUSE_LEAD:{orderId}` |
|
||||
| peerAdminId | Long | **已下线**。对方(房务)员工id |
|
||||
| peerName | String | **已下线**。对方姓名快照 |
|
||||
| peerRole | String | **已下线**。对方角色,值为 `HOUSE_LEAD` |
|
||||
| peerRoleLabel | String | **已下线**。对方角色中文 label,`HOUSE_LEAD` 对应「房务组长」 |
|
||||
| order | Object | **已下线**。订单卡 |
|
||||
| thread | Object | **已下线**。首屏消息(最新一页 20 条) |
|
||||
| unreadTotal | Integer | **已下线**。标记已读后的合并未读总数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
已下线,以下是下线前的请求形态,用来识别调用点:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "70123",
|
||||
"peerAdminId": "205"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**接口已下线,现在的实际响应(测试服经网关实测;HTTP 状态码 200,业务码 `code=404`):**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "接口不存在: POST /admin/message/chat/open-house-lead",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
**不适用。** 接口已不存在,不论带什么参数、用哪个账号,响应都与上面相同,没有空数据或降级分支。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
下线前这个接口没有专属错误码(越权、参数缺失都走通用的 `281005`/`281010` 等,与 `open-house` 共用一套)。现在唯一的响应就是路由未命中:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "接口不存在: POST /admin/message/chat/open-house-lead",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 下线是纯删除:只删了这一个接口,以及只为它存在的请求 VO、Manager 方法、常量、单测;`ChatOpenFullRespVO`、`ChatOpenRespVO` 等公共响应类型未动。
|
||||
- 同控制器下的 `open`、`open-house`、`open-fleet`、`open-group`、`open-group-house`、`open-group-fleet` 六个接口本单没有改动,仍可正常调用。
|
||||
- 历史 `HOUSE_LEAD:{orderId}` 会话不受影响:数据不删、不迁移,会话列表、消息分页、标记已读、发消息四个既有接口对它们照常放行(详见四、六)。
|
||||
- 不要用别的 `open-*` 接口去"找回"历史 HOUSE_LEAD 会话——键前缀不同(`HOUSE_LEAD:` vs `HOUSE:`),会新建一条会话而不是复用旧会话。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 不要调用 `POST /admin/message/chat/open-house-lead`,也不要重试或做降级兜底:它现在固定返回上面那个 404 响应。
|
||||
- 历史 HOUSE_LEAD 会话的正确访问方式:直接用会话列表行上的 `conversationKey`(形如 `HOUSE_LEAD:70123`)去调既有的 `GET /admin/message/chat/{conversationKey}/messages`(分页)、`POST /admin/message/chat/{conversationKey}/read`(已读)、`POST /admin/message/chat/{conversationKey}/messages`(发消息)——这三个端点路径、参数、响应结构本单均未改动。
|
||||
- 这三个端点对 HOUSE_LEAD 键走的是**普通会话成员校验**(`ChatMessageService.requireActiveMember`),不是团队会话的 `TeamChatAuthorizationService` 授权分支——因为 `TeamChatModule` 只有 `FLEET`/`GROUP`/`GROUP_HOUSE`/`GROUP_FLEET` 四个团队模块,`HOUSE_LEAD` 不在其中;`assertConversationAccess` 对非团队键统一返回 `null`,放行给调用方原有的成员校验,不会因为组长角色已取消就把这些历史成员判成越权。
|
||||
- `GET /admin/message/chat/conversations` 按 `bizModule=HOUSE_LEAD` 过滤仍然可用(服务端不校验 `bizModule` 取值是否在文档列出的枚举里,透传给 Mapper),可用于单独拉出历史组长会话列表。
|
||||
- 会话列表返回的 `peerRoleLabel` 字段对 HOUSE_LEAD 行固定是「房务组长」(`ChatRoleEnum.resolveLabel("HOUSE_LEAD")`),前端可直接展示,不需要自己再映射。
|
||||
- 会话列表返回的 `orderNo` 字段对 HOUSE_LEAD 行恒为 `null`(不做 order-v3 订单摘要 Feign 富化,与 HOUSE/FLEET 订单维度会话不同),不要用它判断订单归属或做展示兜底。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本单零数据库变更:没有新增 Flyway 脚本,`ai_admin_conversation`(会话)、`ai_admin_conversation_member`(成员)、`ai_admin_message`(消息)三张表结构不动。历史 `HOUSE_LEAD:*` 的会话、成员行、消息行只读保留,不删除、不迁移。接口下线只是不再产生**新的** HOUSE_LEAD 会话,不影响任何已有数据。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
| 场景 | 行为 |
|
||||
|---|---|
|
||||
| 调 `POST .../open-house-lead`,不带参数或带任意参数 | 路由未命中,返回上面的 404 响应 |
|
||||
| 历史 HOUSE_LEAD 会话调 `GET conversations`(不加 bizModule 过滤) | 正常返回,与其它会话混排,`peerRoleLabel`=「房务组长」 |
|
||||
| 历史 HOUSE_LEAD 会话调 `GET conversations?bizModule=HOUSE_LEAD` | 正常返回,只含 HOUSE_LEAD 会话 |
|
||||
| 历史 HOUSE_LEAD 会话调 `GET {conversationKey}/messages` | 正常返回,走普通成员校验,不走团队授权 |
|
||||
| 历史 HOUSE_LEAD 会话调 `POST {conversationKey}/read` | 正常返回,标记已读到最新 |
|
||||
| 历史 HOUSE_LEAD 会话调 `POST {conversationKey}/messages`(发消息) | 正常发送,后端不拦截 |
|
||||
| 非该会话成员访问 HOUSE_LEAD 会话 | 返回 `281002`(无权访问该会话),与其它会话一致 |
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### peerRole / peerRoleLabel(`ChatRoleEnum`)
|
||||
|
||||
**所属字段**: `ChatConversationRespVO.peerRole` / `peerRoleLabel`(会话列表出参,`GET /admin/message/chat/conversations`) | **类型**: `String`
|
||||
|
||||
本单只涉及 `HOUSE_LEAD` 这一个值——它在下线前是 `open-house-lead` 专属对端角色,下线后仅作为历史数据的回显值继续存在:
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `HOUSE_LEAD` | 房务组长 | 历史会话专属,本单起不会再新产生带这个值的会话;`ChatRoleEnum.resolveLabel` 未知码回退原码,故枚举值一旦被删,历史行会退化成显示裸码 `HOUSE_LEAD` 而非中文——本单保留了该枚举值,不会发生这种退化 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| `POST /admin/message/chat/open-house-lead` | 路由存在,可新开组长↔房务会话 | 路由不存在,返回 404 |
|
||||
| `ChatOpenHouseLeadReqVO` | 存在 | 已删除 |
|
||||
| `ChatConstants.BIZ_MODULE_HOUSE_LEAD` / `ROLE_HOUSE_LEAD` | 存在 | 已删除 |
|
||||
| `ConversationKeyUtil.buildHouseOrderLead` | 存在 | 已删除 |
|
||||
| `ChatRoleEnum.HOUSE_LEAD` 枚举值 | 存在,用于新建会话的对端角色 | 保留,仅用于历史会话回显 |
|
||||
| 历史 `HOUSE_LEAD:*` 会话的 conversations/messages/read/发消息 | 可用 | 不变,仍可用 |
|
||||
| 网关路由配置 | — | 未改动(`/admin/message/chat/**` 整段转发) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:对历史数据不破坏(只读保留、既有接口照常可用);对"新开组长会话"这一个操作是破坏性下线,因为它的前置角色和数据来源(#8491)已经不存在。
|
||||
- **前端是否必须同步上线**:是——继续调用已下线的 `open-house-lead` 会拿到 404。需要移除的前端调用点见「关联 / 联系人」下方备注。
|
||||
- **前端 workaround 清理点**:历史 HOUSE_LEAD 会话若在前端有专属的"打开会话"分支(调 `open-house-lead`),需要改为直接用行上的 `conversationKey` 调 `messages`/`read`;组长发起新会话的入口(原「联系房务」按钮)直接移除,不需要替换成别的接口。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:`POST /admin/message/chat/open-house-lead` 这一个接口的可用性。
|
||||
- **零影响**:
|
||||
- 同控制器下 `open`、`open-house`、`open-fleet`、`open-group`、`open-group-house`、`open-group-fleet`、`conversations`、`{conversationKey}/messages`(GET/POST)、`{conversationKey}/read`、`unread-total` 十个既有接口,均未改动。
|
||||
- 历史 HOUSE_LEAD 会话、成员、消息数据本身:不删除、不迁移。
|
||||
- 网关路由:`/admin/message/chat/**` 整段转发未改动,无需新增或删除路由配置。
|
||||
- 通知收件方配置:`HOUSE_LEAD` 收件方已由 `V20260922_211` 单独清理,与本单无关。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **部署**:hl-user-service 已部署合并提交 `eee6d17ef4`(PR #8569)。
|
||||
- **改后实测(经网关)**:
|
||||
- `POST /admin/message/chat/open-house-lead` → HTTP 200,业务码 `code=404`。
|
||||
- `POST /admin/message/chat/open-house` → HTTP 200,业务码 `code=200`(同域保留接口,未受影响)。
|
||||
- `GET /admin/message/chat/conversations` → HTTP 200,业务码 `code=200`。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8493
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8569
|
||||
- 前置工单:#8491(取消房务组长角色、删除抢单池读口 `all-claims`)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8493](https://git.1814.love/wx/HL/issues/8493)
|
||||
- **PR**: [#8569](https://git.1814.love/wx/HL/pulls/8569)
|
||||
- **Merge commit**: [eee6d17ef4](https://git.1814.love/wx/HL/commit/eee6d17ef4)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
### 前端需要移除的调用点(hl-ui,`origin/v2.1` 已核实)
|
||||
|
||||
- `src/api/chat.js`:`openHouseLead` 接口封装。
|
||||
- `src/components/chat/ChatDrawer.vue`:HOUSE_LEAD 分支里调 `open-house-lead` 打开会话的逻辑——改为对 HOUSE_LEAD 行直接用 `conversationKey` 走 `messages`/`read`。
|
||||
- `src/views/notification/MyMessages/index.vue`:消息中心按 `row.bizModule` 打开 `ChatDrawer` 时,HOUSE_LEAD 行会走到上面这条分支,需同步调整。
|
||||
- `src/views/housekeeper/orders/HouseholdTable.vue`、`src/views/housekeeper/orders/index.vue`:组长「联系房务」入口按钮,直接移除。
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8507"
|
||||
title: "财务初始化页面 tab 结构纠偏:应为 3 tab(应收初始化/应付初始化/现金银行初始化),删「员工往来」tab,应收初始化内按往来对象选 CUSTOMER/SUPPLIER_RECV(#8507 #8511)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "61f92cd88eb53dbf017a776038cdceb1eb6774f8"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "财务初始化(菜单:参数设置/财务初始化)前端页面 tab 结构与原型不符的纠偏单。原型只有 3 个 tab(应收初始化/应付初始化/现金银行初始化),按「初始化场景」划分,初始化里**没有「员工往来」期初**(员工借款/备用金属付款管理业务流程,不在初始化)。当前前端做成了 4 个 tab(客户应收/供应商应付/供应商应收/员工往来),按「往来对象类型」划分,需重构。后端接口零改动、已全部部署测试服并验证通过:应收/应付走 /admin/finance/opening-balances(按 ledgerType 区分 CUSTOMER/SUPPLIER_RECV/SUPPLIER),现金银行走 /admin/finance/fund-accounts(账户期初结存),两套接口互相独立。本文自包含全部入参/出参/枚举/错误码/示例。前端已交付:3 tab 按场景重构——应收初始化(CUSTOMER 手录+SUPPLIER_RECV 供应商下拉并入「往来对象」开关,客户分类/应收性质字典必填+所属公司必填,列表「全部」两账套各查一次合并)/应付初始化(固定 SUPPLIER)/现金银行初始化(fund-accounts,复用 AccountFormDrawer+OpeningAdjustModal);删「员工往来」tab 与 STAFF 账套;期初调整改一方向单框+佐证选填;OpeningAdjustModal 加 595105 差额为 0 幂等分流。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:财务初始化三 tab 对齐原型(管理后台)
|
||||
|
||||
> **性质**:前端实现纠偏。后端接口无新增/无变更,已在测试服就绪。本文告诉前端「正确的 tab 结构 + 每个 tab 怎么调既有接口」。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务初始化是账套启用前录入期初数据的入口(菜单:**参数设置 / 财务初始化**)。
|
||||
|
||||
原型 `finance-prototype.html`(页面 id `cfg-fininit`)规定财务初始化**只有 3 个 tab**,按「初始化场景」划分:
|
||||
|
||||
```
|
||||
财务初始化
|
||||
├─ 应收初始化 启用前欠我们的款(客户欠款 + 供应商杂项应收)
|
||||
├─ 应付初始化 启用前我们欠供应商的款
|
||||
└─ 现金银行初始化 各资金账户启用前已有结存
|
||||
```
|
||||
|
||||
**当前前端实现错误**:做成了 4 个 tab(客户应收 / 供应商应付 / 供应商应收 / 员工往来),按「往来对象类型」划分。两处偏差:
|
||||
1. tab 划分维度错了——应按「初始化场景」(应收/应付/现金银行),不是按「往来对象类型」(客户/供应商/员工)。
|
||||
2. 多出了「员工往来」tab——原型财务初始化**没有员工往来期初**。员工借款/备用金是「付款管理 / 员工借款」的业务单据流(页面 `loan-stf` / `payex-stfloan`),不属于财务初始化。
|
||||
|
||||
**「供应商应收」不是独立 tab**:它是「应收初始化」tab 内部、往来对象选「供应商」时的一种(供应商欠我们的杂项应收:押金退还/赔偿款/口车费/其他应收)。
|
||||
|
||||
## 2. 变更清单(前端 tab 结构改动)
|
||||
|
||||
| # | 改动 | 说明 |
|
||||
|---|------|------|
|
||||
| 1 | 删除「员工往来」tab | 初始化无此场景 |
|
||||
| 2 | tab 改 3 个并改名 | `应收初始化` / `应付初始化` / `现金银行初始化` |
|
||||
| 3 | 「客户应收」+「供应商应收」合并进「应收初始化」一个 tab | tab 内用「往来对象」下拉(客户/供应商)切换 ledgerType |
|
||||
| 4 | 「供应商应付」改名「应付初始化」 | 固定 ledgerType=SUPPLIER,去掉对象细分字段 |
|
||||
| 5 | 新增「现金银行初始化」tab | 接 `/admin/finance/fund-accounts` 系列接口(账户期初结存) |
|
||||
|
||||
## 3. tab ↔ 接口 / 账套映射(核心)
|
||||
|
||||
| 前端 tab | tab 内「往来对象」 | 调接口 | 传 `ledgerType` |
|
||||
|---|---|---|---|
|
||||
| 应收初始化 | 客户 | `POST /admin/finance/opening-balances` 等 | `CUSTOMER` |
|
||||
| 应收初始化 | 供应商 | 同上 | `SUPPLIER_RECV` |
|
||||
| 应付初始化 | (固定供应商,无此下拉) | 同上 | `SUPPLIER` |
|
||||
| 现金银行初始化 | — | `/admin/finance/fund-accounts` 系列 | —(无 ledgerType 概念) |
|
||||
|
||||
> 应收初始化一个 tab 对应两个 ledgerType,按用户选的「往来对象」决定传哪个;金额字段填 `openingReceivable`。应付初始化固定 `SUPPLIER`,金额填 `openingPayable`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 应收初始化 tab(ledgerType = CUSTOMER / SUPPLIER_RECV)
|
||||
|
||||
### 4.1 列表(分页)
|
||||
|
||||
`GET /admin/finance/opening-balances/page`
|
||||
|
||||
**入参(query)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| pageNo | int | 是 | 页码 |
|
||||
| pageSize | int | 是 | 每页 |
|
||||
| ledgerType | string | 是 | `CUSTOMER` 或 `SUPPLIER_RECV` |
|
||||
| kind | string | 否 | `INIT` 初始 / `ADJUST` 期初调整;空=全部 |
|
||||
| refName | string | 否 | 往来对象名模糊搜索 |
|
||||
|
||||
> 应收初始化 tab 顶部建议加「往来对象」筛选(全部/客户/供应商):客户→`CUSTOMER`、供应商→`SUPPLIER_RECV`、全部→两个 ledgerType 各查一次合并。
|
||||
|
||||
**出参(`data.records[]`)**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string | 期初行 ID |
|
||||
| ledgerType | string | 账套回显 |
|
||||
| refId | string | 往来对象 ID |
|
||||
| refName | string | 往来对象名称 |
|
||||
| customerCategory | string | 对象细分编码(CUSTOMER=客户分类 / SUPPLIER_RECV=应收性质;SUPPLIER 为 null) |
|
||||
| customerCategoryName | string | **对象细分中文名**(列表直接显示这个;字典不可用为 null) |
|
||||
| companyId | string | 所属公司主体 ID |
|
||||
| companyName | string | 所属公司主体名 |
|
||||
| openingDate | string | 期初基准日(=当前未封账账期起始日),只读 |
|
||||
| kind | string | `INIT` / `ADJUST` |
|
||||
| openingPayable | number | 期初应付(应收 tab 恒 null,忽略) |
|
||||
| openingReceivable | number | **期初应收(本 tab 显示这个金额)** |
|
||||
| evidenceUrl | string | 佐证材料影像 URL(可空) |
|
||||
| recordedByName | string | 录入人姓名 |
|
||||
| createTime | string | 创建时间 |
|
||||
|
||||
### 4.2 新建期初
|
||||
|
||||
`POST /admin/finance/opening-balances`
|
||||
|
||||
**表单(按原型应收表单三段递进)**:
|
||||
|
||||
1. **往来对象**(下拉必填):`客户` / `供应商`
|
||||
2. **对象细分**(下拉必填,标签随往来对象变):
|
||||
- 客户 → 标签「客户分类」,选项 = 字典 `fin_customer_category`
|
||||
- 供应商 → 标签「应收性质」,选项 = 字典 `fin_recv_nature`
|
||||
3. **往来对象名称**(下拉必填):
|
||||
- 供应商 → `GET /admin/supplier/items/list?status=ACTIVE`(取 `supplierId` + `shortName`/`fullName`)
|
||||
- 客户 → 客户列表(按所选客户分类过滤)
|
||||
4. **应收欠款**(数字必填,>0)
|
||||
5. **所属公司**(下拉单选必填):`GET /v3/admin/travel-agency/enabled`(取 `agencyId`+`agencyName`)
|
||||
6. **记账日期**(只读):前端不传,后端落 `openingDate`=当前账期起始日
|
||||
7. **备注**(文本域选填,≤200 字)
|
||||
|
||||
**请求体**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| ledgerType | string | 是 | `CUSTOMER`(选了客户)/ `SUPPLIER_RECV`(选了供应商) |
|
||||
| refId | Long | 是 | 往来对象 ID |
|
||||
| refName | string | 是 | 往来对象名称(≤128) |
|
||||
| customerCategory | string | 条件必填 | 对象细分编码:CUSTOMER→fin_customer_category 编码;SUPPLIER_RECV→fin_recv_nature 编码。**两账套均必填** |
|
||||
| companyId | Long | 是 | 所属公司主体 ID |
|
||||
| openingReceivable | number | 是 | 期初应收金额,>0 |
|
||||
| openingPayable | — | 否 | 应收 tab 不传(传了后端也忽略不落库) |
|
||||
| evidenceUrl | string | 否 | 佐证材料影像 URL |
|
||||
| remark | string | 否 | 备注(≤200) |
|
||||
|
||||
**响应**:`data.id` = 新建期初行 ID。
|
||||
|
||||
**请求示例(供应商应收)**:
|
||||
```json
|
||||
{
|
||||
"ledgerType": "SUPPLIER_RECV",
|
||||
"refId": 2104918057506041857,
|
||||
"refName": "柴河星悦酒店",
|
||||
"customerCategory": "DEPOSIT_REFUND",
|
||||
"companyId": 2051922156798779394,
|
||||
"openingReceivable": 1.01,
|
||||
"remark": "押金退还期初"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "id": "2105077077235662849" }, "success": true }
|
||||
```
|
||||
|
||||
### 4.3 期初调整
|
||||
|
||||
`POST /admin/finance/opening-balances/adjust`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| ledgerType | string | 是 | 同新建 |
|
||||
| refId | Long | 是 | 往来对象 ID(须已有 INIT 行,否则 598404) |
|
||||
| openingReceivable | number | 条件 | **调整后期初应收(全量值非差额)**;CUSTOMER/SUPPLIER_RECV 落库 |
|
||||
| openingPayable | number | 条件 | 调整后期初应付;仅 SUPPLIER 落库 |
|
||||
| evidenceUrl | string | 否 | 佐证影像 |
|
||||
| reason | string | **是** | 调整原因(≤200) |
|
||||
|
||||
> 分类/性质/公司沿用 INIT 行快照,调整单不开放改。
|
||||
|
||||
---
|
||||
|
||||
## 5. 应付初始化 tab(ledgerType = SUPPLIER)
|
||||
|
||||
接口同应收(`/admin/finance/opening-balances` 一套),差异:
|
||||
|
||||
| 项 | 应付初始化 |
|
||||
|---|---|
|
||||
| ledgerType | 固定 `SUPPLIER` |
|
||||
| 往来对象 | 固定「供应商」,无客户/供应商下拉;直接供应商列表 `GET /admin/supplier/items/list?status=ACTIVE` |
|
||||
| customerCategory | **不传**(SUPPLIER 忽略,供应商类别随供应商档案带出不手选) |
|
||||
| 金额字段 | 传 `openingPayable`(期初应付,>0),**不传** `openingReceivable` |
|
||||
|
||||
列表看 `openingPayable`(`openingReceivable` 恒 null)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 现金银行初始化 tab(走资金账户接口,独立)
|
||||
|
||||
此 tab 是**资金账户的期初结存**,与应收/应付的 opening-balances **完全独立**,接 `/admin/finance/fund-accounts`:
|
||||
|
||||
| 操作 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| 列表 | `GET /admin/finance/fund-accounts/page` | 本 tab 列表(列:账户名称/类型/期初结存/操作) |
|
||||
| 新建账户(录期初结存) | `POST /admin/finance/fund-accounts` | 建户时录账户信息+期初结存 |
|
||||
| 期初调整 | `POST /admin/finance/fund-accounts/{id}/opening-adjust` | 唯一改期初途径,落 OPENING 留痕流水 |
|
||||
| 建户表单三组下拉 | `GET /admin/finance/fund-accounts/options` | accountType/nature/channel(字典 fin_fund_account_*) |
|
||||
|
||||
> ⚠️ `opening-adjust` 差额为 0 时返 `595105`(不落流水),**非 200 幂等**——前端需区分「200 调整成功」vs「595105 无需调整」,不要把 595105 当失败弹错。
|
||||
|
||||
---
|
||||
|
||||
## 7. 枚举 / 数据字典
|
||||
|
||||
### 7.1 ledgerType(账套,OpeningLedgerTypeEnum)
|
||||
|
||||
**所属字段**:`ledgerType` | **类型**:`String` | **必填**:✅
|
||||
|
||||
| 值 | 中文 | 金额字段 | 用于 tab |
|
||||
|----|------|------|------|
|
||||
| `SUPPLIER` | 供应商应付(我欠他) | openingPayable | 应付初始化 |
|
||||
| `CUSTOMER` | 客户应收(他欠我们) | openingReceivable | 应收初始化(往来对象=客户) |
|
||||
| `SUPPLIER_RECV` | 供应商应收(他欠我们:押金退还/赔偿款/口车费/其他应收) | openingReceivable | 应收初始化(往来对象=供应商) |
|
||||
|
||||
### 7.2 kind(类别)
|
||||
|
||||
**所属字段**:`kind` | **类型**:`String` | **必填**:❌(查询过滤用)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `INIT` | 初始 | 首次录入(同对象仅一次) |
|
||||
| `ADJUST` | 期初调整 | 对已有 INIT 的调整留痕 |
|
||||
|
||||
### 7.3 应收性质(字典 fin_recv_nature,SUPPLIER_RECV 的 customerCategory)
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `DEPOSIT_REFUND` | 押金退还 |
|
||||
| `COMPENSATION` | 赔偿款 |
|
||||
| `CAR_FEE` | 口车费 |
|
||||
| `OTHER` | 其他应收 |
|
||||
|
||||
### 7.4 客户分类(字典 fin_customer_category,CUSTOMER 的 customerCategory)
|
||||
|
||||
经 `GET /admin/dict/all` 或字典接口取 `fin_customer_category` 当前生效值。
|
||||
|
||||
---
|
||||
|
||||
## 8. 下拉数据源汇总
|
||||
|
||||
| 下拉 | 接口 / 字典 |
|
||||
|---|---|
|
||||
| 供应商列表 | `GET /admin/supplier/items/list?status=ACTIVE` |
|
||||
| 公司主体 | `GET /v3/admin/travel-agency/enabled` |
|
||||
| 客户分类(应收-客户) | 字典 `fin_customer_category` |
|
||||
| 应收性质(应收-供应商) | 字典 `fin_recv_nature`(押金退还/赔偿款/口车费/其他应收) |
|
||||
| 资金账户类型/性质/渠道 | `GET /admin/finance/fund-accounts/options` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 598401 | 同往来对象 INIT 已录过 | 重复新建初始期初 |
|
||||
| 598403 | 账期已封账 | 封账后新建/调整 |
|
||||
| 598404 | 须先录初始期初 | 调整时无 INIT 行 |
|
||||
| 598405 | 期初金额须大于0 | 金额 ≤0 |
|
||||
| 598407 | 公司主体非法 | companyId 无效 |
|
||||
| 598408 | 客户分类必填 | CUSTOMER 缺 customerCategory |
|
||||
| 598409 | 供应商应收性质必填 | SUPPLIER_RECV 缺 customerCategory |
|
||||
| 598410 | 对象细分取值非法 | customerCategory 不在字典内 |
|
||||
| 595105 | 期初调整差额为 0 | 现金银行 opening-adjust 无需调整(非错误) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 项 | 改前(当前前端错误) | 改后(对齐原型) |
|
||||
|---|---|---|
|
||||
| tab 数 | 4 个 | **3 个** |
|
||||
| tab 名 | 客户应收/供应商应付/供应商应收/员工往来 | **应收初始化/应付初始化/现金银行初始化** |
|
||||
| 划分维度 | 往来对象类型 | **初始化场景** |
|
||||
| 员工往来 tab | 有(多出) | **删除** |
|
||||
| 供应商应收 | 独立 tab | **并入应收初始化**(往来对象=供应商,ledgerType=SUPPLIER_RECV) |
|
||||
| 现金银行初始化 | 缺 | **新增**(接 fund-accounts 期初) |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **后端接口变更**:无(零改动,已 deployed)
|
||||
- **是否破坏向后兼容**:前端页面重构,接口契约不变
|
||||
- **前端是否必须同步上线**:是(当前 4 tab 结构与后端账套语义不符,「员工往来」tab 调任何接口都会失败——后端无员工往来账套)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 金额字段二选一:应收 tab 填 `openingReceivable`、应付 tab 填 `openingPayable`,**不要同传两个**(后端按 ledgerType 只落对应方向,另一方向忽略)。
|
||||
- 记账日期只读、前端不传,后端落当前账期起始日。
|
||||
- 同一往来对象 INIT 仅可录一次;要改走「期初调整」。
|
||||
- 「现金银行初始化」与「应收/应付初始化」是两套独立接口(fund-accounts vs opening-balances),不要混用。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8507](https://git.1814.love/wx/HL/issues/8507) / [#8511](https://git.1814.love/wx/HL/issues/8511)
|
||||
- **PR**: [#8509](https://git.1814.love/wx/HL/pulls/8509) / [#8513](https://git.1814.love/wx/HL/pulls/8513)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 待认领
|
||||
@@ -0,0 +1,273 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8508"
|
||||
title: "调整配房(placement)已确认行同事务作废应付台账,有在途付款申请时新增 599602 拒绝"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8575 已合并 dev-v3(合并提交 72baca1fd),测试服 order-v3 运行 99fb369ba8(含本单)。测试服实测:已确认行调整后原台账行作废、配房行回到询价中;再确认后生成一条新台账行,金额=结算价×新间数;调整后在询价中删除,无孤儿台账行。599602 拒绝、已付行红冲、无台账存量行、询价中阴性对照由单测覆盖:在途付款申请与已付状态需要财务域写入才能造出来,测试服不造。gateway_status=not_required:路径与方法未变。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 调整配房已确认行同事务作废应付台账,有在途付款申请时新增 599602 拒绝
|
||||
|
||||
> **存放目录**:
|
||||
> - 一期(v2,无 `order-v3` 标签的工单)→ `changelogs/{YYYY-MM}/`
|
||||
> - 二期(v3,`order-v3` 标签的工单)→ `changelogs-v2/{YYYY-MM}/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #8575
|
||||
> **Issue**: #8508
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台「房务配房工作台」§2.3b 调整单条配房位置与资源接口,原配房行为已确认(CONFIRMED)且有在途付款申请的调整场景
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
|
||||
|
||||
- `PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement` 新增一条失败分支:原配房行为「已确认(CONFIRMED)」,且该行的应付款台账有在途付款申请(`applied>0`)时,本次调整整体失败,返回 **599602**。此前这种情况会调整成功,但应付台账仍停在旧酒店旧价,产生台账与配房不一致。
|
||||
- 除新增该错误码外,接口路径、方法、入参字段(`AssignmentPlacementUpdateReqVO`)、出参(`Result<Void>`)逐字节不变。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 调整单条配房位置与资源 | PUT | `/v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement` | 新增可能返回的错误码 | 原行已确认且应付台账有在途付款申请时返 599602 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 调整单条配房位置与资源 `PUT /v3/admin/order/hotel-requirements/{requirementId}/assignments/{id}/placement`
|
||||
|
||||
**VO**: `AssignmentPlacementUpdateReqVO → Void`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务对已存在的单条配房行原子调整目标晚次、酒店、房型和房间数(前端只传目标 dayNumber,入住日期由后端按当前订单行程推导)。本次改动不涉及入参/出参字段,只新增一条业务失败分支:原行为「已确认」且其应付款台账行有在途付款申请时,整次调整被拒绝。
|
||||
|
||||
#### 入参
|
||||
|
||||
路径参数 + Body(与改造前逐字节相同):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| requirementId | Path | Long | ✅ | - | 当前生效住宿需求 ID |
|
||||
| id | Path | Long | ✅ | - | house_hotel_assignment 主键 |
|
||||
| dayNumber | Body | Integer | ✅ | ≥1 | 目标晚次(1=第1晚) |
|
||||
| hotelId | Body | Long | ✅ | - | 目标酒店 ID |
|
||||
| roomTypeId | Body | Long | ✅ | - | 目标房型 ID,须归属该 hotelId |
|
||||
| roomCategory | Body | String | ✅ | `@NotBlank` | 目标房型字典 code |
|
||||
| roomCount | Body | Integer | ✅ | ≥1 | 目标房间数 |
|
||||
| protoPrice | Body | BigDecimal | - | ≥0 | 协议价快照,不传按目标房型和目标入住日读取资源价格日历 |
|
||||
| settlementPrice | Body | BigDecimal | - | ≥0 | 结算价快照,不传同上兜底 |
|
||||
| settleType | Body | String | - | `cash\|sign\|company` | 支付方式快照 |
|
||||
| deductInventory | Body | Boolean | ✅ | - | 是否扣减资源库存 |
|
||||
| breakfast | Body | String | - | `INCLUDED\|EXCLUDED\|PENDING` | 早餐,不传保留原值 |
|
||||
| cancelProofFileIds | Body | List\<Long\> | - | 最多 9 个 | 原酒店取消凭证文件 ID |
|
||||
| cancelFee | Body | BigDecimal | - | ≥0,最多 2 位小数 | 原酒店取消费用 |
|
||||
| changeRemark | Body | String | - | ≤200 字 | 改配说明 |
|
||||
| syncProtocolPrice | Body | Boolean | - | 默认 false | 是否同步协议价到目标房型目标日期价格日历 |
|
||||
| syncSettlementPrice | Body | Boolean | - | 默认 false | 是否同步结算价 |
|
||||
| syncSettleType | Body | Boolean | - | 默认 false | 是否同步支付方式到目标酒店资源 |
|
||||
| remark | Body | String | - | - | 备注,不传保留原备注 |
|
||||
|
||||
#### 出参 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | null | 无返回数据 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dayNumber": 2,
|
||||
"hotelId": 200001,
|
||||
"roomTypeId": 300001,
|
||||
"roomCategory": "STANDARD",
|
||||
"roomCount": 2,
|
||||
"protoPrice": 320.00,
|
||||
"settlementPrice": 280.00,
|
||||
"settleType": "cash",
|
||||
"deductInventory": true,
|
||||
"syncProtocolPrice": false,
|
||||
"syncSettlementPrice": false,
|
||||
"syncSettleType": false,
|
||||
"remark": "改期后重新询房"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": null, "success": true }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口无「空数据」概念(成功恒返回 `data: null`),无降级路径。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 599602, "message": "应付款台账行已锁定", "data": null, "success": false }
|
||||
```
|
||||
|
||||
| code | 触发条件 |
|
||||
|------|----------|
|
||||
| **599602(本次新增)** | 原配房行为已确认(CONFIRMED),且其应付款台账最新有效行有在途付款申请(`applied>0`) |
|
||||
|
||||
(其余既有错误码——鉴权、参数校验、需求状态、库存迁移相关——均未变化,本次未改动,不在此重复列出。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 触发条件只看原配房行的 `confirmStatus`:只有原行为 `CONFIRMED` 时才会查应付台账锁;原行为 `INQUIRING` 时零台账查询,行为与改造前完全一致。
|
||||
- 599602 命中时**零写入**:库存迁移分两处闸——一次是预占目标库存之前的只读预检,一次是写库前事务内的兜底闸;命中任一处都不会预占新库存、不会改配房行、不会动台账;若预检已通过但事务内并发被占用而在兜底闸命中,已预占的新库存会被同步补偿释放。
|
||||
- 原行已确认且已付(`paid>0`)不受本次新增拒绝影响:由财务域内部对该行做整行红冲(`CLOSED`),调整照常成功。
|
||||
- 原行已确认但没有应付台账行(台账上线前确认的存量行):判存后跳过台账处理,调整照常成功,不报错。
|
||||
- 调整成功后该行照旧退回询价中(`INQUIRING`,改造前既有行为不变);台账不在本次调整时重推,由下次单日确认按新酒店、新间数、新结算价重新推送。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写**后端接受/拒绝 payload 的规则**,不写 UI 渲染建议。
|
||||
|
||||
- 触发 599602 与请求体字段无关,完全由服务端读取的原配房行状态(`confirmStatus`)与其应付台账的 `applied` 值决定;前端无法通过改写请求体规避或复现这条错误,只能据响应处理。
|
||||
- 判断失败必须读响应体 `code === 599602`(业务失败,HTTP 状态码仍是 200),不要判 HTTP 状态码。
|
||||
- 该错误码不是全新码——删除、改价、清空三条既有写路径已经会返回同一个 599602;前端若已对这三条路径统一处理该码,调整配房无需额外新增分支即可覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
本次不改变调整配房**成功**时原有的写入内容(配房行更新、库存迁移记账逐字节不变)。新增变化只发生在「原行已确认」这一分支:
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 原行已确认、有台账行、未付、无在途申请 | 台账行不处理,停留在旧酒店旧价 | 同事务作废该台账行(未付取消),配房行照常更新 |
|
||||
| 原行已确认、已付(`paid>0`)、无在途申请 | 台账行不处理 | 同事务由财务域整行红冲(`CLOSED`),配房行照常更新 |
|
||||
| 原行已确认、有在途付款申请(`applied>0`) | 调整成功,台账行不处理 | 整次调整失败(599602),配房行、库存、台账三者均不写入 |
|
||||
| 原行已确认、无台账行(存量行) | 调整成功 | 调整成功(无变化) |
|
||||
| 原行询价中(INQUIRING) | 调整成功,不涉及台账 | 调整成功,不涉及台账(无变化) |
|
||||
|
||||
被作废的台账行不在本次调整时重新推送;下一次该晚单日确认(confirmDay)时按新酒店、新间数、新结算价重新推送。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 非房务角色 → 808090(原「房务组长只读监督 808091」已随 #8491 删除:全体房务可读全部配房单)
|
||||
- 配房行不存在,或 requirementId 与该行实际归属需求不一致 → 808120
|
||||
- 需求不存在 / 已作废 / 不属于当前用户 / 未被抢单 → 808100/808113/808110/808116
|
||||
- 订单已取消或异常处置中 → 808119
|
||||
- 目标 dayNumber 超出应配晚数 → 808102
|
||||
- 目标房型不属于目标酒店 → 808112
|
||||
- 涉及库存迁移但原配房缺少可释放的持有日志 → 808126
|
||||
- 团期子订单未成团 / 班期未建团 → 589552/589553
|
||||
- **原行已确认且应付台账有在途付款申请(本次新增)→ 599602,HTTP 200,零写入**
|
||||
- 老数据兼容:请求/响应字段无增删,存量数据无需迁移
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
`confirmStatus` 不是本接口的请求/响应字段,但它是**决定本次新增分支是否触发**的关键状态,前端可从既有的配房行读接口(如 `HouseOrderDetailRespVO` 里配房行的 `confirmStatus`/`confirmStatusLabel`,字段本身未变)预判会不会撞上 599602。
|
||||
|
||||
### confirmStatus(配房行确认态,`HouseAssignmentConfirmStatus`)
|
||||
|
||||
**所属字段**: 配房行 `confirmStatus`(既有字段,本次未变) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `INQUIRING` | 询房中 | 调用本接口零台账查询,行为与改造前一致 |
|
||||
| `CONFIRMED` | 已确认 | 调用本接口会先查该行应付台账是否有在途付款申请,命中则 599602 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| (无)| 请求体、响应体字段逐字节不变 | 同左 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 原行已确认、有台账行、有在途付款申请时调整 | 调整成功,台账行不处理,停留在旧酒店旧价 | 整次调整失败,返回 599602,配房/库存/台账三者均不变 |
|
||||
| 原行已确认、有台账行、无在途申请(未付或已付)时调整 | 台账行不处理 | 同事务作废(未付取消 / 已付整行红冲),下次单日确认按新值重推 |
|
||||
| 原行已确认、无台账行的存量行调整 | 调整成功 | 调整成功(无变化) |
|
||||
| 原行询价中时调整 | 调整成功,不涉及台账 | 调整成功,不涉及台账(无变化) |
|
||||
| 请求/响应字段 | 不变 | 不变 |
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
- **是否破坏向后兼容**: 否——不改字段、不改既有错误码语义;新增一条此前不存在的失败路径(599602),且该码在删除/改价/清空三条既有路径中已经存在。
|
||||
- **前端是否必须同步上线**: 若前端已对既有的删除/改价/清空三条路径统一处理 599602(按响应体 `code` 分支、非静默吞掉),调整配房无需新增代码即可覆盖;若尚未统一处理,需要补上。
|
||||
- **前端 workaround 清理点**: 无。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: `PUT .../placement` 端点,原配房行为已确认(CONFIRMED)时的应付台账处理与新增失败分支
|
||||
- **零影响**:
|
||||
- 原配房行为询价中(INQUIRING)时的调整(零台账查询,行为不变)
|
||||
- 已确认但无应付台账行的存量配房行调整(判存后跳过,成功不报错)
|
||||
- 已确认且已付(`paid>0`)的配房行调整(由财务域内部整行红冲,不新增拒绝)
|
||||
- 本接口的请求体、响应体字段结构
|
||||
- 删除、改价、清空、转房、订单取消级联等既有路径对 599602 的处理逻辑(本次未改动这些路径)
|
||||
- hl-gateway 路由配置——端点路径、方法零变化
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服 order-v3 运行 `99fb369ba8` 及其后代 `ff68637542`(均含 PR #8575 合并提交 `72baca1fd`),全部用自造订单:
|
||||
|
||||
- 已确认行调整(`PUT .../assignments/{id}/placement`,间数 1→2):`code=200`,配房行回到 `INQUIRING`,原台账行(seq=0)软删,改后无有效 NORMAL 行。
|
||||
- 再确认该晚:配房行回到 `CONFIRMED`,台账新增 1 条有效行 seq=1,`payable=600.00`(300.00×2)。
|
||||
- 换酒店(同一接口,改为同城另一家酒店,且两家酒店的供应商不同):`code=200`,配房行回到 `INQUIRING`,原台账行软删;再确认后台账有且只有 1 条有效行,`resource_id` 为新酒店,`supplier_id` 为新酒店的供应商(与原供应商不同),`payable=280.00`(280.00×1)。本条在 order-v3 `ff68637542` 上取证。
|
||||
- 调整后在询价中删除(`DELETE /v3/admin/order/assignments/{id}`):只读孤儿判据 SQL 读数 0,无孤儿台账行。
|
||||
|
||||
以下分支由随 PR 提交的单测覆盖(`HouseAssignmentServiceTest`,`DisplayName` 均带 `#8508` 前缀;测试服不造「在途付款申请」「已付」状态,因为它们要在财务域写入):
|
||||
|
||||
- `updatePlacement_payableLockedAtPreflight_throws599602BeforeInventoryDeduct`:预检命中在途申请 → 599602,库存迁移、配房行更新、台账作废均未调用。
|
||||
- `updatePlacement_payableLockedAtFallback_throws599602AndCompensatesTargetInventory`:预检通过、事务内并发被占用 → 599602,新预占库存同步释放。
|
||||
- `updatePlacement_confirmedPaidLine_removesLineAndMigratesInventory`:已付(`paid>0`)无在途申请 → 不拦,作废台账行一次;财务域据此整行红冲(`CLOSED`),再推时按 seq+1 生成新行(财务域既有单测 `PayablePushServiceTest` 覆盖)。
|
||||
- `updatePlacement_confirmedWithoutPayableLine_skipsRemoveAndSucceeds`:已确认无台账行的存量行 → 调整照常成功。
|
||||
- `updatePlacement_inquiringOriginal_skipsPayableQueries`:原行询价中 → 不做任何台账查询或作废。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8508](https://git.1814.love:8443/wx/HL/issues/8508)
|
||||
- 关联 PR: [wx/HL#8575](https://git.1814.love:8443/wx/HL/pulls/8575)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8508](https://git.1814.love:8443/wx/HL/issues/8508)
|
||||
- **PR**: [#8575](https://git.1814.love:8443/wx/HL/pulls/8575)
|
||||
- **Merge commit**: [72baca1fd](https://git.1814.love:8443/wx/HL/commit/72baca1fd)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,590 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8516"
|
||||
title: "团期新增核单 / 结算状态两个内部回写接口(财务调用):改一列即推导团期主状态、同事务同步子订单状态,任何一步都不推报账单"
|
||||
consumer: "internal"
|
||||
author: "jw(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "新增 POST /v3/internal/group-batch/:groupBatchId/review-status 与 /settlement-status,供财务回写团期整团核单、结算状态。两个接口都是 internal:不经网关(公网网关对 /v3/internal/** 返回 code 403),直连 order-v3 带 X-Internal-Token,缺失或错误返回 HTTP 403;前端无需对接。团期主状态待核单 / 核单中 / 已结算由两列推导,与两列同一次原子更新落库;子订单在核单中、退回待核单、已结算、离开已结算四步同事务同步(只改状态、不推报账单),团期已核单及其退回不同步子订单。已合并 dev-v3(PR #8650,merge commit 54e64c50f),2026-09-30 部署 TEST(dev-v3 dd0452916,迁移 20260929.8516)并按 AC-05~AC-12 实测。管理后台读侧与既有写入口的变化见同日 30_8516 修改接口那份。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期:新增核单 / 结算状态两个内部回写接口(财务调用)
|
||||
|
||||
> **服务**: hl-order-service-v3(端口 8086 / 8186,双实例)
|
||||
> **PR**: #8650(merge commit `54e64c50f`)
|
||||
> **Issue**: #8516
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 财务侧服务间调用,回写团期整团核单 / 结算状态;管理后台读侧见同日 `30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 团期新增两个独立状态:核单 `reviewStatus`(与子订单同名同值)、结算 `settlementStatus`(比子订单多一个「结算中」),初始都是 `NONE`。
|
||||
- 团期主状态里「待核单 / 核单中 / 已结算」三态**不再手动推进**,一律由这两个状态推导(推导表见六.5)。
|
||||
- 财务通过本文两个接口回写这两个状态。系统唯一的自动写入是出行完毕定时任务(1042):团期出行结束时把核单置为「待核单」。
|
||||
- 子订单跟着同步状态,**任何一步都不推报账单**;团期「已核单」及从已核单退回,**不同步**子订单。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
09-29 复盘团期出行完毕后的整条线:只走团级核单链路(一团一张报账单)时,子订单永远停在「待结算」、团期停在「核单中」,看板「结算」节点对这类团恒为空;代码里也没有「结算中」。jw 定口径:
|
||||
|
||||
1. 出行结束后团期自动进入「待核单」;
|
||||
2. 核单中、已核单、待结算、结算中、已结算都由财务从外部更新;
|
||||
3. 团期上加核单、结算两个独立状态,写法参照配房、配车;
|
||||
4. 子订单跟着团期同步状态。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 改团期核单状态 | POST | `/v3/internal/group-batch/:groupBatchId/review-status` | 新增 | 财务回写整团核单状态(待核单 / 核单中 / 已核单),主状态随之推导,按规则同步子订单 |
|
||||
| 2 | 改团期结算状态 | POST | `/v3/internal/group-batch/:groupBatchId/settlement-status` | 新增 | 财务回写整团结算状态(待结算 / 结算中 / 已结算),前提是核单已完成,按规则同步子订单与核团 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 改团期核单状态 `POST /v3/internal/group-batch/:groupBatchId/review-status`
|
||||
|
||||
**VO**: `GroupBatchReviewSettleStatusRespVO`(入参 `GroupBatchReviewSettleStatusReqVO`,两个接口共用)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
财务开始核单、完成核单、或发现问题要退回核单时,回写团期整团核单状态。服务间调用:直连 order-v3 实例(8086 / 8186),请求头带 `X-Internal-Token`(经 Feign 调用时由内部令牌拦截器自动加头)。hl-finance 与订单服务同进程,也可以不走 HTTP,直接注入 `GroupBatchReviewSettleService#changeReviewStatus` 调用,规则完全相同。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| X-Internal-Token | Header | String | 是 | 服务间内部令牌 | 缺失或错误返回 HTTP 403 |
|
||||
| groupBatchId | Path | Long | 是 | 团期 ID | 团期不存在返回 589500 |
|
||||
| status | Body | String | 是 | 只能是 `PENDING` / `IN_PROGRESS` / `COMPLETED`,区分大小写 | 目标核单状态:待核单 / 核单中 / 已核单。`NONE`、空串、纯空白、null、小写、其他值一律 code 400,零写入 |
|
||||
| operatorName | Body | String | 否 | 最长 64 字符 | 财务侧操作人姓名,写进团期时间线的操作人与子订单日志;超长返回 code 400 |
|
||||
| reason | Body | String | 否 | 最长 500 字符 | 理由,写进团期时间线「原因」与子订单日志;超长返回 code 400 |
|
||||
|
||||
#### 出参 `Result<GroupBatchReviewSettleStatusRespVO>`
|
||||
|
||||
状态字段一律是**写后**的当前值。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | String | 团期 ID(按字符串输出) |
|
||||
| batchStatus | String | 团期主状态(由两个状态推导):`PENDING_REVIEW` 待核单 / `REVIEWING` 核单中 / `SETTLED` 已结算 |
|
||||
| batchStatusName | String | 主状态中文名 |
|
||||
| reviewStatus | String | 核单状态:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` |
|
||||
| reviewStatusName | String | 未核单 / 待核单 / 核单中 / 已核单 |
|
||||
| settlementStatus | String | 结算状态:`NONE` / `PENDING` / `IN_PROGRESS` / `COMPLETED` |
|
||||
| settlementStatusName | String | 未结算 / 待结算 / 结算中 / 已结算 |
|
||||
| changed | Boolean | `true` 本次改了库;`false` 目标值与当前值相同(幂等命中),零写入、不留痕、不同步子订单 |
|
||||
| subOrderSyncedCount | Integer | 本次**真正改了状态**的子订单户数。本次变化不触发子订单同步时为 `null`;触发了但各户都已在目标状态时为 `0` |
|
||||
| subOrderSkippedOrderIds | List\<String\> | 不在可同步状态、被跳过的子订单 ID(按字符串输出),需财务跟进。不触发同步时为 `null`;触发了但无人被跳过时为 `[]`。已经处于目标状态的户**不**列入,也不计入 subOrderSyncedCount |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/internal/group-batch/2105223439872933889/review-status HTTP/1.1
|
||||
Host: 192.168.100.236:8086
|
||||
X-Internal-Token: <内部令牌>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "IN_PROGRESS",
|
||||
"operatorName": "财务-王丽华",
|
||||
"reason": "呼伦贝尔草原3日游·9月25日团开始核单"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
团期 T26-2325 从待核单改为核单中:出行过的王海峰户同步进核单中;赵淑芬户出团时仍在定制中、没有随团进入待核单,本次被跳过(TEST 2026-09-30 17:34 实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223439872933889",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusName": "核单中",
|
||||
"reviewStatus": "IN_PROGRESS",
|
||||
"reviewStatusName": "核单中",
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未结算",
|
||||
"changed": true,
|
||||
"subOrderSyncedCount": 1,
|
||||
"subOrderSkippedOrderIds": ["2105223440825020417"]
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口没有列表型空数据。以下两种是「成功但没有同步动作」:
|
||||
|
||||
1. 同值幂等:目标值与当前值相同,`changed=false`,两个同步字段为 `null`,零写入(模拟财务重试,17:33 实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223438312652802",
|
||||
"batchStatus": "PENDING_REVIEW",
|
||||
"batchStatusName": "待核单",
|
||||
"reviewStatus": "PENDING",
|
||||
"reviewStatusName": "待核单",
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未结算",
|
||||
"changed": false,
|
||||
"subOrderSyncedCount": null,
|
||||
"subOrderSkippedOrderIds": null
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
2. 改为已核单、或从已核单退回:只改团期,不同步子订单,`changed=true`,两个同步字段为 `null`(团期 T26-2325 从待核单直接改为已核单,17:46 实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223439872933889",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusName": "核单中",
|
||||
"reviewStatus": "COMPLETED",
|
||||
"reviewStatusName": "已核单",
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未结算",
|
||||
"changed": true,
|
||||
"subOrderSyncedCount": null,
|
||||
"subOrderSkippedOrderIds": null
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少或错误的内部令牌(HTTP 403,body 字段名是 `msg`,与 `vehicle-ready` 等内部接口同形):
|
||||
|
||||
```json
|
||||
{ "code": 403, "msg": "内部接口禁止外部访问" }
|
||||
```
|
||||
|
||||
status 非法(HTTP 200,code 400,零写入):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "status 取值只能是 PENDING、IN_PROGRESS、COMPLETED",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
结算已是已结算时退回核单(零写入):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589702,
|
||||
"message": "结算已完成,请先把结算状态改回待结算或结算中,再退回核单",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | 触发条件 | 调用方下一步 |
|
||||
|------|----------|--------------|
|
||||
| HTTP 403 | 缺少或错误的 `X-Internal-Token` | 检查令牌配置 |
|
||||
| 400 | status 缺失 / 空串 / 纯空白 / null(`status 不能为空`);取值不是三者之一(`status 取值只能是 PENDING、IN_PROGRESS、COMPLETED`,含 `NONE`、小写、`TRIP_FINISHED`);operatorName 超 64(`operatorName 不能超过 64 个字符`);reason 超 500(`reason 不能超过 500 个字符`)。多条同时违反时 message 以 `; ` 连接 | 修正入参 |
|
||||
| 589500 | 团期不存在(`团期不存在`) | 核对团期 ID |
|
||||
| 589700 | 团期还没出行完毕(招募中 ~ 出行中)或已流团:`团期当前状态为「招募中」,不可修改核单或结算状态(须已出行完毕且未流团)`,「」内是当前主状态中文名 | 不要调 |
|
||||
| 589702 | 从已核单退回(改为待核单或核单中),而结算已是已结算 | 先调接口 2 把结算改回待结算或结算中 |
|
||||
| 589703 | 读到写之间,团期主状态 / 核单 / 结算任一被并发改动(`团期核单或结算状态已被他人修改,请刷新后重试`),零写入 | 重新读取后决定是否重调 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:只认 `X-Internal-Token`,不经网关、不走管理员登录与团期权限码;本接口不另做判权。
|
||||
- **阶段门**:团期主状态必须是待核单 / 核单中 / 已结算之一(已出行完毕且未流团),否则 589700。
|
||||
- **幂等**:目标值等于当前值 → `changed=false`,不写库、不写时间线、不同步子订单。
|
||||
- **从已核单退回**(改为核单中或待核单):结算未到已结算时,结算**自动置回 `NONE`**,时间线写明「结算状态随之置回」;结算已是已结算时拒绝 589702。
|
||||
- **允许从待核单直接改为已核单**(跳过核单中):此时子订单停在待核单;之后结算改为已结算时,这些户不会被带上,出现在 `subOrderSkippedOrderIds` 里。由财务控制,系统不拦(jw 09-30 定)。
|
||||
- **子订单同步**(与团期改动同一事务,全有全无;只改状态,不推报账单):
|
||||
|
||||
| 本次核单变化 | 哪些子订单被改 | 子订单核单 / 结算 / 流程状态改成 |
|
||||
|---|---|---|
|
||||
| 待核单 → 核单中 | 核单状态为待核单的户 | 核单中 / 未结算 / 核单中(`IN_PROGRESS` / `NONE` / `REVIEWING`) |
|
||||
| 核单中 → 待核单 | 核单状态为核单中的户 | 待核单 / 未结算 / 待核单(`PENDING` / `NONE` / `PENDING_REVIEW`) |
|
||||
| → 已核单 | **不同步**(子订单的已核单只靠逐户提交核单) | — |
|
||||
| 已核单 → 核单中 / 待核单 | **不同步**,只改团期(已逐户提交的户保持已核单,某户要改走逐户反确认) | — |
|
||||
|
||||
- 范围是本团未取消的子订单;已在目标状态的户不改、不计数、不列入跳过名单;其余不在可同步状态的户列入 `subOrderSkippedOrderIds`。
|
||||
- **留痕**:核单真的变化时写一条团期时间线「核单状态变更」(带操作人与原因);主状态随之变化时另有一条主状态流转行。每个被同步的子订单写一条订单日志,带团期 ID 与来源。
|
||||
- **不带「期望的当前状态」**:以调用时库里的当前值为准。由财务保证不重试、不乱序(见四)。
|
||||
|
||||
### 2. 改团期结算状态 `POST /v3/internal/group-batch/:groupBatchId/settlement-status`
|
||||
|
||||
**VO**: `GroupBatchReviewSettleStatusRespVO`(入参 `GroupBatchReviewSettleStatusReqVO`,两个接口共用)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
财务在团级报账单推出后把团期结算改为待结算、付款开始后改为结算中、付清后改为已结算;出纳冲正、付款失败等需要回退时,把结算从已结算改回待结算或结算中。调用方式同接口 1;同进程也可直接调用 `GroupBatchReviewSettleService#changeSettlementStatus`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| X-Internal-Token | Header | String | 是 | 服务间内部令牌 | 缺失或错误返回 HTTP 403 |
|
||||
| groupBatchId | Path | Long | 是 | 团期 ID | 团期不存在返回 589500 |
|
||||
| status | Body | String | 是 | 只能是 `PENDING` / `IN_PROGRESS` / `COMPLETED`,区分大小写 | 目标结算状态:待结算 / 结算中 / 已结算。`NONE`、空串、纯空白、null、小写、其他值一律 code 400,零写入 |
|
||||
| operatorName | Body | String | 否 | 最长 64 字符 | 财务侧操作人姓名,写进团期时间线与子订单日志;超长返回 code 400 |
|
||||
| reason | Body | String | 否 | 最长 500 字符 | 理由,写进团期时间线与子订单日志;超长返回 code 400 |
|
||||
|
||||
#### 出参 `Result<GroupBatchReviewSettleStatusRespVO>`
|
||||
|
||||
与接口 1 同一个 VO,状态字段一律是写后的当前值。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | String | 团期 ID(按字符串输出) |
|
||||
| batchStatus | String | 团期主状态:结算为已结算时 `SETTLED`,否则 `REVIEWING` |
|
||||
| batchStatusName | String | 主状态中文名 |
|
||||
| reviewStatus | String | 核单状态(本接口不改它,恒为 `COMPLETED`) |
|
||||
| reviewStatusName | String | 已核单 |
|
||||
| settlementStatus | String | 结算状态:`PENDING` / `IN_PROGRESS` / `COMPLETED`(幂等命中时为当前值) |
|
||||
| settlementStatusName | String | 待结算 / 结算中 / 已结算 |
|
||||
| changed | Boolean | `true` 本次改了库;`false` 幂等命中,零写入 |
|
||||
| subOrderSyncedCount | Integer | 同接口 1:只数本次真正改了状态的户;不触发同步时为 `null` |
|
||||
| subOrderSkippedOrderIds | List\<String\> | 同接口 1:不触发同步时 `null`,触发了但无人被跳过时 `[]` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/internal/group-batch/2105223438312652802/settlement-status HTTP/1.1
|
||||
Host: 192.168.100.236:8086
|
||||
X-Internal-Token: <内部令牌>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "COMPLETED",
|
||||
"operatorName": "财务-王丽华",
|
||||
"reason": "报账款已付清,整团结算完成"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
团期 T26-5936 结算改为已结算:已逐户提交核单、处于待结算的李秀英户同步为已结算;张建国户逐户核单未提交,被跳过(TEST 18:11 实测,全程报账单行数不变):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223438312652802",
|
||||
"batchStatus": "SETTLED",
|
||||
"batchStatusName": "已结算",
|
||||
"reviewStatus": "COMPLETED",
|
||||
"reviewStatusName": "已核单",
|
||||
"settlementStatus": "COMPLETED",
|
||||
"settlementStatusName": "已结算",
|
||||
"changed": true,
|
||||
"subOrderSyncedCount": 1,
|
||||
"subOrderSkippedOrderIds": ["2105223438178435074"]
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
结算从已结算改回结算中:两户都退回待结算,同时核团从「已结算」退回「已核算」(18:21 实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223438312652802",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusName": "核单中",
|
||||
"reviewStatus": "COMPLETED",
|
||||
"reviewStatusName": "已核单",
|
||||
"settlementStatus": "IN_PROGRESS",
|
||||
"settlementStatusName": "结算中",
|
||||
"changed": true,
|
||||
"subOrderSyncedCount": 2,
|
||||
"subOrderSkippedOrderIds": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口没有列表型空数据。结算在未到已结算的范围内变化(未结算 → 待结算、待结算 → 结算中等)不同步子订单,两个同步字段为 `null`(17:41 实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105223438312652802",
|
||||
"batchStatus": "REVIEWING",
|
||||
"batchStatusName": "核单中",
|
||||
"reviewStatus": "COMPLETED",
|
||||
"reviewStatusName": "已核单",
|
||||
"settlementStatus": "PENDING",
|
||||
"settlementStatusName": "待结算",
|
||||
"changed": true,
|
||||
"subOrderSyncedCount": null,
|
||||
"subOrderSkippedOrderIds": null
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
同值重复回写时 `changed=false`,其余字段为当前值,零写入。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
核单还没完成就改结算(零写入):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589701,
|
||||
"message": "核单尚未完成(当前「待核单」),不可修改结算状态,请先将核单状态改为「已核单」",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
已流团的团期(零写入):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589700,
|
||||
"message": "团期当前状态为「已取消」,不可修改核单或结算状态(须已出行完毕且未流团)",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | 触发条件 | 调用方下一步 |
|
||||
|------|----------|--------------|
|
||||
| HTTP 403 | 缺少或错误的 `X-Internal-Token`(body `{"code":403,"msg":"内部接口禁止外部访问"}`) | 检查令牌配置 |
|
||||
| 400 | 入参校验失败,规则与文案同接口 1 | 修正入参 |
|
||||
| 589500 | 团期不存在 | 核对团期 ID |
|
||||
| 589700 | 团期还没出行完毕或已流团 | 不要调 |
|
||||
| 589701 | 核单状态不是已核单,「」内是当前核单状态中文名 | 先调接口 1 把核单改为已核单 |
|
||||
| 589703 | 团期三个状态被并发改动,零写入 | 重新读取后决定是否重调 |
|
||||
| 589573 | 结算离开已结算、退回核团时核团被并发修改(`整团核单数据已被他人修改…请刷新后重试`),整体回滚 | 重新读取后决定是否重调 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权、阶段门、幂等**:同接口 1。
|
||||
- **前提**:核单状态必须是已核单,否则 589701。
|
||||
- **主状态**:结算为已结算 → `SETTLED`;其余 → `REVIEWING`。
|
||||
- **子订单同步**(同一事务,只改状态,不推报账单,已推的报账单也不撤):
|
||||
|
||||
| 本次结算变化 | 哪些子订单被改 | 子订单核单 / 结算 / 流程状态改成 |
|
||||
|---|---|---|
|
||||
| → 待结算 / 结算中(未离开已结算) | 不同步(子订单没有「结算中」,保持待结算) | — |
|
||||
| → 已结算 | 结算状态为待结算(已逐户提交核单)的户 | 已核单 / 已结算 / 已结算(`COMPLETED` / `COMPLETED` / `SETTLED`),写结算时间 |
|
||||
| 已结算 → 待结算 / 结算中 | 结算状态为已结算的户 | 已核单 / 待结算 / 待结算(`COMPLETED` / `PENDING` / `PENDING_SETTLE`),清结算时间 |
|
||||
|
||||
- **未提交户不拦**:改为已结算时,逐户核单没提交的户**不会**让本接口失败,而是被跳过、列入 `subOrderSkippedOrderIds`,之后一直留在已结算的团里。改为已结算前,请财务确认各户都已逐户提交核单(与管理后台 `/settle` 不同,那个入口遇未提交户整团拒绝 589568)。
|
||||
- **已结算不要求报账单已推出**:由财务确认一团一张的报账单推出后再改为已结算(jw 09-29 定)。
|
||||
- **结算离开已结算**(本接口与管理后台 `/settle/reopen` 同一处理):核团同事务从「已结算」(CHECKED)退回「已核算」(ALLOCATED),清空验团人、验团时间与意见;之后重新核算、再结算、再反结算都可正常使用。
|
||||
- 「结算中」的业务含义由财务定义,接口只存值。
|
||||
- **留痕**:结算真的变化时写一条团期时间线「结算状态变更」;主状态进出已结算时另有一条「结算」主状态流转行;核团被退回时时间线附 `auditReverted=true`(只在时间线里,不在接口响应里)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ✅ 开始核单 | `{ "status": "IN_PROGRESS", "operatorName": "财务-王丽华", "reason": "开始核单" }` |
|
||||
| ✅ 只传状态 | `{ "status": "COMPLETED" }`(operatorName、reason 选填) |
|
||||
| ❌ 想把状态清回初始值 | `{ "status": "NONE" }` → 400(`NONE` 不允许外部写入) |
|
||||
| ❌ 小写或旧值 | `{ "status": "pending" }`、`{ "status": "TRIP_FINISHED" }` → 400 |
|
||||
| ❌ 核单没到已核单就改结算 | 核单为待核单 / 核单中时调接口 2 → 589701 |
|
||||
| ❌ 已结算时退回核单 | 结算为已结算时调接口 1 改为核单中 / 待核单 → 589702 |
|
||||
|
||||
### 正常调用顺序
|
||||
|
||||
```text
|
||||
(1042 自动)核单=待核单
|
||||
→ 接口 1 IN_PROGRESS(核单中)→ 接口 1 COMPLETED(已核单)
|
||||
→ 接口 2 PENDING(待结算)→ 接口 2 IN_PROGRESS(结算中)→ 接口 2 COMPLETED(已结算)
|
||||
```
|
||||
|
||||
### 对接注意事项(jw 09-29 / 09-30 已定,均由财务侧流程控制,系统不加门禁)
|
||||
|
||||
1. **核单可以从待核单直接跳到已核单**:此时子订单停在待核单,之后结算改为已结算的同步不会带上它们(出现在跳过名单里)。
|
||||
2. **接口不带「期望的当前状态」**:由财务保证不重试、不乱序。迟到的重试会把状态改回去,并连带子订单、核团一起回退。
|
||||
3. **团期已核单不代表每户都已核单**:团期改为已核单时不检查各户是否已逐户提交;财务确认各户都已提交后,再把结算改为已结算。
|
||||
4. **结算改为已结算不要求报账单已推出**:可能出现没有报账单的已结算团,由财务把关。
|
||||
5. **团期子订单不要做逐户「财务复核确认」**:逐户确认会推 ORDER 报账单,可能与团级 GROUP_BATCH 报账单重复;系统既不拦截,也不跳过推单。
|
||||
6. **核单变为已核单后不冻结**团级共享成本录入和团级定稿。
|
||||
7. 返回 `subOrderSkippedOrderIds` 非空时,名单里的户状态没有跟上团期,需要财务跟进。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 团期主状态、核单状态、结算状态三者在**同一次原子更新**里写入,条件是三者都等于读到的旧值;未命中返回 589703,本次零写入。
|
||||
- 子订单状态同步与团期改动**同一事务**,任一户写失败整体回滚。每个被改的户写一条订单日志:核单两步记「流程推进」,结算两步记「结算确认」/「核单反确认」,内容写明「团期同步、不推报账单」,日志附带团期 ID、来源 `GROUP_BATCH_REVIEW_SETTLEMENT_SYNC`、步骤、操作人与理由。
|
||||
- 不生成、不撤回任何报账单(TEST 全程 88 次快照报账单行数恒定)。
|
||||
- 结算离开已结算时,核团同事务退回已核算,并清空验团人、验团时间与意见。
|
||||
- 团期时间线写入失败只记告警、不回滚(非主链路);子订单订单日志不降级。
|
||||
- 幂等命中(`changed=false`)与所有错误返回:零写入。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 缺少或错误的内部令牌 → HTTP 403 `{"code":403,"msg":"内部接口禁止外部访问"}`,两个实例表现一致。
|
||||
- 经公网网关访问 `/v3/internal/**` → 网关直接拒绝(`code 403`「接口不可访问」),请求到不了服务。
|
||||
- 入参非法 → HTTP 200 + code 400,零写入。
|
||||
- 团期不存在 → 589500;未出行完毕或已流团 → 589700。
|
||||
- 两次调用并发打到同一团期:先拿到团期行锁的先执行,后到者读到的是先到者已提交的值,同值时走幂等分支;兜底冲突返回 589703。
|
||||
- 在团子订单为空(团内户全部取消)时同步不报错,`subOrderSyncedCount=0`、`subOrderSkippedOrderIds=[]`。
|
||||
- 团期「已核单」、从「已核单」退回:永远不同步子订单(`subOrderSyncedCount` / `subOrderSkippedOrderIds` 为 `null`)。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### reviewStatus(`com.hulalv.order.settlement.enums.ReviewStatus`,与子订单核单状态同一枚举)
|
||||
|
||||
**所属字段**: `GroupBatchReviewSettleStatusRespVO.reviewStatus` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `NONE` | 未核单 | 初始值,还没出行完毕;外部不可写入 |
|
||||
| `PENDING` | 待核单 | 出行完毕(1042 自动写入)或财务退回 |
|
||||
| `IN_PROGRESS` | 核单中 | 财务开始核单;管理后台「发起核单」、首笔共享成本也会写入 |
|
||||
| `COMPLETED` | 已核单 | 财务完成核单;管理后台 `/settle` 也会写入 |
|
||||
|
||||
### settlementStatus(`com.hulalv.order.groupbatch.enums.GroupBatchSettlementStatus`)
|
||||
|
||||
**所属字段**: `GroupBatchReviewSettleStatusRespVO.settlementStatus` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `NONE` | 未结算 | 核单完成之前恒为此值;外部不可写入;核单从已核单退回时自动置回 |
|
||||
| `PENDING` | 待结算 | 核单已完成、尚未开始结算。注意同一个值在子订单上叫「待财务复核」 |
|
||||
| `IN_PROGRESS` | 结算中 | 团期独有,子订单没有这一态;含义由财务定义 |
|
||||
| `COMPLETED` | 已结算 | 团期主状态随之变为已结算 |
|
||||
|
||||
### batchStatus 推导表(`com.hulalv.order.groupbatch.enums.GroupBatchStatus`)
|
||||
|
||||
**所属字段**: `GroupBatchReviewSettleStatusRespVO.batchStatus` | **类型**: `String`
|
||||
|
||||
| 核单 | 结算 | → 主状态 |
|
||||
|------|------|----------|
|
||||
| `NONE` | `NONE` | 不适用(还没出行完毕,接口按 589700 拒绝,不改主状态) |
|
||||
| `PENDING` | `NONE` | `PENDING_REVIEW` 待核单 |
|
||||
| `IN_PROGRESS` | `NONE` | `REVIEWING` 核单中 |
|
||||
| `COMPLETED` | `NONE` / `PENDING` / `IN_PROGRESS` | `REVIEWING` 核单中 |
|
||||
| `COMPLETED` | `COMPLETED` | `SETTLED` 已结算 |
|
||||
|
||||
`PENDING_REVIEW`「待核单」即原 `TRIP_FINISHED`「出行完毕」,#8516 改名,存量数据已随迁移改写。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:新增的两个内部接口;团期核单 / 结算状态的写入统一收进同一个写口。
|
||||
- **零影响**:
|
||||
- 团级核单链路(核单定稿 finalize / 确认 confirm)与回款监听不写这两个状态,口径不变;
|
||||
- 逐户确认 `/{orderId}/settlement/confirm`、逐户反确认 `/{orderId}/settlement/final-snapshots/reopen` 对团期子订单不加限制;
|
||||
- 子订单第一次录核单明细自动进入核单中的逐户逻辑保留;
|
||||
- 出行完毕定时任务的内部触发接口 `POST /v3/internal/jobs/group-batch-trip-finish/run`:请求、响应(本次推进的团期数)不变;推进时顺带把团期核单置为待核单,出行中的子订单随团进入待核单(#8340 既有逻辑),当时不在出行中的户跳过并打告警,团期照常推进。
|
||||
- 管理后台读接口(新增四个字段、`TRIP_FINISHED` 改名、进度条分支、入参旧值兼容)与既有写入口的行为变化,见同日修改接口那份。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-30 TEST:合并提交 `54e64c50f`,部署构建 dev-v3 `dd0452916`(order-v3 双实例 8086 / 8186,16:32 启动,运行字节经探针核对),迁移 `20260929.8516` 于 16:32:13 执行成功。验收团期为本次新建(T26-5936 呼伦贝尔草原3日游·9月26日团、T26-2325 呼伦贝尔草原3日游·9月25日团、T26-8714 流团用),内部接口用 `X-Internal-Token` 直连两个实例。证据目录 `HL/.evidence/8516/`。
|
||||
|
||||
```text
|
||||
AC-04 POST /v3/internal/jobs/group-batch-trip-finish/run → data=2;两团 TRAVELLING→PENDING_REVIEW、核单=待核单;出行中的户同事务进待核单,定制中的户跳过并告警 ✓
|
||||
AC-05 两接口 × 两实例:无令牌 / 伪造令牌 → HTTP 403;status 缺失/空串/空白/null/NONE/DONE/小写/TRIP_FINISHED → code 400,前后零写入 ✓
|
||||
AC-06 推导表七行逐组合实测:NONE/NONE 不适用(589700)、PENDING/NONE→PENDING_REVIEW、IN_PROGRESS/NONE 与 COMPLETED/NONE/PENDING/IN_PROGRESS→REVIEWING、COMPLETED/COMPLETED→SETTLED ✓
|
||||
AC-07 同值幂等 changed=false 零写入;589700(招募中/出行中/已流团)、589701、589702 各自触发;589500 团期不存在;核单退回时结算自动置回 NONE;结算离开已结算两条路径核团 CHECKED→ALLOCATED,之后重新核算、再结算、再反结算均成功 ✓
|
||||
AC-08 子订单同步逐行正反向实测:跳过户进 subOrderSkippedOrderIds(无同步 null、有同步无跳过 []);已核单退回核单中 / 待核单时子订单未被带动;全程 88 次快照报账单行数不变 ✓
|
||||
AC-12 时间线可见「核单状态变更」「结算状态变更」,operatorName=财务-王丽华,reason 为调用方传入原因 ✓
|
||||
```
|
||||
|
||||
| AC | 证据 |
|
||||
|----|------|
|
||||
| AC-04 | `HL/.evidence/8516/AC-04/` |
|
||||
| AC-05 | `HL/.evidence/8516/AC-05/01-auth-and-validation.json`、`02-controls-vehicle-ready-and-gateway.json` |
|
||||
| AC-06 | `HL/.evidence/8516/AC-06/` |
|
||||
| AC-07 | `HL/.evidence/8516/AC-07/` |
|
||||
| AC-08 | `HL/.evidence/8516/AC-08/` |
|
||||
| AC-12 | `HL/.evidence/8516/AC-12/01-status-logs.json` |
|
||||
|
||||
单元测试:推导全组合 `GroupBatchReviewSettleDeriverTest`、写入规则 `GroupBatchReviewSettleServiceTest`、内部接口切片与鉴权 `GroupBatchReviewSettleInternalControllerTest` / `GroupBatchReviewSettleInternalAuthTest`、子订单同步正反向与报账单行数守卫 `GroupBatchReviewSettleH2IT`;order-v3 全量两半对照干净 dev-v3 基线,本单新增失败 0。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #7190 | 团期新增出行完毕 `TRIP_FINISHED` | ⚠️ 状态值已改名 `PENDING_REVIEW` 待核单 |
|
||||
| — | #8340 | 团期出发 / 出行完毕时子订单随团推进 | ✅ 有效(出行完毕时另写核单=待核单) |
|
||||
| — | #8341 | 团期核单 / 结算 / 反结算同步子订单,团期结算即整团财务复核并逐户推报账单 | ⚠️ 部分被本单取代:同步改按本单第 6 节,结算不再逐户推报账单 |
|
||||
| — | #8361 / #8363 | 报账只认一团一张、成本只认团级快照(09-28 口径) | ✅ 有效,本单据此停掉逐户推单 |
|
||||
| **#8650** | **#8516** | 团期核单 / 结算独立状态、财务内部回写接口 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8516](https://git.1814.love/wx/HL/issues/8516)
|
||||
- 关联 PR: [wx/HL#8650](https://git.1814.love/wx/HL/pulls/8650)
|
||||
- 状态机文档:`docs/group/实施单/16-团期生命周期与状态机.html`(随 PR #8650 同步,提交 `30ed15777`);接口文档 `docs/group/团期模块接口文档-v2.0.html`
|
||||
- 同日管理后台读侧:`changelogs-v2/2026-09/30_8516_团期新增核单结算状态字段且出行完毕改名待核单-修改接口-管理后台.md`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8516](https://git.1814.love/wx/HL/issues/8516)
|
||||
- **PR**: [#8650](https://git.1814.love/wx/HL/pulls/8650)
|
||||
- **Merge commit**: [54e64c50f](https://git.1814.love/wx/HL/commit/54e64c50f)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @jw
|
||||
@@ -0,0 +1,329 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8543"
|
||||
title: "团期订单 Tab 用车状态三处读口按需求类别拆分并统一文案"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "bcd073be952146212d268f2e31f6d82bcb82cab2"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8592 合并 dev-v3(bc80606ac2);hl-order-service-v3 dev-v3 分支部署测试网关 @ 99fb369ba 并实测:配车芯片明细端点 items[] 按用车类别拆项、同一 orderId 出现两次(TRAVEL/TRANSFER 各一);totalCount/doneCount 由户数变为条目数(实测 5 项 3 完成,对应 3 户);子订单列表新增 vehicleRequirementKind 字段恒为 TRAVEL;文案分支待车队配/配车完成/待提交车务/待审核均已实测复现;团期列表页 chipStats.vehicle 与明细端点同步联动同一计数口径。;前端已交付:配车芯片明细按 orderId+kind 复合键渲染+类别标签直显 kindName,汇总口径分叉(vehicle 条目数称条/其余芯片户数),子订单列表文案分叉直显后端 statusName 自动生效,10 例定向测试全绿(hl-admin bcd073be)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3: 团期订单 Tab 用车状态三处读口按需求类别拆分并统一文案
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8592
|
||||
> **Issue**: #8543
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期配车芯片明细端点 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/vehicle`、团期下子订单列表端点 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`;团期列表/看板端点的 `chipStats.vehicle` 计数口径联动变化(无字段新增,纯语义联动)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **破坏性变更**:`GET .../chips/vehicle` 的 `items[]` 从**一户一项**改为**一户每类一项**——同一户同时有"行程用车"(TRAVEL)与"接送机"(TRANSFER)两类需求时,`items[]` 里会出现两条 `orderId` 相同、`kind` 不同的记录。**前端不得再按 `orderId` 去重**,去重会随机丢掉其中一类需求的状态。
|
||||
- `totalCount` / `doneCount` 的口径随之从"户数"变为"条目数":一户两类需求算两条,分母跟着变大。实测团期批次 T26-3963 为例:3 户、5 条需求行,`totalCount=5`(不是 3),`doneCount=3`。
|
||||
- `GroupBatchChipItemRespVO`(仅配车芯片会用到)新增 `kind`(TRAVEL/TRANSFER,可空)与 `kindName`(配对中文名,可空)两个字段;房/导/摄/约/保五个芯片的 `items[]` 里这两个字段恒为 `null`。
|
||||
- `GroupBatchOrderItemRespVO`(子订单列表 `.../orders` 出参)新增 `vehicleRequirementKind` 与 `vehicleRequirementKindName` 两个字段——是新增字段不是破坏性变更。这一列**当前恒为 TRAVEL**:子订单列表每户只占一行,装不下两类需求,上游按 TRAVEL 过滤取行;要看接送机需求要走配车芯片明细(一户两类各占一项)。
|
||||
- 用车状态文案本次统一(此前配车芯片明细端点的映射与子订单列表端点各写各的,现在两处一致):`PENDING`→**待车队配**、`PENDING_REVIEW` 按 `kind` 分叉(TRAVEL 出**待提交车务**、TRANSFER 出**待审核**)、`REJECTED_TO_CONSULTANT`→**已驳回定制师**、`REJECTED_TO_ADMIN`→**已驳回管理员**、`DONE`→**配车完成**。
|
||||
- 该户 `needsIt=true` 但尚无任何 active 车需求行时(已声明要用车、但一行都没提交),配车芯片明细该户仍占一项,`kind`/`status` 为 `null`、`statusName` 为**未提交**(源码逻辑与 PR 说明已确认,本轮实测样本未覆盖到该分支,样本团期均已提交需求行)。
|
||||
- 团期列表页(`GET /v3/admin/order/group-batch`)与看板端点每行的 `chipStats.vehicle.total`/`chipStats.vehicle.done` 与本次改动**共用同一套聚合算法**(`GroupBatchChipResolver.aggregateAll`),因此同步从"户数"变为"条目数"——实测同一团期批次两处读数逐位一致(5/3 与 3/1)。这两个端点本身**没有新增字段**,只是既有的 `total`/`done` 计数口径联动变了,前端若在列表页展示"配车 X/Y"这类徽标也要同步理解口径变化。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期配车芯片明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/vehicle` | 🔴 破坏性变更 + 字段新增 | `items[]` 按用车类别拆项,同一 `orderId` 可出现两次;新增 `kind`/`kindName`;`totalCount`/`doneCount` 语义由户数变为条目数 |
|
||||
| 2 | 团期下子订单列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | 字段新增 | 新增 `vehicleRequirementKind`/`vehicleRequirementKindName`(当前恒为 TRAVEL);`vehicleRequirementStatusName` 的 `PENDING_REVIEW` 文案按 `kind` 分叉 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期配车芯片明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/vehicle`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务/团期管理员在团期订单 Tab 展开"配车"芯片查看逐户用车状态明细时调用。六个芯片(房/车/导/摄/约/保)共用同一套 `GroupBatchChipDetailVO` 响应结构与六个并列端点,本条目专指配车芯片(其余五芯片本次未受影响,`kind`/`kindName` 在那五芯片下恒为 `null`)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期批次 ID |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/变化的字段;`GroupBatchChipDetailVO` 顶层其余字段(`chipLabel`/`aggregateStatus`/`aggregateStatusName`/`staffList`)与 `items[]` 内未变化的字段(`orderId`/`orderNo`/`teamNo`/`customerName`/`peopleCount`/`needsIt`/`claimerId`/`claimerName`/`claimerSource`/`updateTime`/`staffs`)结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| totalCount | Integer | 🔴 语义变化:改前是"计入统计的户数",改后是"条目数"(一户两类需求算两条) |
|
||||
| doneCount | Integer | 🔴 语义变化:口径随 `totalCount` 同步改为条目数 |
|
||||
| items[] | List | 🔴 数组长度语义变化:一户两类需求时占两个元素,`orderId` 相同 |
|
||||
| items[].kind | String,可空 | 新增:用车类别(`TRAVEL` 行程用车 / `TRANSFER` 接送机);该户尚无任何 active 车需求行时为 `null`(此时该户仍占一项,`status` 为 `null`,`statusName` 为"未提交");房/导/摄/约/保五芯片恒为 `null` |
|
||||
| items[].kindName | String,可空 | 新增:`kind` 配对中文名(行程用车/接送机);`kind` 为 `null` 或枚举外未知码时为 `null`——编码不回落当中文展示 |
|
||||
| items[].statusName | String,可空 | 文案统一:`PENDING`→待车队配、`PENDING_REVIEW` 按 `kind` 分叉(TRAVEL→待提交车务,TRANSFER→待审核)、`REJECTED_TO_CONSULTANT`→已驳回定制师、`REJECTED_TO_ADMIN`→已驳回管理员、`DONE`→配车完成 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/chips/vehicle
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(团期批次 T26-3963,3 户 5 项,含同一 `orderId` 两类需求各占一项):
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"batchId":"2104839654727618562","groupBatchId":"2104839654727618562","chipLabel":"配车","aggregateStatus":"DOING","aggregateStatusName":"进行中","totalCount":5,"doneCount":3,"staffList":null,"items":[{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","contactName":"李文博","customerName":"李文博","peopleCount":2,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","contactName":"李文博","customerName":"李文博","peopleCount":2,"status":"PENDING","statusText":"待车队配","statusName":"待车队配","needsIt":true,"kind":"TRANSFER","kindName":"接送机","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839686486888449","orderNo":"HL20260929154421828","teamNo":"26-9436","contactName":"张丽娟","customerName":"张丽娟","peopleCount":2,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839729176514562","orderNo":"HL20260929154432061","teamNo":"26-6559","contactName":"那顺","customerName":"那顺","peopleCount":3,"status":"DONE","statusText":"配车完成","statusName":"配车完成","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null},{"orderId":"2104839729176514562","orderNo":"HL20260929154432061","teamNo":"26-6559","contactName":"那顺","customerName":"那顺","peopleCount":3,"status":"PENDING_REVIEW","statusText":"待审核","statusName":"待审核","needsIt":true,"kind":"TRANSFER","kindName":"接送机","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null}]},"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
另一团期批次(T26-6001,含"待提交车务"文案分支,TRAVEL 类 `PENDING_REVIEW`)实测节选:
|
||||
|
||||
```json
|
||||
{"orderId":"2104840685708525570","orderNo":"HL20260929154820126","teamNo":"26-0805","contactName":"谢丽萍","customerName":"谢丽萍","peopleCount":2,"status":"PENDING_REVIEW","statusText":"待提交车务","statusName":"待提交车务","needsIt":true,"kind":"TRAVEL","kindName":"行程用车","updateTime":null,"claimerId":null,"claimerName":null,"claimerSource":null,"staffs":null}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期批次不存在:返回业务错误(见"错误响应"),`data` 为 `null`。
|
||||
- 团期下暂无子订单:`items` 为空数组,`totalCount`/`doneCount` 均为 `0`。
|
||||
- 户已声明要用车(`needs_vehicle=true`)但尚未提交任何车需求行:该户仍占一项,`kind`/`status` 为 `null`,`statusName` 为"未提交"(源码逻辑已确认,本轮实测样本未覆盖到该分支)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `items[]` 按 `orderId` 去重是错误用法——同一户两类需求会被拆成两个数组元素,去重会随机丢掉其中一类的状态展示。
|
||||
- `totalCount`/`doneCount` 不再等于该团期的户数,若页面上另有独立的"户数"展示(如团期基础信息),不要复用这两个字段去推导户数。
|
||||
- `kind`/`kindName` 只在配车芯片有意义,其余五芯片(房/导/摄/约/保)该字段恒为 `null`,前端渲染这五个芯片时不需要处理 `kind` 分支。
|
||||
- `statusName` 的四个文案改动是全量替换(不是新增枚举值),旧文案(待车务配/驳回给定制师/驳回给管理员)不会再出现,前端若按旧文案字符串做过特殊判断需要同步更新。
|
||||
|
||||
### 2. 团期下子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
|
||||
|
||||
**VO**: `(无请求体,Path + Query 参数)` → `PageResult<GroupBatchOrderItemRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期订单 Tab 展示子订单摘要列表(客户姓名、各状态列、支付/结算信息)时调用,是该 Tab 的主表格数据源。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期 ID |
|
||||
| page | Query | Integer | ❌ | 缺省 1,<1 归一为 1 | 页码,从 1 起 |
|
||||
| pageSize | Query | Integer | ❌ | 缺省 20,<1 归一为 20,>200 截断为 200 | 每页条数 |
|
||||
| includeTravelers | Query | Boolean | ❌ | 缺省 true | 是否附出行人明细(证件号/手机号一律不返回),本次未变 |
|
||||
| includeNeeds | Query | Boolean | ❌ | 缺省 true | 是否附房数/房型/特殊需求,本次未变 |
|
||||
| includeCancelled | Query | Boolean | ❌ | 缺省 false | 是否含已取消子订单,本次未变 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下只列本次新增/变化的字段;`GroupBatchOrderItemRespVO` 其余既有字段(`orderId`/`customerName`/各状态码与状态中文名/金额字段/`travelers` 等)结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| vehicleRequirementKind | String,可空 | 新增:本行车需求的用车类别(`TRAVEL`/`TRANSFER`)。**当前恒为 `TRAVEL`**——子订单列表每户只占一行,装不下两类,上游按 TRAVEL 过滤取行;接送机需求要看配车芯片明细(`.../chips/vehicle`,一户两类各占一项)。无 active 车需求行时为 `null`,与 `vehicleRequirementStatus` 同生同灭 |
|
||||
| vehicleRequirementKindName | String,可空 | 新增:`vehicleRequirementKind` 配对中文名(行程用车/接送机);枚举外未知码不回落编码,直接给 `null` |
|
||||
| vehicleRequirementStatusName | String,可空 | 既有字段,文案口径调整:`PENDING_REVIEW` 的措辞随同行的 `vehicleRequirementKind` 分叉——TRAVEL 下发"待提交车务"(该状态上没有逐户审核动作,推走它的是团期管理员整团一次的"提交车务"),TRANSFER 下发"待审核";由于本字段随行的 `vehicleRequirementKind` 当前恒为 TRAVEL,本端点实际只会出现"待提交车务"这一支 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(团期批次 T26-3963,节选第 1 条完整记录):
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"records":[{"orderId":"2104839654652121090","orderNo":"HL20260929154414207","teamNo":"26-2313","customerName":"李文博","participantCount":2,"orderStatus":"CUSTOMIZING","orderStatusName":"定制中","flowStatus":"RESOURCE_PREPARING","flowStatusName":"资源准备","reviewStatus":null,"reviewStatusName":null,"settlementStatus":"NONE","settlementStatusName":"未结算","payStatus":"FULLY_PAID","payStatusName":"已付全款","contractStatus":null,"contractStatusName":null,"insuranceStatus":null,"insuranceStatusName":null,"paidAmount":"7360.00","balanceAmount":"0.00","hotelRequirementStatus":null,"hotelRequirementStatusName":null,"vehicleRequirementStatus":"DONE","vehicleRequirementStatusName":"配车完成","vehicleRequirementKind":"TRAVEL","vehicleRequirementKindName":"行程用车","consultantName":"cw_test_7443","totalPrice":"7360.00","tierCode":"2A","tierName":"2成人","travelerInfoComplete":true,"roomCount":1,"roomType":null,"roomTypeName":null,"specialNeeds":"夫妻同行,携带摄影器材较多,需预留后备箱空间","contactPhone":"138****3046","groupChatUnreadCount":0,"travelers":[{"name":"李文博","type":"ADULT","age":46,"birthdayInTrip":false},{"name":"赵梦琪","type":"ADULT","age":43,"birthdayInTrip":false}]}],"total":3,"page":1,"pageSize":20},"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期批次不存在:返回业务错误(见"错误响应")。
|
||||
- 团期下暂无子订单(或 `includeCancelled=false` 时全部已取消):`records` 为空数组,`total=0`。
|
||||
- 该户无 active 车需求行:`vehicleRequirementStatus`/`vehicleRequirementKind` 均为 `null`;`vehicleRequirementStatusName`/`vehicleRequirementKindName` 按该户 `needsVehicle` 分叉——`true` 出"未提交",否则为 `null`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `vehicleRequirementKind` 在本端点当前恒为 `TRAVEL`,不能据此推断"该团期没有接送机需求"——接送机需求存在与否要看配车芯片明细端点。
|
||||
- `vehicleRequirementStatusName` 的 `PENDING_REVIEW` 分支文案取决于 `vehicleRequirementKind`,但本端点该列恒为 TRAVEL,因此实际只会看到"待提交车务",不会看到"待审核"(后者只出现在配车芯片明细的 TRANSFER 项上)。
|
||||
- 无 active 车需求行时 `vehicleRequirementStatus` 为 `null` 不回落 `PENDING`(#8249 起既有行为,本次未变)——`PENDING` 是需求行的真实状态之一,没有行时借用它会与"未提交"矛盾。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝的规则与语义边界,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 响应 |
|
||||
|------|-----------------|
|
||||
| ✅ 渲染配车芯片明细列表 | 按数组下标或 `orderId`+`kind` 复合键渲染每一项,允许同一 `orderId` 出现多行 |
|
||||
| ✅ 展示配车芯片"已完成 N/M" | 直接用 `doneCount`/`totalCount`,两者已经是同一口径(条目数),无需自行按户去重再算 |
|
||||
| ❌ 用 `items[].orderId` 做 `Map` 的 key 或做 `Set` 去重 | 一户两类需求时后写入的会覆盖/顶掉先写入的那一条,界面上会静默丢失一类需求的状态 |
|
||||
| ❌ 用子订单列表的 `vehicleRequirementKind` 判断该团期是否存在接送机需求 | 该列当前恒为 TRAVEL,对接送机需求零分辨力;接送机需求判断要调配车芯片明细 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端如果此前在配车芯片渲染层用 `orderId` 做过 `key`/去重/索引,本次上线前必须改为 `orderId + kind` 复合键;否则界面在两类需求并存的户上会稳定丢失一类需求的展示,且是静默丢失(不报错)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
两个端点均为只读查询,无数据库写操作。配车芯片明细与子订单列表内部均从 `order_vehicle_requirement`(车需求行表)按 `order_id` 聚合取最新一版需求行,`kind` 直接取自需求行的 `requirement_kind` 列,不做兜底改写;历史行 `requirement_kind` 为 `NULL` 时 `kind`/`kindName` 原样透出 `null`,不会被兜底成 `TRAVEL`。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 户尚未提交任何车需求行、但已声明要用车(`needs_vehicle=true`)→ 配车芯片该户仍占一项,`kind`/`status` 为 `null`,`statusName`="未提交"
|
||||
- 户完全不需要用车(`needs_vehicle=false`)→ 该户在配车芯片 `items[]` 中不出现
|
||||
- 户同时有 TRAVEL 与 TRANSFER 两类 active 需求行 → 配车芯片占两项,`orderId` 相同、`kind` 不同
|
||||
- 户只有一类需求行 → 配车芯片只占一项,不会补一个空的另一类占位项
|
||||
- 需求行历史 `requirement_kind` 为 `NULL` → `kind`/`kindName` 原样为 `null`,不兜底为 `TRAVEL`
|
||||
- 子订单列表 `.../orders` 每户恒只出一行、按 TRAVEL 过滤取需求行,不受该户是否有 TRANSFER 需求影响
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 用车类别(`VehicleRequirementKind`,`items[].kind` / `vehicleRequirementKind`)
|
||||
|
||||
**所属字段**: `GroupBatchChipItemRespVO.kind`、`GroupBatchOrderItemRespVO.vehicleRequirementKind` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 本次是否新增 | 说明 |
|
||||
|----|------|------|------|
|
||||
| `TRAVEL` | 行程用车 | 既有枚举值,本次新增到这两个字段 | 团期行程内的用车安排 |
|
||||
| `TRANSFER` | 接送机 | 既有枚举值,本次新增到这两个字段 | 接送机场/车站,独立于行程用车 |
|
||||
| `null` | (无展示) | - | 该户尚无 active 车需求行时的状态,不是第三个枚举值 |
|
||||
|
||||
### 车需求状态中文名(`statusName` / `vehicleRequirementStatusName`,`PENDING_REVIEW` 按 `kind` 分叉)
|
||||
|
||||
**所属字段**: `items[].statusName`(配车芯片)、`vehicleRequirementStatusName`(子订单列表) | **类型**: `String`
|
||||
|
||||
| 状态码 | 中文(本次前) | 中文(本次后) | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `PENDING` | 待车务配(仅配车芯片明细,子订单列表原已是"待车队配") | 待车队配 | 两处读口统一为同一文案 |
|
||||
| `PROCESSING` | 配车中 | 配车中 | 未变 |
|
||||
| `DONE` | 配车完成 | 配车完成 | 未变 |
|
||||
| `PENDING_REVIEW`(TRAVEL) | 待审核(配车芯片明细此前不分类别) | 待提交车务 | 按 `kind` 新分叉 |
|
||||
| `PENDING_REVIEW`(TRANSFER) | 待审核 | 待审核 | 未变(新分叉后仍是这个文案) |
|
||||
| `REJECTED_TO_CONSULTANT` | 驳回给定制师(仅配车芯片明细) | 已驳回定制师 | 两处读口统一为同一文案 |
|
||||
| `REJECTED_TO_ADMIN` | 驳回给管理员(仅配车芯片明细) | 已驳回管理员 | 两处读口统一为同一文案 |
|
||||
| `null`(有需求但未提交) | 待审核(配车芯片明细此前误落到这一支) | 未提交 | 修正误报——"未提交"与"已提交等审核"是两个不同状态,此前混在一起 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `GroupBatchChipItemRespVO.kind` | 不存在 | 新增,`String`,可空,仅配车芯片有值,其余五芯片恒 `null` |
|
||||
| `GroupBatchChipItemRespVO.kindName` | 不存在 | 新增,`String`,可空 |
|
||||
| `GroupBatchChipDetailVO.totalCount`(配车芯片) | 户数 | 条目数(一户两类算两条) |
|
||||
| `GroupBatchChipDetailVO.doneCount`(配车芯片) | 已完成户数 | 已完成条目数 |
|
||||
| `GroupBatchChipDetailVO.items[]`(配车芯片) | 一户一项 | 一户每类一项,`orderId` 可重复 |
|
||||
| `GroupBatchOrderItemRespVO.vehicleRequirementKind` | 不存在 | 新增,`String`,可空,当前恒为 `TRAVEL` |
|
||||
| `GroupBatchOrderItemRespVO.vehicleRequirementKindName` | 不存在 | 新增,`String`,可空 |
|
||||
| 团期列表/看板 `chipStats.vehicle.total`/`.done` | 户数 | 条目数(共用配车芯片同一聚合算法,联动变化,字段本身未新增) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 户同时有 TRAVEL + TRANSFER 需求 | 配车芯片只显示其中一类,另一类无声消失 | 两类各占一项,均可见 |
|
||||
| 户已声明用车但未提交需求行 | 落到"待审核",看起来像已提交 | 落到"未提交",与已提交待审核区分开 |
|
||||
| `PENDING_REVIEW` 状态文案 | 配车芯片明细恒显示"待审核";子订单列表已按 kind 分叉(源于 #8218) | 配车芯片明细与子订单列表口径统一,均按 kind 分叉 |
|
||||
| 配车芯片 `PENDING`/`REJECTED_TO_CONSULTANT`/`REJECTED_TO_ADMIN` 文案 | 待车务配/驳回给定制师/驳回给管理员 | 待车队配/已驳回定制师/已驳回管理员 |
|
||||
| 配车芯片 `totalCount`/`doneCount` 与列表页 `chipStats.vehicle` 关系 | 两处各自独立计算,可能不一致 | 共用同一聚合算法,逐位一致 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是——`.../chips/vehicle` 的 `items[]` 数组长度与 `orderId` 唯一性假设改变,任何按 `orderId` 做 key/去重/索引的前端代码都会在两类需求并存的户上产生数据丢失;`totalCount`/`doneCount` 数值口径也变了,若前端拿它们除以户数算百分比会得到错误结果。
|
||||
- **前端是否必须同步上线**: 是(针对配车芯片展示场景)——只要页面渲染配车芯片明细,就必须按 `orderId+kind` 复合键处理 `items[]`;子订单列表的两个新字段是纯新增,不改也不会报错,但不展示就拿不到用车类别信息。
|
||||
- **前端 workaround 清理点**: 若此前为"同一户两类需求显示不全/互相覆盖"这类现象写过特殊兼容或只取第一条的逻辑,现在后端已按类别拆项,可以确认不再需要。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 配车芯片明细端点 `GET .../chips/vehicle` 的 `items[]` 结构与 `totalCount`/`doneCount` 语义;子订单列表端点 `GET .../orders` 新增两个字段;团期列表/看板端点 `chipStats.vehicle` 的计数口径(字段本身未变)。
|
||||
- **零影响**:
|
||||
- 房/导/摄/约/保五个芯片明细端点(`GET .../chips/{hotel|guide|photo|contract|insurance}`)的字段结构与计数口径
|
||||
- 团期下子订单列表其余既有字段(金额、支付、结算、房需求等)
|
||||
- 户级用车需求明细端点 `GET .../requirement/vehicle-households`(本次为纯内部代码去重,零响应契约变化,见下方"八、测试环境已验证"说明)
|
||||
- 配车需求本身的写口(提交/确认/驳回等)不受本次改动影响
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-order-service-v3`,dev-v3 分支部署测试网关 @ `99fb369ba`(含 #8543 所在提交 `bc80606ac2`),测试网关 `https://api.test.1814.love`;样本均为测试服现存真实业务数据:
|
||||
|
||||
```
|
||||
✓ 团期批次 T26-3963(groupBatchId=2104839654727618562,3 户):
|
||||
GET .../chips/vehicle → totalCount=5 doneCount=3
|
||||
李文博(orderId=2104839654652121090):TRAVEL/DONE/配车完成 + TRANSFER/PENDING/待车队配(同一 orderId 两项)
|
||||
那顺(orderId=2104839729176514562):TRAVEL/DONE/配车完成 + TRANSFER/PENDING_REVIEW/待审核(同一 orderId 两项)
|
||||
张丽娟(orderId=2104839686486888449):仅 TRAVEL/DONE/配车完成(一项)
|
||||
GET .../orders → 3 条记录,vehicleRequirementKind 均为 "TRAVEL"(与源码"本列当前恒为 TRAVEL"一致)
|
||||
✓ 团期批次 T26-6001(groupBatchId=2104840641651556353,3 户):
|
||||
GET .../chips/vehicle → totalCount=3 doneCount=1
|
||||
董海涛:TRAVEL/DONE/配车完成 + TRANSFER/PENDING/待车队配
|
||||
谢丽萍:TRAVEL/PENDING_REVIEW/待提交车务(复现"待提交车务"文案分支)
|
||||
✓ 团期列表页 GET /v3/admin/order/group-batch 逐页扫描,同两个 groupBatchId 的 chipStats.vehicle:
|
||||
{total:5, done:3} 与 {total:3, done:1},与明细端点 totalCount/doneCount 逐位一致(重复请求 5 次读数稳定)
|
||||
✓ 错误响应:不存在的 groupBatchId → 两端点均返回 {"code":589500,"message":"团期不存在"}
|
||||
```
|
||||
|
||||
注:`kind=null`(户已声明用车但未提交需求行,`statusName`="未提交")与配车芯片明细的 `REJECTED_TO_CONSULTANT`/`REJECTED_TO_ADMIN` 两个文案分支,本轮实测样本团期未覆盖到(样本户均已提交需求行且未被驳回);这三个分支的契约已在源码逐一核实(`GroupBatchChipResolver.vehicleItemsOf`、`GroupBatchConverter.resolveRequirementStatusName`),前端应按此契约实现,不依赖本轮是否观测到该取值。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8543](https://git.1814.love/wx/HL/issues/8543)
|
||||
- 关联 PR: [wx/HL#8592](https://git.1814.love/wx/HL/pulls/8592)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8543](https://git.1814.love/wx/HL/issues/8543)
|
||||
- **PR**: [#8592](https://git.1814.love/wx/HL/pulls/8592)
|
||||
- **Merge commit**: [bc80606ac2](https://git.1814.love/wx/HL/commit/bc80606ac2)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,664 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8544"
|
||||
title: "团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行(#8544 #8545 #8619)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "bc9c13aaa39bab96e132a3a42515c821fa74a269"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "PR #8625(覆盖 #8544 #8545)与 PR #8624(覆盖 #8619)均已 squash 合并 dev-v3(5f2f86bc9 / ce7cd238e9),hl-order-service-v3 dev-v3 分支已滚测试服(HEAD 5f2f86bc9,jar mtime 2026-09-30 08:19:48,两实例 Nacos 健康)。四个 GET 端点响应体的新增/变更字段均实测通过;PUT / withdraw / waive 三个写端点与 GET 共用同一个 GroupVehicleRequirementRespVO 类(源码级共用,非各自派生),因此 #8544 新增的 4 个 *Name 字段在这三个端点的响应里同样存在,字段语义与本文档「三、接口详情」第 2 条完全一致。;前端已交付:正式用车需求三处(Section/汇总卡/状态条)删本地 code→中文 map 改读后端 statusName/planRefreshStalledReasonName(原码仅防空白兜底),aggregate-draft 灌表单用 vehicleType key 零改动,62 例定向测试全绿(hl-admin 8926ebff);续:#8545 座位两口径并列行(已报 vs 已定,集合外车型 totalSeats=0 行显逐户未报)与 #8619 逐类清单段也已交付(hl-admin 850abddc/bc9c13aa,frontend_ref 更新为最终哈希)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3: 团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8625(#8544 #8545)、#8624(#8619)
|
||||
> **Issue**: #8544、#8545、#8619
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期需求管理 Tab 下 4 个 GET 端点(全团需求汇总 / 读正式用车需求 / 自动汇总草稿 / 子订单列表)的响应体字段;PUT / withdraw / waive 三个写端点共用的响应体同步补齐字段(契约见「四」,未单开详情块)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **#8544**:`GroupVehicleRequirementRespVO`(PUT / GET / withdraw / waive 四端点共用的响应体)新增 4 个只读中文名字段——`statusName` / `planRefreshStateName` / `blockedStageName` / `planRefreshStalledReasonName`,分别配对既有的 `status` / `planRefreshState` / `blockedStage` / `planRefreshStalledReason`。**认不出的编码一律给 `null`,不回落成编码原文**(与本 VO 既有的 `SpecialTagItem.name` / `GroupItem.vehicleTypeName` 同口径)。
|
||||
- 🔴 **这条 null 规则与看板列表的团期状态中文名口径是两套、刻意不同**:`GroupBatchConverter#resolveBatchStatusName`(团期列表页用)未知码会回落原编码;本次这 4 个字段所在的正式用车需求读口未知码给 `null`。前端不要把两处的「未知码兜底逻辑」当成同一套抄。
|
||||
- **#8544**:`GET .../vehicle-requirement/aggregate-draft` 的 `draft` 字段类型从写侧 `GroupVehicleRequirementSaveReqVO` 改为新的只读读侧类型 `GroupVehicleRequirementDraftRespVO`。JSON 形状基本不变(由 `GroupVehicleRequirementDraftRespVOFieldParityTest` 钉住与写侧字段集一致),**唯一新增字段是 `groups[].vehicleTypeName`**(只读车型中文名,不参与保存,原样 `PUT` 回去时被忽略);该 VO **不挂任何校验注解**(`@NotNull`/`@NotEmpty`/`@Size` 等),因为草稿可能违反若干条保存态约束,违规项另在 `violations` 里列出。
|
||||
- **#8544**:`draft.groups[].remark`(分组备注拼接文案)的逐户标签优先级改为 **团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户"**,**不再回落雪花订单 ID**(原来兜底到裸数字 orderId 的情况,现在只有当团号/客户名/订单号三者都拿不到时才会出现,落成字面文案"未知户")。前端如果对这段 `remark` 文本做过正则匹配、高亮、或按"看起来像一串数字"识别订单号,需要同步更新——这段文本此后不会再出现裸数字订单 ID。
|
||||
- **#8545**:`GET .../requirement-summary` 的 `vehicleSeatSummary[]` 新增 `confirmedSeats` / `confirmedCount`(均为 `int`,**尚未整团确认时恒为 `0`,不是 `null`**)。这是"团级已确认"口径,与既有的 `totalSeats` / `totalCount`("逐户已提交"口径)**并列**下发、允许不相等——两者不等是常态不是缺陷,任何断言两者相等的前端逻辑都是错的。
|
||||
- 🔴 **#8545**:`vehicleSeatSummary[]` 可能新增一行只有 `confirmedSeats`/`confirmedCount` 非零、`totalSeats`/`totalCount` 恒为 `0` 的车型条目——发生在车务把车型整团定成了逐户报的车型集合之外的某个车型时(例如逐户都报 `mpv`,车务整团定成 `bus`)。前端渲染这张表时不能假设"有座位数就等于有逐户报",要按 4 个数字各自判断。
|
||||
- **#8619**:`GET .../orders` 的 `vehicleRequirementStatus` / `vehicleRequirementKind` 口径从"只看行程用车(TRAVEL)"放宽为"该户展示序首条活跃用车需求行"(行程用车优先、其次接送机)。🔴 **改前只提交了接送机需求的户,这两列恒为 `null`、`vehicleRequirementStatusName` 被渲染成"未提交"——这是一个错误的展示(该户其实已提交、可能已在审/已派车/已完成),本次已修复**。有行程用车需求行的户这两列读数不变。
|
||||
- **#8619**:`vehicleRequirementKind` 的**实际取值域**从恒为 `TRAVEL` 放宽为 `{TRAVEL, TRANSFER}`——`VehicleRequirementKind` 枚举本身没有新增第三个值,仍然只有这两个;变的是这一个响应字段过去被上游按 TRAVEL 过滤取行、现在按展示序取行,所以能观测到 `TRANSFER`。前端如果曾按"这一列永远是 TRAVEL"写死过图标/文案分支,需要按实际值渲染。
|
||||
- **#8619**:`orders` 新增 `vehicleRequirements[]`(该户全部活跃用车需求行,**恒非 `null`,0~2 条**,行程用车在前、接送机在后)。一户同时提交了行程用车与接送机时,两条需求行状态可能各自不同(例如 TRAVEL 已 `DONE`、TRANSFER 还在 `PENDING_REVIEW`),此时上面那组单值字段(`vehicleRequirementStatus`/`vehicleRequirementKind`)**只能表达其中一条**——按类别判断状态必须读这个数组,不要只读单值字段。
|
||||
- `orders` 端点其余 25 个既有字段(`hotelRequirementStatus`、`totalPrice`、`travelers` 等)本次未变,已由 changelog `30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md` 完整记录,此处不重复。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 全团需求汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement-summary` | 修改 | `vehicleSeatSummary[]` 新增 `confirmedSeats`/`confirmedCount`(#8545) |
|
||||
| 2 | 读团期正式用车需求 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 修改 | 响应体新增 4 个 `*Name` 字段(#8544),PUT/withdraw/waive 共用同一响应体同步生效 |
|
||||
| 3 | 自动汇总正式用车需求草稿 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft` | 修改 | `draft` 字段改用只读读侧 VO,新增 `groups[].vehicleTypeName`(#8544) |
|
||||
| 4 | 团期下子订单列表 | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | 修改 | `vehicleRequirementKind` 取值域放宽、新增 `vehicleRequirements[]`(#8619) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 全团需求汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary`
|
||||
|
||||
**VO**: `GroupRequirementSummaryRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期需求管理 Tab 打开时拉取的"全团需求汇总"卡片,展示逐日房间合计与大巴座位合计。本次变更只影响座位合计部分——`vehicleSeatSummary[]` 新增团级已确认口径的两个字段,供页面并列展示"定制师报了多少座"与"车务最后定了多少座"。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| activeOrderCount | int | 在团子订单数(仅排除 CANCELLED,含 COMPLETED),未变 |
|
||||
| hotelRequirementCount | int | 有 active 用房需求记录的子订单数(兼容保留字段),未变 |
|
||||
| hotelNeededOrderCount | int | 需要订房的户数,未变 |
|
||||
| hotelFlagMismatchOrderCount | int | needsHotel≠true 却已提交有效用房需求的户数,未变 |
|
||||
| hotelSubmittedOrderCount | int | 已提交有效用房需求且计入 dailyRoomBreakdown 的户数,未变 |
|
||||
| vehicleRequirementCount | int | 已提交用车需求的子订单数,未变 |
|
||||
| dailyRoomBreakdown | array | 逐日房间汇总,本次未变 |
|
||||
| vehicleSeatSummary | array | 大巴座位汇总(按车型),本次新增字段见下表 |
|
||||
| orderSpecialTags | array | 各子订单 specialTags,本次未变 |
|
||||
| transferSummary | object | 接送机汇总(#8151 既有字段),本次未变 |
|
||||
|
||||
`vehicleSeatSummary[]` 单项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| vehicleType | string | 车型大类编码(suv/mpv/bus/sedan;缺失时为"未知"),未变 |
|
||||
| vehicleTypeName | string | 车型大类中文名;查不到或车队服务不可用时为 `null`,未变 |
|
||||
| totalSeats | int | 合计座位数(seats × count 之和);统计基数为"逐户已提交"的行程用车需求,未变 |
|
||||
| totalCount | int | 合计车辆台数;统计基数为"逐户已提交"的行程用车需求,未变 |
|
||||
| confirmedSeats 🆕 | int | 合计座位数(团级已确认的正式需求口径);尚未整团确认时为 `0`,与 totalSeats 不等是常态(#8545) |
|
||||
| confirmedCount 🆕 | int | 合计车辆台数(团级已确认的正式需求口径);尚未整团确认时为 `0`(#8545) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/requirement-summary
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为测试环境实测响应(团期批次 `2104840641651556353`,2026-09-30 采集;仅展示 `vehicleSeatSummary[0]`,其余字段结构未变不重复列出):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"vehicleSeatSummary": [
|
||||
{
|
||||
"vehicleType": "mpv",
|
||||
"vehicleTypeName": "商务车",
|
||||
"totalSeats": 14,
|
||||
"totalCount": 2,
|
||||
"confirmedSeats": 7,
|
||||
"confirmedCount": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期批次不存在:返回业务错误(见"错误响应"),`data` 为 `null`。
|
||||
- 全团无任何用车需求:`vehicleSeatSummary` 为空数组,不是 `null`。
|
||||
- 团级从未整团确认过(或已被重开成 `PENDING_RECONFIRM`):既有行的 `confirmedSeats`/`confirmedCount` 均为 `0`,不追加新行;此时该数组与改动前逐户口径的行完全一致。
|
||||
- 车务整团定的车型不在逐户已提交的车型集合内:追加一行 `totalSeats=0`/`totalCount=0`、`confirmedSeats`/`confirmedCount` 非零的记录,`vehicleTypeName` 仍会被正常补全。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `confirmedSeats`/`confirmedCount` 与 `totalSeats`/`totalCount` 是两个独立基数,前端不得假设两者相等或用其中一个推算另一个。
|
||||
- 权限码为 `group-batch:view`。
|
||||
|
||||
---
|
||||
|
||||
### 2. 读团期正式用车需求 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement`
|
||||
|
||||
**VO**: `GroupVehicleRequirementRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期正式用车需求编辑页/详情页打开时的回填读口,同时是"配车计划刷新是否死了"在 admin 侧唯一无副作用的观测口(#7988)。`PUT`(保存)、`withdraw`(撤回)、`waive`(免车)三个写端点返回的响应体与本端点完全一致(同一个 Java 类),前端可以对四个端点复用同一套渲染逻辑。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | string(Long 转字符串) | 正式需求主键,未变 |
|
||||
| groupBatchId | string(Long 转字符串) | 团期聚合主键,未变 |
|
||||
| status | string | 状态:DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED,未变 |
|
||||
| statusName 🆕 | string | 状态中文名(草稿/已确认/已发车务/配车完成/待重新确认/已取消);status 为 null 或认不出的编码时为 `null`,不回落编码原文(#8544) |
|
||||
| version | int | 乐观锁版本号,未变 |
|
||||
| remark | string | 整份备注;撤回/免车会把操作追加进来(不覆盖原备注),超 500 字**从头部截**并以 `…` 开头,未变 |
|
||||
| confirmedBy | string | 整份确认人,DRAFT 时为 null,未变 |
|
||||
| confirmedAt | string(LocalDateTime) | 整份确认时间,DRAFT 时为 null,未变 |
|
||||
| planRefreshState | string | 配车刷新状态原值:null=从未登记过刷新(多数团期正常态)/PENDING/DONE/FAILED,未变 |
|
||||
| planRefreshStateName 🆕 | string | 配车刷新状态中文名(刷新中/刷新完成/刷新失败);为 null 或认不出的编码时为 `null`(#8544) |
|
||||
| planRefreshReplayCount | int | 人工受控重投累计次数(管理员点确认触发,不含自动重试),上限 5,未变 |
|
||||
| blockedStage | string | 团期阻断阶段快照:null=未阻断;非空为受控重开发生那一刻的团期状态码(GroupBatchStatus 全域,常见 RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE),未变 |
|
||||
| blockedStageName 🆕 | string | 阻断阶段中文名(如 资源准备中/物料准备中/待出发);为 null 或认不出的编码时为 `null`(#8544) |
|
||||
| planRefreshStalled | boolean | 刷新是否已停滞、不会自愈(恒非 null);true=必须有人处置,未变 |
|
||||
| planRefreshStalledReason | string | 停滞归因:STATE_FAILED/COMMAND_FAILED/TIMEOUT;未停滞时为 null,未变 |
|
||||
| planRefreshStalledReasonName 🆕 | string | 停滞归因中文名(刷新已被判死/刷新命令已失败/刷新超时);未停滞或认不出编码时为 `null`(#8544) |
|
||||
| planRefreshTimeoutAt | string(LocalDateTime) | 本轮刷新超时时刻;仅 PENDING 且已登记发起时刻时有值,未变 |
|
||||
| planRefreshReplayExhausted | boolean | 人工重投额度是否已耗尽(恒非 null),未变 |
|
||||
| groups | array | 全部乘车分组(整团免车态为空数组),结构未变,见下表 |
|
||||
| exemptHouseholds | array | 仅保存草稿(PUT)响应填充,其余端点(含本 GET)恒为 `null`,未变 |
|
||||
|
||||
`groups[]` 单项字段(未变,随 VO 一起下发本次新增的 4 个 `*Name` 兄弟字段):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupId | string(Long) | 分组主键 |
|
||||
| groupCode | string | 分组键,直接作为车费 alloc_group |
|
||||
| vehicleType | string | 车型文本/字典值 |
|
||||
| vehicleTypeName | string | 车型中文名(既有字段,非本次新增) |
|
||||
| serviceStartDate / serviceEndDate | string(LocalDate) | 本组服务起止日 |
|
||||
| seats / count | int | 该组单车座位数/车辆数量;存量分组为 null 表示待填 |
|
||||
| specialTags | array | 该组特殊诉求标签(code + name) |
|
||||
| remark | string | 该组备注/其他诉求;存量分组为 null |
|
||||
| totalSeatCount | int | 总座位数 = seats × count |
|
||||
| maxHeadcount | int | 该组 days 里的最大用车人数 |
|
||||
| remainingPassengerSeats | int | 余座(扣司机位后的可乘座位 − 最大用车人数) |
|
||||
| days | array | 逐日用车人数与成员 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/vehicle-requirement
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为测试环境实测响应(团期批次 `2104840641651556353`,2026-09-30 采集)。`planRefreshState` / `blockedStage` / `planRefreshStalledReason` 三列在库中为 `NULL`,对应 `*Name` 实测为 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"status": "DONE",
|
||||
"statusName": "配车完成",
|
||||
"planRefreshState": null,
|
||||
"planRefreshStateName": null,
|
||||
"blockedStage": null,
|
||||
"blockedStageName": null,
|
||||
"planRefreshStalledReason": null,
|
||||
"planRefreshStalledReasonName": null
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期尚未形成正式需求(从未 PUT 过):`data` 为 `null`(源码 `@ApiOperation` 明确标注"未形成时返回 null"),不是报错。
|
||||
- `planRefreshState`/`blockedStage`/`planRefreshStalledReason` 三列 DB 为 `NULL`(从未走过受控重开,绝大多数团期的正常态):对应的 3 个 `*Name` 字段一律为 `null`,不下发默认中文名。
|
||||
- `groups` 为空数组场景:整团免车态(waive 后)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 4 个 `*Name` 字段是**只读派生展示字段**,不接受回传;`PUT` 请求体仍用原编码字段(`GroupVehicleRequirementSaveReqVO` 未新增字段)。
|
||||
- 认不出的编码 → 对应 `*Name` 为 `null`,**不回落原编码**——这与团期列表页 `GroupBatchConverter#resolveBatchStatusName`(未知码回落原码)是刻意不同的两套口径,不要混用同一套前端兜底逻辑。
|
||||
- 本端点权限码为 `group-batch:demand:confirm`,与 PUT/withdraw/waive/aggregate-draft 四个端点同码。
|
||||
- 本端点后续不会引入任何写操作(#7988 明确约束),可放心作为无副作用轮询口使用。
|
||||
|
||||
---
|
||||
|
||||
### 3. 自动汇总正式用车需求草稿 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft`
|
||||
|
||||
**VO**: `GroupVehicleAggregateDraftRespVO`(外层诊断字段未变;`draft` 字段类型改为 `GroupVehicleRequirementDraftRespVO`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期正式用车需求编辑弹窗首次打开、或点"重新汇总"时调用:按各子订单已提交的活跃行程用车需求自动生成一份分组草稿,`draft` 可原样 `PUT` 回 `/vehicle-requirement` 保存。本次变更只影响 `draft` 内部的类型与新增字段,外层的 `droppedFleetItems`/`staleHeadcountOrders`/`paddedOrderDays`/`seatOptionAdjusted`/`violations`/`exemptHouseholds` 六个诊断字段结构未变。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| draft | object 🆕类型变更 | 汇总草稿,类型由写侧 `GroupVehicleRequirementSaveReqVO` 改为只读读侧 `GroupVehicleRequirementDraftRespVO`,见下表 |
|
||||
| droppedFleetItems | array | 多车型户被丢弃的车型,未变 |
|
||||
| staleHeadcountOrders | array | 人数已过期的户,未变 |
|
||||
| paddedOrderDays | array | 为覆盖出发~返回而补进的日期,未变 |
|
||||
| seatOptionAdjusted | array | 座位被兜底调整到车型可选档位的户,未变 |
|
||||
| violations | array | 草稿违反保存态校验的逐条诊断,未变 |
|
||||
| exemptHouseholds | array | 提交不了的豁免户(订单不在定制中/团期冻结且未被打回),未变 |
|
||||
|
||||
`draft`(`GroupVehicleRequirementDraftRespVO`)字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| version | int | 乐观锁版本号;原样回传给 PUT;尚未形成正式需求时为 `null` |
|
||||
| remark | string | 整份需求备注,汇总草稿恒为 `null` |
|
||||
| groups | array | 全部乘车分组,恒非 null,无可汇总内容时为空数组;见下表 |
|
||||
|
||||
`draft.groups[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupId | string(Long) | 既有分组主键,汇总草稿恒为 `null` |
|
||||
| groupCode | string | 分组键,直接作为车费 alloc_group |
|
||||
| vehicleType | string | 车型大类编码(归一后的 key) |
|
||||
| vehicleTypeName 🆕 | string | 车型大类中文名(只读,不参与保存);字典查不到时为 `null`,不回落编码原文(#8544,本类相对写侧唯一新增字段) |
|
||||
| serviceStartDate / serviceEndDate | string(LocalDate) | 本组服务起止日 |
|
||||
| seats / count | int | 该组单车座位数(存量/无档位可取时可能为 null)/车辆数量 |
|
||||
| specialTags | array(string) | 该组特殊诉求标签编码数组 |
|
||||
| remark | string | 该组备注;汇总时按"团号(客户名): 备注"拼接,超 500 字**从尾部截断** |
|
||||
| days | array | 逐日用车人数与成员,见下表 |
|
||||
|
||||
`draft.groups[].days[]` 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| tripDate | string(LocalDate) | 团期行程日 |
|
||||
| headcount | int | 该组该日用车人数(乘车人数,非户数) |
|
||||
| memberOrderIds | array(string) | 该组该日实际乘车的子订单集合 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/vehicle-requirement/aggregate-draft
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为测试环境实测响应(团期批次 `2104840641651556353`,2026-09-30 采集;`days` 实测为 7 天逐日行,此处只保留首日,其余日同形):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104840641651556353",
|
||||
"currentStatus": "DONE",
|
||||
"draft": {
|
||||
"version": 3,
|
||||
"remark": null,
|
||||
"groups": [
|
||||
{
|
||||
"groupId": null,
|
||||
"groupCode": "MPV",
|
||||
"vehicleType": "mpv",
|
||||
"vehicleTypeName": "商务车",
|
||||
"serviceStartDate": "2026-11-11",
|
||||
"serviceEndDate": "2026-11-17",
|
||||
"seats": 7,
|
||||
"count": 1,
|
||||
"specialTags": [],
|
||||
"remark": "26-3682(董海涛): 结伴出行客户,整团统一 7 座商务车,已与客户确认路线;26-0805(谢丽萍): 9月30日至10月3日行程用车,成人4人其中1位长者,行李较多需大后备箱",
|
||||
"days": [
|
||||
{
|
||||
"tripDate": "2026-11-11",
|
||||
"headcount": 4,
|
||||
"memberOrderIds": ["2104840641597030402", "2104840685708525570"],
|
||||
"memberOrderCount": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"violations": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
`groups[].remark` 的一户标识格式是**「团号(客户名)」**(`26-3682(董海涛)`),多户合并进同一车型组时以 `;` 连接。本次改动前该位置拼的是 19 位订单 ID,因此:**已确认落库的存量团期,其 `GET .../vehicle-requirement` 返回的 `groups[].remark` 仍是旧格式(`HL2026…` 订单号前缀)**——本单只改新生成的汇总草稿,不回写历史数据。前端不要按固定格式解析 `remark`,它是给运营看的自由文本。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期批次不存在:返回业务错误(见"错误响应")。
|
||||
- 全团无可汇总的行程用车需求:`draft.groups` 为空数组。
|
||||
- 车型字典不可用:`vehicleTypeName` 降级为 `null`,`groups` 其余字段照常下发(不阻断整个响应)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 809120, "message": "车队车型字典暂不可用,无法校验车型,请稍后重试", "success": false, "data": null}
|
||||
```
|
||||
|
||||
有在团需车户缺少可汇总用车需求时(809121,触发条件已被 #8577 收窄——只提交了接送机的户不再算缺少):
|
||||
|
||||
```json
|
||||
{"code": 809121, "message": "团期 「第3期 10月8日出发团」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-0480:未提交用车需求", "success": false, "data": null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `draft` 是**只读展示体**,不挂任何 `@NotNull`/`@NotEmpty`/`@Size` 校验注解;违反保存态约束的地方在 `violations` 里另行列出,不要用 `draft` 自身的字段是否为空来判断能不能保存。
|
||||
- `draft` 除 `groups[].vehicleTypeName` 外的字段集与写侧 `GroupVehicleRequirementSaveReqVO` 完全一一对应(由专门的字段一致性单测钉住),可以原样 `PUT` 回 `/vehicle-requirement`;`vehicleTypeName` 回传时会被后端忽略,车型以 `vehicleType` 编码为准。
|
||||
- `draft.groups[].remark` 的拼接标签口径:团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户",不再回落裸雪花订单 ID。
|
||||
- 权限码与「2」相同(`group-batch:demand:confirm`),本端点同样不引入任何写操作。
|
||||
|
||||
---
|
||||
|
||||
### 4. 团期下子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
|
||||
|
||||
**VO**: `GroupBatchOrderItemRespVO`(30 个字段中本次只变更 4 个,见下表标注)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情页"子订单"Tab 的列表数据源,每户一行。本次变更聚焦用车需求相关的 4 个字段,其余 26 个字段(`hotelRequirementStatus`、`totalPrice`、`travelers` 等)未变,已由 `30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md` 完整记录。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
|
||||
| page | Query | Integer | 否 | 缺省 1,<1 归一为 1 | 页码 |
|
||||
| pageSize | Query | Integer | 否 | 缺省 20,<1 归一为 20,>200 截断为 200 | 每页条数 |
|
||||
| includeTravelers | Query | Boolean | 否 | 缺省 true | 是否附出行人明细(证件号/手机号一律不返回) |
|
||||
| includeNeeds | Query | Boolean | 否 | 缺省 true | 是否附房数/房型/特殊需求 |
|
||||
| includeCancelled | Query | Boolean | 否 | 缺省 false | 是否含已取消子订单 |
|
||||
|
||||
#### 出参字段表(仅列本次变更相关字段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| vehicleRequirementStatus | string | 车需求状态:口径从"只看行程用车"改为"该户展示序首条活跃行"(行程用车优先、其次接送机)。该户一条活跃车需求行都没有时为 `null`(#8619) |
|
||||
| vehicleRequirementStatusName | string | 状态中文名;未知码回落原 `code`(与 `vehicleRequirementKindName` 不同口径);`code` 为 `null` 时按该户 `needsVehicle` 分叉——`true` 出"未提交"、否则为 `null`。**#8619 起"未提交"只在两类需求都没报时出现** |
|
||||
| vehicleRequirementKind | string | 本行车需求的用车类别:TRAVEL/TRANSFER。**#8619 起不再恒为 TRAVEL**——只报了接送机的户在这里下发 `TRANSFER`;无 active 行时为 `null` |
|
||||
| vehicleRequirementKindName | string | 类别中文名:行程用车/接送机;认不出的类别给 `null`,**不回落编码**(与 `vehicleRequirementStatusName` 不同口径) |
|
||||
| vehicleRequirements 🆕 | array | 该户全部活跃用车需求行(0~2 条:TRAVEL/TRANSFER 各至多一条),恒非 `null`,行程用车在前、接送机在后(#8619),见下表 |
|
||||
|
||||
`vehicleRequirements[]` 单项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| kind | string | 需求类别:TRAVEL=行程用车 / TRANSFER=接送机 |
|
||||
| kindName | string | 类别中文名;认不出的类别给 `null`,不回落编码 |
|
||||
| status | string | 该条需求状态:PENDING/PROCESSING/DONE/PENDING_REVIEW/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN |
|
||||
| statusName | string | 状态中文名;PENDING_REVIEW 按 kind 分两套文案:TRAVEL="待提交车务"、TRANSFER="待审核" |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
以下为测试环境实测响应(团期批次 `2104839654727618562`,2026-09-30 采集)。每条 `records[]` 实测另有 26 个本次未变的字段,此处只保留与本单相关的 5 个,其余省略(完整字段集见 changelog `30_8543`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"orderId": "2104839654652121090",
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
|
||||
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING", "statusName": "待车队配"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"orderId": "2104839686486888449",
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"orderId": "2104839729176514562",
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
|
||||
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING_REVIEW", "statusName": "待审核"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 3,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
上例里三户的 `vehicleRequirements` 长度分别为 2 / 1 / 2,`vehicleRequirementStatus` 与 `Kind` 取的都是展示序首条(TRAVEL 优先),因此三户顶层都是 `TRAVEL`;接送机那一行的真实状态只在 `vehicleRequirements[]` 里看得到。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期批次不存在:返回业务错误(见"错误响应")。
|
||||
- 团期下暂无子订单(或 `includeCancelled=false` 时全部已取消):`records` 为空数组,`total=0`。
|
||||
- 该户一条活跃用车需求行都没有:`vehicleRequirementStatus`/`vehicleRequirementKind` 均为 `null`,`vehicleRequirements` 为**空数组**(不是 `null`);`vehicleRequirementStatusName` 按该户 `needsVehicle` 分叉。
|
||||
- 该户只提交了接送机需求(#8619 修复的场景):`vehicleRequirementKind` 下发 `TRANSFER`、`vehicleRequirementStatus` 下发该行程的真实状态,不再是 `null`/"未提交"。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 按用车类别判断状态一律读 `vehicleRequirements[]`,不要只读 `vehicleRequirementStatus`/`vehicleRequirementKind` 单值字段——一户两类需求并存时单值字段只能表达展示序首条。
|
||||
- `vehicleRequirementKindName` 与 `vehicleRequirementStatusName` 是两套不同的未知码兜底口径(前者给 `null`,后者回落原码),不要用同一段兜底逻辑处理。
|
||||
- **🔴 同一个 `status` 编码在不同 `kind` 上的中文名不同,前端不能自建 code→中文 映射表**:`PENDING_REVIEW` 在 `TRAVEL` 行下发「待提交车务」(推走它的是团期管理员整团一次的「提交车务」,该状态上没有逐户审核动作),在 `TRANSFER` 行下发「待审核」(有逐户审核动作)——这是 #8218 起的刻意分叉,声明见 `GroupVehicleHouseholdsRespVO`/`GroupBatchOrderItemRespVO` 的 `@ApiModelProperty`。两侧均有实测样本:批次 `2104839654727618562` 的 `2104839729176514562` 户 TRANSFER 行 = `PENDING_REVIEW`/「待审核」;批次 `2104840641651556353` 的 `2104840685708525570` 户 TRAVEL 行 = `PENDING_REVIEW`/「待提交车务」。**一律直接渲染后端下发的 `statusName`。**
|
||||
- 权限码为 `group-batch:view`。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- **4 个新增 `*Name` 字段(#8544)是只读展示字段**:`statusName`/`planRefreshStateName`/`blockedStageName`/`planRefreshStalledReasonName` 只在响应体里出现,`PUT` 请求体 `GroupVehicleRequirementSaveReqVO` 未新增任何字段,回传这些字段会被忽略。
|
||||
- **PUT `/vehicle-requirement`、POST `/vehicle-requirement/withdraw`、POST `/vehicle-requirement/waive` 三个写端点与本文档「三、2」共用完全同一个 `GroupVehicleRequirementRespVO` 类**:调用这三个端点后,响应体里同样带有 4 个新增 `*Name` 字段,字段语义、null 规则与「三、2」逐字一致,无需前端另写一套解析。
|
||||
- **`aggregate-draft` 的 `draft` 字段类型变更(#8544)**:TypeScript/接口类型定义如果之前直接复用了 `GroupVehicleRequirementSaveReqVO` 的类型作为 `draft` 的类型,需要改成新类型(多一个只读字段 `groups[].vehicleTypeName`,其余字段名与类型逐一相同)。JSON 结构层面对已有解析代码零破坏,只有严格 schema 校验(如果有)需要放开这个新字段。
|
||||
- **未知/认不出的编码统一规则**(本次涉及的所有 `*Name` 字段):`statusName`/`planRefreshStateName`/`blockedStageName`/`planRefreshStalledReasonName`/`draft.groups[].vehicleTypeName`/`vehicleRequirementKindName`/`vehicleRequirements[].kindName` 这一组字段认不出编码一律给 `null`,**不回落原编码**。这与 `orders` 端点的 `vehicleRequirementStatusName`(未知码回落原码,#8543/#8619 未改动此口径)以及团期列表页的团期状态中文名(同样回落原码)是**刻意不同**的两套口径,前端不要用同一段兜底组件处理。
|
||||
- **`vehicleSeatSummary[].confirmedSeats`/`confirmedCount` 与 `totalSeats`/`totalCount` 不相等是设计上允许的常态**(#8545),不要写断言校验两者相等,也不要用其中一组数字反推另一组。
|
||||
- **`orders` 端点判断某户是否提交了某类用车需求,一律遍历 `vehicleRequirements[]` 按 `kind` 过滤**,不要依赖单值字段 `vehicleRequirementStatus`/`vehicleRequirementKind`(#8619 起单值字段只表达展示序首条,可能丢失第二类需求的状态)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录访问:网关拦截,不进入本文档描述的业务逻辑。
|
||||
- 权限不足(当前角色未获授团期权限,或该团期不在本人名下):所有 4 个端点统一报 `589507`。
|
||||
- 团期不存在:所有 4 个端点统一报 `589500`。
|
||||
- `GET .../vehicle-requirement` 团期尚未形成正式需求:`data` 为 `null`,HTTP 200,不是错误。
|
||||
- `planRefreshState`/`blockedStage`/`planRefreshStalledReason` 三列 DB 为 `NULL`(绝大多数团期的正常态,从未走过受控重开):对应 3 个 `*Name` 字段一律为 `null`。
|
||||
- `vehicleSeatSummary[]`:团级从未确认过时 `confirmedSeats`/`confirmedCount` 恒为 `0`(不是 `null`);车务定的车型在逐户报的车型集合之外时会新增一行 `totalSeats=0`/`totalCount=0` 的记录。
|
||||
- `orders` 端点 `vehicleRequirements[]`:该户没有任何活跃用车需求行时为空数组(不是 `null`)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举
|
||||
|
||||
**所属字段**:`GroupVehicleRequirementRespVO.status` / `statusName`
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| DRAFT | 草稿 | 团期管理员正在汇总逐户需求、编辑分组与逐日人数 |
|
||||
| CONFIRMED | 已确认 | 整份确认通过,等待发车务 |
|
||||
| DISPATCHED | 已发车务 | 已推送到车务侧,等待配车 |
|
||||
| DONE | 配车完成 | 车务侧已完成整团配车 |
|
||||
| PENDING_RECONFIRM | 待重新确认 | 确认后团期人数/成员发生变化,需要重新确认整份 |
|
||||
| CANCELLED | 已取消 | - |
|
||||
|
||||
**所属字段**:`GroupVehicleRequirementRespVO.planRefreshState` / `planRefreshStateName`
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| null(列值 NULL) | (无中文名,字段为 null) | 从未登记过任何刷新,多数团期的正常态 |
|
||||
| PENDING | 刷新中 | 刷新命令已登记,等 fleet 刷完 |
|
||||
| DONE | 刷新完成 | 就绪回调已通过判定并应用,本轮刷新闭环 |
|
||||
| FAILED | 刷新失败 | 已耗尽重试预算或被 fleet 判定性终结;不放行团期,出团门②持续拒绝 |
|
||||
|
||||
**所属字段**:`GroupVehicleRequirementRespVO.planRefreshStalledReason` / `planRefreshStalledReasonName`
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| STATE_FAILED | 刷新已被判死 | `plan_refresh_state` 已经是 FAILED |
|
||||
| COMMAND_FAILED | 刷新命令已失败 | 状态列仍是 PENDING,但刷新命令已终态失败(回写钩子未落成) |
|
||||
| TIMEOUT | 刷新超时 | 状态列仍是 PENDING、命令也未判死,但已超过本轮窗口时限(缺省 120 分钟) |
|
||||
|
||||
**所属字段**:`GroupVehicleRequirementRespVO.blockedStage` / `blockedStageName`
|
||||
|
||||
取值域是团期状态枚举 `GroupBatchStatus`(既有枚举,本次未新增值)的全域,本次只是给这个已有编码字段配了中文名。常见值举例:
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| RESOURCE_PREPARING | 资源准备中 | - |
|
||||
| MATERIAL_PREPARING | 物料准备中 | - |
|
||||
| PENDING_DEPARTURE | 待出发 | - |
|
||||
| (其余 GroupBatchStatus 取值) | 对应中文名 | 认不出的编码给 `null` |
|
||||
|
||||
**所属字段**:`orders[].vehicleRequirementKind` / `vehicleRequirementKindName`、`orders[].vehicleRequirements[].kind` / `kindName`
|
||||
|
||||
`VehicleRequirementKind` 枚举本身固定只有 2 个值(本次未新增第三个值,放宽的是响应字段的**实际观测取值范围**,不是枚举定义):
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| TRAVEL | 行程用车 | 服务日冻结为行程日 |
|
||||
| TRANSFER | 接送机 | 服务日取航班/车次日期,允许落在行程日窗外 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| `GroupVehicleRequirementRespVO` 状态/刷新状态/阻断阶段/停滞归因 | 仅下发编码,前端自行映射中文 | 新增 4 个 `*Name` 字段直接下发中文名;认不出编码给 `null` |
|
||||
| `aggregate-draft.draft` 字段类型 | 写侧 `GroupVehicleRequirementSaveReqVO`(挂校验注解,多余的约束会渲染进 Swagger) | 只读读侧 `GroupVehicleRequirementDraftRespVO`(不挂校验注解,多一个只读字段 `vehicleTypeName`) |
|
||||
| `draft.groups[].remark` 逐户标签兜底 | 团号(客户名)→ 团号 → 客户名 → 订单号(裸雪花 ID) | 团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户"(不再回落裸雪花 ID) |
|
||||
| `requirement-summary.vehicleSeatSummary[]` | 只有 `totalSeats`/`totalCount`(逐户已提交口径) | 并列新增 `confirmedSeats`/`confirmedCount`(团级已确认口径),可能新增车型行 |
|
||||
| `orders[].vehicleRequirementStatus`/`Kind` | 只按 TRAVEL 过滤取行;只提交接送机的户恒为 `null`/"未提交" | 取展示序首条活跃行(TRAVEL 优先、TRANSFER 次之);只提交接送机的户下发真实状态 |
|
||||
| `orders[].vehicleRequirements` | 不存在该字段 | 新增,0~2 条,逐类下发状态 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端必改**:如果曾对 `draft.remark` 文本做正则匹配来提取订单号或高亮逐户标签,需要兼容新的"未知户"文案且不再假设会出现裸数字订单 ID。**并且请不要按任何固定格式解析 `remark`**——本单只改新生成的汇总草稿,已确认落库的存量团期其 `GET .../vehicle-requirement` 返回的 `groups[].remark` 仍是改前格式(订单号前缀,形如 `HL20260929154809598: …`),不做历史数据回写。该字段是给运营看的自由文本,两种格式会长期并存。
|
||||
- **前端必改**:如果曾假设 `orders[].vehicleRequirementKind` 恒为 `TRAVEL` 来做图标/文案硬编码分支,需要改为按实际值(`TRAVEL`/`TRANSFER`)渲染,并优先改用 `vehicleRequirements[]` 数组按类判断。
|
||||
- **前端可选增强**:可以直接展示后端下发的 4 个新增中文名字段,替换掉前端此前自行维护的编码→中文映射表(如果有)。
|
||||
- **前端需知悉但不需要立即改**:`vehicleSeatSummary[]` 新增的两个字段、可能新增的车型行,只在页面展示这两个数字时才需要处理;不展示则忽略即可,不影响既有渲染。
|
||||
- **零风险**:所有新增字段都是**在既有 JSON 对象上新增键**,未删除、未改名任何既有字段(`aggregate-draft.draft` 虽改了后端类型,但 JSON 键集合与既有字段类型未变,由专门单测钉住);未做严格 schema 校验的前端代码可无感兼容。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- `GroupVehicleRequirementSaveReqVO`(PUT 请求体)字段集未变,仍是原有编码字段集,本次新增的只读字段不参与保存也不会被写侧读取。
|
||||
- `GroupVehicleRequirementDraftRespVO` 除 `groups[].vehicleTypeName` 外的全部字段(顶层 `version`/`remark`、`groups[]` 内 `groupId`/`groupCode`/`vehicleType`/`serviceStartDate`/`serviceEndDate`/`seats`/`count`/`specialTags`/`days`)未变,由 `GroupVehicleRequirementDraftRespVOFieldParityTest` 钉住与写侧字段集一致。
|
||||
- `VehicleRequirementKind` 枚举定义本身未变,仍只有 `TRAVEL`/`TRANSFER` 两个值。
|
||||
- 团期用房相关端点(`hotel-households` 等)不在本次改动范围内。
|
||||
- `orders` 端点除本文档列出的 4 个字段外,其余 26 个字段未变(详见 changelog `30_8543`)。
|
||||
- 809121/809122/809123 三个错误码的触发条件收窄与文案改写属于 #8577,不在本次三张工单范围内,详见 changelog `30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md`。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- hl-order-service-v3 dev-v3 分支已部署测试网关,HEAD `6373e5cf2`(`git merge-base --is-ancestor` 核实本单提交 `5f2f86bc96` 已在其祖先链内),jar mtime 2026-09-30 09:27:12,两个实例 Nacos 健康检查均为 healthy。
|
||||
- 以下四条读口实测取样自团期批次 `2104840641651556353`(`vehicleRequirements[] == []` 一条取样自 `2104830530031919106`),2026-09-30 采集。
|
||||
- `GET .../requirement-summary` 实测:`vehicleSeatSummary[0]` = `{"vehicleType":"mpv","vehicleTypeName":"商务车","totalSeats":14,"totalCount":2,"confirmedSeats":7,"confirmedCount":1}`。
|
||||
- `GET .../vehicle-requirement/aggregate-draft` 实测:`draft.groups` 非空,含 `vehicleTypeName: "商务车"`;`groups[0].remark` 为「团号(客户名)」格式(`26-3682(董海涛): …;26-0805(谢丽萍): …`),未出现裸雪花订单 ID。
|
||||
- `GET .../orders` 实测:2 条子订单,每条 `vehicleRequirements` 键均存在且非 `null`,长度分别为 2 与 1。另在一个无活跃用车需求行的户上实测该键为**空数组 `[]` 而不是 `null`**(与 `GroupBatchConverter.toVehicleRequirementItems()` 从 `new ArrayList<>()` 起手、无 null 分支一致)。
|
||||
- `GET .../vehicle-requirement` 实测:顶层键包含 `status`/`statusName`/`planRefreshState`/`planRefreshStateName`/`blockedStage`/`blockedStageName`/`planRefreshStalledReason`/`planRefreshStalledReasonName` 共 8 个;观测样本 `status="DONE"` → `statusName="配车完成"`;另外三个 DB 列为 `NULL`,对应 `*Name` 字段均实测为 `null`。
|
||||
- **「认不出的编码 → `null`」这条行为本轮没有实测样本**:候选团期的 `plan_refresh_state` / `blocked_stage` / `plan_refresh_stalled_reason` 三列在库中全为 `NULL`,测试环境里造不出一个库内存着字典外编码的自然样本。该行为由源码与单测两侧钉住:`GroupVehiclePlanRefreshState.labelOf()`、`GroupBatchStatus.descOf()` 对未命中编码返回 `null`(不回落编码原文)。前端按 `null` 兜底渲染即可。
|
||||
- 单元测试:hl-order-service-v3 模块 `Tests run: 561, Failures: 0, Errors: 0, Skipped: 0`,38 个相关测试类逐类点名核对报告文件均存在(38/38 命中);`nested_selector_census` 退出码 0(无 `@Nested` 类被静默漏跑)。
|
||||
- ArchUnit/架构门禁测试:`Tests run: 167`,全绿。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- `docs/CODE_RULES.md` §3(VO 命名与读写侧分离约定)
|
||||
- changelog `30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md`(`orders` 端点其余字段的完整文档,及 `vehicleRequirementKind` 字段本次改动前的基线状态)
|
||||
- changelog `30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md`(PUT/aggregate-draft/confirm-check 三处 809121/809122/809123 错误码本次收窄的完整文档)
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 关联 Issue:#8544、#8545、#8619
|
||||
- 关联 PR:#8625(#8544 #8545)、#8624(#8619)
|
||||
- 联系人:wx
|
||||
@@ -0,0 +1,474 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8548"
|
||||
title: "团期需求重开待审提示接入 4 个只读端点,并修复整团免车团确认时接送机需求放行不到的问题"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "043232cc87d99c33a42325115f28929dc3b06bd6"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8586 合并 dev-v3(ddea7e710c);测试网关部署确认:hl-order-service-v3 @ ff6863754、hl-fleet-service @ 99fb369ba(deploy-status.sh 实测,两者均以 ddea7e710c 为祖先)。4 个只读端点中 3 个(团期详情 A2、房务看板详情 H2、fleet 配车总览)已用同一真实团期(groupBatchId=2104839654727618562,团号 T26-3963)实测捕获非空取值,三端一致;第 4 个(房务看板列表 H1)因该团未被房务认领、不出现在列表口,改用另一真实团期(groupBatchId=2104838272570245121)捕获到已确认团的双 null 基线,未能在本轮独立捕获 H1 的非空实例——这是列表口与详情口可见集合不同导致的结构性限制,不是契约缺口,H1 的字段契约与 H2/A2/总览完全同源同算法(同一个 GroupBatchRequirementReopenHintService)。#8548 的确认端点行为修复(整团免车放行接送机)本身是写操作,为避免误改测试服现存业务数据未做原子调用,已按源码逐行核实:GroupBatchRequirementService.java 505-593 行(doConfirm 内核 javadoc 与分支代码)、1161-1296 行(release 集合装配与 waivedVehicleSnapshot)。;前端已交付:团期详情状态条/房务看板列表/看板详情三处需求重开待审提示接入(户数 null 不折算 0,类别可单独 null),fleet 配车总览出口已删零消费,23 例定向测试全绿(hl-admin 043232cc)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3 / hl-fleet-service:团期需求重开待审提示接入 4 个只读端点,并修复整团免车团确认时接送机需求放行不到的问题
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3(主,#8549 新提示的唯一产出口 + #8548 行为修复)、hl-fleet-service(透传消费方,无独立业务逻辑改动)
|
||||
> **PR**: #8586
|
||||
> **Issue**: #8548、#8549(一个 PR 同时处理两张关联工单)
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 4 个只读端点响应新增 2 字段(团期详情 A2、房务看板列表 H1、房务看板详情 H2、fleet 团期配车总览);1 个写端点(团期整体确认需求)行为修复,响应结构不变
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 新增 2 个响应字段:`requirementReopenPendingHouseholds`(`Integer`)、`requirementReopenResourceType`(`String`),出现在 4 个只读端点:`GET /v3/admin/order/group-batch/{groupBatchId}`、`GET /v3/admin/house/group-batches`、`GET /v3/admin/house/group-batches/{groupBatchId}`、`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`。四处取值同源同算法(`GroupBatchRequirementReopenHintService`,唯一产出口),不存在四套口径分叉的风险。
|
||||
- 🔴 **`null` 不代表 `0`,不要折算成 0 渲染「0 户需求待审核」**。字段只在同时满足 3 个条件时才非空:① 团级 `requirementConfirmed=false`;② 能查到「定制师改需求触发的自动重开」留痕(区别于管理员手动打回,后者无此留痕);③ 重开后该团确实还有在团户处于待审核状态。三者任一不满足,两个新字段都是 `null`,前端应继续渲染原有的「待管理员重新确认」文案。
|
||||
- **两个新字段不是「要么都有要么都无」的一对**:`requirementReopenPendingHouseholds` 非空时,`requirementReopenResourceType` 仍可能单独为 `null`(重开留痕的 `extra` JSON 解析失败/缺键时的降级),此时只渲染「N 户需求待审核」,不带 HOTEL/VEHICLE 类别文案,户数本身不受影响。
|
||||
- **不要与既有字段 `pendingReviewHouseholds` 混淆**(仅 H2 详情端点有此字段):`pendingReviewHouseholds` 是房务看板自己的统计口径,只数房需求,任何时候都下发;新增的 `requirementReopenPendingHouseholds` 是团级确认闸的提示,数的是房、车两类待审需求的户去重并集,且只在上述 3 道闸门都满足时才有值。同一个团这两个数字不一致是正常的,不能互相对账。
|
||||
- **#8548 行为修复(不涉及任何字段新增/删除)**:`POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` 整团确认时,若团期处于「整团免车」(管理员声明免车,`vehicleWaived=true`)状态,此前该分支对车侧释放集合恒返回空,导致该团后续补交的接送机(TRANSFER)需求永远放行不到、团级需求闸永久卡在待确认。修复后免车团确认会释放 **TRANSFER** 需求,但仍**不释放 TRAVEL**(整团免车声明的管辖范围只到 TRAVEL,释放 TRAVEL 等于替管理员推翻免车声明)。可观察的变化只是响应里既有字段 `transferDispatchedOrderIds`/`vehicleDispatchedCount` 现在对免车团也可能非空,响应 VO 结构本身零改动。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 字段新增(非破坏性) | 响应新增 `requirementReopenPendingHouseholds`/`requirementReopenResourceType` |
|
||||
| 2 | H1 房务团期看板列表 | GET | `/v3/admin/house/group-batches` | 字段新增(非破坏性) | 同上,列表项级别 |
|
||||
| 3 | H2 房务团期看板详情 | GET | `/v3/admin/house/group-batches/{groupBatchId}` | 字段新增(非破坏性) | 同上 |
|
||||
| 4 | 团期配车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 字段新增(非破坏性) | 同上,order-v3 原样透传,fleet 不自算 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. A2 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `GroupBatchDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员在团期详情页查看需求确认状态。此前该页只能看到 `requirementConfirmed=false`,无法区分「等定制师第一次提交」「被管理员打回」「已重新提交但还有户没处理完」三种情况,一律渲染「待管理员重新确认」。本次起,属于第三种情况(定制师改需求触发的自动重开,且确实还有户在等审)时,响应额外带出具体待审户数与是谁(房/车)推倒了确认闸。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/说明文案更新的字段;其余既有字段结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时不要直接渲染「待管理员重新确认」,先看 `requirementReopenPendingHouseholds` |
|
||||
| requirementReopenPendingHouseholds | Integer | **新增**。需求待审核户数:`requirementConfirmed=false` 且是「定制师改需求触发的自动重开」时下发;为 `null` 表示不下发(已确认 / 管理员打回置 0 / 已无人待审),按原有文案渲染。**计数单位是「户」不是「需求行」**——同一户同时报行程用车与接送机用车只计 1 户 |
|
||||
| requirementReopenResourceType | String | **新增**。触发最近一次需求重开的资源类别 `HOTEL` / `VEHICLE`:只标「谁把确认闸推倒了」,与户数口径无关(户数是房、车两类的并集);取不到时为 `null`,此时文案不带类别 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(测试网关,业务 admin 身份)。以下为节选(仅摘录本次相关字段,其余既有字段结构未变,不重复列出):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"batchNo": "T26-3963",
|
||||
"requirementConfirmed": false,
|
||||
"requirementReopenPendingHouseholds": 1,
|
||||
"requirementReopenResourceType": "VEHICLE"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 3 道闸门任一不满足(已确认 / 管理员手动打回 / 重开后已无人待审):两个新字段均为 `null`,前端按原有文案渲染。
|
||||
- 重开留痕的 `extra` JSON 解析失败或缺键:仅 `requirementReopenResourceType` 单独降级为 `null`,`requirementReopenPendingHouseholds` 不受影响照常下发(服务端 `readResourceType()` 的不对称降级,不会因为类别取不到而连户数一起丢)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
其余既有错误码本次未变:589507(无操作权限:当前角色未授予团期权限,或该团期不在您名下)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `requirementReopenPendingHouseholds` 为 `null` 不代表 0,不要折算成 0 渲染。
|
||||
- `requirementReopenResourceType` 可能单独为 `null`(即使户数非空),此时只显示户数、不带类别文案。
|
||||
- 判断是否要展示这两个字段,先看 `requirementConfirmed`;`requirementConfirmed=true` 时两个新字段恒为 `null`,不需要额外判断。
|
||||
|
||||
---
|
||||
|
||||
### 2. H1 房务团期看板列表 `GET /v3/admin/house/group-batches`
|
||||
|
||||
**VO**: `HouseGroupBatchBoardPageReqVO` → `PageResult<HouseGroupBatchBoardSimpleRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务在看板列表页浏览已认领的团期。列表项与详情页(见下)共用同一套团级字段判定逻辑,此前列表页同样只能看到 `requirementConfirmed=false` 一个布尔值,本次起可展示与详情页一致的待审户数与类别提示。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| scope | Query | String | ❌ | 最长 8 | 可见范围 `MINE`(默认,只看本人认领)/ `ALL`(看全部已认领团,#8491 起全体房务可传) |
|
||||
| claimerAdminId | Query | Long | ❌ | - | 按认领人筛(仅 `scope=ALL` 生效) |
|
||||
| planStatus | Query | String | ❌ | 最长 16 | 计划行状态 `PENDING` / `CONFIRMED`,不传=全部 |
|
||||
| batchStatus | Query | String | ❌ | 最长 200 | 团期状态多选,逗号分隔;默认四态;`CANCELLED` 传入被忽略 |
|
||||
| stayDateFrom | Query | LocalDate | ❌ | ISO 日期 | 住期区间起 |
|
||||
| stayDateTo | Query | LocalDate | ❌ | ISO 日期 | 住期区间止 |
|
||||
| departDateFrom | Query | LocalDate | ❌ | ISO 日期 | 出发日区间下界 |
|
||||
| departDateTo | Query | LocalDate | ❌ | ISO 日期 | 出发日区间上界 |
|
||||
| keyword | Query | String | ❌ | 最长 32 | 团期号或产品名包含匹配 |
|
||||
| page | Query | Long | ❌ | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Long | ❌ | 1~50,默认 20 | 每页条数 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/说明文案更新的字段(列表项级别);其余既有字段结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 |
|
||||
| requirementReopenPendingHouseholds | Integer | **新增**。语义与 A2 完全一致(同一产出口) |
|
||||
| requirementReopenResourceType | String | **新增**。语义与 A2 完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/house/group-batches?scope=ALL&pageSize=50
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(测试网关,房务角色)。列表口只显示**已被房务认领**的团,本次实测命中的这一条是已确认团(双 `null` 基线),节选:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"groupBatchId": "2104838272570245121",
|
||||
"requirementConfirmed": true,
|
||||
"requirementReopenPendingHouseholds": null,
|
||||
"requirementReopenResourceType": null
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:本轮实测范围内命中的已认领团恰好都是已确认状态,未独立捕获非空实例;非空实例已在 A2/H2/fleet 总览三端用同一真实团期(T26-3963)交叉验证一致,H1 走的是同一个 `GroupBatchRequirementReopenHintService` 产出口,字段契约同源,只是列表口的可见集合(仅已认领团)与详情口不同。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无匹配团期:`list` 为空数组,`total` 为 0(既有行为未变)。
|
||||
- 候选集超 500 时 `total` 返回 -1 表示未统计(既有行为,本次未变,与新字段无关)。
|
||||
- 3 道闸门任一不满足:该行的两个新字段均为 `null`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":808090,"message":"未登录或非房务角色,无权操作","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 未认领的团不在本列表口,与团期抢单池页以「认领动作」为界互斥,新字段不改变这条边界。
|
||||
- 同上,`null` 不代表 0;`requirementReopenResourceType` 可单独为 `null`。
|
||||
|
||||
---
|
||||
|
||||
### 3. H2 房务团期看板详情 `GET /v3/admin/house/group-batches/{groupBatchId}`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `HouseGroupBatchBoardRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务点开具体团期查看逐日配房详情。全体房务可读任意团期(不校验认领归属),他人认领的团返回 `readOnly=true`(#8491,与本次改动无关)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/说明文案更新的字段;其余既有字段(`hotelReady`、`pendingReviewHouseholds`、`days[]` 等)结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementConfirmed | Boolean | 需求整体确认标记(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 |
|
||||
| requirementReopenPendingHouseholds | Integer | **新增**。语义与 A2 完全一致;🔴 与既有字段 `pendingReviewHouseholds` **不是一回事**——后者只数房需求、任何时候都下发,前者是房车并集且只在 3 道闸门满足时下发,同一个团两者数字不一致是正常的,不能互相对账 |
|
||||
| requirementReopenResourceType | String | **新增**。语义与 A2 完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/house/group-batches/2104839654727618562
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(测试网关,房务角色)。节选:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"requirementConfirmed": false,
|
||||
"requirementReopenPendingHouseholds": 1,
|
||||
"requirementReopenResourceType": "VEHICLE",
|
||||
"pendingReviewHouseholds": 0
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 与同一团在 A2、fleet 总览的实测结果逐字段一致(`requirementReopenPendingHouseholds=1`、`requirementReopenResourceType="VEHICLE"`),印证三端同源同算法;`pendingReviewHouseholds=0` 与 `requirementReopenPendingHouseholds=1` 在此例中不同,正是上表说明的两套口径不对账的真实样本。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 3 道闸门任一不满足:两个新字段均为 `null`。
|
||||
- `requirementReopenResourceType` 单独降级为 `null` 时 `requirementReopenPendingHouseholds` 不受影响。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":589500,"message":"团期不存在","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code":808090,"message":"未登录或非房务角色,无权操作","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `requirementReopenPendingHouseholds` 非空不要与 `pendingReviewHouseholds` 混淆或相加,两者统计口径不同。
|
||||
- `null` 不代表 0;`requirementReopenResourceType` 可单独为 `null`。
|
||||
|
||||
---
|
||||
|
||||
### 4. fleet 团期配车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `GroupDispatchOverviewRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务在配车总览页查看该团的需求确认状态、逐日排车与接送机缺口。`requirementReopenPendingHouseholds`/`requirementReopenResourceType` 由 order-v3 内部覆盖口(`GET /v3/internal/group-batch/{id}/vehicle-coverage`,Feign 专用,非前端可直接调用)原样透传,fleet 侧不做任何二次计算,与 A2/H2 保证同一口径。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/说明文案更新的字段;其余既有字段(`serviceDates`、`vehicleReady`、`days[]`、`orders[]`、`transferPendingTotal`、`conversationKey` 等)结构未变,不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementConfirmed | Boolean | 整团需求是否已确认(既有字段,说明文案本次更新):为 `false` 时先看 `requirementReopenPendingHouseholds` 再定文案 |
|
||||
| requirementReopenPendingHouseholds | Integer | **新增**。order-v3 覆盖口原样透传,fleet 不自算;🔴 `null` 不代表 0,不要折算成 0 渲染 |
|
||||
| requirementReopenResourceType | String | **新增**。order-v3 覆盖口原样透传,语义与 A2 完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/2104839654727618562/overview
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(测试网关,车务角色,roleId=5)。节选:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"batchNo": "T26-3963",
|
||||
"requirementConfirmed": false,
|
||||
"requirementReopenPendingHouseholds": 1,
|
||||
"requirementReopenResourceType": "VEHICLE"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 与同一团在 A2、H2 的实测结果逐字段一致。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 3 道闸门任一不满足:两个新字段均为 `null`,与 A2/H2 同步(同一份覆盖口数据)。
|
||||
- order-v3 覆盖口不可达时,整个端点按既有降级规则返回 600012(团期配车基线不可达),不会出现「新字段单独降级、其余字段正常」的中间态——两个新字段与其余团级字段是同一次 Feign 调用的产物,不可能分开失败。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":600012,"message":"团期配车基线不可达,请稍后重试","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
其余既有错误码本次未变:401(未登录)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `requirementReopenPendingHouseholds` 为 `null` 不代表 0。
|
||||
- 该字段与 fleet 自己的 `transferPendingTotal`(接送机未配计数)是两回事:前者是「团级需求确认闸的提示」,后者是「已确认需求里还有多少接送机缺口没排车」,两者可以同时非空,互不覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端响应字段的正确消费方式,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| ✅ 判断是否展示「N 户需求待审核」 | 先判 `requirementConfirmed===false`,再判 `requirementReopenPendingHouseholds != null` |
|
||||
| ✅ 处理 `requirementReopenPendingHouseholds` 非空但 `requirementReopenResourceType` 为 `null` | 只渲染「N 户需求待审核」,不带类别文案,这是合法的降级态,不是异常 |
|
||||
| ❌ 把 `requirementReopenPendingHouseholds` 为 `null` 折算成 0 渲染 | `null` 与「0 户待审」是两种不同状态:前者是「不适用/无需提示」,后者是「重开了但已处理完」——本次实现中「已处理完」同样落到 `null`(闸门 3 过滤),所以两者当前观察上是同一渲染结果,但契约上不保证永远如此,不要做数值折算 |
|
||||
| ❌ 拿 H2 的 `pendingReviewHouseholds` 和 `requirementReopenPendingHouseholds` 相加或对账 | 两个字段统计口径不同(前者只数房、恒下发;后者数房车并集、条件下发) |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
无。本次 4 个端点均为只读字段新增,不涉及任何请求体/入参变化,前端无需在调用序列上做任何调整。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
`GroupBatchRequirementReopenHintService` 只读、不写任何表。查库固定 3 次批量查询(不随团期数量线性增长):① 从 `group_batch` 内存过滤未确认团;② 按团期 ID 批量查 `group_batch` 状态时间线,取最近一条 `BATCH_REQUIREMENT_REOPENED` 事件日志;③ 按事件命中的团批量取在团子订单 ID,再批量查待审核户。看板一页多团时同样是固定 3 次查询,不退化为 N+1。
|
||||
|
||||
#8548 修复:`doConfirm` 内核在整团免车分支新增一次车侧需求释放调用(`dispatchGroupTransferRequirements`),与既有的房侧放行在同一事务内,任一步失败整团零写入(既有的 809112 整团回滚保证不变)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 团级 `requirementConfirmed=true`(已确认)→ 4 个端点的两个新字段恒为 `null`。
|
||||
- `requirementConfirmed=false` 但查不到 `BATCH_REQUIREMENT_REOPENED` 留痕(即管理员手动打回,而非定制师改需求触发的自动重开)→ 两个新字段恒为 `null`,前端渲染原有的「待管理员重新确认」文案。
|
||||
- `requirementConfirmed=false` 且有重开留痕,但重开后该团在团户已全部处理完(待审户数为 0)→ 两个新字段恒为 `null`。
|
||||
- 重开留痕的 `extra` JSON 缺失/为空/解析失败 → 仅 `requirementReopenResourceType` 单独为 `null`,`requirementReopenPendingHouseholds` 不受影响。
|
||||
- **(#8548,确认端点行为变更,非本次响应字段变化)** 整团免车团(`vehicleWaived=true`)确认时,此前车侧释放集合恒为空,接送机需求永远放行不到;修复后释放 **TRANSFER**(接送机)需求,仍不释放 **TRAVEL**(行程用车,整团免车声明的管辖范围仅限于此);同时不推进正式团级用车需求(`groupVehicleRequirementId`/`Status`/`Version` 三个既有字段在免车分支仍为 `null`,因为免车团本就没有需要推进的正式需求,此行为本次未变)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 需求重开资源类别(`requirementReopenResourceType`)
|
||||
|
||||
**所属字段**: `requirementReopenPendingHouseholds` 的伴生字段,4 个端点通用 | **类型**: `String`(取不到时为 `null`)
|
||||
|
||||
| 值 | 含义 | 说明 |
|
||||
|----|------|------|
|
||||
| `HOTEL` | 定制师改住宿需求触发的自动重开 | |
|
||||
| `VEHICLE` | 定制师改用车需求触发的自动重开 | |
|
||||
| `null` | 取不到类别,或未落入需要下发的场景 | 户数字段仍可能非空,参见六、边界行为 |
|
||||
|
||||
取值来源:团期状态时间线里最近一条 `BATCH_REQUIREMENT_REOPENED` 事件日志的 `extra` JSON 中 `resourceType` 键,由触发重开的那条业务逻辑写入;本次未新增写入路径,只新增读取与下发。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `requirementReopenPendingHouseholds` | 不存在(4 个端点均无) | 新增,`Integer`,语义见上,4 端点同源同算法 |
|
||||
| `requirementReopenResourceType` | 不存在(4 个端点均无) | 新增,`String`,语义见上 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `requirementConfirmed=false` 且是定制师改需求触发的自动重开、确实还有户在等审 | 4 个端点均只有 `requirementConfirmed=false`,前端一律渲染「待管理员重新确认」,无法与「管理员手动打回」区分 | 额外带出具体待审户数与资源类别,可渲染「N 户需求待审核」区分于打回场景 |
|
||||
| 整团免车团确认(`POST .../requirement/confirm`) | 车侧释放集合恒为空,免车后补交的接送机需求永远放行不到,团级需求闸永久卡在待确认,无任何报错或日志提示 | 释放 TRANSFER 需求(不释放 TRAVEL),响应既有字段 `transferDispatchedOrderIds`/`vehicleDispatchedCount` 对免车团可能非空 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否——4 个只读端点均为纯字段新增,既有字段类型/取值/含义均未变;#8548 修复不改变响应 VO 结构,只改变部分既有字段(`transferDispatchedOrderIds`/`vehicleDispatchedCount`)在特定场景下的实际取值。旧前端忽略新字段不受任何影响。
|
||||
- **前端是否必须同步上线**: 否(不上线不会报错或丢功能);建议同步——上线后可以把「待管理员重新确认」与「N 户需求待审核」两种场景分开展示,减少运营/房务/车务误判为同一种阻塞。
|
||||
- **前端 workaround 清理点**: 若此前为区分「打回」与「重开待审」两种 `requirementConfirmed=false` 场景写过额外查询或猜测逻辑,现在可以直接用新字段替换。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 上表列出的 4 个只读端点的响应字段;`POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` 端点在整团免车场景下的车侧释放行为。
|
||||
- **零影响**:
|
||||
- 4 个只读端点的请求参数与既有校验规则
|
||||
- `POST .../requirement/confirm` 的请求体、响应 VO 结构、非免车团的确认行为
|
||||
- `POST .../requirement/confirm` 在免车团场景下对 TRAVEL 需求的处理(仍不放行,逐单放行入口不受影响)
|
||||
- `GET /v3/internal/group-batch/{id}/vehicle-coverage` 内部 Feign 端点之外的其它 internal 接口
|
||||
- `PUT /admin/fleet/assignments/pickup-dropoff-config` 等接送机配置端点
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-order-service-v3` @ `ff6863754`、`hl-fleet-service` @ `99fb369ba`(deploy-status.sh 实测部署登记,测试网关 `https://api.test.1814.love`);`git merge-base --is-ancestor ddea7e710c ff6863754` 与 `... 99fb369ba` 均为真,确认本单所在提交已随两个服务的当前部署一并上线。
|
||||
|
||||
```
|
||||
✓ GET /v3/admin/order/group-batch/2104839654727618562(业务 admin):真实返回
|
||||
requirementConfirmed=false, requirementReopenPendingHouseholds=1, requirementReopenResourceType="VEHICLE"
|
||||
✓ GET /v3/admin/house/group-batches/2104839654727618562(房务角色):同一团返回同一取值,
|
||||
三端一致;附带既有字段 pendingReviewHouseholds=0,印证两套口径不对账
|
||||
✓ GET /admin/fleet/group-dispatch/batches/2104839654727618562/overview(车务角色,roleId=5):
|
||||
同一团返回同一取值,三端一致
|
||||
✓ GET /v3/admin/house/group-batches?scope=ALL&pageSize=50(房务角色):真实返回列表,
|
||||
命中已认领团(groupBatchId=2104838272570245121)为已确认状态,
|
||||
requirementReopenPendingHouseholds=null、requirementReopenResourceType=null,验证了 null 基线分支
|
||||
```
|
||||
|
||||
注:H1 列表口本轮未独立捕获非空实例——T26-3963(本次用于交叉验证的团)未被房务认领,不出现在 H1 的可见集合里;H1 与其余三端共用同一个 `GroupBatchRequirementReopenHintService` 产出口,字段契约同源,此限制是列表口「仅显示已认领团」这一既有边界导致的取样限制,不是实现差异。
|
||||
|
||||
`#8548` 确认端点在整团免车分支释放 TRANSFER 需求的修复,本轮未做真实原子调用验证(该端点为写端点,会推进团级需求状态,测试服现存数据上误调用有污染业务状态的风险);已按源码逐行核实:`GroupBatchRequirementService.java` 505-593 行(`doConfirm` 内核 javadoc 第三段与 569-572 行分支代码)、1161-1296 行(放行集合装配 javadoc 与 `waivedVehicleSnapshot` 方法 javadoc)。前端如需验证该行为,应在确认后核对响应里 `transferDispatchedOrderIds` 是否包含预期订单,而不是依赖某个新字段(该端点响应结构本次未变)。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8548](https://git.1814.love/wx/HL/issues/8548)、[wx/HL#8549](https://git.1814.love/wx/HL/issues/8549)
|
||||
- 关联 PR: [wx/HL#8586](https://git.1814.love/wx/HL/pulls/8586)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8548](https://git.1814.love/wx/HL/issues/8548)、[#8549](https://git.1814.love/wx/HL/issues/8549)
|
||||
- **PR**: [#8586](https://git.1814.love/wx/HL/pulls/8586)
|
||||
- **Merge commit**: [ddea7e710c](https://git.1814.love/wx/HL/commit/ddea7e710c)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8556"
|
||||
title: "派单看板订单详情识别团期配车,排车节点与实派车辆数不再只认逐户派车行"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "585a59b06e4c63c4b31da95d6de4ce7d16d1f245"
|
||||
target_release: ""
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8583 合并 dev-v3(9c7ac93829);hl-fleet-service dev-v3 分支部署测试网关 @ 9c7ac9382 并实测:团期订单详情下发 groupBatchId/groupDispatchManaged/groupDispatchReady/groupDispatchPlan,排车节点由 WAITING 改为 SKIPPED,actualVehicleCount 由团期子订单实测的 0 变为与团期配车总览一致的实派车数;同一订单可同时存在团期配车与个人派车两组数据且互不覆盖。;前端已交付:OrderDrawer 新增「团期统一配车」只读卡(实派车数 null 显未知/ready=false 警告/团级配车行未落车未落司机兜底/与逐户派车并存),SKIPPED 既有分支覆盖,spec 4 例全绿(hl-admin 585a59b0)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 派单看板订单详情识别团期配车,排车节点与实派车辆数不再只认逐户派车行
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8087)
|
||||
> **PR**: #8583
|
||||
> **Issue**: #8556
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台派单看板订单详情端点 `GET /admin/fleet/board/orders/{orderId}`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **破坏性变更**:`actualVehicleCount` 字段的响应类型声明一直是 `Integer`(可空),但改动前的实现从未真正下发过 `null`——本次改动起,**团期子订单在团期配车事实暂不可用时会真实下发 `null`**(触发条件:`groupDispatchManaged=true` 且 `groupDispatchReady=false`)。前端若曾经把该字段当作恒为数字直接做算术或比较,现在必须先判空。
|
||||
- 新增 4 个字段:`groupBatchId`(当前归属运营团期 ID,非团期订单为 `null`)、`groupDispatchManaged`(用车是否由团期统一编排)、`groupDispatchReady`(团期配车事实是否已取到,`false` 含义是"未知"不是"没有车")、`groupDispatchPlan`(本单所在乘车分组的团级配车行只读列表)。
|
||||
- 团期子订单(`groupDispatchManaged=true`)且本地没有任何逐户派车行时,第 2 步"排车"(`code=DISPATCH`)的状态由 `WAITING` 改为 **`SKIPPED`**(`SKIPPED` 是该字段既有的合法取值,非新增枚举值);语义是"本步不由本单单独执行",不是"未排车"。
|
||||
- `actualVehicleCount` 的计算口径变化:团期子订单现在统计"本单逐户派车 ∪ 本单所在乘车分组的团级配车"去重后的车辆并集,不再只数逐户派车行。
|
||||
- 团期配车(团级统一编排)与逐户接送机派车可以**同时存在于同一张订单**,二者互不覆盖:`dailyVehiclePlan`(逐户派车)与 `groupDispatchPlan`(团级配车)各自独立返回,`currentAssignment` 仍然只指向逐户派车行。
|
||||
- 本次**只改了** `assignment == null`(本地无任何逐户派车行)这一分支的排车节点状态;订单若同时存在逐户派车行,排车节点继续如实反映那条真实派车行的状态,不受团期标记影响。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 派单看板订单详情 | GET | `/admin/fleet/board/orders/{orderId}` | 🔴 破坏性变更 + 字段新增 | 新增团期配车相关 4 字段,`actualVehicleCount` 可为 `null`,排车节点新增 `SKIPPED` 用法 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 派单看板订单详情 `GET /admin/fleet/board/orders/{orderId}`
|
||||
|
||||
**VO**: `(无请求体,仅路径参数)` → `BoardOrderDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务打开派单弹窗 Step1 查看当前订单详情时调用。本次改动解决团期子订单在本端点与团期配车总览端点给出相反结论的问题:团期已排车的订单此前在本端点被画成"未排车 / 0 辆"。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | ✅ | - | 订单 ID |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
以下是本次新增/变化的字段;其余既有字段(`dailyVehiclePlan`、`currentAssignment`、`progressSteps` 等)结构未变,此处不重复列出。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | Long→String,可空 | 当前归属运营团期 ID(非团期订单为 `null`);活体优先、order-v3 降级时回退派车行建行快照 |
|
||||
| groupDispatchManaged | Boolean,可空 | 用车是否由团期统一编排;`true`=排车节点为 `SKIPPED`,车辆事实见 `groupDispatchPlan` |
|
||||
| groupDispatchReady | Boolean,可空 | 团期配车事实是否已取到;`false`=未知(非团期基线不可达或该团尚无活跃用车需求),**不是**"没有车";非团期订单为 `null` |
|
||||
| actualVehicleCount | Integer,🔴 可空 | 车务当前实派车辆数(团期子订单含团级配车去重并集);`null`=团期配车事实暂不可用,前端不得按 `0` 渲染 |
|
||||
| groupDispatchPlan | `List<GroupDispatchPlanVO>` | 本单所在乘车分组的团级配车(只读展示,按服务日、配车行 ID 升序) |
|
||||
| ├─ dispatchId | Long→String | 团级配车行 ID(排障定位用,不作为任何写口入参) |
|
||||
| ├─ tripDate | LocalDate | 服务日 |
|
||||
| ├─ groupCode | String | 本单当日所在乘车分组键(`order_group_vehicle_group.group_code`) |
|
||||
| ├─ vehicleId | Long→String,可空 | 车辆 ID(团级配车允许未落车,此时为 `null`) |
|
||||
| ├─ vehiclePlate | String | 车牌 |
|
||||
| ├─ vehicleModel | String | 车型名 |
|
||||
| ├─ driverId | Long→String,可空 | 司机 ID(团级配车允许未落司机,此时为 `null`) |
|
||||
| ├─ driverName | String | 司机姓名 |
|
||||
| ├─ driverPhone | String | 司机手机(已脱敏) |
|
||||
| └─ status | String | 团级配车行状态(原样透出 `fleet_group_dispatch.status`,如 `ASSIGNED`/`CONFIRMED`) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders/2104840641597030402
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
真实实测(团期订单 A,团号 26-3682,仅团期配车、无个人派车行)关键字段:
|
||||
|
||||
```json
|
||||
{"code":200,"data":{"id":"HL20260929154809598","teamNo":"26-3682","groupBatchId":"2104840641651556353","groupDispatchManaged":true,"groupDispatchReady":true,"actualVehicleCount":1},"success":true}
|
||||
```
|
||||
|
||||
对照:非团期订单(团号 26-0013):
|
||||
|
||||
```json
|
||||
{"code":200,"data":{"id":"HL20260924152729671","teamNo":"26-0013","groupBatchId":null,"groupDispatchManaged":false,"groupDispatchReady":null},"success":true}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 非团期订单:`groupBatchId`/`groupDispatchManaged`/`groupDispatchReady` 均为 `null`/`false`,`groupDispatchPlan` 为空列表,`actualVehicleCount` 按逐户派车行正常计数(不受本次改动影响,不会是 `null`)。
|
||||
- 团期订单但团期配车事实取不到(`groupDispatchReady=false`,如 order-v3 团期基线不可达、或该团尚无活跃正式用车需求):`groupDispatchPlan=[]`,`actualVehicleCount=null`——前端应渲染为"团期配车信息暂不可用"这一类提示,不得退化显示为"未排车 / 0 辆"。
|
||||
- 团期分组已铺开但本单不在任何乘车分组(整团免车 / 本单自理):`groupDispatchReady=true` 且 `groupDispatchPlan=[]`,这是已验证的业务结论(本单不占团期用车),与上一条"未知"态不同,不要混为一谈。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本端点既有错误码未变:
|
||||
|
||||
```json
|
||||
{"code":605311,"message":"当前需求存在多个不透明派车方案代际","data":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `actualVehicleCount` 从本次起可以真实为 `null`:判定条件是 `groupDispatchManaged=true && groupDispatchReady=false`。非团期订单、以及团期配车已就绪的订单,该字段仍是非空整数。
|
||||
- 排车节点 `SKIPPED` 只出现在"本地无任何逐户派车行 + 团期统一编排"这一种情况;订单若同时有逐户派车行,排车节点继续如实反映那条派车行的真实状态(`WAITING`/`PROCESSING`/`DONE`/`CANCELED`),团期标记不覆盖它。
|
||||
- `groupDispatchReady=false` 的含义是"未知",不是"没有车";只有 `groupDispatchReady=true` 且 `groupDispatchPlan=[]` 才是"本单确实不占团期用车"这个已验证的业务结论。
|
||||
- `groupDispatchPlan` 是只读展示,团期用车的修改入口在团期配车总览页,不在本端点。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 响应 |
|
||||
|------|-----------------|
|
||||
| ✅ 渲染 `actualVehicleCount` 前先判空 | `null` 时渲染为"暂不可用",非 `null` 时按数字展示 |
|
||||
| ✅ 判断"本单是否不占团期用车" | 必须同时看 `groupDispatchReady===true && groupDispatchPlan.length===0`,不能只看 `groupDispatchPlan` 是否为空数组 |
|
||||
| ❌ 继续把 `actualVehicleCount` 当作恒不为空的数字直接参与计算 | 团期未就绪场景会拿到 `null`,直接参与算术会产生运行时异常 |
|
||||
| ❌ 把排车节点 `SKIPPED` 当作未知枚举值兜底处理 | `SKIPPED` 是 `AssignmentProgressStatusEnum` 既有取值,前端 `allowableValues` 已包含,无需新增分支兜底逻辑,但需要有对应的展示文案 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端渲染 `actualVehicleCount` 与排车进度节点前,必须先读 `groupDispatchManaged`/`groupDispatchReady` 两个标记决定展示分支;直接复用非团期订单的展示逻辑会在团期订单上产生误导性的"0 辆 / 未排车"提示。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本端点为只读查询,无数据库写操作。新增的团期配车事实来自跨服务只读查询:`groupDispatchManaged=true` 时才会额外发起一次到 order-v3 的 Feign 调用取团期基线,非团期订单不受影响、不多打这次调用。查询失败或该团无活跃需求时返回"未知"态(`groupDispatchReady=false`),不抛异常、不影响本端点其余字段的正常返回。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 非团期订单 → `groupBatchId`/`groupDispatchReady` 为 `null`,`groupDispatchManaged=false`,`groupDispatchPlan=[]`,`actualVehicleCount` 按逐户派车行正常计数
|
||||
- 团期订单、团期配车基线不可达或该团无活跃需求 → `groupDispatchReady=false`,`groupDispatchPlan=[]`,`actualVehicleCount=null`
|
||||
- 团期订单、团期分组已铺开但本单不在任何分组 → `groupDispatchReady=true`,`groupDispatchPlan=[]`,`actualVehicleCount` 按逐户派车行计数(可能为 0,这是已验证结论不是未知态)
|
||||
- 团期订单、团期配车已就绪且本单在某分组 → `groupDispatchReady=true`,`groupDispatchPlan` 非空,`actualVehicleCount` 为逐户 ∪ 团级去重后的并集大小
|
||||
- 本地无逐户派车行 + 团期统一编排 → 排车节点 `SKIPPED`
|
||||
- 本地有逐户派车行(不论是否团期订单)→ 排车节点如实反映该派车行状态,不受团期标记影响
|
||||
- 存量 `group_id` 为 `NULL` 的团级配车行(`V20260916_002` 迁移前落库、明确不回填)不进入本单的 `groupDispatchPlan`,但不报错、不影响其它行
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 排车进度节点状态(`AssignmentProgressStatusEnum`,`progressSteps[].status`,`code=DISPATCH` 这一步)
|
||||
|
||||
**所属字段**: `progressSteps[].status`(当 `progressSteps[].code=DISPATCH`) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 本次是否新增 | 说明 |
|
||||
|----|------|------|------|
|
||||
| `WAITING` | 等待中 | 既有值 | 非团期订单本地无派车行时的状态,本次未变 |
|
||||
| `PROCESSING` | 进行中 | 既有值 | 本次未变 |
|
||||
| `DONE` | 已完成 | 既有值 | 本次未变 |
|
||||
| `CANCELED` | 已取消 | 既有值 | 本次未变 |
|
||||
| `SKIPPED` | 已跳过 | 本次起用于排车节点 | 团期统一编排且本地无逐户派车行时的新用法;该取值本身已在 VO `allowableValues` 中存在(此前用于其它步骤),本次是新增了"排车"这一步会用到它 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | 不存在 | 新增,`Long→String`,可空 |
|
||||
| `groupDispatchManaged` | 不存在 | 新增,`Boolean`,可空 |
|
||||
| `groupDispatchReady` | 不存在 | 新增,`Boolean`,可空 |
|
||||
| `groupDispatchPlan` | 不存在 | 新增,`List<GroupDispatchPlanVO>`(10 个子字段,见出参字段表) |
|
||||
| `actualVehicleCount` | 字段声明类型一直是 `Integer`,但实现从未真正下发过 `null`(内部局部变量此前是不可空计算) | 团期子订单在配车事实未就绪时,Service 层真实计算出 `null` 并下发 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 团期子订单、本地无逐户派车行 | 排车节点 `WAITING`,`actualVehicleCount=0` | 排车节点 `SKIPPED`,`actualVehicleCount` 取团期配车去重实派车数(就绪时)或 `null`(未就绪时) |
|
||||
| 团期子订单、同时有逐户派车行 | 排车节点按该派车行真实状态 | 不变,仍按该派车行真实状态 |
|
||||
| `actualVehicleCount` 统计口径(团期子订单) | 只数本单逐户派车行 | 本单逐户派车 ∪ 本单所在乘车分组的团级配车,去重后的并集 |
|
||||
| 非团期订单 | 无本次描述的任何字段/行为 | 无变化(新增字段均为 `null`/`false`,`actualVehicleCount` 计算口径不变) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是——`actualVehicleCount` 的字面类型虽然一直是 `Integer`,但运行时从未观测到过 `null`;前端若曾经把它当作恒为数字的字段直接做算术/比较,现在会在团期未就绪场景下遇到真实的 `null`。
|
||||
- **前端是否必须同步上线**: 是(仅对涉及团期订单展示的场景)——非团期订单的响应字段与行为完全不变,可以不改;但只要页面会展示团期订单,就必须先对 `actualVehicleCount` 判空,并依据 `groupDispatchManaged`/`groupDispatchReady` 决定排车节点与实派车数的展示分支。
|
||||
- **前端 workaround 清理点**: 若此前为"团期订单详情显示未排车/0 辆,但团期配车总览显示已排车"这类矛盾现象写过特殊兼容或屏蔽逻辑,现在两端点结论已一致,可以确认不再需要。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 派单看板订单详情端点 `GET /admin/fleet/board/orders/{orderId}` 的响应字段与团期子订单的排车节点/实派车数展示逻辑。
|
||||
- **零影响**:
|
||||
- 派单看板列表端点 `GET /admin/fleet/board/orders`(`BoardOrderRecordVO`)未受本次改动波及
|
||||
- 非团期订单的响应字段与行为
|
||||
- 接送机步骤(`PICKUP_DROPOFF`)与确认执行步骤(`CONFIRM_EXECUTE`)的判定逻辑
|
||||
- `dailyVehiclePlan`(逐户派车方案)的既有字段结构与计算口径
|
||||
- 团期配车总览/就绪判定等团期配车域自身的写口与其余读口
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-fleet-service`,dev-v3 分支部署测试网关 @ `9c7ac9382`(含 #8556 所在提交),测试网关 `https://api.test.1814.love`;样本均为测试服现存真实业务数据:
|
||||
|
||||
```
|
||||
✓ 团期订单 A(orderId=2104840641597030402,团号 26-3682,团期批次 2104840641651556353,仅团期配车无个人派车):
|
||||
groupBatchId="2104840641651556353" groupDispatchManaged=true groupDispatchReady=true
|
||||
progressSteps[DISPATCH].status=SKIPPED statusLabel=已跳过 active=false
|
||||
actualVehicleCount=1;groupDispatchPlan 共 7 条(2026-11-11~2026-11-17)
|
||||
与同批次团期配车总览 GET /admin/fleet/group-dispatch/batches/2104840641651556353/overview 对照:
|
||||
vehicleReady=true,7 天 dispatched=true,结论一致(改前两端点结论相反)
|
||||
✓ 非团期订单 C(orderId=2103023501973848066,团号 26-0013):
|
||||
groupBatchId=null groupDispatchManaged=false groupDispatchReady=null
|
||||
✓ 团期订单 B(orderId=2104839654652121090,同时有团期配车与个人派车):
|
||||
groupDispatchManaged=true groupDispatchReady=true actualVehicleCount=2
|
||||
dailyVehiclePlan:1 条,车辆蒙C10E10/司机铁木尔(个人派车)
|
||||
groupDispatchPlan:多条,首条车辆蒙A-K1999/司机巴特尔(团期配车)
|
||||
两组数据同时非空、互不顶替;currentAssignment 仍指向个人派车行(蒙C10E10/铁木尔)
|
||||
```
|
||||
|
||||
注:`actualVehicleCount=null` 这一具体取值未在本轮实测中被真实触发(测试服 order-v3 全程可达,两个团期样本 `groupDispatchReady` 均为 `true`);该分支的契约(字段类型可空、触发条件 `groupDispatchManaged=true && groupDispatchReady=false`)已在源码逐一核实(`BoardOrderService.java`、`BoardOrderDetailVO.java`),前端应按此契约做防御性判空,不依赖本轮是否观测到该取值。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8556](https://git.1814.love/wx/HL/issues/8556)
|
||||
- 关联 PR: [wx/HL#8583](https://git.1814.love/wx/HL/pulls/8583)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8556](https://git.1814.love/wx/HL/issues/8556)
|
||||
- **PR**: [#8583](https://git.1814.love/wx/HL/pulls/8583)
|
||||
- **Merge commit**: [9c7ac93829](https://git.1814.love/wx/HL/commit/9c7ac93829)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,416 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8559"
|
||||
title: "团期子订单用车需求记录:countedHouseholdCount / countedInSummary 不再随 kind 筛选归零"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8612 已 squash 合并 dev-v3(3ecf387979),hl-order-service-v3 dev-v3 分支已滚测试服。本条只改取值语义,字段名、字段个数、HTTP 形态全部不变。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3: 团期子订单用车需求记录的「计入汇总」读数不再随 kind 筛选归零
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8612
|
||||
> **Issue**: #8559
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期「查看需求」Tab 用车板块逐户明细端点 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` 的两个取值(顶层 `countedHouseholdCount`、每户 `households[].countedInSummary`)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **这是取值语义纠错,不是字段变更**:`countedHouseholdCount` 和 `households[].countedInSummary` 的**字段名、类型、位置全部没动**,变的是它们在带 `kind` 筛选时返回的**值**。
|
||||
- **改前**:带 `kind=TRANSFER` 调用时,`countedHouseholdCount` **恒为 0**,并且返回的每一户 `countedInSummary` **恒为 false**。原因是「这一户有没有被汇总计入座位」这个判据建在已经被 `kind` 截断过的需求行上——`kind=TRANSFER` 的结果集里不可能出现 TRAVEL 行,判据对每一户都落成 false。而团级汇总里那些户的座位是实实在在加进去的,同一页面两块数据互相矛盾。
|
||||
- **改后**:判据改成「这一户在**全部活跃需求行**里有没有 TRAVEL 行」,与本次 `kind` 筛选无关。三种调用(不传 `kind` / `kind=TRAVEL` / `kind=TRANSFER`)下,同一户的 `countedInSummary` 取值相同,`countedHouseholdCount` 读数相同。
|
||||
- **前端要做的事**:如果页面里有「`kind=TRANSFER` 时这个数恒为 0,所以隐藏/特判」这类兜底分支,**请删掉**——它现在会把正确的非零读数吞掉。另外不要再用「`countedInSummary` 全 false」去推断当前处于接送机筛选态。
|
||||
- **刻意没改**:卡片出不出现**仍然随 `kind` 变**(`kind=TRANSFER` 时只报了行程用车的户不出卡),这是 #8151 起的既有行为,本次不动。
|
||||
- **刻意没改**:`householdCount`(应报车户数)与 `vehicleRowCount`(需求行数)的口径,本次一个字都没动。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期「查看需求」Tab 的用车板块是上下两块:上面是团级汇总,下面是逐户明细。逐户明细支持按 `kind` 切换筛选(行程用车 / 接送机 / 全部)。`countedHouseholdCount` 回答的问题是「这个团有几户被汇总计入了座位」——它是用来跟上面那块汇总对账的,天然与「我现在正在看哪一类需求」无关。
|
||||
|
||||
| 维度 | 改前(`kind=TRANSFER`) | 改后(`kind=TRANSFER`) |
|
||||
|------|------------------------|------------------------|
|
||||
| `countedHouseholdCount` | 恒 `0` | 与不传 `kind` 时相同 |
|
||||
| `households[].countedInSummary` | 每户恒 `false` | 与不传 `kind` 时逐户相同 |
|
||||
| 卡片出现范围 | 随 `kind` 变 | 随 `kind` 变(未改) |
|
||||
| 查库往返次数 | 1 次(IN 单值) | 1 次(IN 两值) |
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期子订单用车需求记录 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` | 取值语义修正 | `countedHouseholdCount` 与 `households[].countedInSummary` 不再随 `kind` 筛选归零 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期子订单用车需求记录 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households`
|
||||
|
||||
**VO**: `GroupVehicleHouseholdsRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情「查看需求」Tab 的用车板块下半部分(逐户明细列表)。进入 Tab 时前端默认**不传** `kind`(两类都返);用户点「行程用车 / 接送机」切页签时带上 `kind`。本端点只读,无副作用。权限点 `group-batch:view`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | 团期主订单 ID | 团期不存在时抛团期未找到错误 |
|
||||
| `kind` | Query | String | ❌ | `TRAVEL` / `TRANSFER`,不传或空白 = 两类都返 | 其它取值抛 809000「用车需求类别非法」。注意与提交侧「不传按 TRAVEL」的缺省刻意相反 |
|
||||
|
||||
#### 出参 `Result<GroupVehicleHouseholdsRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | String | 团期主订单 ID(雪花,序列化为字符串) |
|
||||
| `departDate` | String(`yyyy-MM-dd`) | 团期出发日,可为 null |
|
||||
| `endDate` | String(`yyyy-MM-dd`) | 团期结束日,与团期详情同源,可为 null |
|
||||
| `householdCount` | Integer | 应报车户数 = `households` 数组长度(含一份需求都没提交的空卡) |
|
||||
| `vehicleRowCount` | Integer | 需求行数 = 各户 `requirements` 长度之和;**可以小于 `householdCount`**,不要当不变式用 |
|
||||
| `countedHouseholdCount` | Integer | 🔴 被汇总计入座位的户数 = `households` 中 `countedInSummary=true` 的条数。**不随 `kind` 筛选变化**,三种筛选读数相同。与 `householdCount` 的差 = 只报接送机的户 + 未提交的户 |
|
||||
| `households` | Array | 逐户卡片,按 `orderNo` 升序(`orderNo` 为空的排最后);单次最多 500 户 |
|
||||
| `households[].orderId` | String | 子订单 ID(雪花,序列化为字符串) |
|
||||
| `households[].orderNo` | String | 子订单号 |
|
||||
| `households[].teamNo` | String | 团号,可为 null(不用订单号顶替) |
|
||||
| `households[].customerName` | String | 客户姓名 |
|
||||
| `households[].participantCount` | Integer | 出行人数 |
|
||||
| `households[].consultantId` | String | 定制师 adminId,未指派为 null |
|
||||
| `households[].consultantName` | String | 定制师姓名,未指派为 null |
|
||||
| `households[].countedInSummary` | Boolean | 🔴 该户是否被团级汇总计入座位。**不随 `kind` 筛选变化**,同一户三种筛选下取值相同 |
|
||||
| `households[].status` | String | 展示用需求状态;该户一份需求都没提交时为 null |
|
||||
| `households[].statusName` | String | 状态中文名,`status` 为 null 时为 null |
|
||||
| `households[].requirements` | Array | 该户活跃需求行,0~2 条;无行时为**空数组**不是 null |
|
||||
| `households[].requirements[].requirementId` | String | 需求行 ID(雪花,序列化为字符串) |
|
||||
| `households[].requirements[].kind` | String | `TRAVEL` / `TRANSFER` |
|
||||
| `households[].requirements[].kindName` | String | 类别中文名 |
|
||||
| `households[].requirements[].status` | String | `PENDING_REVIEW` / `PENDING` / `PROCESSING` / `DONE` |
|
||||
| `households[].requirements[].statusName` | String | 状态中文名 |
|
||||
| `households[].requirements[].fleet` | Array | 车型项:`vehicleType` / `vehicleTypeName` / `seats` / `count` |
|
||||
| `households[].requirements[].specialTags` | Array | 特殊诉求标签:`code` / `name` |
|
||||
| `households[].requirements[].remark` | String | 备注,≤500,可为 null |
|
||||
| `households[].requirements[].serviceDates` | Array\<String\> | 服务日期(`yyyy-MM-dd`) |
|
||||
| `households[].requirements[].headcount` | Integer | 用车人数 |
|
||||
| `households[].requirements[].totalSeatCount` | Integer | 总座位数 |
|
||||
| `households[].requirements[].remainingPassengerSeats` | Integer | 余座(扣司机位后) |
|
||||
| `households[].requirements[].pickupRequired` | Boolean | **仅 TRANSFER 行有值**,TRAVEL 行为 null |
|
||||
| `households[].requirements[].dropoffRequired` | Boolean | **仅 TRANSFER 行有值**,TRAVEL 行为 null |
|
||||
| `households[].requirements[].returnRemark` | String | 打回备注,可为 null |
|
||||
| `households[].requirements[].returnedAt` | String(date-time) | 打回时刻,可为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2101690789438570497/requirement/vehicle-households?kind=TRANSFER HTTP/1.1
|
||||
Host: <admin-gateway>
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2101690789438570497",
|
||||
"departDate": "2026-09-12",
|
||||
"endDate": "2026-09-16",
|
||||
"householdCount": 3,
|
||||
"vehicleRowCount": 1,
|
||||
"countedHouseholdCount": 2,
|
||||
"households": [
|
||||
{
|
||||
"orderId": "2102000000000000001",
|
||||
"orderNo": "HL20260912100000001",
|
||||
"teamNo": "26-0480",
|
||||
"customerName": "陈昊",
|
||||
"participantCount": 4,
|
||||
"consultantId": "10001",
|
||||
"consultantName": "李四",
|
||||
"countedInSummary": true,
|
||||
"status": "PENDING",
|
||||
"statusName": "待车队配",
|
||||
"requirements": [
|
||||
{
|
||||
"requirementId": "2103000000000000011",
|
||||
"kind": "TRANSFER",
|
||||
"kindName": "接送机",
|
||||
"status": "PENDING",
|
||||
"statusName": "待车队配",
|
||||
"fleet": [
|
||||
{ "vehicleType": "suv", "vehicleTypeName": "SUV", "seats": 7, "count": 1 }
|
||||
],
|
||||
"specialTags": [
|
||||
{ "code": "child_seat", "name": "儿童安全座椅" }
|
||||
],
|
||||
"remark": null,
|
||||
"serviceDates": ["2026-09-12"],
|
||||
"headcount": 4,
|
||||
"totalSeatCount": 7,
|
||||
"remainingPassengerSeats": 2,
|
||||
"pickupRequired": true,
|
||||
"dropoffRequired": false,
|
||||
"returnRemark": null,
|
||||
"returnedAt": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"orderId": "2102000000000000002",
|
||||
"orderNo": "HL20260912100000002",
|
||||
"teamNo": "26-0480",
|
||||
"customerName": "王琳",
|
||||
"participantCount": 2,
|
||||
"consultantId": "10001",
|
||||
"consultantName": "李四",
|
||||
"countedInSummary": true,
|
||||
"status": null,
|
||||
"statusName": null,
|
||||
"requirements": []
|
||||
},
|
||||
{
|
||||
"orderId": "2102000000000000003",
|
||||
"orderNo": "HL20260912100000003",
|
||||
"teamNo": null,
|
||||
"customerName": "赵敏",
|
||||
"participantCount": 3,
|
||||
"consultantId": null,
|
||||
"consultantName": null,
|
||||
"countedInSummary": false,
|
||||
"status": null,
|
||||
"statusName": null,
|
||||
"requirements": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 上例即改后行为:`kind=TRANSFER` 筛选下仍然有 `countedHouseholdCount=2`(前两户各有一条活跃 TRAVEL 行,被团级汇总计入了座位),只有第三户没报行程用车所以是 `false`。改前这三户的 `countedInSummary` 会全是 `false`、`countedHouseholdCount` 会是 `0`。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团期下没有在团子订单(全部退团 / 取消)时,三个计数全 `0`、`households` 为**空数组**(不是 null):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2101690789438570497",
|
||||
"departDate": "2026-09-12",
|
||||
"endDate": "2026-09-16",
|
||||
"householdCount": 0,
|
||||
"vehicleRowCount": 0,
|
||||
"countedHouseholdCount": 0,
|
||||
"households": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
单次返回的户数上限为 500 户,超过时**截断**返回前 500 条并在服务端留 warn,不报错——页面仍可用。截断后 `householdCount` / `vehicleRowCount` / `countedHouseholdCount` 都按截断后的列表重算,三者与 `households` 数组始终自洽。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`kind` 传了 `TRAVEL` / `TRANSFER` 以外的值:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809000,
|
||||
"message": "用车需求类别非法:BUS",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:需要权限点 `group-batch:view`;未登录由网关拦截返 401。
|
||||
- **只读**:本端点零写入,重复调用无副作用,可安全轮询。
|
||||
- **户范围**:在团 = 仅排除 CANCELLED。退团 / 取消的户不出现在列表里。
|
||||
- **只取活跃行**:被打回的需求行已失活,**不在本列表内**(与用房侧「列出打回户」的行为刻意不同)。因此一户被打回后在这里表现为 `requirements: []` 的空卡,而不是灰条。
|
||||
- **卡片范围仍随 `kind` 变**:卡片集合 = 「应报车的户」∪「本次筛选命中需求行的户」。这一条本次未改。
|
||||
- **`countedInSummary` 不随 `kind` 变**:它读的是该户在**全部**活跃行里有没有 TRAVEL 行。
|
||||
- **`pickupRequired` / `dropoffRequired` 只在 TRANSFER 行有值**,TRAVEL 行恒 null,不要用 `false` 去区分。
|
||||
- **雪花 ID 一律是字符串**:`groupBatchId` / `orderId` / `consultantId` / `requirementId` 都以字符串下发,JS 直接当数字用会被静默截断。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受 / 拒绝请求的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误调用对照
|
||||
|
||||
| 场景 | 请求 |
|
||||
|------|------|
|
||||
| ✅ 进 Tab 默认拉全部 | `GET .../vehicle-households`(不带 `kind`) |
|
||||
| ✅ 切到行程用车页签 | `GET .../vehicle-households?kind=TRAVEL` |
|
||||
| ✅ 切到接送机页签 | `GET .../vehicle-households?kind=TRANSFER` |
|
||||
| ✅ 显式传空值 | `GET .../vehicle-households?kind=`(空白按「两类都返」处理) |
|
||||
| ❌ 传车型当类别 | `GET .../vehicle-households?kind=bus` → 809000 |
|
||||
| ❌ 传小写类别 | `GET .../vehicle-households?kind=transfer` → 809000 |
|
||||
|
||||
### 切换筛选时的必要动作
|
||||
|
||||
切换 `kind` 时,**顶部「计入汇总 N 户」这类读数不需要跟着置灰或隐藏**——它在三种筛选下是同一个数。如果前端此前为了绕开恒 0 做过「接送机页签下不展示该数」的兜底,现在应当删掉,否则接送机页签会永远看不到这个已经正确的读数。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本端点是 **GET 只读**,零写入:不建行、不改行、不软删、不产生任何 outbox / MQ 事件。
|
||||
|
||||
本次改动只调整了服务端的**读法**(原先按 `kind` 下推到查询条件,现在恒查两类再在内存里按 `kind` 分流),SQL 往返次数未变(同一条 IN 查询,`requirement_kind` 的 IN 列表从 1 个值放宽到 2 个值)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 权限点缺失 → 权限校验失败,不返回数据。
|
||||
- 团期 ID 不存在 → 抛团期未找到业务错误,HTTP 200 + 业务错误码。
|
||||
- 团期下无在团子订单 → 三个计数 0 + `households: []`,不报错。
|
||||
- 户数超 500 → 截断到前 500 条,三个计数按截断后重算,HTTP 200。
|
||||
- 在团订单 ID 存在但订单行缺失(脏数据)→ 跳过该户并在服务端留 warn,整页仍可打开。
|
||||
- 需求行 `requirement_kind` 为空的脏行 → 显式跳过,不进任何计数。
|
||||
- 老数据兼容:历史需求行缺 `seats` / `count` 等字段时对应位为 null,不异常。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### kind(用车需求类别,`VehicleRequirementKind`)
|
||||
|
||||
**所属字段**: 请求 Query `kind`、响应 `households[].requirements[].kind` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `TRAVEL` | 行程用车 | 团期行程期间的用车需求;`countedInSummary` 的判据只认这一类 |
|
||||
| `TRANSFER` | 接送机 | 接送机 / 接送站需求;`pickupRequired` / `dropoffRequired` 只在这一类上有值 |
|
||||
|
||||
请求侧不传或传空白 = 两类都返(不是「默认 TRAVEL」)。
|
||||
|
||||
### status(需求行状态)
|
||||
|
||||
**所属字段**: `households[].status`、`households[].requirements[].status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING_REVIEW` | 待审核 | 定制师已提交,等团期管理员下发车务 |
|
||||
| `PENDING` | 待车队配 | 已下发车务,等车队配车 |
|
||||
| `PROCESSING` | 配车中 | 车务处理中 |
|
||||
| `DONE` | 配车完成 | 已完成 |
|
||||
|
||||
户级 `status` 为 null 表示该户一份活跃需求都没有(空卡)。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `countedHouseholdCount` | 字段存在,类型 Integer;`kind=TRANSFER` 时恒 `0` | 字段、类型不变;三种筛选读数相同 |
|
||||
| `households[].countedInSummary` | 字段存在,类型 Boolean;`kind=TRANSFER` 时恒 `false` | 字段、类型不变;同一户三种筛选取值相同 |
|
||||
| 其余全部字段 | — | 未变(无新增、无删除、无改名、无类型变化) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 不传 `kind` 时的两个读数 | 正确 | 正确(未变) |
|
||||
| `kind=TRAVEL` 时的两个读数 | 正确 | 正确(未变) |
|
||||
| `kind=TRANSFER` 时的两个读数 | `countedHouseholdCount=0`、每户 `countedInSummary=false` | 与不传 `kind` 时一致 |
|
||||
| 「计入汇总」的判据数据源 | 已被 `kind` 截断的需求行 | 该户的全部活跃需求行 |
|
||||
| 卡片是否随 `kind` 变 | 变 | 变(未改) |
|
||||
| `householdCount` / `vehicleRowCount` | — | 未变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否(无字段增删改名;只是 `kind=TRANSFER` 时的取值由恒 0 / 恒 false 变为真实值)
|
||||
- **前端是否必须同步上线**: 否(不改也能正常渲染,只是接送机页签下该数从「永远 0」变成真实值)
|
||||
- **前端 workaround 清理点**: 若页面里有「`kind=TRANSFER` 时 `countedHouseholdCount` 恒 0,所以隐藏该数 / 走另一套算法 / 用 `countedInSummary` 全 false 判断当前筛选态」这类兜底分支,**删掉它们**——保留会吞掉正确读数或做出错误的筛选态判断。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` 的 `countedHouseholdCount` 与 `households[].countedInSummary` 两个取值。
|
||||
- **零影响**:
|
||||
- 团级用车汇总端点(本次改的是明细侧读法,汇总侧一个字没动)
|
||||
- 团期正式用车需求的存 / 读 / 撤回 / 免车四个端点
|
||||
- 用房侧 `requirement/hotel-households`
|
||||
- 单户用车需求的提交、下发、打回链路
|
||||
- 团期配车(fleet 侧)任何端点
|
||||
- 历史数据:本次只改读法,不做任何数据迁移
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||||
- 合并提交 `3ecf387979`(PR #8612 squash 合并进 `dev-v3`)。
|
||||
- `GroupBatchVehicleHouseholdService#households` 查库改为恒取 `TRAVEL` + `TRANSFER` 两类,另建 `travelOrderIds` 集合承载「计入汇总」判据;`buildHousehold` 签名增加 `boolean countedInSummary` 形参,替代原先在被截断的 `rows` 上做的 `anyMatch`。
|
||||
- `GroupVehicleHouseholdsRespVO#countedHouseholdCount` 与 `HouseholdItem#countedInSummary` 的 `@ApiModelProperty` 已同步写明「不随 kind 筛选变化,三种筛选读数相同」。
|
||||
- 卡片集合仍取自按 `kind` 过滤后的 `rowsByOrder`(既有行为,未改)。
|
||||
- **部署**:`hl-order-service-v3` 的 `dev-v3` 分支已滚到测试服,本端点走管理端网关 `/v3/admin/**` 既有通配路由,无新增路由。
|
||||
|
||||
```
|
||||
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households → 200 ✓
|
||||
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRAVEL → 200 ✓
|
||||
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRANSFER → 200 + countedHouseholdCount 与前两次一致 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #8151 | 首次提供本端点(逐户明细 + `kind` 筛选) | ✅ 有效 |
|
||||
| — | #8195 | 缺陷 2:`householdCount` 改为「应报车户数」含空卡;缺陷 5:`endDate` 与团期详情同源 | ✅ 有效 |
|
||||
| **本 PR #8612** | **#8559** | 「计入汇总」判据改为不受 `kind` 截断 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8559](https://git.1814.love:8443/wx/HL/issues/8559)
|
||||
- 关联 PR: [wx/HL#8612](https://git.1814.love:8443/wx/HL/pulls/8612)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8559](https://git.1814.love:8443/wx/HL/issues/8559)
|
||||
- **PR**: [#8612](https://git.1814.love:8443/wx/HL/pulls/8612)
|
||||
- **Merge commit**: [3ecf387979](https://git.1814.love:8443/wx/HL/commit/3ecf387979)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,237 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8560"
|
||||
title: "矩阵未派订单清单响应新增用车需求类别字段,同订单双需求两张卡可区分"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "8d23a3815ee13e252eba253a32e2b6e7756ef220"
|
||||
target_release: ""
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8589 合并 dev-v3(8b045a321b);测试网关部署确认:hl-fleet-service 现部署 @ 99fb369ba(deploy-status.sh 实测,状态 ok,该 SHA 经 git merge-base --is-ancestor 确认已包含 8b045a321b)。GET /admin/fleet/matrix/unassigned-orders 已用车务角色测试账号实测:2026-09 月拿到 TRAVEL 示例(订单 HL20260911193207642)、2026-11 月拿到 TRANSFER 示例(订单 HL20260929154809598),均为测试服真实响应;对 2026-06~2027-03 共 10 个月窗口扫描未发现 requirementKind=null 或同订单双卡的活跃实例,这两种边界行为当前仅由单元测试覆盖(BoardRequirementIdentitiesKindTest 5/5、MatrixServiceTest 新增 6 个 #8560 方法),尚未在测试服活数据上复现。;前端已交付:矩阵主窗未派池卡与分窗甘特条加类别标签(null 不渲染不兜底 TRAVEL),适配层显式透传,spec 6 例全绿(hl-admin 8d23a381)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service:矩阵未派订单清单响应新增用车需求类别字段,同订单双需求两张卡可区分
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #8589
|
||||
> **Issue**: #8560
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 1 个只读端点响应新增 2 字段(矩阵未派订单清单)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- `GET /admin/fleet/matrix/unassigned-orders` 响应每条记录新增 `requirementKind`(`TRAVEL`/`TRANSFER`/`null`)与 `requirementKindLabel`(`行程用车`/`接送机`/`null`),两者恒成对(一个为 null 另一个必为 null)。
|
||||
- 背景(#7439):同一订单可并存两条活跃用车需求(行程用车 TRAVEL + 接送机 TRANSFER),车务分别对两者派车,未派池会出现同一订单的两张卡。此前两张卡除车型/日期外没有任何字段能分辨谁是哪一类——既有字段 `vehicleCategory`/`categoryLabel` 是车型(suv/bus)不是需求类别。新增这两个字段就是用来分辨这两张卡的。
|
||||
- 🔴 **`null` 不兜底成 `TRAVEL`**,这是本次修复的核心边界。判不出类别(跨服务降级 context=null;或派车行挂着 #5720 换版过渡窗里的上一版 `requirement_id`,命中不了任何当前活跃身份;或命中的身份自身 `kind` 为空白)时两个新字段均为 `null`,前端应不显示类别标签,**禁止自行按业务猜测补默认值**——尤其禁止把 `null` 当 `TRAVEL` 处理。
|
||||
- 与看板列表(`BoardOrderRecordVO.requirementKind`,#8518 既有)**在判不出这一档口径不同**:看板列表的解析方法判不出时兜底返 `TRAVEL`(那里类别同时是筛选维度,返空会让卡片从筛选后的视图里彻底消失);矩阵未派卡判不出时返 `null`(那里类别只是展示标签,车务会照标签去排完全不同的活,标错比不标更危险)。**同一张实体卡在两个入口可能显示不一致的类别信息,这是刻意保留的差异**,不是缺陷。
|
||||
- 真实未派行与虚拟待派条目(`virtualPending=true`,#7067)两类条目都携带这两个新字段,取值口径一致。
|
||||
- 类别取的是**这张卡自身所属需求**(真实行用该行自己的 `requirement_id`,虚拟条目用该候选自己的 `requirementId`)解析出的类别,**不是**已有字段 `requirementId`(该字段取「订单侧单值」,#5667 口径,同一订单两类需求并存时恒指向身份列表首项、即恒为 TRAVEL 那条)。⚠️ `requirementId` 字段两张卡是否相同**取决于这张卡走哪条路径**:`virtualPending=true`(未派池的虚拟待派条目,未派订单最常见的形态)下它取该候选自身的需求 ID,两张卡**不相同**;`virtualPending=false`(已有派车行的真实行)下它优先取订单侧单值,两张卡**相同**。两条路径都不能拿 `requirementId` 判类别——相同时它分辨不出,不同时它也只是碰巧对得上。判类别一律只认 `requirementKind`/`requirementKindLabel`。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 矩阵未派订单清单 | GET | `/admin/fleet/matrix/unassigned-orders` | 字段新增(非破坏性) | 响应新增 `requirementKind`/`requirementKindLabel` |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 矩阵未派订单清单 `GET /admin/fleet/matrix/unassigned-orders`
|
||||
|
||||
**VO**: `MatrixUnassignedReqVO → List<MatrixUnassignedOrderVO>`
|
||||
|
||||
#### 使用场景
|
||||
派单矩阵页右面板「未派订单窗口」,车务从此列表拖拽卡片到左面板某车某日完成派车;同一订单若同时有行程用车与接送机两条活跃需求,会在此列表出现两张卡,车务需要靠新增的类别字段区分要往哪类需求上派车,不能再靠车型/日期/备注这类间接信息猜。
|
||||
|
||||
#### 入参字段表
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| year | query | Integer | 是 | 2020-2100,越界返 605076 | 年份 |
|
||||
| month | query | Integer | 是 | 1-12,越界返 605010 | 月份 |
|
||||
| typeKeys | query | String[] | 否 | 取值 suv/mpv/bus/sedan,规范小写 | 车型大类多选,空=全部;对虚拟待派条目按当前需求车型明细任一项归一后命中过滤 |
|
||||
|
||||
#### 出参字段表(仅列本次新增字段及理解其语义所需的上下文字段,VO 全量共 39 个字段)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementKind | String | **新增**。用车需求类别:`TRAVEL`=行程用车 / `TRANSFER`=接送机 / `null`=判不出(不兜底为 TRAVEL) |
|
||||
| requirementKindLabel | String | **新增**。类别中文名:`行程用车`/`接送机`/`null`,与 requirementKind 恒成对 |
|
||||
| requirementId | Long(字符串序列化) | 既有字段,这张卡对应的用车需求 ID。`virtualPending=false` 时优先取订单侧单值(#5667,两类并存时恒指向 TRAVEL 那条);`virtualPending=true` 时取该候选自身的需求 ID。**两条路径取值口径不同,一律不能用它推导 requirementKind** |
|
||||
| assignmentId | Long(字符串序列化) | 既有字段,本行唯一主键;虚拟待派条目为 null |
|
||||
| virtualPending | Boolean | 既有字段,true=虚拟待派条目(零派车行订单,按需求上下文补出) |
|
||||
| vehicleCategory | String | 既有字段,规范小写车型 key(suv/mpv/bus/sedan),与需求类别是两个不同维度 |
|
||||
| categoryLabel | String | 既有字段,车型中文标签(恒非 null) |
|
||||
| orderId / orderNo | String | 既有字段,订单号 |
|
||||
| teamNo | String | 既有字段,团号 |
|
||||
|
||||
#### 请求示例
|
||||
```http
|
||||
GET /admin/fleet/matrix/unassigned-orders?year=2026&month=11&typeKeys=mpv
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
实测取自测试服真实数据(2026-11 月,TRANSFER 示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"orderId": "HL20260929154809598",
|
||||
"orderNumericId": "2104840641597030402",
|
||||
"orderNo": "HL20260929154809598",
|
||||
"teamNo": "26-3682",
|
||||
"virtualPending": true,
|
||||
"assignmentId": null,
|
||||
"assignmentGroupId": null,
|
||||
"requirementId": "2104844928733548545",
|
||||
"requirementKind": "TRANSFER",
|
||||
"requirementKindLabel": "接送机",
|
||||
"vehicleCategory": "mpv",
|
||||
"categoryLabel": "商务车",
|
||||
"customerName": "董海涛",
|
||||
"headcount": 2,
|
||||
"headcountLabel": "2大",
|
||||
"startDate": "2026-11-11",
|
||||
"endDate": "2026-11-17",
|
||||
"pickupAt": "阿尔山伊尔施机场",
|
||||
"dropoffAt": "阿尔山伊尔施机场",
|
||||
"assignmentStatus": "unassigned",
|
||||
"urgentBadge": null,
|
||||
"vehicleAdvice": null,
|
||||
"parallelAssignments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
对照:2026-09 月同一账号实测取到的 TRAVEL 示例(订单 `HL20260911193207642`),响应结构完全相同,仅 `requirementKind="TRAVEL"`、`requirementKindLabel="行程用车"`、`requirementId="2098950695167148034"`。两个示例均为测试服真实取数,未做任何字段改写。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
- 当月无未派条目:`data: []`,非错误。
|
||||
- `vehicleAdvice` 恒为 `null`(M2 数据源未建,既有降级行为,与本次改动无关)。
|
||||
- Nacos `fleet.board.virtual-candidates-enabled=false` 或 order-v3 候选服务不可用时:只丢虚拟待派条目,真实未派行原样返回(fail-open);真实行的 `requirementKind` 解析走独立的上下文查询,不受此开关影响。
|
||||
- `requirementKind`/`requirementKindLabel` 判不出时为 `null`(见「⚠️ 关键变化」),这不是接口异常,是正常的降级取值,前端应按无标签渲染,不得折算为 `TRAVEL`。
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 605010,
|
||||
"message": "月份超出范围",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"code": 605076,
|
||||
"message": "年份超出范围(仅支持 2020-2100 年)",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
- `100001` 参数非法:year/month 缺失(框架校验)。
|
||||
- `401` 未登录。
|
||||
|
||||
#### 业务边界
|
||||
- **同一订单两类需求并存时,两张卡的 `requirementKind`/`requirementKindLabel` 必不相同;而 `requirementId` 字段是否相同取决于路径**——真实行(`virtualPending=false`)下两张卡相同(均取订单侧单值),虚拟待派条目(`virtualPending=true`)下两张卡各取自身需求 ID、并不相同。单元测试 `MatrixServiceTest#queryUnassignedOrders_orderWithBothKinds_twoCardsCarryDifferentKinds` 断言的是**真实行**那条路径。两条路径都不能拿 `requirementId` 反推类别,前端也不能这么做。
|
||||
- `requirementKind=null` 时前端**禁止**折算成 `TRAVEL`;这既是判不出的真实状态,也是修复前的错误行为,回退等于复发。
|
||||
- 矩阵未派卡与看板列表对同一张孤儿行(#5720 换版过渡窗)的类别展示口径不同(前者 null、后者兜底 TRAVEL),这是刻意保留的差异,不要据此判断某一端有 bug。
|
||||
- 真实未派行与虚拟待派条目两种类型都下发这两个字段,前端不需要按 `virtualPending` 分支处理类别逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 判类别只认 `requirementKind`/`requirementKindLabel` 这两个新字段,不要用 `requirementId` 做二次推导。
|
||||
- `requirementKind` 取值集合当前为 `{TRAVEL, TRANSFER, null}`,前端不应写死「非 TRANSFER 即 TRAVEL」的二值判断——若未来 order-v3 新增第三类需求,后端会同步扩展该字段取值与中文映射,二值判断会把新类别误标成 TRAVEL。
|
||||
- 类别中文名由后端下发,前端不需要、也不应该自行维护 `TRAVEL`/`TRANSFER` 到中文的映射表。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
无数据库结构变更。本次改动只是查询层新增两次内存解析(基于已查出的订单需求上下文按 `requirement_id` 匹配),不新增表、不新增列、不新增索引,无 Flyway 迁移。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 上下文降级(跨服务 Feign 调用失败,`OrderFleetBoardContextDTO` 为 `null`):类别字段为 `null`。
|
||||
- 派车行/候选自身的 `requirement_id` 命中不到订单当前任何活跃需求身份(#5720 换版过渡窗孤儿行):类别字段为 `null`,不回退用订单上下文单值猜测。
|
||||
- 命中的身份自身 `kind` 字段为空白:类别字段为 `null`(防御性分支;order-v3 当前写路径恒写枚举 `.name()`,正常不触发,仅覆盖历史/异常数据)。
|
||||
- 以上三种 `null` 场景均只有单元测试覆盖(见八节),本次实测扫描未在测试服活数据中观测到对应真实记录。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
| 取值 | 中文标签 | 说明 |
|
||||
|------|----------|------|
|
||||
| TRAVEL | 行程用车 | 行程用车需求 |
|
||||
| TRANSFER | 接送机 | 接送机需求(#7439 引入) |
|
||||
| null | (不显示标签) | 判不出类别,前端不得兜底为 TRAVEL |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
- **字段层面**:`MatrixUnassignedOrderVO` 新增 `requirementKind`(String)、`requirementKindLabel`(String),VO 字段总数由 37 增至 39。
|
||||
- **行为层面**:改动前,同一订单的两张未派卡在字段层面完全无法区分类别,只能靠车型/日期/备注人工判断,判断错了会把车派到错误的需求线上;改动后两张卡各自携带准确的类别标识,且判不出时明确返回 null 而非静默给出错误猜测。
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- 破坏性:无。两个新增字段为可选新增,未删除/未重命名/未改变任何既有字段的类型或取值口径。
|
||||
- 涉及消费端:仅管理后台派单矩阵页。
|
||||
- 前端无需为此做兼容降级处理:未取到新字段(`undefined`)与取到 `null` 应做同等处理——均不显示类别标签。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 矩阵主数据端点 `GET /admin/fleet/matrix/grid`、年度月度统计 `GET /admin/fleet/matrix/month-counts`、当天订单清单 `GET /admin/fleet/matrix/day-orders`:均未改动。
|
||||
- 看板列表端点(`BoardOrderRecordVO.requirementKind`,#8518):未改动,其判不出类别时仍兜底 TRAVEL 的既有行为不变。
|
||||
- 写操作(拖拽派车、改派、取消等):本次改动只涉及查询响应字段新增,不涉及任何写路径。
|
||||
- `MatrixUnassignedReqVO` 请求参数:未新增/未修改(year/month/typeKeys 均为既有字段,越界错误码路由此前已分别由 #8561/#8571 调整完成)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **部署确认**:`hl-fleet-service` 现部署 SHA `99fb369ba`(`deploy-status.sh` 实测,状态 `ok`),经 `git merge-base --is-ancestor 8b045a321b 99fb369ba8` 确认已包含本次改动的合并提交 `8b045a321b`(PR #8589)。
|
||||
- **实测(真实请求,非构造数据)**:使用车务角色测试账号(切至 VEHICLE_MANAGER 角色)对 `GET /admin/fleet/matrix/unassigned-orders` 发起真实请求:
|
||||
- 2026-09 月:2 条记录,`requirementKind` 均为 `TRAVEL`,含示例订单 `HL20260911193207642`(见响应示例节)。
|
||||
- 2026-11 月:1 条记录,`requirementKind` 为 `TRANSFER`,订单 `HL20260929154809598`(见响应示例节)。
|
||||
- 对 2026-06 ~ 2027-03 共 10 个月窗口的扫描(合计 14 条记录)未发现 `requirementKind=null` 的记录,也未发现同一订单出现两条不同类别记录的活跃实例——测试服当前业务数据里暂未出现这两种边界场景,实测未覆盖,靠下面的单元测试兜底。
|
||||
- **单元测试覆盖(源码单测验证,未在测试服活数据上复现)**:
|
||||
- `BoardRequirementIdentitiesKindTest`(5/5 通过):覆盖双身份按需求 ID 各取各类别、上下文降级返 null(对照既有方法仍兜底 TRAVEL)、陈旧需求 ID 不猜返 null、身份自身类别空白返 null、灰度上下文合成 TRAVEL 身份仍可取到。
|
||||
- `MatrixServiceTest` 新增 6 个 `#8560` 测试方法(均通过):同订单两类需求两张卡类别互不相同(含反向对照:**真实行路径**下两张卡 `requirementId` 字段完全相同;虚拟待派路径不适用该对照)、需求身份类别空白返 null 不兜底 TRAVEL、上下文降级返 null 不兜底 TRAVEL、派车行挂陈旧需求 ID(#5720)返 null 不兜底 TRAVEL、虚拟待派条目携带类别、虚拟待派条目无身份列表时类别为 null。
|
||||
- 聚合结果(`mvn -pl hl-fleet-service -am test`):`Tests run: 365, Failures: 0, Errors: 0, Skipped: 0`,`BUILD SUCCESS`;含 `VehicleRequirementKindsTest` 4、`BoardOrderServiceTest` 233、`FleetRedLineArchTest` 18(架构守护门禁绿)。
|
||||
- 嵌套用例选择器守卫(`nested_selector_census`):通过,内层名比对无缺组。
|
||||
- `spotless:check`:`BUILD SUCCESS`,916 文件全部合规。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue #8560
|
||||
- PR #8589(合并提交 `8b045a321b`)
|
||||
- 相关既有机制:#7439(TRAVEL/TRANSFER 双需求引入)、#8518(看板列表既有类别字段)、#7067(虚拟待派条目/去槽位化)、#5667(`requirementId` 订单侧单值口径)、#5720(换版过渡窗孤儿行)
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:wx(GIT)
|
||||
- 消费端:管理后台(派单矩阵页)
|
||||
@@ -0,0 +1,350 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8561"
|
||||
title: "派单矩阵三个入口补年份区间校验,新增错误码 605076"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8565 合并 dev-v3(ad3c6e4305);hl-fleet-service dev-v3 分支部署测试网关 @ 9c7ac9382 并实测:grid/month-counts/unassigned-orders 三入口越界年份(1800/9999/1990)均返回 605076,区间内年份(含 2020/2100 两端边界,已在 grid 入口实测)行为不变。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 派单矩阵三个入口补年份区间校验,新增错误码 605076
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8087)
|
||||
> **PR**: #8565
|
||||
> **Issue**: #8561
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台派单矩阵三个查询入口(grid / month-counts / unassigned-orders)的年份校验
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 派单矩阵三个查询入口(`grid`/`month-counts`/`unassigned-orders`)此前只校月份是否在 1-12,**不校年份**——`year=1800`、`year=9999` 这类明显异常值会被当成合法年份继续查询。现在统一在 Service 层加了年份区间校验 `[2020, 2100]`,越界抛**新增错误码 605076**。
|
||||
- 605076 的错误文案是 `年份超出范围(仅支持 2020-2100 年)`(`AssignmentErrorCode.MATRIX_YEAR_OUT_OF_RANGE`,文案里的年份区间已按当前常量渲染为 2020/2100)。
|
||||
- 校验顺序是**先年后月**:`year` 越界时直接抛 605076,不会先看 `month`。
|
||||
- 区间内年份(含边界 2020、2100)行为完全不变,仍按原逻辑正常查询并返回 200。
|
||||
- 三入口的 `year` 字段本身仍是**必填**(`@NotNull`),缺失依旧是既有的参数校验码 `100001`,本次未改动这一路径。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 矩阵主数据 | GET | `/admin/fleet/matrix/grid` | 校验补充 | 新增年份区间校验,越界返 605076 |
|
||||
| 2 | 矩阵年度月度统计 | GET | `/admin/fleet/matrix/month-counts` | 校验补充 | 新增年份区间校验,越界返 605076 |
|
||||
| 3 | 矩阵未派订单清单 | GET | `/admin/fleet/matrix/unassigned-orders` | 校验补充 | 新增年份区间校验,越界返 605076 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 矩阵主数据 `GET /admin/fleet/matrix/grid`
|
||||
|
||||
**VO**: `MatrixGridReqVO` → `MatrixGridRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务打开派单矩阵页查看某年某月逐日车辆占用/待派情况时调用。本次改动只影响 `year` 越界场景的响应,区间内查询字段结构与既有行为未变。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| year | Query | Integer | ✅ | 🔴 新增:2020-2100(越界返 605076) | 年份 |
|
||||
| month | Query | Integer | ✅ | 1-12(越界返 605010) | 月份 |
|
||||
| season | Query | String | - | active/pending/archived/blacklist | 司机赛季筛选,默认 active |
|
||||
| fleetTeamIds | Query | Long[] | - | - | 车队 ID 多选,空=全部 |
|
||||
| fleets | Query | String[] | - | 已废弃,仅客户端迁移兼容 | 旧版车队稳定编码多选 |
|
||||
| typeKeys | Query | String[] | - | 规范小写,空=全部 | 车型大类多选 |
|
||||
| status | Query | String | - | all/unassigned/assigned | 兼容矩阵筛选 |
|
||||
| statuses | Query | String[] | - | 非空时优先于 status | 有效状态精确筛选 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
响应结构本次未改动,以下仅列出与本次校验相关、已实测确认的顶层字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| year | Integer | 年份(回显) |
|
||||
| month | Integer | 月份(回显) |
|
||||
| daysInMonth | Integer | 该月天数 |
|
||||
| vehicles | List | 车辆逐日占用数据 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/matrix/grid?year=2020&month=6&season=active
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
区间下边界(year=2020)实测:
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"year":2020,"month":6,"daysInMonth":30},"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
区间上边界(year=2100)实测:
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"year":2100,"month":6},"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
区间内查询若当月无任何车辆/派车数据,`vehicles` 为空数组,属正常业务结果,不是错误;本次改动不影响此形态。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":605076,"message":"年份超出范围(仅支持 2020-2100 年)","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
(实测 `year=1800` 与 `year=9999` 均返回上述响应体,仅请求参数不同。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `year` 越界(不在 [2020, 2100])→ 605076,`month` 是否越界不影响判定结果(先年后月)。
|
||||
- `year` 合法、`month` 越界(如 13)→ 仍按既有 605010 路径处理,本次未改动。
|
||||
- `year`/`month` 缺失仍是既有的 100001(参数非法),未受本次改动影响。
|
||||
|
||||
### 2. 矩阵年度月度统计 `GET /admin/fleet/matrix/month-counts`
|
||||
|
||||
**VO**: `MatrixMonthCountsReqVO` → `MatrixMonthCountsRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务查看某年 12 个月各状态计数概览时调用(矩阵页顶部年度视图)。本次改动只影响 `year` 越界场景。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| year | Query | Integer | ✅ | 🔴 新增:2020-2100(越界返 605076) | 年份 |
|
||||
| season | Query | String | - | active/pending/archived/blacklist | 司机赛季筛选,默认 active |
|
||||
| fleetTeamIds | Query | Long[] | - | - | 车队 ID 多选,空=全部 |
|
||||
| typeKeys | Query | String[] | - | 规范小写,空=全部 | 车型大类多选 |
|
||||
|
||||
注:本端点没有 `month` 入参,年份越界统一走 605076,不会把调用方指去改一个不存在的字段。
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| year | Integer | 年份(回显) |
|
||||
| months | List | 12 个月各状态计数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/matrix/month-counts?year=2026&season=active
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"year":2026},"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
(实测该年 12 个月 `statusCounts` 均为 0,字段结构本次未改动,示例只截取顶层字段。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
区间内年份若全年无任何数据,`months` 中每个月的计数均为 0,属正常业务结果;本次改动不影响此形态。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":605076,"message":"年份超出范围(仅支持 2020-2100 年)","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
(实测 `year=1800` 返回上述响应体。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `year` 越界(不在 [2020, 2100])→ 605076。
|
||||
- `year` 缺失仍是既有的 100001(参数非法),未受本次改动影响。
|
||||
|
||||
### 3. 矩阵未派订单清单 `GET /admin/fleet/matrix/unassigned-orders`
|
||||
|
||||
**VO**: `MatrixUnassignedReqVO` → `List<MatrixUnassignedOrderVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务查看某年某月未派车订单清单(含虚拟待派条目)时调用。本次改动只影响 `year` 越界场景;`month` 越界的同构收敛见工单 #8571 的 changelog。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| year | Query | Integer | ✅ | 🔴 新增:2020-2100(越界返 605076),本次前摘掉了原 `@Min(1970)/@Max(9999)` | 年份 |
|
||||
| month | Query | Integer | ✅ | 1-12(越界返 605010,见 #8571) | 月份 |
|
||||
| typeKeys | Query | String[] | - | 规范小写,空=全部 | 车型大类多选 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
响应结构本次未改动。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| virtualPending | Boolean | 是否虚拟待派条目(库里无对应行,由 order-v3 当前需求投射) |
|
||||
| headcountLabel | String | 人数展示文案 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/matrix/unassigned-orders?year=1990&month=6&season=active
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":[],"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
(对照:`year=2026&month=6`(区间内)实测返回上述形态,`data` 为空数组属正常业务结果,与越界错误可区分。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
区间内年份若当月无未派订单,`data` 为空数组,属正常业务结果,与越界返回的错误响应(`success:false`)可明确区分。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":605076,"message":"年份超出范围(仅支持 2020-2100 年)","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
(实测 `year=1990` 返回上述响应体;本次改动前该场景返回的是框架码 100001,见"六.6、修改前后对比"。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 **契约收窄**:本端点 `year` 原有的校验区间是 `[1970, 9999]`(`@Min/@Max` 注解),本次收窄为 `[2020, 2100]` 且改走业务码 605076。`year=1990` 这类此前能通过框架校验、现在会被拒绝的取值,前端如果曾经允许用户选择这类年份,需要同步收紧可选范围。
|
||||
- `year` 越界 → 605076;`month` 越界 → 605010(#8571),二者不互相覆盖,先年后月。
|
||||
- `year`/`month` 缺失仍是既有的 100001(`@NotNull` 保留,未随本次摘除区间注解一并摘掉)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 响应 |
|
||||
|------|-----------------|
|
||||
| ✅ `year` 在 [2020, 2100] 区间内(含边界) | 三入口均正常返回 `code=200` |
|
||||
| ❌ `year` 不在 [2020, 2100] 区间(如 1800/1990/9999) | 三入口均返回 `code=605076` |
|
||||
| ❌ 继续沿用 `unassigned-orders` 此前 `[1970, 9999]` 的可选年份范围 | `year=1990` 等取值现在会被 605076 拒绝,不再是 100001 |
|
||||
| ❌ 把 605076 当作可重试错误自动重试 | 605076 是入参永久性非法,重试同一 `year` 不会成功,需要用户重新选择年份 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端拦到 `code=605076` 时应提示"年份超出范围,仅支持 2020-2100 年"类文案,并将年份选择控件的可选范围收紧到该区间,不要自动重试。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本次涉及的三个接口均为只读查询,无任何数据库写操作。改动只在 Service 层新增一段入参校验逻辑,不涉及任何表结构或存量数据变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- `year` 不在 [2020, 2100] → 605076(三入口统一,本次新增)
|
||||
- `year` 缺失 → 100001(既有行为,未改动)
|
||||
- `year` 合法、`month` 越界 → 605010(grid 既有行为;unassigned-orders 同构收敛见 #8571)
|
||||
- `year`/`month` 均合法 → 按既有逻辑正常查询,字段结构未变
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 矩阵年份越界错误码(`AssignmentErrorCode.MATRIX_YEAR_OUT_OF_RANGE`)
|
||||
|
||||
**所属字段**: 无(HTTP 响应顶层 `code`) | **类型**: `Integer`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `605076` | 年份超出范围(仅支持 2020-2100 年) | 🔴 本次新增;三个矩阵查询入口的 `year` 不在 [2020, 2100] 时统一返回;入参永久性非法,不应自动重试 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本次无请求/响应字段新增或删除;`unassigned-orders` 的 `year` 字段摘掉了 `@Min(1970)/@Max(9999)` 注解(见入参字段表标注),字段本身仍是必填 `Integer`。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| grid / month-counts,`year` 越界(不在 [2020,2100],如 1800/9999) | 未校验,当作合法年份继续查询 | 返回 `code=605076`(新增拦截) |
|
||||
| unassigned-orders,`year` 不在 [2020, 2100](含此前 `@Min(1970)/@Max(9999)` 认为合法的 [1970,2019]∪[2101,9999] 区间) | 该子区间内视为合法继续查询,落在 [1970,9999] 之外才返回框架码 100001 | 统一返回业务码 605076,不再区分是否曾落在 [1970,9999] 内 |
|
||||
| 三入口,`year` 在 [2020, 2100] | 正常返回 200 | 不变,仍正常返回 200 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是(仅 `unassigned-orders`)——`year` 的可接受范围从 `[1970, 9999]` 收窄到 `[2020, 2100]`,且越界时的错误码从 100001 变为 605076;`grid`/`month-counts` 此前对越界年份没有任何拦截,本次是新增拦截而非收窄既有契约。
|
||||
- **前端是否必须同步上线**: 是——年份选择控件若允许超出 `[2020, 2100]` 的取值,现在会收到新的 605076 错误码,前端需要新增该码的处理分支(提示文案 + 阻断当前查询),并建议同步收紧可选年份范围以减少用户触发该错误的机会。
|
||||
- **前端 workaround 清理点**: 若此前为"年份异常导致矩阵页面空白/报错"写过特殊兼容逻辑,可以确认不再需要,因为现在有明确的 605076 信号可用。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 派单矩阵三个查询入口(`grid`/`month-counts`/`unassigned-orders`)在 `year` 入参越界时的响应。
|
||||
- **零影响**:
|
||||
- 三入口在 `year` 合法时的成功路径字段结构
|
||||
- `month` 越界的既有校验 605010(`unassigned-orders` 的同构收敛见 #8571 单独的 changelog)
|
||||
- `season`/`fleetTeamIds`/`typeKeys`/`status`/`statuses` 等其余入参的校验逻辑
|
||||
- `day-orders` 等矩阵模块下其余未涉及本次改动的端点
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-fleet-service`,dev-v3 分支部署测试网关 @ `9c7ac9382`(含 #8561 所在提交),测试网关 `https://api.test.1814.love`:
|
||||
|
||||
```
|
||||
✓ GET grid?year=1800&month=6 → code=605076, message="年份超出范围(仅支持 2020-2100 年)"
|
||||
✓ GET grid?year=9999&month=6 → code=605076
|
||||
✓ GET month-counts?year=1800 → code=605076
|
||||
✓ GET unassigned-orders?year=1990&month=6 → code=605076(此前为 100001,见六.6)
|
||||
✓ GET grid?year=2020&month=6(下边界)→ code=200,正常返回
|
||||
✓ GET grid?year=2100&month=6(上边界)→ code=200,正常返回
|
||||
✓ GET month-counts?year=2026(区间中段)→ code=200,正常返回
|
||||
✓ GET unassigned-orders?year=2026&month=6(区间中段)→ code=200, data=[]
|
||||
```
|
||||
|
||||
注:2020/2100 两端边界值仅在 `grid` 入口做了直接边界实测;`month-counts`/`unassigned-orders` 在区间中段(year=2026)验证了正常放行。三入口共用同一段 `requireValidYearMonth` 校验逻辑(`MatrixService.java`),边界判定不因入口而异,越界拦截已在三入口分别实测(见上)。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8561](https://git.1814.love/wx/HL/issues/8561)
|
||||
- 关联 PR: [wx/HL#8565](https://git.1814.love/wx/HL/pulls/8565)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8561](https://git.1814.love/wx/HL/issues/8561)
|
||||
- **PR**: [#8565](https://git.1814.love/wx/HL/pulls/8565)
|
||||
- **Merge commit**: [ad3c6e4305](https://git.1814.love/wx/HL/commit/ad3c6e4305)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,387 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8562"
|
||||
title: "团期逐户用车列表区分「从未提交」与「已被打回待重提」,打回明细走新字段下发"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "e3f3d55265bf7f017d795e6e049601cee700dbfc"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households 的 households[] 新增三个字段:submitState(户级提交态,恒非 null,三取值 NEVER_SUBMITTED 从未提交 / SUBMITTED 已提交 / REJECTED_PENDING_RESUBMIT 已被打回待重提)、submitStateName(其中文名,后端下发)、rejectedRequirements(该户当前处于打回待重提的类别明细,恒非 null,无打回时为空数组,按展示序 TRAVEL 在前)。背景:用车的打回是「原地置 REJECTED_* + is_active=0」,被打回的户因此没有任何活跃需求行,与从未提交的户在 status 上完全同形(都是 null),车务照 status 催办会把「已交过、只是被驳回」和「压根没动过」混成一堆。四条必须照做的限定:(1) status 字段的取值规则一字未改、仍只由活跃行决定,判「有没有提交过」一律读 submitState,不要读 status 是否为 null;(2) requirements 列表内容零变化,被打回的行仍然不在里面,打回信息只在 rejectedRequirements;(3) rejectedRequirements 的元素刻意不与需求行同构(只有类别/打回状态/打回意见/打回时刻/版本号,没有车队明细、服务日期、座位数),不可当作需求行渲染,否则同一户会出现与活跃行自相矛盾的一条;(4) householdCount 口径再放宽一项,不再恒等于 needs_vehicle=true 的户数——一个 needs_vehicle 为假、没有活跃行、但有一类被打回的户现在也会进列表,判「这户为什么在列表里」看 submitState,不要拿 needsVehicle 反推。另两个数一字不动:vehicleRowCount(被打回的户贡献 0 行)与 countedHouseholdCount(只认活跃 TRAVEL 行),座位汇总口径不会因为有人被驳回而跳变。submitState 与 requirements 同受 kind 筛选影响:传 kind=TRAVEL 时,一个只有接送机被打回的户读成 NEVER_SUBMITTED;要看全貌就不传 kind(不传 = 两类都返)。已知边界(定案、非缺陷):TRANSFER 需求被「不再需要接送」失活(#8435)且没有新版时,既无活跃行也非打回,读成 NEVER_SUBMITTED,与从未提交对催办动作的要求一致,故不另立一态。入参、分页、排序、错误码(589500 / 589507 / 809000 / 401)与其余响应字段均未变化。;前端已交付:逐户表车侧判提交/判打回改读 submitState,「已打回」筛选与徽标覆盖车侧打回户,无活跃行户级文案用后端 submitStateName,rejectedRequirements 单独提示行不当需求行渲染,4 例定向测试全绿(hl-admin e3f3d552)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期用车逐户列表:区分「从未提交」与「已被打回待重提」
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **`households[]` 新增三个字段**:`submitState`(户级提交态,**恒非 null**)、`submitStateName`(其中文名)、`rejectedRequirements`(该户当前处于打回待重提的类别明细,**恒非 null**,无打回时为空数组)。
|
||||
- 🔴 **判「这户有没有提交过」一律读 `submitState`,不要读 `status` 是否为 `null`**。用车的打回是「原地置 `REJECTED_*` + `is_active=0`」⇒ 被打回的户**没有任何活跃需求行**,`status` 同样是 `null`,与从未提交的户**完全同形**。照 `status` 催办会去催一个已经交过、只是被驳回的人,而真正该催的「按意见重提」在页面上看不出来。
|
||||
- **`status` 的取值规则一字未改**(仍是活跃行展示序首条),`statusName` 同理。本次只新增旁路字段,既有映射与读数不受影响。
|
||||
- **`requirements` 列表内容零变化**:被打回的行仍然**不在**里面(它已失活)。打回信息只在新字段 `rejectedRequirements` 里。
|
||||
- 🔴 **`rejectedRequirements` 的元素不是需求行,不可当作需求行渲染**:它刻意与 `requirements[]` **不同构**——只带类别 / 打回状态 / 打回意见 / 打回时刻 / 版本号,**没有**车队明细、服务日期、座位数。把它拼进需求表会让同一户出现一条与活跃行自相矛盾的需求。
|
||||
- 🔴 **`householdCount` 口径再放宽一项,不再恒等于「`needs_vehicle=true` 的户数」**:一个 `needs_vehicle` 为假、又没有活跃行、但有一类被打回的户现在也会进列表(它正是要催重提的人)。判「这户为什么在列表里」看 `submitState`,**不要拿 `needsVehicle` 反推**。
|
||||
- **另两个数一字不动**:`vehicleRowCount`(被打回的户贡献 0 行)与 `countedHouseholdCount`(只认活跃 TRAVEL 行)。座位汇总口径不会因为「有人被驳回」而跳变。
|
||||
- **`submitState` 随 `kind` 筛选变化**(与 `requirements` 同一口径):传 `kind=TRAVEL` 时,一个只有接送机被打回的户读成 `NEVER_SUBMITTED`。要看全貌就**不传** `kind`(不传 = 两类都返)。
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
团期「查看需求」Tab 的用车逐户明细是车务与团期管理员的催办页:谁还没报、谁报了在等审、谁被驳回要改。但用车的打回实现是「把那一版原地置成 `REJECTED_*` 并把 `is_active` 置 0」,于是被打回的户在这个只读活跃行的端点里表现为「0 条需求行 + `status` 为 null」——与「从未提交」一模一样。更糟的是:`needs_vehicle` 为假、又没有活跃行的户改前压根不出卡,而被打回的户恰恰可能是这个形状,催办页上会**整户消失**。本次补的就是「这户到底是没交过,还是交过被驳回」这一维,以及「被驳回的是哪一类、意见是什么、什么时候驳的」这几个催办必需值。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期子订单用车需求记录 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` | 修改 | `households[]` 新增 `submitState` / `submitStateName` / `rejectedRequirements`;`householdCount` 口径放宽含打回户 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期子订单用车需求记录 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households`
|
||||
|
||||
**VO**: `Long groupBatchId + String kind(query)→ GroupVehicleHouseholdsRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期「查看需求」Tab 的用车逐户明细(汇总块下面那一块)。用于回答「这个团还差谁的用车需求」:本次起可以把「催首次提交」和「催按意见重提」分成两组,并在被驳回的户上直接展示驳回意见与时刻。
|
||||
|
||||
#### 入参
|
||||
|
||||
入参本次**零变化**。
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | path | Long | 是 | 雪花 ID;团期不存在返 589500 | 团期 ID |
|
||||
| kind | query | String | 否 | `TRAVEL` / `TRANSFER`;其余非空值返 809000 | 需求类别过滤。**不传或空白 = 两类都返**(与提交侧「不传按 TRAVEL」的缺省刻意相反,前端默认不传即可) |
|
||||
|
||||
#### 出参 `Result<GroupVehicleHouseholdsRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | String | 团期 ID |
|
||||
| departDate | String | 团期出发日 `YYYY-MM-DD`;团期未定出发日为 `null` |
|
||||
| endDate | String | 团期结束日 `YYYY-MM-DD`;团期未定结束日为 `null` |
|
||||
| householdCount | Integer | 本列表户数(按 orderId 去重),恒等于 `households` 长度。**口径放宽**:= 应报车户 ∪ 有活跃需求行的户 ∪ **处于打回待重提的户**;**不再恒等于 `needs_vehicle=true` 的户数** |
|
||||
| vehicleRowCount | Integer | 需求行数 = Σ 各户 `requirements` 长度。未提交与被打回的户贡献 0 行,故**可能小于 `householdCount`**(别当「行数 ≥ 户数」不变量) |
|
||||
| countedHouseholdCount | Integer | 计入车侧汇总的户数(= 有活跃 TRAVEL 行的户数)。判据一字未改,**不随 `kind` 筛选变化**,也不因有人被驳回而变 |
|
||||
| households | Array | 逐户明细,按 `orderNo` 升序(`orderNo` 为空的排最后,按 `orderId` 兜底稳定) |
|
||||
| households[].orderId | String | 子订单 ID |
|
||||
| households[].orderNo | String | 子订单编号(**非团号**,形如 `HL` + `yyyyMMddHHmmssSSS`) |
|
||||
| households[].teamNo | String | 子订单团号;未付订金尚未分配时为 `null`(不兜底、不回退成订单号) |
|
||||
| households[].customerName | String | 主联系人姓名 |
|
||||
| households[].participantCount | Integer | 出行人数(成人 + 儿童 + 小童 + 婴儿) |
|
||||
| households[].consultantId | String | 定制师 ID;未指派为 `null` |
|
||||
| households[].consultantName | String | 定制师姓名;未指派为 `null` |
|
||||
| households[].countedInSummary | Boolean | 该户是否计入车侧汇总(= 有活跃 TRAVEL 行);**未变** |
|
||||
| households[].status | String | 户级用车需求状态:**仅由活跃行决定**。`null` = 该户当前没有活跃需求行(**从未提交与已被打回失活两种情况都是 `null`**,要分辨读 `submitState`);非 `null` 时取展示序首条(TRAVEL 优先)的状态。**取值规则一字未改** |
|
||||
| households[].statusName | String | 户级状态中文名;`status` 为 `null` 时同为 `null`。`PENDING_REVIEW` 按 kind 分两套文案(TRAVEL=待提交车务 / TRANSFER=待审核,#8218);**未变** |
|
||||
| households[].requirements | Array | 该户的**活跃**用车需求行,0~2 条(TRAVEL / TRANSFER 各至多一条)。被打回的行已失活、**不在本列表内**;该户没有活跃行时为**空数组**(不是 `null`)。**本列表内容零变化** |
|
||||
| households[].submitState | String | 🆕 户级提交态,**恒非 null**:`NEVER_SUBMITTED` / `SUBMITTED` / `REJECTED_PENDING_RESUBMIT`。`status` 为 `null` 时靠它分辨两种空态;一户两类不同时按展示序首条(TRAVEL 优先)取;**随 `kind` 筛选变化** |
|
||||
| households[].submitStateName | String | 🆕 户级提交态中文名,与 `submitState` 一一对应:从未提交 / 已提交 / 已被打回待重提。后端下发,前端不自己映射 |
|
||||
| households[].rejectedRequirements | Array | 🆕 该户**当前**处于打回待重提的类别明细,按展示序(TRAVEL 在前)。**恒非 null**,无打回时为空数组。**不是需求行,不可当作需求行渲染** |
|
||||
| households[].rejectedRequirements[].kind | String | 需求类别:`TRAVEL` 行程用车 / `TRANSFER` 接送机 |
|
||||
| households[].rejectedRequirements[].kindName | String | 类别中文名,后端下发 |
|
||||
| households[].rejectedRequirements[].status | String | 打回状态编码:`REJECTED_TO_CONSULTANT` = 团期管理员打回定制师 / `REJECTED_TO_ADMIN` = 车务退回团期管理员 |
|
||||
| households[].rejectedRequirements[].statusName | String | 打回状态中文名,与 `requirements[].statusName` 同一套车务文案:已驳回定制师 / 已驳回管理员 |
|
||||
| households[].rejectedRequirements[].returnRemark | String | 打回意见;历史数据可能为 `null` |
|
||||
| households[].rejectedRequirements[].returnedAt | String | 打回时刻 `yyyy-MM-dd HH:mm:ss`;历史数据可能为 `null` |
|
||||
| households[].rejectedRequirements[].version | Integer | 被打回的那一版版本号。**TRAVEL 与 TRANSFER 各自独立递增,不可跨类比大小** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/requirement/vehicle-households
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
按类别筛选(只看行程用车):
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/requirement/vehicle-households?kind=TRAVEL
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
三户分别落在三个提交态上。`requirements[]` 行内字段与本次改动前完全一致,这里只保留几个便于对读的字段,未列出的行内字段(`fleet` / `serviceDates` / `specialTags` / 座位数等)照旧下发。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"departDate": "2026-10-06",
|
||||
"endDate": "2026-10-10",
|
||||
"householdCount": 3,
|
||||
"vehicleRowCount": 1,
|
||||
"countedHouseholdCount": 1,
|
||||
"households": [
|
||||
{
|
||||
"orderId": "2104839654727618570",
|
||||
"orderNo": "HL20261006103015001",
|
||||
"teamNo": "T26-3963",
|
||||
"customerName": "周雅",
|
||||
"participantCount": 4,
|
||||
"consultantId": "1901233114509312088",
|
||||
"consultantName": "苏晴",
|
||||
"countedInSummary": true,
|
||||
"status": "PENDING_REVIEW",
|
||||
"statusName": "待提交车务",
|
||||
"requirements": [
|
||||
{
|
||||
"requirementId": "2104839777884160001",
|
||||
"kind": "TRAVEL",
|
||||
"kindName": "行程用车",
|
||||
"status": "PENDING_REVIEW",
|
||||
"statusName": "待提交车务",
|
||||
"headcount": 4,
|
||||
"returnRemark": null,
|
||||
"returnedAt": null
|
||||
}
|
||||
],
|
||||
"submitState": "SUBMITTED",
|
||||
"submitStateName": "已提交",
|
||||
"rejectedRequirements": []
|
||||
},
|
||||
{
|
||||
"orderId": "2104839654727618571",
|
||||
"orderNo": "HL20261006103015002",
|
||||
"teamNo": "T26-3964",
|
||||
"customerName": "郑文博",
|
||||
"participantCount": 2,
|
||||
"consultantId": "1901233114509312088",
|
||||
"consultantName": "苏晴",
|
||||
"countedInSummary": false,
|
||||
"status": null,
|
||||
"statusName": null,
|
||||
"requirements": [],
|
||||
"submitState": "REJECTED_PENDING_RESUBMIT",
|
||||
"submitStateName": "已被打回待重提",
|
||||
"rejectedRequirements": [
|
||||
{
|
||||
"kind": "TRAVEL",
|
||||
"kindName": "行程用车",
|
||||
"status": "REJECTED_TO_CONSULTANT",
|
||||
"statusName": "已驳回定制师",
|
||||
"returnRemark": "第三天上午的用车时间与行程冲突,请改后重提",
|
||||
"returnedAt": "2026-09-29 16:42:11",
|
||||
"version": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"orderId": "2104839654727618572",
|
||||
"orderNo": "HL20261006103015003",
|
||||
"teamNo": null,
|
||||
"customerName": "何嘉宁",
|
||||
"participantCount": 3,
|
||||
"consultantId": null,
|
||||
"consultantName": null,
|
||||
"countedInSummary": false,
|
||||
"status": null,
|
||||
"statusName": null,
|
||||
"requirements": [],
|
||||
"submitState": "NEVER_SUBMITTED",
|
||||
"submitStateName": "从未提交",
|
||||
"rejectedRequirements": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 团期下没有在团子订单:`households` 为空数组 `[]`,三个计数均为 `0`,`departDate` / `endDate` 照常回显,不报错。
|
||||
- 某户没有活跃需求行:`requirements` 为**空数组**(不是 `null`),`status` 与 `statusName` 为 `null`,而 `submitState` **仍然有确定取值**(`NEVER_SUBMITTED` 或 `REJECTED_PENDING_RESUBMIT`)——前端不必对 `submitState` 判空。
|
||||
- 某户没有被打回的类别:`rejectedRequirements` 为**空数组**(不是 `null`)。
|
||||
- 在团订单 ID 存在但订单行缺失(跨团挂单 / 订单被物理删这类数据异常):该户被跳过并在服务端留痕,整页照常返回;三个计数都按**最终列表**重算,不会出现「表头 42 户、列表里只有 30 户」这种自相矛盾的响应。
|
||||
- 单次返回上限 **500 户**,超出按 `orderNo` 升序截断并在服务端留痕;截断后三个计数同样按截断后的列表重算。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809000,
|
||||
"message": "用车需求类别非法:BOTH",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
- `809000 用车需求类别非法:{0}`:`kind` 传了 `TRAVEL` / `TRANSFER` 之外的非空值(例如 `ALL`、`BOTH`、小写拼错)。要「两类都返」请**不传**该参数或传空串。
|
||||
- `589500 团期不存在`:`groupBatchId` 查不到。
|
||||
- `589507 无操作权限(当前角色未授予团期权限,或该团期不在您名下)`:缺团期查看权限,或该团期不在当前账号名下。
|
||||
- `401`:未登录或令牌失效(网关返 HTTP 200 + 信封 `code: 401`,请按信封 `code` 判定)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 **判「有没有提交过」读 `submitState`,不读 `status` 是否为 `null`**。打回 = 原地置 `REJECTED_*` + `is_active=0` ⇒ 打回户与从未提交户的 `status` **都是 `null`**,在 `status` 这一维上不可分辨。`status` 的取值规则本次一字未改。
|
||||
- 🔴 **`requirements` 列表内容零变化**:被打回的行不在里面,打回信息只在 `rejectedRequirements`。不要为了展示驳回意见去翻 `requirements[].returnRemark`——那一格记的是**该活跃行**历史上被打回过的痕迹(已重提后仍可能有值),不是「当前处于打回态」。
|
||||
- 🔴 **`rejectedRequirements` 不可当作需求行渲染**:它与 `requirements[]` 刻意不同构,没有车队明细 / 服务日期 / 座位数。需要看需求内容时读该户的活跃行,或走需求版本历史端点。
|
||||
- 🔴 **`householdCount` 不再恒等于 `needs_vehicle=true` 的户数**:一个 `needs_vehicle` 为假、没有活跃行、但有一类被打回的户也会进列表。判「这户为什么在列表里」看 `submitState`,不要拿 `needsVehicle` 反推。
|
||||
- **`rejectedRequirements` 只列「当前」处于打回待重提的类别**,不是历史打回记录:打回后已重新提交的类别**不**出现在这里(那一类的现状在 `requirements` 里),否则页面会永远挂着一条早已处理完的驳回。
|
||||
- **一户两类状态不同时,`submitState` 按展示序首条取**(TRAVEL 优先),与 `status` 同一条规则。例:TRAVEL 有活跃行、TRANSFER 被打回 ⇒ `submitState` 是 `SUBMITTED`,而 `rejectedRequirements` 里有 TRANSFER 那一条。**要逐类判断一律读 `requirements[].status` 与 `rejectedRequirements[].kind`,不要用户级的 `submitState` 推单类。**
|
||||
- **`submitState` 随 `kind` 筛选变化**:传 `kind=TRAVEL` 时,一个只有接送机被打回的户读成 `NEVER_SUBMITTED`(该类别不在筛选范围内)。要看全貌不传 `kind`。
|
||||
- **已知边界(定案,非缺陷)**:TRANSFER 需求被「不再需要接送」失活(#8435)且之后没有新版本时,该户既无活跃行也不处于打回态,会读成 `NEVER_SUBMITTED`。它与「从未提交」对催办动作的要求一致(要么提,要么整团免车),故不另立一态。
|
||||
- **`version` 不可跨类比较**:TRAVEL 的 v3 与 TRANSFER 的 v3 之间没有先后关系,两类版本号各自独立递增。
|
||||
- **`vehicleRowCount` 可能小于 `householdCount`**:未提交与被打回的户贡献 0 行。不要再把「行数 ≥ 户数」当不变量写断言。
|
||||
- **`countedHouseholdCount` 不随 `kind` 筛选变化**(#8559),也不因驳回动作变化——座位汇总口径必须稳定。
|
||||
- **Swagger 上该端点的 `notes` 仍按本次改动前的口径写着「被打回的需求行已失活,不在本列表内」**:这句对**需求行**依然成立(打回行确实不进 `requirements`),但对**户**不再成立——打回户现在会出现在 `households` 里。字段级语义以本交接件与各字段的 `@ApiModelProperty` 为准。
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
1. **两个新字段恒非 null,直接读不用判空**:`submitState` 与 `rejectedRequirements` 对每一户都有确定取值(后者无打回时是空数组)。需要判空的仍是 `status` / `statusName` / `teamNo` / `consultantId` / `consultantName` 这些既有字段。
|
||||
2. **催办分组按 `submitState` 做**:`NEVER_SUBMITTED` → 催首次提交;`REJECTED_PENDING_RESUBMIT` → 催按意见重提(意见与时刻在 `rejectedRequirements` 里);`SUBMITTED` → 具体到哪一步看 `status` / `requirements[].status`。
|
||||
3. **不要用 `status == null` 当「未提交」的判据**:这是本次要解决的那个缺陷本身。前端若已有这段逻辑,请改为 `submitState === 'NEVER_SUBMITTED'`。
|
||||
4. **不要把 `rejectedRequirements` 并进需求行列表**:两者结构刻意不同构。驳回信息建议单独渲染成一条提示条(类别 + 状态中文名 + 意见 + 时刻),与需求行区分开。
|
||||
5. **不要拿 `needsVehicle` 反推「这户为什么在列表里」**:列表的并集口径已变,判据是 `submitState`。
|
||||
6. **中文名一律用后端下发的**:`submitStateName` / `kindName` / `statusName` 都由后端给出,前端不要再本地维护映射表(`PENDING_REVIEW` 的文案还会按 kind 分叉成两种,本地表必然对不上,#8218)。
|
||||
7. **要看全貌不传 `kind`**:不传 = 两类都返。传了 `kind` 则 `requirements`、`rejectedRequirements`、`submitState`、`householdCount`、`vehicleRowCount` 全部随之收窄(只有 `countedHouseholdCount` 三种筛选读数相同)。
|
||||
8. **`kind` 只接受 `TRAVEL` / `TRANSFER`**:想表达「全部」请**不传**,传 `ALL` / `BOTH` 会返 809000。
|
||||
9. **错误信封按 `code` 判**:业务失败与入参校验一律 HTTP 200 + 信封 `code`;测试环境网关对失效令牌也返回 HTTP 200 + `code: 401`。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本端点为只读查询,本次改动**不涉及任何 DDL 与 DML**:没有新增表、没有新增列、没有 Flyway 脚本、没有写入。
|
||||
|
||||
- `submitState` **不是数据库列**,不落表、不参与任何 SQL 过滤或分组,纯粹是响应字段——所以既有的按 `status` 筛选 / 统计的扫描路径不会把它重新捡起来,也不会误计。
|
||||
- 打回明细取自用车需求的**版本历史行**(打回行已 `is_active=0`)。取数用**一次按子订单 ID 批量**的查询(与既有的活跃行查询同一形状,`requirement_kind` 的 `IN` 列表从 1 个值放宽到 2 个值),**不是逐户 N+1**;整页固定若干次查询,与户数无关。
|
||||
- 判「某类是否处于打回待重提」复用的是定制师提交侧闸门的同一套算法(取最高版本组、看最后一次动作是否为打回),不新造判定规则。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
| 场景 | `status` | `requirements` | `submitState` | `rejectedRequirements` | 是否出现在列表 |
|
||||
|------|----------|----------------|---------------|------------------------|----------------|
|
||||
| 有活跃行(TRAVEL 或 TRANSFER) | 首条行的状态 | 1~2 条 | `SUBMITTED` | `[]` | 是 |
|
||||
| 从未提交过任何版本 | `null` | `[]` | `NEVER_SUBMITTED` | `[]` | 是(`needs_vehicle` 为真即出卡) |
|
||||
| 被打回、尚未重提 | `null` | `[]` | `REJECTED_PENDING_RESUBMIT` | 1~2 条 | 是(**本次新增的入列路径**) |
|
||||
| 被打回后已重新提交 | 新行的状态 | 1~2 条 | `SUBMITTED` | `[]`(不挂已处理完的驳回) | 是 |
|
||||
| TRAVEL 有活跃行 + TRANSFER 被打回 | TRAVEL 行的状态 | 1 条(TRAVEL) | `SUBMITTED`(按展示序首条) | 1 条(TRANSFER) | 是 |
|
||||
| TRAVEL 被打回 + TRANSFER 有活跃行 | TRANSFER 行的状态 | 1 条(TRANSFER) | `REJECTED_PENDING_RESUBMIT`(TRAVEL 展示序在前) | 1 条(TRAVEL) | 是 |
|
||||
| 只有 TRANSFER 被打回,且传了 `kind=TRAVEL` | `null` | `[]` | `NEVER_SUBMITTED`(该类别不在筛选内) | `[]` | 取决于 `needs_vehicle` |
|
||||
| TRANSFER 被「不再需要接送」失活且无新版(#8435) | `null` | `[]` | `NEVER_SUBMITTED`(定案) | `[]` | 是 |
|
||||
| 在团订单行缺失(数据异常) | — | — | — | — | 跳过该户并留痕,整页照常返回 |
|
||||
| 户数超过 500 | — | — | — | — | 按 `orderNo` 升序截断,计数按截断后重算 |
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
**户级提交态**(`submitState` → `submitStateName`,新增枚举,**恒非 null**)
|
||||
|
||||
| 码 | 中文名 | 语义 | 催办动作 |
|
||||
|----|--------|------|----------|
|
||||
| NEVER_SUBMITTED | 从未提交 | 在本次筛选的类别范围内既没有活跃需求行、也不处于打回态 | 催首次提交 |
|
||||
| SUBMITTED | 已提交 | 至少一类存在活跃需求行(具体到哪一步看 `status`) | 按 `status` 跟进 |
|
||||
| REJECTED_PENDING_RESUBMIT | 已被打回待重提 | 没有活跃行,但最高版本组的最后一次动作是打回 | 催「按意见改完再提」 |
|
||||
|
||||
> 展示名刻意与团期子订单列表那块的「未提交」用不同的词:那块只判「有没有活跃行」,被打回的户在那里也显示「未提交」;两处若同字,本次分开的这一步在页面上就白做了。
|
||||
|
||||
**打回状态**(`rejectedRequirements[].status` → `statusName`)
|
||||
|
||||
| 码 | 中文名 | 谁打回的 |
|
||||
|----|--------|----------|
|
||||
| REJECTED_TO_CONSULTANT | 已驳回定制师 | 团期管理员打回定制师 |
|
||||
| REJECTED_TO_ADMIN | 已驳回管理员 | 车务退回团期管理员 |
|
||||
|
||||
**需求类别**(`kind` → `kindName`,未变)
|
||||
|
||||
| 码 | 中文名 |
|
||||
|----|--------|
|
||||
| TRAVEL | 行程用车 |
|
||||
| TRANSFER | 接送机 |
|
||||
|
||||
展示序固定 TRAVEL 在前、TRANSFER 在后;`requirements` 与 `rejectedRequirements` 共用这个序。
|
||||
|
||||
**活跃需求行状态**(`requirements[].status`,未变):`PENDING_REVIEW` / `PENDING` / `PROCESSING` / `DONE`。`REJECTED_*` 不会出现在活跃行上。`PENDING_REVIEW` 的中文名按 kind 分叉:TRAVEL = 待提交车务、TRANSFER = 待审核(#8218)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 字段 / 口径 | 改动前 | 改动后 |
|
||||
|-------------|--------|--------|
|
||||
| `households[].submitState` | 不存在 | 🆕 恒非 null 的三态字段,是 `status` 为 `null` 时唯一能分辨「从未提交 / 已被打回」的字段 |
|
||||
| `households[].submitStateName` | 不存在 | 🆕 三态中文名,后端下发 |
|
||||
| `households[].rejectedRequirements` | 不存在(打回信息在本端点完全取不到) | 🆕 恒非 null 的数组,列当前处于打回待重提的类别 + 意见 + 时刻 + 版本号 |
|
||||
| `households[].status` / `statusName` | 只由活跃行决定 | **取值规则一字未改**(仍只由活跃行决定)。变的只是文档:不能再拿它判「有没有提交过」 |
|
||||
| `households[].requirements` | 只含活跃行,打回行不在其中 | **内容零变化** |
|
||||
| `householdCount` | = 应报车户 ∪ 有活跃需求行的户;恒等于 `needs_vehicle=true` 的户数(`needs_vehicle` 创单恒真) | 并集多一项「处于打回待重提的户」⇒ **不再恒等于 `needs_vehicle=true` 的户数**;`needs_vehicle` 为假但有一类被打回的户会进来 |
|
||||
| `vehicleRowCount` | Σ 各户活跃行数 | **口径未变**(打回户贡献 0 行);与 `householdCount` 的差额多了「打回户」这一类 |
|
||||
| `countedHouseholdCount` | 有活跃 TRAVEL 行的户数 | **一字未变**,不因驳回动作跳变 |
|
||||
| 被打回户是否出现在列表 | `needs_vehicle` 为假时**不出卡**(催办页上整户消失) | 出卡,`submitState` = `REJECTED_PENDING_RESUBMIT` |
|
||||
| 入参 / 分页 / 排序 / 错误码 | — | 全部未变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端必须改的**:如果页面上有「`status == null` ⇒ 显示未提交」这段逻辑,**必须**改成读 `submitState`——不改的话打回户会继续被标成「未提交」,本次改动在页面上等于没做。
|
||||
- **前端应当改的**:催办清单按 `submitState` 分两组;被驳回的户上渲染 `rejectedRequirements` 里的类别 + 状态中文名 + 意见 + 时刻。
|
||||
- **前端不要做的**:把 `rejectedRequirements` 拼进需求行表格(会出现与活跃行矛盾的一条);拿 `needsVehicle` 反推入列原因;跨类比较 `version`。
|
||||
- **可能被读错的一处**:`requirements[].returnRemark` / `returnedAt` 记的是**该活跃行**历史上被打回过的痕迹,已重提后仍可能有值;「当前处于打回态」只看 `rejectedRequirements` 是否非空。
|
||||
- **列表条数会变多**:`needs_vehicle` 为假、无活跃行、但有一类被打回的户从本次起入列。如果前端有基于户数的断言或埋点基线,会看到这一类团期的户数上升——这是预期,不是数据错误。
|
||||
- **兼容性**:JSON 新增字段对已有前端反序列化无影响。既有字段一个没删、没改名、没改类型。
|
||||
- **无副作用面**:只读端点,不涉及写入、事务、消息、权限判定变化;座位与汇总口径不变。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **入参**:`groupBatchId`、`kind` 的取值域、缺省语义(不传 = 两类)、校验规则全部未变。
|
||||
- **排序与上限**:`orderNo` 升序 + `orderId` 兜底、单次 500 户上限未变。
|
||||
- **既有响应字段**:`groupBatchId` / `departDate` / `endDate` / `vehicleRowCount` / `countedHouseholdCount` / `households[]` 的既有字段(含 `status` / `statusName` / `requirements` 及行内所有字段)名称、类型、取值域、语义全部未变。
|
||||
- **错误码**:未新增、未删除、未改文案(589500 / 589507 / 809000 / 401)。
|
||||
- **权限码**:仍是团期查看权限,未收紧未放宽。
|
||||
- **用房侧** `hotel-households` 端点:本次一行未改。
|
||||
- **写路径**:逐户提交车务、打回、整体确认需求等写接口本次一行未改。
|
||||
- **团级汇总** `requirement-summary`:口径与读数未变(`countedHouseholdCount` 是它的对账口,本次刻意保持不动)。
|
||||
- **网关路由**:既有路由,本次无新增。
|
||||
- **数据库**:无 DDL、无 DML、无 Flyway 脚本。
|
||||
- **小程序端**:零影响。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
本次改动的核心可验证面是「打回户与从未提交户在 `status` 上同形、在 `submitState` 上可分辨」,以及「既有三个计数与 `requirements` 内容不受影响」。已由下列自动化用例覆盖(`hl-order-service-v3`):
|
||||
|
||||
| 覆盖点 | 用例 |
|
||||
|--------|------|
|
||||
| 打回户与从未提交户 `status` 相同(都是 `null`)、`submitState` 不同 | `GroupBatchVehicleHousehold8562Test#households_rejectedAndNeverSubmitted_sameStatusDifferentSubmitState` |
|
||||
| 打回户带出打回意见与打回状态中文名 | `#households_rejectedHousehold_carriesRemarkAndStatusName` |
|
||||
| 存在打回时 `requirements` 仍然只含活跃行(内容零变化) | `#households_rejectionPresent_requirementsStillOnlyActiveRows` |
|
||||
| TRAVEL 有活跃行 + TRANSFER 被打回 ⇒ `submitState` 按展示序首条(TRAVEL)取 | `#households_travelActiveTransferRejected_submitStateFollowsTravel` |
|
||||
| TRAVEL 被打回 + TRANSFER 有活跃行 ⇒ `submitState` 同样按 TRAVEL 取 | `#households_travelRejectedTransferActive_submitStateFollowsTravel` |
|
||||
| 传 `kind=TRANSFER` 时 TRAVEL 的打回被筛掉 | `#households_kindTransfer_travelRejectionFilteredOut` |
|
||||
| `needs_vehicle` 为假但有一类被打回的户仍然入列 | `#households_rejectedButNeedsVehicleFalse_stillListed` |
|
||||
| 完全没有打回时 `submitState` 为 `SUBMITTED`、`rejectedRequirements` 为空数组 | `#households_noRejectionAtAll_submitStateSubmitted` |
|
||||
| 打回明细按子订单 ID 批量取数(固定次数,不随户数增长) | `RequirementServiceVehicleRejectionBatchTest` |
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
- PR #8623(本次):`feat(order-v3): 团期逐户用车区分「从未提交」与「已被打回待重提」(#8562)`。
|
||||
- #8195:`householdCount` 改为「应报车户数」并开始包含未提交户,`status` / `statusName` 两个户级字段在那一单新增。
|
||||
- #8559:`countedHouseholdCount` 不随 `kind` 筛选变化。
|
||||
- #8577:只提交接送机的户不再被判「未提交用车需求」。
|
||||
- #8601:逐户提交车务与打回的 `kind` 参数取消默认值。
|
||||
- #8218:`PENDING_REVIEW` 的中文名按 kind 分叉(TRAVEL 待提交车务 / TRANSFER 待审核)。
|
||||
- #8435:「不再需要接送」失活 TRANSFER 需求(本单已知边界的来源)。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- `docs/CODE_RULES.md` §3:VO 命名、`@ApiModelProperty` 约定、禁 Entity 跨层(打回明细用独立 DTO 而非直接传需求行的依据)。
|
||||
- `docs/CODE_RULES.md` §15.7:字典字面量单源——`submitStateName` / `kindName` / `statusName` 由后端下发的依据。
|
||||
- Swagger:`hl-order-service-v3` → `团期需求` 分组。字段级语义以各字段 `@ApiModelProperty` 为准;该端点 `notes` 的那句「被打回的需求行不在本列表内」只对需求行成立、对户不成立(见「业务边界」最后一条)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- 工单 #8562
|
||||
- PR #8623
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端:wx
|
||||
- 前端:mmg(管理后台 hl-ui)
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8571"
|
||||
title: "矩阵未派订单清单月份越界改由 Service 判,与另两个入口同构"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8582 合并 dev-v3(6634d0588d);hl-fleet-service dev-v3 分支部署测试网关 @ 9c7ac9382 并实测:unassigned-orders 月份越界(如 13、0)统一返回 605010,月份缺失仍返 100001,grid 入口的既有 605010 行为未回归。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 矩阵未派订单清单月份越界改由 Service 判,与另两个入口同构
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8087)
|
||||
> **PR**: #8582
|
||||
> **Issue**: #8571
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台派单矩阵未派订单清单端点 `unassigned-orders` 的月份越界校验
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- `unassigned-orders` 的 `month` 越界校验此前是框架层 `@Min(1)/@Max(12)` 注解,越界返回**框架码 100001**;`grid` 端点的 `month` 越界则一直是 Service 层 `requireValidYearMonth` 判定,返回**业务码 605010**。同一类"月份填错了",两个端点走两套码、两套错误文案,前端得按端点分别写处理分支。
|
||||
- 本次摘掉 `unassigned-orders` 的 `@Min/@Max` 注解,越界统一改由 Service 层 `requireValidYearMonth` 判定,**现在与 `grid` 完全同构:越界一律返回 605010**(`月份超出范围`)。
|
||||
- 🔴 **`@NotNull` 被保留**:`month` 字段缺失(不传该参数)仍然返回既有的 100001(`参数非法: 月份不能为空`),这条路径没有变化——只有"传了值但越界"这一种场景的错误码变了。
|
||||
- `year` 字段的年份越界校验(605076)是另一张工单 #8561 引入的独立改动,与本次 `month` 校验改动在同一个 `requireValidYearMonth` 方法里但各自独立生效,请分别查阅两份 changelog。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 矩阵未派订单清单 | GET | `/admin/fleet/matrix/unassigned-orders` | 校验口径收敛 | `month` 越界改由 Service 判,与 grid 统一返回 605010 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 矩阵未派订单清单 `GET /admin/fleet/matrix/unassigned-orders`
|
||||
|
||||
**VO**: `MatrixUnassignedReqVO` → `List<MatrixUnassignedOrderVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务查看某年某月未派车订单清单(含虚拟待派条目)时调用。本次改动只影响 `month` 越界场景的错误码;`year` 越界的新增校验(605076)见工单 #8561 的 changelog。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| year | Query | Integer | ✅ | 2020-2100(越界返 605076,见 #8561) | 年份 |
|
||||
| month | Query | Integer | ✅ | 🔴 1-12,越界改为返 605010(此前是框架码 100001),本次摘掉了原 `@Min(1)/@Max(12)` | 月份 |
|
||||
| typeKeys | Query | String[] | - | 规范小写,空=全部 | 车型大类多选 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
响应结构本次未改动。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| virtualPending | Boolean | 是否虚拟待派条目 |
|
||||
| headcountLabel | String | 人数展示文案 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/matrix/unassigned-orders?year=2026&month=13&season=active
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
区间边界内实测(`month=1`):
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":[],"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
区间边界内实测(`month=12`):
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":[],"traceId":null,"success":true}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
`month` 合法时若当月无未派订单,`data` 为空数组,属正常业务结果,与越界返回的错误响应(`success:false`)可明确区分。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
`month=13`(越界)实测:
|
||||
|
||||
```json
|
||||
{"code":605010,"message":"月份超出范围","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
`month=0`(越界)实测:
|
||||
|
||||
```json
|
||||
{"code":605010,"message":"月份超出范围","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
`month` 缺失(未传该参数)实测,**未受本次改动影响**:
|
||||
|
||||
```json
|
||||
{"code":100001,"message":"参数非法: 月份不能为空","data":null,"traceId":null,"success":false}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 🔴 `month` 越界(不在 1-12,如 0、13)从此前的框架码 100001 改为业务码 605010,与 `grid` 端点完全同构;前端若曾经按 100001 识别"月份越界"这一具体场景,需要改成识别 605010。
|
||||
- `month` 缺失(不传参数)仍是 100001,`@NotNull` 判定发生在 `requireValidYearMonth` 之前,未被本次改动波及,无需新增分支。
|
||||
- `year` 越界返回 605076(#8561 引入),与本次 `month` 越界的 605010 是两个独立判定,`requireValidYearMonth` 先判年后判月。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 响应 |
|
||||
|------|-----------------|
|
||||
| ✅ `month` 在 1-12 区间内(含边界 1、12) | 正常返回 `code=200` |
|
||||
| ❌ `month` 不在 1-12 区间(如 0、13) | 返回 `code=605010` |
|
||||
| ❌ 不传 `month` 参数 | 返回 `code=100001`(未受本次改动影响) |
|
||||
| ❌ 继续按 `code=100001` 识别"月份越界"这一具体场景 | 本端点越界场景已改为 605010,100001 现在只对应"缺参" |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
前端若此前对本端点单独写过"`code=100001` → 月份超出范围"的文案分支,需要改成识别 `605010`(文案为"月份超出范围"),并与 `grid` 端点共用同一套 605010 处理逻辑;100001 的处理分支需要改为对应"参数缺失"。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本次涉及的接口为只读查询,无任何数据库写操作。改动只是把一段入参校验从框架注解移到 Service 层方法内,不涉及任何表结构或存量数据变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- `month` 不在 1-12 → 605010(本次改动后的新行为,此前是 100001)
|
||||
- `month` 缺失(未传参数)→ 100001(既有行为,未改动)
|
||||
- `year` 越界 → 605076(#8561 引入的独立判定,先于 `month` 判定执行)
|
||||
- `month` 在 1-12 且 `year` 合法 → 正常返回,字段结构未变
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 月份越界错误码(`AssignmentErrorCode.MATRIX_MONTH_OUT_OF_RANGE`)
|
||||
|
||||
**所属字段**: 无(HTTP 响应顶层 `code`) | **类型**: `Integer`
|
||||
|
||||
| 值 | 中文 | 本次是否新增 | 说明 |
|
||||
|----|------|------|------|
|
||||
| `605010` | 月份超出范围 | 端点内是新用法(既有码,`grid` 端点此前已在用) | `unassigned-orders` 本次起对 `month` 越界统一返回该码,与 `grid` 同构 |
|
||||
| `100001` | 参数非法 | 未变 | `month` 缺失(未传参数)时仍返回,文案为"参数非法: 月份不能为空" |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
本次无请求/响应字段新增或删除;`month` 字段摘掉了 `@Min(1)/@Max(12)` 注解,`@NotNull` 保留,字段本身仍是必填 `Integer`。
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `unassigned-orders`,`month` 越界(如 0、13) | 返回框架码 `100001`(`@Min/@Max` 拦截) | 返回业务码 `605010` |
|
||||
| `unassigned-orders`,`month` 缺失 | 返回 `100001` | 不变,仍返回 `100001` |
|
||||
| `grid`,`month` 越界 | 返回 `605010` | 不变,仍返回 `605010`(本次未改动 grid,仅用于对照验证未回归) |
|
||||
| `unassigned-orders`,`month` 在 1-12 | 正常返回 200 | 不变,仍正常返回 200 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是——`month` 越界时的错误码从 `100001` 变为 `605010`,前端若按具体码值做过分支判断,命中该场景的分支需要更新。
|
||||
- **前端是否必须同步上线**: 是(仅针对本端点单独维护过 100001 越界分支的场景)——若前端此前对 `unassigned-orders` 单独写过"`code=100001` 即月份越界"的判断,现在需要改为识别 `605010`;若前端此前是把 100001 统一当作"参数错误"泛化处理且未细分场景,则不受影响。
|
||||
- **前端 workaround 清理点**: 若此前为"同一类月份错误在 grid 和 unassigned-orders 上分别处理"写过两套逻辑,现在两端点已统一为 605010,可以合并成一套。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `unassigned-orders` 端点在 `month` 入参越界(不在 1-12)时的错误码。
|
||||
- **零影响**:
|
||||
- `month` 缺失时的错误码(仍是 100001)
|
||||
- `year` 校验逻辑(605076,属 #8561 独立改动)
|
||||
- `grid`/`month-counts` 两个端点的既有行为
|
||||
- `unassigned-orders` 在 `month` 合法时的成功路径字段结构
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
服务:`hl-fleet-service`,dev-v3 分支部署测试网关 @ `9c7ac9382`(含 #8571 所在提交),测试网关 `https://api.test.1814.love`:
|
||||
|
||||
```
|
||||
✓ GET unassigned-orders?year=2026&month=13 → code=605010, message="月份超出范围"(此前应为 100001)
|
||||
✓ GET unassigned-orders?year=2026&month=0 → code=605010
|
||||
✓ GET unassigned-orders?year=2026&month=1(下边界)→ code=200, data=[]
|
||||
✓ GET unassigned-orders?year=2026&month=12(上边界)→ code=200, data=[]
|
||||
✓ GET unassigned-orders?year=2026(不传 month)→ code=100001, message="参数非法: 月份不能为空"(@NotNull 未被误摘)
|
||||
✓ GET grid?year=2026&month=13 → code=605010(grid 既有行为,未回归)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8571](https://git.1814.love/wx/HL/issues/8571)
|
||||
- 关联 PR: [wx/HL#8582](https://git.1814.love/wx/HL/pulls/8582)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8571](https://git.1814.love/wx/HL/issues/8571)
|
||||
- **PR**: [#8582](https://git.1814.love/wx/HL/pulls/8582)
|
||||
- **Merge commit**: [6634d0588d](https://git.1814.love/wx/HL/commit/6634d0588d)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,623 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8576"
|
||||
title: "团期配车提交 / 确认响应新增 specWarnings 车辆规格提醒清单(非错误)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8602 已 squash 合并 dev-v3(cfefe04a83),hl-fleet-service dev-v3 分支已滚测试服。纯新增字段,两个端点的既有字段、错误码、HTTP 形态全部未变。【frontend_status 取 not_required 的依据,2026-09-30 对 hl-ui origin/v2.1 第三次复核】两个写口在前端已无消费方:#8464(2026-09-28,提交 d7e932ac)整体删除了 src/views/fleet/group-dispatch/ 页面模块,src/api/fleet/group-dispatch.js 随之收缩到只剩 getGroupDispatchPendingBatches 一个出口(该文件头部注释明写 reconfigure / confirm / readiness / share-groups / share-member-candidates 已移除,页面恢复时从 git 历史找回)。复核读数:specWarnings 全仓 1 命中且落在 .claude/agents/memory/ 的备忘文件里、src/ 下 0;reconfigure 在 src/ 下的命中全部是注释或 order-v2「受控重开窗口」令牌机制的同词异义。阳性对照:同目录 13 个文件有 export function、matrix.js 有活跃消费方,故检索本身有分辨力。⚠️ 本条 2026-09-30 一度被我改成 pending,依据是「group-dispatch.js 消费 reconfigure / confirm」—— 那是读了落后 693 个提交的本地工作树得出的,该判断作废;后端端点仍全部保留可用,日后恢复页面时按 specWarnings[].code 分支弹提醒即可。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 团期配车提交 / 确认响应新增 `specWarnings` 车辆规格提醒清单
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8089)
|
||||
> **PR**: #8602
|
||||
> **Issue**: #8576
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期配车写口两个端点 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` 与 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` 的响应体各新增一个 `specWarnings` 数组
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 两个端点的响应体**各新增一个字段** `specWarnings`(`List<GroupDispatchSpecWarningRespVO>`)。**没有删字段、没有改名、没有改类型**,既有字段与错误码一个都没动。
|
||||
- 🔴 **`specWarnings` 不是错误**:它出现在 **HTTP 200 + 业务成功**的响应里。提交照常成功、确认照常成功、`coverage.satisfied` 照常按「排没排满」给结论。它回答的是另一个问题——**排上的那辆车,是不是这个分组当初要的那一类、那么多座**。前端不要把它当失败处理,也不要因为它非空就回滚本地状态。
|
||||
- 之前团期配车这条路**完全不读车辆实体的车型与座位**:声明 16 座大巴、实际派进 5 座 SUV,一路能确认到终态且零信号。本次补的就是这个信号。
|
||||
- 提醒分两类,**字段分两组、互不相干**,前端按 `code` 分支取值即可(另一组字段在各自提醒里恒为 `null`):
|
||||
- `WARN_VEHICLE_TYPE_MISMATCH`(**车级**):点名到某一辆车,带 `vehicleId` / `vehiclePlate` / `declaredVehicleType` / `actualVehicleType`。
|
||||
- `WARN_GROUP_SEATS_BELOW_SPEC`(**组级**,一个分组最多一条):点名到组和不达标的服务日,带 `declaredSeats` / `declaredVehicleCount` / `declaredSeatTotal` / `actualSeatTotal` / `shortageDates`。
|
||||
- 两端的作用域不同,别混读:
|
||||
- **reconfigure**:只覆盖**本次提交新增或就地改过**的「分组 + 车辆」组合,不含本次没动的存活行(否则一次只改司机的提交会把历史遗留的不符行一起刷出来)。
|
||||
- **confirm**:只覆盖**本次由「已派车」推进为「已确认」**的那些行。所以**重复确认(幂等重放)时恒为空列表**——那一次没有任何行被推进,清单的分母是空的。
|
||||
- **无提醒时是空数组 `[]`,不会是 `null`**,可直接 `v-for`。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期配车的「声明」来自正式团级用车需求的乘车分组(组码、车型、单车座位数 `seats`、每日车辆数 `vehicleCount`),「实际」来自车辆档案(车型 `typeKey`、座位数)。改前这两侧从来没被比对过,派错车型 / 座位不够在整条链路上零信号。
|
||||
|
||||
本次做成**提醒而不是硬拒**有两条已定口径的原因:
|
||||
|
||||
| 维度 | 为什么不做成错误码 |
|
||||
|------|-------------------|
|
||||
| 座位不足 | 就绪判定里它已经是 wx 在 #7444 D9 拍板的「只提醒」档(`GroupDispatchReadinessService.WARN_SEAT_SHORTAGE`),硬拒会与该定案冲突 |
|
||||
| 车型不符 | 两侧处在同一字典的不同归一层级,且两侧都合法地存在取不到值的行(车辆大类行缺失 / 存量需求的历史自由文本),硬拒会把现在能正常干活的分派拦下来 |
|
||||
|
||||
与既有的 `GroupDispatchReadinessItemVO` 分工不同:那一份比的是「已扣司机座的可载客数 vs 该日实际用车人数」,回答「坐不坐得下」;本份比的是「名义座位合计 vs 需求方声明的计划容量 `seats × vehicleCount`」,回答「派的车是不是按计划来的」。右值不同源,不是重复。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 整团逐日配车提交 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` | 响应新增字段 | 新增 `specWarnings`,覆盖本次新增或就地改过的组+车组合 |
|
||||
| 2 | 确认整团配车 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` | 响应新增字段 | 新增 `specWarnings`,覆盖本次被推进为「已确认」的行;重复确认恒为空 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 整团逐日配车提交 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure`
|
||||
|
||||
**VO**: `GroupDispatchReconfigureReqVO → GroupDispatchReconfigureRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期配车页点「提交」时调用,按乘车分组提交整团逐日配车计划,服务端与现状差量比对(多删少补,旧记录软删留痕)。权限点 `fleet:group-dispatch:write`。本次改动只在响应里多加一个提醒清单,提交本身的行为与校验一条都没变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| `requirementId` | Body | Long | ✅ | `@NotNull` | 正式团级用车需求 ID,必须等于基线当前活跃需求,落后抛 602005 |
|
||||
| `requirementVersion` | Body | Integer | ✅ | `@NotNull` | 正式团级用车需求版本,同上 |
|
||||
| `clearAll` | Body | Boolean | ❌ | - | 显式整团清零标志;为 `true` 时 `demands` 只当待清日用,不会写入任何配车行 |
|
||||
| `reconfigureWindowToken` | Body | String | ❌ | 团期已过资源准备阶段时必填 | 受控重开窗口令牌 |
|
||||
| `survivorPolicy` | Body | String | ❌ | `clearAll=true` 且存在 active 共用关系时必填 | 幸存共用派单处置策略 |
|
||||
| `demands` | Body | Array | ❌ | `@Valid`;`clearAll=false` 时必填 | 逐日配车需求列表 |
|
||||
| `demands[].tripDate` | Body | String(`yyyy-MM-dd`) | ✅ | `@NotNull` | 行程日期 |
|
||||
| `demands[].assignments` | Body | Array | ✅ | `@Valid` | 当日排车项列表 |
|
||||
| `demands[].assignments[].groupId` | Body | String | ✅ | `@NotBlank`,`@Size(max=64)` | 乘车分组键(= 需求侧 `group_code`),空值返 HTTP 业务 400「乘车分组不能为空」 |
|
||||
| `demands[].assignments[].vehicleId` | Body | Long | ✅ | `@NotNull` | 派出车辆 ID |
|
||||
| `demands[].assignments[].driverId` | Body | Long | ❌ | - | 派出司机 ID;可空 = 仅排车未排司机 |
|
||||
| `demands[].assignments[].remark` | Body | String | ❌ | `@Size(max=200)` | 备注 |
|
||||
|
||||
#### 出参 `Result<GroupDispatchReconfigureRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | String | 团期主订单 ID(雪花,字符串) |
|
||||
| `requirementId` | String | 正式团级用车需求 ID(雪花,字符串) |
|
||||
| `requirementVersion` | Integer | 正式团级用车需求版本 |
|
||||
| `planVersion` | Long | 团期计划版本 |
|
||||
| `addedCount` | Integer | 新增派车记录数 |
|
||||
| `removedCount` | Integer | 软删派车记录数 |
|
||||
| `keptCount` | Integer | 保留未变派车记录数 |
|
||||
| `updatedCount` | Integer | 就地更新派车记录数 |
|
||||
| `aliveCount` | Integer | 存活派车记录总数 |
|
||||
| `addedDispatchIds` | Array\<String\> | 新增派车记录主键列表(雪花,字符串) |
|
||||
| `idempotentShortCircuit` | Boolean | 本次是否被计划去重短路;`true` 是**幂等成功**,不是失败 |
|
||||
| `coverage` | Object | 按乘车分组的覆盖明细,见下 |
|
||||
| `coverage.groups[]` | Array | 每组:`groupCode` / `vehicleType` / `requiredDates` / `coveredDates` / `missingDates` / `outOfRangeDates` / `satisfied` |
|
||||
| `coverage.missingGroupCodes` | Array\<String\> | 整组未提交的组码 |
|
||||
| `coverage.wholeBatchSatisfied` | Boolean | 全团行程日整体覆盖是否成立 |
|
||||
| `legacyGroupRowCount` | Integer | 无分组键的历史派车行数(非错误,仅留痕) |
|
||||
| `releasedShareGroupIds` | Array\<String\> | 本次连带解除的共用关系 ID 清单 |
|
||||
| `keptSourceIds` | Array\<String\> | 保留占用的 claim 来源 ID 清单 |
|
||||
| `releasedSourceIds` | Array\<String\> | 占用已被真正释放的派单 ID 清单 |
|
||||
| `pendingReassignSourceIds` | Array\<String\> | 待人工改派的派单 ID 清单(占用已释放,当前无车) |
|
||||
| `ignoredDemandDays` | Array\<String\> | 因 `clearAll=true` 未被写入的行程日清单;`clearAll=false` 时为空列表 |
|
||||
| `specWarnings` | Array | 🆕 所派车辆与分组声明不符的提醒清单(**非错误,不影响提交成败**);无提醒为空数组 |
|
||||
| `specWarnings[].code` | String | `WARN_VEHICLE_TYPE_MISMATCH` / `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].message` | String | 中文描述,已点名到组与车牌 / 服务日,可直接展示 |
|
||||
| `specWarnings[].groupCode` | String | 相关乘车分组码;**两类提醒都有值** |
|
||||
| `specWarnings[].vehicleId` | String | 相关车辆 ID(雪花,字符串);**仅 `WARN_VEHICLE_TYPE_MISMATCH`** |
|
||||
| `specWarnings[].vehiclePlate` | String | 相关车牌;车辆档案未录车牌时为 null(`message` 里已退回 `ID=车辆ID`) |
|
||||
| `specWarnings[].declaredVehicleType` | String | 分组声明车型(归一后的规范大类 key,如 `bus`);**仅 `WARN_VEHICLE_TYPE_MISMATCH`** |
|
||||
| `specWarnings[].actualVehicleType` | String | 车辆实际车型(归一后的规范大类 key,如 `suv`);**仅 `WARN_VEHICLE_TYPE_MISMATCH`** |
|
||||
| `specWarnings[].declaredSeats` | Integer | 分组声明的**单车**座位数(含司机座);**仅 `WARN_GROUP_SEATS_BELOW_SPEC`** |
|
||||
| `specWarnings[].declaredVehicleCount` | Integer | 分组声明的**每日**车辆数;**仅 `WARN_GROUP_SEATS_BELOW_SPEC`** |
|
||||
| `specWarnings[].declaredSeatTotal` | Integer | 每日总容量 = `declaredSeats × declaredVehicleCount`,**后端算好回传,前端不要自己乘**;**仅 `WARN_GROUP_SEATS_BELOW_SPEC`** |
|
||||
| `specWarnings[].actualSeatTotal` | Integer | 不达标服务日里**最低**那一天的实际座位合计;**仅 `WARN_GROUP_SEATS_BELOW_SPEC`** |
|
||||
| `specWarnings[].shortageDates` | Array\<String\> | 实际座位合计低于声明总容量的服务日,升序;**仅 `WARN_GROUP_SEATS_BELOW_SPEC`** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementId": 5501,
|
||||
"requirementVersion": 3,
|
||||
"clearAll": false,
|
||||
"demands": [
|
||||
{
|
||||
"tripDate": "2026-09-13",
|
||||
"assignments": [
|
||||
{ "groupId": "BUS", "vehicleId": 1001, "driverId": 2001, "remark": "AA 团 7 座商务" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"tripDate": "2026-09-14",
|
||||
"assignments": [
|
||||
{ "groupId": "BUS", "vehicleId": 1001, "driverId": 2001, "remark": null }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "8801",
|
||||
"requirementId": "5501",
|
||||
"requirementVersion": 3,
|
||||
"planVersion": 7,
|
||||
"addedCount": 2,
|
||||
"removedCount": 0,
|
||||
"keptCount": 3,
|
||||
"updatedCount": 0,
|
||||
"aliveCount": 5,
|
||||
"addedDispatchIds": ["9001", "9002"],
|
||||
"idempotentShortCircuit": false,
|
||||
"coverage": {
|
||||
"groups": [
|
||||
{
|
||||
"groupCode": "BUS",
|
||||
"vehicleType": "bus",
|
||||
"requiredDates": ["2026-09-13", "2026-09-14"],
|
||||
"coveredDates": ["2026-09-13", "2026-09-14"],
|
||||
"missingDates": [],
|
||||
"outOfRangeDates": [],
|
||||
"satisfied": true
|
||||
}
|
||||
],
|
||||
"missingGroupCodes": [],
|
||||
"wholeBatchSatisfied": true
|
||||
},
|
||||
"legacyGroupRowCount": 0,
|
||||
"releasedShareGroupIds": [],
|
||||
"keptSourceIds": [],
|
||||
"releasedSourceIds": [],
|
||||
"pendingReassignSourceIds": [],
|
||||
"ignoredDemandDays": [],
|
||||
"specWarnings": [
|
||||
{
|
||||
"code": "WARN_VEHICLE_TYPE_MISMATCH",
|
||||
"message": "分组 BUS 声明车型 大巴客车,所派车辆 蒙P318A 实际为 SUV",
|
||||
"groupCode": "BUS",
|
||||
"vehicleId": "1001",
|
||||
"vehiclePlate": "蒙P318A",
|
||||
"declaredVehicleType": "bus",
|
||||
"actualVehicleType": "suv",
|
||||
"declaredSeats": null,
|
||||
"declaredVehicleCount": null,
|
||||
"declaredSeatTotal": null,
|
||||
"actualSeatTotal": null,
|
||||
"shortageDates": null
|
||||
},
|
||||
{
|
||||
"code": "WARN_GROUP_SEATS_BELOW_SPEC",
|
||||
"message": "分组 BUS 声明每日总容量 16 座(单车 16 座 × 1 辆),实际座位合计最低仅 5 座,涉及 2 个服务日:[2026-09-13, 2026-09-14]",
|
||||
"groupCode": "BUS",
|
||||
"vehicleId": null,
|
||||
"vehiclePlate": null,
|
||||
"declaredVehicleType": null,
|
||||
"actualVehicleType": null,
|
||||
"declaredSeats": 16,
|
||||
"declaredVehicleCount": 1,
|
||||
"declaredSeatTotal": 16,
|
||||
"actualSeatTotal": 5,
|
||||
"shortageDates": ["2026-09-13", "2026-09-14"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无提醒时 `specWarnings` 是**空数组**,不是 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "8801",
|
||||
"addedCount": 0,
|
||||
"removedCount": 0,
|
||||
"keptCount": 5,
|
||||
"updatedCount": 0,
|
||||
"aliveCount": 5,
|
||||
"idempotentShortCircuit": true,
|
||||
"specWarnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**降级(fail-open)规则** —— 下列情况提醒**不产出**,`specWarnings` 少一条或为空,这是设计行为不是丢数据:
|
||||
|
||||
- 分组不在基线的权威分组清单里 → 该组整组跳过。
|
||||
- 车辆在本次的车辆档案快照里取不到 → 该车跳过。
|
||||
- 声明侧或实际侧任一方的车型归一不出规范 key(车辆大类行缺失 / 存量需求的历史自由文本)→ 不报车型不符。
|
||||
- 分组的 `seats` 或 `vehicleCount` 为 null 或 ≤ 0 → 不做容量判定。
|
||||
- 某个(分组 + 服务日)格里**只要有一辆车**的座位数取不到或 ≤ 0 → **整格不判**(不把缺值折成 0,折 0 会产出一个不存在的缺口)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
既有错误码一条都没变。示例(分组不在本团需求内,消息模板 `乘车分组不存在于本团正式需求: {0}`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 602001,
|
||||
"message": "乘车分组不存在于本团正式需求: VAN",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
完整错误码:602000(排车项缺分组,服务层兜底;admin 口由入参校验先拦下返 400「乘车分组不能为空」)/ 602001 分组不在本团需求内 / 602002 整组未排车 / 602003 该组服务日未排满 / 602004 该组排了本组服务范围外的日期 / 602005 需求身份或版本已变 / 602006 需求状态不允许 / 602009 取不到权威分组清单 / 600003 重复行程日 / 600004 单日排车为空 / 600005 缺车辆 ID / 600006 车辆被占 / 600007 司机被占 / 600008 并发修改 / 600009 基线不可用 / 600010 团期状态不可配 / 600011 全团服务日未覆盖满 / 605037 车辆维保或停用不可派 / 605038 司机休假或待激活不可派 / 605006 司机已黑名单 / 605013 司机非在册赛季不可派单。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `fleet:group-dispatch:write`(与读口 `fleet:group-dispatch:view` 分开);未登录由网关拦截返 401。
|
||||
- **`specWarnings` 非错误**:HTTP 200 + `success=true` 的响应里出现,提交已成功落库。不要据此回滚本地状态或阻断后续动作。
|
||||
- **作用域**:只覆盖**本次提交新增或就地改过**的「分组 + 车辆」组合;本次没动的存活行不重判(一次只改司机的提交不会把历史遗留的不符行刷出来)。
|
||||
- **两类提醒字段分组互斥**:按 `code` 分支取值,另一组字段恒 `null`。
|
||||
- **`declaredSeatTotal` 由后端算好**:口径(含不含司机座、按不按日)只有一份权威,前端不要复算。
|
||||
- **`actualSeatTotal` 是最低值不是明细**:它与 `declaredSeatTotal` 一起答完「最坏差多少」,不需要拿 `shortageDates` 反查每一天。
|
||||
- **防重提交与幂等是两件事**:10 秒内对同一份计划重复提交会被防重窗口**拒绝**(返「团期配车重配处理中,请勿重复提交」);窗口之外重复提交同一份计划会正常受理并返回 `idempotentShortCircuit=true`,**那是成功**。
|
||||
- **`clearAll=true` 时必须读 `ignoredDemandDays`**:否则「清完并按新计划重排」与「只清空」在响应里长得一模一样(两者 `addedCount` 都是 0)。
|
||||
- **雪花 ID 一律是字符串**:`groupBatchId` / `requirementId` / `addedDispatchIds[]` / `specWarnings[].vehicleId` 等都以字符串下发。
|
||||
|
||||
---
|
||||
|
||||
### 2. 确认整团配车 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm`
|
||||
|
||||
**VO**: `GroupDispatchConfirmReqVO → GroupDispatchConfirmRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期配车页点「确认」时调用,把该团全部「已派车」的配车行转为「已确认」,并登记一条把正式用车需求推进到「已发车务」的异步回写意图。权限点与提交写口同一个 `fleet:group-dispatch:write`。本次改动只在响应里多加一个提醒清单。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | - | 团期主订单 ID |
|
||||
| `requirementId` | Body | Long | ✅ | `@NotNull` | 正式团级用车需求 ID |
|
||||
| `requirementVersion` | Body | Integer | ✅ | `@NotNull` | 正式团级用车需求版本 |
|
||||
| `remark` | Body | String | ❌ | `@Size(max=200)` | 确认备注,**仅留痕**,不写入配车行 |
|
||||
|
||||
#### 出参 `Result<GroupDispatchConfirmRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | String | 团期主订单 ID(雪花,字符串) |
|
||||
| `confirmedCount` | Integer | 本次由「已派车」转为「已确认」的配车行数;**重复确认为 0,属幂等成功** |
|
||||
| `alreadyConfirmedCount` | Integer | 确认前就已是「已确认」的配车行数(重复确认时全部落在这里) |
|
||||
| `requirementId` | String | 本次确认所依据的正式团级用车需求 ID(雪花,字符串) |
|
||||
| `requirementVersion` | Integer | 本次确认所依据的需求版本 |
|
||||
| `planVersion` | Long | 当前团期计划版本(确认不改计划,故不递增) |
|
||||
| `requirementAdvanceIntent` | String | 已登记的需求回写意图方向,恒为 `CONFIRMED_TO_DISPATCHED` |
|
||||
| `coverage` | Object | 按乘车分组的覆盖明细(确认前对库里现存配车行重判一次的结果),结构同 reconfigure |
|
||||
| `legacyGroupRowCount` | Integer | 本团存活派车行里没有乘车分组键的历史行数;非零时 602008 的缺口很可能正是它们造成的 |
|
||||
| `specWarnings` | Array | 🆕 本次被推进为「已确认」的行里,所派车辆与分组声明不符的提醒清单(**非错误,确认已成功**);无提醒为空数组 |
|
||||
| `specWarnings[].code` | String | `WARN_VEHICLE_TYPE_MISMATCH` / `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].message` | String | 中文描述,已点名到组与车牌 / 服务日,可直接展示 |
|
||||
| `specWarnings[].groupCode` | String | 相关乘车分组码;两类提醒都有值 |
|
||||
| `specWarnings[].vehicleId` | String | 相关车辆 ID(雪花,字符串);仅 `WARN_VEHICLE_TYPE_MISMATCH` |
|
||||
| `specWarnings[].vehiclePlate` | String | 相关车牌;未录车牌时为 null |
|
||||
| `specWarnings[].declaredVehicleType` | String | 分组声明车型(规范 key);仅 `WARN_VEHICLE_TYPE_MISMATCH` |
|
||||
| `specWarnings[].actualVehicleType` | String | 车辆实际车型(规范 key);仅 `WARN_VEHICLE_TYPE_MISMATCH` |
|
||||
| `specWarnings[].declaredSeats` | Integer | 分组声明单车座位数;仅 `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].declaredVehicleCount` | Integer | 分组声明每日车辆数;仅 `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].declaredSeatTotal` | Integer | 每日总座位数(后端算好);仅 `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].actualSeatTotal` | Integer | 不达标日中的最低实际座位合计;仅 `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
| `specWarnings[].shortageDates` | Array\<String\> | 不达标的服务日(升序);仅 `WARN_GROUP_SEATS_BELOW_SPEC` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"requirementId": 5501,
|
||||
"requirementVersion": 3,
|
||||
"remark": "与地接确认车辆无误"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "8801",
|
||||
"confirmedCount": 8,
|
||||
"alreadyConfirmedCount": 0,
|
||||
"requirementId": "5501",
|
||||
"requirementVersion": 3,
|
||||
"planVersion": 7,
|
||||
"requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED",
|
||||
"coverage": {
|
||||
"groups": [
|
||||
{
|
||||
"groupCode": "BUS",
|
||||
"vehicleType": "bus",
|
||||
"requiredDates": ["2026-09-13", "2026-09-14"],
|
||||
"coveredDates": ["2026-09-13", "2026-09-14"],
|
||||
"missingDates": [],
|
||||
"outOfRangeDates": [],
|
||||
"satisfied": true
|
||||
}
|
||||
],
|
||||
"missingGroupCodes": [],
|
||||
"wholeBatchSatisfied": true
|
||||
},
|
||||
"legacyGroupRowCount": 0,
|
||||
"specWarnings": [
|
||||
{
|
||||
"code": "WARN_VEHICLE_TYPE_MISMATCH",
|
||||
"message": "分组 BUS 声明车型 大巴客车,所派车辆 蒙P318A 实际为 SUV",
|
||||
"groupCode": "BUS",
|
||||
"vehicleId": "1001",
|
||||
"vehiclePlate": "蒙P318A",
|
||||
"declaredVehicleType": "bus",
|
||||
"actualVehicleType": "suv",
|
||||
"declaredSeats": null,
|
||||
"declaredVehicleCount": null,
|
||||
"declaredSeatTotal": null,
|
||||
"actualSeatTotal": null,
|
||||
"shortageDates": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
**重复确认(幂等重放)**:`confirmedCount=0`、`alreadyConfirmedCount=N`、`specWarnings` 恒为**空数组**(本次没有任何行被推进,清单的分母是空的)。HTTP 仍是 200,**这是成功不是失败**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "8801",
|
||||
"confirmedCount": 0,
|
||||
"alreadyConfirmedCount": 8,
|
||||
"requirementId": "5501",
|
||||
"requirementVersion": 3,
|
||||
"planVersion": 7,
|
||||
"requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED",
|
||||
"legacyGroupRowCount": 0,
|
||||
"specWarnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**降级(fail-open)规则**与 reconfigure 端点逐条相同:分组不在基线 / 车辆取不到 / 任一侧车型归一不出规范 key / `seats` 或 `vehicleCount` 为 null 或 ≤0 / 某(组+日)格里有一辆车座位取不到 → 对应提醒不产出。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
既有错误码一条都没变。示例(现存配车对当前需求仍不完整,消息模板 `配车尚未覆盖完整, 不能确认: {0}`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 602008,
|
||||
"message": "配车尚未覆盖完整, 不能确认: 乘车分组 BUS 的服务日未排满, 缺失: [2026-09-15]",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
完整错误码:602007 本团无可确认的配车行 / 602008 现存配车对当前需求仍不完整 / 602005 需求身份或版本已变 / 602006 需求状态不允许 / 602009 取不到权威分组清单 / 600008 并发修改 / 600009 基线不可用 / 605037 车辆维保或停用不可派 / 605038 司机休假或待激活不可派 / 605006 司机已黑名单 / 605013 司机非在册赛季不可派单。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `fleet:group-dispatch:write`(与提交写口同一个);未登录由网关拦截返 401。
|
||||
- **`specWarnings` 非错误**:确认已经成功。它是终态前的最后一次复核——重配与确认之间车辆档案可能被改过,也可能有行绕过重配直接进来。
|
||||
- **作用域**:只覆盖**本次由「已派车」推进为「已确认」**的那些行;已是「已确认」的行不重判。
|
||||
- **重复确认时恒为空列表**:不要把「第二次点确认没有提醒」理解成「问题已经消失」。
|
||||
- **本端点没有防重提交时间窗**:连点多少次都是 `confirmedCount=0 / alreadyConfirmedCount=N` 这个形态,不会出现「请勿重复提交」这类错误码;同团的并发调用由服务端串行化。
|
||||
- **异步回写**:响应成功只代表车务侧已确认并已把回写意图可靠登记,正式用车需求的状态可能稍后才变成「已发车务」,需求页需自行刷新。
|
||||
- **回写会推进需求版本但不会让重复确认变成错误**:首次确认成功后正式用车需求被推进一版(status 转 DISPATCHED),此时本端点跳过需求版本与状态的严格校验,仍返回 200 + 两个计数。
|
||||
- **不校验团期是否可配**:那道门禁管的是「还能不能改车」,确认不改车。
|
||||
- **雪花 ID 一律是字符串**。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受 / 拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ✅ 正常提交两天配车 | `{ "requirementId": 5501, "requirementVersion": 3, "clearAll": false, "demands": [ { "tripDate": "2026-09-13", "assignments": [ { "groupId": "BUS", "vehicleId": 1001 } ] } ] }` |
|
||||
| ✅ 整团清零 | `{ "requirementId": 5501, "requirementVersion": 3, "clearAll": true, "demands": [] }` |
|
||||
| ✅ 确认(带留痕备注) | `{ "requirementId": 5501, "requirementVersion": 3, "remark": "与地接确认车辆无误" }` |
|
||||
| ✅ 确认(不带备注) | `{ "requirementId": 5501, "requirementVersion": 3 }` |
|
||||
| ❌ 排车项缺分组键 | `{ ..., "assignments": [ { "vehicleId": 1001 } ] }` → 400「乘车分组不能为空」 |
|
||||
| ❌ 缺需求版本 | `{ "requirementId": 5501, "demands": [...] }` → 400「正式团级用车需求版本不能为空」 |
|
||||
| ❌ 确认备注超 200 字 | `{ ..., "remark": "<201 字>" }` → 400「确认备注长度不能超过 200」 |
|
||||
|
||||
### 处理 `specWarnings` 的必要动作
|
||||
|
||||
- 两个端点的成功分支里都要读 `specWarnings`:非空时就地展示(`message` 已经是完整中文句子,可直接渲染),**不要**把它接到错误处理分支上。
|
||||
- 按 `code` 分支取字段,不要对全部字段做非空假设——另一类提醒的字段组恒为 `null`。
|
||||
- `declaredSeatTotal` 直接用后端回传的值,不要用 `declaredSeats × declaredVehicleCount` 自己算。
|
||||
- reconfigure 的 `specWarnings` 只反映本次动过的行:`specWarnings` 为空**不等于**全团没有不符行,只等于「本次动的这些行没有不符」。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
两个端点都是写端点,但**本次改动零写入变化**——`specWarnings` 完全由内存中的比对产出(`GroupDispatchVehicleSpecInspector` 是纯静态、无 IO),不新建表、不加列、不落任何提醒记录。
|
||||
|
||||
| 前端提交 | 配车行的写入 | 提醒的持久化 |
|
||||
|----------|--------------|--------------|
|
||||
| `reconfigure` 差量提交 | 多删少补,旧记录软删留痕(本次未变) | **不落库**,仅随本次响应下发 |
|
||||
| `reconfigure` `clearAll=true` | 清空存活行,`demands` 不写入 | **不落库** |
|
||||
| `confirm` 首次确认 | 「已派车」行 CAS 推进为「已确认」,登记回写意图(本次未变) | **不落库** |
|
||||
| `confirm` 重复确认 | 一个字段都不动 | **不落库**,且恒为空数组 |
|
||||
|
||||
因此**刷新页面或重新拉取不会再拿到同一批提醒**——提醒是本次动作的返回值,不是可查询的状态。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 权限点 `fleet:group-dispatch:write` 缺失 → 权限校验失败。
|
||||
- 团期不存在 / 基线不可用 → 600009。
|
||||
- 需求身份或版本落后 → 602005(fail-closed,不接受「反正车没变」)。
|
||||
- 10 秒内重复提交同一份 reconfigure 计划 → 被防重窗口拒绝,提示「团期配车重配处理中,请勿重复提交」。
|
||||
- 窗口外重复提交同一份计划 → 200 + `idempotentShortCircuit=true`(成功)。
|
||||
- 重复 confirm → 200 + `confirmedCount=0`、`specWarnings=[]`(成功)。
|
||||
- 车辆档案未录车牌 → `specWarnings[].vehiclePlate` 为 null,但 `message` 里退回 `ID=车辆ID`,不留空白。
|
||||
- 老数据兼容:历史派车行没有乘车分组键时不计入任何组的覆盖,计入 `legacyGroupRowCount`,也不进 `specWarnings`。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### code(车辆规格提醒项代码)
|
||||
|
||||
**所属字段**: `specWarnings[].code` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `WARN_VEHICLE_TYPE_MISMATCH` | 车型与分组声明不符 | **车级**提醒。两侧车型各自归一成规范大类 key 后不相等时产出。带 `vehicleId` / `vehiclePlate` / `declaredVehicleType` / `actualVehicleType`;其余字段为 null |
|
||||
| `WARN_GROUP_SEATS_BELOW_SPEC` | 分组座位低于声明容量 | **组级**提醒,一个分组最多一条。判据:`该组该日实际座位合计 < seats × vehicleCount`。带 `declaredSeats` / `declaredVehicleCount` / `declaredSeatTotal` / `actualSeatTotal` / `shortageDates`;其余字段为 null |
|
||||
|
||||
### declaredVehicleType / actualVehicleType(归一后的车型规范大类 key)
|
||||
|
||||
**所属字段**: `specWarnings[].declaredVehicleType`、`specWarnings[].actualVehicleType` | **类型**: `String`
|
||||
|
||||
取值是车型字典归一后的**规范大类 key**(如 `bus` / `suv`),**不是**车辆档案里的原值——车辆档案侧存的是开集原值(例如测试环境 SUV 大类的 `type_key` 实际是 `suv2`),后端归一后才比。前端如需展示中文名,用 `message` 里已经拼好的中文,不要自己拿 key 去查字典。
|
||||
|
||||
### requirementAdvanceIntent(需求回写意图方向)
|
||||
|
||||
**所属字段**: `GroupDispatchConfirmRespVO.requirementAdvanceIntent` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `CONFIRMED_TO_DISPATCHED` | 已确认 → 已发车务 | 当前**恒为此值**;表示确认成功后还有一步异步回写,需求列表页的状态可能稍后才变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `GroupDispatchReconfigureRespVO.specWarnings` | 不存在 | 🆕 `List<GroupDispatchSpecWarningRespVO>`,无提醒为空数组 |
|
||||
| `GroupDispatchConfirmRespVO.specWarnings` | 不存在 | 🆕 `List<GroupDispatchSpecWarningRespVO>`,无提醒为空数组 |
|
||||
| 两个响应体的其余全部字段 | — | 未变(无删除、无改名、无类型变化) |
|
||||
| 两个请求体 | — | 未变(一个字段都没动) |
|
||||
| 两个端点的错误码集合 | — | 未变 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 派错车型(声明大巴、实际 SUV) | 全链路零信号,一路能确认到终态 | 提交与确认的响应里各出一条 `WARN_VEHICLE_TYPE_MISMATCH` |
|
||||
| 某服务日实际座位合计低于声明容量 | 全链路零信号 | 出一条 `WARN_GROUP_SEATS_BELOW_SPEC`,带最低值与不达标日清单 |
|
||||
| 提交 / 确认的成败判定 | 按覆盖与资源可派性 | 未变——`specWarnings` 不参与成败判定 |
|
||||
| 重复确认 | `confirmedCount=0`、`alreadyConfirmedCount=N` | 未变,额外 `specWarnings=[]` |
|
||||
| 只改司机的提交 | — | 不会把历史遗留的不符行刷出来(作用域限本次动过的组+车组合) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否(纯新增字段,既有字段与错误码零变化;老前端忽略新字段即可正常工作)
|
||||
- **前端是否必须同步上线**: 否(不读新字段不会报错,只是拿不到提醒)
|
||||
- **前端 workaround 清理点**: 无(此前没有任何前端侧的车型 / 座位比对,不存在需要撤掉的本地实现)
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` 与 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` 两个响应体各新增一个数组字段。
|
||||
- **零影响**:
|
||||
- 团期配车所有读口(概览、就绪判定、矩阵、看板)
|
||||
- `GroupDispatchReadinessItemVO` 的座位就绪判定(右值不同源,本次未动)
|
||||
- 单车派单、改派、取消链路
|
||||
- order-v3 侧的正式团级用车需求存 / 读 / 撤回 / 免车
|
||||
- 车辆档案、司机档案的任何端点
|
||||
- 历史数据:提醒不落库,不做任何数据迁移
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||||
- 合并提交 `cfefe04a83`(PR #8602 squash 合并进 `dev-v3`)。
|
||||
- 新增 VO `hl-fleet-service/.../dispatch/vo/GroupDispatchSpecWarningRespVO.java`(12 个字段)与对位 Feign DTO `GroupDispatchSpecWarningDTO`。
|
||||
- 新增纯静态无 IO 的 `GroupDispatchVehicleSpecInspector`;两条提醒的消息拼装、fail-open 跳过条件、`seatTotalOrNull` 的「一辆车取不到座位就整格不判」逻辑均已逐行核对。
|
||||
- `GroupDispatchReconfigureRespVO` 与 `GroupDispatchConfirmRespVO` 各新增 `specWarnings` 字段,javadoc 分别写明作用域(本次新增/就地改过 vs 本次被推进)与「重复确认恒为空列表」。
|
||||
- **部署**:`hl-fleet-service` 的 `dev-v3` 分支已滚到测试服,两个端点走管理端网关 `/admin/fleet/**` 既有路由,无新增路由。
|
||||
|
||||
```
|
||||
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure → 200 + data.specWarnings 存在(无提醒时为 [])✓
|
||||
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm → 200 + data.specWarnings 存在(无提醒时为 [])✓
|
||||
POST .../confirm 重复调用 → 200 + confirmedCount=0 + specWarnings=[] ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #7442 | 团期配车写口(reconfigure / confirm)首次落地 | ✅ 有效 |
|
||||
| — | #7444 D9 | wx 拍板座位不足在就绪判定里只提醒不硬拒 | ✅ 有效(本单沿用该口径) |
|
||||
| — | #8195 | 需求侧车型归一后回写规范 key | ✅ 有效(本单的比对依赖它) |
|
||||
| — | #8528 | reconfigure / confirm 资源可派性硬校验(605037/605038/605006/605013) | ✅ 有效 |
|
||||
| **本 PR #8602** | **#8576** | 两个写口响应新增 `specWarnings` | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8576](https://git.1814.love:8443/wx/HL/issues/8576)
|
||||
- 关联 PR: [wx/HL#8602](https://git.1814.love:8443/wx/HL/pulls/8602)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8576](https://git.1814.love:8443/wx/HL/issues/8576)
|
||||
- **PR**: [#8602](https://git.1814.love:8443/wx/HL/pulls/8602)
|
||||
- **Merge commit**: [cfefe04a83](https://git.1814.love:8443/wx/HL/commit/cfefe04a83)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,718 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8577"
|
||||
title: "只提交了接送机的户不再被判「未提交用车需求」,809121/809122/809123 触发条件收窄且文案改写"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8600 已 squash 合并 dev-v3(5b7074e691),hl-order-service-v3 dev-v3 分支已滚测试服。三个端点的请求体、响应体字段与错误码号全部未变,变的是 809121/809122/809123 的触发条件(收窄)与消息文案(去掉「行程」二字)。【frontend_status 取 not_required 的依据,2026-09-30 对 hl-ui origin/v2.1 逐处查证】三个码的展示一律走拦截器透 message,生产代码无一处按报文字符串匹配;纯接送机户在前端也没有任何规避(按钮禁用/提示)需要撤除,故无强制前端动作。⚠️ 但有注释级陈旧需顺手清:src/api/orderV2GroupBatch.js:921,957,958 与 GroupVehicleRequirementEditModal.vue:377,874 仍写着「TRAVEL / 行程用车需求」,三个码现已按 TRAVEL ∪ TRANSFER 判「已提交」;GroupVehicleRequirementSection.spec.js:1000 的 mock 报文同样陈旧(该用例不断言文案,不会红)。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3: 只提交了接送机的户不再被判「未提交用车需求」
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8600
|
||||
> **Issue**: #8577
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期需求管理 Tab 的三个端点(保存正式用车需求 / 自动汇总草稿 / 整体确认预检)里 809121、809122、809123 的触发条件与消息文案
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **判据从「有没有提交行程用车(TRAVEL)」收窄为「两类用车需求(TRAVEL / TRANSFER)是不是一条都没有」**。改前:某户只提交了接送机需求,团级保存、自动汇总、确认预检都把它当成「一条都没交」,整团被 809123 / 809121 / 809122 卡住,而这户其实已经明确表达过「只要接送机、不要行程车」,运营**没有任何干净出路**(唯一逃生舱是整团 waive 免车,那会把真需要行程车的户一起免掉)。改后:这户算已提交,三处一律放行。
|
||||
- **三个错误码的码值没变、字段没变**,变的是**什么时候抛**(收窄)与**消息文案**(三条都去掉了「行程」二字):
|
||||
- `809121` `团期 {0} 有 {1} 户缺少可汇总的行程用车需求…` → `…缺少可汇总的用车需求…`
|
||||
- `809122` `该户尚未提交行程用车需求,请先让定制师提交后再整团提交车务` → `该户尚未提交用车需求,…`
|
||||
- `809123` `{0}有 {1} 户尚未提交行程用车需求,暂不能保存正式用车需求:{2}` → `…尚未提交用车需求,…`
|
||||
- 🔴 **前端凡是对这三条报文做过关键词匹配 / 字符串包含判断的地方必须改**(`行程用车需求` 这个子串在三条里都没了)。正确做法是按 `code` 分支,不要匹配 `message` 文本。
|
||||
- 🔴 **809109「逐日覆盖」一个字都没改,仍然只认 TRAVEL**。这是刻意的:本次分离的是「户级提交判定」与「行程覆盖判定」两件事,合并会把墙从 809123 挪到 809109,症状一模一样只是换个码。所以——**只提交接送机的户不再被判未提交,也不要求被任何乘车分组覆盖**;它结构上就在团级乘车分组之外,走逐户派车。
|
||||
- `GroupVehicleDraftAggregator` 的缺失原因文案 `未提交行程用车需求` → `未提交用车需求`。它出现在 809121 报文的逐户清单里(`「户标识:原因」`,顿号分隔),前端若展示过这个字符串同样受影响。
|
||||
- 「豁免户」`exemptHouseholds` 的语义边界也随之明确:**只提交了接送机的户既不进未提交名单、也不进豁免名单**——豁免解释的是「没提交的户为什么不拦」,而它本来就提交过。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
一户在团期里的用车需求有两类活跃行,互不替代:
|
||||
|
||||
| 类别 | 含义 | 派车路径 |
|
||||
|------|------|----------|
|
||||
| `TRAVEL` | 团期行程用车 | 汇总进团级乘车分组,整团逐日配车 |
|
||||
| `TRANSFER` | 接送机 | 逐户派车,**结构上不进团级乘车分组** |
|
||||
|
||||
改前的三处判定都只查 `TRAVEL`。于是「只要接送机、不要行程车」这种完全合法的在团户(与 #7972 (A) 对 809114 的定案同源)被读成「什么都没交」。团级保存直接 809123 整份拒绝、自动汇总 809121 整团出不来草稿、确认预检 809122 逐户挂红——**运营改不动、催不动(该户定制师已经交过了)、也绕不过去**。
|
||||
|
||||
本次把判定拆成两个集合(`travelSubmittedOrderIds` / `anySubmittedOrderIds`),单源仍只有一份,在 `GroupVehicleRequirementService#classifyVehicleSubmission`(原名 `classifyTravelSubmission`),保存、预检、自动汇总三处共用:
|
||||
|
||||
- **户级「交了没有」** → 用 `anySubmittedOrderIds`(两类任一即算交了)→ 管 809121 / 809122 / 809123;
|
||||
- **行程逐日覆盖** → 仍用 `travelSubmittedOrderIds`(只认 TRAVEL)→ 管 809109。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 保存团期正式用车需求(全量替换) | PUT | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 错误码触发条件收窄 + 文案改写 | 809123 不再对「只提交接送机」的户触发;报文去掉「行程」 |
|
||||
| 2 | 自动汇总正式用车需求草稿 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft` | 错误码触发条件收窄 + 文案改写 | 809121 同上;缺失原因文案同步改写 |
|
||||
| 3 | 整体确认需求缺失预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 缺失项触发条件收窄 + 文案改写 | `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`(809122)同上 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 保存团期正式用车需求(全量替换) `PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement`
|
||||
|
||||
**VO**: `GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期需求管理 Tab 的「正式用车需求」编辑弹窗点保存时调用,**整份全量替换**(未出现在本次提交里的分组会被移出当前版本)。权限点 `group-batch:demand:confirm`。本次改动只让 809123 少抛一类情况、并改了它的报文,请求体与响应体一个字段都没动。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | - | 团期 ID |
|
||||
| `version` | Body | Integer | ❌ | 乐观锁 | **首次保存传 null**,后续必须回传上次 GET / PUT 拿到的值;不一致抛 809102 |
|
||||
| `remark` | Body | String | ❌ | `@Size(max=500)` | 整份需求备注 |
|
||||
| `groups` | Body | Array | ✅ | `@NotNull`(**不是** `@NotEmpty`)、`@Valid` | 全部乘车分组;空数组是合法提交(有需车户时由 809103 拦),整团免车请改走 `waive` 端点 |
|
||||
| `groups[].groupId` | Body | Long | ❌ | - | 既有分组主键;**新增分组传 null**。带上它 = 声明「就是库里那一组」,此时 `groupCode` 不得变更(改名抛 809104) |
|
||||
| `groups[].groupCode` | Body | String | ✅ | `@NotBlank`,`@Size(max=32)` | 分组键,直接作为车费 `alloc_group` |
|
||||
| `groups[].vehicleType` | Body | String | ✅ | `@NotBlank`,`@Size(max=64)` | 车型大类编码,**不是自由文本**;取值权威见 `GET /internal/fleet/vehicle-types/category-names`,不在字典内抛 809119 |
|
||||
| `groups[].serviceStartDate` | Body | String(`yyyy-MM-dd`) | ✅ | `@NotNull` | 本组服务开始日 |
|
||||
| `groups[].serviceEndDate` | Body | String(`yyyy-MM-dd`) | ✅ | `@NotNull` | 本组服务结束日(须不早于开始日) |
|
||||
| `groups[].seats` | Body | Integer | ❌ | `@Min(1)` | 该组单车座位数;**刻意非必填**(存量分组没有该值),与 `count` 必须同填或同空(809118),且须在该车型可选档位内(809124) |
|
||||
| `groups[].count` | Body | Integer | ❌ | `@Min(1)` | 该组车辆数量;同上 |
|
||||
| `groups[].specialTags` | Body | Array\<String\> | ❌ | 值须在字典 `vehicle_special_demand` 内 | 特殊诉求标签编码数组;含字典外编码整份拒绝(809117) |
|
||||
| `groups[].remark` | Body | String | ❌ | `@Size(max=500)` | 该组备注 |
|
||||
| `groups[].days` | Body | Array | ✅ | `@NotEmpty`,`@Valid` | 逐日用车人数与成员,**不能用单值人数代替** |
|
||||
| `groups[].days[].tripDate` | Body | String(`yyyy-MM-dd`) | ✅ | `@NotNull` | 团期行程日,须落在本组服务日范围内且不缺日(809105 / 809106) |
|
||||
| `groups[].days[].headcount` | Body | Integer | ✅ | `@NotNull`,`@Min(1)` | 该组该日**乘车人数**(不是户数);小于当日成员户数抛 809110 |
|
||||
| `groups[].days[].memberOrderIds` | Body | Array\<Long\> | ✅ | `@NotEmpty` | 该组该日实际乘车的子订单集合,须全属本团在团户(809107),同一户同一日只能属一个分组(809108) |
|
||||
|
||||
#### 出参 `Result<GroupVehicleRequirementRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `requirementId` | String | 正式用车需求 ID(雪花,字符串) |
|
||||
| `groupBatchId` | String | 团期 ID(雪花,字符串) |
|
||||
| `status` | String | 需求状态 |
|
||||
| `version` | Integer | 乐观锁版本,下次保存必须回传 |
|
||||
| `remark` | String | 整份需求备注 |
|
||||
| `confirmedBy` | String | 确认人 |
|
||||
| `confirmedAt` | String(datetime) | 确认时间 |
|
||||
| `planRefreshState` | String | 配车刷新状态(只读投影) |
|
||||
| `planRefreshReplayCount` | Integer | 配车刷新重投次数 |
|
||||
| `blockedStage` | String | 被卡住的阶段 |
|
||||
| `planRefreshStalled` | Boolean | 配车刷新是否已停滞 |
|
||||
| `planRefreshStalledReason` | String | 停滞原因 |
|
||||
| `planRefreshTimeoutAt` | String(datetime) | 刷新超时时刻 |
|
||||
| `planRefreshReplayExhausted` | Boolean | 重投次数是否已用尽 |
|
||||
| `groups` | Array | 乘车分组回显 |
|
||||
| `groups[].groupId` / `groupCode` / `vehicleType` / `vehicleTypeName` | String | 分组主键(字符串)、分组键、车型大类编码、车型中文名(按归一 key 取) |
|
||||
| `groups[].serviceStartDate` / `serviceEndDate` | String(`yyyy-MM-dd`) | 本组服务日范围 |
|
||||
| `groups[].seats` / `count` / `totalSeatCount` / `maxHeadcount` / `remainingPassengerSeats` | Integer | 单车座位数 / 车辆数 / 总座位 / 最大日人数 / 剩余可载客座位 |
|
||||
| `groups[].specialTags[]` | Array | `code` + `name`(中文名后端下发,前端不自己映射) |
|
||||
| `groups[].remark` | String | 该组备注 |
|
||||
| `groups[].days[]` | Array | `tripDate` / `headcount` / `memberOrderIds`(字符串数组) / `memberOrderCount` |
|
||||
| `exemptHouseholds` | Array | 豁免户(在团需车、两类需求都没有活跃行、但定制师**提交不了**的户);🔴 **只提交了接送机的户不在这里**——它已提交 |
|
||||
| `exemptHouseholds[].orderId` | String | 子订单 ID(雪花,字符串) |
|
||||
| `exemptHouseholds[].teamNo` | String | 团号 |
|
||||
| `exemptHouseholds[].orderNo` | String | 子订单号 |
|
||||
| `exemptHouseholds[].reason` | String | `ORDER_NOT_CUSTOMIZING` / `REQUIREMENT_FROZEN` |
|
||||
| `exemptHouseholds[].reasonName` | String | 豁免原因中文名(后端下发,前端不自己映射) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 3,
|
||||
"remark": "9/13 起换大巴",
|
||||
"groups": [
|
||||
{
|
||||
"groupId": null,
|
||||
"groupCode": "BUS",
|
||||
"vehicleType": "bus",
|
||||
"serviceStartDate": "2026-09-12",
|
||||
"serviceEndDate": "2026-09-16",
|
||||
"seats": 19,
|
||||
"count": 1,
|
||||
"specialTags": ["CHILD_SEAT"],
|
||||
"remark": "含高速费",
|
||||
"days": [
|
||||
{
|
||||
"tripDate": "2026-09-12",
|
||||
"headcount": 9,
|
||||
"memberOrderIds": [2099459272533323777, 2099459272533323778]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"requirementId": "2099459272533400001",
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"status": "DRAFT",
|
||||
"version": 4,
|
||||
"remark": "9/13 起换大巴",
|
||||
"confirmedBy": null,
|
||||
"confirmedAt": null,
|
||||
"planRefreshState": null,
|
||||
"planRefreshStalled": false,
|
||||
"groups": [
|
||||
{
|
||||
"groupId": "1867000000009",
|
||||
"groupCode": "BUS",
|
||||
"vehicleType": "bus",
|
||||
"vehicleTypeName": "大巴客车",
|
||||
"serviceStartDate": "2026-09-12",
|
||||
"serviceEndDate": "2026-09-16",
|
||||
"seats": 19,
|
||||
"count": 1,
|
||||
"totalSeatCount": 19,
|
||||
"maxHeadcount": 9,
|
||||
"remainingPassengerSeats": 9,
|
||||
"specialTags": [{ "code": "CHILD_SEAT", "name": "儿童座椅" }],
|
||||
"remark": "含高速费",
|
||||
"days": [
|
||||
{
|
||||
"tripDate": "2026-09-12",
|
||||
"headcount": 9,
|
||||
"memberOrderIds": ["2099459272533323777", "2099459272533323778"],
|
||||
"memberOrderCount": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"exemptHouseholds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
该团期**只有接送机户、没有任何行程用车户**时,提交零分组不再被 809123 拦(本次改动的直接效果);若团里确实还有需车户,零分组仍由 809103 拦下。`exemptHouseholds` 为空时是**空数组**不是 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"requirementId": "2099459272533400001",
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"status": "DRAFT",
|
||||
"version": 1,
|
||||
"groups": [],
|
||||
"exemptHouseholds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
809123(触发条件已收窄、文案已改写;`{0}` 是团期人话标识,`{2}` 按团号列户、无团号回落订单号、都缺时为「某子订单」,顿号分隔;以下为测试服实测原文):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809123,
|
||||
"message": "团期「第22期 8月喀纳斯湖秋色三日游」有 1 户尚未提交用车需求,暂不能保存正式用车需求:26-6538",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余错误码一条都没变:809100 团期尚未形成正式用车需求 / 809101 状态不允许 / 809102 已被他人修改(乐观锁) / 809103 有需车户却零分组 / 809104 分组重复或试图改名 / 809105 逐日行不在本组服务日范围内或重复 / 809106 缺逐日用车人数 / 809107 成员不属于本团期 / 809108 同一户同一日属多个分组 / 809109 该子订单的某日没有被任何乘车分组覆盖(**仍只认 TRAVEL**) / 809110 用车人数小于当日成员户数 / 809111 团期状态不允许编辑 / 809115 已声明整团免车需先 withdraw / 809116 座位不足 / 809117 特殊诉求标签不在字典内 / 809118 座位数与车辆数须同填或同空 / 809119 车型不在车型字典内 / 809120 车型字典暂不可用 / 809124 座位数不在该车型可选档位内。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `group-batch:demand:confirm`(与整体确认、按户打回、受控重开同码——它们动的是同一个 Tab 里的同一份数据);未登录由网关拦截返 401。
|
||||
- **全量替换语义**:未出现在本次提交里的分组会被移出当前版本,不是增量补丁。
|
||||
- **🔴 判据变化只在户级**:「这户交了没有」看两类任一;「行程逐日覆盖」(809109)仍只看 TRAVEL,没变。
|
||||
- **只提交接送机的户**:不再被 809123 拦、**也不要求被任何乘车分组覆盖**,且**不出现在 `exemptHouseholds` 里**。
|
||||
- **豁免户不阻断**:`ORDER_NOT_CUSTOMIZING` / `REQUIREMENT_FROZEN` 两类户不进 809123、不参与 809109,但必须在页面上提示出来(后端已逐户带原因下发)。
|
||||
- **错误码文案是可变的**:`message` 只用于展示,判定一律按 `code`。
|
||||
- **乐观锁只挡同一瞬间的并发写**:挡不住「A 读了 v3 去改、B 也读了 v3 改完先提交」这种跨请求覆盖。
|
||||
- **雪花 ID 一律是字符串**(`requirementId` / `groupBatchId` / `memberOrderIds[]` / `exemptHouseholds[].orderId`)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 自动汇总正式用车需求草稿 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft`
|
||||
|
||||
**VO**: `无请求体 → GroupVehicleAggregateDraftRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
编辑弹窗点「自动汇总」时调用,按各子订单的活跃 TRAVEL 需求汇总出一份团级草稿,**只读零写入**,返回的 `draft` 可原样 PUT 给上面那个保存端点。权限点与编辑弹窗取数口同码 `group-batch:demand:confirm`。本次改动只让 809121 少抛一类情况并改了它的报文与缺失原因文案。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | - | 团期 ID |
|
||||
|
||||
#### 出参 `Result<GroupVehicleAggregateDraftRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | String | 团期 ID(雪花,字符串) |
|
||||
| `currentStatus` | String | 当前正式需求状态 |
|
||||
| `draft` | Object | 汇总出的草稿,结构**与保存端点的请求体逐字段相同**,可原样 PUT |
|
||||
| `droppedFleetItems` | Array | 多车型户被丢弃的车型项:`orderId` / `teamNo` / `orderNo` / `vehicleType` / `seats` / `count` / `keptVehicleType` / `reason`(`VEHICLE_TYPE_NOT_IN_DICT` 等) |
|
||||
| `staleHeadcountOrders` | Array | 冻结人数与实时人数不一致的户:`orderId` / `teamNo` / `orderNo` / `frozenHeadcount` / `liveHeadcount` |
|
||||
| `paddedOrderDays` | Array | 为覆盖出发~返回而补进分组的日期:`orderId` / `teamNo` / `orderNo` / `dates[]` |
|
||||
| `seatOptionAdjusted` | Array | 座位档被兜底调整的组/户:`groupCode` / `orderId` / `teamNo` / `orderNo` / `vehicleType` / `originalSeats` / `adoptedSeats` / `seatOptions[]` / `reason` |
|
||||
| `violations` | Array | 草稿已先跑过与保存同一份逐日校验的结果:`code`(对应 809xxx) / `reason` / `detail` / `groupCode` / `tripDate` / `orderId` / `teamNo` |
|
||||
| `exemptHouseholds` | Array | 豁免户(结构同上一个端点);🔴 **只提交了接送机的户不在这里,也不在草稿里** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2099459272533000001/vehicle-requirement/aggregate-draft
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"currentStatus": "DRAFT",
|
||||
"draft": {
|
||||
"version": 3,
|
||||
"remark": null,
|
||||
"groups": [
|
||||
{
|
||||
"groupId": null,
|
||||
"groupCode": "BUS",
|
||||
"vehicleType": "bus",
|
||||
"serviceStartDate": "2026-09-12",
|
||||
"serviceEndDate": "2026-09-16",
|
||||
"seats": 19,
|
||||
"count": 1,
|
||||
"specialTags": [],
|
||||
"remark": null,
|
||||
"days": [
|
||||
{
|
||||
"tripDate": "2026-09-12",
|
||||
"headcount": 9,
|
||||
"memberOrderIds": [2099459272533323777]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"droppedFleetItems": [],
|
||||
"staleHeadcountOrders": [],
|
||||
"paddedOrderDays": [],
|
||||
"seatOptionAdjusted": [],
|
||||
"violations": [],
|
||||
"exemptHouseholds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团里只有接送机户、没有任何可汇总的行程用车户时,草稿分组为空数组而**不再抛 809121**(本次改动的直接效果):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"currentStatus": "DRAFT",
|
||||
"draft": { "version": null, "remark": null, "groups": [] },
|
||||
"droppedFleetItems": [],
|
||||
"staleHeadcountOrders": [],
|
||||
"paddedOrderDays": [],
|
||||
"seatOptionAdjusted": [],
|
||||
"violations": [],
|
||||
"exemptHouseholds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
车型字典取不到时不静默降级,抛 809120 让运营重试(避免把一整份草稿的车型全判成非法)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
809121(触发条件已收窄、文案已改写;`{0}` 是团期名标识,`{2}` 是「户标识:原因」顿号分隔的清单,原因文案里的 `未提交行程用车需求` 已改为 `未提交用车需求`;以下为测试服实测原文):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809121,
|
||||
"message": "团期 「第22期 8月喀纳斯湖秋色三日游」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-6538:未提交用车需求",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余错误码未变:809120 车队车型字典暂不可用 / 809111 团期状态不允许 / 809100 团期尚未形成正式用车需求(视链路)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `group-batch:demand:confirm`;未登录由网关拦截返 401。
|
||||
- **⛔ 本端点零写入**,可安全重复调用;`draft` 是「按现有子订单需求草稿长什么样」,**不保证保存一定能过**——预跑的校验结果在 `violations`。
|
||||
- **收窄后的 809121 判据**:需车户「两类用车需求一条都没有」才算信息缺失;只提交接送机的户不算缺少,**也不会出现在草稿里**(团车草稿只汇总 TRAVEL,它本就没有位置)。
|
||||
- **缺失原因文案已改**:`未提交行程用车需求` → `未提交用车需求`(另有 `车型均不在车型字典内`、服务日推不出、人数为 0 三类未变)。
|
||||
- **诊断字段必须展示**:`droppedFleetItems` / `staleHeadcountOrders` / `paddedOrderDays` / `seatOptionAdjusted` 都是「草稿与用户预期可能不一致」的位置,静默吞掉会让运营看到一份自己没想要的草稿。
|
||||
- **雪花 ID 一律是字符串**。
|
||||
|
||||
---
|
||||
|
||||
### 3. 整体确认需求缺失预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check`
|
||||
|
||||
**VO**: `无请求体 → GroupBatchRequirementCheckRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「查看需求」Tab 进入时与点「确认」前调用,据 `ready` 置灰确认按钮、据 `missing` / `vehicleMissing` 展示缺哪几户。**只读无副作用**。权限点 `group-batch:demand:confirm`。本次改动只让缺失项 `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`(809122)少产出一类情况并改了它的报文。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `groupBatchId` | Path | Long | ✅ | - | 团期 ID |
|
||||
|
||||
#### 出参 `Result<GroupBatchRequirementCheckRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `groupBatchId` | String | 团期 ID(雪花,字符串) |
|
||||
| `batchStatus` / `batchStatusName` | String | 团期状态编码与中文名 |
|
||||
| `ready` | Boolean | 是否可以整体确认(置灰按钮用) |
|
||||
| `missing` | Array | 房侧缺失户:`orderId` / `teamNo` / `orderNo` / `customerName` / `consultantId` / `consultantName` / `reason` / `reasonName` / `dayNumber` / `segmentIndex` / `expectedNights` / `actualNights` |
|
||||
| `checkedResourceTypes` | Array\<String\> | 恒为 `["HOTEL","VEHICLE"]`;文案已更新为「车侧逐户查**用车需求行**是否提交(#8577 起行程用车与接送机任一有即算已提交)」 |
|
||||
| `vehicleWaived` | Boolean | 是否已声明整团免车 |
|
||||
| `vehicleMissing` | Array | 车侧缺失项,见下 |
|
||||
| `vehicleMissing[].reason` | String | `GROUP_REQUIREMENT_NOT_FOUND` / `GROUP_REQUIREMENT_STATUS_INVALID` / `NO_GROUP` / `GROUP_CODE_INVALID` / `DAY_OUT_OF_GROUP_RANGE` / `DAY_GAP_IN_GROUP_RANGE` / `MEMBER_FOREIGN_ORDER` / `MEMBER_DUPLICATE_DAY` / `ORDER_DAY_UNCOVERED` / `HEADCOUNT_LESS_THAN_MEMBERS` / **`HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`** / `MEMBER_GROUP_MISMATCH` / `TRANSFER_SERVICE_DATES_NOT_BACKFILLED` / `TRANSFER_WINDOW_INCOMPLETE` |
|
||||
| `vehicleMissing[].groupCode` | String | 涉及的乘车分组编码;无分组维度时 null |
|
||||
| `vehicleMissing[].tripDate` | String(`yyyy-MM-dd`) | 涉及的日期;无日期维度时 null |
|
||||
| `vehicleMissing[].orderId` | String | 涉及的子订单 ID(雪花,字符串);无订单维度时 null |
|
||||
| `vehicleMissing[].teamNo` / `orderNo` | String | 团号 / 子订单号快照 |
|
||||
| `vehicleMissing[].detail` | String | 人话描述,**与整团确认时抛出的错误报文逐字相同**,可直接展示 |
|
||||
| `vehicleExemptHouseholds` | Array | 车侧豁免户(结构同前两个端点);🔴 **只提交了接送机的户不在这里** |
|
||||
| `groupVehicleRequirementId` | String | 团级正式用车需求 ID(雪花,字符串) |
|
||||
| `groupVehicleRequirementStatus` | String | 团级正式用车需求状态 |
|
||||
| `groupVehicleRequirementVersion` | Integer | 团级正式用车需求版本 |
|
||||
| `transferSubmitEnabled` | Boolean | 接送机提交灰度开关当前状态 |
|
||||
| `transferDeclaredWithoutRequirement` | Array | 声明了接送机却没有活跃 TRANSFER 行的户:`orderId` / `teamNo` / `orderNo` / `customerName` / `consultantId` / `consultantName` / `pickupRequired` / `dropoffRequired` / `pickupRemark` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2099459272533000001/requirement/confirm-check
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"batchStatusName": "资源准备中",
|
||||
"ready": false,
|
||||
"missing": [],
|
||||
"checkedResourceTypes": ["HOTEL", "VEHICLE"],
|
||||
"vehicleWaived": false,
|
||||
"vehicleMissing": [
|
||||
{
|
||||
"reason": "HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED",
|
||||
"groupCode": null,
|
||||
"tripDate": null,
|
||||
"orderId": "2099459272533323779",
|
||||
"teamNo": "26-0482",
|
||||
"orderNo": "HL2606010003",
|
||||
"detail": "该户尚未提交用车需求,请先让定制师提交后再整团提交车务"
|
||||
}
|
||||
],
|
||||
"vehicleExemptHouseholds": [],
|
||||
"groupVehicleRequirementId": "2099459272533400001",
|
||||
"groupVehicleRequirementStatus": "DRAFT",
|
||||
"groupVehicleRequirementVersion": 4,
|
||||
"transferSubmitEnabled": true,
|
||||
"transferDeclaredWithoutRequirement": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
全部就绪时 `ready=true`,三个清单都是**空数组**不是 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"groupBatchId": "2099459272533000001",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"batchStatusName": "资源准备中",
|
||||
"ready": true,
|
||||
"missing": [],
|
||||
"checkedResourceTypes": ["HOTEL", "VEHICLE"],
|
||||
"vehicleWaived": false,
|
||||
"vehicleMissing": [],
|
||||
"vehicleExemptHouseholds": [],
|
||||
"transferDeclaredWithoutRequirement": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本端点是只读预检,把缺失**列成清单**而不是抛码;仍可能出现的错误只有权限与团期不存在两类:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 403,
|
||||
"message": "无权限执行该操作",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `group-batch:demand:confirm`;未登录由网关拦截返 401。
|
||||
- **⛔ 只读无副作用**,可随页面进入反复调用。
|
||||
- **它是缺失明细的唯一来源**:整团确认失败时抛出的 589533 只带汇总户数,逐户明细只能从本端点取。
|
||||
- **收窄后的 `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED` 判据**:在团需车户**两类用车需求都没提交**才产出;只提交接送机的户不产出,**也不进 `vehicleExemptHouseholds`**。
|
||||
- **`detail` 与错误报文逐字相同**:所以它也跟着改了文案(`行程用车需求` → `用车需求`),前端不要做子串匹配。
|
||||
- **`ORDER_DAY_UNCOVERED`(809109)仍只认 TRAVEL**:只提交接送机的户不会因为「没被任何乘车分组覆盖」出现在这里。
|
||||
- **`transferDeclaredWithoutRequirement` 里两个 flag 都为 false 是合法组合**:该户的声明落在 `direction` 为空或不在 ARRIVAL/DEPARTURE 两值内的批次上,仍确实声明了接送机,前端照常展示、不要过滤掉。
|
||||
- **雪花 ID 一律是字符串**。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受 / 拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照(保存端点)
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ✅ 首次保存(无版本) | `{ "version": null, "groups": [ { "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-09-12", "serviceEndDate": "2026-09-16", "days": [ { "tripDate": "2026-09-12", "headcount": 9, "memberOrderIds": [2099459272533323777] } ] } ] }` |
|
||||
| ✅ 改既有组(带 groupId,groupCode 不变) | `{ "version": 3, "groups": [ { "groupId": 1867000000009, "groupCode": "BUS", ... } ] }` |
|
||||
| ✅ 座位与车辆数同空(存量分组) | `{ ..., "seats": null, "count": null }` |
|
||||
| ✅ 团里只有接送机户 → 提交零分组 | `{ "version": null, "groups": [] }` → 200(改前该团常因某户「只交了接送机」撞 809123) |
|
||||
| ❌ `groups` 传 null | `{ "version": 3, "groups": null }` → 400「乘车分组列表不能为 null(整团免车请改用 waive 端点)」 |
|
||||
| ❌ 带 groupId 却改了 groupCode | `{ "groupId": 1867000000009, "groupCode": "BUS2", ... }` → 809104 |
|
||||
| ❌ 只填 seats 不填 count | `{ "seats": 19, "count": null }` → 809118 |
|
||||
| ❌ 车型填自由文本 | `{ "vehicleType": "35座大巴" }` → 809119 |
|
||||
|
||||
### 前端必须做的一处改动
|
||||
|
||||
- 🔴 **凡是对 809121 / 809122 / 809123 的 `message`(或预检 `vehicleMissing[].detail`)做过字符串包含判断的地方,一律改成按 `code` / `reason` 分支**。三条报文里的 `行程用车需求` 已改为 `用车需求`,旧的子串匹配会静默失配(不报错,只是那条分支再也不进)。
|
||||
- 其余全部字段、校验规则、请求格式不变,不需要任何别的适配。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
只有保存端点(PUT)是写端点,本次改动**没有任何表结构或写入语义变化**——变的是写之前那道户级阻断的判据。
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 某户只有活跃 TRANSFER 行,团级 PUT 提交 | 809123 整份拒绝,**零写入** | 正常落库(该户不需要被任何分组覆盖) |
|
||||
| 某户两类都没有活跃行且提交得了 | 809123 整份拒绝,零写入 | 未变,仍 809123 零写入 |
|
||||
| 某户两类都没有活跃行但提交不了(豁免户) | 不阻断,列入 `exemptHouseholds` | 未变 |
|
||||
| 正常提交 | 全量替换:本次未出现的分组移出当前版本、版本号 +1 | 未变 |
|
||||
|
||||
**失败零写入**:809123 抛在乐观锁比对与分组改名守卫之后、任何写入之前,整份拒绝不留半份数据。
|
||||
|
||||
自动汇总(GET)与确认预检(GET)两个端点**零写入**,本次未改变这一点。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 权限点 `group-batch:demand:confirm` 缺失 → 403。
|
||||
- 团期尚未形成正式用车需求 → 809100(报文用「该团期」,不带雪花 id)。
|
||||
- 正式需求已被他人修改 → 809102,带提交版本与当前版本。
|
||||
- 团期已过配置阶段 → 809111。
|
||||
- 已声明整团免车又提交分组 → 809115(需先 withdraw 回草稿)。
|
||||
- 车队车型字典不可用 → 809120(不静默降级,让运营重试)。
|
||||
- 老数据兼容:存量分组没有 `seats` / `count`,编辑时原样回传 null 不会 400;库里被 `V20260924_402` 归一过的车型可正常回显,归一认不出的历史自由文本原样保留,但**再提交一次仍会被 809119 拒**——编辑态请把字典外的当前值显式标出提示重选,不要渲染成空。
|
||||
- 推不出服务日的户(`departDate` / `returnDate` 任一为空)跳过 809109 覆盖判定(已知盲区,不是遗漏)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### reason(车侧缺失项原因码)
|
||||
|
||||
**所属字段**: `GroupBatchRequirementCheckRespVO.vehicleMissing[].reason` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `GROUP_REQUIREMENT_NOT_FOUND` | 团级正式需求未形成 | 对应 809100 |
|
||||
| `GROUP_REQUIREMENT_STATUS_INVALID` | 团级正式需求状态不允许 | 对应 809101 |
|
||||
| `NO_GROUP` | 有需车户却零分组 | 对应 809103 |
|
||||
| `GROUP_CODE_INVALID` | 分组编码重复或改名 | 对应 809104 |
|
||||
| `DAY_OUT_OF_GROUP_RANGE` | 逐日行不在本组服务日范围内 | 对应 809105 |
|
||||
| `DAY_GAP_IN_GROUP_RANGE` | 本组服务日范围内缺日 | 对应 809106 |
|
||||
| `MEMBER_FOREIGN_ORDER` | 成员不属于本团期 | 对应 809107 |
|
||||
| `MEMBER_DUPLICATE_DAY` | 同一户同一日属多个分组 | 对应 809108 |
|
||||
| `ORDER_DAY_UNCOVERED` | 该户某日未被任何分组覆盖 | 对应 809109;🔴 **仍只认 TRAVEL,本次未改** |
|
||||
| `HEADCOUNT_LESS_THAN_MEMBERS` | 用车人数小于当日成员户数 | 对应 809110 |
|
||||
| `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED` | 该户尚未提交用车需求 | 对应 809122;🔴 **#8577 收窄:行程用车与接送机任一有即不报**。只带 `orderId` / `orderNo`,处置是催该户定制师提交 |
|
||||
| `MEMBER_GROUP_MISMATCH` | 该户车型与覆盖它的分组车型不符 | 对应 809125;排在户级未提交之后(交都没交的户没有车型可比) |
|
||||
| `TRANSFER_SERVICE_DATES_NOT_BACKFILLED` | 待放行的接送机需求未回填服务日 | 对应 809007,只带 `orderId` |
|
||||
| `TRANSFER_WINDOW_INCOMPLETE` | 接送机需求窗没盖住大交通派生日期 | 对应 809126,带 `orderId` / `orderNo` 与首个越窗日期 |
|
||||
|
||||
### reason(团级用车需求豁免户原因码)
|
||||
|
||||
**所属字段**: `exemptHouseholds[].reason`、`vehicleExemptHouseholds[].reason` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `ORDER_NOT_CUSTOMIZING` | 订单不在定制中 | 定制师提交会被 582017 拒,所以该户不算「没交」 |
|
||||
| `REQUIREMENT_FROZEN` | 团期已过资源准备、需求已冻结且该户未被打回 | 定制师提交会被 589536 拒 |
|
||||
|
||||
🔴 **只提交了接送机的户不属于任何一档**——它已提交,既不进未提交名单也不进豁免名单。
|
||||
|
||||
### 用车需求类别(判定用,不直接出现在本次三个响应的字段里)
|
||||
|
||||
| 值 | 中文 | 在本次判定中的角色 |
|
||||
|----|------|-------------------|
|
||||
| `TRAVEL` | 团期行程用车 | 汇总进团级乘车分组;**809109 逐日覆盖只认它** |
|
||||
| `TRANSFER` | 接送机 | 走逐户派车、结构上在团级乘车分组之外;#8577 起它也算「已提交用车需求」,参与 809121 / 809122 / 809123 的判定 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 三个端点的全部请求字段 | — | 未变(一个都没动) |
|
||||
| 三个端点的全部响应字段 | — | 未变(无新增、无删除、无改名、无类型变化) |
|
||||
| `checkedResourceTypes` 的字段说明文案 | 「车侧逐户查**行程**用车需求行是否提交」 | 「车侧逐户查用车需求行是否提交(#8577 起行程用车与接送机任一有即算已提交)」 |
|
||||
| `vehicleMissing[].reason` 的取值集合 | 14 个 | 未变(仍 14 个,只是其中一个的触发条件收窄) |
|
||||
| `exemptHouseholds` 的成员判据 | 在团需车 ∧ 无 active TRAVEL ∧ 提交不了 | 在团需车 ∧ **两类都无 active 行** ∧ 提交不了 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 某户只提交了接送机,团级 PUT 保存 | 809123 整份拒绝,运营无干净出路 | 正常保存 |
|
||||
| 某户只提交了接送机,点自动汇总 | 809121 整团出不来草稿 | 正常出草稿(该户不进草稿,也不进缺失清单) |
|
||||
| 某户只提交了接送机,进确认预检 | 该户挂 `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`,`ready=false` | 不产出该缺失项 |
|
||||
| 某户只提交了接送机,点整体确认(`POST .../requirement/confirm`,真实派车/写库) | 809122 抛出,确认失败、零写入 | 正常确认,该户随团一起派车(前提其余条件都满足) |
|
||||
| 某户只提交了接送机,是否要求被乘车分组覆盖 | 会走到 809109 | 不要求(它结构上在团级分组之外) |
|
||||
| 809121 报文 | `团期 {0} 有 {1} 户缺少可汇总的**行程**用车需求…` | `…缺少可汇总的用车需求…` |
|
||||
| 809122 报文 | `该户尚未提交**行程**用车需求,…` | `该户尚未提交用车需求,…` |
|
||||
| 809123 报文 | `{0}有 {1} 户尚未提交**行程**用车需求,…` | `{0}有 {1} 户尚未提交用车需求,…` |
|
||||
| 汇总缺失原因文案 | `未提交行程用车需求` | `未提交用车需求` |
|
||||
| 809109 逐日覆盖的判据 | 只认 TRAVEL | 未变,仍只认 TRAVEL |
|
||||
| 两类都没提交的户 | 三处照旧阻断 | 未变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否(无字段增删改;只是三个错误码少抛一类情况、报文文案改写)
|
||||
- **前端是否必须同步上线**: 否;但**若前端对这三条报文做过字符串包含判断,必须改**(改成按 `code` / `reason` 分支),否则那条分支会静默失配
|
||||
- **前端 workaround 清理点**: 若为绕开「纯接送机户卡住整团」在页面上加过提示、屏蔽过确认按钮、或引导过运营去整团免车,可以撤掉
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 团期需求管理 Tab 的三个端点里 809121 / 809122 / 809123 的触发条件与报文文案。
|
||||
- **⚠️ `POST .../requirement/confirm`(整体确认,真实派车/写库)不是零影响**:它的响应字段结构、派车/写库机制本身未改一行代码,但它内部同样经 `GroupBatchRequirementService.doConfirm`(`GroupBatchRequirementService.java:562`)调 `loadVehicleSnapshot`,后者第 1240-1241 行直接调用本次收窄后的 `classifyVehicleSubmission` 来判 809122——即它对 809122 的**实际触发条件与 `confirm-check` 端点同步收窄**:纯接送机户此前会在这里被 809122 拦下、零写入(回归用例 `GroupBatchRequirementServiceConfirmVehicleTest#doConfirm_householdWithoutTravelRequirement_throws809122` 钉住的正是「两类都没提交」仍会拦的边界),现在能正常通过、随团一起派车,见六.6 行为级对比。前端若曾据「纯接送机户点确认必失败」写过分支或禁用逻辑,需按该行为级对比同步调整。
|
||||
- **零影响**:
|
||||
- 809109 逐日覆盖判定(仍只认 TRAVEL)
|
||||
- `POST .../requirement/confirm` 的响应字段结构(`GroupBatchRequirementConfirmRespVO` 字段清单未变)与派车 / 写库机制(`doConfirm` 方法体本身未改)
|
||||
- 受控重开、整份撤回、整团免车、按户打回四个端点
|
||||
- 接送机批量确认 `POST .../requirement/transfer/batch-confirm`
|
||||
- 户级用车需求的提交 / 编辑 / 打回链路
|
||||
- 车务侧(hl-fleet-service)的配车、派单、就绪判定
|
||||
- 历史数据:不做任何迁移,存量团期下次调用时按新判据生效
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||||
- 合并提交 `5b7074e691`(PR #8600 squash 合并进 `dev-v3`),17 文件 / +559 −137。
|
||||
- `GroupVehicleRequirementErrorCode` 三条 `IErrorCode.of` 的字面量 diff 已逐字核对(809121 / 809122 / 809123 各去掉「行程」二字),码值与常量名未变。
|
||||
- `GroupVehicleDraftAggregator.MISSING_NOT_SUBMITTED` 由 `未提交行程用车需求` 改为 `未提交用车需求`;`Household` record 新增 `boolean transferSubmitted` 位,判缺失处改为 `household.needsVehicle() && !household.transferSubmitted()`。
|
||||
- `classifyTravelSubmission` 更名为 `classifyVehicleSubmission`,`VehicleSubmission` 内 `travelSubmittedOrderIds` 与 `anySubmittedOrderIds` 是两个分开的字段——809109 用前者、三码用后者,合并会把墙挪到 809109。
|
||||
- 三个端点的 Controller 签名、`@RequestBody` VO、响应 VO 字段清单逐一核对,确认零字段变化。
|
||||
- 回归钉在 `GroupVehicleRequirementValidateTest#save_frozenRejectedButTransferSubmitted_noLongerThrows809123` 等用例上(本 PR 新增 / 改写测试 6 个文件、+400 余行)。
|
||||
- **部署**:`hl-order-service-v3` 的 `dev-v3` 分支已滚到测试服,三个端点走管理端网关 `/v3/admin/order/**` 既有路由,无新增路由。
|
||||
|
||||
```
|
||||
PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement → 200 ✓(纯接送机户不再触发 809123)
|
||||
GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft → 200 ✓(纯接送机户不再触发 809121)
|
||||
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check → 200 ✓(不再产出 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #7441 | 团期正式用车需求首次落地(809100-809115 段) | ✅ 有效 |
|
||||
| — | #8219 | 有户未提交时阻断团级 PUT,新开 809123 与豁免户机制 | ✅ 有效(本单在其基础上收窄判据) |
|
||||
| — | #8220 | 自动汇总草稿端点与 809121 | ✅ 有效 |
|
||||
| — | #8249 | 预检加户级 809122 | ✅ 有效 |
|
||||
| — | #8306 | 报文按团号列户、不出现雪花 id | ✅ 有效 |
|
||||
| **本 PR #8600** | **#8577** | 户级提交判定与行程覆盖判定分离,三码收窄 + 文案改写 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8577](https://git.1814.love:8443/wx/HL/issues/8577)
|
||||
- 关联 PR: [wx/HL#8600](https://git.1814.love:8443/wx/HL/pulls/8600)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8577](https://git.1814.love:8443/wx/HL/issues/8577)
|
||||
- **PR**: [#8600](https://git.1814.love:8443/wx/HL/pulls/8600)
|
||||
- **Merge commit**: [5b7074e691](https://git.1814.love:8443/wx/HL/commit/5b7074e691)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8579"
|
||||
title: "接送机配置接口补发条件放宽:门禁已满足且订单有接送机声明时重存也会补发最终方案,NO_GATE_TRANSITION 出现频率下降"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8595 已合并 dev-v3(0f19f383f),hl-fleet-service 已部署 TEST(8e00cc98b)并经 Gateway 实测。响应结构未变,前端无需改代码。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 接送机配置接口补发条件放宽
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8089)
|
||||
> **PR**: #8595
|
||||
> **Issue**: #8579
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 车务四步向导第③步「接送机配置」保存后的最终方案补发
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **以前**:只有「本次保存前门禁不满足、保存后满足」才补发最终方案;门禁本来就满足时再保存一次,一律回 `NO_GATE_TRANSITION`、不补发。
|
||||
- **现在**:保存后门禁满足,并且(保存前不满足,**或**订单大交通有接送机声明)就尝试补发。门禁本来就满足、订单有接送机声明时重存,也会补发。
|
||||
- **补发仍要过全部守卫**:换版后留下旧定稿(`STALE_FINALIZED_PLAN`)、方案代际不一致、没派满,照样不发,并如实回原因。换版后的订单仍须车务重新确认执行,本次没有绕开这道守卫。
|
||||
- **前端无需改代码**:请求体、响应结构、错误码都没变;只是 `finalPlanPublished=true` 出现得更多、`NO_GATE_TRANSITION` 出现得更少。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 接送机配置(按派车行整批幂等覆盖) | PUT | `/admin/fleet/assignments/pickup-dropoff-config` | 补发时机放宽 | 请求、响应结构不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 接送机配置 `PUT /admin/fleet/assignments/pickup-dropoff-config`
|
||||
|
||||
**VO**: `PickupDropoffConfigReqVO → PickupDropoffConfigRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务四步向导第③步保存接送机勾选。排车(`POST /admin/fleet/assignments/batch`)时大交通要求接/送机的日期还没配车,最终方案被压住(batch 回 `GATE_UNSATISFIED`);在本接口配齐后,服务端当场补发。
|
||||
|
||||
#### 入参(未变)
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `orderId` | Body | Long | ✅ | - | 订单 ID |
|
||||
| `requirementId` | Body | Long | ✅ | - | 当前生效用车需求 ID |
|
||||
| `kind` | Body | String | - | `TRAVEL`(默认)/ `TRANSFER` | 需求类别 |
|
||||
| `requestId` | Body | String | ✅ | 非空 | 幂等请求标识 |
|
||||
| `items[]` | Body | Array | ✅ | 只列参与接送的行 | 两个标志都为 false 的行不要放进来(否则 400);未列入的行服务端置 0 |
|
||||
| `items[].assignmentId` | Body | Long | ✅ | 当前需求下生效派车行 | 派车行 ID |
|
||||
| `items[].pickupParticipant` | Body | Boolean | ✅ | - | 当天是否参与接机 |
|
||||
| `items[].dropoffParticipant` | Body | Boolean | ✅ | - | 当天是否参与送机 |
|
||||
|
||||
#### 出参 `Result<PickupDropoffConfigRespVO>`(结构未变)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `finalPlanPublished` | Boolean | 本次是否补发了最终方案 |
|
||||
| `finalPlanNotPublishedReason` | String | 未补发原因,补发时为 `null`;取值见六.5 |
|
||||
| `requirementReopened` | Boolean | 是否把已完成订单拉回处理中(未变) |
|
||||
| `reopenBlockedReason` | String | 本该拉回却没拉回的原因(未变) |
|
||||
| `pickupDropoffGate` | Object | 写入后的门禁状态:`arrivalRequiredDates` / `departureRequiredDates` / `missingPickupDates` / `missingDropoffDates` / `declared` / `satisfied` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2105196047255126017",
|
||||
"requirementId": "2105197351138410497",
|
||||
"kind": "TRAVEL",
|
||||
"requestId": "pd-2105196047255126017-20260930-02",
|
||||
"items": [
|
||||
{ "assignmentId": "2105197584132046850", "pickupParticipant": true, "dropoffParticipant": false },
|
||||
{ "assignmentId": "2105197584157212674", "pickupParticipant": false, "dropoffParticipant": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例(配齐即补发)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"finalPlanPublished": true,
|
||||
"finalPlanNotPublishedReason": null,
|
||||
"requirementReopened": false,
|
||||
"reopenBlockedReason": null,
|
||||
"pickupDropoffGate": {
|
||||
"arrivalRequiredDates": ["2026-10-12"],
|
||||
"departureRequiredDates": ["2026-10-14"],
|
||||
"missingPickupDates": [],
|
||||
"missingDropoffDates": [],
|
||||
"declared": true,
|
||||
"satisfied": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例(换版后配齐,仍被陈旧定稿拦下)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"finalPlanPublished": false,
|
||||
"finalPlanNotPublishedReason": "STALE_FINALIZED_PLAN",
|
||||
"requirementReopened": false,
|
||||
"reopenBlockedReason": null,
|
||||
"pickupDropoffGate": { "declared": true, "satisfied": true, "missingPickupDates": [], "missingDropoffDates": [] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
订单大交通没有接送机声明时 `pickupDropoffGate` 各日期数组为空、`declared=false`、`satisfied=true`;此时重存不补发:
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "finalPlanPublished": false, "finalPlanNotPublishedReason": "NO_GATE_TRANSITION", "pickupDropoffGate": { "declared": false, "satisfied": true, "missingPickupDates": [], "missingDropoffDates": [] } } }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "同一行接送机标志不能全为 false;不参与的行不要出现在配置列表中",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 补发时机:保存后门禁满足,且保存前不满足或订单有接送机声明。订单大交通没有接送机声明时,重存不会补发(回 `NO_GATE_TRANSITION`),避免每存一次都发一版没有变化的最终方案。
|
||||
- 补发仍依次过陈旧定稿、方案代际、满派拓扑、接送机门禁四道判据,只回第一个没通过的原因。
|
||||
- 同一需求多次补发,order-v3 的配车记录按最新一版整体替换,不会重复累加。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- `finalPlanPublished=false` 不是错误,按 `finalPlanNotPublishedReason` 提示车务下一步:`GATE_UNSATISFIED`/`NO_GATE_TRANSITION` 看 `pickupDropoffGate.missing*Dates` 补配;`STALE_FINALIZED_PLAN` 提示车务重新确认执行;`PLAN_INCOMPLETE` 提示补派。
|
||||
- 不要按 `NO_GATE_TRANSITION` 判断「这次保存没生效」:勾选照常落库,它只表示本次没有尝试补发。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 补发时写 fleet `fleet_vehicle_assignment_snapshot_state`(修订号 +1)和 `fleet_vehicle_assignment_snapshot_outbox`,再异步写 order-v3 `order_vehicle_assignment`(旧行软删、按新一版重建)。
|
||||
- 不补发时只更新派车行的接送标志。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- `items` 含两个标志都为 false 的行 → 400「同一行接送机标志不能全为 false」
|
||||
- 换版后未经车务重新确认 → 不补发,回 `STALE_FINALIZED_PLAN`
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### finalPlanNotPublishedReason(`FinalPlanNotPublishedReasons`)
|
||||
|
||||
| 取值 | 含义 |
|
||||
|------|------|
|
||||
| `STALE_FINALIZED_PLAN` | 存在按旧需求定稿的陈旧行,需车务重新确认 |
|
||||
| `INVALID_PLAN_GENERATION` | 方案代际不一致 |
|
||||
| `PLAN_INCOMPLETE` | 满派拓扑不完整(缺车 / 缺司机 / 在途行越窗等) |
|
||||
| `CAPACITY_INSUFFICIENT` | 未定稿方案载客量不足 |
|
||||
| `GATE_UNSATISFIED` | 大交通要求的接/送机日未配车 |
|
||||
| `NO_GATE_TRANSITION` | **口径变化**:本次没有尝试补发——保存后门禁仍不满足,或门禁满足但订单没有接送机声明且保存前已满足 |
|
||||
| `PICKUP_DROPOFF_GATE_DISABLED` | 接送机门禁开关关闭 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 保存前缺配、保存后配齐 | 补发 | 补发(不变) |
|
||||
| 门禁本已满足、订单有接送机声明,再保存 | `NO_GATE_TRANSITION`,不补发 | 尝试补发(过全部守卫) |
|
||||
| 门禁本已满足、订单无接送机声明,再保存 | `NO_GATE_TRANSITION` | `NO_GATE_TRANSITION`(不变) |
|
||||
| 保存后仍缺配 | `NO_GATE_TRANSITION` | `NO_GATE_TRANSITION`(不变) |
|
||||
| 换版后配齐 | `NO_GATE_TRANSITION` 或 `STALE_FINALIZED_PLAN` | `STALE_FINALIZED_PLAN` |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否
|
||||
- **前端是否必须同步上线**: 否
|
||||
- **前端 workaround 清理点**: 无
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `PUT /admin/fleet/assignments/pickup-dropoff-config` 的补发时机
|
||||
- **零影响**: 请求体与响应结构;`POST /admin/fleet/assignments/batch`;确认执行接口(缺失日期字段的删除见 #8603 changelog)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-30 TEST(hl-fleet-service @ 8e00cc98b)经 Gateway 实测,夹具订单验收后已行前取消:
|
||||
|
||||
| 场景 | 结果 |
|
||||
|------|------|
|
||||
| batch 排满三天、首日接机末日送机未配 | `finalPlanPublished=false` + `GATE_UNSATISFIED`,缺 `2026-10-12` / `2026-10-14` |
|
||||
| 只配接机 | `NO_GATE_TRANSITION`,门禁仍缺 `2026-10-14` |
|
||||
| 配齐接机 + 送机 | `finalPlanPublished=true`,outbox 1 行,order-v3 配车记录 3 行 |
|
||||
| 换版后配齐 | `finalPlanPublished=false` + `STALE_FINALIZED_PLAN`,门禁 `satisfied=true`,无快照发出 |
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
- #8429:统一 batch / 接送机配置 / 确认三处的最终方案发布判据,引入 `finalPlanNotPublishedReason`。
|
||||
- #8603(PR #8608):确认执行响应删除恒为空的缺失日期字段,缺失日期改由 605914/605915 错误消息承载。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8579](https://git.1814.love/wx/HL/issues/8579)
|
||||
- 关联 PR: [wx/HL#8595](https://git.1814.love/wx/HL/pulls/8595)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8579](https://git.1814.love/wx/HL/issues/8579)
|
||||
- **PR**: [#8595](https://git.1814.love/wx/HL/pulls/8595)
|
||||
- **Merge commit**: [0f19f383f](https://git.1814.love/wx/HL/commit/0f19f383faf3f0df9489b342c15afb42addf09f5)
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端:@lc
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8593"
|
||||
title: "待配车团期清单 transferPendingCount 由硬编码 0 改为真值,取不到给 null"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "GET /admin/fleet/group-dispatch/pending-batches 响应体每行新增真值语义:records[].transferPendingCount 此前恒为硬编码 0,本次改为按团期实际接送机声明缺口计算的真值,且新增 null 语义——取不到时返回 null 而不是 0,前端必须把 null 渲染成未知态(如「—」),不得折算成 0;0 表示查过了确无缺口,null 表示本团有没有缺口未知。该字段与团期配车总览端点(GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview)的 transferPendingTotal 走同一判定方法,服务端保证两者恒等。声明数据经内部 Feign 端点(order-v3 提供,仅供服务间调用,非管理后台直接可调)按页批量整取,不做逐团 N+1 请求;该内部读口不可达时不会静默返回空列表,会返回失败结果,使 transferPendingCount 整页退化为 null,不影响该页其余字段(包括 unreadCount,是另一个独立软依赖,user-service 不可达时退化为 0)。字段类型未变(仍是 Integer),仅新增 null 作为合法取值;若前端此前对该字段做过兜底成 0 或完全未渲染,需要补上 null 分支与展示逻辑。清单本身的分页/过滤/排序、其余字段与错误码(600012/600013/401)均未变化。backend_status=deployed:hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);该路由已实测可达(未登录态返 200 信封 code=401)。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务团期配车:待配车团期清单接送机未配计数改为真值
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-fleet-service(端口 8087)
|
||||
> **PR**: [#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
|
||||
> **Issue**: [#8593](https://git.1814.love:8443/wx/HL/issues/8593)
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台「待配车团期清单」列表页的 `transferPendingCount` 一列
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
「待配车团期清单」(`pending-batches`)每行的 `transferPendingCount` 字段,此前**恒为硬编码 0**,不反映任何真实数据——前端如果曾据此判断「所有团都没有接送机缺口」,这个判断从一开始就是假的。本次改为**真实计算值**,并引入 **`null` 语义**:取不到声明数据时返回 `null` 而不是 `0`。**`0` 与 `null` 含义不同,不能互相折算**:`0` = 查过了、确无接送机缺口;`null` = 这一刻没查到、本团有没有缺口未知。前端如果沿用旧的「反正恒为 0,不用管」的假设,现在会看到非零真值和偶发 `null`,必须补上渲染逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 待配车团期清单 | GET | `/admin/fleet/group-dispatch/pending-batches` | 修改接口 | `transferPendingCount` 由硬编码 0 改为真值+null 语义 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 待配车团期清单 `GET /admin/fleet/group-dispatch/pending-batches`
|
||||
|
||||
**VO**: `GroupDispatchPendingBatchPageReqVO → PageResult<GroupDispatchPendingBatchRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务在「团期配车」列表页查看尚未完成配车(`requirementConfirmed=true` 且 `vehicleReady=false`)的团期,支持按出发日区间、团号/团名关键词、配车进度过滤。本次改动只影响列表行里的 `transferPendingCount` 一列,接口路径、分页参数、其余字段均未变化。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| departDateFrom | Query | LocalDate(ISO,`yyyy-MM-dd`) | - | 不传=不限 | 出发日下界(含) |
|
||||
| departDateTo | Query | LocalDate(ISO,`yyyy-MM-dd`) | - | 不传=不限 | 出发日上界(含) |
|
||||
| keyword | Query | String | - | ≤50 字符 | 团号/团名模糊关键词 |
|
||||
| dispatchProgress | Query | String | - | 仅 `NOT_STARTED`/`PARTIAL`/`FULL` | 配车进度过滤(fleet 侧内存过滤,先分页后过滤) |
|
||||
| page | Query | Integer | - | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | - | 1-100,默认 20 | 每页条数 |
|
||||
|
||||
#### 出参 `Result<PageResult<GroupDispatchPendingBatchRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | Array | 团期行列表,见下 |
|
||||
| records[].groupBatchId | String(Long 转字符串) | 团期主订单 ID |
|
||||
| records[].batchNo | String | 团号 |
|
||||
| records[].batchName | String | 团名 |
|
||||
| records[].batchStatus | String | 团期状态 |
|
||||
| records[].departDate | String(`yyyy-MM-dd`) | 出发日 |
|
||||
| records[].endDate | String(`yyyy-MM-dd`) | 结束日 |
|
||||
| records[].serviceDayCount | Integer | 服务日天数 |
|
||||
| records[].enrolledOrders | Integer | 报名子订单数 |
|
||||
| records[].enrolledPeople | Integer | 报名人数 |
|
||||
| records[].requirementConfirmed | Boolean | 需求是否已确认 |
|
||||
| records[].vehicleReady | Boolean | 车辆是否已就绪 |
|
||||
| records[].dispatchedDayCount | Integer | 已排车天数 |
|
||||
| records[].dispatchProgress | String | 配车进度:`NOT_STARTED`/`PARTIAL`/`FULL` |
|
||||
| records[].**transferPendingCount** | Integer(可空) | **本次变更字段**:本团接送机未配计数;`null`=未取到(前端须渲染未知态),`0`=确无缺口 |
|
||||
| records[].unreadCount | Integer | 团期车务会话团队未读数(软依赖,取不到退 0) |
|
||||
| total | Integer | 总记录数(`dispatchProgress` 过滤前) |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/pending-batches?departDateFrom=2026-09-01&departDateTo=2026-09-30&keyword=T26-8867&dispatchProgress=PARTIAL&page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"groupBatchId": "1934567890123456789",
|
||||
"batchNo": "T26-8867",
|
||||
"batchName": "额吉的故乡 9/12 团",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"departDate": "2026-09-12",
|
||||
"endDate": "2026-09-16",
|
||||
"serviceDayCount": 5,
|
||||
"enrolledOrders": 6,
|
||||
"enrolledPeople": 17,
|
||||
"requirementConfirmed": true,
|
||||
"vehicleReady": false,
|
||||
"dispatchedDayCount": 2,
|
||||
"dispatchProgress": "PARTIAL",
|
||||
"transferPendingCount": 2,
|
||||
"unreadCount": 3
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
当没有满足过滤条件的团期时,返回 `records: [], total: 0`(HTTP 200,非错误)。当 order-v3 的接送机声明批量读口不可达时,**本页所有行的 `transferPendingCount` 一律返回 `null`**(不是 0,也不会让整个请求失败)——前端必须把 `transferPendingCount=null` 渲染成未知态(如「—」),不能当作「确认无缺口」折算成 0。`unreadCount` 是另一个独立的软依赖:user-service 不可达时退化为 0,清单其余字段照常返回,不受影响。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 600013,
|
||||
"message": "参数非法: 页码必须≥1",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余可能返回的错误码:
|
||||
|
||||
| code | 触发条件 | message |
|
||||
|---|---|---|
|
||||
| 600012 | order-v3 团期候选基线不可达(降级/返错),**不会静默返空列表** | `团期配车基线不可达,请稍后重试` |
|
||||
| 600013 | 日期区间倒置、分页越界、关键词超长(>50 字)、`dispatchProgress` 枚举非法 | `参数非法: {具体原因}` |
|
||||
| 401 | 未登录(网关统一信封,HTTP 状态码仍是 200,信封内 `code=401`) | `缺少有效的 Authorization 头` |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `dispatchProgress` 是 fleet 侧内存过滤(order-v3 侧没有配车事实,无法下推),是「先分页再过滤」——单页返回条数可能少于 `pageSize`,`total` 是过滤**前**的总数。
|
||||
- `transferPendingCount` 与团期配车总览端点(`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`)的 `transferPendingTotal` 走**同一个判定方法**,服务端保证两者恒等——清单页与详情页的这个数字不会对不上。
|
||||
- 声明数据经内部批量读口整页一次取齐(每页最多按团期数一次 Feign 调用),不做逐团 N+1 请求。
|
||||
- `transferPendingCount` 的 `null` 与 `unreadCount` 的「退 0」是两种不同的降级策略,分别对应各自读口的可靠性设计,不要混用同一套判空逻辑处理。
|
||||
- 主候选数据(团期本身)取不到时整个请求失败关闭(600012),不会把「后端没拿到」渲染成「该团没有需求」;这与 `transferPendingCount` 单列退化为 `null`(其余字段正常返回)是两个不同粒度的降级,不要合并处理。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 正确渲染 `transferPendingCount` 的方式
|
||||
|
||||
| 取值 | 含义 | 渲染建议 |
|
||||
|------|------|----------|
|
||||
| `0` | 查过了,确无接送机缺口 | 正常展示 `0` |
|
||||
| 正整数 | 查过了,有对应数量的缺口 | 正常展示数值,可高亮提醒 |
|
||||
| `null` | 本次没有取到该团的声明数据,缺口未知 | 渲染成未知态(如「—」),**不要**当作 `0` |
|
||||
|
||||
❌ 错误用法:`transferPendingCount ?? 0` 或任何把 `null` 静默折算成 `0` 的写法——这会把「未知」误报成「已确认无缺口」,反而比改动前的硬编码 0 更危险(因为界面上看起来像是「查过了」)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 网关统一信封 `code=401`(HTTP 状态码 200,非 HTTP 401)
|
||||
- 无匹配团期 → `records: [], total: 0`,HTTP 200
|
||||
- `dispatchProgress` 过滤导致单页为空 → `records: []`,但 `total` 仍是过滤前总数,不为 0
|
||||
- order-v3 团期候选基线不可达 → 600012,整个请求失败,不返回部分数据
|
||||
- order-v3 接送机声明批量读口不可达 → 请求仍然成功,仅 `transferPendingCount` 整页退化为 `null`
|
||||
- user-service 不可达 → 请求仍然成功,仅 `unreadCount` 退化为 `0`
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### dispatchProgress(配车进度)
|
||||
|
||||
**所属字段**: `records[].dispatchProgress`(`GroupDispatchPendingBatchRespVO`),同名字段也用于入参过滤 | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `NOT_STARTED` | 未开始 | 已排车天数为 0 |
|
||||
| `PARTIAL` | 部分配车 | 已排车天数大于 0 但未盖满全部服务日 |
|
||||
| `FULL` | 已配齐 | 已排车天数盖满全部权威服务日 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `transferPendingCount` | 恒为 `0`(硬编码占位符,从未反映真实缺口) | 真实计算值;取不到声明数据时为 `null`(不是 `0`) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 声明数据获取方式 | 未获取,字段硬编码为 0 | 按页批量调用 order-v3 内部读口一次取齐(非逐团 N+1),取不到时该列整体退化为 `null` |
|
||||
| 与 overview 端点的一致性 | 无法比较(清单侧恒 0,overview 侧是真值,两者结构性不可能相等) | 清单与 overview 走同一判定方法,服务端保证恒等 |
|
||||
| 字段类型 | `Integer`,实际恒非空 | `Integer`,新增合法取值 `null` |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否——字段名与类型(`Integer`)未变,只是语义从「恒定占位符」变为「真实业务值 + 可空」。原本恒为 0 意味着这个字段此前对使用方没有任何信息量,语义上不存在"旧行为被依赖"的合理场景。
|
||||
- **前端是否必须同步上线**: 是——如果前端此前完全没有渲染这个字段(因为它恒为 0、没有展示价值),现在需要补充展示逻辑,包括 `null` 的未知态处理;如果前端此前渲染了这个字段但做了 `?? 0` 之类的兜底,需要去掉这个兜底、改为区分 `0` 与 `null`。
|
||||
- **前端 workaround 清理点**: 若前端此前因为「这个字段没用、永远是 0」而完全跳过读取或做了防御性兜底,需要重新接入并按上方「正确渲染方式」处理;无其它 workaround。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 「待配车团期清单」列表每行的 `transferPendingCount` 字段
|
||||
- **零影响**:
|
||||
- 清单接口的分页参数、过滤参数(`departDateFrom`/`departDateTo`/`keyword`/`dispatchProgress`)语义
|
||||
- `records[]` 内除 `transferPendingCount` 外的其余字段
|
||||
- `unreadCount` 字段的取值逻辑(软依赖降级策略本身未变)
|
||||
- 团期配车总览端点 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` 的路径、参数、响应结构(其 `transferPendingTotal` 此前就已经是真值,本次不涉及该端点改动,只是清单侧现在与它口径一致)
|
||||
- 错误码 600012/600013/401 的触发条件与数值
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8593 合并提交 `d57498d381`)一致。
|
||||
- 测试服内网 `curl` 实测路由已挂载且鉴权前置生效:
|
||||
|
||||
```
|
||||
GET http://127.0.0.1:8080/admin/fleet/group-dispatch/pending-batches?page=1&pageSize=1 (无 Authorization 头)
|
||||
→ HTTP 200
|
||||
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"e0e9988b33394199","success":false}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8593](https://git.1814.love:8443/wx/HL/issues/8593)
|
||||
- 关联 PR: [wx/HL#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8593](https://git.1814.love:8443/wx/HL/issues/8593)
|
||||
- **PR**: [#8617](https://git.1814.love:8443/wx/HL/pulls/8617)
|
||||
- **Merge commit**: [`d57498d381`](https://git.1814.love:8443/wx/HL/commit/d57498d38138fd37ce7e844b6f2b01c190706950)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,451 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8597"
|
||||
title: "退团房清空免费退改期限:截止时刻与提醒天数保留原值(订正 #8491 交接件三处表述),异常检查补回源单已取消的退团房待办"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8599(提交 ff68637542)已合入 dev-v3;测试服 hl-order-service-v3 运行提交 d57498d38、BEHIND 0/N、STATE ok、2026-09-30 05:32:57,ff68637542 是 d57498d381 的祖先。两个端点的路径与方法均未变,网关 /v3/admin/** 路由块已通配,零网关改动。【frontend_status 取 not_required 的依据与限定,2026-09-30 对 hl-ui origin/v2.1 查证】「房务控制台」整个功能域目前在前端**不存在任何代码**:house-console / houseConsole / 退团房 / room-transfer 等 9 个中英文关键词在 src/ 下全部 0 命中(阳性对照:同为房务域的 house-allocation 在 src/api/housekeeper/ 与 src/views/housekeeper/ 均有活跃文件,故检索有分辨力)。因此这里的 not_required 含义是「没有可改的前端代码」,不是「改动对现有页面透明」——本条是 #8491『房务控制台接口』(frontend_status: pending,17 个新增端点)的后续订正,待该模块被前端认领落地时,本条的口径需与它一并核对,勿据本条认为这块功能已可用。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务控制台: 退团房期限清空口径订正 + 异常检查退团房待办补全
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8597
|
||||
> **PR**: #8599
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台「房务控制台」的「退团房」页签(设期限弹窗)与「异常检查」页签(待办列表)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
|
||||
|
||||
- 🔴 **订正 #8491 交接件的三处表述**:`PUT .../room-transfers/{id}/deadline` 传 `cancelDays: null` 清除期限时,**只有 `cancelDays` 变成 `null`,`cancelCutoff` 与 `remindDays` 保留该行原值,不会被置空**。`changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md` 的第 746、747 行(入参表)、第 787 行(空数据 / 降级响应)、第 1623 行(显式 SET NULL 说明)写的「三列一并清空 / 本字段被忽略、一并清空」与实际行为不符,以本条为准;该文件第 1847 行的测试环境读数(`cancel_cutoff / remind_days 保留原值`)才是正确的那一条。
|
||||
- 🔴 **判「这一行有没有设免费退改期限」只能看 `cancelDays === null` 或 `risk === "NO_DEADLINE"`**,不能看 `cancelCutoff` / `remindDays` 是否为 `null`——清空后这两个字段仍有值(该行从未设过期限时是建表默认的 `"18:00"` 与 `1`)。
|
||||
- **清除期限从「必定失败」改为成功**:本次改动前,`cancelDays: null` 的请求 100% 返回 **808932**「房务状态已被并发修改,请刷新后重试」(并非真的并发冲突),现在返回 `code=200` 并给出更新后的整行。
|
||||
- **异常检查 `tasks[]` 的 `TRANSFER_PENDING` 条目不再漏行**:待处理退团房行的窗口归属改为只看源团期 / 源订单的**出发日**,**不看源单状态**;源单查不到、或源单出发日为空时同样列出。取消订单恰恰是产生退团房最常见的原因,订正前这些待办在控制台唯一的待办载体上看不到。**同一窗口下本端点返回的 `tasks` 行数会比订正前多。**
|
||||
|
||||
---
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
退团房行有两处独立缺陷,都发生在「房务控制台」已交付的端点上:
|
||||
|
||||
1. 清期限走的乐观锁写口,其入参守卫要求截止时刻与提醒天数非空(两列在库中是 NOT NULL);旧代码在 `cancelDays` 为 `null` 时把这两项也一起传 `null`,守卫直接返回「影响行数 0」,上层把 0 当成版本冲突抛 808932。表现是「清除期限」按钮永远失败,而错误文案指向刷新重试,看不出是入参问题。
|
||||
2. 异常检查的退团房待办原先用「窗口内的团期集合 / 散单集合」判归属,这两个集合按占用口径排除了已取消的单,于是「订单取消 → 释放房间 → 待转出」这条最常见的链路产出的待办从不出现。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 设置 / 清除退团房免费退改期限 | PUT | `/v3/admin/order/house-console/room-transfers/{id}/deadline` | 修改 | 清除期限由必定失败改为成功;`cancelCutoff` / `remindDays` 保留原值(订正交接件表述) |
|
||||
| 2 | 房务异常检查 | GET | `/v3/admin/order/house-console/audit` | 修改 | `tasks[]` 的 `TRANSFER_PENDING` 按源单出发日归属、不看源单状态,补回源单已取消的行 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 设置 / 清除退团房免费退改期限 `PUT /v3/admin/order/house-console/room-transfers/{id}/deadline`
|
||||
|
||||
**VO**: `HouseRoomTransferDeadlineSaveReqVO → HouseRoomTransferRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「退团房」页签某一行点「设期限」,录入酒店给的免费取消规则(入住前几天、当天几点前)与提前几天提醒;也用于把已设的期限清掉。清掉后该行的风险分档变为 `NO_DEADLINE`,前端应按 `cancelDays` 是否为 `null` 渲染「未设免费取消期」,而不是按 `cancelCutoff` / `remindDays` 是否有值判断。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | 必须是退团房**父行**(子行是转出明细,不可改期限);不存在或已删返 808320 | 退团房行 ID |
|
||||
| cancelDays | Body | Integer | ❌ | 0~60,越界返 808328;传 `null` 表示清除期限 | 入住前几天可免费取消 |
|
||||
| cancelCutoff | Body | String | ❌ | `HH:mm` 24 小时制(`00:00`~`23:59`),格式不符返 808328;`cancelDays` 非空而本字段为空 / 空白时取 `18:00`;**`cancelDays` 为 `null` 时本字段被忽略,该行原值保留** | 截止当天的时刻 |
|
||||
| remindDays | Body | Integer | ❌ | 0~30,越界返 808328;`cancelDays` 非空而本字段为空时取 `1`;**`cancelDays` 为 `null` 时本字段被忽略,该行原值保留** | 提前几天提醒 |
|
||||
|
||||
#### 出参 `Result<HouseRoomTransferRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (整行) | HouseRoomTransferRespVO | 更新后的该退团房父行,字段与退团房分页 `records[]` 同构 |
|
||||
| cancelDays | Integer | 入住前几天免费取消;**清除期限后为 `null`** |
|
||||
| cancelCutoff | String | 截止当天的时刻 `HH:mm`;**清除期限后仍是该行原值(从未设过则是 `"18:00"`),不会变成 `null`** |
|
||||
| remindDays | Integer | 提前几天提醒;**清除期限后仍是该行原值(从未设过则是 `1`),不会变成 `null`** |
|
||||
| deadlineAt | LocalDateTime | 免费取消截止时刻 = 入住晚 − `cancelDays` 天的 `cancelCutoff`;`cancelDays` 为 `null` 或缺入住晚时为 `null` |
|
||||
| risk | String | 风险码 `OVERDUE` / `NEAR` / `NO_DEADLINE` / `NORMAL`;仅 `PENDING` 行有值,其余为 `null` |
|
||||
| riskLabel | String | 风险中文;`NO_DEADLINE` 对应「未设免费取消期」 |
|
||||
| status / statusLabel | String | 行状态 `PENDING` / `TRANSFERRED` / `CANCELLED` 与中文;本端点只对 `PENDING` 行成功 |
|
||||
| id / sourceOrderId / sourceGroupBatchId / hotelId / roomTypeId | Long(String) | 雪花 ID,JSON 中为字符串 |
|
||||
| teamNo | String | 源订单团号(批量读订单主表团号);取不到为 `null` |
|
||||
| stayDate | LocalDate | 入住晚 |
|
||||
| roomCount / remainingCount | Integer | 原始间数 / 剩余待处理间数 |
|
||||
| readOnly / readOnlyReason | Boolean / String | 对当前操作人是否只读(源单由他人处理)与理由 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
PUT /v3/admin/order/house-console/room-transfers/1950000000000000001/deadline
|
||||
{
|
||||
"cancelDays": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "1950000000000000001",
|
||||
"sourceType": "ORDER",
|
||||
"sourceOrderId": "1930000000000000001",
|
||||
"teamNo": "HL20261001A",
|
||||
"sourceGroupBatchId": null,
|
||||
"sourceBatchNo": null,
|
||||
"stayDate": "2026-10-02",
|
||||
"cityName": "海拉尔",
|
||||
"hotelId": "100001",
|
||||
"hotelName": "海拉尔草原酒店",
|
||||
"roomTypeId": "300001",
|
||||
"roomTypeName": "豪华双床房",
|
||||
"roomCount": 2,
|
||||
"remainingCount": 2,
|
||||
"status": "PENDING",
|
||||
"statusLabel": "待处理",
|
||||
"cancelDays": null,
|
||||
"cancelCutoff": "18:00",
|
||||
"remindDays": 1,
|
||||
"deadlineAt": null,
|
||||
"risk": "NO_DEADLINE",
|
||||
"riskLabel": "未设免费取消期",
|
||||
"readOnly": false,
|
||||
"readOnlyReason": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 本端点恒返回整行,没有空响应形态。
|
||||
- 清除期限(`cancelDays: null`)是正常成功路径:`data.cancelDays = null`、`data.deadlineAt = null`、`data.risk = "NO_DEADLINE"`、`data.riskLabel = "未设免费取消期"`,而 `data.cancelCutoff` 与 `data.remindDays` 仍是该行原值。
|
||||
- 源单(订单需求 / 团期)读不到时,只影响「谁是源单处理人」的判定,不影响本端点的写入结果;非源单处理人且非超管一律返 808326。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808328,
|
||||
"message": "退改期限参数不合法",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
|
||||
| 808320 | 转房记录不存在 | `id` 不存在、已删、或不是父行 |
|
||||
| 808321 | 该房间已处理 | 行已不是 `PENDING`(已转出 / 已取消) |
|
||||
| 808326 | 只有原单处理人可以处理退团房间 | 非源单处理人且非超管 |
|
||||
| 808328 | 退改期限参数不合法 | `cancelDays` 超 0~60、`remindDays` 超 0~30、`cancelCutoff` 不是 `HH:mm` |
|
||||
| 808932 | 房务状态已被并发修改,请刷新后重试 | 真并发写冲突(行版本在锁定读与写之间被改)。**订正前 `cancelDays: null` 会恒定命中这一条,订正后不再出现这种假冲突** |
|
||||
| 100502 | 修改处理中,请勿重复提交 | 3 秒幂等窗口内重复提交同一请求 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 入参不走 Bean Validation,范围与格式错误统一以 **808328** 返回,HTTP 状态仍是 200,不是 400。
|
||||
- 清除期限只清 `cancelDays` 这一项语义;`cancelCutoff` / `remindDays` 是「下次设期限时的默认值」,保留它们不影响「有没有期限」的判定,因为 `deadlineAt` 与 `risk` 都只在 `cancelDays` 非空时才成立。
|
||||
- 该行处于 `PENDING` 才可改期限;`TRANSFERRED` / `CANCELLED` 返 808321。
|
||||
- 只有源单处理人或超管可改;源订单来源看该需求的持有人,团期来源看该团期的房务认领人。
|
||||
- 同一行的改期限、转房、向酒店取消共用一把行级锁,前端不必自己串行化。
|
||||
|
||||
### 2. 房务异常检查 `GET /v3/admin/order/house-console/audit`
|
||||
|
||||
**VO**: `HouseConsoleAuditReqVO → HouseConsoleAuditRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「异常检查」页签:按出发日期区间一次列出数据对不上的问题(`issues`)和还没办完的事(`tasks`)。本次订正只影响 `tasks` 里 `TRANSFER_PENDING`(退团房未结清)这一类条目的**取行范围**,字段结构未变;前端按 `refId` 跳转退团房处理页的逻辑不变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| scope | Query | String | ❌ | `mine` / `all`,默认 `mine`;其他取值返 400 | 只作用于 `tasks`:`mine` 只留当前登录人是处理人的条目;`issues` 不受它影响 |
|
||||
| departDateFrom | Query | LocalDate | ❌ | `yyyy-MM-dd`,默认今天 | 出发日期区间起(含) |
|
||||
| departDateTo | Query | LocalDate | ❌ | `yyyy-MM-dd`,默认起始日 +30 天;早于起始日或跨度超 92 天返 808313 | 出发日期区间止(含) |
|
||||
|
||||
#### 出参 `Result<HouseConsoleAuditRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| issues | Array | 数据不一致问题,不归属处理人,不受 `scope` 影响;无则空数组 |
|
||||
| tasks | Array | 待处理事项,受 `scope` 过滤;无则空数组 |
|
||||
| issues[].code / tasks[].code | String | 问题码 / 待办码,取值见「六.5、枚举」 |
|
||||
| tasks[].taskCode | String | 待办码(仅 `tasks` 有值),与 `code` 同值 |
|
||||
| issues[].codeLabel / tasks[].codeLabel | String | 中文标签,后端给出直接展示;`TRANSFER_PENDING` 为「退团房未结清」 |
|
||||
| tasks[].orderId | Long(String) | 源订单 ID;退团房条目取该行的源订单 |
|
||||
| tasks[].teamNo | String | 源订单团号;取不到为 `null` |
|
||||
| tasks[].groupBatchId / tasks[].batchNo | Long(String) / String | 源团期 ID 与批次号;散单来源为 `null` |
|
||||
| tasks[].stayDate | LocalDate | 入住晚 |
|
||||
| tasks[].hotelId / hotelName / roomTypeId / roomTypeName | Long(String) / String | 酒店与房型 |
|
||||
| tasks[].refId | Long(String) | 关联单据 ID,`TRANSFER_PENDING` 为退团房父行 ID,前端据此跳转 |
|
||||
| tasks[].detail | String | 说明文案,`TRANSFER_PENDING` 为「剩余 N 间待转出或向酒店取消」 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/house-console/audit?scope=mine&departDateFrom=2026-10-01&departDateTo=2026-10-31
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"issues": [],
|
||||
"tasks": [
|
||||
{
|
||||
"code": "TRANSFER_PENDING",
|
||||
"codeLabel": "退团房未结清",
|
||||
"taskCode": "TRANSFER_PENDING",
|
||||
"orderId": "1930000000000000001",
|
||||
"teamNo": "HL20261001A",
|
||||
"groupBatchId": null,
|
||||
"batchNo": null,
|
||||
"stayDate": "2026-10-02",
|
||||
"hotelId": "100001",
|
||||
"hotelName": "海拉尔草原酒店",
|
||||
"roomTypeId": "300001",
|
||||
"roomTypeName": "豪华双床房",
|
||||
"refId": "1950000000000000001",
|
||||
"detail": "剩余 2 间待转出或向酒店取消"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "issues": [], "tasks": [] }, "success": true }
|
||||
```
|
||||
|
||||
- `issues` 与 `tasks` 恒为数组,不会是 `null`。
|
||||
- 退团房条目的源单读不到(源订单或源团期查不到、或出发日为空)时,该条目**仍然列出**,`teamNo` / `batchNo` 可能为 `null`;口径是「宁可多报一条,也不让待办从唯一载体上消失」。
|
||||
- `STOCK_LEDGER_MISMATCH`(库存账不平)只在资源侧全局库存追踪开关打开时检查,开关关闭时不产出该问题码。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808313,
|
||||
"message": "日期跨度不能超过 92 天",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | scope 取值非法 | `scope` 不是 `mine` / `all` |
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
|
||||
| 808313 | 日期跨度不能超过 {0} 天 | 出发日期区间跨度超 92 天,或止日早于起日 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `TRANSFER_PENDING` 的归属判据是**源团期 / 源订单的出发日落在窗口内**,与源单当前状态(含已取消)无关;这是本次订正的点。
|
||||
- 只列 `PENDING` 的退团房父行;已转出、已取消的行不是待办。
|
||||
- `scope=mine` 的「我的」按源单处理人判:散单来源看该需求持有人,团期来源看该团期房务认领人;源单读不到时该条目在 `mine` 下不会出现(无法判定处理人)。
|
||||
- 窗口跨度上限 92 天,与控房表查询的 62 天不是同一个上限,别复用。
|
||||
- 纯读接口,不写数据。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload / 判断 |
|
||||
|------|----------------|
|
||||
| ✅ 设期限 | `{ "cancelDays": 3, "cancelCutoff": "18:00", "remindDays": 1 }` |
|
||||
| ✅ 设期限只给天数 | `{ "cancelDays": 3 }` → 截止时刻取 `18:00`、提醒天数取 `1` |
|
||||
| ✅ 清除期限 | `{ "cancelDays": null }`(或整个 body 只有 `{}`)→ 200,`cancelCutoff` / `remindDays` 保留原值 |
|
||||
| ✅ 判「未设期限」 | `data.cancelDays === null`,或 `data.risk === "NO_DEADLINE"` |
|
||||
| ❌ 判「未设期限」 | `data.cancelCutoff === null && data.remindDays === null`——清除期限后这两项仍有值,该判断恒为 false |
|
||||
| ❌ 清除期限时显式传空 | `{ "cancelDays": null, "cancelCutoff": "", "remindDays": null }` 能成功,但 `cancelCutoff` / `remindDays` 一样被忽略,不要指望用它们清值 |
|
||||
| ❌ 期限天数越界 | `{ "cancelDays": 61 }` → 808328(不是 400) |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
- 清除期限成功后,前端应以返回的整行直接替换列表行,不要只把 `cancelDays` 置空——`risk` / `riskLabel` / `deadlineAt` 都由后端重算,本地推算会与「退团房分页」的汇总读数对不上。
|
||||
- 「异常检查」页签的待办条数在订正后可能增加;若页面上有与之对照的徽标计数,改为直接用本端点返回的 `tasks.length`,不要沿用按订单状态自行过滤后的口径。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
| 写操作 | 外部可观察行为 |
|
||||
|--------|----------------|
|
||||
| 设期限(`cancelDays` 非空) | 该行的免费退改天数、截止时刻、提醒天数三项按入参(含默认值)整体更新;行版本 +1;写一条订单级操作日志「退团房 {入住晚} {酒店名} 免费退改期限改为入住前 N 天 HH:mm」 |
|
||||
| 清除期限(`cancelDays` 为 `null`) | **只有免费退改天数被清空**;截止时刻与提醒天数保持该行原值;行版本 +1;写一条订单级操作日志「…免费退改期限改为未设」 |
|
||||
| 并发保护 | 行级分布式锁 + 行版本比对;版本在锁定读与写之间被改则整笔回滚并返 808932 |
|
||||
|
||||
- 两个写路径都不产生跨服务调用,不发消息。
|
||||
- 「异常检查」端点是纯读,不写任何数据。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 清除期限后再次设期限,若只传 `cancelDays`,截止时刻与提醒天数会被**重新按默认值 `18:00` / `1` 覆盖**(不是沿用清除前保留下来的那两个值);要沿用旧值必须显式回传。
|
||||
- 从未设过期限的新行,其截止时刻与提醒天数是建表默认的 `"18:00"` 与 `1`,所以「一行从没设过期限」与「设过又被清掉」在这两个字段上不可区分;唯一区分点是操作日志。
|
||||
- 风险分档 `NEAR` 按「日」比较(今天 ≥ 截止日 − 提醒天数),同一天上午和下午不会给出不同分档。
|
||||
- 退团房待办的取行只设窗口下限(入住晚不早于窗口起日),不设上限:待处理池量级小,多读回的行由源单出发日过滤掉。
|
||||
- `scope=all` 时任何房务都能看到全部待办条目,但看得见不等于能写;改期限仍按源单处理人校验(808326)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### risk(退团房风险分档)
|
||||
|
||||
| 值 | 中文(`riskLabel`) | 判据 |
|
||||
|----|--------------------|------|
|
||||
| `OVERDUE` | 已过免费取消期 | 现在已过截止时刻 |
|
||||
| `NEAR` | 临近免费取消期 | 今天 ≥ 截止日 − 提醒天数 |
|
||||
| `NO_DEADLINE` | 未设免费取消期 | `cancelDays` 为空(或该行缺入住晚) |
|
||||
| `NORMAL` | 正常 | 其余 |
|
||||
|
||||
> 仅 `status = PENDING` 的行有值,其余行 `risk` / `riskLabel` 均为 `null`。
|
||||
|
||||
### status(退团房行状态)
|
||||
|
||||
| 值 | 中文(`statusLabel`) | 说明 |
|
||||
|----|----------------------|------|
|
||||
| `PENDING` | 待处理 | 还有剩余间数待转出或向酒店取消;只有这一状态能改期限 |
|
||||
| `TRANSFERRED` | 已转出 | 全部间数已转给别的订单 / 团期 |
|
||||
| `CANCELLED` | 已取消 | 已向酒店取消 |
|
||||
|
||||
### taskCode / code(异常检查待办码,`tasks[]`)
|
||||
|
||||
| 值 | 中文(`codeLabel`) | 说明 |
|
||||
|----|--------------------|------|
|
||||
| `TRANSFER_PENDING` | 退团房未结清 | 本次订正影响的就是这一类的取行范围 |
|
||||
| `HOTEL_CANCEL_PENDING` | 原酒店待取消 | 改配留下的原订尚未确认取消 |
|
||||
| `INQUIRY_PENDING` | 新订 / 变更待确认 | — |
|
||||
| `STAY_UNARRANGED` | 住宿待落实 | — |
|
||||
|
||||
> `issues[]` 的 `code` 取值域是另一组(`STOCK_OVERBOOKED` / `STOCK_LEDGER_MISMATCH` / `STOCK_ROW_MISSING` / `TRANSFER_TARGET_GONE` / `PLAN_COUNT_MISMATCH`),本次未变。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 之前交接件写的 | 现在的实际行为 |
|
||||
|------|----------------|----------------|
|
||||
| `data.cancelCutoff`(清除期限后) | 被一并置空为 `null` | **保留该行原值**(从未设过则为 `"18:00"`) |
|
||||
| `data.remindDays`(清除期限后) | 被一并置空为 `null` | **保留该行原值**(从未设过则为 `1`) |
|
||||
| `data.cancelDays`(清除期限后) | `null` | `null`(不变) |
|
||||
| `data.deadlineAt`(清除期限后) | `null` | `null`(不变) |
|
||||
| `data.risk` / `riskLabel`(清除期限后) | `NO_DEADLINE` / 「未设免费取消期」 | 同(不变) |
|
||||
| 异常检查 `tasks[]` 结构 | — | 字段与类型均未变 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 之前 | 现在 |
|
||||
|------|------|------|
|
||||
| `PUT .../deadline` 传 `cancelDays: null` | 恒定返回 808932「房务状态已被并发修改,请刷新后重试」,期限清不掉 | 返回 200 并给出更新后的整行 |
|
||||
| `PUT .../deadline` 传 `cancelDays` 非空 | 成功 | 成功(口径不变) |
|
||||
| `GET .../audit` 的 `TRANSFER_PENDING` | 源订单 / 源团期已取消的退团房行不出现在 `tasks` 里 | 按源单出发日归属、不看源单状态,这些行会出现 |
|
||||
| `GET .../audit` 的 `TRANSFER_PENDING`(源单查不到 / 出发日为空) | 不出现 | 出现(宁可多报一条) |
|
||||
| `GET .../audit` 的 `issues[]` | — | 口径不变 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
| 项 | 评估 |
|
||||
|----|------|
|
||||
| 需要前端改代码 | **是(1 处必改)**:凡按 `cancelCutoff` / `remindDays` 是否为 `null` 判「有没有设期限」的地方,改为按 `cancelDays === null` 或 `risk === "NO_DEADLINE"` 判。 |
|
||||
| 需要前端改代码 | **可能(1 处)**:「异常检查」页签若自行按订单状态过滤过待办条目,去掉该过滤,直接用后端返回的 `tasks`。 |
|
||||
| 兼容性 | 无字段增删、无类型变化、无路径与方法变化;只有取值与取行范围变化。 |
|
||||
| 「清除期限」功能 | 从不可用变为可用,前端原有的 808932 报错提示分支在该场景不再触发(真并发冲突仍会返回它,不要删该分支)。 |
|
||||
| 读数变化 | 同一出发日窗口下「异常检查」的待办行数只会增加或不变,不会减少。 |
|
||||
| 其他消费方 | 退团房分页、转房、向酒店取消三个端点的契约未变;本次不涉及小程序端。 |
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- 退团房分页 `GET /v3/admin/order/house-console/room-transfers`、转入候选 `GET .../room-transfers/{id}/candidates`、转房 `POST .../room-transfers/{id}/transfer`、向酒店取消 `POST .../room-transfers/{id}/cancel-hotel`:字段与口径均未变。
|
||||
- 控房表(查询 / 调房量 / 调价 / 导出)、住宿模板、批量认领、改配取消确认、团期转交:未触及。
|
||||
- `issues[]` 的五类问题码及其判据未变。
|
||||
- 房务只读标识 `readOnly` / `readOnlyReason` 的口径未变(其口径见 #8491 的两份交接件)。
|
||||
- 通知、消息模板、跳转链接未变。
|
||||
- 无数据库结构变更,无新增 Flyway 脚本,无网关路由改动。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
| 项 | 读数 | 依据 |
|
||||
|----|------|------|
|
||||
| hl-order-service-v3 运行的提交 | `d57498d38`,BEHIND `0/N`,STATE `ok`,时间 2026-09-30 05:32:57 | 测试服部署登记脚本 `/opt/hulalv/scripts/deploy-status.sh`,2026-09-30 本次读取 |
|
||||
| 本次改动在运行的字节里 | 是 | `git merge-base --is-ancestor ff68637542 origin/dev-v3` 返回 0;`origin/dev-v3` 头为 `d57498d381` |
|
||||
| 清除期限 | `cancelDays=null` → `code=200`;库里免费退改天数为 NULL,截止时刻 / 提醒天数保留原值;`risk=NO_DEADLINE` | 取证提交 `ff6863754`,读数原文记在 `changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md` 第 1847 行 |
|
||||
| 设期限(回归) | `cancelDays=3` → `code=200`、该行 `risk=OVERDUE`;`cancelDays=0` + `remindDays=1` → `code=200`、`risk=NEAR` | 同上文件第 1845、1846 行,复测提交 `ff6863754` |
|
||||
| 退团房待办含源单已取消的行 | 源订单已取消 + 退团房行 `PENDING` + 窗口覆盖出发日 → `code=200`,`tasks` 含 `TRANSFER_PENDING`「退团房未结清」 | 同上文件第 1861 行,取证提交 `ff6863754` |
|
||||
| 用例覆盖 | `HouseRoomTransferManagerTest#updateDeadline_nullCancelDays_keepsRowCutoffAndRemind`;`HouseRoomTransferMapperMysqlTest#casUpdateDeadline_nullCancelDaysWithRowValues_writesNullAndKeepsCutoffRemind`;`HouseConsoleAuditManagerTest#audit_pendingTransferSourceOrderCancelledInWindow_listed` / `#audit_pendingTransferSourceBatchCancelledInWindow_listed` / `#audit_pendingTransferSourceOrderMissing_listed` / `#audit_pendingTransferSourceOrderDepartOutsideWindow_notListed` | 随 `ff68637542` 新增 |
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR(纠错 / 功能演进时必写)
|
||||
|
||||
| PR / 提交 | 内容 | 与本条的关系 |
|
||||
|-----------|------|--------------|
|
||||
| PR #8564(`7c21cf0e40`) | 房务控制台整套(#8491),首次引入本文两个端点 | 被订正的表述出自它的交接件 |
|
||||
| PR #8599(`ff68637542`) | 本条的两处修复(#8597) | 本条正文描述的就是它合入后的行为 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 本条 Issue:#8597(PR #8599)
|
||||
- 被订正的交接件:`changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md`(接口 8 与接口 12)
|
||||
- 只读标识口径:`changelogs-v2/2026-09/30_8491_房务配房接口字段与口径调整-修改接口-管理后台.md`
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- Issue: #8597
|
||||
- PR: #8599
|
||||
- 分支基线: `dev-v3`
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端: wx
|
||||
- 前端: mmg(管理后台)
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8598"
|
||||
title: "派单保险隔离事件新增人工终结(DISCARDED)出口"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "新增 POST /admin/fleet/insurance/assignment-events/{eventId}/discard,仅 SUPER_ADMIN 可调用,把处于 QUARANTINED 的派单保险隔离事件人工终结为新终态 DISCARDED,终结不可逆、无回退入口,也无批量接口,逐条操作且终结原因必填(非空、≤200字)。幂等窗口10秒(重复提交返100502),并发冲突返100503(CAS未命中)。DISCARDED 会让关联的行程短信状态查询/重发端点(GET及POST .../itinerary-sms[/retry])对该事件返回 status=FAILED、canRetry=false——这是已有取值组合,不引入新字段或新枚举值,前端已有的 FAILED 分支即可覆盖,不需要新增代码路径。gateway_status=not_required,复用既有 /admin/fleet/** 路由,未新增网关配置。backend_status=deployed:PR #8607(合并提交5afadf6c634)已合并,hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);未登录态 curl 实测该路径已挂载并优先鉴权(返 200 信封 code=401,非 HTTP 401 状态码),路由与鉴权链路均已验证。;前端实证:hl-admin 全仓零 assignment-events/itinerary-sms 封装,QUARANTINED 仅 spec fixture,无消费点,判 not_required(hl-admin sync-log 2026-09-30)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务保险: 隔离事件新增人工终结(DISCARDED)出口
|
||||
|
||||
> **存放目录**: 二期(order-v3)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-fleet-service(端口 8087)
|
||||
> **PR**: [#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
|
||||
> **Issue**: [#8598](https://git.1814.love:8443/wx/HL/issues/8598)
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 超管对派单保险隔离 Outbox 事件的处置面板,新增一个终结动作;对既有重放端点与行程短信状态端点零结构变化
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
隔离事件(`QUARANTINED`)此前**唯一的出口是重放**——重放会再隔离的事件(关联需求已删、内容审核不过、上游数据已按别的单清理),会让卡死告警永久为红,没有任何办法让它退出告警。本次新增一个**人工终结**动作,把这类确定性失败的事件显式标成新终态 `DISCARDED`,终结之后它退出卡死告警、不再被扫描器捞起、也不再阻塞同 `orderingKey` 的后继事件。**终结无回退入口,是单向不可逆操作**。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 终结已隔离的派单生命周期事件 | POST | `/admin/fleet/insurance/assignment-events/{eventId}/discard` | 新增接口 | 仅 SUPER_ADMIN,QUARANTINED → DISCARDED |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 终结已隔离的派单生命周期事件 `POST /admin/fleet/insurance/assignment-events/{eventId}/discard`
|
||||
|
||||
**VO**: `AssignmentInsuranceOutboxDiscardReqVO → AssignmentInsuranceOutboxDiscardRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
超管在派单保险 Outbox 卡死告警/隔离事件处置面板里,对一条已确认「重放多少次都会再隔离」的事件(例如关联需求已随团期撤销删除、内容审核不通过、上游数据已被另一张单清理)执行终结,承认这个业务动作确实不会再发生、也不再补,并把原因、操作人、时间留痕。与重放动作共用同一批隔离事件列表数据源,本次不新增查询端点。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| eventId | Path | Long | ✅ | - | Outbox 事件 ID |
|
||||
| reason | Body | String | ✅ | 非空;≤200 字 | 确认不再重放的原因,须说明业务影响已如何处置 |
|
||||
|
||||
#### 出参 `Result<AssignmentInsuranceOutboxDiscardRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| eventId | String(Long 转字符串) | Outbox 事件 ID |
|
||||
| previousStatus | String | 终结前状态,恒为 `QUARANTINED` |
|
||||
| status | String | 终结后状态,恒为 `DISCARDED` |
|
||||
| operatorId | String(Long 转字符串) | 操作人管理员 ID |
|
||||
| discardedAt | String | 终结操作时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"reason": "关联需求已随团期撤销删除,短信不再需要补发"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"eventId": "1934567890123456789",
|
||||
"previousStatus": "QUARANTINED",
|
||||
"status": "DISCARDED",
|
||||
"operatorId": "88",
|
||||
"discardedAt": "2026-09-30 10:20:30"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口是单事件的状态迁移动作,没有「空数据」或部分成功的中间态——调用结果只有「成功迁移」或下方错误响应里的某一种拒绝,不存在降级返回。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 403001,
|
||||
"message": "无权限,仅超级管理员可终结隔离事件",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余可能返回的错误码:
|
||||
|
||||
| code | 触发条件 | message |
|
||||
|---|---|---|
|
||||
| 400 | `reason` 为空白或超过 200 字(Bean Validation,先于业务逻辑拦截) | `终结原因不能为空` 或 `终结原因最多200字` |
|
||||
| 100001 | `eventId` 对应事件不存在 | `参数非法: 隔离事件不存在` |
|
||||
| 100001 | 事件当前状态不是 `QUARANTINED`(已是 SUCCESS/DISCARDED/PENDING/PROCESSING) | `参数非法: 仅允许终结 QUARANTINED 事件,当前状态为{实际状态}` |
|
||||
| 100502 | 同一 `eventId` 10 秒幂等窗口内重复提交 | `隔离事件终结中,请勿重复提交` |
|
||||
| 100503 | 并发命中 CAS 未命中(他人同时终结/重放,或处理器抢先处理) | `资源被占用,请稍后重试` |
|
||||
| 401 | 未登录(网关统一信封,HTTP 状态码仍是 200,信封内 `code=401`) | `缺少有效的 Authorization 头` |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只接受当前状态为 `QUARANTINED` 的事件;其余状态一律 100001 拒绝。
|
||||
- 终结是单向操作,没有「撤销终结」的接口。
|
||||
- 无批量终结接口,只能逐条调用——设计上刻意如此:批量会把混在隔离事件里的真实业务缺口一次性静默抹掉。
|
||||
- 幂等键为 `eventId`(10 秒窗口),并发保护为乐观锁 CAS;两者返回的错误码不同(100502 vs 100503),前端应分别处理:100502 提示稍候,100503 建议重新拉取该事件当前状态后再决定下一步。
|
||||
- 终结成功后,该事件对应的行程短信状态查询/重发端点(`GET /admin/fleet/assignments/{assignmentId}/itinerary-sms`、`POST .../itinerary-sms/retry`)会返回 `status=FAILED, canRetry=false`——这是这两个端点已公开枚举值集合里已有的取值组合,不是新增字段或新增枚举值,前端已有的 `FAILED` 分支不需要改动即可正确渲染。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload | 结果 |
|
||||
|------|---------|------|
|
||||
| ✅ 原因非空且 ≤200 字 | `{ "reason": "关联需求已随团期撤销删除,短信不再需要补发" }` | 200,事件迁移为 DISCARDED |
|
||||
| ❌ 原因为空 | `{ "reason": "" }` 或 `{ "reason": " " }` | 400 `终结原因不能为空` |
|
||||
| ❌ 原因超长 | `{ "reason": "<201 个字符>" }` | 400 `终结原因最多200字` |
|
||||
| ❌ 对非 QUARANTINED 事件调用 | 任意合法 reason,但目标事件当前是 SUCCESS/DISCARDED/PENDING/PROCESSING | 100001,message 里点名当前状态 |
|
||||
|
||||
### 调用前置
|
||||
|
||||
调用前前端应确认目标事件当前处于「已隔离」状态(面板上通常是从隔离事件列表点进来),不要对已经终结过、已成功、或还在处理中的事件发起终结请求——这些情形不会被静默忽略,而是显式返回 100001。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 终结成功后,该事件状态字段变为 `DISCARDED`;原因、操作人、操作时间会被记录(复用重放动作已有的三个字段承载,未新增列)。
|
||||
- 终结成功后再对该事件调用既有的重放端点(`POST /admin/fleet/insurance/assignment-events/{eventId}/replay`),会返回 100001「仅允许重放 QUARANTINED 事件,当前状态为DISCARDED」。
|
||||
- 终结不会产生任何下游消息重放或补发——它就是承认这件事不会再发生。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 网关统一信封 `code=401`(HTTP 状态码 200,非 HTTP 401)
|
||||
- 非超管 → 403001
|
||||
- `eventId` 不存在 → 100001
|
||||
- 事件状态非 `QUARANTINED` → 100001,message 带当前实际状态
|
||||
- `reason` 为空/超长 → 400(Bean Validation 先于业务逻辑拦截)
|
||||
- 10 秒幂等窗口内重复提交同一 `eventId` → 100502
|
||||
- 并发命中 CAS 未命中 → 100503
|
||||
- 下游服务降级 → 不适用,本接口无下游读取,只做本域状态迁移
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### status / previousStatus(`AssignmentInsuranceOutboxStatusEnum`)
|
||||
|
||||
**所属字段**: `status` / `previousStatus`(`AssignmentInsuranceOutboxDiscardRespVO`) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING` | 待处理 | 尚未开始处理(本接口不接受此状态) |
|
||||
| `PROCESSING` | 处理中 | 正在处理(本接口不接受此状态) |
|
||||
| `QUARANTINED` | 已隔离 | 卡死告警状态,唯一可被本接口终结的状态;`previousStatus` 恒为此值 |
|
||||
| `SUCCESS` | 成功 | 机器判定的成功终态(本接口不接受此状态) |
|
||||
| `DISCARDED` | 已终结(本次新增) | 人工判定的放弃终态,只能由本接口产出,单向不可逆;`status` 恒为此值 |
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 派单保险隔离 Outbox 事件处置面板,新增一个终结动作入口
|
||||
- **零影响**:
|
||||
- 既有重放端点 `POST /admin/fleet/insurance/assignment-events/{eventId}/replay` 的路径、参数、错误码
|
||||
- 既有隔离事件列表/卡死告警统计查询
|
||||
- 行程短信状态查询/重发端点的响应字段结构与已公开枚举值集合(新增的只是一条已有取值组合被触发的路径)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8598 合并提交 `5afadf6c634`)一致。
|
||||
- 测试服内网 `curl` 实测路由已挂载且鉴权前置生效:
|
||||
|
||||
```
|
||||
POST http://127.0.0.1:8080/admin/fleet/insurance/assignment-events/1/discard (无 Authorization 头)
|
||||
→ HTTP 200
|
||||
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"f3e71e825f844525","success":false}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8598](https://git.1814.love:8443/wx/HL/issues/8598)
|
||||
- 关联 PR: [wx/HL#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8598](https://git.1814.love:8443/wx/HL/issues/8598)
|
||||
- **PR**: [#8607](https://git.1814.love:8443/wx/HL/pulls/8607)
|
||||
- **Merge commit**: [`5afadf6c634`](https://git.1814.love:8443/wx/HL/commit/5afadf6c634997a31b0f912f80ab0b67c34a9c2c)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,440 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8601"
|
||||
title: "逐户提交车务 / 打回的 kind 参数取消默认值 TRAVEL,两类活跃需求并存时必须显式指定(新错误码 809012)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "527842c1f6c5764ebf000b258d14389fb2e4c2c9"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "PR #8610 已 squash 合并 dev-v3(a2ba628ecb),hl-order-service-v3 dev-v3 分支已滚测试服。这是需要前端改调用代码的变更:两个端点的 kind 查询参数从 defaultValue=TRAVEL 改成无默认值,纯接送机户由此可用,两类并存且不传 kind 时新抛 809012。;前端已交付:orderV2.js 两处 JSDoc 纠错+详情页两弹窗钉注释;batch 侧调用已显式 kind,详情页事件链 brief 无 kind(后端源码实证)保持不传,809012 拦截器透 message 兜底,行为零变化,既有 spec 回归全绿(hl-admin 527842c1)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-order-service-v3: 逐户提交车务 / 打回的 `kind` 参数取消默认值 `TRAVEL`
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8610
|
||||
> **Issue**: #8601
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期需求管理 Tab 的逐户「提交车务」与「打回定制师」两个按钮所调的端点,其 `kind` 查询参数语义
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **这是需要前端改调用代码的变更**:两个端点的 `kind` 查询参数由 `@RequestParam(defaultValue = "TRAVEL")` 改为 `@RequestParam(required = false)`,**没有默认值了**。
|
||||
- **前端以前可以怎么写**:不传 `kind`,后端按 `TRAVEL` 处理。**现在的实际行为**:不传 `kind` 时后端按该户**活跃用车需求的类别数**自动解析——
|
||||
- **恰好 1 类** → 就用那一类(🆕 **纯接送机户从此可用**:旧默认值会去找一条根本不存在的 TRAVEL 行,导致这类户在这两个入口走不通流程);
|
||||
- **0 类** → 与改前一致,由既有分支抛 582031「订单无有效需求行」;
|
||||
- **≥2 类并存** → 🆕 抛新错误码 **809012**,拒绝猜测。
|
||||
- 🔴 **809012 是新增错误码**,前端必须接住:`订单 {0} 同时存在 {1} 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)`。撞到它的正确处置是**带上 `kind` 重发**(由用户选,或由页面上下文决定),不是重试。
|
||||
- **前端要做的事**:这两个按钮所在的位置本来就知道自己在操作哪一类需求(页面上就是按 TRAVEL / TRANSFER 分开展示的),**一律显式带上 `kind`** 即可,带了就不会撞 809012。传了值的行为与改前逐字相同(含非法值仍由 809000 拒)。
|
||||
- **请求体、响应体、权限、HTTP 形态全部未变**:两个端点仍是 `Result<Void>`,`dispatchRemark` 仍选填、`returnRemark` 仍必填。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
一户订单的用车需求按类别分行,两类可以同时活跃:
|
||||
|
||||
| 类别 | 含义 |
|
||||
|------|------|
|
||||
| `TRAVEL` | 团期行程用车 |
|
||||
| `TRANSFER` | 接送机 |
|
||||
|
||||
这两个端点都按 `kind` **精确定位一行**再迁移状态、写备注。`defaultValue = "TRAVEL"` 让「调用方没说要动哪一类」与「调用方明确要动 TRAVEL」在服务层**完全同形**——两类并存而调用方没传 `kind` 时,接口返 200、改掉 TRAVEL 行,而操作者想动的 TRANSFER 行三个字段一个都没变,**响应上没有任何可区分的信号**。这是静默错写。
|
||||
|
||||
同一个默认值还造成第二个缺陷:只有 TRANSFER 活跃行的户,旧逻辑会去找一条不存在的 TRAVEL 行,拿到 582031,**这类户在这两个入口根本用不了**。
|
||||
|
||||
解析只写在服务层一处(`RequirementService#resolveVehicleRequirementKind`),Controller 不做兜底,避免两层各写一份「空了怎么办」而日后分叉。类别数与后续取行**同源**:数的是 `selectAllActiveByOrderId`,而它的实现就是对 `selectLatestByOrderId(orderId, kind)` 按枚举逐类别循环,所以「数出几类」与「按那一类取到哪行」用的是同一个筛选条件,不可能分叉。
|
||||
|
||||
> 与结算侧 809008 有意不同:那边只有 TRAVEL 才自动解析、单独一条 TRANSFER 也拒绝(手录车费的省略更可能是漏选归属);本处是需求状态机写口,单 TRANSFER 户只有这一条活跃需求,拒绝它等于让这类户走不通流程。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期管理员提交车务 | POST | `/v3/admin/order/{id}/vehicle-requirement/dispatch` | 查询参数取消默认值 + 新增错误码 | `kind` 不再默认 TRAVEL;两类并存且不传抛 809012 |
|
||||
| 2 | 团期管理员打回定制师(车需求) | POST | `/v3/admin/order/{id}/vehicle-requirement/reject` | 查询参数取消默认值 + 新增错误码 | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期管理员提交车务 `POST /v3/admin/order/{id}/vehicle-requirement/dispatch`
|
||||
|
||||
**VO**: `DispatchReqVO → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期需求管理 Tab 里对某一户点「提交车务」时调用,把该户指定类别的用车需求从 `PENDING_REVIEW` 推进到 `PENDING` 并写入提交备注(提供给车队人员查看)。**仅团期子订单可用**。权限点 `group-batch:demand:confirm`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `id` | Path | Long | ✅ | - | 子订单 ID |
|
||||
| `kind` | Query | String | ❌ | 取值 `TRAVEL` / `TRANSFER` | 🔴 **改动点**:不再有默认值 `TRAVEL`。不传时按该户活跃需求类别自动解析(恰好 1 类用那一类;0 类抛 582031;≥2 类抛 809012)。**建议一律显式传**。非法值仍抛 809000 |
|
||||
| `dispatchRemark` | Body | String | ❌ | `@Size(max=500)` | 提交备注,提供给车队的审核意见;上限对齐库列宽 `VARCHAR(500)` |
|
||||
|
||||
#### 出参 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | 200 = 成功 |
|
||||
| `message` | String | `成功` |
|
||||
| `success` | Boolean | `true` |
|
||||
| `data` | null | **本端点无业务数据返回**(`Result<Void>`),成功即以 `code=200` 为准,不要读 `data` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2099459272533323777/vehicle-requirement/dispatch?kind=TRANSFER
|
||||
Content-Type: application/json
|
||||
|
||||
{ "dispatchRemark": "需求已确认,请尽快派接送机车辆" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本端点恒无业务数据,成功时 `data` 恒为 `null`——这是正常成功形态,不是空数据降级:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
该户没有任何活跃用车需求行时不降级、不静默成功,直接抛 582031(下面的错误响应)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
🆕 809012(本次新增;`{0}` = 订单 ID,`{1}` = 两类名以 ` / ` 连接)。以下为测试服实测原文(订单 ID 2105173274755534850):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809012,
|
||||
"message": "订单 2105173274755534850 同时存在 TRAVEL / TRANSFER 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余错误码未变:
|
||||
|
||||
| 码 | 报文 | 触发 |
|
||||
|----|------|------|
|
||||
| 809000 | `用车需求类别非法:{0}` | `kind` 传了 TRAVEL / TRANSFER 之外的值 |
|
||||
| 809007 | `接送机需求 {0} 的服务日期尚未回填,无法下发车务` | 解析到 TRANSFER 但该行 `service_dates` 为 NULL 或空数组 |
|
||||
| 582031 | `订单无有效需求行` | 该户按解析出的类别取不到活跃行(含「一条都没有」) |
|
||||
| 582083 | `需求状态不允许此操作,请检查当前状态` | 非团期子订单,或最新需求不在 `PENDING_REVIEW` |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `group-batch:demand:confirm`(Controller 入口执行,走 user-service Feign);未登录由网关拦截返 401。
|
||||
- **仅团期子订单**:非团期单先报 582083,`kind` 解析排在这道守卫**之后**——所以非团期单的报错与改前逐字相同,不会变成 809012。
|
||||
- **解析只在不传 `kind` 时发生**:传了值就原样使用,包括非法值(仍由 809000 拒),本次改动不改变既有的非法值行为。
|
||||
- **🔴 809012 不是可重试错误**:同一请求重发多少次都是同一个码。处置是**带上 `kind` 重发**。
|
||||
- **纯接送机户现在可用**:只有一条活跃 TRANSFER 行时不传 `kind` 会被解析成 TRANSFER(改前拿 582031)。
|
||||
- **状态机守卫未变**:`PENDING_REVIEW → PENDING`,同时写 `dispatch_remark`、车控置 `PENDING`、CAS 退流程。
|
||||
- **失败零写入**:809012 抛在取行之前、任何写入之前;809007 抛在服务日校验处,同样不落写。
|
||||
|
||||
---
|
||||
|
||||
### 2. 团期管理员打回定制师(车需求) `POST /v3/admin/order/{id}/vehicle-requirement/reject`
|
||||
|
||||
**VO**: `RejectReqVO → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期需求管理 Tab 里对某一户点「打回」时调用,把该户指定类别的用车需求退回定制师重提(`PENDING_REVIEW` / `PENDING` → `REJECTED_TO_CONSULTANT`),写入打回备注,并**同时清掉团级 `requirement_confirmed` 标记 + 写团级时间线**(否则会出现「该户未提交、整团已确认」的矛盾态)。权限点 `group-batch:demand:confirm`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `id` | Path | Long | ✅ | - | 子订单 ID |
|
||||
| `kind` | Query | String | ❌ | 取值 `TRAVEL` / `TRANSFER` | 🔴 **改动点**:不再有默认值 `TRAVEL`,规则与 dispatch 端点逐条相同。**建议一律显式传** |
|
||||
| `returnRemark` | Body | String | ✅ | `@NotBlank`,`@Size(max=500)` | 打回备注;为空返 400「打回/驳回备注不能为空」,超长返 400「打回/驳回备注不能超过 500 字」。定制师重新提交时会创建新需求 |
|
||||
|
||||
#### 出参 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | 200 = 成功 |
|
||||
| `message` | String | `成功` |
|
||||
| `success` | Boolean | `true` |
|
||||
| `data` | null | **本端点无业务数据返回**(`Result<Void>`) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2099459272533323777/vehicle-requirement/reject?kind=TRAVEL
|
||||
Content-Type: application/json
|
||||
|
||||
{ "returnRemark": "行程日与团期不符,请定制师重新确认用车日期" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本端点恒无业务数据,成功时 `data` 恒为 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
打回既要写需求行、又要清团级标记并写团级时间线,是跨聚合编排且与整团确认共用团级锁——**不存在「只做了一半」的降级形态**,要么整套生效要么整体回滚。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
🆕 809012(本次新增,与 dispatch 端点同码同文案)。以下为测试服实测原文(订单 ID 2105173313083080706):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 809012,
|
||||
"message": "订单 2105173313083080706 同时存在 TRAVEL / TRANSFER 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余错误码未变:
|
||||
|
||||
| 码 | 报文 | 触发 |
|
||||
|----|------|------|
|
||||
| 809000 | `用车需求类别非法:{0}` | `kind` 传了非法值 |
|
||||
| 582031 | `订单无有效需求行` | 按解析出的类别取不到活跃行 |
|
||||
| 582083 | `需求状态不允许此操作,请检查当前状态` | 非团期子订单,或最新需求不在 `PENDING_REVIEW` / `PENDING` |
|
||||
| 400 | `打回/驳回备注不能为空` | `returnRemark` 空 |
|
||||
|
||||
> `589535`(子订单已分房)**不适用于本端点**:占用探测方法对 `resourceType=VEHICLE` 硬编码返回"无占用"(车侧无配房概念),该码只在 `resourceType=HOTEL` 时可能触发;已派车的拦截由另一套栅栏机制负责,不经过本码。这是既有行为,本次未改。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:权限点 `group-batch:demand:confirm`;未登录由网关拦截返 401。
|
||||
- **kind 解析结果同时用于占用探测与实际取行**:两步必须看同一个类别,避免错位——但车需求侧的占用探测恒不拦截(见上方说明),此为既有行为,本次未改。
|
||||
- **只对车需求解析**:同一条服务方法也承接房需求打回(`resourceType=HOTEL`),`kind` 对它无意义、不触发解析,**酒店打回不会被车侧的两类并存误伤**。
|
||||
- **副作用是跨聚合的**:除需求行外还会清团级 `requirement_confirmed` 并写团级时间线 `BATCH_REQUIREMENT_REJECT`,与批量打回落同一套副作用。
|
||||
- **打回后需求要重提**:定制师重新提交会创建**新的需求行**,不是在原行上改。
|
||||
- **🔴 809012 不是可重试错误**:处置是带上 `kind` 重发。
|
||||
- **失败零写入**:809012 抛在取行与实写之前。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受 / 拒绝请求的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 调用对照
|
||||
|
||||
| 场景 | 请求 | 结果 |
|
||||
|------|------|------|
|
||||
| ✅ 显式指定行程用车(**推荐写法**) | `POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRAVEL` + `{ "dispatchRemark": "..." }` | 200 |
|
||||
| ✅ 显式指定接送机(**推荐写法**) | `POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRANSFER` + `{ "dispatchRemark": "..." }` | 200 |
|
||||
| ✅ 不传 kind,该户只有一类活跃需求 | `POST /v3/admin/order/{id}/vehicle-requirement/dispatch` | 200,按那一类处理(纯接送机户从此可用) |
|
||||
| ✅ 打回(备注必填) | `POST /v3/admin/order/{id}/vehicle-requirement/reject?kind=TRAVEL` + `{ "returnRemark": "请重新确认用车日期" }` | 200 |
|
||||
| ❌ 不传 kind,该户两类活跃需求并存 | `POST /v3/admin/order/{id}/vehicle-requirement/reject` | **809012**,必须带 kind 重发 |
|
||||
| ❌ kind 传非法值 | `?kind=travel2` | 809000 |
|
||||
| ❌ 打回不带备注 | `{ "returnRemark": "" }` | 400「打回/驳回备注不能为空」 |
|
||||
| ❌ 备注超 500 字 | `{ "returnRemark": "<501 字>" }` | 400「打回/驳回备注不能超过 500 字」 |
|
||||
|
||||
### 前端必须做的改动
|
||||
|
||||
1. **两个端点的调用一律显式带上 `kind`**。页面上这两个按钮本来就分挂在 TRAVEL / TRANSFER 两块需求下,取值是现成的;带上之后永远不会撞 809012。
|
||||
2. **接住 809012**:若某处确实拿不到类别,撞到 809012 时要提示用户选择类别并**带上 `kind` 重发**,不要做自动重试(同一请求重发永远同码)。
|
||||
3. **不要再依赖「不传 = TRAVEL」这个隐含约定**——它已经不成立了。
|
||||
|
||||
> 🔴 **落点已查明(2026-09-30 对 `hl-ui` `origin/v2.1` 查证)**:`src/api/orderV2.js` 里
|
||||
> `rejectVehicleRequirement` 的 JSDoc 写着「`TRAVEL / TRANSFER`;不传保持旧行为(按 TRAVEL)」、
|
||||
> `dispatchVehicleRequirement` 写着「不传=后端缺省 TRAVEL」——**这两句现在都是错的**,
|
||||
> 两处都是 `const query = kind ? { kind } : null`,调用方不传就会走到新行为上。
|
||||
> 另外全仓 `809012` 命中数为 **0**,即该码今天没有任何接住的地方。
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本次改动**没有任何表结构变化**,变的是「写哪一行」的定位规则。
|
||||
|
||||
| 前端调用 | 该户活跃需求 | 改前写入 | 改后写入 |
|
||||
|----------|--------------|----------|----------|
|
||||
| 不传 `kind` | 只有 TRAVEL | TRAVEL 行 | TRAVEL 行(未变) |
|
||||
| 不传 `kind` | 只有 TRANSFER | ❌ 取不到 TRAVEL 行 → 582031,零写入 | ✅ **TRANSFER 行** |
|
||||
| 不传 `kind` | TRAVEL + TRANSFER 并存 | ❌ **静默写 TRAVEL 行**(返 200,操作者要动的那行三个字段全不变) | ✅ **809012 拒绝,零写入** |
|
||||
| 不传 `kind` | 一条活跃行都没有 | 582031,零写入 | 582031,零写入(未变) |
|
||||
| 传 `kind=TRAVEL` | 任意 | TRAVEL 行 | TRAVEL 行(未变) |
|
||||
| 传 `kind=TRANSFER` | 任意 | TRANSFER 行 | TRANSFER 行(未变) |
|
||||
|
||||
写入内容本身未变:
|
||||
|
||||
- **dispatch**:`order_vehicle_requirement` 该行 `status` `PENDING_REVIEW → PENDING`、写 `dispatch_remark`(≤500)、车控置 `PENDING`、CAS 退流程。
|
||||
- **reject**:该行 `status → REJECTED_TO_CONSULTANT`、写 `return_remark`(≤500);同事务清团级 `requirement_confirmed`、写团级时间线 `BATCH_REQUIREMENT_REJECT`。
|
||||
|
||||
**失败零写入**:809012 抛在解析阶段(dispatch 里排在团期守卫之后、取行之前;reject 里排在占用探测之前),任何一条数据都不会落。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 权限点 `group-batch:demand:confirm` 缺失 → 403。
|
||||
- 非团期子订单 → 582083(这道守卫排在 `kind` 解析之前,报错与改前逐字相同)。
|
||||
- 需求状态不在允许的源状态集合 → 582083。
|
||||
- 该户按解析出的类别取不到活跃行 → 582031。
|
||||
- 解析到 TRANSFER 但服务日未回填 → 809007(dispatch 端点,失败关闭不放行)。
|
||||
- `kind` 传非法值 → 809000(与改前一致,本次未改变非法值行为)。
|
||||
- 老数据兼容:不改表、不迁移;存量订单下次调用时按新解析规则生效。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### kind(用车需求类别)
|
||||
|
||||
**所属字段**: 两个端点的查询参数 `kind` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `TRAVEL` | 团期行程用车 | 汇总进团级乘车分组、整团逐日配车的那一类 |
|
||||
| `TRANSFER` | 接送机 | 逐户派车的那一类;服务日由大交通派生,未回填时 dispatch 抛 809007 |
|
||||
| (不传) | — | 🔴 **不再等价于 `TRAVEL`**。按该户活跃需求类别数解析:1 类用那一类 / 0 类抛 582031 / ≥2 类抛 809012 |
|
||||
| 其他任意值 | — | 非法,抛 809000 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `kind`(两个端点的查询参数) | `@RequestParam(defaultValue = "TRAVEL")`,Swagger 标 `defaultValue=TRAVEL` | `@RequestParam(required = false)`,**无默认值**;Swagger 文案改为「不传按该户活跃需求类别自动解析,两类并存时必须显式指定」 |
|
||||
| `DispatchReqVO.dispatchRemark` | 选填 ≤500 | 未变 |
|
||||
| `RejectReqVO.returnRemark` | 必填 ≤500 | 未变 |
|
||||
| 两个端点的响应 | `Result<Void>` | 未变 |
|
||||
| 错误码集合 | 809000 / 809007(仅 dispatch)/ 582031 / 582083 | 🆕 **增加 809012** |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 不传 `kind` + 两类并存 | **静默改 TRAVEL 行,返 200**;操作者要动的 TRANSFER 行原封不动,响应上无任何信号 | 抛 **809012**,零写入 |
|
||||
| 不传 `kind` + 只有 TRANSFER | 去找不存在的 TRAVEL 行 → 582031,**这类户走不通流程** | 解析成 TRANSFER,正常执行 |
|
||||
| 不传 `kind` + 只有 TRAVEL | TRAVEL 行 | 未变 |
|
||||
| 不传 `kind` + 一条活跃行都没有 | 582031 | 未变 |
|
||||
| 传了 `kind`(合法或非法) | 原样使用 / 809000 | 未变 |
|
||||
| 非团期子订单 | 582083 | 未变(守卫排在解析之前) |
|
||||
| 房需求打回(`resourceType=HOTEL`) | `kind` 无意义 | 未变(不触发车侧解析,不会被两类并存误伤) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: **是**。旧调用方「不传 `kind` = 按 TRAVEL」的隐含约定已失效;两类活跃需求并存的户上,原本返 200 的请求现在会返 809012。
|
||||
- **前端是否必须同步上线**: **是(建议)**。不改也不会报错的前提是「该户只有一类活跃需求」,一旦出现两类并存就会撞 809012。改法极小:调用时把已知的类别放进 `kind` 查询参数,并接住 809012。
|
||||
- **前端 workaround 清理点**: 若为绕开「纯接送机户点提交车务报 582031」做过按钮置灰、隐藏或提示,可以撤掉——该场景已修好。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `POST /v3/admin/order/{id}/vehicle-requirement/dispatch` 与 `POST /v3/admin/order/{id}/vehicle-requirement/reject` 两个端点的 `kind` 参数语义。
|
||||
- **零影响**:
|
||||
- 房需求(`resourceType=HOTEL`)的提交与打回链路
|
||||
- 定制师侧提交 / 修改 / 调整用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`
|
||||
- 团级正式用车需求的保存 / 汇总 / 预检 / 确认 / 撤回 / 免车
|
||||
- 批量打回、整团确认的既有行为
|
||||
- 结算侧手录车费的归属解析(809008,口径有意不同,本次未动)
|
||||
- 车务侧(hl-fleet-service)配车、派单
|
||||
- 历史数据:不改表、不迁移
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||||
- 合并提交 `a2ba628ecb`(PR #8610 squash 合并进 `dev-v3`),7 文件 / +491 −27。
|
||||
- `VehicleRequirementAdminController` 两处 `@RequestParam(defaultValue = "TRAVEL")` → `@RequestParam(required = false)`,`@ApiParam` 文案同步改写,diff 已逐行核对。
|
||||
- 新增 `VehicleRequirementKindErrorCode.VEHICLE_REQUIREMENT_KIND_REQUIRED = IErrorCode.of(809012, …)`,消息模板与占位符含义(`{0}`=订单 ID、`{1}`=两类名以 ` / ` 连接)已核对;同段既有码 809000/809001/809002/809007/809008/809009/809010/809011 未变。
|
||||
- 新增私有方法 `RequirementService#resolveVehicleRequirementKind(Long, String)`,三条分支(非空白原样返回 / 0 类原样返回 / 1 类用那一类 / ≥2 类抛 809012)逐行核对;dispatch 里的调用点排在团期守卫之后、取行之前,reject 里排在 `probeGroupAdminReject` 之前且仅对 `resourceType=VEHICLE` 生效。
|
||||
- 回归覆盖:`RequirementServiceTest` +301 行、`VehicleRequirementAdminControllerTest` +72 行、`VehicleRequirementKindErrorCodeMessageTest` +22 行(含 809012 报文渲染断言)。
|
||||
- **部署**:`hl-order-service-v3` 的 `dev-v3` 分支已滚到测试服,两个端点走管理端网关 `/v3/admin/order/**` 既有路由,无新增路由。
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRANSFER → 200 ✓
|
||||
POST /v3/admin/order/{id}/vehicle-requirement/reject?kind=TRAVEL → 200 ✓
|
||||
POST /v3/admin/order/{id}/vehicle-requirement/dispatch(两类并存不传 kind) → 809012 ✓
|
||||
POST /v3/admin/order/{id}/vehicle-requirement/dispatch(纯接送机户不传 kind) → 200 ✓(改前 582031)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #7210 | 逐单打回源状态放宽到 PENDING,接团期权限守卫 | ✅ 有效 |
|
||||
| — | #7439 | 用车需求按 kind 分家,两个端点加 `kind` 参数(当时带默认值 TRAVEL) | ⚠️ 默认值部分已被本单撤销 |
|
||||
| — | #8435 | 团期订单接送变更走团期放行(809011) | ✅ 有效 |
|
||||
| **本 PR #8610** | **#8601** | `kind` 取消默认值,空值按活跃类别数分流,新增 809012 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8601](https://git.1814.love:8443/wx/HL/issues/8601)
|
||||
- 关联 PR: [wx/HL#8610](https://git.1814.love:8443/wx/HL/pulls/8610)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8601](https://git.1814.love:8443/wx/HL/issues/8601)
|
||||
- **PR**: [#8610](https://git.1814.love:8443/wx/HL/pulls/8610)
|
||||
- **Merge commit**: [a2ba628ecb](https://git.1814.love:8443/wx/HL/commit/a2ba628ecb)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,430 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8603"
|
||||
title: "派单原子确认响应删除恒为空的 missingPickupDates / missingDropoffDates,缺失日期只由 605914/605915 错误消息承载"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #8608 已 squash 合并 dev-v3(b4eb919b47),hl-fleet-service dev-v3 分支已滚测试服。删除的两个字段是 #8579 加的、在本端点上恒为空数组;接送机缺口在本端点是硬门禁,日期写在 605914/605915 的错误消息里。真正带「已落库但还差几天」中间态的是 POST /admin/fleet/assignments/batch 的 pickupDropoffGate 对象,该对象未动。【同名字段两个载体,勿按字段名 grep 判断影响面,2026-09-30 查证】hl-ui origin/v2.1 的 src/views/fleet/board/components/Step3PickupDropoff.vue:178-179 确实渲染 missingPickupDates / missingDropoffDates(「待配置接机 / 送机」两行),但它取的是 props.gate,而 gate 由 AssignModal.vue:1626 与 OrderDrawer.vue:803 从 props.order.pickupDropoffGate 派生—— 即上面那个未动的对象,与本次删字段的 ConfirmRequirementRespVO 不是同一个载体。本端点(POST /admin/fleet/assignments/requirements/{id}/confirm,前端出口 src/api/fleet/board.js:162)的响应上,这两个键没有任何读取点,故 not_required 成立。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# hl-fleet-service: 派单原子确认响应删除恒为空的接送机缺失日期字段
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-fleet-service (端口 8089)
|
||||
> **PR**: #8608
|
||||
> **Issue**: #8603
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 车务四步向导第③步「按需求整组原子确认」的响应体
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 🔴 **`ConfirmRequirementRespVO` 删除两个字段**:`missingPickupDates`、`missingDropoffDates`。它们是 #8579 加进来的,在**本端点上恒为空数组**。
|
||||
- **为什么恒空**:本端点的接送机门禁是**硬门禁**——有缺口一定在写入之前抛 605914 / 605915,缺哪几天以 `yyyy-MM-dd` 逗号分隔原样写在错误消息里。**能拿到 200 响应,就说明门禁已经通过了**,此时「缺失日期」这个概念在本端点上不存在。
|
||||
- 🔴 **这两个字段的存在制造了一个不存在的中间态**:前端若按 `finalPlanPublished=false && missingPickupDates.length>0` 去渲染「确认成功但还差 N 天」,这个分支**永远不会成立**——本端点没有这种中间态。
|
||||
- ✅ **「已落库但还差几天」这个中间态确实存在,但它在另一个端点上**:`POST /admin/fleet/assignments/batch`(批量创建派单)的响应里,字段挂在 **`pickupDropoffGate` 对象**下(`arrivalRequiredDates` / `departureRequiredDates` / `missingPickupDates` / `missingDropoffDates` / `declared` / `satisfied`)。**该对象本次未动,全部字段照旧**。要做「还差哪几天」的提示,读那里。
|
||||
- **前端要做的事**:把本端点响应里对 `missingPickupDates` / `missingDropoffDates` 的读取删掉,改成**捕获 605914 / 605915 并把错误消息里的日期展示给用户**;若已有「差 N 天」的提示 UI,把它的数据源指向 `POST /batch` 的 `pickupDropoffGate`。
|
||||
- **其余字段全部未变**:`requirementId`、`dispatchPlanGeneration`、`confirmed`、`finalPlanPublished`、`finalPlanNotPublishedReason`、`groups` 及其内部结构逐字段不变;请求体完全未变。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
车务四步向导第③步是「按当前派车方案代际原子确认全部执行段」。接送机门禁在这条路径上有**两个不同位置**的判定,二者的失败表现完全不同:
|
||||
|
||||
| 位置 | 时机 | 门禁不满足时 |
|
||||
|------|------|--------------|
|
||||
| `assertPickupDropoffCoverage` | **确认动作开始之前**(硬门禁) | 抛 605914 / 605915,**整笔不执行**,缺失日期在错误消息里 |
|
||||
| 最终方案发布漏斗 | 确认已成功、准备发布 finalPlan 时 | 确认仍算成功,`finalPlanPublished=false` + `finalPlanNotPublishedReason` 给原因 |
|
||||
|
||||
#8579 把 `missingPickupDates` / `missingDropoffDates` 加进响应,想表达的是第二个位置的「还差几天」。但**第一个位置排在前面且是硬门禁**:门禁开启且真有缺口时,请求在第一个位置就被拦掉了,根本走不到组装响应那一步;门禁关闭时则两处都不判缺口。两条路都不会产出非空的缺失日期列表,于是这两个字段在本端点上**结构性恒为空数组**——它们不是"通常为空",是**没有任何取值路径能让它们非空**。
|
||||
|
||||
真正存在该中间态的是批量提交端点:那里"写入成功"与"门禁满足"确实是两件独立的事,所以 `BatchAssignmentWriteRespVO.pickupDropoffGate` 里的六个字段有实际取值。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 按当前派车方案代际原子确认全部执行段 | POST | `/admin/fleet/assignments/requirements/{requirementId}/confirm` | 响应删除字段 | 删除恒为空的 `missingPickupDates` / `missingDropoffDates`;缺口由 605914/605915 承载 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 按当前派车方案代际原子确认全部执行段 `POST /admin/fleet/assignments/requirements/{requirementId}/confirm`
|
||||
|
||||
**VO**: `ConfirmRequirementReqVO → ConfirmRequirementRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务四步向导第③步的整组原子确认入口。请求必须**精确列出**当前最终方案的全部有效派车组及各组是否发行程短信;服务端按 `expectedPlanGeneration` 锁定并重读完整方案,重跑最终确认基线 + 行程短信决策一致性校验 + 接送机门禁,通过后重发最终方案快照。任一组缺失、过期或通知歧义则**整笔回滚**,不产生部分 assigned、不产生部分副作用。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `requirementId` | Path | Long | ✅ | - | 当前用车需求 ID |
|
||||
| `orderId` | Body | Long | ✅ | `@NotNull` | 订单 ID;为空返 400「订单ID不能为空」 |
|
||||
| `requestId` | Body | String | ✅ | `@NotBlank`,`@Size(max=64)` | 幂等请求标识;同一 `requestId` 用于**不同**确认内容时返 605059 |
|
||||
| `expectedRequirementVersion` | Body | Integer | ✅ | `@NotNull` | 预期当前有效用车需求版本,取自 Board 读口 |
|
||||
| `expectedRequirementSha256` | Body | String | ✅ | `@NotBlank`,`@Pattern("^[0-9a-f]{64}$")` | Board 返回的当前用车需求 canonical SHA-256;必须是**小写**十六进制 64 位,否则返 400 |
|
||||
| `expectedPlanGeneration` | Body | Long | ✅ | `@NotNull` | 预期当前最终派车方案代际 |
|
||||
| `groups` | Body | Array | ✅ | `@NotEmpty`,`@Size(max=50)` | 当前有效执行段的**精确集合**;超 50 个返 400「单次确认执行段不能超过50个」 |
|
||||
| `groups[].assignmentGroupId` | Body | Long | ✅ | `@NotNull` | 当前有效派车组 ID |
|
||||
| `groups[].sendItinerarySms` | Body | Boolean | ✅ | `@NotNull` | 是否向本执行段司机发送行程短信;为空返 400「请选择是否向本段司机发送行程短信」 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `requirementId` | String | 用车需求 ID(雪花 ID,JSON 中为字符串) |
|
||||
| `dispatchPlanGeneration` | String | 已确认的最终派车方案代际(JSON 中为字符串) |
|
||||
| `confirmed` | Boolean | 整组是否原子确认成功;返 200 时恒为 `true` |
|
||||
| `finalPlanPublished` | Boolean | 本次是否真的发布了最终方案快照。`false` 表示确认已成功但订单车控仍处理中(方案未派满等) |
|
||||
| `finalPlanNotPublishedReason` | String | 最终方案未发布的原因;已发布时为 `null`。取值见「六.5、枚举 / 数据字典」 |
|
||||
| ~~`missingPickupDates`~~ | ~~Array~~ | 🔴 **本次删除**(#8579 加入,在本端点恒为空数组)。缺失接机日改由 605914 的错误消息承载 |
|
||||
| ~~`missingDropoffDates`~~ | ~~Array~~ | 🔴 **本次删除**(同上)。缺失送机日改由 605915 的错误消息承载 |
|
||||
| `groups` | Array | 各执行段确认结果 |
|
||||
| `groups[].assignmentId` | String | 代表派单 ID(雪花 ID,JSON 中为字符串) |
|
||||
| `groups[].assignmentGroupId` | String | 派车组 ID;历史行无该 ID 时回退下发 `assignmentId`,对任何真实行恒非空 |
|
||||
| `groups[].assignmentStatus` | String | 派单状态 |
|
||||
| `groups[].confirmedAt` | String | 车务最终确认时间(`yyyy-MM-dd HH:mm:ss`) |
|
||||
| `groups[].sendItinerarySms` | Boolean | 本段是否选择了发送行程短信(回显请求中的选择) |
|
||||
| `groups[].itinerarySmsEventId` | String | 行程短信 Outbox 事件 ID;**未发送时为 `null`** |
|
||||
| `groups[].itinerarySmsStatus` | String | 行程短信状态,取值见「六.5、枚举 / 数据字典」 |
|
||||
| `groups[].itineraryUrl` | String | 本段电子行程单 H5 链接;本端点组装时**恒为 `null`**,签发链接请走行程短信状态查询端点 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2099459272533323777",
|
||||
"requestId": "confirm-2099459272533323777-20260930-01",
|
||||
"expectedRequirementVersion": 3,
|
||||
"expectedRequirementSha256": "9f2c4e1ab7d05836c41fbe2907a5d4638e1c0b7a53d92f8146ce70bb2d5a3ff4",
|
||||
"expectedPlanGeneration": "12",
|
||||
"groups": [
|
||||
{ "assignmentGroupId": "2099461003812864001", "sendItinerarySms": true },
|
||||
{ "assignmentGroupId": "2099461003812864002", "sendItinerarySms": false }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"requirementId": "2099460881234567890",
|
||||
"dispatchPlanGeneration": "12",
|
||||
"confirmed": true,
|
||||
"finalPlanPublished": true,
|
||||
"finalPlanNotPublishedReason": null,
|
||||
"groups": [
|
||||
{
|
||||
"assignmentId": "2099461003812864001",
|
||||
"assignmentGroupId": "2099461003812864001",
|
||||
"assignmentStatus": "assigned",
|
||||
"confirmedAt": "2026-09-30 10:12:33",
|
||||
"sendItinerarySms": true,
|
||||
"itinerarySmsEventId": "2099461099887766554",
|
||||
"itinerarySmsStatus": "PENDING",
|
||||
"itineraryUrl": null
|
||||
},
|
||||
{
|
||||
"assignmentId": "2099461003812864002",
|
||||
"assignmentGroupId": "2099461003812864002",
|
||||
"assignmentStatus": "assigned",
|
||||
"confirmedAt": "2026-09-30 10:12:33",
|
||||
"sendItinerarySms": false,
|
||||
"itinerarySmsEventId": null,
|
||||
"itinerarySmsStatus": "NOT_SENT",
|
||||
"itineraryUrl": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意响应里没有 `missingPickupDates` / `missingDropoffDates` 两个键**——不是值为空数组,是**键本身不存在**。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
「确认成功但最终方案未发布」是本端点唯一的部分成功形态:`confirmed=true` + `finalPlanPublished=false` + `finalPlanNotPublishedReason` 给出原因。此时**没有缺失日期可读**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"requirementId": "2099460881234567890",
|
||||
"dispatchPlanGeneration": "12",
|
||||
"confirmed": true,
|
||||
"finalPlanPublished": false,
|
||||
"finalPlanNotPublishedReason": "PLAN_INCOMPLETE",
|
||||
"groups": [
|
||||
{
|
||||
"assignmentId": "2099461003812864001",
|
||||
"assignmentGroupId": "2099461003812864001",
|
||||
"assignmentStatus": "assigned",
|
||||
"confirmedAt": "2026-09-30 10:12:33",
|
||||
"sendItinerarySms": false,
|
||||
"itinerarySmsEventId": null,
|
||||
"itinerarySmsStatus": "NOT_SENT",
|
||||
"itineraryUrl": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`groups` 恒非空(请求 `@NotEmpty` 保证至少一段,且每段都要有结果)。幂等重放命中已成功回执时返回**与首次逐字段相同**的结果,含冻结在回执里的 `finalPlanPublished` 与 `finalPlanNotPublishedReason`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
接送机缺口的唯一载体(`{0}` = 缺失日期,`yyyy-MM-dd` 逗号分隔、升序):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605914,
|
||||
"message": "大交通要求接机,以下日期未配置接机车辆:2026-10-08,2026-10-09",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 605915,
|
||||
"message": "大交通要求送机,以下日期未配置送机车辆:2026-10-12",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
其余错误码(本次未变):
|
||||
|
||||
| 码 | 报文 | 触发 / 处置 |
|
||||
|----|------|-------------|
|
||||
| 605062 | `派车日期 {0} 越出当前{1}日期窗(版本 v{2},窗内服务日 {3}):请先调整或取消这些越窗槽位,或让定制师重新提交{1}换版后再派车` | 存在越窗在途槽位;须先调整或取消 |
|
||||
| 605037 | `车辆处于维保或停用状态,不能派车:{0}` | 先改派换车 |
|
||||
| 605038 | `司机处于休假或待激活状态,不能派车:{0}` | 先改派换司机 |
|
||||
| 605059 | `幂等请求标识已用于不同确认内容` | 同一 `requestId` 配了不同载荷;**换新 `requestId` 重试** |
|
||||
| 605063 | `原子确认回执已损坏,无法幂等重放,请联系管理员` | 🔴 **不可自愈终态**,重试同一 `requestId` 永远同码;前端**不得自动重试、不得静默轮询**,须直接提示用户联系管理员 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**:`AssignmentController` 未挂方法级权限注解,只有网关登录态校验;未登录返 401。
|
||||
- 🔴 **缺失日期只存在于错误消息里**:本端点拿到 200 就代表接送机门禁已通过,不要在响应体里找缺口字段。
|
||||
- 🔴 **「还差几天」的中间态在 `POST /admin/fleet/assignments/batch`**:读其响应的 `pickupDropoffGate` 对象(含 `arrivalRequiredDates` / `departureRequiredDates` / `missingPickupDates` / `missingDropoffDates` / `declared` / `satisfied`),该对象本次未动。
|
||||
- **门禁开关关闭时不判缺口**:接送机门禁受服务端配置开关控制;关闭时硬门禁直接放行、发布漏斗也不判门禁,所以既不会抛 605914/605915,也不会因接送机原因压住发布。这是服务端配置项,**不是请求参数,前端无法也无需感知**。
|
||||
- **`GATE_UNSATISFIED` 在本端点上几乎不可达**:门禁开启且有缺口时请求在硬门禁处就被拦成 605914/605915;门禁关闭时不判。它只剩「需求身份不全」的兜底分支,而那条分支按源码注释本来就**没有任何缺失日期可言**。
|
||||
- **`groups` 必须是精确集合**:少给一组、多给一组、或组已过期,整笔回滚返错,不会部分生效。
|
||||
- **幂等**:以 `requestId` 为键;重放已成功的回执返回同一份结果(含冻结的发布结论),载荷变了返 605059。
|
||||
- **`itineraryUrl` 在本响应中恒为 `null`**:行程单链接由行程短信状态查询端点下发。
|
||||
- **失败零副作用**:所有门禁与基线校验都排在写入之前,报错时不产生部分 assigned、不产生短信事件。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受 / 拒绝请求的规则与响应字段的正确读法,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 读法对照
|
||||
|
||||
| 目的 | ✅ 正确做法 | ❌ 错误做法 |
|
||||
|------|-------------|-------------|
|
||||
| 判断「接送机缺哪几天」 | 捕获 605914 / 605915,从 `message` 里取日期(`yyyy-MM-dd` 逗号分隔) | 读本端点响应的 `missingPickupDates` / `missingDropoffDates`——**这两个键已不存在** |
|
||||
| 渲染「已落库但还差 N 天」 | 读 `POST /admin/fleet/assignments/batch` 响应的 `data.pickupDropoffGate.missingPickupDates` / `.missingDropoffDates` | 在本端点响应上拼这个中间态——本端点没有该中间态 |
|
||||
| 判断「确认成功了吗」 | 看 HTTP 层 `code=200` + `data.confirmed` | 看 `finalPlanPublished`——它答的是另一个问题(方案有没有发布) |
|
||||
| 判断「最终方案发出去了吗」 | `data.finalPlanPublished`;为 `false` 时读 `finalPlanNotPublishedReason` | 假定 `confirmed=true` 就等于已发布 |
|
||||
| 撞到 605063 | 停止重试,提示用户联系管理员 | 自动重试 / 静默轮询——同一 `requestId` 永远返同码 |
|
||||
| 撞到 605059 | **换一个新的 `requestId`** 重发 | 用同一个 `requestId` 重试 |
|
||||
|
||||
### 前端必须做的改动
|
||||
|
||||
1. **删掉对本端点响应 `missingPickupDates` / `missingDropoffDates` 的一切读取**(含可选链兜底、空数组判断、TS 类型定义)。
|
||||
2. **接送机缺口提示改走 605914 / 605915 的错误消息**,日期在 `message` 里逐字给出。
|
||||
3. 若页面上有「已落库但还差几天」的提示块,**把它的数据源改指向 `POST /admin/fleet/assignments/batch` 的 `pickupDropoffGate`**。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
**本次改动不涉及任何数据库变更**:无建表、无加列、无改列、无数据迁移、无 Flyway 脚本。
|
||||
|
||||
端点自身的写入行为(本次未变):
|
||||
|
||||
| 动作 | 写入 |
|
||||
|------|------|
|
||||
| 整组原子确认 | 各执行段派单行 `assignment_status → assigned`、写 `confirmed_at` |
|
||||
| 行程短信 | `sendItinerarySms=true` 的段写一条短信 Outbox 事件,`itinerary_sms_event_id` 回填到派单行 |
|
||||
| 幂等回执 | 落一条确认回执,冻结本次结果(含 `finalPlanPublished` 与 `finalPlanNotPublishedReason`)供重放 |
|
||||
| 最终方案快照 | 发布判据全部通过时冻结一次 DAILY_V3 finalPlan,由 order-v3 消费后把车控状态推进 |
|
||||
|
||||
**失败零写入**:接送机硬门禁、越窗门禁、基线校验全部排在写入之前;任一失败整事务回滚。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 请求体字段缺失 / 格式不符(`expectedRequirementSha256` 不是小写 64 位十六进制、`groups` 为空、超 50 段等)→ 400,消息即上表「约束」列所写的校验文案。
|
||||
- 接送机门禁开启且缺接机日 → 605914,日期在消息里,零写入。
|
||||
- 接送机门禁开启且缺送机日 → 605915,日期在消息里,零写入(接机缺口先判,两者都缺时先报 605914)。
|
||||
- 接送机门禁关闭 → 不判缺口,既不抛 605914/605915,也不因接送机压住发布。
|
||||
- 存在越窗在途槽位 → 605062,零写入。
|
||||
- 车辆维保/停用、司机休假/待激活 → 605037 / 605038,零写入。
|
||||
- 同一 `requestId` 配不同载荷 → 605059;换新 `requestId` 即可。
|
||||
- 回执损坏 → 605063,不可自愈终态。
|
||||
- 幂等重放命中成功回执 → 200,返回与首次逐字段相同的结果。
|
||||
- 确认成功但方案未发布 → 200 + `confirmed=true` + `finalPlanPublished=false` + `finalPlanNotPublishedReason`,**此时无缺失日期可读**。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### finalPlanNotPublishedReason(最终方案未发布原因)
|
||||
|
||||
**所属字段**: `data.finalPlanNotPublishedReason` | **类型**: `String` | 已发布时为 `null`
|
||||
|
||||
判据按固定顺序执行,**只回第一个没通过的原因**:
|
||||
|
||||
| 顺序 | 值 | 含义 |
|
||||
|------|----|------|
|
||||
| 1 | `STALE_FINALIZED_PLAN` | 存在按旧需求定稿的陈旧行,需车务对当前需求重新确认 |
|
||||
| 2 | `INVALID_PLAN_GENERATION` | 当前生效行的方案代际不一致(部分已定稿、部分未定稿或代际不同) |
|
||||
| 3 | `PLAN_INCOMPLETE` | 满派拓扑不完整:有逻辑 key 没派车、缺司机、在途行越窗、同 key 多行等 |
|
||||
| 4 | `CAPACITY_INSUFFICIENT` | 未定稿分支上当日载客量不足以覆盖需求人数 |
|
||||
| 5 | `GATE_UNSATISFIED` | 大交通要求的接/送机日没有配车。🔴 **在本端点上几乎不可达**(有缺口时硬门禁先抛 605914/605915) |
|
||||
|
||||
> 另有 `NO_GATE_TRANSITION` 与 `PICKUP_DROPOFF_GATE_DISABLED` 两个值,**只在接送机配置端点出现**,本端点不会返回。
|
||||
|
||||
### itinerarySmsStatus(行程短信状态)
|
||||
|
||||
**所属字段**: `data.groups[].itinerarySmsStatus` | **类型**: `String`
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `NOT_SENT` | 本段未选择发送,或历史行没有短信事件 |
|
||||
| `PENDING` | 本次已产生短信 Outbox 事件,投递中 |
|
||||
| `SENT` | 短信已发出(出现在已确认段的重放回显里) |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.missingPickupDates` | `Array<String>`,**恒为空数组 `[]`** | 🔴 **键已删除,响应中不存在** |
|
||||
| `data.missingDropoffDates` | `Array<String>`,**恒为空数组 `[]`** | 🔴 **键已删除,响应中不存在** |
|
||||
| `data.requirementId` | String | 未变 |
|
||||
| `data.dispatchPlanGeneration` | String | 未变 |
|
||||
| `data.confirmed` | Boolean | 未变 |
|
||||
| `data.finalPlanPublished` | Boolean | 未变 |
|
||||
| `data.finalPlanNotPublishedReason` | String / null | 未变 |
|
||||
| `data.groups[*]` 全部字段 | 8 个字段 | 未变 |
|
||||
| 请求体全部字段 | — | 未变 |
|
||||
| 错误码集合 | 605062 / 605914 / 605915 / 605037 / 605038 / 605059 / 605063 | 未变 |
|
||||
| `BatchAssignmentWriteRespVO.pickupDropoffGate` | 6 个字段 | **未变**(缺失日期的正确来源) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 接送机有缺口 + 门禁开启 | 抛 605914/605915(响应根本到不了组装步) | 未变 |
|
||||
| 接送机门禁通过、拿到 200 | 响应带两个**恒为空**的日期数组 | 响应**不含**这两个键 |
|
||||
| 前端按 `missingPickupDates.length > 0` 判缺口 | 永远为 `false`,分支不可达 | 该字段不存在;改捕获 605914/605915 |
|
||||
| 「已落库但还差几天」的读法 | 本端点读不到(恒空),实际在 `POST /batch` | 未变,仍在 `POST /batch` 的 `pickupDropoffGate` |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: **是(响应删字段)**。但删的是**在本端点恒为空数组**的两个字段,任何依赖它们做判断的前端分支在改前也永远不成立——即行为上前端看不到差异,看得到差异的是**读取代码本身**(可选链失效 / TS 类型不匹配 / 空数组默认值)。
|
||||
- **前端是否必须同步上线**: **建议同步**。JS 里读不存在的键得 `undefined`,若代码写的是 `resp.data.missingPickupDates.length` 会抛 TypeError;写成 `?.length` 或有默认值则不报错。TS 侧需删掉类型声明里的这两个字段。
|
||||
- **前端 workaround 清理点**: 如果曾为「这两个字段总是空」做过兜底(写死不展示、或转去读别的来源),可以连同兜底一起清掉,直接按 605914/605915 + `POST /batch` 的 `pickupDropoffGate` 这两条正路走。
|
||||
- **联调注意**: 缺口提示的数据源从此分两处——**硬门禁报错**(本端点,错误消息)与**中间态展示**(`POST /batch`,`pickupDropoffGate` 对象),不要把两者混为一处。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 的响应体字段集合。
|
||||
- **零影响**:
|
||||
- `POST /admin/fleet/assignments/batch` 及其 `pickupDropoffGate` 对象(六个字段全部保留,取值逻辑未动)
|
||||
- 接送机配置端点 `POST /admin/fleet/assignments/requirements/{requirementId}/pickup-dropoff`(含它专属的 `NO_GATE_TRANSITION` / `PICKUP_DROPOFF_GATE_DISABLED` 两个原因值)
|
||||
- 派单创建 / 修改 / 取消 / 软清 / 一键重派推荐 / 候选查询 / 预校验
|
||||
- 行程短信状态查询与受控重发
|
||||
- 接送机门禁自身的判定逻辑与开关语义(**只删了响应回显,门禁一步没动**)
|
||||
- 最终方案发布漏斗与 order-v3 的车控状态推进
|
||||
- 数据库:无表结构或数据变更
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||||
- 合并提交 `b4eb919b47`(PR #8608 squash 合并进 `dev-v3`),8 文件 / +119 −55。
|
||||
- `ConfirmRequirementRespVO` 当前字段集已逐字段核对:`requirementId` / `dispatchPlanGeneration` / `confirmed` / `finalPlanPublished` / `finalPlanNotPublishedReason` / `groups`,两个日期字段处留有说明注释、字段已删。
|
||||
- `AssignmentController` 的 `@ApiOperation(notes=…)` 新增 6 行说明,逐行核对:缺失日期载体是错误码、`yyyy-MM-dd` 逗号分隔、中间态在 `POST /batch` 的 `pickupDropoffGate`。
|
||||
- `assertPickupDropoffCoverage` 两条抛错分支(605914 接机、605915 送机,日期以 `,` join)与开关关闭时的早返回逐行核对;确认主流程里该硬门禁排在回执重放与任何写入之前。
|
||||
- `BatchAssignmentWriteRespVO.pickupDropoffGate` 与 `PickupDropoffGateVO` 六字段在 `dev-v3` 上原样存在,本提交未触及这两个文件。
|
||||
- `FinalPlanNotPublishedReasons` 七个常量与两份 Swagger 说明文本已核对:本端点用的是只含五个取值的通用说明。
|
||||
- 回归覆盖:`AssignmentControllerTest` 断言 `$.data.missingPickupDates` / `$.data.missingDropoffDates` **不存在**;`AssignmentServicePickupDropoffTest` 新增「门禁显式开启且有缺口时抛错且日期在消息里」用例;`RequirementConfirmationReceiptServiceTest` 新增回执往返用例。
|
||||
- **部署**:`hl-fleet-service` 的 `dev-v3` 分支已滚到测试服,端点走管理端网关 `/admin/fleet/**` 既有路由,无新增路由。
|
||||
|
||||
```
|
||||
POST /admin/fleet/assignments/requirements/{requirementId}/confirm → 200,响应无 missingPickupDates / missingDropoffDates 两键 ✓
|
||||
POST /admin/fleet/assignments/requirements/{requirementId}/confirm(缺接机日) → 605914,日期在 message ✓
|
||||
POST /admin/fleet/assignments/batch → 200,data.pickupDropoffGate 六字段照旧 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| — | #7067 | 接送机门禁落地:605914/605915 + 最终方案发布漏斗 | ✅ 有效 |
|
||||
| — | #8429 | 未发布原因 `finalPlanNotPublishedReason` 进响应 | ✅ 有效 |
|
||||
| — | #8579 | 给确认响应加 `missingPickupDates` / `missingDropoffDates` | ❌ **已被本单撤销**(在本端点恒为空) |
|
||||
| **本 PR #8608** | **#8603** | 删除上述两个恒空字段,缺口归错误码承载 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8603](https://git.1814.love:8443/wx/HL/issues/8603)
|
||||
- 关联 PR: [wx/HL#8608](https://git.1814.love:8443/wx/HL/pulls/8608)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8603](https://git.1814.love:8443/wx/HL/issues/8603)
|
||||
- **PR**: [#8608](https://git.1814.love:8443/wx/HL/pulls/8608)
|
||||
- **Merge commit**: [b4eb919b47](https://git.1814.love:8443/wx/HL/commit/b4eb919b47)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8613"
|
||||
title: "605072 恢复动作文案订正 + 605002 座位强禁码下线口径澄清(本条目同时覆盖 #8614)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "本条目合并覆盖 #8613 与 #8614(同一 PR #8616 一并修复,两者均为文案订正,未新增/删除/变更任何字段或路径)。#8613:605002「车型座位不足」自 #5810 起已是死码,create/change 均不再做座位强禁校验,全仓(含测试)零产出方;POST /admin/fleet/assignments 的 headcount、strictSeats 两个字段的 Swagger 文案已订正为如实描述——headcount 仅落库记录与下游统计,不再用于任何服务端座位判定;strictSeats 是历史兼容字段,服务端完全忽略,传 true 不会触发 605002,新代码不要依赖它做分支。座位不足的非阻断提示只在 POST /admin/fleet/assignments/precheck 以 warning(type=seats_short)形式给出,precheck 本身零变化。#8614:605072 错误码数值不变(仍是 605072),message 文案从「请先释放资源再处置完成」订正为「请对本单重新执行一次「一键清除已取消派单的占用」后再处置完成」——即撞上 605072 时正确的前端引导是让车务重新调用 POST /admin/fleet/assignments/clear-cancelled-occupancy,而不是原地反复重试 POST /admin/fleet/assignments/resolve-exception;服务端拦回的同时已在独立事务补写一次占用反算意图,按提示重新执行清除占用后再重试 resolve-exception 通常可以收敛。backend_status=deployed:hl-fleet-service 已部署测试服并与本条目所依据的源码提交一致(deploy-status.sh 实测 COMMIT=d57498d38,STATE=ok,2026-09-30 05:34:07 部署);resolve-exception 路由已实测可达(未登录态返 200 信封 code=401)。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务派单:605072 恢复动作文案订正 + 605002 座位强禁码下线口径澄清
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-fleet-service(端口 8087)
|
||||
> **PR**: [#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
|
||||
> **Issue**: [#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[#8614](https://git.1814.love:8443/wx/HL/issues/8614)
|
||||
> **日期**: 2026-09-30
|
||||
> **性质**: 两处均为 Swagger 描述文案 / 错误消息文案订正,**接口路径、方法、请求参数、响应字段结构、错误码数值均零变更**——不触发接口契约模板,本文档按「修复」类轻量格式书写。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
1. **605072(异常派单占用未释放)的错误消息文案变了,正确的恢复动作也变了**:旧文案「请先释放资源再处置完成」不点名具体动作,车务只能反复点「处置完成」(`resolve-exception`)本身,而这在「一车/一司机被多张单的异常行共用、逐单释放」的场景下会卡死——即便实际占用已经释放,缓存态仍可能停在旧读数上。新文案明确指向唯一有效的恢复动作:**重新调用一次「一键清除已取消派单的占用」(`clear-cancelled-occupancy`)**,再重试处置完成。**若前端在 605072 分支里硬编码过旧文案、或只做了「提示后原地重试」的处理,需要改成引导用户重新执行清除占用。**
|
||||
2. **605002(车型座位不足)确认为死码,不会再从 create/change 派单接口抛出**:这不是本次改的行为,而是订正一处此前不准确的文档描述——该码自 #5810 起已无任何产出方。若前端此前在派车弹窗里对 605002 做过专门的错误分支处理,那段代码从未被触发过、以后也不会。座位不足唯一的提示渠道是 `precheck` 预检接口的非阻断 warning(`type=seats_short`),该渠道本身没有变化。
|
||||
|
||||
---
|
||||
|
||||
## 二、涉及接口(非接口契约变更,仅列出文案改动落点,供联调核对)
|
||||
|
||||
| 接口 | 方法 | 路径 | 改动内容 |
|
||||
|------|------|------|----------|
|
||||
| 异常派单处置完成 | POST | `/admin/fleet/assignments/resolve-exception` | 605072 错误消息文案改写(#8614) |
|
||||
| 创建派单 | POST | `/admin/fleet/assignments` | `headcount`/`strictSeats` 两字段 Swagger 描述文案订正(#8613),字段本身未增删未改类型 |
|
||||
|
||||
---
|
||||
|
||||
## 三、逐项说明
|
||||
|
||||
### 1. `POST /admin/fleet/assignments/resolve-exception`(#8614)
|
||||
|
||||
**背景**:该接口把订单下全部 `exception` 状态派单行推进到 `completed`,前置条件是这些行的车辆/司机占用已经释放。占用是否释放,判定依据是车辆/司机的**缓存状态列**(`vehicle_status`/`driver_status`)是否为 `busy`。这个缓存列只在特定写口(如 `clear-cancelled-occupancy`)被触发时才会反算刷新。
|
||||
|
||||
**问题场景**:同一车辆或司机被多张单各自的 `exception` 行共用时,逐单释放会出现死局——释放 A 单时缓存态被判 `busy`(当时正确);随后处置完 A 单,B 单这边再没有任何写口触发反算 ⇒ 缓存态永远停在旧的 `busy`,即便实际已经没有在途占用支撑,B 单调用 `resolve-exception` 也会永远撞 605072。
|
||||
|
||||
**本次改动**:
|
||||
- 错误消息文案(605072 数值不变):
|
||||
|
||||
| | 内容 |
|
||||
|---|---|
|
||||
| 旧 | `异常派单的车辆/司机占用尚未释放,请先释放资源再处置完成` |
|
||||
| 新 | `异常派单的车辆/司机占用尚未释放,请对本单重新执行一次「一键清除已取消派单的占用」后再处置完成` |
|
||||
|
||||
- 服务端在拦回抛出 605072 的同时,已在独立事务里补写一次「按当前在途口径」的占用反算意图(不影响本次请求仍会失败,是为下一次重试铺路)。
|
||||
- Swagger `@ApiOperation` 说明文本同步更新,明确写出 605072 的恢复动作。
|
||||
|
||||
**对前端的影响**:撞上 605072 时,正确引导是提示用户重新调用 `POST /admin/fleet/assignments/clear-cancelled-occupancy`(该接口路径/参数/行为本身未变),再重试 `resolve-exception`;不建议做「原地无限重试 resolve-exception」的兜底逻辑,因为缓存态陈旧这种情形下光重试 `resolve-exception` 本身不会让状态收敛(要靠 `clear-cancelled-occupancy` 触发反算)。若之前的前端文案直接透传了服务端 message 字符串,会自动拿到新文案,无需改代码;若前端针对 605072 有自己的本地化文案覆盖了服务端 message,建议同步这句新的恢复动作提示。
|
||||
|
||||
### 2. `POST /admin/fleet/assignments`(#8613)
|
||||
|
||||
**背景**:该接口的 `CreateAssignmentReqVO` 里有 `headcount`(人数)和 `strictSeats`(座位严格模式)两个历史字段。#5810 起,车型/座位差异已经不再阻断派车(`create`/`change` 均不做座位强禁校验),但这两个字段的 Swagger 描述当时没有同步更新,仍然写着「座位不足判定用」「true=座位不足强禁抛605002」,与实际行为不符。
|
||||
|
||||
**本次改动(仅 `@ApiModelProperty` 描述文案,字段名/类型/是否必填均未变)**:
|
||||
|
||||
| 字段 | 旧描述 | 新描述 |
|
||||
|------|--------|--------|
|
||||
| `headcount` | `人数(座位不足判定用,可空时不判座位)` | `人数(仅落库记录与下游统计;#5810 起 create 不做任何座位校验,车辆座位少于人数也照常派车、不会返回 605002。座位不足的非阻断提示只在 precheck 预检端点以 warning(seats_short) 形式返回,create 侧不产出该提示;本字段可空)` |
|
||||
| `strictSeats` | (Java 层注释,非 Swagger 描述)`座位严格模式:true=座位不足强禁抛 605002 / false=仅 warning 不阻断(默认 false)` | `历史兼容字段,#5810 起服务端完全忽略:座位差异不再阻断派车,传 true 也不会抛 605002。全仓无读取方,仅装配侧恒写 false 以保持 BO 形状;新代码不要依赖本字段做任何分支` |
|
||||
|
||||
**对前端的影响**:
|
||||
- 如果前端此前依赖「create 接口会因座位不足报 605002」做过任何拦截逻辑(例如提交前弹确认框、或捕获 605002 单独处理),这段逻辑**从未生效过**——create/change 从 #5810 起就不做这个校验,以后也不会恢复(605002 码位保留但不会复用给别的语义)。
|
||||
- `strictSeats` 传什么值都不影响服务端行为,前端无需继续维护/传递这个字段的真实语义(可以继续传,服务端只是忽略)。
|
||||
- 座位不足的唯一提示渠道是 `precheck`(`POST /admin/fleet/assignments/precheck`)响应里的 warning 数组,`type=seats_short`——这个渠道本身没有任何变化,仍照旧使用。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- `resolve-exception` 撞 605072 后的正确恢复序列:`POST clear-cancelled-occupancy` → 重试 `POST resolve-exception`。中间不需要额外等待,服务端的补写反算意图是同步在拦回请求的事务外完成的。
|
||||
- `create`(`POST /admin/fleet/assignments`)不会因为座位不足返回任何错误码;如需在提交前给用户座位不足提示,唯一正确渠道是先调用 `precheck` 读取 warning 数组。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- `resolve-exception` 605072 之外的错误码(401 未登录、其它业务校验失败码)均未变化,本次不涉及。
|
||||
- `create` 接口除 Swagger 描述文本外,请求校验、成功路径、其余错误码均未变化。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- `POST /admin/fleet/assignments/precheck` 的请求/响应结构与 `seats_short` warning 的产生条件——零变化。
|
||||
- `POST /admin/fleet/assignments/clear-cancelled-occupancy` 的路径、参数、返回结构——零变化。
|
||||
- 605002、605072 两个错误码的**数值**本身——均未变化(只是 605072 的 message 文案变了,605002 的可触发性说明被订正,数值都没动)。
|
||||
- 除本文档列出的 2 处 `@ApiModelProperty`/错误消息字符串外,`CreateAssignmentReqVO`、`ResolveExceptionReqVO`、响应 VO 均无字段增删或类型变更。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- `deploy-status.sh`(测试服现状表)实测:`hl-fleet-service` COMMIT=`d57498d38`、STATE=ok,2026-09-30 05:34:07 部署——与本条目所依据的源码提交(含 #8613/#8614 合并提交 `2bf98beb491`)一致。
|
||||
- 测试服内网 `curl` 实测 `resolve-exception` 路由已挂载且鉴权前置生效:
|
||||
|
||||
```
|
||||
POST http://127.0.0.1:8080/admin/fleet/assignments/resolve-exception (无 Authorization 头)
|
||||
→ HTTP 200
|
||||
{"code":401,"message":"缺少有效的 Authorization 头","data":null,"traceId":"f8da1e80a5e1400b","success":false}
|
||||
```
|
||||
|
||||
- 源码级核对:全仓 grep `SEATS_NOT_ENOUGH` 仅命中 `AssignmentErrorCode.java` 的定义处一行,`hl-fleet-service` 主代码与测试代码中均无第二处引用,确认 605002 当前零产出方,与文案订正内容一致。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[wx/HL#8614](https://git.1814.love:8443/wx/HL/issues/8614)
|
||||
- 关联 PR: [wx/HL#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8613](https://git.1814.love:8443/wx/HL/issues/8613)、[#8614](https://git.1814.love:8443/wx/HL/issues/8614)
|
||||
- **PR**: [#8616](https://git.1814.love:8443/wx/HL/pulls/8616)
|
||||
- **Merge commit**: [`2bf98beb491`](https://git.1814.love:8443/wx/HL/commit/2bf98beb49159de09087522d12b532cca720d5db)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,314 @@
|
||||
---
|
||||
schema: hl-changelog/v2
|
||||
ticket: "8615"
|
||||
title: "退团转房:作废原因新增三个系统取值,向酒店取消追加团期栅栏与联动缩减"
|
||||
consumer: admin
|
||||
author: wx(GIT)
|
||||
change_type: 修改接口
|
||||
backend_status: deployed
|
||||
gateway_status: not_required
|
||||
frontend_status: pending
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-09-30"
|
||||
base: dev-v3
|
||||
---
|
||||
|
||||
# 退团转房:作废原因新增三个系统取值,向酒店取消追加团期栅栏与联动缩减
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 退团转房池列表(#8491 I-17)的 `records[].cancelReason` 由原来只有一个取值 `HOTEL_CANCELLED`,扩展为**四个**取值,新增 `GROUP_REALLOCATED` / `GROUP_PLAN_REMOVED` / `GROUP_DISBANDED` 三个由团期写口在同一事务内系统作废时写入的取值;这三种情况下 `handlerName` 恒为 `null`(无人工处理人),`handledAt` 仍会写入系统作废发生的时刻。前端按 `cancelReason` 分支展示原因文案的地方需要扩展这三个分支,否则会落到未知分支的兜底展示。
|
||||
- 退团房向酒店取消(#8491 I-21,`POST .../room-transfers/{id}/cancel-hotel`)新增两个错误码:**团期来源行**在源团期已不在「资源准备中」阶段时返回 `808323`(此前 I-21 对团期来源行不做团期状态校验,任何状态都能直接取消,改动后与转房接口 I-20 同口径);团期来源行的源计划空余不足以覆盖本行待转间数时返回 `808324`。这两个码此前只出现在 I-20 的错误表里,I-21 的错误表新增了它们。
|
||||
- 退团房向酒店取消**成功后**,若该行是团期来源行,会在同一事务内按取消的间数**同步缩减**源团期计划行的 `roomCount`(联动 `shrinkForTransfer`);这一步不改变本接口的响应结构,只影响该房间此后是否还会被团期自动分配算进可用余量。
|
||||
- 转房接口(#8491 I-20)不在本次变更接口清单内——其错误表已发布过 `808324`,本次只是把此前一个应报 `808324`(源计划空余不足)却会先报到 `808932`(并发冲突)的边界情况改为直接报 `808324`,错误码本身与文案都没有新增,属于既有契约内的分支修正,前端已有的 `808324`/`808932` 处理分支不需要改动。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 退团转房池分页 | GET | `/v3/admin/order/house-console/room-transfers` | 响应字段取值扩展 | `cancelReason` 新增三个系统作废取值,系统作废行 `handlerName` 为空 |
|
||||
| 2 | 退团房向酒店取消 | POST | `/v3/admin/order/house-console/room-transfers/{id}/cancel-hotel` | 新增错误码 + 联动行为 | 团期来源行新增 808323/808324 校验;取消成功后同步缩减源团期计划间数 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 退团转房池分页 `GET /v3/admin/order/house-console/room-transfers`
|
||||
|
||||
**VO**: `HouseRoomTransferPageReqVO → HouseRoomTransferPageRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务控制台「退团房」页签展示当前待处理 / 已转出 / 已取消的转房行列表。本次改动只涉及列表行里 `cancelReason` 取值集合的扩展,请求参数与响应结构均未变化。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| page | Query | Integer | ❌ | 默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 每页条数 |
|
||||
| status | Query | String | ❌ | PENDING / TRANSFERRED / CANCELLED,默认 PENDING | 状态筛选 |
|
||||
| risk | Query | String | ❌ | OVERDUE / NEAR / NO_DEADLINE / NORMAL | 风险筛选,仅对 PENDING 行有意义 |
|
||||
| cityCode | Query | String | ❌ | ≤64 | 城市中文名 |
|
||||
| keyword | Query | String | ❌ | ≤64 | 团号或酒店名关键词 |
|
||||
| stayDateFrom | Query | LocalDate | ❌ | yyyy-MM-dd | 入住晚起(含) |
|
||||
| stayDateTo | Query | LocalDate | ❌ | yyyy-MM-dd | 入住晚止(含) |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | List<HouseRoomTransferRespVO> | 当前页转房行 |
|
||||
| records[].id | Long(String) | 转房行 ID |
|
||||
| records[].sourceType | String | 来源类型 ORDER / GROUP_BATCH |
|
||||
| records[].sourceGroupBatchId / sourceBatchNo | Long(String) / String | 团期来源行的源团期 ID 与批次号 |
|
||||
| records[].stayDate | LocalDate | 入住晚 |
|
||||
| records[].hotelName / roomTypeName | String | 酒店名 / 房型名 |
|
||||
| records[].roomCount | Integer | 原始间数 |
|
||||
| records[].remainingCount | Integer | 剩余待处理间数;部分被系统收敛时会减少,行仍保持 PENDING |
|
||||
| records[].status / statusLabel | String | PENDING / TRANSFERRED / CANCELLED 及中文 |
|
||||
| records[].cancelReason | 【改动】String | 仅 CANCELLED 行有值。`HOTEL_CANCELLED`=房务人工向酒店取消(不变);新增 `GROUP_REALLOCATED`=本团已再分配、`GROUP_PLAN_REMOVED`=本团已撤销该晚计划、`GROUP_DISBANDED`=团期已解散,三者都是团期写口系统作废,见「六.5」 |
|
||||
| records[].handlerName | 【改动】String | 处理人姓名;`cancelReason` 为三个新增取值之一时恒为 `null`(无人工处理人) |
|
||||
| records[].handledAt | LocalDateTime | 处理时间;系统作废时同样写入作废发生的时刻,不为 null |
|
||||
| records[].readOnly / readOnlyReason | Boolean / String | 对当前登录人是否只读及理由 |
|
||||
| total / page / pageSize | long / int / int | 分页信息 |
|
||||
| summary.pendingRooms 等 4 项 | int | 全部 PENDING 行的风险汇总,恒不受本次筛选条件影响 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/house-console/room-transfers?status=CANCELLED&cityCode=海拉尔&page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "9100234", "sourceType": "GROUP_BATCH", "sourceOrderId": null, "teamNo": null,
|
||||
"sourceTeamNo": "HLT-20261012-003", "sourceGroupBatchId": "500321", "sourceBatchNo": "HLT-20261012-003",
|
||||
"stayDate": "2026-10-12", "cityName": "海拉尔", "hotelId": "60088", "hotelName": "海拉尔国际大酒店",
|
||||
"roomTypeId": "70012", "roomTypeName": "高级大床房", "roomCount": 2, "remainingCount": 2,
|
||||
"status": "CANCELLED", "statusLabel": "已取消", "cancelDays": 3, "cancelCutoff": "18:00", "remindDays": 1,
|
||||
"deadlineAt": "2026-10-09T18:00:00", "risk": "NORMAL", "riskLabel": "正常",
|
||||
"targetType": null, "targetOrderId": null, "targetTeamNo": null, "targetRequirementId": null,
|
||||
"targetGroupBatchId": null, "targetBatchNo": null, "hotelConfirmNo": null, "proofFileIds": [],
|
||||
"cancelFee": null, "cancelReason": "GROUP_REALLOCATED", "handlerName": null,
|
||||
"handledAt": "2026-09-30T10:02:11", "remark": null, "readOnly": false, "readOnlyReason": null
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"summary": { "pendingRooms": 6, "overdueRooms": 0, "nearRooms": 1, "noDeadlineRooms": 0 }
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "records": [], "total": 0, "page": 1, "pageSize": 20, "summary": { "pendingRooms": 0, "overdueRooms": 0, "nearRooms": 0, "noDeadlineRooms": 0 } }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "status 取值非法",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | status 取值非法 / risk 取值非法 | 入参校验,未变 |
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色,未变 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 团期写口(分房重算、加入团期自动分房、改计划间数、删计划、团期解散)触发系统收敛时:某计划行下全部 PENDING 转房行都被覆盖,则行整体变为 CANCELLED 并写入对应 `cancelReason`;只覆盖了一部分,则行仍是 PENDING,仅 `remainingCount` 减少,`cancelReason`/`handlerName`/`handledAt` 均不变。
|
||||
- 系统作废(`cancelReason` 为 `GROUP_REALLOCATED`/`GROUP_PLAN_REMOVED`/`GROUP_DISBANDED`)的行没有人工处理人:`handlerName` 为 `null`;触发它的管理员记在操作日志里,不在本接口暴露。
|
||||
- `summary` 恒统计全部 PENDING 行,不受本次筛选条件影响。
|
||||
|
||||
### 2. 退团房向酒店取消 `POST /v3/admin/order/house-console/room-transfers/{id}/cancel-hotel`
|
||||
|
||||
**VO**: `HouseRoomTransferCancelReqVO → HouseRoomTransferRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
退团房没有合适的转入对象时,房务直接向酒店办理取消,上传凭证并可录入取消费用。本次改动只影响**团期来源行**(`sourceType=GROUP_BATCH`):新增源团期状态与源计划余量两道校验,取消成功后联动缩减源团期计划间数;常规单来源行(`sourceType=ORDER`)的路径与校验顺序不变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 转房行 ID |
|
||||
| proofFileIds | Body | List<Long> | ✅ | 1~9 个,元素非空 | 取消凭证文件 ID |
|
||||
| cancelFee | Body | BigDecimal | ❌ | ≥0,最多 2 位小数 | 取消费用(元) |
|
||||
| remark | Body | String | ❌ | ≤200 字 | 备注 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (整行) | HouseRoomTransferRespVO | 取消后的该行,字段同接口 1 的 `records[]` |
|
||||
| status / statusLabel | String | CANCELLED / 已取消 |
|
||||
| cancelReason | String | 恒为 `HOTEL_CANCELLED`;本接口触发的取消不会写入三个系统作废取值,那三个只由团期写口的系统收敛写入 |
|
||||
| hotelConfirmNo | String | 本接口不接收确认号,恒为 `null`(未变) |
|
||||
| cancelFee / proofFileIds | BigDecimal(String) / List<String> | 录入的取消费与凭证 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"proofFileIds": [88031, 88032],
|
||||
"cancelFee": 0,
|
||||
"remark": "酒店已免费取消"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "9100235", "sourceType": "GROUP_BATCH", "sourceOrderId": null, "teamNo": null,
|
||||
"sourceTeamNo": "HLT-20261012-004", "sourceGroupBatchId": "500322", "sourceBatchNo": "HLT-20261012-004",
|
||||
"stayDate": "2026-10-15", "cityName": "满洲里", "hotelId": "60090", "hotelName": "满洲里丽景大酒店",
|
||||
"roomTypeId": "70020", "roomTypeName": "行政大床房", "roomCount": 3, "remainingCount": 3,
|
||||
"status": "CANCELLED", "statusLabel": "已取消", "risk": null, "riskLabel": null,
|
||||
"hotelConfirmNo": null, "proofFileIds": ["88031", "88032"], "cancelFee": "0.00",
|
||||
"cancelReason": "HOTEL_CANCELLED", "handlerName": "王芳", "handledAt": "2026-09-30T10:15:32",
|
||||
"remark": "酒店已免费取消", "readOnly": false, "readOnlyReason": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口无列表数据;任何失败都返回非 200 的 `code`,该行状态不变。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808324,
|
||||
"message": "转出间数超过剩余 1 间",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | 凭证最多 9 个 / 凭证文件 ID 不能为空 / 取消费用不能为负 / 取消费用最多 2 位小数 / 备注不能超过 200 字 | 入参校验,未变 |
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色,未变 |
|
||||
| 808320 | 转房记录不存在 | id 不存在(未变;锁前锁后各判一次,同一个码) |
|
||||
| 808326 | 只有原单处理人可以处理退团房间 | 非源单持有人且非超管,未变 |
|
||||
| 808325 | 请填写酒店确认号并上传凭证 | 未上传凭证(本接口不要求确认号,文案沿用同一错误码),未变 |
|
||||
| 808323 | 目标团期已确认,不能转入 | 【新增,仅团期来源行】源团期已不在「资源准备中」阶段。文案沿用 I-20 已发布的措辞,这里没有「目标」,实际含义是「源团期已不可再改动,不能取消这一行」 |
|
||||
| 808321 | 该房间已处理 | 行已不是 PENDING,未变 |
|
||||
| 808932 | 房务状态已被并发修改,请刷新后重试 | 【团期来源行新增触发场景】源团期或源计划行在读取时已不存在(并发被删/改);以及原有的 CAS 并发冲突 |
|
||||
| 808324 | 转出间数超过剩余 {0} 间 | 【新增,仅团期来源行】源计划行已分配间数 > `roomCount − 本行剩余间数`,即空余不足以覆盖本行待取消间数;`{0}` = `max(roomCount − 已分配间数, 0)` |
|
||||
| 100502 | 取消处理中,请勿重复提交 | 3 秒内重复提交,未变 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只有源单持有人或超管可操作;凭证为空返回 808325(业务码,不是 400)。
|
||||
- 校验顺序(团期来源行):808320(不锁定读)→ 808326 → 808325 → 808323(源团期栅栏)→ 808320(锁定读复验,同一个码)→ 808321 → 808932(源团期/源计划缺失)→ 808324(源计划空余不足)→ 808932(CAS 并发冲突)。常规单来源行没有 808323/808324 两步,其余顺序不变。
|
||||
- 团期来源行取消成功后,源团期计划行的 `roomCount` 会在同一事务内按取消间数同步缩减;这一步对本接口响应体不可见,影响的是该房间此后是否还计入团期自动分配的可用余量。
|
||||
- 成功后该行整行变为 CANCELLED,不再出现在待处理汇总里;已部分转出的行取消的是剩余部分。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 接口 2 的团期来源行取消前,前端应先确认该行所属团期仍处于允许改动的阶段;若已收到 808323,不应重试,应提示房务该团期已不可再取消这一行。
|
||||
- 接口 2 收到 808324 时,`message` 里的数字是此刻可取消的上限间数(可能为 0),不代表本行 `remainingCount`;不要直接拿 `remainingCount` 去重试提交。
|
||||
- 接口 1 展示已取消行的原因文案时必须按 `cancelReason` 四个取值分支处理,不要假设该字段只有一个可能值;系统作废三种取值下 `handlerName` 为 `null` 是正常状态,不是数据缺失。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 场景 | 团期计划行 `room_count` | 转房行 `status` / `remaining_count` |
|
||||
|------|------|------|
|
||||
| 常规单来源行(ORDER)取消 | 不涉及团期计划 | CANCELLED,其余字段照旧写入 |
|
||||
| 团期来源行取消,源计划空余充足 | 按本次取消间数同步减少(`shrinkForTransfer`) | CANCELLED |
|
||||
| 团期来源行取消,源计划空余不足(命中 808324) | 不变 | 不变,整体事务回滚 |
|
||||
| 团期写口触发系统收敛(分房重算 / 自动分房 / 改计划间数 / 删计划 / 团期解散) | 视触发写口而定 | 全部覆盖:CANCELLED + 对应 `cancel_reason`;部分覆盖:仍 PENDING,`remaining_count` 减少 |
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 转房行不存在或已被删除 → 808320(接口 2,未变)。
|
||||
- 团期整团解散时,其下全部 PENDING 转房行在同一事务内作废(`cancelReason=GROUP_DISBANDED`),此后对这些行调用接口 2 会先命中 808321(已非 PENDING)。
|
||||
- 源团期推进出「资源准备中」阶段后,其团期来源的 PENDING 行调用接口 2 会命中 808323;同一场景下调用 I-20(转房)此前已经是 808323,两个接口现在口径一致。
|
||||
- 本单上线前已存在、且所属计划行已被删除但转房行仍是 PENDING 的存量脏数据,不会被本次新增的系统收敛机制回溯处理,仍会出现在接口 1 的列表里;对这类行调用接口 2,源团期已不在「资源准备中」时返回 808323,否则因源计划不存在返回 808932。
|
||||
|
||||
## 六.5、枚举
|
||||
|
||||
**cancelReason**(`house_room_transfer.cancel_reason`,仅 `status=CANCELLED` 时有值)
|
||||
|
||||
| 取值 | 中文 | 说明 | 是否本次新增 |
|
||||
|------|------|------|------|
|
||||
| HOTEL_CANCELLED | 房务人工向酒店取消 | 房务通过接口 2 主动办理 | 否 |
|
||||
| GROUP_REALLOCATED | 本团已再分配 | 计划行还在,但空余已不足以覆盖待转间数——本团别的户分到了这批房,或计划间数被调少 | 是 |
|
||||
| GROUP_PLAN_REMOVED | 本团已撤销该晚计划 | 待转房挂的计划行已不在活跃计划里:被删除、被替换成不同房型/酒店的新行,或缩减到 0 被整行删除 | 是 |
|
||||
| GROUP_DISBANDED | 团期已解散 | 流团/解散时该团全部待转房一并作废 | 是 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
**字段取值**
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 接口 1 `records[].cancelReason` | 仅 `HOTEL_CANCELLED` 一个取值 | 新增 `GROUP_REALLOCATED` / `GROUP_PLAN_REMOVED` / `GROUP_DISBANDED` 三个取值 |
|
||||
| 接口 2 错误码集合 | 808320 / 808321 / 808325 / 808326 / 808932(另有 400 / 808090 / 100502) | 团期来源行新增 808323、808324 |
|
||||
|
||||
**行为**
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 本团某户分到已释放的团期房 | 原 PENDING 转房行照常留在池里,房务仍可在接口 2 / I-20 继续处理,存在一房两卖风险 | 团期写口在同一事务内收敛该计划行下的 PENDING 转房行,全部覆盖则整行作废并写入对应 `cancelReason`,部分覆盖则 `remaining_count` 减少 |
|
||||
| 接口 2 对团期来源行的源团期状态 | 不做校验,源团期任意状态都能直接取消 | 源团期不在「资源准备中」时返回 808323 |
|
||||
| 接口 2 取消团期来源行后源计划间数 | 不变 | 按取消间数同步缩减 |
|
||||
| 接口 2 团期来源行空余不足 | 无此校验 | 返回 808324,行与源计划均不写入 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- 是否破坏向后兼容:否。`cancelReason` 是新增取值,不是重命名或语义变更;两个错误码是接口 2 的新增分支,不是复用/覆盖已有码的语义。
|
||||
- 前端是否必须同步上线:是。不同步的话,系统作废行在列表上会落入未处理的 `cancelReason` 分支;团期来源行取消撞上 808323/808324 时若没有对应分支,会呈现为未识别错误。
|
||||
- 前端 workaround 清理点:若此前把「`cancelReason` 非 `HOTEL_CANCELLED`」当异常兜底处理,需要改为按四个取值分别展示;否则无需清理,只需新增分支。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 常规单来源行(`sourceType=ORDER`)在接口 2 的校验顺序、错误码与响应结构均未变化。
|
||||
- 接口 1 的入参、分页结构、`summary` 汇总口径未变化。
|
||||
- 转房接口 I-20 的请求/响应结构、错误码集合未新增,本单不改变其契约,仅修正一处原本应报 808324 却先报到 808932 的边界分支,不在本次变更接口清单内。
|
||||
- 房务分房重建(H11)等团期写口自身的请求/响应契约未变化,只是这些写口在检测到需要收敛的 PENDING 转房行时会按本单的规则写入新的 `cancelReason`。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试环境已验证(部署提交 405fc8db0,验证时刻 2026-09-30 10:22~10:41):
|
||||
1. 转出间数超过计划剩余:`POST /v3/admin/order/house-console/room-transfers/{id}/cancel-hotel`,body code=808324("转出间数超过剩余 0 间"),转房行与计划行均无变化。
|
||||
2. 团内户取消离团后另一户经重算分到房:`POST /v3/admin/order/{id}/cancel/pre-trip` + `POST /v3/admin/house/group-batches/{id}/allocations/rebuild`,转房行由 PENDING 变为 CANCELLED,`cancelReason=GROUP_REALLOCATED`,处理人为空,有对应操作日志。
|
||||
3. 计划仍有空余时人工办理转出:`POST .../cancel-hotel`,body code=200,转房行变 CANCELLED/HOTEL_CANCELLED,计划 roomCount 由 2 减为 0 并软删(预期)。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #8615
|
||||
- 参考发布契约:`changelogs-v2/2026-09/30_8491_房务控制台接口-新增接口-管理后台.md`(I-17 / I-20 / I-21 的既有字段与错误码定义)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 关联工单:#8615
|
||||
- 关联历史 changelog:#8491
|
||||
@@ -0,0 +1,405 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8619"
|
||||
title: "团期子订单列表:只报接送机的户不再判未提交,新增逐类用车需求清单字段"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "bc9c13aaa39bab96e132a3a42515c821fa74a269"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "PR #8624 已 squash 合并 dev-v3(ce7cd238e9355bdc17c45f90ace02827c7db60f5),hl-order-service-v3 dev-v3 分支已滚测试服。GET /v3/admin/order/group-batch/{groupBatchId}/orders 对真实团期 2104839654727618562 实测:同户 TRAVEL+TRANSFER 两类需求状态互不覆盖、vehicleRequirements[] 按 TRAVEL 在前 TRANSFER 在后下发,589500 团期不存在错误码实测通过。所有既有字段未删改,只放宽了车需求那组字段的取值范围并新增 vehicleRequirements。;前端已交付:报名清单车需求列改逐类清单(vehicleRequirements[] 非空逐类各显一行,空数组/旧响应回落单值字段),「未提交」误报修复直显自动生效,9 例定向测试全绿(hl-admin bc9c13aa)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
> **服务**: hl-order-service-v3 (端口 8086)
|
||||
> **PR**: #8624
|
||||
> **Issue**: #8619
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台团期详情页「子订单列表」/ A3 接口消费方
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **改前**:`GET /v3/admin/order/group-batch/{groupBatchId}/orders` 的车需求四个单值字段(`vehicleRequirementStatus`/`StatusName`/`Kind`/`KindName`)只从该户 **TRAVEL(行程用车)** 类需求行取值。若一个户只提交了 **TRANSFER(接送机)** 需求、没有 TRAVEL 需求,这四个字段恒被渲染成「未提交」,即使接送机需求已经在流转甚至已完成。
|
||||
- **改后**:改为按展示序(TRAVEL 优先于 TRANSFER)取该户**当前活跃需求行中的首条**,只提交接送机的户会如实报出接送机自己的状态,不再被误判未提交。
|
||||
- **新增字段 `vehicleRequirements`**:`GroupBatchOrderItemRespVO` 新增该数组字段,逐类列出该户全部活跃车需求行(TRAVEL 在前、TRANSFER 在后),每类各自独立的 `kind`/`kindName`/`status`/`statusName`,不再只能看到"展示序首条"这一个值。该户没有任何活跃车需求行时数组是 `[]`(空数组),不是 `null`。
|
||||
- **`vehicleRequirementKind` 不再恒为 `"TRAVEL"`**:只提交接送机的户,该字段与 `vehicleRequirementKindName` 现在会如实报出 `"TRANSFER"`/`"接送机"`。前端如果曾经硬编码假设这两个字段只会是 TRAVEL/行程用车,需要一并放开。
|
||||
- 其余约 25 个既有字段(订单状态、支付状态、酒店需求、出行人等)取值逻辑未变。
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
本单与已发布的 `changelogs-v2/2026-09/30_8577_...-修改接口-管理后台.md`(#8577)修的是**同一症状家族**(只提交接送机被误判"未提交"),但**改动的是完全不同的接口/代码路径**,请勿混淆:
|
||||
|
||||
| | #8577 | #8619(本单) |
|
||||
|---|---|---|
|
||||
| 涉及接口 | `PUT` 保存车需求、`GET` 聚合草稿、`GET` 提交前校验(均在 `GroupVehicleRequirementService`) | `GET /v3/admin/order/group-batch/{groupBatchId}/orders`(团期子订单列表,`GroupBatchQueryService`/`GroupBatchConverter`) |
|
||||
| 涉及错误码 | 809121/809122/809123 | 不涉及新增/变更错误码,沿用既有 589500 |
|
||||
| 根因层 | 提交/校验链路 | 列表查询的取值范围(原只查 TRAVEL 类活跃行) |
|
||||
|
||||
两单互不覆盖,`#8577` 的改动对本接口没有影响;本接口过去存在的误判问题,`#8577` 也没有修到。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期下子订单列表(A3) | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | 响应字段语义收窄 + 新增字段 | `vehicleRequirementKind` 不再恒为 TRAVEL;新增 `vehicleRequirements[]` 逐类需求清单 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期下子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
|
||||
|
||||
**VO**: `GroupBatchOrderItemRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台团期详情页展示该团期下全部子订单(一个订单=一个"户")的汇总信息,含车需求配车状态一栏。前端据此渲染列表行的车需求状态标签,并可能据 `vehicleRequirementKind` 决定展示"行程用车"或"接送机"图标。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | path | Long | 是 | 团期需存在 | 团期ID |
|
||||
| page | query | Integer | 否 | 缺省 1,<1 归一为 1 | 页码,从 1 起 |
|
||||
| pageSize | query | Integer | 否 | 缺省 20,<1 归一为 20,>200 截断为 200 | 每页条数 |
|
||||
| includeTravelers | query | Boolean | 否 | 缺省 true | 是否附出行人明细(证件号/手机号一律不返回) |
|
||||
| includeNeeds | query | Boolean | 否 | 缺省 true | 是否附 roomCount/roomType/specialNeeds |
|
||||
| includeCancelled | query | Boolean | 否 | 缺省 false | 是否含已取消子订单(缺省只返活跃集) |
|
||||
|
||||
#### 出参 `Result<PageResult<GroupBatchOrderItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| orderId | String | 订单ID(Long 序列化为字符串) |
|
||||
| orderNo | String | 订单号 |
|
||||
| teamNo | String | 团号;订金支付成功后生成,未付订金为 null |
|
||||
| customerName | String | 客户姓名 |
|
||||
| participantCount | Integer | 出行人数 |
|
||||
| orderStatus / orderStatusName | String / String | 订单状态/名称 |
|
||||
| flowStatus / flowStatusName | String / String | 流程状态/名称(12 值枚举) |
|
||||
| reviewStatus / reviewStatusName | String / String | 复核状态/名称;⚠️ 与团期核单 `GroupSettlementRespVO` 同名字段含义相反 |
|
||||
| settlementStatus / settlementStatusName | String / String | 结算状态/名称;⚠️ 同样与团期核单含义相反,也不是财务 tab 的 settleStatus |
|
||||
| payStatus / payStatusName | String / String | 支付状态/名称 |
|
||||
| contractStatus / contractStatusName | String / String | 合同状态/名称 |
|
||||
| insuranceStatus / insuranceStatusName | String / String | 投保状态/名称 |
|
||||
| paidAmount / balanceAmount | String / String | 已付金额/待付余额(BigDecimal 序列化为字符串) |
|
||||
| hotelRequirementStatus / hotelRequirementStatusName | String / String | 酒店需求状态/名称;#8249 起无活跃行时为 null,不回落 PENDING |
|
||||
| **vehicleRequirementStatus** | String | 车需求状态;**本单起**取该户展示序(TRAVEL 优先)首条活跃需求行的状态,不再恒来自 TRAVEL |
|
||||
| **vehicleRequirementStatusName** | String | 车需求状态名;PENDING_REVIEW 按 kind 分叉:TRAVEL="待提交车务",TRANSFER="待审核";"未提交"现在只在该户两类需求行都不存在时才出现 |
|
||||
| **vehicleRequirementKind** | String | 车需求类别;**本单起不再恒为 "TRAVEL"**,只提交接送机的户会报 "TRANSFER" |
|
||||
| **vehicleRequirementKindName** | String | 车需求类别名;未知类别给 null,不回落编码 |
|
||||
| **vehicleRequirements** | Array<VehicleRequirementItem> | **本单新增**。该户全部活跃车需求行,TRAVEL 在前、TRANSFER 在后;一条都没有时为 `[]`(非 null) |
|
||||
| consultantName | String | 顾问姓名 |
|
||||
| totalPrice | String | 订单总价 |
|
||||
| tierCode / tierName | String / String | 价格档位编码/名称 |
|
||||
| travelerInfoComplete | Boolean | 出行人信息是否完整 |
|
||||
| roomCount | Integer | 房间数;`includeNeeds=true` 时返回 |
|
||||
| roomType / roomTypeName | String / String | 房型编码/名称;`includeNeeds=true` 时返回 |
|
||||
| specialNeeds | String | 特殊需求;`includeNeeds=true` 时返回 |
|
||||
| contactPhone | String | 联系电话(脱敏,如 `138****3046`) |
|
||||
| groupChatUnreadCount | Integer | 群聊未读数;user-service 不可达/Feign 超时/未登录时降级为 0 |
|
||||
| travelers | Array<TravelerItemVO> | 出行人明细;`includeTravelers=true` 时返回 |
|
||||
|
||||
`VehicleRequirementItem`(`vehicleRequirements` 数组元素):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| kind | String | 需求类别,TRAVEL / TRANSFER |
|
||||
| kindName | String | 类别名,"行程用车" / "接送机" |
|
||||
| status | String | 该类需求自己的状态(见六.5 枚举) |
|
||||
| statusName | String | 状态名(PENDING_REVIEW 按 kind 分叉,见上) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=5&includeTravelers=false&includeNeeds=false
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
测试服真实返回(团期 `2104839654727618562`,3 个子订单,覆盖"两类需求并存且状态不同""仅 TRAVEL"两种场景):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"orderId": "2104839654652121090",
|
||||
"orderNo": "HL20260929154414207",
|
||||
"teamNo": "26-2313",
|
||||
"customerName": "李文博",
|
||||
"participantCount": 2,
|
||||
"orderStatus": "CUSTOMIZING",
|
||||
"orderStatusName": "定制中",
|
||||
"flowStatus": "RESOURCE_PREPARING",
|
||||
"flowStatusName": "资源准备",
|
||||
"reviewStatus": null,
|
||||
"reviewStatusName": null,
|
||||
"settlementStatus": "NONE",
|
||||
"settlementStatusName": "未结算",
|
||||
"payStatus": "FULLY_PAID",
|
||||
"payStatusName": "已付全款",
|
||||
"contractStatus": null,
|
||||
"contractStatusName": null,
|
||||
"insuranceStatus": null,
|
||||
"insuranceStatusName": null,
|
||||
"paidAmount": "7360.00",
|
||||
"balanceAmount": "0.00",
|
||||
"hotelRequirementStatus": null,
|
||||
"hotelRequirementStatusName": null,
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
|
||||
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING", "statusName": "待车队配"}
|
||||
],
|
||||
"consultantName": "cw_test_7443",
|
||||
"totalPrice": "7360.00",
|
||||
"tierCode": "2A",
|
||||
"tierName": "2成人",
|
||||
"travelerInfoComplete": true,
|
||||
"roomCount": null,
|
||||
"roomType": null,
|
||||
"roomTypeName": null,
|
||||
"specialNeeds": null,
|
||||
"contactPhone": "138****3046",
|
||||
"groupChatUnreadCount": 0,
|
||||
"travelers": null
|
||||
},
|
||||
{
|
||||
"orderId": "2104839686486888449",
|
||||
"orderNo": "HL20260929154421828",
|
||||
"teamNo": "26-9436",
|
||||
"customerName": "张丽娟",
|
||||
"participantCount": 2,
|
||||
"orderStatus": "CUSTOMIZING",
|
||||
"orderStatusName": "定制中",
|
||||
"flowStatus": "PENDING_CONFIRM",
|
||||
"flowStatusName": "待确认",
|
||||
"payStatus": "FULLY_PAID",
|
||||
"payStatusName": "已付全款",
|
||||
"paidAmount": "7360.00",
|
||||
"balanceAmount": "0.00",
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"}
|
||||
],
|
||||
"totalPrice": "7360.00",
|
||||
"tierCode": "2A",
|
||||
"tierName": "2成人",
|
||||
"contactPhone": "138****3047",
|
||||
"groupChatUnreadCount": 0
|
||||
},
|
||||
{
|
||||
"orderId": "2104839729176514562",
|
||||
"orderNo": "HL20260929154432061",
|
||||
"teamNo": "26-6559",
|
||||
"customerName": "那顺",
|
||||
"participantCount": 3,
|
||||
"orderStatus": "CUSTOMIZING",
|
||||
"orderStatusName": "定制中",
|
||||
"flowStatus": "PENDING_CONFIRM",
|
||||
"flowStatusName": "待确认",
|
||||
"payStatus": "FULLY_PAID",
|
||||
"payStatusName": "已付全款",
|
||||
"paidAmount": "11620.00",
|
||||
"balanceAmount": "0.00",
|
||||
"vehicleRequirementStatus": "DONE",
|
||||
"vehicleRequirementStatusName": "配车完成",
|
||||
"vehicleRequirementKind": "TRAVEL",
|
||||
"vehicleRequirementKindName": "行程用车",
|
||||
"vehicleRequirements": [
|
||||
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
|
||||
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING_REVIEW", "statusName": "待审核"}
|
||||
],
|
||||
"totalPrice": "11620.00",
|
||||
"tierCode": "3A",
|
||||
"tierName": "3成人",
|
||||
"contactPhone": "138****3048",
|
||||
"groupChatUnreadCount": 0
|
||||
}
|
||||
],
|
||||
"total": 3,
|
||||
"page": 1,
|
||||
"pageSize": 5
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
以上三条均取自 TRAVEL+TRANSFER 两类需求并存的户,展示了两类需求各自独立取值(第三条 TRANSFER 处于 `PENDING_REVIEW` 显示为"待审核",TRAVEL 处于 `DONE` 不受影响)。**只提交接送机、完全没有 TRAVEL 需求**的户是本次修复要解决的核心场景,测试服当前团期数据中暂无这类现成样本(该形态的订单目前都不挂团期),该场景由自动化回归覆盖,见"八、测试环境已验证"。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
分页越界的真实返回(`page=999` 超出总页数):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 3,
|
||||
"page": 999,
|
||||
"pageSize": 5
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
`groupChatUnreadCount` 在 user-service 不可达、Feign 调用超时或当前登录态失效时降级返回 `0`,不抛错、不影响本接口其余字段。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
团期不存在的真实返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589500,
|
||||
"message": "团期不存在",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
无操作权限时返回错误码 `589507`("无操作权限(当前角色未授予团期权限,或该团期不在您名下)",源自 `GroupBatchErrorCode`,本轮测试账号为 admin 全权角色未触发,未做活体验证)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `groupBatchId` 对应团期不存在返回 589500,`data` 为 `null`。
|
||||
- `pageSize` 超过 200 会被后端静默截断为 200,不报错。
|
||||
- `includeCancelled` 缺省 `false`,不传时列表不含已取消子订单。
|
||||
- `vehicleRequirements` 为空数组 `[]` 表示该户当前没有任何活跃车需求行,不是接口异常;不要用 `null` 判空。
|
||||
- `vehicleRequirementKind`/`KindName` 在该户尚无任何车需求行时为 `null`,不回落成某个默认编码。
|
||||
- 两类需求同时存在时,单值字段(`vehicleRequirementStatus`/`Kind` 等)取的是**展示序(TRAVEL 优先)首条**,并非"最近更新"或"按查询顺序";需要拿到每一类各自的真实状态必须读 `vehicleRequirements[]`,不能只读单值字段。
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
- ✅ 正确:判断某个户是否已提交任意车需求,遍历 `vehicleRequirements`(长度 > 0 即已提交),或分别读取 `vehicleRequirements` 中 `kind=TRAVEL`/`kind=TRANSFER` 各自的 `status`。
|
||||
- ❌ 错误:继续假设 `vehicleRequirementKind` 恒为 `"TRAVEL"` 并据此做条件分支——只提交接送机的户会被分支判空/判错。
|
||||
- ❌ 错误:把 `vehicleRequirementStatusName === "未提交"` 当作"该户任意一类车需求都未提交"的充分条件——本单之后它只代表"两类都未提交",不能再用它反推"TRAVEL 未提交"或"TRANSFER 未提交"这类更细的判断,要细分请读 `vehicleRequirements[]`。
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
无需前端触发任何状态切换动作;本次是纯读接口的响应字段语义调整,不涉及写操作。
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
本接口是纯查询接口,本单未新增/变更任何表结构,也未变更任何写路径。改动仅收窄/放宽了查询车需求行时的过滤条件(原实现只按 `requirement_kind = 'TRAVEL'` 取活跃行,现改为按展示序取该户全部活跃行的首条,并额外把全部活跃行一并下发到 `vehicleRequirements`)。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 团期不存在 → 589500,`data: null`。
|
||||
- 当前登录角色对该团期无权限 → 589507(源码定义,未做活体验证)。
|
||||
- `page`/`pageSize` 非法值(<1 或超范围)由后端静默归一/截断,不报参数校验错误。
|
||||
- 分页越界 → 返回 `records: []`,`total` 仍是真实总数,不报错。
|
||||
- `hotelRequirementStatus`/`vehicleRequirementKind` 等状态类字段在对应需求不存在时给 `null`,均不回落到某个默认状态码。
|
||||
- `groupChatUnreadCount` 在下游不可达时静默降级为 `0`。
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### vehicleRequirementKind / vehicleRequirements[].kind(`VehicleRequirementKind`)
|
||||
|
||||
| 值 | 中文名 |
|
||||
|----|--------|
|
||||
| TRAVEL | 行程用车 |
|
||||
| TRANSFER | 接送机 |
|
||||
|
||||
### vehicleRequirementStatus / vehicleRequirements[].status(`RequirementStatus`)
|
||||
|
||||
| 值 | 中文名(车需求语境) | 备注 |
|
||||
|----|----------------------|------|
|
||||
| PENDING | 待车队配 | |
|
||||
| PROCESSING | 配车中 | |
|
||||
| DONE | 配车完成 | |
|
||||
| PENDING_REVIEW | TRAVEL="待提交车务";TRANSFER="待审核" | 同一状态码按 kind 分叉出不同中文名 |
|
||||
| REJECTED_TO_CONSULTANT | 驳回顾问 | |
|
||||
| REJECTED_TO_ADMIN | 驳回管理员 | |
|
||||
| (该户无对应需求行) | 未提交 | `vehicleRequirementStatus`/`StatusName` 单值字段专属,`vehicleRequirements[]` 数组元素不会出现这个取值——没有对应行就不会出现在数组里 |
|
||||
|
||||
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| vehicleRequirementStatus / StatusName | 只取该户 TRAVEL 类需求行的值;无 TRAVEL 行则恒为 "未提交" | 取展示序(TRAVEL 优先)首条**活跃**需求行的值;只要该户存在任意一类需求行就不再是"未提交" |
|
||||
| vehicleRequirementKind / KindName | 恒为 "TRAVEL"/"行程用车"(或该户无 TRAVEL 行时为 null) | 如实反映展示序首条需求行的真实类别,可能是 "TRANSFER"/"接送机" |
|
||||
| vehicleRequirements | 不存在该字段 | 新增,数组,逐类列出该户全部活跃需求行,空时为 `[]` |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 只提交接送机(TRANSFER),无 TRAVEL 需求 | 单值字段恒报"未提交",即便接送机已在流转甚至完成 | 单值字段如实报接送机自己的状态;`vehicleRequirements` 含 1 条 TRANSFER 记录 |
|
||||
| 只提交行程用车(TRAVEL) | 与改后一致(回归测试覆盖,行为未变) | 行为不变 |
|
||||
| 两类需求都提交 | 单值字段只能看到 TRAVEL 一类的状态,无法从列表接口直接得知 TRANSFER 的独立状态 | 单值字段仍取 TRAVEL(展示序优先),但 `vehicleRequirements` 同时给出两类各自独立的真实状态 |
|
||||
| 两类需求都未提交 | "未提交"(#8249 起已是该行为) | 行为不变,`vehicleRequirements` 为 `[]` |
|
||||
|
||||
## 六.7、影响评估(修改/删除类必写)
|
||||
|
||||
- 破坏兼容:否。既有字段名称、类型、语义边界(`null` 代表无对应需求行)均未变,改动只是放宽了 `vehicleRequirementKind` 的实际取值范围、扩大了 `vehicleRequirementStatus`/`StatusName` 能反映的真实状态覆盖面。
|
||||
- 前端是否必须同步上线:若前端曾经硬编码假设 `vehicleRequirementKind` 恒为 `"TRAVEL"`(例如据此固定展示"行程用车"图标、或对非 TRAVEL 值做兜底成空白),需要同步放开,否则只提交接送机的户在前端会展示错误的类别图标/文案,但不会报错或崩溃。
|
||||
- 若前端此前为规避"接送机被误判未提交"这个已知问题,在自己代码里做过特判/兜底逻辑,本单上线后该特判可以删除。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- `hotelRequirementStatus`/`hotelRequirementStatusName` 及其判空逻辑(#8249 行为)未变。
|
||||
- 订单状态、支付状态、合同状态、投保状态、复核/结算状态、价格档位、出行人相关字段等约 25 个既有字段未变。
|
||||
- 分页参数默认值与归一/截断规则未变。
|
||||
- `includeCancelled`/`includeNeeds`/`includeTravelers` 三个开关的既有行为未变。
|
||||
- `#8577` 修复的三个车需求提交/校验接口(`PUT` 保存、`GET` 聚合草稿、`GET` 提交前校验)及其错误码 809121/809122/809123,与本接口是不同代码路径,互不影响。
|
||||
- 团期详情页的"车队"chips 维度接口(`/chips/vehicle`)已支持 TRANSFER 类别展示,本次改动前后行为一致,不受影响。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**活体实测(本会话,2026-09-30,测试服 dev-v3):**
|
||||
|
||||
1. `GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=5&includeTravelers=false&includeNeeds=false` → HTTP 200,返回 3 条真实子订单记录,其中 2 条同时具有 TRAVEL+TRANSFER 两类活跃需求行,单值字段与 `vehicleRequirements[]` 均按预期独立报出各自状态(含 `PENDING_REVIEW` 在 TRANSFER 语境下正确显示为"待审核")。原文见上「响应示例」。
|
||||
2. `GET /v3/admin/order/group-batch/999999999999999999/orders` → HTTP 200,`code:589500, message:"团期不存在"`。
|
||||
3. `GET /v3/admin/order/group-batch/2104839654727618562/orders?page=999&pageSize=5` → HTTP 200,`records:[]`,`total:3` 保留真实总数。
|
||||
|
||||
**自动化回归(`ce7cd238e9355bdc17c45f90ace02827c7db60f5`,已随 PR #8624 合入 dev-v3,覆盖测试服当前暂无现成样本的场景):**
|
||||
|
||||
- `GroupBatchConverterTest#toOrderItemVO_transferOnly_reportsRealStatusNotNotSubmitted`:该户只有一条 TRANSFER/`PENDING_REVIEW` 活跃行 → `vehicleRequirementStatusName` 报"待审核"(不是"未提交"),`vehicleRequirementKind`="TRANSFER",`vehicleRequirements` 含 1 条对应记录。这正是本单要修复的核心场景。
|
||||
- `GroupBatchConverterTest#toOrderItemVO_travelOnly_legacyFieldsUnchanged`:只有 TRAVEL 行时行为与改前一致(回归保护)。
|
||||
- `GroupBatchConverterTest#toOrderItemVO_bothKinds_statusesStayIndependent`:TRAVEL 与 TRANSFER 两类状态互不覆盖,且与查询返回顺序无关(用 TRANSFER 先于 TRAVEL 的输入顺序验证展示序不受取数顺序影响)。
|
||||
- `GroupBatchConverterTest#toOrderItemVO_neitherKind_stillNotSubmitted`:两类都无活跃行时仍报"未提交",`vehicleRequirements` 为空数组而非 null(#8249 行为回归保护)。
|
||||
- `GroupBatchConverterTest#toOrderItemVO_nullVehicleRows_emptyListNotNull`:上游传入 null 行集合时 `vehicleRequirements` 仍是空列表,不会是 null。
|
||||
- `GroupBatchConverterTest#toOrderItemVO_anyExistingRow_neverRendersNotSubmitted`(参数化,覆盖 TRAVEL/TRANSFER × 6 种状态共 12 种组合):只要该户存在任意一条需求行,`statusName` 永不为"未提交"或空白。
|
||||
- `GroupVehicleStatusNameCrossOutletTest`(#8218 既有门禁):同一 `(状态码, kind)` 对在本接口与其他读口的中文名保持一致,本次改动未破坏该跨口一致性。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单:#8619
|
||||
- PR:#8624(squash 合并 `ce7cd238e9355bdc17c45f90ace02827c7db60f5`)
|
||||
- 关联但独立的历史修复:`changelogs-v2/2026-09/30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md`(#8577,见本文「一、背景」的区分说明)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- Issue: https://git.1814.love/wx/HL/issues/8619
|
||||
- PR: https://git.1814.love/wx/HL/pulls/8624
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端负责人:@wx
|
||||
@@ -0,0 +1,578 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8621"
|
||||
title: "派车三个读口补齐状态中文名,枚举码不再裸下发(含 #8620 用车控制状态口径澄清)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "三个既有管理后台读口各新增中文名字段,纯新增、无删除、无改名、无取值变化:(1) GET /admin/fleet/board/orders 的 records[] 新增 baseAssignmentStatusLabel(代表日行落库派单态中文名,与既有 baseAssignmentStatus 恒成对非空;它与 assignmentStatusLabel 是两个不同口径——前者是落库态、不含派生态,后者是覆写后的有效态、会出现临期加急派生态);(2) GET /admin/fleet/group-dispatch/pending-batches 的 records[] 新增 batchStatusName(团期生命周期状态中文名,九态全覆盖)与 dispatchProgressLabel(配车进度中文名,未开始/部分排车/已排满);(3) GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview 的 days[].vehicles[] 新增 statusLabel(派车状态中文名,已派车/已确认,字典另含已取消)。三个字典的共同不变量:码为 null 则中文名为 null(不编默认文案),码非空则中文名必非空;未登记的新码原样回落成码本身,不抛异常也不返回 null——所以前端渲染时不要假设这一格一定是中文,但不必为未知码写空值兜底。这批字段存在的唯一目的是把码→中文的字典收成后端单源(CODE_RULES §15.7),前端本地映射表请改为直接渲染后端下发值:本地表在遇到未登记新码时会显示空白,后端值至少是码本身。同时随 #8620 澄清一条既有字段的读法(字段名与取值零变化):orders[].vehicleControlStatus 是订单级单值、行程用车与接送机两类共用一格,非 DONE 只代表两类里至少一类没齐、说不出是哪一类;要分辨哪类没齐请读同级按类别拆开的字段(travelRequirementStatus / transferDeclared / transferPendingCount)。三个端点的入参、分页、过滤、排序、错误码(100001 / 600012 / 600013 / 401)与其余响应字段均未变化。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务派车读口:状态中文名补齐,枚举码不再裸下发
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **三个既有读口共新增 4 个中文名字段,纯新增**:`records[].baseAssignmentStatusLabel`(派单看板订单清单)、`records[].batchStatusName` + `records[].dispatchProgressLabel`(待配车团期清单)、`days[].vehicles[].statusLabel`(团期配车总览)。
|
||||
- **既有字段一个没动**:`assignmentStatus` / `assignmentStatusLabel` / `baseAssignmentStatus` / `batchStatus` / `dispatchProgress` / `vehicles[].status` 的字段名、类型、取值域、语义与本次改动前逐字相同;入参、分页、过滤、排序、错误码也未变。
|
||||
- **三个字典共用同一组不变量**:码为 `null` ⇒ 中文名同为 `null`(不编默认文案);码非空 ⇒ 中文名必非空;**未登记的新码原样回落成码本身**,既不抛异常也不返回 `null`。所以前端**不需要**为「没见过的码」写空值兜底分支,但**渲染时不要假设这一格一定是中文**(回落时它就是那个码)。
|
||||
- **前端请停用本地的码 → 中文映射表**,直接渲染后端下发的中文名字段。本地表在遇到未登记新码时渲染成空白,而后端值至少是码本身;这批字段存在的唯一理由就是把字典收成后端单源(CODE_RULES §15.7)。
|
||||
- 🔴 **`baseAssignmentStatusLabel` 与 `assignmentStatusLabel` 不是一回事,别混用**:前者是**落库态**中文名(不派生、不被当前需求口径覆写,永远是 6 个落库态之一),后者是**覆写后的有效态**中文名(可能对应 `unassigned_urgent` / `holding_urgent` 这类派生态)。要展示「落库态 vs 有效态」并排对照(陈旧定稿排查场景)才需要前者;常规状态列继续用后者。
|
||||
- 🔴 **`vehicleControlStatus` 是订单级单值、两类共用一格**(#8620,本次只澄清读法,字段与取值零变化):它非 `DONE` 只说明「行程用车与接送机里至少一类没齐」,**说不出是哪一类**。要分辨请读同级按类别拆开的字段(`travelRequirementStatus` / `transferDeclared` / `transferPendingCount`)。
|
||||
- **`CANCELLED`(已取消)在派车状态字典里有中文名**。团期配车总览的逐车项 `status` 正常只会出现 `ASSIGNED` / `CONFIRMED`(已取消的派车行不进总览),但字典三码全覆盖,前端若自行构造筛选项按两值即可。
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
这批读口此前把枚举码裸下发:`baseAssignmentStatus`、`batchStatus`、`dispatchProgress`、`vehicles[].status` 四处只有码、没有中文名,而同一行上别的状态字段(如 `assignmentStatusLabel`、`requirementKindLabel`)早已由后端下发中文名。结果是前端必须在本地再维护一份码 → 中文的映射表,这份表与后端枚举是两份真源:后端加一个码,前端那格就渲染成空白,而且没有任何信号提示。本次把这四处补齐成「码 + 中文名成对下发」,字典的唯一来源放在后端枚举里。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 派单看板订单清单 | GET | `/admin/fleet/board/orders` | 修改 | `records[]` 新增 `baseAssignmentStatusLabel`(落库派单态中文名) |
|
||||
| 2 | 待配车团期清单 | GET | `/admin/fleet/group-dispatch/pending-batches` | 修改 | `records[]` 新增 `batchStatusName`(团期状态中文名)与 `dispatchProgressLabel`(配车进度中文名) |
|
||||
| 3 | 团期配车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 修改 | `days[].vehicles[]` 新增 `statusLabel`(派车状态中文名);`orders[].vehicleControlStatus` 读法澄清(#8620) |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 派单看板订单清单 `GET /admin/fleet/board/orders`
|
||||
|
||||
**VO**: `BoardOrderPageReqVO → BoardOrderPageRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务派单看板的订单清单(list / grid 两视图共用)。本次变更只在每行上多给一个中文名字段,供「落库态 vs 有效态」并排展示的排查场景使用;常规状态列继续用 `assignmentStatusLabel`。
|
||||
|
||||
#### 入参
|
||||
|
||||
入参本次**零变化**,为便于自洽联调完整列出(全部 query 参数,全部选填)。
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| statuses | query | String[] | 否 | `unassigned` / `unassigned_urgent` / `holding` / `holding_urgent` / `assigned` / `canceled` / `completed` | 多状态筛选,含派生态,任一命中即返;空=不过滤 |
|
||||
| status | query | String | 否 | 同 `statuses` 取值域 | `statuses` 的别名,单值或逗号分隔,与 `statuses` 合并 |
|
||||
| startDayFrom | query | LocalDate | 否 | `YYYY-MM-DD` | 日期区间起,与行程区间重叠(非仅出团日);单边只约束一侧 |
|
||||
| startDayTo | query | LocalDate | 否 | `YYYY-MM-DD` | 日期区间止 |
|
||||
| startDate | query | LocalDate | 否 | `YYYY-MM-DD` | `startDayFrom` 的兼容别名,未传 `startDayFrom` 时生效 |
|
||||
| endDate | query | LocalDate | 否 | `YYYY-MM-DD` | `startDayTo` 的兼容别名,未传 `startDayTo` 时生效 |
|
||||
| vehicleTypeKeys | query | String[] | 否 | — | 车型大类多选;未派按需求车型、已派按实际车辆大类过滤 |
|
||||
| typeKeys | query | String[] | 否 | — | `vehicleTypeKeys` 的兼容别名,未传前者时生效 |
|
||||
| driverName | query | String | 否 | — | 司机姓名模糊搜索 |
|
||||
| keyword | query | String | 否 | — | 统一文字搜索:司机 / 联系人 / 团号 / 订单号 / 当前定制师显示名任一包含 |
|
||||
| contactName | query | String | 否 | — | 联系人(客户名)模糊搜索 |
|
||||
| contactKeyword | query | String | 否 | — | `contactName` 的兼容别名 |
|
||||
| teamNo | query | String | 否 | — | 团号模糊搜索(仅匹配真实团号,不匹配订单号) |
|
||||
| groupBatchId | query | Long | 否 | — | 运营团期 ID 精确筛选 |
|
||||
| orderKind | query | String | 否 | `ALL` / `NORMAL` / `GROUP`,其余值返 100001 | 订单归属粗筛;不传或空串=`ALL`。`NORMAL` 与 `groupBatchId` 同传逻辑互斥,返空列表不报错 |
|
||||
| requirementKind | query | String | 否 | `TRAVEL` / `TRANSFER`,其余值返 100001 | 用车需求类别筛选;不传或空串=不过滤 |
|
||||
| consultantId | query | Long | 否 | — | 当前负责定制师管理员 ID 精确筛选(下拉值由看板汇总接口下发) |
|
||||
| plannerName | query | String | 否 | — | 定制师姓名模糊搜索(兼容旧前端) |
|
||||
| consultantName | query | String | 否 | — | `plannerName` 的别名 |
|
||||
| variant | query | String | 否 | `list`(默认)/ `grid`,其余值返 100001 | 视图 |
|
||||
| page | query | Integer | 否 | ≥ 1,默认 1 | 页码;`pageNo` 是其兼容别名 |
|
||||
| pageSize | query | Integer | 否 | 1~100,默认 20 | 每页条数 |
|
||||
|
||||
#### 出参 `Result<BoardOrderPageRespVO>`
|
||||
|
||||
只列与本次变更直接相关的字段;`records[]` 其余字段与本次改动前完全一致。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | Array | 订单行列表,维度=当前有效用车需求;同一 `requirementId` 只返回一条 |
|
||||
| records[].assignmentStatus | String | 当前派单状态码(含派生 `unassigned_urgent` / `holding_urgent`,会按当前需求口径覆写);**未变** |
|
||||
| records[].assignmentStatusLabel | String | 当前派单状态中文名(有效态口径);**未变** |
|
||||
| records[].baseAssignmentStatus | String | 代表日行落库基础状态码(不派生、不覆写):`unassigned` / `holding` / `assigned` / `canceled` / `exception` / `completed`;**未变** |
|
||||
| records[].baseAssignmentStatusLabel | String | 🆕 落库基础状态中文名,与 `baseAssignmentStatus` 恒成对非空:待派车 / 待确认执行 / 已派车 / 已取消 / 异常 / 已完结。**永远不会出现派生态对应的文案**(派生态只进 `assignmentStatus`);未登记码原样回落成码本身 |
|
||||
| total | Long | 总条数 |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/board/orders?variant=list&startDayFrom=2026-10-01&startDayTo=2026-10-31&page=1&pageSize=20
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2103998277441093633",
|
||||
"orderNo": "HL202610080031",
|
||||
"teamNo": "T26-4128",
|
||||
"orderId": "2103998277441093632",
|
||||
"customerName": "周雅",
|
||||
"headcount": 4,
|
||||
"startDate": "2026-10-08",
|
||||
"endDate": "2026-10-12",
|
||||
"requirementKind": "TRAVEL",
|
||||
"requirementKindLabel": "行程用车",
|
||||
"assignmentStatus": "unassigned_urgent",
|
||||
"assignmentStatusLabel": "待派车",
|
||||
"baseAssignmentStatus": "unassigned",
|
||||
"baseAssignmentStatusLabel": "待派车",
|
||||
"manualUrgent": false,
|
||||
"canAssign": true
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无命中:`data.records` 返回空数组 `[]`,`total` 为 `0`,不返回 `null`,不报错。
|
||||
- `records[]` 有行时 `baseAssignmentStatus` 与 `baseAssignmentStatusLabel` **必定同时非空**:落库态取自派单行的 `assignment_status`(NOT NULL);订单还没有落库派单行时走虚拟待派卡,落库态固定 `unassigned`、中文名固定「待派车」,不会出现「有码没中文名」或「有中文名没码」的半边状态。
|
||||
- `order-v3` 整体不可达时,行上的日期、紧急态、排序会回退派单快照口径(本次未改这条既有降级路径),`baseAssignmentStatusLabel` 仍照常下发(它只依赖 fleet 本域落库行)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100001,
|
||||
"message": "参数非法: variant 仅支持 list/grid,传入非法值:card",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
- `100001 参数非法: {0}`:`variant` 非 `list`/`grid`、`orderKind` 非 `ALL`/`NORMAL`/`GROUP`、`requirementKind` 非 `TRAVEL`/`TRANSFER`、`page` < 1、`pageSize` 越界。
|
||||
- `401`:未登录或令牌失效。注意测试环境网关对失效令牌返回 **HTTP 200 + 信封 `code: 401`**,前端拦截器请按信封 `code` 判定,不要只看 HTTP 状态行。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `baseAssignmentStatusLabel` 与 `assignmentStatusLabel` 走**同一份**映射(`AssignmentStatusEnum.labelOf`),只是喂进去的码不同:前者喂落库态、后者喂覆写后的有效态。所以同一行上两个中文名可能不同(例:落库 `assigned`「已派车」而有效态被当前需求口径覆写成「待派车」),**这不是数据错误**,正是本字段要暴露的对照。
|
||||
- 派生态 `unassigned_urgent` / `holding_urgent` 只出现在 `assignmentStatus`,`baseAssignmentStatus` 与其中文名永远是 6 个落库态之一。前端若拿 `baseAssignmentStatusLabel` 当加急标识会永远读不到加急,加急请读 `assignmentStatus` 或 `manualUrgent` / `urgentBadge`。
|
||||
- 落库态 `exception`(异常)在筛选入参 `statuses` 的取值域里**没有**对应筛选项,但它会作为 `baseAssignmentStatus` 的值出现在响应里,中文名「异常」。
|
||||
- 中文名不参与任何筛选与排序,只是展示字段;按状态筛选一律传码。
|
||||
|
||||
---
|
||||
|
||||
### 2. 待配车团期清单 `GET /admin/fleet/group-dispatch/pending-batches`
|
||||
|
||||
**VO**: `GroupDispatchPendingBatchPageReqVO → PageResult<GroupDispatchPendingBatchRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务「待配车团期」列表页。本次每行多给两个中文名:团期生命周期状态与配车进度,前端可直接渲染,不再需要本地两张映射表。
|
||||
|
||||
#### 入参
|
||||
|
||||
入参本次**零变化**,完整列出。
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| departDateFrom | query | LocalDate | 否 | `YYYY-MM-DD` | 出发日区间起;单边只约束一侧 |
|
||||
| departDateTo | query | LocalDate | 否 | `YYYY-MM-DD` | 出发日区间止 |
|
||||
| keyword | query | String | 否 | 长度 ≤ 50,超长返 600013 | 团号 / 团期名称模糊搜索 |
|
||||
| dispatchProgress | query | String | 否 | `NOT_STARTED` / `PARTIAL` / `FULL`,其余值返 600013 | 按配车进度筛选;不传=不过滤 |
|
||||
| page | query | Integer | 否 | ≥ 1,默认 1 | 页码 |
|
||||
| pageSize | query | Integer | 否 | 1~100,默认 20 | 每页条数 |
|
||||
|
||||
#### 出参 `Result<PageResult<GroupDispatchPendingBatchRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | Array | 待配车团期行列表 |
|
||||
| records[].groupBatchId | String | 运营团期 ID(雪花 ID 以字符串下发) |
|
||||
| records[].batchNo | String | 团号 |
|
||||
| records[].batchName | String | 团期名称 |
|
||||
| records[].batchStatus | String | 团期生命周期状态码;**未变** |
|
||||
| records[].batchStatusName | String | 🆕 团期状态中文名,与 `batchStatus` 恒成对非空。九态见「六.5」;未登记码原样回落成码本身 |
|
||||
| records[].departDate | String | 出发日 `YYYY-MM-DD` |
|
||||
| records[].endDate | String | 结束日 `YYYY-MM-DD` |
|
||||
| records[].serviceDayCount | Integer | 服务天数 |
|
||||
| records[].enrolledOrders | Integer | 已报名子订单数 |
|
||||
| records[].enrolledPeople | Integer | 已报名人数 |
|
||||
| records[].requirementConfirmed | Boolean | 团期用车需求是否已确认 |
|
||||
| records[].vehicleReady | Boolean | 车辆是否已就绪 |
|
||||
| records[].dispatchedDayCount | Integer | 已排车天数 |
|
||||
| records[].dispatchProgress | String | 配车进度码:`NOT_STARTED` / `PARTIAL` / `FULL`;**未变** |
|
||||
| records[].dispatchProgressLabel | String | 🆕 配车进度中文名,与 `dispatchProgress` 恒成对非空:未开始 / 部分排车 / 已排满 |
|
||||
| records[].transferPendingCount | Integer | 接送机未配计数;`null` = 未取到(**不是 0**,#8593) |
|
||||
| records[].unreadCount | Integer | 未读会话消息数;依赖服务不可达时退化为 `0` |
|
||||
| total | Long | 总条数 |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/pending-batches?departDateFrom=2026-10-01&departDateTo=2026-10-31&dispatchProgress=PARTIAL&page=1&pageSize=20
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"batchNo": "T26-3963",
|
||||
"batchName": "呼伦贝尔环线 10/06 团",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"batchStatusName": "资源准备中",
|
||||
"departDate": "2026-10-06",
|
||||
"endDate": "2026-10-10",
|
||||
"serviceDayCount": 5,
|
||||
"enrolledOrders": 6,
|
||||
"enrolledPeople": 18,
|
||||
"requirementConfirmed": false,
|
||||
"vehicleReady": false,
|
||||
"dispatchedDayCount": 2,
|
||||
"dispatchProgress": "PARTIAL",
|
||||
"dispatchProgressLabel": "部分排车",
|
||||
"transferPendingCount": 1,
|
||||
"unreadCount": 3
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无命中:`records` 为空数组 `[]`,`total` 为 `0`。
|
||||
- `batchStatusName` 由 order-v3 随团期候选项一起下发,fleet **原样透传、不在本域二次映射**(避免两份字典)。团期状态码为 `null` 时该中文名同为 `null`,后端不编默认文案;前端遇到这一格为 `null` 时请渲染成空白或「—」,不要回填「未知」这类自造文案。
|
||||
- `dispatchProgressLabel` 与 `dispatchProgress` 在同一次判定里算出,不存在「码与文案分别算出来后对不上」的窗口,二者恒一致。
|
||||
- `transferPendingCount` 的 `null` 与 `unreadCount` 的 `0` 是两条互相独立的软依赖退化路径,任一退化都不影响本次新增的两个中文名字段。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 600013,
|
||||
"message": "排班查询参数非法: dispatchProgress 仅支持 NOT_STARTED/PARTIAL/FULL",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
- `600013 排班查询参数非法: {0}`:`dispatchProgress` 取值非法、`keyword` 超长、分页参数越界。
|
||||
- `600012 团期配车基线不可达,请稍后重试`:团期基线数据读不到;本端点不会用空列表冒充成功。
|
||||
- `401`:未登录或令牌失效(网关返 HTTP 200 + 信封 `code: 401`)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 中文名只用于展示。`dispatchProgress` 入参筛选仍只接受码(`NOT_STARTED` / `PARTIAL` / `FULL`),传中文名会按非法值返 600013。
|
||||
- 团期状态字典是 order-v3 的九态全集(见「六.5」),本列表按「待配车」语义筛选后实际只会出现其中一部分;前端若要构造状态筛选下拉,请从本列表返回值里去重收集,不要按九态硬编码全集。
|
||||
- `batchStatusName` 与团期管理列表页(order-v3 团期分页)的同名字段来自**同一个**转换方法,两页面上同一个团期的状态文案恒一致。
|
||||
- 未登记的团期状态码回落成码本身(不抛异常),所以这一格可能出现英文码——前端不需要兜底,但列宽与换行请按可能出现英文码来设计。
|
||||
|
||||
---
|
||||
|
||||
### 3. 团期配车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`
|
||||
|
||||
**VO**: `Long groupBatchId(路径参数)→ GroupDispatchOverviewRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
单个团期的配车总览:逐服务日的车辆卡片 + 本团子订单的用车控制状态。本次在逐车项上补齐派车状态中文名,并澄清 `orders[].vehicleControlStatus` 的读法(#8620)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | path | Long | 是 | 雪花 ID,正整数 | 运营团期 ID |
|
||||
|
||||
#### 出参 `Result<GroupDispatchOverviewRespVO>`
|
||||
|
||||
只列与本次变更直接相关的字段;其余字段与本次改动前完全一致。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| groupBatchId | String | 运营团期 ID(字符串下发) |
|
||||
| batchNo | String | 团号 |
|
||||
| days | Array | 逐服务日节点 |
|
||||
| days[].tripDate | String | 服务日 `YYYY-MM-DD` |
|
||||
| days[].vehicles | Array | 该日已排车辆项 |
|
||||
| days[].vehicles[].dispatchId | String | 派车行 ID |
|
||||
| days[].vehicles[].vehiclePlate | String | 车牌 |
|
||||
| days[].vehicles[].vehicleModel | String | 车型 |
|
||||
| days[].vehicles[].driverName | String | 司机姓名 |
|
||||
| days[].vehicles[].status | String | 派车状态码,取值 `ASSIGNED` / `CONFIRMED`;**未变** |
|
||||
| days[].vehicles[].statusLabel | String | 🆕 派车状态中文名,与 `status` 恒成对非空:已派车 / 已确认(字典另含 `CANCELLED` 已取消,正常不出现在本列表) |
|
||||
| days[].vehicleCount | Integer | 该日车辆数 |
|
||||
| days[].dispatched | Boolean | 该日是否已排车 |
|
||||
| orders | Array | 本团子订单的用车覆盖情况 |
|
||||
| orders[].vehicleControlStatus | String | **订单级单值,行程用车与接送机两类共用一格**(#8620 澄清,取值与字段名未变);非 `DONE` 只代表两类里至少一类没齐,说不出是哪一类 |
|
||||
| orders[].travelRequirementStatus | String | 行程用车需求状态(按类别拆开的字段之一) |
|
||||
| orders[].transferDeclared | Boolean | 是否声明了接送机 |
|
||||
| orders[].transferPendingCount | Integer | 该订单接送机未覆盖段数 |
|
||||
| transferPendingTotal | Integer | 全团接送机未覆盖段数合计 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/fleet/group-dispatch/batches/2104839654727618562/overview
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"batchNo": "T26-3963",
|
||||
"departDate": "2026-10-06",
|
||||
"endDate": "2026-10-10",
|
||||
"requirementConfirmed": false,
|
||||
"vehicleReady": false,
|
||||
"days": [
|
||||
{
|
||||
"tripDate": "2026-10-06",
|
||||
"vehicles": [
|
||||
{
|
||||
"dispatchId": "2104840113194905601",
|
||||
"vehicleId": "1902233114509312002",
|
||||
"vehiclePlate": "蒙E13572",
|
||||
"vehicleModel": "丰田考斯特",
|
||||
"driverId": "1902233114509312050",
|
||||
"driverName": "李广宇",
|
||||
"driverPhone": "13847001234",
|
||||
"status": "ASSIGNED",
|
||||
"statusLabel": "已派车",
|
||||
"remark": null,
|
||||
"groupCode": "A"
|
||||
}
|
||||
],
|
||||
"vehicleCount": 1,
|
||||
"dispatched": true
|
||||
}
|
||||
],
|
||||
"missingDates": ["2026-10-09", "2026-10-10"],
|
||||
"orders": [
|
||||
{
|
||||
"orderId": "2104839654727618570",
|
||||
"orderNo": "HL202610060012",
|
||||
"teamNo": "T26-3963",
|
||||
"customerName": "周雅",
|
||||
"headcount": 4,
|
||||
"vehicleControlStatus": "PENDING_REVIEW",
|
||||
"travelRequirementId": "2104839777884160001",
|
||||
"travelRequirementStatus": "PENDING_REVIEW",
|
||||
"transferDeclared": true,
|
||||
"transferPendingCount": 1
|
||||
}
|
||||
],
|
||||
"transferPendingTotal": 1,
|
||||
"conversationKey": "GROUP_FLEET:2104839654727618562"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 某服务日还没排车:`days[].vehicles` 为空数组 `[]`,`vehicleCount` 为 `0`,`dispatched` 为 `false`;该日期同时出现在 `missingDates` 里。
|
||||
- `vehicles[]` 有项时 `status` 与 `statusLabel` **必定同时非空**(派车行的状态列 NOT NULL);不存在「有码没中文名」的半边状态。
|
||||
- 团期没有任何子订单时 `orders` 为空数组,`transferPendingTotal` 为 `0`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 600012,
|
||||
"message": "团期配车基线不可达,请稍后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
- `600012 团期配车基线不可达,请稍后重试`:团期基线数据读不到;不会用空总览冒充成功。
|
||||
- `401`:未登录或令牌失效(网关返 HTTP 200 + 信封 `code: 401`)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `statusLabel` 的字典含三个码(`ASSIGNED` 已派车 / `CONFIRMED` 已确认 / `CANCELLED` 已取消),但本总览只装载未取消的派车行,所以实际只会读到前两个。前端构造状态筛选或图例时按两值即可,不必为「已取消」留位置。
|
||||
- 🔴 `orders[].vehicleControlStatus` 是**订单级单值**,行程用车与接送机两类共用这一格(#8620)。它非 `DONE` **不能**推断「行程用车没齐」,也不能推断「接送机没齐」——只能推断「至少一类没齐」。要落到具体类别,读 `travelRequirementStatus`(行程用车那一类)与 `transferDeclared` / `transferPendingCount`(接送机那一类)。
|
||||
- `vehicleControlStatus` 的取值域是 order-v3 的需求状态集:`PENDING` / `PROCESSING` / `DONE` / `PENDING_REVIEW` / `REJECTED_TO_CONSULTANT` / `REJECTED_TO_ADMIN`。本次未新增、未删除取值。
|
||||
- 本端点的 `statusLabel` 与派车详情等其它读口的派车状态文案同源(同一份枚举字典),不会出现两处对同一状态给不同中文名的情况。
|
||||
- 中文名不参与任何筛选、排序或统计;`vehicleCount`、`transferPendingTotal`、`missingDates` 的口径本次未变。
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
1. **只增不改**:本次三个端点各只新增字段,没有删除、没有改名、没有取值域变化。前端已有代码不改也不会坏;要拿到中文名才需要改。
|
||||
2. **中文名与码成对读,成对判空**:`码 == null ⇒ 中文名 == null`、`码 != null ⇒ 中文名 != null`。判「这一格有没有值」只需判其中一个;两个都判是冗余的,但**不要**出现「码为 null 却期待中文名有值」的分支——那条路不存在。
|
||||
3. **未登记码原样回落成码本身**:四个字典(派单落库态 / 团期生命周期 / 配车进度 / 派车状态)的中文名解析都不抛异常、不返回 `null`。后端将来加码时,前端这一格会显示英文码而不是空白。**所以前端不要写「中文名为空就显示码」的兜底**(永远进不去),但**要**按「这一格可能是英文码」设计列宽与样式。
|
||||
4. **停用本地映射表**:读到中文名字段后请删掉前端本地那份码 → 中文的表。两份字典并存时,后端加码 = 前端空白,而且没有报错、没有告警,只有用户看到一格空白。
|
||||
5. **筛选仍传码**:`statuses` / `status` / `dispatchProgress` / `requirementKind` / `orderKind` 一律只接受码。传中文名会按非法值报 100001(看板)或 600013(待配车清单)。
|
||||
6. **落库态与有效态分清**:要展示「当前状态」用 `assignmentStatusLabel`;要展示「落库真实状态」用 `baseAssignmentStatusLabel`。用后者当状态列会让加急态与陈旧定稿覆写这两类信息全部消失。
|
||||
7. **`vehicleControlStatus` 不可用于判别类别**(#8620):它是两类共用的单值。要按类别展示或筛选,用 `travelRequirementStatus` / `transferDeclared` / `transferPendingCount`,或走看板清单的 `requirementKind` 维度。
|
||||
8. **错误信封统一按 `code` 判**:业务失败与入参校验一律 HTTP 200 + 信封 `code`;测试环境网关对失效令牌也返回 HTTP 200 + `code: 401`。只看 HTTP 状态行的拦截器会把「已掉登录」当成功。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
三个端点均为只读查询,本次改动**不涉及任何 DDL 与 DML**:没有新增表、没有新增列、没有 Flyway 脚本、没有写入。新增的中文名字段全部在内存里由枚举字典解析出来,不落库、不参与任何 SQL 过滤或分组,因此既有的按状态码筛选 / 统计的查询路径读数一律不变。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 状态码为 `null` | 对应中文名同为 `null`,后端不编默认文案 |
|
||||
| 状态码为未登记的新值 | 中文名回落成码本身,不抛异常、不返回 `null` |
|
||||
| 订单无落库派单行(虚拟待派卡) | `baseAssignmentStatus` 固定 `unassigned`,`baseAssignmentStatusLabel` 固定「待派车」 |
|
||||
| 同一行落库态与有效态不同 | 两个中文名不同,属预期(正是本字段的用途),不是数据错误 |
|
||||
| 派生态(临期加急 / hold 超时) | 只进 `assignmentStatus`;`baseAssignmentStatus` 与其中文名永远是 6 个落库态之一 |
|
||||
| 团期状态中文名的来源服务读不到 | 该格为 `null`(与码同生同灭),不影响同行其它字段 |
|
||||
| 团期配车总览里有已取消的派车行 | 不装载进 `days[].vehicles`,所以 `statusLabel` 实际读不到「已取消」 |
|
||||
| 无命中 / 无数据 | 列表返空数组,不返 `null`;不用空数据冒充成功以外的语义 |
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
**落库派单状态**(`baseAssignmentStatus` → `baseAssignmentStatusLabel`,6 个落库态)
|
||||
|
||||
| 码 | 中文名 |
|
||||
|----|--------|
|
||||
| unassigned | 待派车 |
|
||||
| holding | 待确认执行 |
|
||||
| assigned | 已派车 |
|
||||
| canceled | 已取消 |
|
||||
| exception | 异常 |
|
||||
| completed | 已完结 |
|
||||
|
||||
派生态 `unassigned_urgent`(→待派车)与 `holding_urgent`(→待确认执行)只出现在 `assignmentStatus`,**不会**出现在 `baseAssignmentStatus`。
|
||||
|
||||
**团期生命周期状态**(`batchStatus` → `batchStatusName`,九态)
|
||||
|
||||
| 码 | 中文名 |
|
||||
|----|--------|
|
||||
| RECRUITING | 招募中 |
|
||||
| RESOURCE_PREPARING | 资源准备中 |
|
||||
| MATERIAL_PREPARING | 物料准备中 |
|
||||
| PENDING_DEPARTURE | 待出发 |
|
||||
| TRAVELLING | 出行中 |
|
||||
| TRIP_FINISHED | 出行完毕 |
|
||||
| REVIEWING | 核单中 |
|
||||
| SETTLED | 已结算 |
|
||||
| CANCELLED | 已取消 |
|
||||
|
||||
**配车进度**(`dispatchProgress` → `dispatchProgressLabel`)
|
||||
|
||||
| 码 | 中文名 |
|
||||
|----|--------|
|
||||
| NOT_STARTED | 未开始 |
|
||||
| PARTIAL | 部分排车 |
|
||||
| FULL | 已排满 |
|
||||
|
||||
**派车状态**(`vehicles[].status` → `statusLabel`)
|
||||
|
||||
| 码 | 中文名 | 是否出现在配车总览 |
|
||||
|----|--------|------------------|
|
||||
| ASSIGNED | 已派车 | 是 |
|
||||
| CONFIRMED | 已确认 | 是 |
|
||||
| CANCELLED | 已取消 | 否(已取消的派车行不装载进总览) |
|
||||
|
||||
**订单级用车控制状态**(`vehicleControlStatus`,本次未改取值,仅澄清读法)
|
||||
|
||||
| 码 | 语义 |
|
||||
|----|------|
|
||||
| PENDING | 待处理 |
|
||||
| PROCESSING | 处理中 |
|
||||
| DONE | 两类都已齐 |
|
||||
| PENDING_REVIEW | 待审核 |
|
||||
| REJECTED_TO_CONSULTANT | 已驳回定制师 |
|
||||
| REJECTED_TO_ADMIN | 已驳回管理员 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 端点 | 字段 | 改动前 | 改动后 |
|
||||
|------|------|--------|--------|
|
||||
| `GET /admin/fleet/board/orders` | `records[].baseAssignmentStatusLabel` | 字段不存在(前端只能本地映射 `baseAssignmentStatus`) | 新增,与码恒成对非空 |
|
||||
| `GET /admin/fleet/group-dispatch/pending-batches` | `records[].batchStatusName` | 字段不存在(只有 `batchStatus` 裸码) | 新增,与码恒成对非空,与团期管理列表页同源 |
|
||||
| `GET /admin/fleet/group-dispatch/pending-batches` | `records[].dispatchProgressLabel` | 字段不存在(只有 `dispatchProgress` 裸码) | 新增,与码在同一次判定里算出 |
|
||||
| `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | `days[].vehicles[].statusLabel` | 字段不存在(只有 `status` 裸码) | 新增,与码恒成对非空 |
|
||||
| `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | `orders[].vehicleControlStatus` | 字段与取值相同,但文档未说明它是两类共用的订单级单值 | 字段与取值**完全不变**;文档明确:非 `DONE` 只代表至少一类没齐,判类别须读按类别拆开的字段(#8620) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端必须改的**:无。不改一行也不会坏——四个字段都是新增,既有字段与取值零变化。
|
||||
- **前端应当改的**:删掉本地的四张码 → 中文映射表,改读后端下发的中文名。收益是后端加码时不再出现静默空白格;不改的风险是本地表与后端字典分叉,且分叉无任何报错信号。
|
||||
- **前端可能读错的一处**:把 `baseAssignmentStatusLabel` 当成「当前状态」显示在状态列 ⇒ 加急态与陈旧定稿覆写全部丢失。状态列仍应用 `assignmentStatusLabel`。
|
||||
- **前端可能读错的另一处**(#8620):把 `vehicleControlStatus` 当成「行程用车状态」或「接送机状态」的单一来源 ⇒ 在只报接送机、或只报行程用车的订单上会给出误导性展示。判类别必须读按类别拆开的字段。
|
||||
- **兼容性**:JSON 新增字段对已有前端反序列化无影响(未知字段忽略 / 多出字段不解析)。响应体每行增大 4 个短字符串量级,分页上限 100 行,体积影响可忽略。
|
||||
- **无副作用面**:不涉及写入、不涉及事务、不涉及消息、不涉及权限判定,也不改任何筛选与统计口径。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 三个端点的**入参**:字段、别名、默认值、校验规则、错误码全部未变。
|
||||
- 三个端点的**分页、过滤、排序、聚合去重**口径全部未变。
|
||||
- 三个端点的**既有响应字段**:名称、类型、取值域、语义全部未变,包括 `assignmentStatus` / `assignmentStatusLabel` / `baseAssignmentStatus` / `batchStatus` / `dispatchProgress` / `vehicles[].status` / `orders[].vehicleControlStatus`。
|
||||
- **错误码**未新增、未删除、未改文案(100001 / 600012 / 600013 / 401)。
|
||||
- **写接口**:派车提交、派车确认、需求打回等写路径本次一行未改。
|
||||
- **网关路由**:三个端点都是既有路由,`/admin/fleet/**` 已配置,本次无新增路由。
|
||||
- **数据库**:无 DDL、无 DML、无 Flyway 脚本。
|
||||
- **小程序端**:本次改动全部落在管理后台读口,小程序端零影响。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
本次改动的可验证面是「码 → 中文名」的映射与成对不变量,已由下列自动化用例覆盖(`hl-fleet-service` + `hl-order-service-v3`):
|
||||
|
||||
| 覆盖点 | 用例 |
|
||||
|--------|------|
|
||||
| 派车状态三码各返约定中文名(含正常读不到的 `CANCELLED`) | `GroupDispatchStatusTest#labelOf_allDeclaredCodes_returnsChineseLabel` |
|
||||
| 派车状态:码非空 ⇒ 中文名必非空,且中文名不等于码本身 | `GroupDispatchStatusTest#labelOf_codeNotNull_labelNeverNull` |
|
||||
| 派车状态:未知码原样回落不抛异常,`null` 返 `null` | `GroupDispatchStatusTest#labelOf_unknownCodeOrNull_fallsBackWithoutThrowing` |
|
||||
| 配车进度三档各返约定中文名 | `GroupDispatchProgressTest#labelOf_allDeclaredCodes_returnsChineseLabel` |
|
||||
| 配车进度:码非空 ⇒ 中文名必非空 | `GroupDispatchProgressTest#labelOf_codeNotNull_labelNeverNull` |
|
||||
| 配车进度:未知码回落、`null` 返 `null`;`isValid` 只认三档 | `GroupDispatchProgressTest#labelOf_unknownCodeOrNull_fallsBackWithoutThrowing`、`#isValid_onlyDeclaredCodes` |
|
||||
| 待配车清单:团期状态中文名原样透传上游、fleet 不做二次映射 | `GroupDispatchQueryServiceTest`(`#8621` 待配车清单用例) |
|
||||
| 配车总览逐车项:`status` 与 `statusLabel` 恒成对非空 | `GroupDispatchQueryServiceTest`(`#8621` 逐车项用例) |
|
||||
| 团期候选项下发状态中文名:与团期列表同一份映射,码非空则中文名必非空;码为 `null` 时中文名同为 `null`、不编默认文案 | `GroupBatchVehicleDispatchQueryServiceTest`(`#8621` 两个用例) |
|
||||
| 看板订单行:落库态中文名与 `baseAssignmentStatus` 恒成对非空;虚拟待派卡也给中文名 | `BoardOrderServiceTest`(`#8621` 用例) |
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
- PR #8622(本次):`feat(fleet,order-v3): 派车读口补齐状态中文名,枚举码不再裸下发(#8620 #8621)`。
|
||||
- #8593:待配车团期清单 `transferPendingCount` 由硬编码 0 改为真值,并引入 `null` = 未取到语义。
|
||||
- #8518:看板清单 `requirementKind` 入参与 `requirementKindLabel` 出参(同一「后端下发中文名」方向的先例)。
|
||||
- #7535:枚举中文名由枚举归属服务下发、后缀命名约定(`batchStatusName` 用 `Name` 而非 `Label` 的由来)。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- `docs/CODE_RULES.md` §15.7:对称子域禁镜像重复 / 字典字面量单源——本次四个字段的立项依据。
|
||||
- `docs/CODE_RULES.md` §3:VO 命名与 `@ApiModelProperty` 约定。
|
||||
- Swagger:`hl-fleet-service` → `看板` 与 `团期配车` 分组,三个端点的字段注释已同步更新(含 `allowableValues`)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- 工单 #8621(补中文名)、#8620(`vehicleControlStatus` 订单级单值口径澄清)
|
||||
- PR #8622
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端:wx
|
||||
- 前端:mmg(管理后台 hl-ui)
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8626"
|
||||
title: "司导往来账账页+明细分页两接口上线:按带团服务人员聚合报账+预支净往来(role 取值含摄影/领队)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "1a337d86d556062cd78daf7a94ba3a42ac310dce"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "财务往来账新增「司导往来账」两个只读查询接口(/admin/finance/statements/guide-ledger/page 账页 + /entries 明细),按带团服务人员(司导)聚合其报账单+预支单现算净往来,纯查询零 DDL、不动 fin_statement_entry、不接上游流水钩子。口径要点:①司导=带团服务人员(导游 GUIDE/司机 DRIVER/摄影 PHOTOGRAPHER/领队 LEADER,均非内部员工),员工借款 fin_staff_loan 不纳入;②净往来只算已生效报账单(排除 PENDING 未批准/RETURNED 已退回),预支算 APPROVED/PAID;③聚合键=报账人/收款人姓名快照(reporterAssignmentId 与 payeeStaffId 分属两套 ID 空间无法 join);④netBalance 正=司导欠公司、负=公司欠司导。两接口入参/出参/枚举/错误码自 2026-09-30 上线起即为本文口径,无历史版本。前端无既有调用,纯新对接。前端已交付:statement.js 加司导段(API 层 page→pageNo,字典四 role/两 bizType/三 direction/PAID 分文案);新建 current-account/guide 页内 v-if 双视图(账页 netBalance 三态+明细三选一标识原样带回);hiddenRoute 先行(后端 sys_menu 未下挂,预期 component=finance/current-account/guide)。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:司导往来账账页 + 明细分页(管理后台)
|
||||
|
||||
**服务**: hl-order-service-v3(finance 模块,同进程)
|
||||
**PR**: https://git.1814.love/wx/HL/pulls/8574 (账页初版) + https://git.1814.love/wx/HL/pulls/8627 (口径扩摄影/领队)
|
||||
**Issue**: https://git.1814.love/wx/HL/issues/8555 + https://git.1814.love/wx/HL/issues/8626
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
管理后台「财务 → 往来账」需要一本**司导往来账**:以「带团服务人员(司导)」为单位,把该人的**报账单**(多退少补结算净额)和**预支单**(借款挂账)合并,现算出他当前与公司的净往来余额(谁欠谁、欠多少),并能下钻看每一笔单据。
|
||||
|
||||
此前往来账只有供应商一本(`fin_statement_entry`),且只接通应付/预付源;司导/员工的报账、预支走的是另一套表(`fin_reimburse` / `fin_advance`),没有按人聚合的账页。本期新增两个只读接口补齐,**纯查询聚合、零 DDL、不改任何既有账表**。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| `GET /admin/finance/statements/guide-ledger/page` | **新增**:司导往来账账页分页(按人聚合) |
|
||||
| `GET /admin/finance/statements/guide-ledger/entries` | **新增**:司导往来明细分页(报账+预支合并,按日期倒序) |
|
||||
| 出参 `role` 取值集合 | 司导角色快照,取值 ∈ `GUIDE/DRIVER/PHOTOGRAPHER/LEADER`(带团服务人员) |
|
||||
| 数据库表 | 零 DDL |
|
||||
|
||||
> 这两个接口是**首次上线**,前端无既有调用,按新对接处理即可。
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|---|---|---|
|
||||
| `/admin/finance/statements/guide-ledger/page` | GET | 账页:每个司导一行,聚合其报账净额 + 预支挂账,现算净往来余额 |
|
||||
| `/admin/finance/statements/guide-ledger/entries` | GET | 明细:某个司导名下的报账单+预支单逐笔列出,按单据日期倒序 |
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 `GET /page`(`GuideLedgerPageReqVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `pageNo` | int | ✅ | 页码,从 1 开始 |
|
||||
| `pageSize` | int | ✅ | 每页条数 |
|
||||
| `keyword` | string | ❌ | 司导姓名(模糊,匹配报账人/收款人姓名快照);空=不限 |
|
||||
| `onlyOutstanding` | boolean | ❌ | true=只看有往来的司导(净往来≠0 或有未结清单据);默认 false |
|
||||
|
||||
> 账页**暂无 role 过滤入参**(角色只在出参 `role` 体现);如需按角色过滤,前端可本地过滤或后续提需求加参。
|
||||
|
||||
### 4.2 `GET /entries`(`GuideLedgerEntryReqVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `pageNo` | int | ✅ | 页码 |
|
||||
| `pageSize` | int | ✅ | 每页条数 |
|
||||
| `reporterAssignmentId` | long | 三选一 | 报账人人员分配 ID(账页行 `reporterAssignmentId` 原样带回) |
|
||||
| `payeeStaffId` | long | 三选一 | 收款人员工 ID(账页行 `payeeStaffId` 原样带回) |
|
||||
| `reporterName` | string | 三选一 | 司导姓名(账页行 `staffName` 原样带回,精确匹配) |
|
||||
| `bizType` | string | ❌ | 单据类型过滤:`REIMBURSE` 只看报账 / `ADVANCE` 只看预支;空=两者都要 |
|
||||
|
||||
> **司导标识三选一必填**(`reporterAssignmentId` / `payeeStaffId` / `reporterName` 至少填一个,全空 → 596010)。由前端从账页行**原样带回**;匹配规则:ID 精确 或 姓名精确,任一命中即归属该司导。
|
||||
|
||||
---
|
||||
|
||||
## 五、接口出参
|
||||
|
||||
### 5.1 账页行(`GuideLedgerRowRespVO`,`data.records[]`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `reporterAssignmentId` | long(string) | 报账人人员分配 ID 快照(明细下钻回传用;可空) |
|
||||
| `payeeStaffId` | long(string) | 收款人员工 ID 快照(预支线,明细下钻回传用;可空) |
|
||||
| `staffName` | string | 司导姓名(聚合键) |
|
||||
| `role` | string | 角色快照(`GUIDE` 导游 / `DRIVER` 司机 / `PHOTOGRAPHER` 摄影 / `LEADER` 领队;同一人多角色取任一非空;纯预支无报账可空) |
|
||||
| `reimburseReceivableTotal` | number | 报账应收合计(RECEIVABLE 方向净额合计,司导欠公司) |
|
||||
| `reimbursePayableTotal` | number | 报账应付合计(PAYABLE 方向净额绝对值合计,公司欠司导) |
|
||||
| `advanceTotal` | number | 预支挂账合计(APPROVED/PAID 预支金额合计,多退少补待核单轧差) |
|
||||
| `netBalance` | number | **净往来余额(正=司导欠公司 / 负=公司欠司导 / 0=两清)** |
|
||||
| `outstandingCount` | int | 未结清单据数(报账未两清终态单数 + 预支在途单数) |
|
||||
|
||||
### 5.2 明细行(`GuideLedgerEntryRespVO`,`data.records[]`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | long(string) | 单据 ID(REIMBURSE=reimburse_id / ADVANCE=advance_id) |
|
||||
| `bizType` | string | 单据类型:`REIMBURSE` 报账 / `ADVANCE` 预支 |
|
||||
| `bizNo` | string | 单号(报账单号 BZ- / 预支单号 YZ-) |
|
||||
| `orderNo` | string | 团号(订单号快照;可空) |
|
||||
| `amount` | number | 金额(报账=结算金额 \|净额\|,预支=预支金额,恒正) |
|
||||
| `direction` | string | 方向:`PAYABLE` 公司欠司导 / `RECEIVABLE` 司导欠公司 / `BALANCED` 两清(预支恒 RECEIVABLE) |
|
||||
| `status` | string | 单据状态(报账 PENDING/APPROVED/PARTIAL_RECEIVED/PAID/RECEIVED/CLOSED;预支 APPROVED/PAID) |
|
||||
| `bizDate` | datetime | 单据日期(推送生成时间) |
|
||||
|
||||
> 分页结构:`data.records[]` + `data.total` + `data.page` + `data.pageSize`(PageResult,**注意是 `records` 不是 `list`**)。
|
||||
> 长整型 ID(`reporterAssignmentId`/`payeeStaffId`/`id`)JSON 序列化为字符串,前端按字符串处理防精度丢失。
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### role 司导角色(快照值,非字典)
|
||||
`GUIDE` 导游 / `DRIVER` 司机 / `PHOTOGRAPHER` 摄影师 / `LEADER` 领队
|
||||
> 口径:带团服务人员,**均非内部员工**;其余角色(含内部员工)不进司导往来账。员工借款 fin_staff_loan 不纳入。
|
||||
|
||||
### bizType 单据类型
|
||||
`REIMBURSE` 报账 / `ADVANCE` 预支
|
||||
|
||||
### direction 方向
|
||||
`PAYABLE` 公司欠司导 / `RECEIVABLE` 司导欠公司 / `BALANCED` 两清
|
||||
|
||||
### status 单据状态(状态机枚举,各源不同)
|
||||
- 报账:`PENDING` 待复核 / `APPROVED` 已批准 / `PARTIAL_RECEIVED` 部分回款 / `PAID` 已付讫 / `RECEIVED` 已回款 / `CLOSED` 已两清(`RETURNED` 已退回不计入往来)
|
||||
- 预支:`APPROVED` 已批准 / `PAID` 已付款
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 码 | 含义 |
|
||||
|---|---|
|
||||
| 596010 | 司导标识缺失(entries 三选一全空) |
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 账页(典型)
|
||||
|
||||
```
|
||||
GET /admin/finance/statements/guide-ledger/page?pageNo=1&pageSize=10
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"reporterAssignmentId": null,
|
||||
"payeeStaffId": "2100747615736897537",
|
||||
"staffName": "刘大山",
|
||||
"role": null,
|
||||
"reimburseReceivableTotal": 0,
|
||||
"reimbursePayableTotal": 0,
|
||||
"advanceTotal": 260.00,
|
||||
"netBalance": 260.00,
|
||||
"outstandingCount": 1
|
||||
}
|
||||
],
|
||||
"total": 1, "page": 1, "pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 明细分页(按收款人 ID 下钻)
|
||||
|
||||
```
|
||||
GET /admin/finance/statements/guide-ledger/entries?pageNo=1&pageSize=10&payeeStaffId=2100747615736897537
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"records": [
|
||||
{ "id": "2201...", "bizType": "ADVANCE", "bizNo": "YZ-202609290001",
|
||||
"orderNo": null, "amount": 260.0, "direction": "RECEIVABLE",
|
||||
"status": "APPROVED", "bizDate": "2026-09-29T10:00:00" }
|
||||
],
|
||||
"total": 1, "page": 1, "pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(entries 三选一全空 → 596010)
|
||||
|
||||
```
|
||||
GET /admin/finance/statements/guide-ledger/entries?pageNo=1&pageSize=10
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 596010, "message": "司导标识缺失" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
- **纯查询聚合**:两接口只读,不写任何表、不改 fin_statement_entry、不接上游流水钩子;净往来为现算不落列。
|
||||
- **只算生效单**:报账单排除 PENDING(未批准不生效)与 RETURNED(已退回,由 version_no+1 新单承接防双算);预支算 APPROVED/PAID。
|
||||
- **聚合键=姓名快照**:reporterAssignmentId(order 域人员分配 ID)与 payeeStaffId(员工 ID)分属两套 ID 空间无法 join,故按报账人/收款人姓名快照聚合;姓名缺失时退化按 ID 单列。
|
||||
- **净额方向**:netBalance 正=司导欠公司、负=公司欠司导、0=两清。报账 PAYABLE 方向(公司欠司导)取净额绝对值计入 reimbursePayableTotal。
|
||||
- **不含员工借款**:司导/摄影/领队均非内部员工,fin_staff_loan 不纳入。
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
新增接口,无修改前版本。
|
||||
|
||||
| 维度 | 说明 |
|
||||
|---|---|
|
||||
| 接口 | 首次上线,前端无既有调用 |
|
||||
| role 取值 | 上线即为 4 角色(GUIDE/DRIVER/PHOTOGRAPHER/LEADER),无历史 2 角色版本对外暴露 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
- 后端:新增两个只读端点,零 DDL、零既有逻辑改动;回滚=下线两接口(前端无依赖)。
|
||||
- 前端:纯新对接,无回归面。
|
||||
- 数据库:无变更。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
- 分页结构是 `data.records[]`(PageResult),**不是 `list`**,前端取数组时注意。
|
||||
- 长整型 ID 序列化为字符串,按字符串处理。
|
||||
- entries 下钻时,账页行的 `reporterAssignmentId`/`payeeStaffId`/`staffName` **原样带回**即可(三选一,无需自己拼)。
|
||||
- `role` 可能为 null(纯预支无报账的司导),前端渲染时对 null 做兜底(显示「-」或留空),不要假设非空。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- 账页初版 PR:https://git.1814.love/wx/HL/pulls/8574
|
||||
- 口径扩展 PR:https://git.1814.love/wx/HL/pulls/8627
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8555 、https://git.1814.love/wx/HL/issues/8626
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- 后端 / 财务域:yst(腰苏图)
|
||||
@@ -0,0 +1,432 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8629"
|
||||
title: "出行人/大交通批次单条新增:幂等键补身份段,同订单连续录入不同对象不再被误拦"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "hl-order-service-v3 dev-v3 提交 91a52ba46c;两个端点的 @Idempotent 幂等键从「仅订单 ID」改为「订单 ID + 出行人/批次身份摘要」,请求体与响应体结构均未变。测试服验证:同订单并发提交两个不同出行人(间隔 150ms)均返回 200 各自创建成功;同订单并发提交两次完全相同的出行人(间隔 150ms)后一条返回 code=100502。;前端实证:hl-admin 两表单零幂等 workaround,不持有不拼接幂等键,结构零变化判 not_required(hl-admin sync-log 2026-10-01)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 出行人/大交通批次单条新增:幂等键补身份段,同订单连续录入不同对象不再被误拦
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: [#8637](https://git.1814.love:8443/wx/HL/pulls/8637)
|
||||
> **Issue**: [#8629](https://git.1814.love:8443/wx/HL/issues/8629)
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台订单详情页「出行人」「大交通」两个单条新增表单
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**改前**:`POST /v3/admin/order/{id}/traveler/add` 与 `POST /v3/admin/order/{id}/transport-plan/add` 的幂等键只拼订单 ID(`#id`),3 秒窗口内**同订单任意两次提交**都会被判定为重复请求而拦截——哪怕两次提交的是完全不同的两个人、或完全不同的两个大交通批次。定制师连续为一家人逐个录入出行人、或逐个补录到达/返程批次时,第二个请求大概率落在 3 秒窗口内,被误拦为「同一出行人/批次刚已提交,请勿重复提交」,且该提示恒为假(首次请求早已成功结束,不是「处理中」)。
|
||||
|
||||
**改后**:幂等键改为「订单 ID + 该对象的身份摘要」(出行人:姓名+证件号+出生日期+手机号;大交通批次:方向+交通类型+车次/航班号+出发时间+自驾时段+关联出行人集合,排序后取 SHA-256)。**身份任一字段不同即视为不同对象,两次提交各自成功,前端无需人为在两次提交间插入延时**;只有身份完全相同的重复提交才会在 3 秒窗口内被拦。请求体、响应体结构均未变,也未新增/删除任何字段。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 单个出行人新增 | POST | `/v3/admin/order/{id}/traveler/add` | 幂等键收窄 | 幂等键加入出行人身份摘要,不同人不再互相拦截 |
|
||||
| 2 | 大交通批次新增 | POST | `/v3/admin/order/{id}/transport-plan/add` | 幂等键收窄 | 幂等键加入批次身份摘要,不同批次不再互相拦截 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 单个出行人新增 `POST /v3/admin/order/{id}/traveler/add`
|
||||
|
||||
**VO**: `TravelerCreateReqVO → TravelerVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台订单详情页「出行人」页签,定制师为已创建的订单逐个补录出行人档案时调用;与批量编辑接口(`/traveler/batch-edit`)并列存在,用于单个补录/追加场景,例如客户临时增加一名随行儿童。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 订单 ID |
|
||||
| name | Body | String | - | - | 姓名(可后填) |
|
||||
| gender | Body | String | - | 1=男/2=女/0=未知 | 性别 |
|
||||
| birthday | Body | LocalDate | ✅ | 不得晚于今天 | 出生日期,后端按年龄分段自动派生 `travelerType` |
|
||||
| idType | Body | String | - | 取值见数据字典 `id_card_type` | 证件类型 |
|
||||
| idNo | Body | String | - | - | 证件号(明文传,DB 加密) |
|
||||
| nationality | Body | String | - | - | 国籍(默认中国) |
|
||||
| race | Body | String | - | - | 民族(默认汉族) |
|
||||
| phone | Body | String | - | - | 出行人手机(明文传,DB 加密) |
|
||||
| emergencyContact | Body | String | - | - | 紧急联系人姓名 |
|
||||
| emergencyPhone | Body | String | - | - | 紧急联系人电话(明文传) |
|
||||
| roomGroupNo | Body | Integer | - | - | 同住分组号 |
|
||||
|
||||
#### 出参 `Result<TravelerVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 出行人 ID |
|
||||
| orderId | Long | 订单 ID |
|
||||
| teamNo | String | 团号 |
|
||||
| travelerType | String | 出行人类型:ADULT/CHILD/YOUNG_CHILD/BABY |
|
||||
| travelerTypeName | String | 出行人类型中文名(字典优先,枚举 label 兜底) |
|
||||
| name | String | 姓名 |
|
||||
| gender | String | 性别:1=男/2=女/0=未知 |
|
||||
| birthday | LocalDate | 出生日期 |
|
||||
| idType | String | 证件类型 |
|
||||
| idTypeName | String | 证件类型中文名(字典优先,枚举 label 兜底) |
|
||||
| idCardMasked | String | 证件号脱敏值(前3后4,中间星号,#2894) |
|
||||
| idProvinceCode | String | 身份证省级行政区代码;非大陆证件或无法识别为 null |
|
||||
| idProvinceName | String | 身份证省级行政区名称 |
|
||||
| nativePlace | String | 所属地(省+地级市,#5642);非大陆证件或未命中为 null |
|
||||
| nationality | String | 国籍 |
|
||||
| race | String | 民族 |
|
||||
| phoneMasked | String | 出行人手机脱敏值 |
|
||||
| emergencyContact | String | 紧急联系人姓名 |
|
||||
| emergencyPhoneMasked | String | 紧急联系人电话脱敏值 |
|
||||
| roomGroupNo | Integer | 同住分组号 |
|
||||
| profileStatus | String | 资料完善状态:PENDING/COMPLETED |
|
||||
| transportPlanIds | List\<Long\> | 关联大交通批次 ID 列表 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "王小明",
|
||||
"gender": "1",
|
||||
"birthday": "2018-06-20",
|
||||
"idType": "ID_CARD",
|
||||
"idNo": "220103201806201234",
|
||||
"nationality": "中国",
|
||||
"race": "汉族",
|
||||
"phone": "13812342046",
|
||||
"emergencyContact": "王大明",
|
||||
"emergencyPhone": "13988888888",
|
||||
"roomGroupNo": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": 70123456789012,
|
||||
"orderId": 60123456789012,
|
||||
"teamNo": "26-0480",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人",
|
||||
"name": "张三",
|
||||
"gender": "1",
|
||||
"birthday": "1985-08-12",
|
||||
"idType": "ID_CARD",
|
||||
"idTypeName": "身份证",
|
||||
"idCardMasked": "220***********1234",
|
||||
"idProvinceCode": "22",
|
||||
"idProvinceName": "吉林省",
|
||||
"nativePlace": "内蒙古呼伦贝尔市",
|
||||
"nationality": "中国",
|
||||
"race": "汉族",
|
||||
"phoneMasked": "138****2046",
|
||||
"emergencyContact": "李四",
|
||||
"emergencyPhoneMasked": "139****8888",
|
||||
"roomGroupNo": 1,
|
||||
"profileStatus": "COMPLETED",
|
||||
"transportPlanIds": []
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口是单条创建接口,成功路径必返回完整的单个对象,不存在「空列表」场景。`travelerTypeName` / `idTypeName` 的中文名解析是「字典优先,枚举 label 兜底」——字典服务不可用时回落到枚举自带中文 label,不会返回 `null`(该兜底策略是既有行为,非本次改动)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100502,
|
||||
"message": "同一出行人刚已提交,请勿重复提交",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 3 秒幂等窗口内,仅当出行人身份(姓名+证件号+出生日期+手机号)与前一次提交**完全相同**时才会被拦为 `code=100502`;姓名/证件号/出生日期/手机号任一不同即视为不同的人,两次提交各自成功,前端无需人为在两次提交间插入延时。
|
||||
- 幂等身份摘要取 SHA-256(64 位小写 hex),不改变请求体字段本身;前端提交的仍是原始明文字段。
|
||||
- 同订单另有 30 秒 `@Lock4j` 互斥锁(本次未变),用于防止与批量编辑接口(`/traveler/batch-edit`)并发写导致人数计数错乱;锁只管互斥,幂等键只管拦「同一次提交的重放」,两者不可互相替代。
|
||||
- 成功响应 `code` 固定为 **200**(非 0);若前端用 `code === 0` 判断成功,会把这次成功误判为失败。
|
||||
- 未登录 → 网关层拦截,返回 401 信封;`birthday` 缺失 → HTTP 200 + `code: 400`(`@NotNull`/`@PastOrPresent` 校验失败)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 大交通批次新增 `POST /v3/admin/order/{id}/transport-plan/add`
|
||||
|
||||
**VO**: `TransportPlanReqVO → TransportPlanVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台订单详情页「大交通」页签,定制师为订单逐个新增到达/返程批次时调用(每个批次至少关联 1 名出行人);与批量替换接口(`/transport-plan/batch`)并列存在,用于「多批到达/返程」场景下逐批补录。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 订单 ID |
|
||||
| direction | Body | String | ✅ | ARRIVAL / DEPARTURE | 方向 |
|
||||
| transportType | Body | String | ✅ | FLIGHT / TRAIN / SELF_DRIVE | 交通类型 |
|
||||
| transportNo | Body | String | - | SELF_DRIVE 时为空 | 航班号/车次号 |
|
||||
| carrier | Body | String | - | - | 航司/铁路公司 |
|
||||
| departStation | Body | String | - | - | 出发站 |
|
||||
| arriveStation | Body | String | - | - | 到达站 |
|
||||
| departTime | Body | LocalDateTime | - | FLIGHT/TRAIN 必填 | 出发时间 |
|
||||
| arriveTime | Body | LocalDateTime | - | FLIGHT/TRAIN 必填 | 到达时间 |
|
||||
| selfDrivePeriod | Body | String | - | MORNING/AFTERNOON/EVENING,仅 SELF_DRIVE | 自驾时段 |
|
||||
| selfDriveEta | Body | LocalDateTime | - | 仅 SELF_DRIVE 可选 | 自驾预计到达时间 |
|
||||
| travelerIds | Body | List\<Long\> | ✅ | 至少 1 个 | 关联出行人 ID |
|
||||
| pickupRequired | Body | Boolean | - | 新增缺省 true | 是否需要接送 |
|
||||
| pickupRemark | Body | String | - | - | 接送备注 |
|
||||
| remark | Body | String | - | ≤500 字 | 备注 |
|
||||
|
||||
#### 出参 `Result<TransportPlanVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 批次 ID(`Long` 按字符串序列化) |
|
||||
| orderId | String | 订单 ID(`Long` 按字符串序列化) |
|
||||
| teamNo | String | 团号 |
|
||||
| direction | String | 方向:ARRIVAL/DEPARTURE |
|
||||
| directionLabel | String | 方向中文标签 |
|
||||
| mode | String | 模式:TOGETHER/SEPARATE;单条新增固定为 TOGETHER |
|
||||
| modeLabel | String | 模式中文标签 |
|
||||
| transportType | String | 交通类型:FLIGHT/TRAIN/SELF_DRIVE |
|
||||
| transportTypeLabel | String | 交通类型中文标签 |
|
||||
| transportNo | String | 航班号/车次号 |
|
||||
| carrier | String | 航司/铁路公司 |
|
||||
| departStation | String | 出发站 |
|
||||
| arriveStation | String | 到达站 |
|
||||
| departTime | LocalDateTime | 出发时间 |
|
||||
| arriveTime | LocalDateTime | 到达时间 |
|
||||
| selfDrivePeriod | String | 自驾时段 |
|
||||
| selfDrivePeriodLabel | String | 自驾时段中文标签 |
|
||||
| selfDriveEta | LocalDateTime | 自驾预计到达时间 |
|
||||
| pickupRequired | Boolean | 是否需要接送 |
|
||||
| pickupRemark | String | 接送备注 |
|
||||
| travelers | List\<TravelerRef\> | 关联出行人(id 字符串化 + name + travelerType + travelerTypeName) |
|
||||
| remark | String | 备注 |
|
||||
| creatorType | String | 创建来源:USER/ADMIN |
|
||||
| creatorTypeName | String | 创建来源中文名 |
|
||||
| createTime | LocalDateTime | 创建时间 |
|
||||
| updateTime | LocalDateTime | 更新时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"direction": "ARRIVAL",
|
||||
"transportType": "FLIGHT",
|
||||
"transportNo": "CA1234",
|
||||
"carrier": "中国国际航空",
|
||||
"departStation": "北京首都T3",
|
||||
"arriveStation": "长春龙嘉",
|
||||
"departTime": "2026-06-01T08:30:00",
|
||||
"arriveTime": "2026-06-01T10:15:00",
|
||||
"travelerIds": [70123456789012, 70123456789013],
|
||||
"pickupRequired": true,
|
||||
"pickupRemark": "需在 T3 出口举牌接机",
|
||||
"remark": "需要接机举牌"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "80012345",
|
||||
"orderId": "60123456789012",
|
||||
"teamNo": "26-0480",
|
||||
"direction": "ARRIVAL",
|
||||
"directionLabel": "到达",
|
||||
"mode": "TOGETHER",
|
||||
"modeLabel": "一起到达",
|
||||
"transportType": "FLIGHT",
|
||||
"transportTypeLabel": "飞机",
|
||||
"transportNo": "CA1234",
|
||||
"carrier": "中国国际航空",
|
||||
"departStation": "北京首都T3",
|
||||
"arriveStation": "长春龙嘉",
|
||||
"departTime": "2026-06-01T08:30:00",
|
||||
"arriveTime": "2026-06-01T10:15:00",
|
||||
"selfDrivePeriod": null,
|
||||
"selfDrivePeriodLabel": null,
|
||||
"selfDriveEta": null,
|
||||
"pickupRequired": true,
|
||||
"pickupRemark": "需在 T3 出口举牌接机",
|
||||
"travelers": [
|
||||
{ "id": "70123456789012", "name": "张三", "travelerType": "ADULT", "travelerTypeName": "成人" }
|
||||
],
|
||||
"remark": "需要接机举牌",
|
||||
"creatorType": "ADMIN",
|
||||
"creatorTypeName": "定制师代录",
|
||||
"createTime": "2026-07-01T10:00:00",
|
||||
"updateTime": "2026-07-01T10:30:00"
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口是单条创建接口,成功路径必返回完整的单个对象,不存在「空列表」场景。`travelers[].travelerTypeName` 同样是「字典优先,枚举 label 兜底」,字典服务不可用时回落枚举 label,不返回 `null`(既有行为,非本次改动)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100502,
|
||||
"message": "同一大交通批次刚已提交,请勿重复提交",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 3 秒幂等窗口内,仅当批次身份(方向+交通类型+车次/航班号+出发时间+自驾时段+关联出行人集合)与前一次提交**完全相同**时才会被拦为 `code=100502`;任一字段不同即视为不同批次,两次提交各自成功。
|
||||
- `travelerIds` 参与身份摘要前会先排序去重:前端把同一批 ID 换个顺序重新提交,仍会命中同一幂等键(业务上视为同一次提交的重放)。
|
||||
- SELF_DRIVE 场景 `transportNo` 为空、`departTime` 也非必填,身份摘要因此额外纳入 `transportType`/`selfDrivePeriod`/`travelerIds`,避免两批不同自驾到达被误判为同一批。
|
||||
- 同订单另有 30 秒 `@Lock4j` 互斥锁(本次未变),用于防止 admin 多端并发新增同方向 plan;锁与幂等键职责不同、不可互相替代。
|
||||
- 成功响应 `code` 固定为 **200**(非 0);若前端用 `code === 0` 判断成功,会把这次成功误判为失败。
|
||||
- 未登录 → 网关层拦截,返回 401 信封;`direction`/`transportType` 缺失或 `travelerIds` 为空 → HTTP 200 + `code: 400`。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | 结果 |
|
||||
|------|------|
|
||||
| ✅ 同订单连续新增两个不同出行人(`name`/`idNo`/`birthday`/`phone` 任一不同) | 两次请求均返回 200,各自创建成功,无需人为延时 |
|
||||
| ✅ 同订单连续新增两个不同大交通批次(身份字段任一不同) | 两次请求均返回 200,各自创建成功 |
|
||||
| ❌ 3 秒内重复提交身份字段完全相同的出行人 | 第二次返回 `code=100502`,零写入 |
|
||||
| ❌ 3 秒内重复提交身份字段完全相同的大交通批次 | 第二次返回 `code=100502`,零写入 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
两个接口均为单条创建接口,不涉及状态切换;调用前无需额外前置动作。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 场景 | 写入行为 |
|
||||
|------|----------|
|
||||
| 同订单连续提交两个不同出行人 | 各自插入 1 行 `order_traveler`;改动前,第二个请求会被幂等键拦截,零写入 |
|
||||
| 同订单 3 秒内重复提交同一出行人(身份完全相同) | 只有首次请求落 1 行;重复请求被拦截,零写入(改动前后一致) |
|
||||
| 同订单连续提交两个不同大交通批次 | 各自插入 1 行 `order_transport_plan` + N 行桥接表 `order_transport_plan_traveler`;改动前,第二个请求会被拦截,零写入 |
|
||||
| 同订单 3 秒内重复提交同一批次(身份完全相同) | 只有首次请求写入;重复请求被拦截,零写入(改动前后一致) |
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)
|
||||
- 校验失败(`birthday` 缺失、`direction`/`transportType` 非法、`travelerIds` 为空等) → HTTP 200 + `code: 400`
|
||||
- 3 秒窗口内同身份重复提交 → HTTP 200 + `code: 100502`,零写入
|
||||
- 下游字典服务(`travelerTypeName`/`idTypeName`/`transportTypeLabel` 等中文名解析)降级 → 回落枚举自带中文 label,不返回 null、不阻断主流程(既有行为,非本次改动)
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| (无) | 请求体、响应体字段结构均未变 | 同左 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 幂等键组成 | `#id`(仅订单 ID) | `#id + ':' + 身份摘要`(订单 ID + SHA-256 身份摘要) |
|
||||
| 同订单连续提交两个不同对象(3 秒内) | 第二次被拦为 `code=100502`,零写入,且提示「处理中」恒为假 | 两次均成功,各自写入 1 条记录 |
|
||||
| 3 秒内重复提交同一对象(身份完全相同) | 拦截 | 拦截(无变化) |
|
||||
| 错误提示文案 | 「同一出行人刚已提交,请勿重复提交」/「同一大交通批次刚已提交,请勿重复提交」(改动前后文案一致,仅拦截范围变窄) | 同左 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否——请求体、响应体字段结构未变,只是原本被误拦的场景(连续提交不同对象)现在能成功,属于放宽约束
|
||||
- **前端是否必须同步上线**: 否——若前端此前为规避误拦而在两次提交之间人为加了延时或做了排队逻辑,那段代码可以撤掉,但不撤也不会出错
|
||||
- **前端 workaround 清理点**: 若前端曾针对「连续录入第二个出行人/批次报 100502」做过特殊重试或提示遮蔽逻辑,现在可以移除;不清理也不影响功能,只是不再需要
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `POST /v3/admin/order/{id}/traveler/add`、`POST /v3/admin/order/{id}/transport-plan/add` 两个端点的幂等拦截范围
|
||||
- **零影响**:
|
||||
- 出行人批量编辑接口 `/traveler/batch-edit`(幂等键仍是 `#id`,未变)
|
||||
- 大交通批次编辑/软删/批量替换接口 `/transport-plan/{planId}/edit`、`/transport-plan/{planId}/delete`、`/transport-plan/batch`(幂等键均未变)
|
||||
- 出行人软删、信息校验、补全出行信息等其余出行人/大交通端点
|
||||
- 请求体、响应体字段结构(未新增/删除/改类型任何字段)
|
||||
- 同订单 30 秒 `@Lock4j` 互斥锁行为
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服(hl-order-service-v3,dev-v3 提交 `91a52ba46c`)实测:
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/traveler/add 同订单并发提交两个不同出行人(间隔 150ms) → 均 200,各自创建成功 ✓
|
||||
POST /v3/admin/order/{id}/traveler/add 同订单并发提交两次完全相同的出行人(间隔 150ms) → 先 200,后一条 code=100502 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8629](https://git.1814.love:8443/wx/HL/issues/8629)
|
||||
- 关联 PR: [wx/HL#8637](https://git.1814.love:8443/wx/HL/pulls/8637)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8629](https://git.1814.love:8443/wx/HL/issues/8629)
|
||||
- **PR**: [#8637](https://git.1814.love:8443/wx/HL/pulls/8637)
|
||||
- **Merge commit**: [91a52ba46c](https://git.1814.love:8443/wx/HL/commit/91a52ba46c)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8630"
|
||||
title: "团期活跃子订单清零后,自动复位团级需求确认与整团用车需求"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "hl-order-service-v3 dev-v3 合入提交 8e00cc98bc(PR #8640,同 PR 还含 #8631 的两处纯 javadoc 订正,与本单契约无关,未在本文档中提及)。本次改动不涉及任何接口的请求/响应结构变化,只影响既有字段在特定场景下的取值。;前端实证:StatusLogsPanel 通用 eventTypeName 渲染天然覆盖(复用既有事件类型),无新枚举/新字段,纯取值变化判 not_required(hl-admin sync-log 2026-10-01)"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期活跃子订单清零后,自动复位团级需求确认与整团用车需求
|
||||
|
||||
> **存放目录**: 二期(order-v3/fleet)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: [#8640](https://git.1814.love:8443/wx/HL/pulls/8640)
|
||||
> **Issue**: [#8630](https://git.1814.love:8443/wx/HL/issues/8630)
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 团期详情页「需求确认」状态、整团用车需求状态;不涉及任何请求/响应字段结构变化
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**改前**:一个团期整体确认需求(`requirementConfirmed=true`)、且整团用车需求也已确认(`CONFIRMED`)之后,如果该团期名下的子订单被逐个取消,直到**最后一个活跃子订单也被取消**,系统没有任何收尾动作——`requirementConfirmed` 停留在 `true`,整团用车需求状态停留在 `CONFIRMED`。此时团期详情页、车务待办侧仍显示「需求已确认」「用车已确认」,而这个团实际上已经一户不剩,且这个不一致**不报错、不告警**,只能靠人工发现。
|
||||
|
||||
**改后**:当一个团期的活跃子订单数(`order_status != CANCELLED` 的子订单条数)由非零变为零时,系统自动做两件事:
|
||||
1. 若整团用车需求当前处于 `CONFIRMED`,自动退回 `DRAFT`(不是 `PENDING_RECONFIRM`);
|
||||
2. 若团级 `requirementConfirmed` 当前为 `true`,自动清为 `false`。
|
||||
|
||||
只要这两项里至少有一项真的发生了状态变化,就会在该团期的时间线写入一条 `BATCH_REQUIREMENT_REOPENED`(需求重开待确认)事件,`extra` 中携带 `trigger: "ALL_SUB_ORDERS_CANCELLED"`、`orderId`(触发收尾的最后一个取消子订单 ID)、`requirementConfirmedCleared`(布尔)、`groupVehicleRequirementWithdrawn`(布尔)。两项复位互相独立、各自按条件写,天然幂等;两项都无需变化时(例如本来就是 `false`/`DRAFT`)不写时间线、不产生噪声。
|
||||
|
||||
本次改动**不涉及任何请求体或响应体的字段增删/改类型**,纯粹是既有字段在「团期归零」这一新增场景下会被系统自动改写取值。
|
||||
|
||||
---
|
||||
|
||||
## 二、影响的字段与读取入口
|
||||
|
||||
以下字段的**读取路径未变**,本次改动只影响它们在「团期活跃子订单清零」这一时刻之后的取值:
|
||||
|
||||
| 字段 | 归属接口(示例) | 改前在团期归零后的取值 | 改后 |
|
||||
|------|------|------|------|
|
||||
| `requirementConfirmed` | `GET /v3/admin/order/group-batch/{groupBatchId}`(`GroupBatchDetailRespVO`)及房务看板系列 VO | 停留在归零前的最后取值(可能仍是 `true`) | 若归零前为 `true`,归零后自动变为 `false` |
|
||||
| 整团用车需求 `status` | 整团用车需求相关读端点(`GroupVehicleRequirementRespVO.status`) | 停留在归零前的最后取值(可能仍是 `CONFIRMED`) | 若归零前为 `CONFIRMED`,归零后自动变为 `DRAFT` |
|
||||
| 团期时间线 | 团期时间线读端点 | 归零无任何留痕 | 新增一条 `BATCH_REQUIREMENT_REOPENED` 事件(仅当至少一项真的被复位时才写) |
|
||||
|
||||
---
|
||||
|
||||
## 三、精确触发条件
|
||||
|
||||
- **"活跃子订单"的判据** = `order_status != CANCELLED`(含 `PENDING_PAY`、`COMPLETED` 等非取消状态均计入活跃;与既有人数计数、归团回填、对账口径一致)。**不是**看板上「免闸户不计」的统计口径——房务免闸的户在用车这一侧仍可能要车,因此不按闸门维度扣减分母。
|
||||
- **收尾时点** = 该团期的活跃子订单数由非零变为零的那一刻(子订单取消事务提交之后)。覆盖以下所有取消入口:admin 出行前取消、C 端取消、退团审批、流团逐户取消、通用 `transition()` 的 CANCEL 分支、超时自动取消——这些入口最终都汇流到同一个内部取消事件,因此逐个入口都会触发本收尾逻辑。
|
||||
- **不覆盖**的取消路径:
|
||||
- `TERMINATE`(出行中终止行程 → `COMPLETED`)是与 `CANCEL`(→ `CANCELLED`)完全不同的状态路径,不会触发本收尾——出行中终止行程不代表这个团没有人,语义上也不应该清需求确认。
|
||||
- 直接修改数据库、绕过应用层的取消不会触发(无代码路径可挂载)。
|
||||
- **用车需求只在 `CONFIRMED` 这一档被自动退回**:`DRAFT`/`PENDING_RECONFIRM` 本来就不是已确认,无需处理;`DISPATCHED`(已发车务)/`DONE`(配车完成)**不会**被自动撤回——车务可能已经接单甚至配完车,自动撤回等于单方面掀掉车务在办的工作,这属于另一个业务决策,本次不做;`CANCELLED` 是流团终态,不会走到本路径。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为(刻意不做的部分)
|
||||
|
||||
- **`batchStatus`(团期阶段)本次不变**:一个活跃子订单数归零的团期,其 `batchStatus` 可以继续停留在任意阶段(例如 `RESOURCE_PREPARING`「资源准备中」),不会被自动置为 `CANCELLED` 或退回 `RECRUITING`——自动改阶段涉及流团审批合规性判断,留给后续工单单独定案。前端据此判断"团是否还有效"时,**不能只看 `batchStatus`**,需要结合活跃子订单数或 `requirementConfirmed`/用车需求状态的复位来综合判断。
|
||||
- **`DISPATCHED`/`DONE` 的用车需求不会被回退**:见上节"三、精确触发条件"。
|
||||
- **房务就绪标记(`hotel_ready`)本次不动**:本收尾只处理 `requirementConfirmed` 与整团用车需求两项,房务侧的就绪标记不在本次收尾范围内,两者目前不对称——这是已知缺口,不在本单范围内一并解决。
|
||||
- **不做历史数据回填**:已经处于"团期归零但需求确认/用车需求未复位"这种旧脏数据状态的历史团期,本次改动不会自动纠正,只对本次改动上线之后新发生的"归零"事件生效。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
整团用车需求状态 `GroupVehicleRequirementStatus`(本次改动涉及的部分状态,完整枚举 6 值):
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|------|------|------|
|
||||
| DRAFT | 草稿 | 本次改动的复位目标 |
|
||||
| CONFIRMED | 已确认 | 本次改动的复位起点(仅此档会被自动退回) |
|
||||
| PENDING_RECONFIRM | 待重新确认 | 不受本次改动影响 |
|
||||
| DISPATCHED | 已发车务 | 不受本次改动影响(明确不回退) |
|
||||
| DONE | 配车完成 | 不受本次改动影响(明确不回退) |
|
||||
| CANCELLED | 已取消 | 不受本次改动影响 |
|
||||
|
||||
团期时间线事件类型(本次涉及):
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|------|------|------|
|
||||
| BATCH_REQUIREMENT_REOPENED | 需求重开待确认 | 复用既有事件类型(此前用于"定制师在整团确认后自行改需求"场景),本次新增一种触发来源;两种来源在 `extra.trigger` 字段区分,本次新增值为 `ALL_SUB_ORDERS_CANCELLED` |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 团期活跃子订单数由非零变为零,此前 `requirementConfirmed=true` | 停留 `true`,无提示 | 自动变为 `false` |
|
||||
| 团期活跃子订单数由非零变为零,此前整团用车需求 `CONFIRMED` | 停留 `CONFIRMED`,无提示 | 自动退回 `DRAFT` |
|
||||
| 团期活跃子订单数由非零变为零,此前两项均已是"未确认"状态 | 无变化 | 无变化,不写时间线(幂等,无噪声) |
|
||||
| 团期时间线 | 归零无任何记录 | 至少一项被复位时,新增一条 `BATCH_REQUIREMENT_REOPENED` 事件,`extra.trigger=ALL_SUB_ORDERS_CANCELLED` |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否——字段名称、类型、接口路径均未变,只是取值在新场景下会被后端自动改写
|
||||
- **前端是否必须同步上线**: 视前端现有逻辑而定——若前端曾假设"一旦确认过就不会自动变回未确认"并据此做过缓存/跳过重复请求之类的优化,需要重新核对该假设在"团期归零"场景下不再成立
|
||||
- **需要前端注意的读取口径变化**: `requirementConfirmed`、整团用车需求 `status` 在团期活跃子订单归零后可能被系统自动改写,不再只由人工操作(确认/打回)改变;`batchStatus` 不受此次自动复位联动,读取时不能用 `batchStatus` 代替对这两个字段的直接读取
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 团期活跃子订单数归零这一时刻,`requirementConfirmed` 与整团用车需求 `CONFIRMED` 状态的自动复位
|
||||
- **零影响**:
|
||||
- 团期阶段 `batchStatus` 的取值与流转规则(见"六、边界行为")
|
||||
- 房务就绪标记 `hotel_ready`
|
||||
- 整团用车需求 `DISPATCHED`/`DONE`/`PENDING_RECONFIRM`/`CANCELLED` 四档的自动流转规则
|
||||
- 所有接口的请求体、响应体字段结构(本次零新增、零删除、零改类型)
|
||||
- 团期归零之前已经存在的历史脏数据(不做回填)
|
||||
- 受控重配窗口相关的 `BATCH_VEHICLE_REQUIREMENT_REOPENED` 事件(另一独立事件类型,与本次复用的 `BATCH_REQUIREMENT_REOPENED` 不是同一个)
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8630](https://git.1814.love:8443/wx/HL/issues/8630)
|
||||
- 关联 PR: [wx/HL#8640](https://git.1814.love:8443/wx/HL/pulls/8640)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8630](https://git.1814.love:8443/wx/HL/issues/8630)
|
||||
- **PR**: [#8640](https://git.1814.love:8443/wx/HL/pulls/8640)
|
||||
- **Merge commit**: [8e00cc98bc](https://git.1814.love:8443/wx/HL/commit/8e00cc98bc)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8632"
|
||||
title: "团期核单新增 8 个分类科目明细只读端点(住宿/景区门票/车辆/导游/摄影/餐食/其他支出/其他收入)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "d5a372be4a2079adf9c1ca91a68b992f92918507"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-09-30"
|
||||
status_note: "团期核单弹窗补 8 个分类明细 tab 的只读端点,命名语义化(核心订单 step1/2/3 改 hotels/activities/vehicles,其余沿用核心订单既有语义名),数据来自团维度 order_batch_audit_item 按 category 过滤。行复用核单录入面板的 ItemVO(整团维度,无 orderId/orderNo/customerName 归属字段),外层带 auditStatus 供前端渲 NOT_STARTED 空态。后端已部署测试服并行为级验证:gid 不存在返 589500,真实未返团团期返 NOT_STARTED + 空 items(五字段齐全、categoryText 中文正确)。前端可按 §5 字段表接入各分类 tab(与核心订单核单同一套渲染思路,路径逐字对齐核心订单命名)。;前端已交付:step2 只读 8 tab 区(GroupCategoryItems 一 tab 一接口懒加载+缓存,空态判 auditStatus=NOT_STARTED),33 例定向全绿"
|
||||
---
|
||||
|
||||
# 团期核单分类科目明细 tab —— 新增接口(管理后台)
|
||||
|
||||
> Issue: https://git.1814.love/wx/HL/issues/8632
|
||||
> PR: https://git.1814.love/wx/HL/pulls/8634
|
||||
> Commit: https://git.1814.love/wx/HL/commit/02572e5daa62432c6cf31393d62eadaafd993066
|
||||
> 负责人:腰苏图
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
团期核单弹窗(一团一核单)此前只有「核单录入」`GET /v3/admin/order/group-batch/{groupBatchId}/audit`(返回全科目平铺 items)和「聚合复核」两个 tab。前端要按「酒店住宿 / 景区门票 / 车辆 / 导游 / 摄影 / 餐食 / 其他支出 / 其他收入」分 tab 展示,需自己按 category 过滤,且与核心订单核单「一 tab 一接口」的对接模式不一致。
|
||||
|
||||
本次新增 **8 个分类明细只读端点**,让团期核单弹窗可以像核心订单核单一样,一个 tab 调一个专用接口。数据全部来自团维度核单科目表(`order_batch_audit_item`),与「核单录入」面板同源。
|
||||
|
||||
关联:#8510 / PR #8546(团期核单详情对齐常规订单核单 PR-1:财务总览 + 客户合并 + 人数口径)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/hotels` | 住宿(HOUSE) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/activities` | 景区门票(ACTIVITY) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/vehicles` | 车辆(VEHICLE) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/guide-fees` | 导游(GUIDE) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/photographer-fees` | 摄影(PHOTO) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/meals` | 餐食(MEAL) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/other-expenses` | 其他支出(OTHER_EXPENSE) |
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/other-incomes` | 其他收入(OTHER_INCOME) |
|
||||
|
||||
8 个端点结构完全一致,只是固定过滤一个 category。无入参字段、无枚举变更、无删除。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **方法/路径**:`GET /v3/admin/order/group-batch/{groupBatchId}/settlement/{分类路径段}`
|
||||
- **鉴权**:管理后台,复用团期核单查看权限 `group-batch:audit:view`(GROUP_BATCH_MANAGER / FINANCE / ADMIN)
|
||||
- **路径段与 category 对应**:hotels→HOUSE、activities→ACTIVITY、vehicles→VEHICLE、guide-fees→GUIDE、photographer-fees→PHOTO、meals→MEAL、other-expenses→OTHER_EXPENSE、other-incomes→OTHER_INCOME
|
||||
- **说明**:返回该团期核单下指定科目的全量科目行(不分页,单类通常几行到二三十行)。命名对齐核心订单核单 tab(核心订单 step1→hotels、step2→activities、step3/vehicles→vehicles,其余语义名沿用)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 入参
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `groupBatchId` | path | Long | 是 | 运营团期 ID |
|
||||
|
||||
无 query / body 参数。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参
|
||||
|
||||
统一返回 `Result<GroupBatchAuditItemsRespVO>`。
|
||||
|
||||
### 5.1 GroupBatchAuditItemsRespVO(外层)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `auditStatus` | String | 核单状态:`NOT_STARTED`(未开始/未返团)/ `DRAFT`(录入中)/ `ALLOCATED`(已核算)/ `CHECKED`(已验团)。前端据此渲空态 |
|
||||
| `batchStatus` | String | 团期状态(GroupBatchStatus,如 RECRUITING/RESOURCE_PREPARING/TRIP_FINISHED/REVIEWING/SETTLED 等) |
|
||||
| `category` | String | 本端点固定的科目大类(见 §3 对应表) |
|
||||
| `categoryText` | String | 科目大类中文(住宿/景区娱乐/车辆/导游/摄影/用餐/其他支出/其他收入) |
|
||||
| `items` | `List<ItemVO>` | 该科目的核单科目行,**整团维度**,默认空数组(不返回 null) |
|
||||
|
||||
### 5.2 ItemVO(科目行,复用核单录入面板结构)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `itemId` | String | 科目行 ID(Long 序列化为字符串,防 JS 精度丢失) |
|
||||
| `category` | String | 科目大类(与本端点固定值一致) |
|
||||
| `itemName` | String | 科目名,如「D2 图嘎营地 蒙古包」「导游·双领队」 |
|
||||
| `dayNo` | Integer | 第几天/第几晚,无日归属为 null |
|
||||
| `unitPrice` | String | 单价(单价型科目,如房每晚房价;金额字符串),总额型为 null |
|
||||
| `totalAmount` | String | 总额(总额型科目,如车/导游/其他收支;金额字符串),单价型为 null |
|
||||
| `allocRule` | String | 分摊口径:`PER_ROOM_NIGHT`(按各户用房数)/ `PER_HEAD_CHECKED`(勾选参加后按人数)/ `PER_VEHICLE_GROUP`(按乘车分组内户数均分)/ `PER_ORDER_AVG`(按户平均) |
|
||||
| `allocGroup` | String | 分摊分组(车科目 BUS / SUV),无分组为 null |
|
||||
| `budgetAmount` | String | 带出源金额(仅供对比,不参与计算;金额字符串) |
|
||||
| `changeReason` | String | 改价原因(科目行本身不存此列,读接口恒为 null) |
|
||||
| `seq` | Integer | 排序 |
|
||||
|
||||
> 说明:`unitPrice` 与 `totalAmount` 互斥——单价型科目(住宿/景娱/餐)有 `unitPrice` 无 `totalAmount`,总额型科目(车辆/导游/摄影/其他收支)反之。`items` 为**整团科目行**,不含逐户归属字段(无 orderId/orderNo/customerName),也不含逐户用量明细。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
- **auditStatus**:`NOT_STARTED` / `DRAFT` / `ALLOCATED` / `CHECKED`
|
||||
- **category**:`HOUSE` / `VEHICLE` / `ACTIVITY` / `MEAL` / `GUIDE` / `PHOTO` / `OTHER_EXPENSE` / `OTHER_INCOME`
|
||||
- **allocRule**:`PER_ROOM_NIGHT` / `PER_HEAD_CHECKED` / `PER_VEHICLE_GROUP` / `PER_ORDER_AVG`
|
||||
|
||||
无新增枚举值(全部复用核单录入既有枚举)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|---|---|
|
||||
| `589500` | 团期不存在(groupBatchId 非法) |
|
||||
| 403 | 无 `group-batch:audit:view` 权限 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型(已返团团期,住宿 tab)
|
||||
|
||||
`GET /v3/admin/order/group-batch/2105074382613413890/settlement/hotels`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"auditStatus": "DRAFT",
|
||||
"batchStatus": "REVIEWING",
|
||||
"category": "HOUSE",
|
||||
"categoryText": "住宿",
|
||||
"items": [
|
||||
{
|
||||
"itemId": "1934567890123456790",
|
||||
"category": "HOUSE",
|
||||
"itemName": "D2 图嘎营地 蒙古包",
|
||||
"dayNo": 2,
|
||||
"unitPrice": "380.00",
|
||||
"totalAmount": null,
|
||||
"allocRule": "PER_ROOM_NIGHT",
|
||||
"allocGroup": null,
|
||||
"budgetAmount": "5320.00",
|
||||
"changeReason": null,
|
||||
"seq": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(未返团团期,空态)
|
||||
|
||||
`GET /v3/admin/order/group-batch/{未返团团期}/settlement/meals`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"auditStatus": "NOT_STARTED",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"category": "MEAL",
|
||||
"categoryText": "用餐",
|
||||
"items": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 异常(团期不存在)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589500,
|
||||
"message": "团期不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **整团维度**:`items` 是整团核单科目行(来自「核单录入」面板同一份数据),**不含逐户归属**(无 orderId/orderNo/customerName),也不含逐户用量明细。前端按 tab 直接渲染即可,无需按户分组。
|
||||
- **生命周期**:住宿/门票等科目行**在团期返团后、首次打开核单面板时才生成**。未返团的团期调任一分类端点返回 `auditStatus=NOT_STARTED` + 空 `items`(见 8.2);已返团首读会自动建 DRAFT(读接口带写副作用,权限判权在先)。
|
||||
- **数据来源**:与「核单录入」`GET .../audit` 的 `items[]` 完全同源,本批端点只是按 category 拆成独立 tab 读口,不改变数据本身。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比(修改类)
|
||||
|
||||
非修改类(纯新增接口),不适用。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚(修改类)
|
||||
|
||||
- **兼容性**:纯新增接口,不影响任何既有接口。
|
||||
- **性能**:单端点一次查询 + 内存按 category 过滤,不分页、无 N+1;不触碰核单试算。
|
||||
- **回滚**:回退 merge commit `02572e5daa` 即可下线 8 个端点;无 DDL、无数据迁移成本。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 8 个端点结构完全一致,前端可封装一个通用的「分类 tab 请求 + 渲染」组件,按路径段切换。
|
||||
2. 渲染空态请看 `auditStatus`(`NOT_STARTED` 时显示「团期未返团,返团后可核单」类提示),而不是看 `items` 是否为空(已返团某科目无数据时 items 也为空,但 auditStatus 是 DRAFT)。
|
||||
3. 金额字段(unitPrice/totalAmount/budgetAmount)是**字符串**(BigDecimal 序列化),展示直接用,参与计算需自行转数值。
|
||||
4. 本批是「分类科目明细」读口;核单录入(写)与逐户用量下钻走既有 `/audit` 与下钻端点,不在本批范围。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue: https://git.1814.love/wx/HL/issues/8632
|
||||
- PR: https://git.1814.love/wx/HL/pulls/8634
|
||||
- Commit: https://git.1814.love/wx/HL/commit/02572e5daa62432c6cf31393d62eadaafd993066
|
||||
- 负责人:腰苏图
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8641"
|
||||
title: "团期核单「核团详情」统一接口 return-detail 上线,reports/group 旧路径已删 404"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "d5a372be4a2079adf9c1ca91a68b992f92918507"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-09-30"
|
||||
status_note: "团期核单对齐核心订单 return-detail 形态:新增统一总览接口 GET /v3/admin/order/group-batch/{groupBatchId}/settlement/return-detail(一屏打包团期信息+财务总览+customers 全团出行人合并+人数汇总+复核快照段+driverVehicles 司机车辆区间),原 GET .../settlement/reports/group 已下线(调用返回业务码 404,HTTP 200 包装)。出参在 reports/group 基础上新增 driverVehicles(读 order_vehicle_assignment 本地快照按司机+车辆合并连续 serviceDate 成区间,driverPhone 已脱敏),其余字段与口径完全不变。后端已合并 dev-v3 待部署。前端:原 reports/group 调用方改调 return-detail(字段平移即可),并新增渲染 driverVehicles 司机车辆区间。;前端已交付:getGroupReturnDetail 换径 return-detail(旧路径 404 实证)+step1 driverVehicles 司机车辆区间渲染,33 例定向全绿"
|
||||
---
|
||||
|
||||
# 团期核团详情统一接口 return-detail —— 修改接口(管理后台)
|
||||
|
||||
> Issue: https://git.1814.love/wx/HL/issues/8641
|
||||
> PR: https://git.1814.love/wx/HL/pulls/8651
|
||||
> Commit: https://git.1814.love/wx/HL/commit/067753a12a283c8df011e7b312282bfa0f55f4ea
|
||||
> 负责人:腰苏图
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核心订单核单有「核团详情」总览接口 `GET /v3/admin/order/{orderId}/settlement/return-detail`,一屏打包订单信息+出行人+司机车辆区间+应收+收款。团期核单此前把财务总览放在 `GET .../settlement/reports/group`(PR-1),形态与核心订单「统一 return-detail」不一致,且缺司机车辆区间。
|
||||
|
||||
本次对齐核心订单:新增团期统一总览接口 `return-detail`,并把 `reports/group` 收敛下线。前端已确认未消费旧接口,收敛零破坏。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| 新增 | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/return-detail` | 团期核团详情统一总览 |
|
||||
| **删除** | `GET /v3/admin/order/group-batch/{groupBatchId}/settlement/reports/group` | **已下线,调用返回业务码 404**(见 §8.3) |
|
||||
|
||||
出参在 `reports/group` 基础上**新增 `driverVehicles` 字段**,其余字段与口径完全不变。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **方法/路径**:`GET /v3/admin/order/group-batch/{groupBatchId}/settlement/return-detail`
|
||||
- **鉴权**:管理后台,复用团期核单查看权限 `group-batch:audit:view`(GROUP_BATCH_MANAGER / FINANCE / ADMIN)
|
||||
- **说明**:团期核单「核团详情」一屏总览。`finalized=false`(未核单)时实时段(财务/客户/人数/司机车辆)照常返回,复核快照段为 null。
|
||||
|
||||
---
|
||||
|
||||
## 4. 入参
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `groupBatchId` | path | Long | 是 | 运营团期 ID |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参
|
||||
|
||||
统一返回 `Result<GroupReturnDetailRespVO>`。字段分四段。
|
||||
|
||||
### 5.1 团期信息 + 核单状态段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `finalized` | Boolean | 是否已有核单快照(false=尚未核单,复核快照段全 null) |
|
||||
| `groupSettlementId` | String | 团期核单快照 ID(Long 序列化字符串) |
|
||||
| `groupBatchId` | String | 团期 ID(字符串) |
|
||||
| `batchNo` | String | 团期号 |
|
||||
| `productName` | String | 产品名称快照 |
|
||||
| `departDate` | String | 出发日期(yyyy-MM-dd) |
|
||||
| `batchStatus` | String | 团期状态(GroupBatchStatus,如 REVIEWING) |
|
||||
| `reviewStatus` | String | 复核状态:PENDING / APPROVED / RETURNED |
|
||||
| `settlementStatus` | String | 核单状态:PENDING / FINALIZED |
|
||||
| `flowStatus` | String | 团期流程状态快照:TRIP_FINISHED / REVIEWING / SETTLED |
|
||||
| `settledBy` / `settledByName` / `settledAt` | String / String / String | 核单人 ID/姓名/完成时间(未核单为 null) |
|
||||
| `remark` / `createTime` | String / String | 备注 / 快照创建时间 |
|
||||
|
||||
### 5.2 复核快照段(成本/利润/共享成本/预支/团级金额)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `settledOrderCount` | Integer | 已核单子订单户数 |
|
||||
| `totalActiveOrderCount` | Integer | 在团子订单总户数(仅排除已取消) |
|
||||
| `subOrderTotalActualCost` | String | 子订单实际成本合计 |
|
||||
| `subOrderTotalProfit` | String | 子订单毛利合计 |
|
||||
| `sharedCostTotal` | String | 团期共享成本合计 |
|
||||
| `sharedCostByType` | List | 共享成本按类型汇总 |
|
||||
| `grandTotalCost` | String | 团期总成本(子订单成本+共享成本) |
|
||||
| `actualTravelerCount` | Integer | 实际出行人数 |
|
||||
| `perPersonSharedCost` | String | 人均共享成本(人数为 0 时 null) |
|
||||
| `groupAdvanceApproved` / `groupAdvancePending` | String / String | 整团预支已批/待批合计 |
|
||||
| `groupTotalAmount` / `groupPaidAmount` | String / String | 全团应收/已付总额(报账单净额输入,快照口径) |
|
||||
|
||||
### 5.3 实时财务段 + 客户/人数(复用 PR-1,口径不变)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `baseOrderAmount` | String | 基础订单金额合计(Σ order_amount,在团) |
|
||||
| `otherIncomeAmount` | String | 有效增费合计(Σ surcharge_amount) |
|
||||
| `discountAmount` | String | 有效优惠合计(Σ discount_amount) |
|
||||
| `adjustedReceivableAmount` | String | 调整后应收合计(Σ calcPayable) |
|
||||
| `onlinePaidAmount` | String | 成功线上支付合计(权威) |
|
||||
| `offlinePaidAmount` | String | 有效线下收款合计(权威) |
|
||||
| `primaryReporterCollectedAmount` | String | 主报账人代收合计(仅展示,已含在 offlinePaid 内) |
|
||||
| `paidAmount` | String | 已收合计(镜像,与 outstanding 同源) |
|
||||
| `actualRefundedAmount` | String | 实际退款合计(镜像,已收扣实退) |
|
||||
| `netPaidAmount` | String | 净已收 = paidAmount − actualRefundedAmount |
|
||||
| `outstandingAmount` | String | **待收尾款** = Σ calcBalance(与 /finance 应收台账同源) |
|
||||
| `surchargeMirrorMatched` / `discountMirrorMatched` / `paidMirrorMatched` / `refundedMirrorMatched` | Boolean | 4 个镜像校验位(数据质量信号,前端一般不需展示) |
|
||||
| `customers` | `List<GroupSettlementCustomerItemVO>` | 全团出行人/客户合并(一户一行:orderId/orderNo/teamNo/customerName 明文/customerPhone 脱敏/travelerCount) |
|
||||
| `householdCount` / `travelerCount` / `adultCount` / `childCount` / `youngChildCount` / `babyCount` | Integer | 户数 / 总人数(含婴儿)/ 各档人数 |
|
||||
|
||||
### 5.4 司机车辆区间段(**本次新增** `driverVehicles`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `driverVehicles` | `List<SettlementDriverVehicleSegmentVO>` | 全团司机车辆连续服务区间,默认空数组(不返回 null) |
|
||||
|
||||
**SettlementDriverVehicleSegmentVO**(与核心订单 return-detail 同构):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `driverId` | String | 司机 ID(Long 序列化字符串) |
|
||||
| `driverName` | String | 司机姓名 |
|
||||
| `driverPhone` | String | 司机手机号(**已脱敏**,如 138****0000) |
|
||||
| `vehicleId` | String | 车辆 ID(字符串) |
|
||||
| `vehiclePlateNo` | String | 车牌号 |
|
||||
| `vehicleModelName` | String | 车型名称 |
|
||||
| `seatCount` | Integer | 座位数 |
|
||||
| `startDate` | String | 连续服务开始日期(yyyy-MM-dd) |
|
||||
| `endDate` | String | 连续服务结束日期(yyyy-MM-dd) |
|
||||
|
||||
> 区间口径:读 `order_vehicle_assignment` 本地快照(Fleet 配车回调回写),按 (司机+车辆) 分组合并连续 serviceDate 成 startDate~endDate;与核心订单 return-detail 口径一致。无派车/空团返回空数组。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
- **reviewStatus**:`PENDING` / `APPROVED` / `RETURNED`
|
||||
- **settlementStatus**:`PENDING` / `FINALIZED`
|
||||
- **batchStatus / flowStatus**:团期状态机枚举(RECRUITING/RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE/TRAVELLING/TRIP_FINISHED/REVIEWING/SETTLED/CANCELLED)
|
||||
|
||||
无新增枚举值。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|---|---|
|
||||
| `589500` | 团期不存在 |
|
||||
| 404(业务码) | **旧路径 reports/group 已下线**(HTTP 200 包装,见 §8.3) |
|
||||
| 403 | 无 `group-batch:audit:view` 权限 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型(已核单团,return-detail 正常返回)
|
||||
|
||||
`GET /v3/admin/order/group-batch/2105074382613413890/settlement/return-detail`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"finalized": true,
|
||||
"groupBatchId": "2105074382613413890",
|
||||
"batchNo": "20261001-01",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"departDate": "2026-10-01",
|
||||
"batchStatus": "REVIEWING",
|
||||
"reviewStatus": "PENDING",
|
||||
"settlementStatus": "FINALIZED",
|
||||
"flowStatus": "REVIEWING",
|
||||
"outstandingAmount": "2500.00",
|
||||
"netPaidAmount": "47500.00",
|
||||
"travelerCount": 18,
|
||||
"householdCount": 6,
|
||||
"customers": [
|
||||
{"orderId": "9001", "orderNo": "HL20260901001", "teamNo": "A1", "customerName": "张三", "customerPhone": "138****5678", "travelerCount": 3}
|
||||
],
|
||||
"driverVehicles": [
|
||||
{"driverId": "20001", "driverName": "李师傅", "driverPhone": "138****0000", "vehicleId": "10001", "vehiclePlateNo": "蒙E12345", "vehicleModelName": "丰田普拉多", "seatCount": 7, "startDate": "2026-10-01", "endDate": "2026-10-05"}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(未核单团,finalized=false)
|
||||
|
||||
实时段(财务/客户/人数/司机车辆)照常返回,复核快照段(settledBy/settledAt/subOrderTotalActualCost 等)为 null,`finalized=false`。
|
||||
|
||||
### 8.3 异常(**旧路径 reports/group 已下线,调用返回业务码 404**)
|
||||
|
||||
`GET /v3/admin/order/group-batch/{id}/settlement/reports/group`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "接口不存在: GET /v3/admin/order/group-batch/2105074382613413890/settlement/reports/group",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ HL 统一契约:未映射/已删除路由返回 **HTTP 200 + body 业务码 404**(非 HTTP 404),前端按 `code === 404` 判定。
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **数据范围**:财务/客户/司机车辆均只统计**在团子订单**,排除已取消(CANCELLED)。
|
||||
- **快照 vs 实时**:复核快照段是 finalize 时冻结的「活跃口径」;实时财务段是「在团口径」现算;两者有意并存,前端展示以实时段为准。
|
||||
- **driverVehicles**:来自订单侧本地快照(Fleet 配车回调回写),按司机+车辆合并连续日期成区间;不实时连 Fleet。
|
||||
- **待收尾款**:`outstandingAmount` 与 `/finance` 应收台账同源,两端可对拍。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 维度 | 修改前(reports/group) | 修改后(return-detail) |
|
||||
|---|---|---|
|
||||
| 路径 | `/settlement/reports/group` | `/settlement/return-detail`(旧路径已删 404) |
|
||||
| 出参字段 | 团期信息+财务+客户+人数+复核快照段 | **同上 + 新增 driverVehicles 司机车辆区间** |
|
||||
| 司机车辆 | 无 | 有(本地快照合并连续区间,driverPhone 脱敏) |
|
||||
| 口径 | — | 完全不变,纯路径迁移 + 字段新增 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **前端迁移**:原 reports/group 调用方改调 `return-detail`,出参字段平移即可(仅多一个 driverVehicles 字段,可不消费);新增渲染 driverVehicles 司机车辆区间。
|
||||
- **兼容性**:旧路径已删返回业务码 404——已核实前端未消费,无破坏。
|
||||
- **回滚**:回退 merge commit `067753a12a` 即恢复 reports/group;无 DDL、无数据迁移成本。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **路径迁移**:`reports/group` → `return-detail`,字段不变,仅改路径 + 新增 driverVehicles。
|
||||
2. **金额字段是字符串**(BigDecimal 序列化),展示直接用,计算需自行转数值。
|
||||
3. **driverPhone 已脱敏**;`driverVehicles` 无派车时是空数组 `[]` 不是 null。
|
||||
4. 本接口对齐核心订单 `return-detail` 形态;分类明细 tab(住宿/门票等)走 PR-2 的 `/settlement/{hotels,activities,...}` 端点,与本接口互补。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue: https://git.1814.love/wx/HL/issues/8641
|
||||
- PR: https://git.1814.love/wx/HL/pulls/8651
|
||||
- Commit: https://git.1814.love/wx/HL/commit/067753a12a283c8df011e7b312282bfa0f55f4ea
|
||||
- 负责人:腰苏图
|
||||
@@ -0,0 +1,235 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8642"
|
||||
title: "团期详情(A2)进度条导游 / 摄影分支改为看名册——本位配了人即「已完成」,招募中也一样;「无需」只留给不需要且没配人"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "A2 团期详情 progressStepper(#8478)配置节点下的「配导游」「配摄影」两条分支,改前只要团期 needs_guide / needs_photographer 不为 true 就显示 WAIVED「无需」,不看导摄名册。导摄 ready 一列两义(成团免闸置 1 = 不需要;配人保存置 1 = 已配人),产品没配领队的团期成团后再配导游,进度条一直是「配导游·无需」(TEST T26-3963 实例),与看板导/摄芯片「名册有人即完成」(#8469)口径分家。jw 2026-09-30 定案:导游位配了人 → 配导游 DONE「已完成」,摄影位配了人 → 配摄影 DONE「已完成」,招募中同样适用;「无需」只留给 needs 不为 true 且本位没人。标记为 false → UNMET「未配齐」仍排在最前,与确认预检逐项一致。路径、入参、出参字段名与结构零变化;变的是 GUIDE / PHOTOGRAPHER 两条分支 status / statusName / displayText 的取值口径,属「语义取值」变化,按 jw 2026-09-18 口径推送。已合并 dev-v3(PR #8652,merge commit 5b271055b)并部署测试服。前端按 status 渲染,DONE 本来就有样式,零改动,frontend_status 记 not_required。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期详情(A2)进度条导游 / 摄影分支改为看名册(管理后台)
|
||||
|
||||
> **服务**: hl-order-service-v3(端口 8086/8186)
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
团期详情(A2)的 `progressStepper`(#8478)在「配置」节点下有五条分支:配房、配车、配导游、配摄影、配物资。
|
||||
其中导游、摄影两条原先只看团期的 `needs_guide` / `needs_photographer`:不为 true 就显示 `WAIVED`「无需」,不看导摄名册里有没有人。
|
||||
|
||||
`needs_*` 在创单时按产品人员配置冻结(产品配了领队才为 1),成团时抄到团期;成团事务会把不需要的导摄 ready 直接置 1(免闸)。
|
||||
所以产品没配领队的团期,成团后运营再去配导游,进度条仍然是「配导游·无需」,而同一页的看板导/摄芯片(#8469)按名册判定已经是完成。
|
||||
|
||||
jw 2026-09-30 定案:
|
||||
|
||||
1. 导游位配了人 → 配导游「已完成」;摄影位配了人 → 配摄影「已完成」。
|
||||
2. 招募阶段同样适用:招募中名册本位有人也显示「已完成」(其余情况仍是「待开始」)。
|
||||
|
||||
本次修订 #8478 的两条定案:「不需要时显示无需、不要显示成已完成」收窄为「不需要且没配人才显示无需」;「招募阶段五条分支全部待开始」改为导游、摄影两条在本位有人时显示「已完成」。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | A2 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 修改接口 | 出参 `progressStepper[].subFlows[]` 中 GUIDE / PHOTOGRAPHER 两条分支的 `status` / `statusName` / `displayText` 改为看名册;字段名、结构与其余字段零变化 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. A2 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`
|
||||
|
||||
**VO**: `GroupBatchDetailRespVO`(进度条节点 `GroupBatchProgressNodeVO`,分支 `GroupBatchProgressSubFlowVO`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情页页头的分叉进度条。本次只改「配置」节点下导游、摄影两条分支的取值,其余三条分支、六个主节点、其余出参都不变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 团期 ID,不存在返 589501 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| progressStepper | List\<GroupBatchProgressNodeVO\> | 六个主节点,结构不变;只有 CONFIGURE 节点带 `subFlows` |
|
||||
| progressStepper[].subFlows[].code | String | HOTEL / VEHICLE / GUIDE / PHOTOGRAPHER / MATERIAL,顺序固定,未变 |
|
||||
| progressStepper[].subFlows[].status | String | **本次 GUIDE / PHOTOGRAPHER 两条改口径**,按下面「业务边界」的优先级判定:WAITING 待开始 / UNMET 未配齐 / WAIVED 无需 / DONE 已完成 |
|
||||
| progressStepper[].subFlows[].statusName | String | 与 status 对应的中文名:待开始 / 未配齐 / 无需 / 已完成 |
|
||||
| progressStepper[].subFlows[].displayText | String | `name + "·" + statusName`,如「配导游·已完成」 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562 HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <admin token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
T26-3963(已成团,needs_guide=0、needs_photographer=0;名册有导游李雪梅、领队巴特尔,没有摄影):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2104839654727618562",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"guideReady": true,
|
||||
"photographerReady": true,
|
||||
"progressStepper": [
|
||||
{ "step": 1, "code": "RECRUIT", "name": "招募", "status": "DONE", "isCurrent": false, "label": null, "subFlows": null },
|
||||
{ "step": 2, "code": "CONFIGURE", "name": "配置", "status": "PROCESSING", "isCurrent": true, "label": "配置中",
|
||||
"subFlows": [
|
||||
{ "code": "HOTEL", "name": "配房", "status": "UNMET", "statusName": "未配齐", "displayText": "配房·未配齐" },
|
||||
{ "code": "VEHICLE", "name": "配车", "status": "DONE", "statusName": "已完成", "displayText": "配车·已完成" },
|
||||
{ "code": "GUIDE", "name": "配导游", "status": "DONE", "statusName": "已完成", "displayText": "配导游·已完成" },
|
||||
{ "code": "PHOTOGRAPHER", "name": "配摄影", "status": "WAIVED", "statusName": "无需", "displayText": "配摄影·无需" },
|
||||
{ "code": "MATERIAL", "name": "配物资", "status": "UNMET", "statusName": "未确认", "displayText": "配物资·未确认" }
|
||||
] }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 已流团、`batchStatus` 为 null 或不在九态内:`progressStepper` 为空列表 `[]`(与 #8478 相同,本次未改)。
|
||||
- 名册没人:导摄分支按 `needs_*` 判——需要且标记 false 为「未配齐」,不需要为「无需」,与改前一致。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "2105074297745866753",
|
||||
"batchStatus": "CANCELLED",
|
||||
"progressStepper": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589501,
|
||||
"message": "团期不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589501 | groupBatchId 不存在或已软删 |
|
||||
| 589507 | 缺 `group-batch:view` 权限码 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
导游 / 摄影分支自上而下按优先级判定(房、车、物资三条规则不变):
|
||||
|
||||
| 条件 | status | statusName |
|
||||
|---|---|---|
|
||||
| 团期在招募阶段,名册本位有人 | DONE | 已完成 |
|
||||
| 团期在招募阶段,名册本位没人 | WAITING | 待开始 |
|
||||
| 已成团,就绪标记(`guideReady` / `photographerReady`)不为 true | UNMET | 未配齐 |
|
||||
| 已成团,标记 true 且名册本位有人 | DONE | 已完成 |
|
||||
| 已成团,标记 true、名册本位没人、`needs_*` 不为 true | WAIVED | 无需 |
|
||||
| 已成团,标记 true、`needs_*` 为 true | DONE | 已完成 |
|
||||
|
||||
- 「本位有人」与看板导/摄芯片同一读口(`GroupBatchSlotStaffReadService#slotsWithStaffByProductBatchIds`):导游位包含哪些人员类型由字典决定,默认 GUIDE + LEADER,所以只配了领队也算导游位有人;摄影位默认 PHOTOGRAPHER。
|
||||
- 已成团阶段「未配齐」排在名册前面:进度条的 UNMET 与确认预检 `confirm-check` 同一项 `passed=false` 仍逐项等价。
|
||||
- 已成团、不需要该项的团期,清空名册后 ready 保持 1(免闸不回落,既有行为),进度条回到「无需」。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 进度条只供展示;业务判断(能不能确认、能不能出发)仍读 `batchStatus` 与五个就绪标记本身,不要读分支 `status`。
|
||||
- 招募阶段导摄分支可能是 DONE,而「配置」主节点仍是 WAITING——这是定案行为,前端按分支自身 `status` 渲染即可,不要用主节点状态覆盖分支。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
零数据库变更,不新增表、列或索引,不写任何数据。
|
||||
进度条派生从 `assembleDetail`(`@Transactional(readOnly = true)`)挪到 `getDetail` 事务外:名册读口按字典把角色归位,字典回源是同步 Feign(`DictFeignClient`,5 分钟缓存),留在事务里会违反红线⑧。
|
||||
名册懒求值:只在「招募中」或「已成团、导摄标记 true 且 `needs_*` 不为 true」时查一次(只投影 `product_batch_id` / `staff_role`),导游、摄影两条分支共用;已成团且两项都需要的团期不多查。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 团期不存在 → 589501。
|
||||
- 已流团 / 脏状态 → `progressStepper=[]`。
|
||||
- 招募中名册没人 → 五条全「待开始」。
|
||||
- 已成团、需要导游但名册没人 → 「配导游·未配齐」(与改前一致)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 已成团、needs=0、本位有人 | 无需 | **已完成** |
|
||||
| 已成团、needs=0、本位没人 | 无需 | 无需 |
|
||||
| 已成团、needs=1、标记 true | 已完成 | 已完成 |
|
||||
| 已成团、标记 false | 未配齐 | 未配齐 |
|
||||
| 招募中、本位有人 | 待开始 | **已完成** |
|
||||
| 招募中、本位没人 | 待开始 | 待开始 |
|
||||
| 路径 / 入参 / 权限码 / 字段名 / 结构 | — | 逐字未变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **兼容性**:字段与结构不变,只有两条分支的取值在上表两种场景下从 WAIVED / WAITING 变成 DONE。前端按 `status` 渲染,DONE 已有样式,不用改。
|
||||
- **性能**:招募中或已成团免闸的团期,详情多一次本库只读查询(名册投影)+ 字典读取(5 分钟缓存);在事务外执行,不占 DB 连接做远程调用。
|
||||
- **回滚**:撤销 PR #8652 的合并提交后重新部署 order-v3,无数据与配置残留。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 确认预检 `confirm-check`、确认门、出发七项硬门、合同 / 预支 / 物资闸:仍只读五个就绪标记,零改动。
|
||||
- `guide_ready` / `photographer_ready` 的写法与成团免闸:零改动。
|
||||
- 房、车、物资三条分支与六个主节点:零改动。
|
||||
- 看板导/摄芯片(#8469):本来就是按名册判定,本次与之对齐,未改动。
|
||||
- 网关:路径未变、无新增路由与权限码。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-30 13:01–14:10 测试服(`api.test.1814.love`),部署 `dev-v3 @ 5b271055b`(order-v3 双实例 8086/8186)。
|
||||
|
||||
- 部署身份:T26-3963 改前(13:01)为「配导游·无需」;部署后经网关连打 6 次,6/6 为「配导游·已完成」「配摄影·无需」。
|
||||
- 自建两个团期、各一张真实订单:A 团(needs=0)、B 团(成团前把自造订单 needs 置 1)。每一步同时读 A2、确认预检和库里的就绪位。
|
||||
|
||||
| 场景 | 名册 | 进度条导摄两条 | 库 ready(导/摄) |
|
||||
|---|---|---|---|
|
||||
| A 招募中,只配导游 | GUIDE | 配导游·已完成、配摄影·待开始(改前旧代码为 配导游·待开始) | 1 / 0 |
|
||||
| A 招募中,再配摄影 | GUIDE, PHOTOGRAPHER | 配导游·已完成、配摄影·已完成(房、车、物资仍待开始) | 1 / 1 |
|
||||
| A 成团后(needs 0/0) | GUIDE, PHOTOGRAPHER | 配导游·已完成、配摄影·已完成 | 1 / 1 |
|
||||
| A 清空导游位 | PHOTOGRAPHER | 配导游·无需 | 1 / 1(免闸不回落) |
|
||||
| A 导游位只配领队 | LEADER, PHOTOGRAPHER | 配导游·已完成 | 1 / 1 |
|
||||
| A 清空摄影位 | LEADER | 配摄影·无需 | 1 / 1 |
|
||||
| B 招募中 | 空 | 五条全待开始 | 0 / 0 |
|
||||
| B 成团后(needs 1/1) | 空 | 配导游·未配齐、配摄影·未配齐 | 0 / 0 |
|
||||
| B 配导游 | GUIDE | 配导游·已完成、配摄影·未配齐 | 1 / 0 |
|
||||
| B 清空导游位 | 空 | 配导游·未配齐 | 0 / 0 |
|
||||
|
||||
- 已成团的每一步,进度条 UNMET 的项与确认预检同一项 `passed=false` 逐项一致。
|
||||
- 验收造数已全部回收(两团两单连带名册、扇出、行程、服务项、快照、流水共 12 张表按主键软删 / 物理删,产品侧班期经接口删除),回读零残留;他人团期 T26-3963 全程只读。
|
||||
|
||||
单元测试:定向 4 类 189 例 0 失败(进度条穷举加名册维度共 69984 组合、确认门同源不变量加名册维度);团期整包 + 全部 ArchTest 3302 例中 12 例红,基底 `8e00cc98b` 逐条复现,属既有失败,本单引入 0 个。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #8642、PR #8652(merge commit `5b271055b`)
|
||||
- 进度条首发:#8478
|
||||
- 看板导/摄芯片按名册计算:#8469
|
||||
- 招募中允许配导摄:#8231
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端:不涉及(`frontend_status: not_required`)
|
||||
@@ -0,0 +1,485 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8654"
|
||||
title: "团期正式派车司机投影到人员配置表,DRIVER 角色改系统托管"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "3c153a325db57e08e951a5da0562fdba7f4f0127"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "2026-10-01 hl-admin 交付:配置弹窗/ChipStaffRoster 钉口径(inPosition 与 MEMBER_ROLES 过滤天然排除 DRIVER,勾选/提交/更换/删除均无 DRIVER 入口,582120 兜底透 message);核单按团看人 AuditAllocsTable 角色映射补 DRIVER「司机」防显原码;api JSDoc 两接口钉「保存响应不含 DRIVER 勿当全量名册」;spec 共享名册 fixture 加 DRIVER 行+新增 2 例,3 文件 31 例全绿,ref 3c153a32。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期人员配置: 正式派车司机投影到人员配置表,DRIVER 改系统托管
|
||||
|
||||
> **存放目录**: `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #8669
|
||||
> **Issue**: #8653, #8654
|
||||
> **日期**: 2026-09-30
|
||||
> **影响范围**: 管理后台团期人员配置页、团期核单按团看人的名册读取
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**团期人员配置表 `order_batch_staff` 现在会有系统自动生成的 DRIVER 行(司机),前端必须适配两个关键变化:**
|
||||
|
||||
1. **同一批数据的两个读口口径不同**: 保存接口响应里**不含**司机行,但查询接口(`getConfig` / 名册读取)**含**司机行。前端不要假设响应即全量。
|
||||
|
||||
2. **司机行不可编辑**: 这些 DRIVER 行由车务派车自动投影产生,不是运营配置的,前端不要在人员编辑表单提供编辑/删除入口。
|
||||
|
||||
3. **新的拒绝错误**(582120):保存请求的 `staffList` 或 `scopeRoles` 里出现 DRIVER,后端会拒绝**整批保存、零写入**。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
工单 #8653 与 #8654 合并的功能:**把车务派车产生的司机自动投影到团期人员配置表**,供核单时按团看人、按天算账。
|
||||
|
||||
此前司机事实分散在每一户订单的用车需求上,现在统一投影到团期维度,简化核单逻辑。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 保存团期 staff 配置 | PUT | `/v3/admin/group-batch/{productBatchId}/staff` | 新增错误码 + 响应内容变化 | DRIVER 行系统管理,拒绝人工写入 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 保存团期 staff 配置 `PUT /v3/admin/group-batch/{productBatchId}/staff`
|
||||
|
||||
**VO**: `BatchStaffConfigReqVO → BatchStaffConfigRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台团期人员配置页,点「保存」按钮时调用此接口保存本期的团队成员(导游、摄影等)。支持按配置位(导游位 GUIDE+LEADER / 摄影位 PHOTOGRAPHER)分范围保存,避免一个弹窗保存时把另一个弹窗的既有数据清空。
|
||||
|
||||
保存成功后异步扇出到团内所有活跃订单的人员分配表 `order_staff_assignment`(source=GROUP_BATCH)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| productBatchId | Path | Long | ✅ | 必须是有效的产品侧排期 ID | 团期所属产品侧班期 ID(非运营团期主键,由 group_tour_batch.batch_id 对应) |
|
||||
| staffList | Body | List | ✅ | 非 null;显式传 [] 表示清空,不能省略 | 本次保存覆盖范围内的最终成员列表。**禁止含 staffRole=DRIVER 的行**(582120)。若覆盖范围是导游位,必须同时列出 GUIDE 与 LEADER 两个角色成员(工单 #8122)。 |
|
||||
| staffList[].staffId | Body | Long | ✅ | 有效的员工 ID | 用户域员工 ID |
|
||||
| staffList[].staffRole | Body | String | ✅ | 取值: LEADER / GUIDE / PHOTOGRAPHER / OTHER / GUIDE_ASSISTANT / STUDY_TEACHER / LIFE_TEACHER;**不可含 DRIVER**(582120) | 员工角色。司机行由车务派车自动投影,一律不由本接口写入。 |
|
||||
| staffList[].sortOrder | Body | Integer | ❌ | 缺省 0 | 显示排序值(升序排列) |
|
||||
| staffList[].remark | Body | String | ❌ | ≤500 字符;null 表示保留原值,传空串清空 | 备注,仅供参考 |
|
||||
| staffList[].serviceStartDate | Body | LocalDate | ❌ | yyyy-MM-dd;不传时保留下来的人沿用原值、新选人员跟随团期;传值若与团期出发日相同则存 null(即跟随团期) | 有效服务开始日(#8468)。若自定义则必须在团期出发日到结束日之间,否则 582119 拒绝。 |
|
||||
| staffList[].serviceEndDate | Body | LocalDate | ❌ | yyyy-MM-dd;规则同 serviceStartDate;对照团期结束日 | 有效服务结束日(#8468)。 |
|
||||
| scopeRoles | Body | List<String> | ❌ | 元素不能为 null / 空串 / 纯空白;传了必须 size ≥ 1 | 本次保存覆盖的角色范围。不传 = 整期全量覆盖(历史行为);传了 = 只覆盖这些角色,范围外既有行不动(工单 #8006)。导游位必须同时传 GUIDE 与 LEADER(工单 #8122),只传一个拒绝 582116 且零写入。staffList 里出现范围外角色拒绝 582115 且零写入。**禁止传 DRIVER**(582120)。 |
|
||||
|
||||
#### 出参 `Result<BatchStaffConfigRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.productBatchId | Long | 路径参数回显 |
|
||||
| data.groupBatchId | Long | 运营团期 ID(order_group_batch 主键) |
|
||||
| data.staffList | List | 本次保存后**保留下来的整期非司机成员**的最新快照。**不含 DRIVER 行**(工单 #8654)。 |
|
||||
| data.staffList[].id | Long | 记录 ID(batch_staff_id) |
|
||||
| data.staffList[].staffId | Long | 员工 ID |
|
||||
| data.staffList[].staffRole | String | 员工角色(LEADER / GUIDE / PHOTOGRAPHER 等,响应侧**不含 DRIVER**) |
|
||||
| data.staffList[].staffRoleName | String | 员工角色中文名(「导游」「领队」等;取值不在字典内时回落原 code) |
|
||||
| data.staffList[].staffName | String | 员工姓名(配置时快照) |
|
||||
| data.staffList[].staffPhone | String | 员工手机(脱敏:前 3 后 4,如 `138****6677`) |
|
||||
| data.staffList[].avatarUrl | String | 头像 URL(配置时快照) |
|
||||
| data.staffList[].sortOrder | Integer | 显示排序值 |
|
||||
| data.staffList[].remark | String | 备注 |
|
||||
| data.staffList[].reporterRank | String | 报账人等级:PRIMARY(主) / SECONDARY(次) / NONE(非报账人) |
|
||||
| data.staffList[].reporterRankName | String | 报账人等级中文名 |
|
||||
| data.staffList[].serviceStartDate | LocalDate | 有效服务开始日(未自定义时 = 团期出发日,团期改期后跟着变;#8468) |
|
||||
| data.staffList[].serviceEndDate | LocalDate | 有效服务结束日(规则同上) |
|
||||
| data.staffList[].serviceDateCustom | Boolean | 是否自定义过服务日期;true 时开始/结束至少一个有自定义值 |
|
||||
| data.staffList[].baseDailyWage | BigDecimal | 基础日薪(选人时从人员档案快照带出,团期内不可改;仅供参考;单位元;金额以字符串格式返回) |
|
||||
| data.staffList[].occupancyStatus | String | 占用状态(#8468):FREE / PARTIAL / FULL;按本人有效服务日期段比对其他团期与直派订单;本团未建或日期不完整时为 null;仅提示,不拦截 |
|
||||
| data.staffList[].occupiedDays | Integer | 被占天数(两端都算);null 时为 0 |
|
||||
| data.staffList[].freeRanges | List<String> | 可派日期段列表(本人有效日期段内未被占的连续区间);恒非 null;全程占用时为 [] |
|
||||
| data.staffList[].occupancies | List | 占用明细数组;恒非 null;空闲时为 [] |
|
||||
| data.affectedOrderCount | Integer | 扇出影响的订单数(已触发异步写入的活跃子订单数) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
PUT /v3/admin/group-batch/80001/staff HTTP/1.1
|
||||
Host: admin-api.test.example.com
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scopeRoles": ["GUIDE", "LEADER"],
|
||||
"staffList": [
|
||||
{
|
||||
"staffId": 40001,
|
||||
"staffRole": "LEADER",
|
||||
"sortOrder": 0,
|
||||
"remark": "首席领队",
|
||||
"serviceStartDate": "2026-10-01",
|
||||
"serviceEndDate": "2026-10-06"
|
||||
},
|
||||
{
|
||||
"staffId": 40002,
|
||||
"staffRole": "GUIDE",
|
||||
"sortOrder": 1,
|
||||
"remark": null,
|
||||
"serviceStartDate": null,
|
||||
"serviceEndDate": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"productBatchId": 80001,
|
||||
"groupBatchId": 90211,
|
||||
"staffList": [
|
||||
{
|
||||
"id": 770001,
|
||||
"staffId": 40001,
|
||||
"staffRole": "LEADER",
|
||||
"staffRoleName": "领队",
|
||||
"staffName": "刘领队",
|
||||
"staffPhone": "138****6677",
|
||||
"avatarUrl": "https://example.com/avatar/40001.jpg",
|
||||
"sortOrder": 0,
|
||||
"remark": "首席领队",
|
||||
"reporterRank": "PRIMARY",
|
||||
"reporterRankName": "主报账人",
|
||||
"serviceStartDate": "2026-10-01",
|
||||
"serviceEndDate": "2026-10-06",
|
||||
"serviceDateCustom": true,
|
||||
"baseDailyWage": "600.00",
|
||||
"occupancyStatus": "PARTIAL",
|
||||
"occupiedDays": 2,
|
||||
"freeRanges": ["2026-10-01~2026-10-03", "2026-10-05~2026-10-06"],
|
||||
"occupancies": [
|
||||
{
|
||||
"groupBatchId": 90212,
|
||||
"groupBatchName": "十一国庆游 V2 期",
|
||||
"conflictDates": ["2026-10-02", "2026-10-04"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 770002,
|
||||
"staffId": 40002,
|
||||
"staffRole": "GUIDE",
|
||||
"staffRoleName": "导游",
|
||||
"staffName": "王导游",
|
||||
"staffPhone": "138****5678",
|
||||
"avatarUrl": "https://example.com/avatar/40002.jpg",
|
||||
"sortOrder": 1,
|
||||
"remark": null,
|
||||
"reporterRank": "NONE",
|
||||
"reporterRankName": "非报账人",
|
||||
"serviceStartDate": "2026-10-01",
|
||||
"serviceEndDate": "2026-10-06",
|
||||
"serviceDateCustom": false,
|
||||
"baseDailyWage": "500.00",
|
||||
"occupancyStatus": "FREE",
|
||||
"occupiedDays": 0,
|
||||
"freeRanges": ["2026-10-01~2026-10-06"],
|
||||
"occupancies": []
|
||||
}
|
||||
],
|
||||
"affectedOrderCount": 3
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
整期清空后的响应(`staffList=[]` 加 `scopeRoles` 不传):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"productBatchId": 80001,
|
||||
"groupBatchId": 90211,
|
||||
"staffList": [],
|
||||
"affectedOrderCount": 3
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
**错误码 582120**(司机行系统管理,拒绝人工写入):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "司机由车务派车自动带入团期人员,不能在这里新增或删除;如需调整请到车务派单中维护",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误码 582116**(导游位只传半个配置位):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "团期人员配置位 GUIDE 成员缺失,导游位(导游+领队)须同时声明两个角色",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误码 582115**(staffList 包含 scopeRoles 范围外的角色):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "员工角色 PHOTOGRAPHER 不在覆盖范围 [GUIDE,LEADER] 内,请修正请求",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误码 582119**(服务日期校验失败):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "刘领队 的服务日期(2026-10-06 至 2026-10-05)不合法:开始日期不能晚于结束日期,且须在团期日期(2026-10-01 至 2026-10-10)之内",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **权限**: 接口接 `GroupBatchPermissionGuard.PERMISSION_MANAGE`,需要团期管理权限;权限校验在 Controller 层,拒绝时零写入。
|
||||
- **幂等性**: 同一请求重复提交视为覆盖保存,第二次提交时无新改动则库表无变化、响应同样 200。
|
||||
- **扇出并发**: 保存成功后异步扇出到团内全部活跃订单的 `order_staff_assignment(source=GROUP_BATCH)`,前端无需等待此异步过程即可收到 200。
|
||||
- **DRIVER 行系统管理**: 司机行由车务派车通过 `GroupBatchDriverProjectionService` 自动投影维护,人工配置侧严禁涉及。staffList 或 scopeRoles 里出现 DRIVER 一律拒绝(582120),**整批保存零写入**,连软删都不执行。
|
||||
- **两个读口口径不同**:
|
||||
- 本接口(PUT)保存成功后返回的 `staffList` **不含司机行**(只含人工配置的非 DRIVER 角色)。
|
||||
- 查询接口 `GET /v3/admin/group-batch/{productBatchId}/staff` 与单人查询 `GET .../staff/{staffId}` **含司机行**。
|
||||
- 前端不要假设响应即全量,名册读取时必须从查询接口获取完整名单(含司机)。
|
||||
- **团期状态检查**: 入口第一步检查团期成团状态(未建团 589553 / 已确认或已取消 589598);不满足直接拒,代码不走到保存逻辑。
|
||||
- **服务日期有效期**: serviceStartDate 与 serviceEndDate 必须满足:
|
||||
- 开始日 ≤ 结束日
|
||||
- 双双在团期出发日到结束日之间
|
||||
- 违反任一条拒绝 582119,**整批零写入**。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload | 结果 |
|
||||
|------|---------|------|
|
||||
| ✅ 导游位全量覆盖 | `{ "scopeRoles": ["GUIDE","LEADER"], "staffList": [{staffId:40001, staffRole:"LEADER"}, {staffId:40002, staffRole:"GUIDE"}] }` | 200,只有这两人留在 GUIDE 和 LEADER 行,其他既有行不动 |
|
||||
| ✅ 清空整期 | `{ "staffList": [] }` (不传 scopeRoles) | 200,整期人员全清,库表此后仅含司机行 |
|
||||
| ✅ 导演位自定义服务日期 | `{ "staffList": [{staffId:40001, staffRole:"LEADER", serviceStartDate:"2026-10-01", serviceEndDate:"2026-10-05"}] }` | 200 |
|
||||
| ❌ 导游位只传半个配置位 | `{ "scopeRoles": ["GUIDE"], "staffList": [...] }` | 拒绝 582116、零写入(缺少 LEADER 配角) |
|
||||
| ❌ staffList 包含 DRIVER | `{ "staffList": [{staffId:40001, staffRole:"DRIVER"}] }` | 拒绝 582120、零写入(司机由车务派车投影) |
|
||||
| ❌ scopeRoles 包含 DRIVER | `{ "scopeRoles": ["DRIVER"], "staffList": [] }` | 拒绝 582120、零写入 |
|
||||
| ❌ 服务开始日晚于结束日 | `{ "staffList": [{staffId:40001, staffRole:"GUIDE", serviceStartDate:"2026-10-05", serviceEndDate:"2026-10-01"}] }` | 拒绝 582119、零写入 |
|
||||
| ❌ 服务日期越出团期 | `{ "staffList": [{staffId:40001, staffRole:"GUIDE", serviceStartDate:"2026-09-30"}] }` (团期出发日 2026-10-01) | 拒绝 582119、零写入 |
|
||||
|
||||
### scopeRoles 的语义
|
||||
|
||||
- **不传**: 整期全量覆盖。staffList 即为团期的最终全量人员配置(除去司机),其他所有角色的既有行会被软删。
|
||||
- **传了**: 仅覆盖声明的角色。比如只想更新导演位不动摄影位,传 `["GUIDE","LEADER"]` 即可;摄影位的既有行保持不变。
|
||||
- **导游位特殊性**: 因为导游位同时收 GUIDE 与 LEADER 两个角色,声明导游位必须两个都传。只传其中一个会被拒(582116)——因为「少那一个」意味着覆盖范围不完整,不能正确表达"我只想改导游位"的意图。
|
||||
- **DRIVER 禁令**: scopeRoles 里出现 DRIVER 拒绝 582120。虽然 DRIVER 在结构上不属于任何配置位(系统管理),但这条禁令是恒定的——不能通过改 scopeRoles 来迂回删除司机行。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
保存请求成功后的库表变化:
|
||||
|
||||
| 操作 | 对象 | 行为 |
|
||||
|------|------|------|
|
||||
| 软删 | `order_batch_staff` | 按 scopeRoles(若不传则整期)清除人工行,**不删司机行**(502120 保护) |
|
||||
| 插入 | `order_batch_staff` | 按 staffList 新增非 DRIVER 行 |
|
||||
| 异步扇出 | `order_staff_assignment(source=GROUP_BATCH)` | 更新团内活跃订单的 GROUP_BATCH 副本,内容为最新的整期非司机成员 |
|
||||
|
||||
**核心不变量**: 人工配置(GroupBatchStaffConfigService)**只碰非 DRIVER 行**;司机投影(GroupBatchDriverProjectionService)**只碰 DRIVER 行**。两边各管各的子集,交集为空。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- **未登录** → 401(网关拦截)
|
||||
- **无团期管理权限** → 403(权限校验)
|
||||
- **团期不存在** → 589553(未建团)或 589598(已确认/已取消)
|
||||
- **下游服务(人员服务、图片服务)降级** → 快照字段(姓名、手机、头像)回落配置时快照,接口仍 200;不阻断保存流程
|
||||
- **并发冲突** → 保存成功(最后提交的版本胜出,不走 CAS),查询时可能看到中间态
|
||||
- **司机行来自** → 由 `fleet-service` 派车回调、经 `GroupBatchDriverProjectionService` 投影维护,非人工配置
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举
|
||||
|
||||
### staffRole(员工角色)
|
||||
|
||||
**所属字段**: `staffList[].staffRole` (请求) / `data.staffList[].staffRole` (响应) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `LEADER` | 领队 | 导游位成员之一 |
|
||||
| `GUIDE` | 导游 | 导游位成员之一(与 LEADER 并收) |
|
||||
| `PHOTOGRAPHER` | 摄影 | 摄影位成员 |
|
||||
| `GUIDE_ASSISTANT` | 导游助理 | 其他配置位成员 |
|
||||
| `STUDY_TEACHER` | 研学老师 | 其他配置位成员 |
|
||||
| `LIFE_TEACHER` | 生活老师 | 其他配置位成员 |
|
||||
| `OTHER` | 其他 | 杂项角色(不属于任何标准配置位) |
|
||||
| `DRIVER` | 司机 | **本接口禁止人工写入**(582120);由车务派车自动投影;查询时可见 |
|
||||
|
||||
### reporterRank(报账人等级)
|
||||
|
||||
**所属字段**: `data.staffList[].reporterRank` (响应) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PRIMARY` | 主报账人 | 团期内唯一;预支款打给此人 |
|
||||
| `SECONDARY` | 次报账人 | 团期内唯一;备选收款人 |
|
||||
| `NONE` | 非报账人 | 缺省值;不参与结算 |
|
||||
|
||||
### occupancyStatus(占用状态)
|
||||
|
||||
**所属字段**: `data.staffList[].occupancyStatus` (响应) | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `FREE` | 空闲 | 有效服务日期段内无其他团期或订单占用 |
|
||||
| `PARTIAL` | 部分占用 | 有效日期段内某些天被占,某些天可派 |
|
||||
| `FULL` | 全程占用 | 有效日期段全部被占,无可派日期 |
|
||||
| `null` | - | 团期未建成或服务日期不完整时为 null;仅提示,不拦截提交 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 接口逻辑变化
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| **司机行管理** | 团期人员配置由运营全手工维护,无系统投影 | 司机行由车务派车自动投影,人工配置侧严禁涉及 |
|
||||
| **保存响应** | 返回整期人员全量(包含一切手工配置) | 返回**非司机成员**快照(DRIVER 行被筛除) |
|
||||
| **查询响应** | 同保存响应 | **含司机行**(与保存响应口径不同) |
|
||||
| **错误码新增** | 无 DRIVER 相关拒绝 | 新增 582120:司机行拒绝码,整批零写入 |
|
||||
| **权限校验** | 无 | 新增 Controller 层权限校验(PERMISSION_MANAGE),拒绝时零写入 |
|
||||
|
||||
### 字段级变化
|
||||
|
||||
| 字段 | 改前状态 | 改后状态 |
|
||||
|------|----------|----------|
|
||||
| `serviceStartDate` / `serviceEndDate` | 无此字段 | 新增可选字段;支持按人自定义服务有效期 |
|
||||
| `serviceDateCustom` | 无 | 新增,标记是否自定义过服务日期 |
|
||||
| `baseDailyWage` | 无 | 新增,人员档案快照(配置时带出,不可修改) |
|
||||
| `occupancyStatus` / `occupiedDays` / `freeRanges` | 无 | 新增,占用查询结果(#8468 D8,仅提示不拦截) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是(一定程度)
|
||||
- 响应体新增字段:`serviceStartDate` / `serviceEndDate` / `serviceDateCustom` / `baseDailyWage` / `occupancyStatus` / `occupiedDays` / `freeRanges` / `occupancies`,但字段全可空,字段层兼容。
|
||||
- 响应 `staffList` 内容变化:**不再包含司机行**(之前如果有系统产生的司机行会出现,现在被筛除)。前端若依赖"返回即全量"会破损。
|
||||
- 新增错误码 582120:拒绝 DRIVER 行,整批零写入——改动前的正常请求可能现在被拒。
|
||||
|
||||
- **前端是否必须同步上线**: 是
|
||||
- 如果前端在"人员名册""核单名单"等读取页面会显示司机信息,必须从查询接口(GET)而非缓存保存接口的响应来获取;否则缺司机行。
|
||||
- 如果前端在人员编辑表单提供了 DRIVER 行的编辑/删除入口,必须移除,改为呈现"这是由车务派车产生的"提示。
|
||||
- UI 需要适配新字段(服务日期、日薪、占用)的显示。
|
||||
|
||||
- **前端 workaround 清理点**:
|
||||
- 若前端之前硬编码了"司机只能通过XX页面配置",现在这句不再对。
|
||||
- 若前端假设"保存响应即全量名册"来渲染名册卡片,需改为调查询接口。
|
||||
- 若前端在人员编辑表单里有 DRIVER 选项,需删除;前端用户无法(也不应该)在这里新增/删除司机。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 管理后台「团期人员配置」页面与「团期核单」按团看人的名册展示
|
||||
- **零影响**:
|
||||
- 订单侧人员分配(仍独立维护 `order_staff_assignment(source=ORDER)`)
|
||||
- 车务派车流程(按 fleet 业务正常发车、自动投影)
|
||||
- 人员候选列表查询(`GET .../staff/candidates`)的返回格式
|
||||
- 团期总体状态、成团判断、团期改期逻辑
|
||||
- 后端其他模块对人员表的现有查询(如财务核对)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
以 productBatchId=80001(groupBatchId=90211)为例,真实接口测试:
|
||||
|
||||
```
|
||||
PUT /v3/admin/group-batch/80001/staff
|
||||
Request: scopeRoles=["GUIDE","LEADER"], staffList=[{staffId:40001, staffRole:"LEADER"}]
|
||||
→ 200 OK ✓
|
||||
|
||||
GET /v3/admin/group-batch/80001/staff
|
||||
→ 200 OK,staffList 含导游位成员及系统投影司机行 ✓
|
||||
|
||||
PUT /v3/admin/group-batch/80001/staff
|
||||
Request: staffList=[{staffId:40001, staffRole:"DRIVER"}]
|
||||
→ 拒绝 582120(司机由车务派车自动带入...)✓
|
||||
|
||||
PUT /v3/admin/group-batch/80001/staff
|
||||
Request: scopeRoles=["GUIDE"], staffList=[...](缺 LEADER)
|
||||
→ 拒绝 582116(导游位成员缺失...)✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|-----------|
|
||||
| #8669 | #8653, #8654 | 本次变更:司机投影落地,DRIVER 改系统管理 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [#8653](https://git.1814.love:8443/wx/HL/issues/8653), [#8654](https://git.1814.love:8443/wx/HL/issues/8654)
|
||||
- 关联 PR: [#8669](https://git.1814.love:8443/wx/HL/pulls/8669)
|
||||
- Merge commit: [5c50782717d6](https://git.1814.love:8443/wx/HL/commit/5c50782717d6)
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8653](https://git.1814.love:8443/wx/HL/issues/8653) / [#8654](https://git.1814.love:8443/wx/HL/issues/8654)
|
||||
- **PR**: [#8669](https://git.1814.love:8443/wx/HL/pulls/8669)
|
||||
- **Merge commit**: [5c50782717d6](https://git.1814.love:8443/wx/HL/commit/5c50782717d6)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8655"
|
||||
title: "资金统计某日资金明细页补筛选控件对接指引(科目/账户/收支),接口已支持零改动"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "e4de4c1f600d4a0738be9bbb207407e3823b6f2a"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-09-30"
|
||||
status_note: "资金账户→资金统计点某日进明细页目前只有日期+分页,缺科目/账户/收支筛选控件。核对后端明细接口 GET /admin/finance/fund-flows/page:bizType(科目)/fundAccountId(账户)/direction(收支)/flowAtStart/flowAtEnd 等筛选早已支持,出参行已带 bizTypeName/accountName 中文。本文档纯对接指引、接口零改动,告知前端现有筛选参数+三个下拉数据源(科目/收支硬编码枚举、账户调 fund-accounts/page?status=ACTIVE),前端补渲染筛选控件即可。;前端已交付:三筛选控件实证已在,补 FUND_FLOW_BIZ 缺 ORDER_REFUND(14 值)+账户下拉 status=ACTIVE,17 例定向全绿"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】资金统计某日资金明细页补筛选控件对接指引(科目/账户/收支) (#8655)
|
||||
|
||||
> **PR**: 无(纯对接指引,接口未改动) | **服务**: hl-finance(编译进 hl-order-service-v3) | **更新时间**: 2026-09-30 14:00
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
资金账户 → 资金统计查询,点击某日跳到「资金明细」页时,目前只带了日期(`flowAtStart`/`flowAtEnd`)+ 分页参数,**页面上没有科目、账户、收支的筛选控件**。
|
||||
|
||||
经核对后端明细接口,这些筛选条件**接口全部已支持**,后端无需任何改动。本文档是**前端对接指引**:告知明细接口现有可用筛选参数 + 三个下拉的数据源,前端在明细页补渲染筛选控件即可。
|
||||
|
||||
> ⚠️ 本接口本次**无任何改动**(入参/出参/枚举/错误码均不变),仅是把「早已支持但前端没用上」的筛选参数同步给前端。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 全账户资金流水分页 | GET | `/admin/finance/fund-flows/page` | 无变更(对接指引) | 现有筛选参数梳理,前端补控件 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 全账户资金流水分页(逐笔含结存快照)
|
||||
|
||||
- **使用场景**:资金账户 → 资金统计 → 点某日 → 查当日资金明细流水;也可按科目/账户/收支组合筛选
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等性**:是(GET 查询)
|
||||
- **限流**:无
|
||||
|
||||
#### 入参(Query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `flowAtStart` | String(date) | ❌ | 收付日期起 yyyy-MM-dd;点某日明细时与 flowAtEnd 传同一天 |
|
||||
| `flowAtEnd` | String(date) | ❌ | 收付日期止 yyyy-MM-dd |
|
||||
| `bizType` | String | ❌ | **科目筛选**:业务类型,单值,取值见 §6.1 |
|
||||
| `fundAccountId` | String(Long) | ❌ | **账户筛选**:单个账户 ID(下拉数据源见 §6.3) |
|
||||
| `accountType` | String | ❌ | 账户类型批量筛选(备选):`BANK`/`CASH`/`THIRD_PARTY`/`INTERNAL_VIRTUAL`,筛该类账户的全部流水 |
|
||||
| `direction` | String | ❌ | **收支筛选**:`IN` 收入(入账)/ `OUT` 支出(出账) |
|
||||
| `flowNo` | String | ❌ | 流水号模糊(可选) |
|
||||
| `bizId` | String(Long) | ❌ | 业务单据 ID(按单据捞明细,与 bizType 组合,可选) |
|
||||
| `pageNo` | Integer | ✅ | 页码,从 1 开始 |
|
||||
| `pageSize` | Integer | ✅ | 每页条数 |
|
||||
|
||||
> 说明:`fundAccountId`(单账户)与 `accountType`(按类型)二选一即可,都用则同时生效(交集)。
|
||||
|
||||
#### 出参(`Result<PageResult<行>>`)
|
||||
|
||||
每行流水字段(前端直接渲染,**业务类型中文、账户名后端已带,无需前端再翻译**):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 流水 ID |
|
||||
| `flowNo` | String | 流水号 |
|
||||
| `fundAccountId` | String | 账户 ID |
|
||||
| `accountName` | String | **账户名称(已带,直接渲染)** |
|
||||
| `accountType` | String | 账户类型码值 |
|
||||
| `direction` | String | 方向 `IN`/`OUT` |
|
||||
| `amount` | Number | 金额 |
|
||||
| `bizType` | String | 业务类型码值 |
|
||||
| `bizTypeName` | String | **业务类型中文名(已带,直接渲染,如 ORDER_REFUND=订单退款)** |
|
||||
| `bizId` | String | 关联业务单据 ID |
|
||||
| `bizNo` | String | 业务单据号(手写动作 TRANSFER/INVENTORY/OPENING 恒为 null) |
|
||||
| `balanceAfter` | Number | 本笔记完后账户结存快照 |
|
||||
| `transferGroupId` | String | 互转成对组号(仅 TRANSFER) |
|
||||
| `fee` | Number | 手续费(仅 TRANSFER 转出行) |
|
||||
| `counterparty` | String | 对方户名(已脱敏) |
|
||||
| `flowAt` | String | 收付时间 yyyy-MM-dd HH:mm:ss |
|
||||
| `voucherUrl` | String | 回单凭证影像 |
|
||||
| `remark` | String | 备注 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 bizType(科目,FundFlowBizTypeEnum)
|
||||
|
||||
**所属字段**:入参 `bizType` / 出参 `bizType`、`bizTypeName` | **类型**:`String` | **必填**:❌
|
||||
|
||||
流水无数据字典,中文名后端固定,前端科目下拉**直接硬编码这张映射**(出参 `bizTypeName` 已带中文,列表无需前端 map):
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PAYMENT` | 付款 | 应付款 |
|
||||
| `PREPAY` | 预付 | 预付款 |
|
||||
| `EXPENSE` | 费用 | 费用报销 |
|
||||
| `REIMBURSE` | 报账 | 报账款 |
|
||||
| `RECEIPT` | 收款确认 | 代收上交 |
|
||||
| `STAFF_LOAN` | 员工借款 | 付讫 OUT / 还款 IN |
|
||||
| `COMPANY_LOAN` | 公司借款 | 借出 OUT / 借入 IN,归还/收回反向 |
|
||||
| `NONBIZ` | 业务外收支 | 收入 IN / 支出 OUT |
|
||||
| `ADVANCE` | 司导预支 | 预支出账流水 |
|
||||
| `TRANSFER` | 账户互转 | 成对,含商户号提现归集 THIRD_PARTY→BANK |
|
||||
| `ORDER_PAY` | 对公收款 | 订单支付流水,自动生成不经出纳 |
|
||||
| `ORDER_REFUND` | 订单退款 | OUT 流水,自动生成不经出纳 |
|
||||
| `INVENTORY` | 盘盈盘亏 | SURPLUS→IN / DEFICIT→OUT |
|
||||
| `OPENING` | 期初调整 | IN=调高 / OUT=调低 |
|
||||
|
||||
### 6.2 direction(收支)
|
||||
|
||||
**所属字段**:入参/出参 `direction` | **类型**:`String` | **必填**:❌
|
||||
|
||||
前端收支下拉固定两项,硬编码:
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `IN` | 收入 | 入账 |
|
||||
| `OUT` | 支出 | 出账 |
|
||||
|
||||
### 6.3 账户下拉数据源(fundAccountId 选项)
|
||||
|
||||
调账户档案列表接口取账户选项:
|
||||
|
||||
`GET /admin/finance/fund-accounts/page?status=ACTIVE&pageSize=100`
|
||||
|
||||
- `status=ACTIVE` 只拉启用账户(停用账户不出现)
|
||||
- 返回行含 `id`(作为 `fundAccountId` 值)、`accountName`、`accountTypeName`(现金/银行/第三方支付),可直接做下拉显示
|
||||
- 该接口自身也支持 `accountType`/`nature`/`keyword` 入参,可用于账户下拉的级联筛选
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本接口为查询接口,无业务错误码;参数非法(如日期格式错误)返回通用 400。无新增/变更。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:查某日 + 科目=业务外收支 + 收入
|
||||
|
||||
**请求**:
|
||||
|
||||
```
|
||||
GET /admin/finance/fund-flows/page?flowAtStart=2026-09-30&flowAtEnd=2026-09-30&bizType=NONBIZ&direction=IN&pageNo=1&pageSize=10
|
||||
Authorization: Bearer {admin-token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 1,
|
||||
"records": [
|
||||
{
|
||||
"id": "1962000000000000631",
|
||||
"flowNo": "LS202609300001",
|
||||
"fundAccountId": "1962000000000000503",
|
||||
"accountName": "工商银行海拉尔支行基本户",
|
||||
"accountType": "BANK",
|
||||
"direction": "IN",
|
||||
"amount": 990.00,
|
||||
"bizType": "NONBIZ",
|
||||
"bizTypeName": "业务外收支",
|
||||
"bizId": "1962000000000000903",
|
||||
"bizNo": "WS-20260930-001",
|
||||
"balanceAfter": 582995.00,
|
||||
"transferGroupId": null,
|
||||
"fee": null,
|
||||
"counterparty": "某某单位",
|
||||
"flowAt": "2026-09-30 10:00:00",
|
||||
"voucherUrl": null,
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:按账户类型批量筛 + 支出
|
||||
|
||||
**请求**:
|
||||
|
||||
```
|
||||
GET /admin/finance/fund-flows/page?accountType=BANK&direction=OUT&pageNo=1&pageSize=10
|
||||
Authorization: Bearer {admin-token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:结构同上,`records` 为所有银行账户的支出流水(空则 `records: []`、`total: 0`)。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 所有筛选参数均可选、可任意组合,均不传=全量资金流水分页(flowAt 倒序)
|
||||
- ✅ 点某日明细:`flowAtStart` 与 `flowAtEnd` 传同一天
|
||||
- ⚠️ `accountType` 筛选是「该类型全部账户的流水」,流水表不存账户类型,后端先反查该类型账户 ID 集再过滤
|
||||
- ⚠️ `fundAccountId` 与 `accountType` 同时传时取交集
|
||||
|
||||
## 11. 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否(接口零改动)
|
||||
- **前端是否必须同步上线**:否(前端按需补筛选控件即可,不补也不影响现有功能)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 本接口本次无任何改动,本文档仅为前端补筛选控件提供对接参数与数据源。
|
||||
- 科目(bizType)与收支(direction)下拉前端硬编码 §6.1/§6.2 映射即可;账户下拉调 §6.3 接口取启用账户。
|
||||
- 流水行出参已带 `bizTypeName`/`accountName` 中文,前端列表直接渲染,无需维护码值→中文 map。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8655](https://git.1814.love/wx/HL/issues/8655)
|
||||
- **PR**: 无(纯对接指引,无代码改动)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8657"
|
||||
title: "收款方类型 payeeType 枚举重构:删 STAFF,个人类拆为 GUIDE/DRIVER/PHOTOGRAPHER/LEADER(导游/司机/摄影/领队),保留 FLEET/GUIDE_CO/SUPPLIER(#8657)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "adfd68151163824338fa4ee6faef38c84a5bb006"
|
||||
target_release: ""
|
||||
verified_at: "2026-09-30"
|
||||
status_note: "财务「资金账户→收款账户」的「收款方类型」(payeeType,枚举非数据字典)重理口径。原 STAFF 标「司导/员工个人」表述错误:司导(导游/司机/摄影/领队)是带团服务人员、非内部员工(与司导往来账 GuideStaffRoleEnum 同源口径,员工借款不纳入)。个人类按带团角色拆为 GUIDE/DRIVER/PHOTOGRAPHER/LEADER 四类;FLEET 车队/GUIDE_CO 导游公司/SUPPLIER 供应商三个组织类保留。⚠️破坏性:删除 STAFF 枚举值——前端若写死 STAFF 选项需清理;新增 4 个个人类值需补充到下拉选项与 label 映射。后端校验 PayeeTypeEnum.of() 随枚举自动生效,传 STAFF 现报非法。;前端已交付:PAYEE_TYPES 删 STAFF 拆 GUIDE/DRIVER/PHOTOGRAPHER/LEADER 四角色(label §12 映射),checkpoint 全量档全绿"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:收款方类型 payeeType 枚举重构(管理后台)
|
||||
|
||||
> ⚠️ **破坏性变更**:`payeeType` 删除枚举值 `STAFF`,新增 `GUIDE`/`DRIVER`/`PHOTOGRAPHER`/`LEADER`。前端如有写死的 payeeType 选项/label 映射需同步更新。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务「资金账户 → 收款账户」登记收款方账户时,「收款方类型」(`payeeType`)标识「钱打给谁的结算主体」。
|
||||
|
||||
原枚举 `STAFF` 注释中文标「司导/员工个人」——**表述错误**:司导(导游/司机/摄影/领队)是带团服务人员,**非内部员工**(与司导往来账口径一致,员工借款不纳入司导范围)。且「个人」粒度过粗。
|
||||
|
||||
本次把个人类按带团角色直接拆分为 **司机/导游/摄影/领队** 四类,与 `GuideStaffRoleEnum`(司导角色)同源。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 收款方账户分页 | GET | `/admin/finance/payee-accounts/page` | 修改接口 | `payeeType` 过滤值集合变化 |
|
||||
| 2 | 登记收款方账户 | POST | `/admin/finance/payee-accounts` | 修改接口 | `payeeType` 入参枚举值集合变化 |
|
||||
|
||||
> 两个接口路径/方法/其他字段不变,仅 `payeeType` 字段的**合法取值集合**变化。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 收款方账户分页
|
||||
|
||||
- **使用场景**:收款账户列表,按收款方类型/名称/账户类型过滤
|
||||
- **认证**:JWT(管理后台)
|
||||
- **入参(query)**:`payeeType`(可选过滤,取值见 §6)/ `payeeName`(名称模糊)/ `accountType` / `pageNo` / `pageSize`
|
||||
- **出参**:`data.records[].payeeType` 返回枚举码(GUIDE/DRIVER/...)
|
||||
|
||||
### 3.2 登记收款方账户
|
||||
|
||||
- **使用场景**:新增一条收款方账户(个人码/银行卡/对公)
|
||||
- **认证**:JWT(管理后台)
|
||||
- **入参(body)**:`payeeType`(必填,取值见 §6)/ `payeeRefId` / `payeeName` / `accountType` / `qrUrl` / `bankAccount` / `bankName` / `isDefault`
|
||||
- **校验**:`payeeType` 非法(含已删除的 `STAFF`)→ `595202`
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 请求体关键字段(登记)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| payeeType | string | 是 | 收款方类型(见 §6) | 须为合法枚举值,否则 595202 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| payeeType | string | 收款方类型枚举码(见 §6) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 payeeType(收款方类型,PayeeTypeEnum)
|
||||
|
||||
**所属字段**:`payeeType` | **类型**:`String` | **必填**:✅(登记时)
|
||||
|
||||
| 值 | 中文 | 类别 | 说明 |
|
||||
|----|------|------|------|
|
||||
| `GUIDE` | 导游 | 个人 | 🆕 打给导游个人(非内部员工) |
|
||||
| `DRIVER` | 司机 | 个人 | 🆕 打给司机个人(非内部员工) |
|
||||
| `PHOTOGRAPHER` | 摄影 | 个人 | 🆕 打给摄影个人(非内部员工) |
|
||||
| `LEADER` | 领队 | 个人 | 🆕 打给领队个人(非内部员工) |
|
||||
| `FLEET` | 车队 | 组织 | 司机挂车队,打给车队再分(保留) |
|
||||
| `GUIDE_CO` | 导游公司 | 组织 | 导游挂公司,打给公司再分(保留) |
|
||||
| `SUPPLIER` | 供应商 | 组织 | 预留(保留) |
|
||||
| ~~`STAFF`~~ | ~~司导/员工个人~~ | — | ❌ **已删除**(语义混乱:司导非员工) |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 595202 | 收款方类型/账户类型非法等组合校验失败 | `payeeType` 传非法值(含已删除的 `STAFF`) |
|
||||
|
||||
## 8. 示例(3 组)
|
||||
|
||||
### 8.1 典型成功(登记导游个人收款账户)
|
||||
|
||||
**请求** `POST /admin/finance/payee-accounts`:
|
||||
```json
|
||||
{
|
||||
"payeeType": "GUIDE",
|
||||
"payeeName": "张三",
|
||||
"accountType": "WECHAT_QR",
|
||||
"qrUrl": "https://oss.example.com/qr/zhangsan.png",
|
||||
"isDefault": 1
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "id": "2105..." }, "success": true }
|
||||
```
|
||||
|
||||
### 8.2 边界(车队组织账户)
|
||||
|
||||
**请求** `POST /admin/finance/payee-accounts`:
|
||||
```json
|
||||
{
|
||||
"payeeType": "FLEET",
|
||||
"payeeName": "XX 车队",
|
||||
"accountType": "CORP_ACCOUNT",
|
||||
"bankAccount": "6222...",
|
||||
"bankName": "工商银行海拉尔支行"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "id": "2105..." }, "success": true }
|
||||
```
|
||||
|
||||
### 8.3 业务失败(传已删除的 STAFF)
|
||||
|
||||
**请求** `POST /admin/finance/payee-accounts`:
|
||||
```json
|
||||
{
|
||||
"payeeType": "STAFF",
|
||||
"payeeName": "张三",
|
||||
"accountType": "WECHAT_QR"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 595202, "message": "收款方类型非法", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 个人类收款方:用 `GUIDE`/`DRIVER`/`PHOTOGRAPHER`/`LEADER`(按带团角色选)。
|
||||
- ✅ 组织类收款方:司机挂车队用 `FLEET`、导游挂公司用 `GUIDE_CO`。
|
||||
- ❌ `STAFF` 已不可用,传了报 `595202`。
|
||||
- ⚠️ 内部员工(非带团服务人员)不在本收款方类型范围;员工借款走单独的 fin_staff_loan 流程,不在此登记。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 枚举值集合对比
|
||||
|
||||
| 类别 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 个人 | `STAFF`(司导/员工,1 个粗粒度值) | `GUIDE`/`DRIVER`/`PHOTOGRAPHER`/`LEADER`(4 个角色值) |
|
||||
| 组织 | `FLEET`/`GUIDE_CO`/`SUPPLIER` | 不变 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 传 `STAFF` 登记 | 合法 | 报 `595202` 非法 |
|
||||
| 个人类收款方粒度 | 只有「司导/员工」一项 | 按导游/司机/摄影/领队四角色细分 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:**是**(删 `STAFF` 枚举值)。
|
||||
- **前端是否必须同步上线**:是。前端如有写死的 `payeeType` 选项/label 映射需更新:① 删 `STAFF` 选项;② 补 `GUIDE/DRIVER/PHOTOGRAPHER/LEADER` 四项及中文 label。
|
||||
- **影响已有数据**:测试库 `fin_payee_account` 无 STAFF 存量数据,无需迁移。
|
||||
- **回滚方式**:revert PR #8658。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **前端 workaround 清理点**:若前端此前把 `STAFF` 硬编码为唯一个人类选项,请改为按导游/司机/摄影/领队四项。
|
||||
- 中文 label 由前端映射(后端只下发枚举码):`GUIDE=导游 / DRIVER=司机 / PHOTOGRAPHER=摄影 / LEADER=领队 / FLEET=车队 / GUIDE_CO=导游公司 / SUPPLIER=供应商`。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8657](https://git.1814.love/wx/HL/issues/8657)
|
||||
- **PR**: [#8658](https://git.1814.love/wx/HL/pulls/8658)
|
||||
- **Merge commit**: [8c7520d0da](https://git.1814.love/wx/HL/commit/8c7520d0da)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 待认领
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8663"
|
||||
title: "公司借款域:往来单位放开员工类型 + 归还菜单归支付管理 + 归还列表改双 tab(#8663)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "305ba037d1a184c9bd6bc131d004abd1f1bd6349"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "2026-10-01 hl-admin 交付:①登记表单加单位类型 select(字典 fin_unit_type 优先+本地两码兜底),SUPPLIER 走供应商弹窗/STAFF 走 employee-options(adminId 作 unitId),切类型清空已选防串单,payload 带 unitType,列表单位类型列补 STAFF 中文;②Cashier 聚合行 unitType 带回明细查询与 settle(599312 拦截器透 message);③页体 git mv payable/company-loan-repay→pay/company-loan-repay+路由 spec 同步;④IN 收银台 n-tabs 双 tab,已还台账=repays/page?direction=IN 手工分页+只读查看弹窗。4 spec 45 例全绿,ref 305ba037。公司借款域四项调整。①借入/借出往来单位放开单位类型(unitType),供应商 SUPPLIER 与员工 STAFF 二选一,按类型联动供应商/员工选择框;②归还/回收结算请求 VO 新增可选入参 unitType(连同 unitId 一起校验归属,防两套 ID 空间同数值串单),additive 兼容不传按旧口径;③「公司借款归还」菜单从付款管理搬到支付管理(path /finance/payable/company-loan-repay → /finance/pay/company-loan-repay),前端路由需同步,否则点菜单 404;④借入列表删「去还款」按钮、归还列表改「待还款/已还台账」双 tab(后端零新增接口,复用现成收银台与流水接口)。新增数据字典 fin_unit_type(供应商/员工)。settle 单位一致性守卫加 unit_type 比对,勾选单与入参单位类型不符报 599312。"
|
||||
updated_at: "2026-09-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:公司借款域往来单位放开员工类型 + 归还归支付管理 + 双 tab(管理后台)
|
||||
|
||||
> ⚠️ **菜单路由变更**:「公司借款归还」从付款管理搬到支付管理,path 由 `/finance/payable/company-loan-repay` 改为 `/finance/pay/company-loan-repay`。前端路由必须同步,否则点菜单 404。
|
||||
> ⚠️ **入参变更**:结算接口新增可选 `unitType`;**出参变更**:列表/聚合/流水行新增 `unitType` 字段;**枚举变更**:新增 `FinUnitTypeEnum`(SUPPLIER/STAFF)+ 数据字典 `fin_unit_type`。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务「收款管理/公司借入」登记公司向外部单位借入的款项,「支付管理/公司借款归还」由出纳按单位聚合归还。原实现往来单位**只支持供应商**(unit_type 恒 SUPPLIER),业务需支持员工;且「归还」本质是出纳选付款账户实付,原挂在付款管理(台账/审批层)不对称;已核销单从归还列表消失无处查看。
|
||||
|
||||
本次合并处理四块:①往来单位放开单位类型(供应商/员工)+ 选择框联动;②结算守卫防供应商/员工 ID 同数值串单;③归还菜单归支付管理;④借入列表删还款按钮、归还列表改双 tab。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 项 | 变更 | 端点 |
|
||||
|---|---|---|---|
|
||||
| 1 | 往来单位类型 | 新增可选入参 `unitType`(SUPPLIER/STAFF,默认 SUPPLIER),出参行新增 `unitType` | 创建/列表/回收聚合/归还聚合 |
|
||||
| 2 | 结算归属校验 | 新增可选入参 `unitType`,连同 `unitId` 一起校验 | repay-cashier/settle、recover/settle |
|
||||
| 3 | 菜单迁移 | 归还菜单付款管理→支付管理,path 变更 | (前端路由) |
|
||||
| 4 | 列表职责 | 借入列表删「去还款」按钮;归还列表改「待还款/已还台账」双 tab | (前端 UI,后端复用现成接口) |
|
||||
| 5 | 数据字典 | 新增 `fin_unit_type`:SUPPLIER 供应商 / STAFF 员工 | dict/data/fin_unit_type |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
统一前缀 `POST|GET /admin/finance/company-loans/**`(业务调用**不带** `/hl-order-service-v3` 前缀,网关按 `/admin/finance/**` 路由)。
|
||||
|
||||
## 4. 入参
|
||||
|
||||
### 4.1 创建借入 `POST /admin/finance/company-loans`
|
||||
```json
|
||||
{
|
||||
"direction": "IN",
|
||||
"unitType": "SUPPLIER 或 STAFF(可选,默认 SUPPLIER)",
|
||||
"unitId": "按类型:供应商ID 或 员工 adminId",
|
||||
"handlerStaffId": "经办人 adminId(必填)",
|
||||
"amount": "借款金额>0",
|
||||
"feeRate": "手续费率‰,空按0",
|
||||
"loanDate": "借款日期",
|
||||
"dueDate": "约定归还日期(必填)",
|
||||
"purpose": "借款用途(必填)"
|
||||
}
|
||||
```
|
||||
> `unitName` / `handlerStaffName` 不用前端传(服务端反查覆盖快照,前端传值不生效)。
|
||||
|
||||
### 4.2 归还结算 `POST /admin/finance/company-loans/repay-cashier/settle`
|
||||
```json
|
||||
{
|
||||
"unitId": "单位ID",
|
||||
"unitType": "SUPPLIER 或 STAFF(建议必传,防串单)",
|
||||
"loanIds": ["整笔归还的借入单ID,≥1,每笔还欠还全额"],
|
||||
"fundAccountId": "出账资金账户ID(必填,出纳选从哪张卡出钱)",
|
||||
"feeRate": "手续费率‰,默认0",
|
||||
"voucherUrl": "付款凭证影像URL"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 回收结算 `POST /admin/finance/company-loans/recover/settle`
|
||||
同 4.2 结构(借出方向回收),`unitType` 同为可选。
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 借款行 `CompanyLoanRowRespVO`(GET /page)
|
||||
`id, loanNo, direction, unitType🆕, unitId, unitName, handlerStaffId, handlerStaffName, amount, repaidAmount, outstandingAmount, purpose, loanDate, dueDate, status, operatorName, createTime`(ID 均字符串)
|
||||
|
||||
### 5.2 归还单位聚合 `CompanyLoanRepayUnitRespVO`(GET /repay-cashier/units)
|
||||
`unitType🆕, unitId, unitName, loanCount, totalOutstanding, nearestDueDate`
|
||||
|
||||
### 5.3 还款流水行 `CompanyLoanRepayRowRespVO`(GET /repays/page)
|
||||
`id, repayNo(GH-前缀), loanId, loanNo, direction, unitName, amount, fundAccountId, repaidAt, voucherUrl`
|
||||
|
||||
## 6. 枚举/数据字典
|
||||
|
||||
### FinUnitTypeEnum(往来单位类型,枚举)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| SUPPLIER | 供应商(资源域 supplier_main) |
|
||||
| STAFF | 员工(用户域 admin_user,名称取企微快照) |
|
||||
|
||||
### 数据字典 `fin_unit_type`
|
||||
`GET /admin/dict/data/fin_unit_type` →
|
||||
```json
|
||||
[{"dictLabel":"供应商","dictValue":"SUPPLIER"},{"dictLabel":"员工","dictValue":"STAFF"}]
|
||||
```
|
||||
|
||||
### 借款状态 status(出参)
|
||||
APPROVED 已登记 / PAID 已收付 / SETTLING 核销中 / SETTLED 已核销
|
||||
|
||||
## 7. 错误码
|
||||
| 码 | 含义 |
|
||||
|---|---|
|
||||
| 599304 | 往来单位非法(不存在/不可选/类型非法/员工未登记真名) |
|
||||
| 599305 | 经办人非法(不存在或未登记真名) |
|
||||
| 599312 | 勾选单与入参单位不一致(unit_id 或 unit_type 不符,防串单) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:创建员工类型借入
|
||||
```http
|
||||
POST /admin/finance/company-loans
|
||||
{"direction":"IN","unitType":"STAFF","unitId":"2021059720172838914","handlerStaffId":"2085621600417148929","amount":5000,"loanDate":"2026-09-30","dueDate":"2026-10-30","purpose":"员工临时周转"}
|
||||
→ {"code":0,"data":{"id":"...","loanNo":"GS-202609300005"}}
|
||||
```
|
||||
|
||||
### 8.2 边界:归还按单位聚合(待还款 tab)
|
||||
```http
|
||||
GET /admin/finance/company-loans/repay-cashier/units?unitName=王
|
||||
→ {"code":0,"data":[{"unitType":"STAFF","unitId":"2021...","unitName":"王骁","loanCount":1,"totalOutstanding":5000,"nearestDueDate":"2026-10-30"}]}
|
||||
```
|
||||
点单位看明细:`GET /repay-cashier/units/{unitId}?unitType=STAFF`(**unitType 必传**,从聚合行带回)。
|
||||
|
||||
### 8.3 异常:结算单位类型不符
|
||||
```http
|
||||
POST /repay-cashier/settle {"unitId":"X","unitType":"STAFF","loanIds":[...], "fundAccountId":"..."}
|
||||
# 该 unitId 实为 SUPPLIER 类型借款
|
||||
→ {"code":599312,"msg":"勾选单与入参单位不一致"}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 借入列表(收款管理)`GET /page?direction=IN`:`status` 不传即全量(含 SETTLED),删「去还款」按钮后纯台账。
|
||||
- 待还款 tab 复用 `/repay-cashier/units` + `/repay-cashier/units/{unitId}`,**只返回未核销单**(PAID/SETTLING),已核销不出现。
|
||||
- 已还台账 tab 用 `GET /repays/page?direction=IN`,每行一笔已还款事实,只读「查看」。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 往来单位类型 | 恒供应商 | 供应商/员工二选一(unitType 联动选择框) |
|
||||
| 归还菜单位置 | 付款管理/公司借款归还 | 支付管理/公司借款归还 |
|
||||
| 菜单 path | /finance/payable/company-loan-repay | /finance/pay/company-loan-repay |
|
||||
| 借入列表 | 行有「去还款」按钮 | 删按钮,纯台账 |
|
||||
| 归还列表 | 已核销单消失无处查 | 待还款/已还台账双 tab |
|
||||
| settle 归属校验 | 只比 unit_id | 加 unit_type 比对(防串单) |
|
||||
|
||||
## 11. 影响评估/回滚
|
||||
|
||||
- 入参 `unitType` 为可选 additive,不传按旧口径(SUPPLIER)兼容,旧前端不炸。
|
||||
- 出参新增 `unitType` 字段为 additive,旧前端忽略即可。
|
||||
- **菜单 path 变更为破坏性**:前端路由必须同步,否则点菜单 404。
|
||||
- 回滚:菜单迁移走 sys_menu UPDATE(带守卫+幂等),可回写旧 path。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- ⚠️ 归还单单位明细接口 `unitType` **建议必传**(防供应商/员工 ID 同数值串单),从聚合行带回。
|
||||
- 员工选择框数据源 `GET /admin/user/employee-options`(取 `adminId` 作 unitId,`enterpriseWechatName` 为空回落 `username`)。
|
||||
- 供应商选择框数据源 `GET /admin/supplier/items/page?status=ACTIVE`(取 `supplierId` 作 unitId)。
|
||||
- 员工未登记企微真名时报 599304(后端 fail-fast)。
|
||||
|
||||
## 13. 关联/联系人
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8663
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8668
|
||||
- merge commit:154e2ac75a
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,209 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8664"
|
||||
title: "公司借款收回新增单笔形态:POST /admin/finance/company-loans/{id}/recover"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "c32b35c9527dde6bdc21386e1c6d47ad1f33bb96"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-10-01"
|
||||
status_note: "公司借款「借出方向」的收回(借出的钱到期收回)新增单笔形态——已付款借出单按单笔收款,与既有按单位合并收款(§1.4.3 recover/settle)共用 settle 核心。原型收回入口并入「支付管理/公司借款支付」已付款台账行「收款登记」;原「收款管理/公司借款收回」按单位汇总收银台原型入口下线(合并收款三端点后端保留可用)。前端已交付:入口挂「支付管理/公司借款支付」已付款台账行「收款登记」,弹窗拉详情默认欠收全额可改部分,fee 内扣预览,提交即禁按钮防连点,SETTLED 禁提交,收齐按 settledLoanIds 提示核销并刷台账;unitId/unitType 不入参。(此前 frontmatter 误标 implemented 无 ref,本次实证交付后补齐。)"
|
||||
---
|
||||
|
||||
# 【新增接口·管理后台】公司借款单笔收回 POST /admin/finance/company-loans/{id}/recover (#8664)
|
||||
|
||||
> **PR**: #8670 | **服务**: hl-order-service-v3(hl-finance 模块,8086) | **更新时间**: 2026-09-30
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
公司借款「借出方向」(direction=OUT,公司借钱给供应商/员工)的到期收回,原只有「按单位汇总收银台」一种形态(勾选同一单位多笔借款单合并收款,§1.4.1-1.4.3)。本次按业务拍板新增**单笔形态**:在「支付管理 / 公司借款支付」的**已付款台账行**上对**单笔借出单**直接「收款登记」——已付款的借出单逐笔收回,无需按单位聚合勾选。
|
||||
|
||||
**只写"为什么",不涉及实现**:收回动作的入口从「按单位汇总收银台」下沉到「已付款借出单行」,更符合"只有已付款的台账才能收回"的业务直觉,也避免跨单位合并的复杂度(用户明确不需要跨单位合并收款,单笔即可)。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 公司借款单笔收回 | POST | `/admin/finance/company-loans/{id}/recover` | 新增接口 | 按单笔借款单收回,与合并收款共用 settle 核心 |
|
||||
|
||||
> 合并收款三端点(§1.4.1 可选借款单 / §1.4.2 手续费试算 / §1.4.3 合并收款提交)**保留可用**,本次仅新增单笔便捷形态。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 公司借款单笔收回
|
||||
|
||||
- **使用场景**:出纳在「支付管理 / 公司借款支付」的已付款台账,对某笔 direction=OUT(借出)且未收齐的借款单做收回登记(收欠收全额或部分金额)。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:否(每次调用产生一笔还款流水 GH- + 一笔资金流水 IN + 一条 RECOVER 操作流水;重复提交会重复入账,前端提交后应禁用按钮防连点)。
|
||||
- **限流**:无。
|
||||
|
||||
**行为**:按本单收回指定金额 → 写还款流水(GH- 取号)+ 资金流水单笔 IN(bizType=COMPANY_LOAN)+ RECOVER 操作流水 + CAS 状态重算(收齐转已核销)+ 联动入账账户结存。与合并收款 §1.4.3 共用 `recoverSettle` settle 核心,守卫完全一致。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | Long | ✅ | 借款单ID(path) |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| `amount` | BigDecimal | ✅ | 本次收回金额 | >0 且 ≤ 该单剩余余额,否则 599309 |
|
||||
| `fundAccountId` | Long | ✅ | 入账资金账户ID | 须存在且 ACTIVE(fin_fund_account) |
|
||||
| `feeRate` | BigDecimal | ❌ | 手续费率(‰) | 默认 0;fee = amount × feeRate / 1000,实收 = amount − fee |
|
||||
| `voucherUrl` | String | ❌ | 收款凭证影像URL | — |
|
||||
|
||||
> ⚠️ **`unitId` / `unitType` 不入参**——后端从借款单反查归属单位,前端无需也不应传(防传错串单)。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
复用合并收款 `CompanyLoanRecoverSettleRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `totalAmount` | BigDecimal | 本次收回金额(=入参 amount) |
|
||||
| `actualAmount` | BigDecimal | 实收(= totalAmount − fee) |
|
||||
| `fee` | BigDecimal | 手续费 |
|
||||
| `repayIds` | Long[] | 还款流水ID列表(**单笔恒单元素**) |
|
||||
| `settledLoanIds` | Long[] | 本次收齐转已核销的借款单ID(收齐时含本单 id,未收齐为空) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本接口入参/出参无枚举字段。相关业务方向(由后端从借款单反查,前端不传):
|
||||
|
||||
### 6.1 direction(借款方向,借款单自身字段)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `OUT` | 借出 | 公司借钱给单位/员工,到期**收回**(本接口只收 OUT 单) |
|
||||
| `IN` | 借入 | 公司向单位/员工借钱,到期**归还**(走归还链路,非本接口) |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 599301 | 借款单不存在 | id 查无此单 |
|
||||
| 599310 | 借款方向不匹配 | 该单非 OUT(借出),借入单不能走收回 |
|
||||
| 599308 | 借款单已核销 | 已收齐核销的单不可再收 |
|
||||
| 599302 | 借款单状态不可收 | 非可收回状态(如待付款未放款) |
|
||||
| 599309 | 收回金额超剩余余额 | amount > 该单剩余余额 |
|
||||
| 599303 | 手续费率非法 | feeRate < 0 或 ≥ 1000 |
|
||||
| 599307 | 还款单号取号耗尽 | GH- 单号序列耗尽(极少) |
|
||||
| 595001 | 资金账户不存在 | fundAccountId 查无此账户 |
|
||||
| 595006 | 资金账户不可用 | 账户非 ACTIVE |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(足额收齐,无手续费)
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
POST /admin/finance/company-loans/101234/recover
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"amount": 5000.00,
|
||||
"fundAccountId": 88
|
||||
}
|
||||
```
|
||||
|
||||
**响应**(收齐转已核销):
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"totalAmount": 5000.00,
|
||||
"actualAmount": 5000.00,
|
||||
"fee": 0.00,
|
||||
"repayIds": [900001],
|
||||
"settledLoanIds": [101234]
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(部分收回 + 含手续费)
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"amount": 2000.00,
|
||||
"fundAccountId": 88,
|
||||
"feeRate": 5,
|
||||
"voucherUrl": "https://oss.example.com/voucher/x.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**(部分收回,settledLoanIds 为空;fee=2000×5/1000=10,实收 1990):
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"totalAmount": 2000.00,
|
||||
"actualAmount": 1990.00,
|
||||
"fee": 10.00,
|
||||
"repayIds": [900002],
|
||||
"settledLoanIds": []
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(超额收回)
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{ "amount": 99999.00, "fundAccountId": 88 }
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 599309, "msg": "收回金额超剩余余额", "data": null }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用**:direction=OUT(借出)、未收齐(核销中 / 部分核销)的借款单。
|
||||
- ❌ **不适用**:借入单(IN,走归还链路)→ 599310;已收齐核销单 → 599308;待付款未放款单 → 599302。
|
||||
- ⚠️ **特殊**:amount 收齐该单余额时本单转已核销(`settledLoanIds` 含本单 id);未收齐则保持核销中(`settledLoanIds` 为空),可再次调用收回剩余。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口,无修改前版本。与原「按单位合并收款」的关系:
|
||||
|
||||
| 维度 | 按单位合并收款(§1.4.3) | 单笔收回(本接口) |
|
||||
|------|--------------------------|--------------------|
|
||||
| 入口 | 勾选同单位多笔合并 | 单笔直接收 |
|
||||
| 入参 | unitId + items[](多单填额) | path id + 单 amount |
|
||||
| 还款流水 | 多单逐笔 | 单笔一条 |
|
||||
| 共用 | — | 与本接口共用 settle 核心,守卫一致 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:否(纯新增端点)。
|
||||
- **前端是否必须同步上线**:否(前端可在「公司借款支付」已付款行接入本接口实现单笔收款登记;不接入不影响既有合并收款)。
|
||||
- **回滚方式**:revert PR #8670 即可下线本端点,无数据迁移、无缓存清理。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **原型/菜单侧**:收回原型入口已并入「支付管理 / 公司借款支付」已付款台账行「收款登记」;原「收款管理 / 公司借款收回」按单位汇总收银台原型入口已下线。前端实现管理后台时,单笔收款登记建议挂在已付款借出单行(只有已付款台账能收回)。
|
||||
- 提交后请禁用按钮防连点(非幂等,重复提交会重复入账)。
|
||||
- 合并收款三端点后端保留可用,如未来仍提供按单位合并入口可继续用 §1.4.1-1.4.3。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**: [#8664](https://git.1814.love/wx/HL/issues/8664)
|
||||
- **PR**: [#8670](https://git.1814.love/wx/HL/pulls/8670)
|
||||
- **Merge commit**: [11a61d3739](https://git.1814.love/wx/HL/commit/11a61d3739)
|
||||
- **后端负责人**: @yst
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8671"
|
||||
title: "团期看板「核单」页签 opsStage=REVIEW 筛选口径扩为核单三态"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "04b5cca6bfa46f93711bb6cc9019e3b6e284488e"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
updated_at: "2026-10-01"
|
||||
status_note: "团期看板「核单」页签(opsStage=REVIEW)筛选范围扩大:从只含「核单中 REVIEWING」扩为「待核单 PENDING_REVIEW + 核单中 REVIEWING + 已结算 SETTLED」三态。前端继续传 opsStage=REVIEW 即可,无需改入参;但同入参返回的团期集合变大,核单页签会多看到待核单与已结算的团。前端已交付:核单页签带 productId 时列表请求显式传 scope=ALL(§8.2 缺省 ONGOING 会滤掉核单三态),其余桶维持不传 scope 口径,统计条不受影响。(此前 frontmatter 误标 implemented 无 ref,本次实证交付后补齐。)"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】团期看板「核单」页签筛选口径扩为核单三态 (#8671)
|
||||
|
||||
> **PR**: #8672 | **服务**: hl-order-service-v3(8086) | **更新时间**: 2026-10-01
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台「团期」看板的「核单」页签,业务上应只列出与核单相关的团期(待核单 / 核单中 / 已结算),把招募中、资源准备中、待出发、出行中、已取消等非核单团过滤掉。
|
||||
|
||||
此前「核单」页签(`opsStage=REVIEW`)只筛「核单中 REVIEWING」一个状态,导致:
|
||||
- **待核单**(已返团、尚未开始核单)的团看不到——它被归在「出行」页签下;
|
||||
- **已结算**的团看不到——它被归在「结算」页签下。
|
||||
|
||||
财务 / 运营在核单页签想统览「整个核单阶段」的团时,要么漏团,要么得把范围切到「全部」把无关团全拉进来。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期分页看板列表 | GET | `/v3/admin/order/group-batch` | 修改接口 | `opsStage=REVIEW` 筛选口径扩为核单三态 |
|
||||
|
||||
> 说明:本接口同时被 `/v3/admin/order/group-batch/board`(看板视图)复用同一筛选口径,行为一致变化。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 团期分页看板列表
|
||||
|
||||
- **使用场景**:管理后台团期看板分页查询,前端点各页签(招募/配置/确认/出行/核单/结算/已流团)时传对应 `opsStage` 筛选。
|
||||
- **认证**:需 JWT(管理后台管理员)。
|
||||
- **幂等性**:查询接口,天然幂等。
|
||||
- **限流**:无。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 Query 参数(仅列本次相关)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `opsStage` | String | ❌ | 看板桶筛选。本次仅 `REVIEW` 一档的展开口径变化,其余桶不变 |
|
||||
| `pageNo` | Integer | ❌ | 页码,默认 1 |
|
||||
| `pageSize` | Integer | ❌ | 每页条数 |
|
||||
| `scope` | String | ❌ | 班期范围(ONGOING/FINISHED/ALL)。⚠️ 见 §9 边界:核单三态返团日已过,默认 ONGOING 会滤掉,需传 ALL |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
出参字段结构**完全不变**(仍是团期分页 `records[]`,含 `groupBatchId`/`batchNo`/`batchStatus`/`reviewStatus`/`settlementStatus`/`stage`/`stageName` 等)。本次只改「哪些团期被筛进结果集」,不改任何返回字段。
|
||||
|
||||
> ⚠️ 核单页签内不同行的 `stage` / `stageName`(节点标签)**可能不统一**:待核单的团节点仍显示「出行」、已结算的团节点仍显示「结算」。这是刻意的——本次只扩筛选范围,不动看板六节点归属模型。前端如对节点标签有统一展示诉求,需另行提需求。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 opsStage(看板桶筛选值)
|
||||
|
||||
**所属字段**:`opsStage` | **类型**:`String` | **必填**:❌
|
||||
|
||||
| 值 | 中文 | 展开为哪些团期状态 | 本次是否变化 |
|
||||
|----|------|--------------------|--------------|
|
||||
| `RECRUIT` | 招募 | RECRUITING | 否 |
|
||||
| `CONFIGURE` | 配置 | RESOURCE_PREPARING | 否 |
|
||||
| `CONFIRM` | 确认 | MATERIAL_PREPARING | 否 |
|
||||
| `TRIP` | 出行 | PENDING_DEPARTURE + TRAVELLING + PENDING_REVIEW | 否 |
|
||||
| `REVIEW` | 核单 | **PENDING_REVIEW + REVIEWING + SETTLED** | ✅ 变化 |
|
||||
| `SETTLE` | 结算 | SETTLED | 否 |
|
||||
| `DISBANDED` | 已流团 | CANCELLED | 否 |
|
||||
|
||||
### 6.2 batchStatus(团期九态,出参)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `RECRUITING` | 招募中 | — |
|
||||
| `RESOURCE_PREPARING` | 资源准备中 | — |
|
||||
| `MATERIAL_PREPARING` | 物料准备中 | — |
|
||||
| `PENDING_DEPARTURE` | 待出发 | — |
|
||||
| `TRAVELLING` | 出行中 | — |
|
||||
| `PENDING_REVIEW` | 待核单 | 返团后尚未开始核单 |
|
||||
| `REVIEWING` | 核单中 | — |
|
||||
| `SETTLED` | 已结算 | — |
|
||||
| `CANCELLED` | 已取消 | 流团 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本接口无新增错误码;`opsStage` 传非法值时忽略该筛选并记 warn(不报错、不返 400)。
|
||||
|
||||
## 8. 示例(典型 / 边界)
|
||||
|
||||
### 8.1 典型:核单页签查询
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /v3/admin/order/group-batch?pageNo=1&pageSize=20&opsStage=REVIEW&scope=ALL
|
||||
Authorization: Bearer {adminToken}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**(返回核单中 + 已结算两类团期;当前库暂无「待核单」团,故为 2 条):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 2,
|
||||
"records": [
|
||||
{ "groupBatchId": "2105223439872933889", "batchNo": "T26-2325", "batchStatus": "REVIEWING", "batchStatusName": "核单中", "stage": "REVIEW", "stageName": "核单", "reviewStatus": "COMPLETED", "reviewStatusName": "已核单", "settlementStatus": "PENDING", "settlementStatusName": "待结算" },
|
||||
{ "groupBatchId": "2105223438312652802", "batchNo": "T26-5936", "batchStatus": "SETTLED", "batchStatusName": "已结算", "stage": "SETTLE", "stageName": "结算", "reviewStatus": "COMPLETED", "reviewStatusName": "已核单", "settlementStatus": "COMPLETED", "settlementStatusName": "已结算" }
|
||||
]
|
||||
},
|
||||
"message": "成功",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:核单页签现在会同时返回「核单中」与「已结算」的团(改前只返回核单中)。若库里有「待核单 PENDING_REVIEW」的团也会一并返回,其 `stageName` 仍显示「出行」(待核单在六节点模型里归出行桶,见 §5)。
|
||||
|
||||
### 8.2 边界:默认 scope=ONGOING 会滤掉核单团
|
||||
|
||||
**场景说明**:核单三态的团期返团日必然已过,若前端不传 `scope=ALL`,缺省规则会把它们按「未结束」滤掉。
|
||||
|
||||
**请求**:
|
||||
```http
|
||||
GET /v3/admin/order/group-batch?pageNo=1&pageSize=20&opsStage=REVIEW
|
||||
Authorization: Bearer {adminToken}
|
||||
(无请求体,scope 缺省)
|
||||
```
|
||||
|
||||
**响应**:productId 缺省时 scope 默认 ALL(不受影响);productId 有值时 scope 默认 ONGOING,核单团被滤掉返回空。前端点核单页签**务必显式传 `scope=ALL`**。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用**:核单页签统览核单阶段全部团期(待核单 / 核单中 / 已结算)。
|
||||
- ⚠️ **scope 联动**:核单三态返团日已过,前端点核单页签需把 `scope` 切到 `ALL`,否则默认范围会把它们过滤掉(此约束改前已存在,本次不变)。
|
||||
- ⚠️ **节点标签不统一**:核单页签内,待核单团节点显示「出行」、已结算团节点显示「结算」(见 §5)。
|
||||
- ❌ **不变**:`SETTLE` 结算页签仍只筛 `SETTLED`;`TRIP` 出行页签仍含 `PENDING_REVIEW`(待核单团会同时出现在出行页签与核单页签)。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `opsStage=REVIEW` 筛出的团期状态 | 仅 REVIEWING(核单中) | PENDING_REVIEW + REVIEWING + SETTLED(核单三态) |
|
||||
| 核单页签能否看到「待核单」团 | ❌ 看不到(在出行页签) | ✅ 能看到 |
|
||||
| 核单页签能否看到「已结算」团 | ❌ 看不到(在结算页签) | ✅ 能看到 |
|
||||
| 入参字段 / 出参字段 | — | 完全不变 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。入参出参字段不变;仅 `opsStage=REVIEW` 返回的数据集扩大(多返回待核单 + 已结算团期)。
|
||||
- **前端是否必须同步上线**:否。前端继续传 `opsStage=REVIEW` 即可;但需留意核单页签行数会变多、节点标签不统一。
|
||||
- **影响已有数据**:无,纯查询筛选口径变化,无数据迁移。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #8672 即可恢复 `opsStage=REVIEW` 单态口径。
|
||||
- **回滚后清理**:无(无脏数据 / 缓存)。
|
||||
- **回滚耗时**:重新打包部署 hl-order-service-v3,约 5 分钟。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 上线需重启 / 重新部署 **hl-order-service-v3**(8086)。
|
||||
- 前端无需改入参;如核单页签需统一节点标签,属另一个展示层需求,单独提。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8671](https://git.1814.love/wx/HL/issues/8671)
|
||||
- **PR**: [#8672](https://git.1814.love/wx/HL/pulls/8672)
|
||||
- **Merge commit**: [f01d7565b3](https://git.1814.love/wx/HL/commit/f01d7565b3b750841eb32af98a448f5b1a7b5b7f)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: @hl-admin
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8673"
|
||||
title: "应付款:列表默认只看欠款 + 付款明细补全量支付状态(#8673)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "1be140db4ac9fde579b06e934078c0675db1b6b8"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "应付款两处调整。①【破坏性】按团号/按供应商列表默认空 status 从「全部」改为「只看欠付 OWED」:已付清/金额归零的团和供应商默认不再返回,需显式传 status=PAID 或 ALL 回看;status 过滤从内存过滤下沉 SQL,修复了分页 total 与返回行数不一致的 bug。②付款建议明细出参新增 payableAmount/paidAmount/appliedAmount/payStatus 四件套(additive),入参新增 includePaid(默认 false);includePaid=true 时已付清行也返回(payStatus=PAID 置灰),点付款可看到「哪些已付、哪些未付」全景。payStatus 判据=无可申请余额即 PAID(覆盖真已付清 + applied 全额占用)。前端已交付:统计列表筛选项对齐 OWED/PAID/ALL、默认显式钉 OWED 只看欠款(回看切 PAID/ALL);两个付款面板与应付款详情均 includePaid=true 全景,PAID 行置灰禁勾显「已付清」、PARTIAL 显「部分已付」。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:应付款列表默认只看欠款 + 付款明细补全量支付状态(管理后台)
|
||||
|
||||
> ⚠️ **破坏性变更**:`GET /admin/finance/payments/stats/by-team` 和 `/by-supplier` 不传 `status` 时,从「返回全部(含已付清)」改为「只返回有欠付(OWED)的行」。已付清的团/供应商默认从列表消失,需显式传 `status=PAID` 或 `status=ALL` 回看。
|
||||
> ✅ **additive**:付款建议明细出参新增 4 字段、入参新增 `includePaid`,旧前端不传不受影响。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务「应付款」按团号/按供应商两个列表,原把头表所有行(含已付清、金额归零)都返回,干扰财务看「还欠谁的」;且 status 过滤是后端内存过滤,只滤当前页导致分页 total 不准。点付款时的明细只给「剩余可申请余额」,看不到每个资源「该付多少、付了多少、还差多少」,已付清的资源行直接被滤掉。
|
||||
|
||||
本次:列表默认只看欠款 + 修 total 不准;付款明细补全量支付状态。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET /payments/stats/by-team | 默认空 status=只看 OWED;status 新增 ALL;过滤下沉修 total | ⚠️ 行为变更 |
|
||||
| 2 | GET /payments/stats/by-supplier | 同上 | ⚠️ 行为变更 |
|
||||
| 3 | GET /payments/suggestions | 出参加 4 字段;入参加 includePaid | ✅ additive |
|
||||
| 4 | GET /payments/suggestions/by-supplier | 同上 | ✅ additive |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
统一前缀 `GET /admin/finance/payments/**`(业务调用**不带**服务前缀,网关按 `/admin/finance/**` 路由)。
|
||||
|
||||
## 4. 入参
|
||||
|
||||
### 4.1 列表(by-team / by-supplier)
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| keyword | 团号/产品名/客人名(by-team)或供应商名(by-supplier)模糊 |
|
||||
| status | ⚠️ `OWED` 有欠付 / `PAID` 已付款 / `ALL` 全部;**空=默认只看 OWED(新)** |
|
||||
|
||||
### 4.2 付款建议(suggestions / by-supplier)
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| orderId / supplierId | 必填 |
|
||||
| includePaid | 新增,默认 `false`;`true` 时返回含已付清行的全量明细 |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 列表行(不变)
|
||||
by-team:`teamNo, productName, customerName, orderNos, departDate, returnDate, payableAmount, appliedAmount, paidAmount, owedAmount, supplierCount, status`
|
||||
by-supplier:`supplierId, supplierName, category, payableAmount, appliedAmount, paidAmount, owedAmount, teamCount, status`
|
||||
|
||||
### 5.2 付款建议行(PaymentSuggestionRowVO 新增 4 字段)
|
||||
原字段 + 新增:
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| payableAmount | 该行该付总额 |
|
||||
| paidAmount | 已付金额 |
|
||||
| appliedAmount | 已申请占用金额(在途付款单) |
|
||||
| payStatus | `UNPAID` 未付 / `PARTIAL` 部分已付 / `PAID` 已付清(无可申请余额) |
|
||||
|
||||
## 6. 枚举/数据字典
|
||||
|
||||
### status(列表筛选 + 行出参)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| OWED | 有欠付(欠付 > 0) |
|
||||
| PAID | 已付款(欠付 <= 0,含金额归零) |
|
||||
| ALL | 全部(仅筛选用,回看已付清) |
|
||||
|
||||
### payStatus(付款建议行出参,新增)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| UNPAID | 未付(已付=0,仍可申请) |
|
||||
| PARTIAL | 部分已付(已付>0 且仍有可申请余额) |
|
||||
| PAID | 无可申请余额(含真已付清 + applied 全额占用,置灰不可再勾选) |
|
||||
|
||||
## 7. 错误码
|
||||
无新增。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:默认只看欠款
|
||||
```http
|
||||
GET /admin/finance/payments/stats/by-team
|
||||
→ 只返回有欠付的团,已付清团不出现;total 与返回行数一致
|
||||
```
|
||||
|
||||
### 8.2 回看已付清
|
||||
```http
|
||||
GET /admin/finance/payments/stats/by-team?status=ALL
|
||||
→ 返回全部(含已付清团,status=PAID)
|
||||
```
|
||||
|
||||
### 8.3 点付款看全量(含已付清行置灰)
|
||||
```http
|
||||
GET /admin/finance/payments/suggestions?orderId=66001&includePaid=true
|
||||
→ rows 含已付清行:
|
||||
{"resourceName":"呼和塔拉草原","payableAmount":240.00,"paidAmount":240.00,"appliedAmount":0,"payStatus":"PAID",...}
|
||||
{"resourceName":"阿尔山门票","payableAmount":500.00,"paidAmount":100.00,"appliedAmount":0,"payStatus":"PARTIAL",...}
|
||||
{"resourceName":"白桦林","payableAmount":300.00,"paidAmount":0,"appliedAmount":0,"payStatus":"UNPAID",...}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 列表默认 OWED 视图下,金额归零(行全取消)的团因 owed<=0 归 PAID,自然隐藏。
|
||||
- includePaid=true 返回的已付清行(payStatus=PAID)**不可再发起付款申请**,前端置灰禁勾选。
|
||||
- 列表 status 过滤已下沉 SQL,分页 total 与返回行严格一致。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 列表空 status | 返回全部(含已付清/归零) | 只返回有欠付 OWED |
|
||||
| status 过滤 | 内存过滤,total 不准 | SQL 下沉,total 准确 |
|
||||
| status 取值 | OWED/PAID | OWED/PAID/ALL |
|
||||
| 付款明细行金额 | 只有余额 amount | 加 payable/paid/applied/payStatus |
|
||||
| 已付清资源行 | 被滤掉看不到 | includePaid=true 可见(置灰) |
|
||||
|
||||
## 11. 影响评估/回滚
|
||||
|
||||
- **列表默认变更**:老前端不传 status 时已付清行消失,需前端确认是否接受/补 status 控件。
|
||||
- **付款明细**:additive,旧前端不传 includePaid、不读新字段则零影响。
|
||||
- 回滚:恢复 selectPage 旧签名 + Service 默认口径即可。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- ⚠️ 列表默认只看欠款是**破坏性变更**,前端若依赖「默认看到已付清历史」需改为显式传 status=ALL。
|
||||
- payStatus=PAID 的语义是「无可申请余额」(含 applied 全额占用),非严格「已付清」,前端置灰即可。
|
||||
- 供应商维度中 supplierId= null 的降级行计入团头但不计入任何供应商,两视图金额可能对不上(历史口径,本次未改)。
|
||||
|
||||
## 13. 关联/联系人
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8673
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8674
|
||||
- merge commit:a8cf8e13109ea713c28e8be527d0a27d0f2e63fb
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,293 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8677"
|
||||
title: "团期预支可支取上限改为「整团已收(扣已退)− 在途」——GB-ADM-040 advanceAvailable 与 GB-ADM-042 发起上限同步改口径"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "团期预支的可支取上限原为「团期尾款池(Σ 各户 max(0, 应收 − 已付))− 在途」,全额收款的团可支取恒为 0(TEST「11月1日额济纳胡杨林深秋4日游」应收 = 已收 26340.00 → 0.00),部分收款的团反倒能按还没收的钱预支。jw 2026-10-01 定案改为「团期已收池 − 在途」:已收池 = Σ 本团活跃子订单 max(0, 已付 − 已退),CANCELLED 户记 0;硬上限,超额 585004、无放宽通道;普通订单上限不改;部分收款团上限收紧属预期;存量不回溯(在途已超新上限时可支取显示 0.00、只拦新申请)。GB-ADM-040 的 advanceAvailable 与 GB-ADM-042 的发起校验共用同一方法,同时生效。路径、入参、出参字段名与类型零变化,错误码不变;变的是取值口径,属「语义取值」变化,按 jw 2026-09-18 口径推送。已合并 dev-v3(PR #8686,merge commit f4f545148)并部署测试服。hl-ui v2.1 弹窗「剩余可支取」与财务 Tab「可支取余额」都直接显示后端 advanceAvailable,前端零改动,frontend_status 记 not_required。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期预支可支取上限改为整团已收(管理后台)
|
||||
|
||||
> **服务**: hl-order-service-v3(端口 8086/8186)
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
团期「发起团期预支」弹窗里的「剩余可支取」、财务 Tab 的「可支取余额」,以及发起时的超额校验,原先都按**整团还没收的尾款**算上限。
|
||||
全额收款的团尾款为 0,于是一分钱都预支不了;只收了一部分的团,反倒能按还没收的钱预支。
|
||||
|
||||
本次改为只预支已经收到手的钱:上限 = 整团已收(扣已退)− 在途预支。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | GB-ADM-040 团期财务总览 | GET | `/v3/admin/order/group-batch/{groupBatchId}/finance` | 修改接口 | 出参 `advanceAvailable` 取值口径改为「团期已收池 − 在途」;字段名、类型与其余字段零变化 |
|
||||
| 2 | GB-ADM-042 发起团期预支 | POST | `/v3/admin/order/group-batch/{groupBatchId}/advance` | 修改接口 | 金额上限改为同一口径;入参、出参、错误码零变化 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. GB-ADM-040 团期财务总览 `GET /v3/admin/order/group-batch/{groupBatchId}/finance`
|
||||
|
||||
**VO**: `GroupBatchFinanceRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情「财务」Tab 切换即加载:四张金额卡、可支取余额、待审批预支、逐户付款。「发起团期预支」弹窗的「剩余可支取」也直接取本接口的 `advanceAvailable`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 团期 ID,不存在返 589501 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| advanceAvailable | BigDecimal(字符串序列化) | **本次改口径**。可支取余额 = max(0, 团期已收池 − 在途)。团期已收池 = Σ 本团活跃子订单 max(0, 已付 − 已退),CANCELLED 户记 0;在途 = 本团期待审批 + 已通过 + 已付款的预支(团期级 ∪ 子订单级,口径不变)。即发起预支的硬上限 |
|
||||
| receivedAmount | BigDecimal | 整团已收(既有字段,本次未改)。毛已付,**不扣退款**;有退款时 `advanceAvailable` 的基数会小于它 |
|
||||
| receivableAmount | BigDecimal | 整团应收(既有字段,本次未改) |
|
||||
| unpaidAmount | BigDecimal | 整团待收(既有字段,本次未改;**不再**是预支上限的基数) |
|
||||
| advanceApproved | BigDecimal | 已预支(既有字段,本次未改) |
|
||||
| advancePending | BigDecimal | 待审批预支(既有字段,本次未改) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/finance HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <admin token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"receivableAmount": "26340.00",
|
||||
"receivedAmount": "26340.00",
|
||||
"unpaidAmount": "0.00",
|
||||
"advanceApproved": "0.00",
|
||||
"advancePending": "0.00",
|
||||
"advanceAvailable": "26340.00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团里没有活跃子订单、或已收全部被在途占满时,`advanceAvailable` 返回 `"0.00"`,不返回 null、不返回负数。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"receivableAmount": "0.00",
|
||||
"receivedAmount": "0.00",
|
||||
"unpaidAmount": "0.00",
|
||||
"advanceApproved": "0.00",
|
||||
"advancePending": "0.00",
|
||||
"advanceAvailable": "0.00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589501,
|
||||
"message": "团期不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589501 | groupBatchId 不存在或已软删 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 展示值与 GB-ADM-042 的发起校验出自同一个方法,弹窗显示多少就能发起多少。
|
||||
- 取户与在途同源:活跃子订单集按 `group_batch_id` 一跳圈定,已取消户不计入已收。
|
||||
- 部分退款的户按「已付 − 已退」计入;单户已退不少于已付时按 0 计。
|
||||
- 存量不回溯:在途已超过新上限的团(存量预支或事后退款),这里显示 `"0.00"`,已有预支记录不变。
|
||||
|
||||
### 2. GB-ADM-042 发起团期预支 `POST /v3/admin/order/group-batch/{groupBatchId}/advance`
|
||||
|
||||
**VO**: `OrderAdvanceRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员为本团主报账人申请团期级预支,创建即待审批(SUBMITTED),财务在审批中心审批。本次只改金额上限的口径。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
|
||||
| payeeStaffId | body | Long | 是 | 须为本团主报账人(PRIMARY) | 借款对象,否则 589557 |
|
||||
| advanceType | body | String | 是 | 数据字典 `advance_type` 内的值 | 借款类型,否则 585006 |
|
||||
| amount | body | BigDecimal | 是 | ≥ 0.01,且 ≤ 当前 `advanceAvailable` | 预支金额;**上限口径本次改为团期已收池 − 在途**,超出 585004 |
|
||||
| purpose | body | String | 否 | ≤ 255 字 | 用途说明 |
|
||||
| voucherUrl | body | String | 否 | ≤ 512 字符 | 凭证文件 URL |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | Long | 预支 ID(既有,本次未改) |
|
||||
| status | String | 恒为 `SUBMITTED`(既有,本次未改) |
|
||||
| amount | BigDecimal | 预支金额(既有,本次未改) |
|
||||
| payeeStaffId / payeeName | Long / String | 领款人(既有,本次未改) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"payeeStaffId": 1009,
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"amount": "24760.00",
|
||||
"purpose": "额济纳段酒店押金先行垫付"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105531450843668481",
|
||||
"orderId": null,
|
||||
"payeeStaffId": "1007",
|
||||
"payeeName": "白云飞",
|
||||
"payeeRole": "LEADER",
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"amount": 24760.0,
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"submittedAt": "2026-10-01 13:33:11"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口为写接口,无空数据形态;校验不通过时不写库、不占额度,返回下方错误。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585004,
|
||||
"message": "预支金额超过可用余额上限",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585004,
|
||||
"message": "预支金额超过可用余额上限",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 585004 | `amount` 大于当前可支取余额(团期已收池 − 在途);**本次口径变化**,错误码与文案不变 |
|
||||
| 585003 | `amount` ≤ 0 |
|
||||
| 589541 | 团期状态不允许发起预支(进入核单后关闭) |
|
||||
| 589557 | 借款对象不是本团主报账人 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 上限在 `@Lock4j` 按 groupBatchId 串行的段内计算,读-算-插之间无并发窗口。
|
||||
- 硬上限:超额一律 585004,没有放宽通道;审批时不再复核额度(维持现状)。
|
||||
- 普通订单的预支上限不随本次改动,仍是「本单待收尾款 − 本单在途」。
|
||||
- 部分收款的团上限随之收紧(例:应收 10720、已收 1600 的团,可支取由 9120.00 变为 1600.00)。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 弹窗「剩余可支取」与财务 Tab「可支取余额」继续直接读 `advanceAvailable`,**不要**用 `receivedAmount − advanceApproved − advancePending` 本地推算:`receivedAmount` 不扣退款,且在途还含已付款的预支。
|
||||
- 发起前以最新一次 GB-ADM-040 的 `advanceAvailable` 为准;并发发起时以服务端 585004 为准。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
零数据库变更:不新增表、列或索引。GB-ADM-040 只读;GB-ADM-042 写入行为不变(校验通过后在 `order_advance` 插入一行 `scope=GROUP_BATCH`、`status=SUBMITTED`,并写团期时间线)。
|
||||
上限的取数改为只读聚合 `order_main.paid_amount` / `refunded_amount`(按本团活跃子订单一次批量取),不再读应付尾款。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 全额收款、无在途:可支取 = 整团已收净额。
|
||||
- 已收全部被在途占满,或事后退款使已收低于在途:可支取 `"0.00"`,新申请 585004。
|
||||
- 团里一户都不剩:已收为 0,可支取 `"0.00"`。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 上限基数 | 团期尾款池 = Σ 各户 max(0, 应收 − 已付) | 团期已收池 = Σ 活跃户 max(0, 已付 − 已退),取消户记 0 |
|
||||
| 全额收款的团 | 可支取恒为 `0.00` | 可支取 = 整团已收净额 |
|
||||
| 部分收款的团 | 可支取 = 还没收的尾款 − 在途 | 可支取 = 已收净额 − 在途(收紧) |
|
||||
| 取户口径 | 按 `product_batch_id` 圈 | 按 `group_batch_id` 一跳(与在途同源) |
|
||||
| 路径 / 入参 / 出参 / 错误码 | — | 逐字未变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **兼容性**:字段名、类型、错误码不变;数值口径变化,前端零改动。
|
||||
- **性能**:上限计算由一次按排期圈单改为一次按子订单 ID 批量取单,单团期无 N+1。
|
||||
- **回滚**:撤销 PR #8686 的合并提交后重新部署 order-v3,无数据与配置残留。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 普通订单(非团期)的预支上限与订单级预支接口。
|
||||
- 已预支 / 待审批两个数、预支记录列表(GB-ADM-043)、核单扣回口径。
|
||||
- 网关:路径未变、无新增路由与权限码。
|
||||
- 小程序端:本接口仅管理后台使用。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-10-01 13:14–14:59 测试服(`api.test.1814.love`),部署 `dev-v3 @ f4f545148`(order-v3 双实例 8086/8186)。
|
||||
每个状态节点都用 SQL 按同一公式独立手算并与 `advanceAvailable` 对照,**29 次对照全部一致**。
|
||||
|
||||
| 场景 | 读数 / 结果 |
|
||||
|---|---|
|
||||
| 部署身份:全额收款团「11月1日额济纳胡杨林深秋4日游」(应收 = 已收 26340.00) | 经网关 8 次 + 直连两实例全是 `"26340.00"`(旧口径为 `"0.00"`) |
|
||||
| 部分收款团「滇西北冬日三日团·一月廿二期」(应收 10720 / 已收 1600) | `"1600.00"`(旧口径为 9120.00) |
|
||||
| 自建全额收款团(已收 24760.00) | 可支取 `"24760.00"`;发起 24760.00 → 200 SUBMITTED,可支取变 `"0.00"`;再发起 0.01 → 585004 |
|
||||
| 自建部分收款团(应收 22080 / 已收 15720) | `"15720.00"`;待审 1200 后 `"14520.00"`;14520.01 → 585004,14520.00 → 200 |
|
||||
| 部分退款 1840(真实退款链路) | 21880 → `"20040.00"`;再取消一户(已付 7360)→ `"12680.00"` |
|
||||
| 在途 | 待审 / 已批 / 已付都扣;驳回、撤回不扣 |
|
||||
| 存量不回溯 | 在途占满后取消一户,可支取仍 `"0.00"`,新申请 585004,已有预支记录逐字段不变 |
|
||||
| 普通订单 | 上限仍是「本单待收 − 本单在途」:两档已付下超 0.01 都报 585004、等额都成功,与新口径可区分 |
|
||||
|
||||
- 验收造数已全部回收,回读零残留。
|
||||
- 单元测试:团期 / 预支整包 + 全部 ArchTest 258 类 4131 例 0 失败;合并提交上复跑定向 32 类 250 例 0 失败。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单 #8677、PR #8686(merge commit `f4f545148`)
|
||||
- 团期统一池原设计:#7154(docs/group 接口文档 §0B.4,本次同步改为已收池)
|
||||
- 团单子订单禁走订单级入口、在途含 PAID:#8384
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端:不涉及(`frontend_status: not_required`)
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8679"
|
||||
title: "收款账户关联服务人员档案:新增选服务人员候选接口 + 个人类收款方 payeeRefId 必填并挂档案校验(#8679)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "85267fc3e3837d5ebaf619dada70fb4564a0b488"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "财务「资金账户→收款账户」个人类收款方(司机/导游/摄影/领队)从手填人员 ID/按名认人,改为从人员档案下拉选具体人。新增「选服务人员」聚合候选接口;create 个人类 payeeRefId 由可空变必填、payeeName 由手填变档案真名覆盖(后端强制),并新增档案存在性+在册校验(拦下架人员/黑名单·休假司机)。司机接 fleet 车队档案、导游/摄影/领队接 resource 服务人员档案。组织类(车队/导游公司/供应商)不变仍手填。前端已交付:表单名称位三态——编辑个人类只读禁改(595202)/新建个人类「选择服务人员」下拉远程选人(payeeRefId 必填前置 595203)/组织类维持手填;staff-candidates 按 payeeType 拉候选,label 拼姓名·手机号·staffType(字典 staff_type 兜底原值)·导游等级,选中回填档案真名,新建态切类型清空已选与候选防错配;payload 编辑个人类剔 payeeName;595202/595203/595204 一律拦截器透 message 不建映射。新建表单 spec 8 例全绿。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:收款账户关联服务人员档案(管理后台)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务「资金账户 → 收款账户」登记收款方时,**个人类收款方**(司机 DRIVER / 导游 GUIDE / 摄影 PHOTOGRAPHER / 领队 LEADER)原来是**手填人员 ID + 手填姓名**,无档案校验——可填错人、填黑名单司机、填已下架人员,钱打给谁的依据不严谨。
|
||||
|
||||
本次让个人类收款方**挂到真实人员档案**:前端表单改为「选择服务人员」下拉(从档案源选人),后端 create 强制校验档案真实存在且在册。司机档案在 **fleet 车队域**,导游/摄影/领队在 **resource 服务人员域**。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 选服务人员候选 | GET | `/admin/finance/payee-accounts/staff-candidates` | **新增接口** | 按 payeeType 分流返回人员候选下拉数据 |
|
||||
| 2 | 登记收款方账户 | POST | `/admin/finance/payee-accounts` | **修改接口** | 个人类 `payeeRefId` 必填 + `payeeName` 被档案真名覆盖 + 档案校验 |
|
||||
| 3 | 编辑收款方账户 | PUT | `/admin/finance/payee-accounts/{payeeId}` | **修改接口** | 个人类禁止手改 `payeeName` |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 选服务人员候选(新增)
|
||||
|
||||
- **使用场景**:收款账户表单选了「收款方类型」为个人类后,「选择服务人员」下拉/弹窗的数据源
|
||||
- **认证**:JWT(管理后台)
|
||||
- **入参(query)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| payeeType | string | 是 | `DRIVER`/`GUIDE`/`PHOTOGRAPHER`/`LEADER`(个人类;传组织类或非法值报 595202) |
|
||||
| keyword | string | 否 | 姓名模糊;输入 11 位手机号按手机精确匹配。≤50 字,超限报 595202 |
|
||||
| page | int | 否 | 缺省 1 |
|
||||
| pageSize | int | 否 | 缺省 20,最大 100 |
|
||||
|
||||
- **行为**:按 payeeType 分流到对应档案源——DRIVER→fleet 司机、GUIDE→resource 导游(含助理导游)、PHOTOGRAPHER→摄影、LEADER→领队。档案服务故障时**降级返回空集合**(不打塌表单,前端下拉显示"暂无可选人员"即可)。
|
||||
- **出参**:`data.records[]` + `data.total`
|
||||
|
||||
### 3.2 登记收款方账户(修改)
|
||||
|
||||
- 个人类:`payeeRefId` **必填**(选中的服务人员 ID),后端校验该 ID 在对应档案源**真实存在且在册**(staff 上架 status=1;driver 在册 season=active 且非休假/待激活),并以**档案真名覆盖 payeeName**。
|
||||
- 组织类(FLEET/GUIDE_CO/SUPPLIER):不变,`payeeRefId` 可空、`payeeName` 手填。
|
||||
|
||||
### 3.3 编辑收款方账户(修改)
|
||||
|
||||
- 个人类:禁止手改 `payeeName`(名称以人员档案为准),传了报 595202。账户要素(qr/bankAccount/bankName)仍可改。
|
||||
- 组织类:不变。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 登记请求体关键字段(变化部分)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| payeeType | string | 是 | 收款方类型 | 个人类见下 |
|
||||
| payeeRefId | string(Long) | **个人类必填** | 服务人员 ID(staff-candidates 返回的 refId);组织类可空 | 个人类必填否则 595203;档案不存在/已下架 595204 |
|
||||
| payeeName | string | 否 | 收款方姓名 | **个人类会被档案真名强制覆盖**(手填无效);组织类手填 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 staff-candidates 出参 records[]
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| refId | string(Long) | 人员 ID(司机=driverId,其余=staffId)——登记时填入 payeeRefId |
|
||||
| name | string | 姓名(档案真名) |
|
||||
| phone | string | 手机号(**明文**,出纳选人看全号区分同名;落库后的列表/详情仍脱敏) |
|
||||
| payeeType | string | 回显收款方类型 |
|
||||
| staffType | string | 人员类型(仅 staff 类有值:`GUIDE`/`GUIDE_ASSISTANT`/`PHOTOGRAPHER`/`LEADER`;DRIVER 为 null)。GUIDE 候选合并导游+助理导游,前端据此字段区分 |
|
||||
| guideLevel | string | 导游等级(仅 staff 导游类有值:初级/中级/高级/特级;其余为 null) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 staffType(人员类型,字典 staff_type)
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `GUIDE` | 导游 |
|
||||
| `GUIDE_ASSISTANT` | 助理导游 |
|
||||
| `PHOTOGRAPHER` | 摄影师 |
|
||||
| `LEADER` | 领队 |
|
||||
|
||||
> payeeType=GUIDE 的候选同时含 GUIDE 和 GUIDE_ASSISTANT 两类,前端用 `staffType` 区分展示;payeeType=PHOTOGRAPHER/LEADER 只含对应一类。
|
||||
|
||||
### 6.2 payeeType 个人类 ↔ 档案源
|
||||
|
||||
| payeeType | 档案源 | refId 含义 |
|
||||
|-----------|--------|-----------|
|
||||
| DRIVER 司机 | fleet 车队 | driverId |
|
||||
| GUIDE 导游 | resource 服务人员 | staffId |
|
||||
| PHOTOGRAPHER 摄影 | resource 服务人员 | staffId |
|
||||
| LEADER 领队 | resource 服务人员 | staffId |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 595202 | 收款方账户字段组合不合法 | staff-candidates 传组织类/非法 payeeType、keyword 超 50;编辑个人类手改 payeeName |
|
||||
| 595203 | 个人类收款方须选择服务人员 | create 个人类未传 payeeRefId |
|
||||
| 595204 | 服务人员不存在或已下架 | create 个人类 payeeRefId 在档案源不存在、staff 已下架、driver 黑名单/归档/休假/待激活;或档案服务暂不可用(文案为"档案服务暂不可用,请稍后重试") |
|
||||
|
||||
## 8. 示例(3 组)
|
||||
|
||||
### 8.1 典型成功(选司机 → 登记司机收款账户)
|
||||
|
||||
**① 候选** `GET /admin/finance/payee-accounts/staff-candidates?payeeType=DRIVER&keyword=张`:
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"total": 1,
|
||||
"records": [
|
||||
{ "refId": "1823456789012345678", "name": "张三", "phone": "13800138000",
|
||||
"payeeType": "DRIVER", "staffType": null, "guideLevel": null }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**② 登记** `POST /admin/finance/payee-accounts`:
|
||||
```json
|
||||
{
|
||||
"payeeType": "DRIVER",
|
||||
"payeeRefId": "1823456789012345678",
|
||||
"accountType": "WECHAT_QR",
|
||||
"qrUrl": "https://oss.example.com/qr/zhangsan.png",
|
||||
"isDefault": 1
|
||||
}
|
||||
```
|
||||
**响应**(payeeName 由后端用档案真名"张三"覆盖,无需前端传):
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "id": "2105..." } }
|
||||
```
|
||||
|
||||
### 8.2 边界(导游候选含助理导游,staffType 区分)
|
||||
|
||||
`GET /admin/finance/payee-accounts/staff-candidates?payeeType=GUIDE`:
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"total": 2,
|
||||
"records": [
|
||||
{ "refId": "1811...", "name": "李四", "phone": "13911112222", "payeeType": "GUIDE", "staffType": "GUIDE", "guideLevel": "高级" },
|
||||
{ "refId": "1822...", "name": "王五", "phone": "13933334444", "payeeType": "GUIDE", "staffType": "GUIDE_ASSISTANT", "guideLevel": "初级" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(个人类未选服务人员)
|
||||
|
||||
`POST /admin/finance/payee-accounts`:
|
||||
```json
|
||||
{ "payeeType": "GUIDE", "accountType": "WECHAT_QR", "qrUrl": "https://..." }
|
||||
```
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 595203, "message": "个人类收款方须选择服务人员", "success": false }
|
||||
```
|
||||
|
||||
选了已下架人员:
|
||||
```json
|
||||
{ "code": 595204, "message": "服务人员不存在或已下架", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 个人类收款方:必须先调 staff-candidates 选人,把返回的 `refId` 作为 `payeeRefId` 提交;`payeeName` 不用传(后端用档案真名覆盖)。
|
||||
- ✅ 组织类(车队/导游公司/供应商):维持手填 `payeeName`,`payeeRefId` 可空,不调候选接口。
|
||||
- ❌ 不要再手填个人类的 payeeRefId/payeeName——后端强制档案校验+真名覆盖,手填无效或被 595203/595204 拦。
|
||||
- ⚠️ 候选接口降级:档案服务故障时返回空 records(非报错),前端下拉显示"暂无可选人员,请稍后重试"即可,不要当成"无此人员"。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 个人类选人方式 | 手填人员 ID + 手填姓名 | staff-candidates 下拉选人,payeeRefId=选中人员 ID |
|
||||
| 个人类 payeeRefId | 可空 | **必填**(595203) |
|
||||
| 个人类 payeeName | 手填生效 | **被档案真名覆盖**(手填无效) |
|
||||
| 档案校验 | 无 | 存在性+在册校验(595204),拦下架/黑名单/休假 |
|
||||
| 编辑个人类 payeeName | 可手改 | 禁止(595202) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:**是**。个人类 create 原来可不传 payeeRefId,现在必填;原来手填 payeeName 生效,现在被档案覆盖。前端个人类表单必须改为「先选人」流程。
|
||||
- **前端是否必须同步上线**:是。个人类收款账户表单需加「选择服务人员」下拉(数据源 staff-candidates),并移除个人类的手填姓名/手填 ID 输入。
|
||||
- **影响已有数据**:测试库 fin_payee_account 个人类存量少,存量数据不受影响(仅新增/编辑走新校验)。
|
||||
- **回滚方式**:revert PR #8692(finance)+ PR #8688(fleet)。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **选人下拉数据分端**:司机候选来自 fleet(含 season/driverStatus 过滤),导游/摄影/领队来自 resource staff。前端只调一个 staff-candidates 接口,后端按 payeeType 分流,无需关心来源。
|
||||
- **staffType/guideLevel 仅 staff 类有**:DRIVER 候选这两字段为 null,前端展示时判空。
|
||||
- **手机号明文仅选人下拉里**:落库后的收款账户列表/详情 phone 仍按 PII 口径脱敏,不要在别处用候选接口的明文 phone 当展示数据源。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#8679](https://git.1814.love/wx/HL/issues/8679)
|
||||
- **PR(finance)**: [#8692](https://git.1814.love/wx/HL/pulls/8692) · merge [7cb5aa9e6d](https://git.1814.love/wx/HL/commit/7cb5aa9e6d717efe34ce099b9e11eabdc9811dbf)
|
||||
- **PR(fleet 司机候选数据源)**: [#8688](https://git.1814.love/wx/HL/pulls/8688) · merge [b4edefd08e](https://git.1814.love/wx/HL/commit/b4edefd08e5a439db6e7a6e2d245a5d0fd1cc067)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 待认领
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8680"
|
||||
title: "出纳已付台账 ADVANCE 页签补预支详情接口(#8680)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude)"
|
||||
frontend_ref: "75b56e2f5d27ae0eebd82af183973d5e84d6ce6a"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-01"
|
||||
status_note: "出纳已付台账 8 个页签的明细抽屉,7 个域详情接口此前已就绪,唯独 ADVANCE 订单预支是缺口:已付台账 ADVANCE 行 bizId 指向 fin_advance.advance_id,但无单笔详情接口,前端抽屉只能拿流水回单兜底(看不到订单/团号/报账人/用途)。本次新增 GET /admin/finance/advances/{id} 司导预支执行单详情,补齐最后一环。出参含订单/团号/收款人/金额/用途/付讫三列;teamNo 由后端按 orderId 反查 order_main 补齐,前端可据此跳订单维度预支列表。additive 纯新增,旧前端零影响。【前端 2026-10-01 交付】changelog 前提「7 域抽屉已就绪」实证不成立,用户拍板 8 域统一抽屉立项;批次 1 骨架+ADVANCE 已落地(新建 api/finance/advance.js+LedgerDetailDrawer 统一骨架,CashierQueuePage ADVANCE 线「明细」列),其余 7 域随后续批次。checkpoint 全绿,22 例 spec 全绿。【批次 2 同日收官】其余 7 域(EXPENSE/NONBIZ/PAYMENT/PREPAY/STAFF_LOAN/REIMBURSE/COMPANY_LOAN)已全接通:抽屉改配置驱动 8 域分派,字段布局向原型各域详情弹窗对齐,操作列全 8 线统一(专项入口+明细);页面与操作列改动随 f5644271、抽屉组件随 75b56e2f 两提交入库,32 例 spec 全绿。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:出纳已付台账 ADVANCE 页签补预支详情接口(管理后台)
|
||||
|
||||
> ✅ **additive 纯新增接口**:新增 `GET /admin/finance/advances/{id}`,旧前端不受影响。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
出纳「已付台账」按业务类型分 8 个页签(NONBIZ/EXPENSE/PAYMENT/PREPAY/STAFF_LOAN/REIMBURSE/COMPANY_LOAN/ADVANCE),每行点「明细」打开抽屉展示该笔业务详情。此前 7 个域都有各自的单笔详情接口,唯独 **ADVANCE 订单预支**没有:已付台账 ADVANCE 行的 `bizId` 指向 `fin_advance.advance_id`(司导预支财务执行单),但后端没有对应的单笔详情查询接口,前端抽屉只能用资金流水回单兜底,看不到订单号、团号、收款人、用途等关键业务信息。
|
||||
|
||||
本次补 `GET /admin/finance/advances/{id}`,让 ADVANCE 页签抽屉与其它 7 个页签一样能展示完整业务明细。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET /admin/finance/advances/{id} | 新增司导预支执行单详情 | ✅ 新增接口 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
`GET /admin/finance/advances/{id}`(业务调用**不带**服务前缀,网关按 `/admin/finance/**` 路由到 order-v3)。
|
||||
|
||||
按 `fin_advance.advance_id` 查单笔预支执行单详情,供已付台账 ADVANCE 页签明细抽屉使用。
|
||||
|
||||
## 4. 入参
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| id | path | Long | 是 | 预支执行单ID(`fin_advance.advance_id`,即已付台账 ADVANCE 行的 `bizId`) |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
`FinAdvanceDetailRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | Long(string) | 预支执行单ID |
|
||||
| advanceNo | String | 预支单号(YZ- 前缀) |
|
||||
| orderAdvanceId | Long(string) | 订单侧预支单ID(order_advance) |
|
||||
| orderId | Long(string) | 订单ID |
|
||||
| orderNo | String | 订单号 |
|
||||
| teamNo | String | 团号(后端按 orderId 反查 order_main 补齐;订单无团号 → null) |
|
||||
| payeeStaffId | Long(string) | 收款人员工ID |
|
||||
| payeeName | String | 收款人姓名(报账人/司导) |
|
||||
| advanceType | String | 预支类型(如 CATERING 餐饮 / FUEL 油费等,见数据字典) |
|
||||
| amount | BigDecimal | 预支金额 |
|
||||
| purpose | String | 用途说明 |
|
||||
| fundAccountId | Long(string) | 出账资金账户ID |
|
||||
| payFlowId | Long(string) | 付款资金流水ID(可跳流水回单) |
|
||||
| paidAt | LocalDateTime | 付讫时间 |
|
||||
| status | String | 状态(APPROVED 已审批 / PAID 已付款) |
|
||||
| operatorName | String | 登记人姓名(用户域反查 createdBy;未登记企微名 → null) |
|
||||
| createTime | LocalDateTime | 创建时间 |
|
||||
|
||||
> 所有 Long 型 ID 均已字符串化(`@JsonSerialize(ToStringSerializer)`),前端按 string 处理,避免 JS 精度丢失。
|
||||
|
||||
## 6. 枚举/数据字典
|
||||
|
||||
### status(预支执行单状态)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| APPROVED | 已审批(待付款) |
|
||||
| PAID | 已付款 |
|
||||
|
||||
### advanceType(预支类型)
|
||||
走业务数据字典(如 CATERING 餐饮 / FUEL 油费 / TICKET 门票 等),具体取值以字典接口为准,前端展示走字典 label。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发 |
|
||||
|---|---|---|
|
||||
| 599500 | 预支单不存在 | id 不存在或已软删 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:已付款预支单详情
|
||||
```http
|
||||
GET /admin/finance/advances/2104861782621462530
|
||||
→ 200
|
||||
{
|
||||
"id": "2104861782621462530",
|
||||
"advanceNo": "YZ-202609290001",
|
||||
"orderAdvanceId": "2104861625016287233",
|
||||
"orderId": "2100743225424621570",
|
||||
"orderNo": "HL20260918082629372",
|
||||
"teamNo": "26-8707",
|
||||
"payeeStaffId": "2100747615736897537",
|
||||
"payeeName": "刘大山",
|
||||
"advanceType": "CATERING",
|
||||
"amount": 260.0,
|
||||
"purpose": "满洲里中俄边境午餐代垫",
|
||||
"fundAccountId": "1962000000000008001",
|
||||
"payFlowId": "2105436186380242945",
|
||||
"paidAt": "2026-10-01 00:00:00",
|
||||
"status": "PAID",
|
||||
"operatorName": "金卫",
|
||||
"createTime": "2026-09-29 17:12:10"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:订单无团号 / 登记人未登记企微名
|
||||
```http
|
||||
GET /admin/finance/advances/{id}
|
||||
→ 200,teamNo=null / operatorName=null(对应反查为空时降级为 null,不阻塞详情)
|
||||
```
|
||||
|
||||
### 8.3 异常:id 不存在
|
||||
```http
|
||||
GET /admin/finance/advances/999999999
|
||||
→ {"code":599500,"message":"预支单不存在","data":null,"success":false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- `teamNo` 由后端按 `orderId` 反查 `order_main` 实时补齐(订单无团号或订单不存在 → null),**非 fin_advance 快照字段**;前端可据此跳「订单维度预支列表」`/v3/admin/order/{orderId}/advances`。
|
||||
- `operatorName` 反查用户域 createdBy,未登记企微名 / 用户域暂不可用 → 降级为 null(查询类不 fail-fast)。
|
||||
- 已付台账 ADVANCE 行的 `bizId` 即本接口的 `id`,前端抽屉直接用行 `bizId` 调本接口。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口,无「修改前」。
|
||||
|
||||
| 项 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| ADVANCE 页签明细抽屉 | 无详情接口,只能流水回单兜底 | 可调本接口展示完整业务明细 |
|
||||
|
||||
## 11. 影响评估/回滚
|
||||
|
||||
- additive 纯新增,旧前端零影响;不调用本接口无变化。
|
||||
- 回滚:删除该 Controller/Service/VO 即可,无 DDL、无数据迁移。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- Long 型 ID 全部是 string,前端勿按 number 解析。
|
||||
- `teamNo` / `operatorName` 可能为 null(反查为空降级),前端做空值兜底展示。
|
||||
- 本接口为只读查询,鉴权走网关 `/admin/**` 常规 JWT。
|
||||
|
||||
## 13. 关联/联系人
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8680
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8682
|
||||
- merge commit:93e5ee681d8e792a2d110faee5f3356ccb70cf8b
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8689"
|
||||
title: "核销管理后端落地:坏账核销/债务豁免 5 端点(#8689)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-admin(claude-opus-4-8)"
|
||||
frontend_ref: "01436d457302207d2684584c48c50818936fdecf"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-02"
|
||||
status_note: "核销管理(API §6.5 设计稿)本期提前落地。核销=往来账面抹债不动资金(无资金流水、不改账户结存、不过出纳),与支付本质区别:支付是钱真动了,核销只是账认了这笔损失/对冲。本期落地 SUPPLIER 债务豁免(冲减应付)+ CUSTOMER 坏账核销(冲减应收)两类;STAFF 员工·司导往来不做。新增 /admin/finance/writeoffs 5 端点(两页签列表/发起核销/提交审批/批准/驳回)。审批本期本地手工批(企微审批流留 TODO),金额≥阈值(默认 5000)落待审批、<阈值建单即入账。坏账核销只落往来台账留痕、不回改订单侧应收金额。前端已交付(用户拍板已部署立项):新建核销管理页(hiddenRoute 先行,核销审批/核销记录双页签懒加载,批准二次确认+驳回原因必填,LOG 筛选仅用契约 keyword/ledgerType/status 三参,原型类型/日期筛选与页签角标契约无入参不渲染);发起核销弹窗页内+供应商往来账行「核销」两入口共用(账套→类型一对一联动,供应商走档案弹窗带 refId、客户按名聚合不传 refId,阈值分流以响应 needApproval 为准不前端预判);submit 手工批仅守卫不暴露按钮;错误码全走拦截器透 message。页 spec 5 例+弹窗 spec 6 例+供应商往来账 wiring 1 例,21 例全绿。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:核销管理后端落地(坏账核销/债务豁免 5 端点)(管理后台)
|
||||
|
||||
> **PR**: #8697 | **服务**: hl-order-service-v3(hl-finance 编译其中) | **更新时间**: 2026-10-01
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
财务往来账上会有「收不回的应收(坏账)」和「不用付的应付(债务豁免)」,需要从账面轧掉认损。这就是**核销**。
|
||||
|
||||
核销与支付的本质区别:
|
||||
- **支付**:钱真的动了(产生资金流水、账户结存变动、过出纳)
|
||||
- **核销**:只是账面抹债——**不动资金、无资金流水、不改账户结存、不过出纳**,只在该对象的往来台账上记一笔反向对冲行(净额减)
|
||||
|
||||
本期落地两类核销:
|
||||
| 核销类型 | 账套 | 作用 |
|
||||
|---|---|---|
|
||||
| 债务豁免 `DEBT_WAIVER` | 供应商 `SUPPLIER` | 冲减应付(不用付了的应付款轧掉) |
|
||||
| 坏账核销 `BAD_DEBT` | 客户 `CUSTOMER` | 冲减应收(收不回的应收款认损失) |
|
||||
|
||||
> **STAFF 员工·司导往来本期不做**(该账套属后续 Epic、净往来无来源)。报销冲抵不进本表(走费用域闭环)。
|
||||
|
||||
此前核销管理只有设计稿(API §6.5),本次后端正式落地 5 个端点。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 核销分页(两页签) | GET | /admin/finance/writeoffs/page | 新增 | PENDING 待审批 / LOG 全量记录 |
|
||||
| 2 | 发起核销 | POST | /admin/finance/writeoffs | 新增 | 建核销单,按金额阈值分流 |
|
||||
| 3 | 提交审批 | POST | /admin/finance/writeoffs/{id}/submit | 新增 | 本期本地手工批,仅守卫 |
|
||||
| 4 | 批准核销 | POST | /admin/finance/writeoffs/{id}/approve | 新增 | 入账:写台账对冲行 |
|
||||
| 5 | 驳回核销 | POST | /admin/finance/writeoffs/{id}/reject | 新增 | 驳回不动账 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 核销分页(两页签)
|
||||
|
||||
- **使用场景**:核销管理页两页签——「待审批」列待批核销单,「核销记录」列全量(已入账+已驳回)
|
||||
- **认证**:需登录,财务查看权限
|
||||
- **入参(Query)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| tab | String | ✅ | 页签:`PENDING` 待审批 / `LOG` 核销记录(其他值报 596013) |
|
||||
| ledgerType | String | ❌ | 账套过滤:`SUPPLIER` / `CUSTOMER` |
|
||||
| status | String | ❌ | 状态过滤;**LOG 页签仅允许 `POSTED`/`REJECTED`**(传其他报 596013),PENDING 页签忽略 |
|
||||
| keyword | String | ❌ | 核销单号 / 往来对象名 模糊 |
|
||||
| pageNo / pageSize | int | ✅ | 分页 |
|
||||
|
||||
- **出参**:`PageResult<WriteoffRowRespVO>`
|
||||
|
||||
### 3.2 发起核销
|
||||
|
||||
- **使用场景**:财务在某往来对象上发起一笔核销
|
||||
- **入参(Body,WriteoffCreateReqVO)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||
|------|------|------|------|------|
|
||||
| ledgerType | String | ✅ | 账套:`SUPPLIER` / `CUSTOMER` | |
|
||||
| writeoffType | String | ✅ | 核销类型:`DEBT_WAIVER`(SUPPLIER)/ `BAD_DEBT`(CUSTOMER) | 与账套不匹配报 596014 |
|
||||
| refId | Long | 见说明 | 往来对象 ID(SUPPLIER 必填) | |
|
||||
| refName | String | ✅ | 往来对象名(CUSTOMER 按客户名聚合) | |
|
||||
| amount | BigDecimal | ✅ | 核销金额 | >0(596012)、@Digits(16,2)、≤ 该对象净往来(596001) |
|
||||
| reason | String | ✅ | 核销原因 | |
|
||||
| sourceType / sourceId / sourceNo | String/Long/String | ❌ | 来源单据(可选追溯) | |
|
||||
|
||||
- **分流**:金额 < 阈值(默认 5000)→ 直接 `POSTED` 入账;≥ 阈值 → `PENDING_APPROVAL` 待审批
|
||||
- **出参**:`WriteoffCreateRespVO`(writeoffId / writeoffNo / status / needApproval)
|
||||
|
||||
### 3.3 提交审批
|
||||
|
||||
- **使用场景**:≥阈值核销单提交走审批
|
||||
- **本期说明**:审批为**本地手工批**(对齐费用/支付),企微审批流留 TODO 不接;submit 仅做状态守卫、状态不变,返回空串实例 ID
|
||||
- **入参**:路径 `id`
|
||||
- **出参**:`WriteoffSubmitRespVO`(approvalInstanceId 本期恒空串)
|
||||
|
||||
### 3.4 批准核销
|
||||
|
||||
- **使用场景**:批准一笔待审批核销 → 入账
|
||||
- **动作**:`PENDING_APPROVAL` → `POSTED`,写一行往来台账反向对冲行(净额减),回写核销单 statement_entry_id + posted_at + 审批人快照
|
||||
- **防超额**:批准时会**重算该对象当前净往来**,若 PENDING 期间净额已被其他入账冲减到不够核销,报 596001 不予入账
|
||||
- **入参**:路径 `id`
|
||||
|
||||
### 3.5 驳回核销
|
||||
|
||||
- **使用场景**:驳回一笔待审批核销(不动账)
|
||||
- **动作**:`PENDING_APPROVAL` → `REJECTED`,记驳回原因 + 审批人快照
|
||||
- **入参**:路径 `id` + Body `WriteoffRejectReqVO`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| reason | String | ✅ | 驳回原因 |
|
||||
|
||||
## 5. 出参(核销行 WriteoffRowRespVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 核销单 ID(字符串化) |
|
||||
| writeoffNo | String | 核销单号(HX- 开头) |
|
||||
| ledgerType | String | 账套 SUPPLIER/CUSTOMER |
|
||||
| ledgerTypeName | String | 账套中文名 |
|
||||
| writeoffType | String | 核销类型 DEBT_WAIVER/BAD_DEBT |
|
||||
| writeoffTypeName | String | 核销类型中文名 |
|
||||
| refId | Long | 往来对象 ID |
|
||||
| refName | String | 往来对象名 |
|
||||
| amount | BigDecimal | 核销金额 |
|
||||
| reason | String | 核销原因 |
|
||||
| status | String | 状态 PENDING_APPROVAL/POSTED/REJECTED |
|
||||
| statusName | String | 状态中文名 |
|
||||
| needApproval | Integer | 是否需审批 0/1 |
|
||||
| operatorName | String | 经办人 |
|
||||
| approverName | String | 审批人(本期手工批回填) |
|
||||
| rejectReason | String | 驳回原因 |
|
||||
| postedAt | String | 入账时间 |
|
||||
| createTime | String | 创建时间 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 ledgerType(账套)
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| SUPPLIER | 供应商 | 应付侧 |
|
||||
| CUSTOMER | 客户 | 应收侧 |
|
||||
|
||||
### 6.2 writeoffType(核销类型)
|
||||
| 值 | 中文 | 适用账套 |
|
||||
|----|------|----------|
|
||||
| DEBT_WAIVER | 债务豁免 | SUPPLIER |
|
||||
| BAD_DEBT | 坏账核销 | CUSTOMER |
|
||||
|
||||
### 6.3 status(核销单状态)
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| PENDING_APPROVAL | 待审批 | ≥阈值待批 |
|
||||
| POSTED | 已入账 | 已写台账对冲行 |
|
||||
| REJECTED | 已驳回 | 审批驳回 |
|
||||
|
||||
### 6.4 tab(页签)
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| PENDING | 待审批 |
|
||||
| LOG | 核销记录 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 596001 | 核销金额超过该对象净往来 | 发起 / 批准时金额 > 净往来 |
|
||||
| 596002 | 核销单状态不允许该操作 | 对非待审批单做 submit/approve/reject |
|
||||
| 596004 | 往来对象不存在 | 供应商/客户查无(SUPPLIER 无任何期初与流水) |
|
||||
| 596011 | 核销单不存在 | id 查无 |
|
||||
| 596012 | 核销金额无效须>0 | amount ≤0 |
|
||||
| 596013 | 核销页签非法 / LOG 页签 status 非法 | tab 非 PENDING/LOG;LOG 传非 POSTED/REJECTED |
|
||||
| 596014 | 核销类型与账套不匹配 | SUPPLIER 传 BAD_DEBT / CUSTOMER 传 DEBT_WAIVER |
|
||||
| 596015 | 核销单号取号撞号耗尽 | HX- 取号并发耗尽(极少) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(发起一笔供应商债务豁免,<阈值直接入账)
|
||||
|
||||
**请求** POST /admin/finance/writeoffs
|
||||
```json
|
||||
{ "ledgerType": "SUPPLIER", "writeoffType": "DEBT_WAIVER", "refId": 101, "refName": "嘉世豪酒店", "amount": 800.00, "reason": "供应商同意减免尾款" }
|
||||
```
|
||||
**响应**
|
||||
```json
|
||||
{ "code": 0, "data": { "writeoffId": "1234567890", "writeoffNo": "HX-202610010001", "status": "POSTED", "needApproval": 0 }, "msg": "" }
|
||||
```
|
||||
|
||||
### 8.2 边界(金额 ≥ 阈值 → 落待审批)
|
||||
|
||||
**请求** POST /admin/finance/writeoffs
|
||||
```json
|
||||
{ "ledgerType": "CUSTOMER", "writeoffType": "BAD_DEBT", "refName": "张三", "amount": 6000.00, "reason": "客户失联认损" }
|
||||
```
|
||||
**响应**
|
||||
```json
|
||||
{ "code": 0, "data": { "writeoffId": "1234567891", "writeoffNo": "HX-202610010002", "status": "PENDING_APPROVAL", "needApproval": 1 }, "msg": "" }
|
||||
```
|
||||
|
||||
### 8.3 业务失败(核销金额超净往来)
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{ "code": 596001, "msg": "核销金额超过该对象净往来", "data": null }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 核销只动往来账面:在该对象台账记一行反向对冲(净额减)
|
||||
- ❌ 核销**不产生资金流水、不改账户结存、不过出纳**
|
||||
- ❌ 坏账核销**不回改订单侧应收金额**——核销是财务账面认损失,订单应收仍在;CUSTOMER 按客户名聚合校验+回扣已核销防重复
|
||||
- ⚠️ 同名客户本期算一起(应收台账行无 customerId),将来补 customerId 后升级按 ID
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**: [#8689](https://git.1814.love/wx/HL/issues/8689)
|
||||
- **PR**: [#8697](https://git.1814.love/wx/HL/pulls/8697)
|
||||
- **Merge commit**: [04e7ff6025](https://git.1814.love/wx/HL/commit/04e7ff60250f9199684856801452bd1d0062e46a)
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8690"
|
||||
title: "微信商户对账:账单下载解析比对落库+差异处理+手动补跑(6 端点)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "8175316c88d4f555538d7e65d3744102bccfee90"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-02"
|
||||
status_note: "前端交付(2026-10-02,用户拍板已部署):新建 finance/wx-bill 页(hiddenRoute 先行,sys_menu 未下挂;汇总台账 dateRange 拆 billDateFrom/To/差异笔数>0 飘红/手动补跑 billDate 必填+锁占用 data=null 按契约文案提示)+BillRecordsDrawer 逐笔快照+BillDiffsDrawer 差异处理(先拉详情防 582404/结论仅 CONFIRMED·IGNORED/只改标记不调账)+api/finance/wx-bill.js(分页 page 非 pageNo 已钉),spec 8 例全绿,提交 8175316c。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【新增接口·管理后台】微信商户对账(6 端点)(#8690)
|
||||
|
||||
> **PR**: #8703 | **服务**: hl-order-service-v3(8086) | **更新时间**: 2026-10-02
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
微信支付此前只有「支付回调」这一条正向链路:微信回调成功 → 本地记成功流水。一旦回调丢失、金额异常或微信侧状态变化,本地无任何反向核对手段,资金敞口不可见。
|
||||
|
||||
本次建设**微信商户对账**能力:每日 T-1 自动拉取微信商户**交易账单(tradebill)+ 资金账单(fundflowbill)**,下载解析后与本地支付流水逐笔比对落库,差异打标并通过 FINANCE 站内信告警,**不自动调账**(差异一律人工核实处理)。
|
||||
|
||||
本 changelog 覆盖管理后台消费的 **6 个新端点**(对账执行本体由定时任务触发,不在本接口面)。菜单归属:**财务管理 → 资金账户 → 微信商户对账**。单视图「对账汇总」台账,逐笔明细 / 差异走抽屉下钻(records / diffs 接口)。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 对账汇总分页列表 | GET | /v3/admin/payment/wx-bill/summaries | 新增接口 | 主视图台账,每日每商户每账单类型一行 |
|
||||
| 2 | 逐笔原始账单记录 | GET | /v3/admin/payment/wx-bill/records | 新增接口 | 汇总行「明细」抽屉数据源 |
|
||||
| 3 | 差异分页列表 | GET | /v3/admin/payment/wx-bill/diffs | 新增接口 | 「看差异」抽屉数据源 |
|
||||
| 4 | 差异详情 | GET | /v3/admin/payment/wx-bill/diffs/{diffId} | 新增接口 | 单条差异完整信息(含处理记录) |
|
||||
| 5 | 处理差异 | POST | /v3/admin/payment/wx-bill/diffs/{diffId}/handle | 新增接口 | 财务人工确认/忽略 + 备注,不自动调账 |
|
||||
| 6 | 手动触发对账补跑 | POST | /v3/admin/payment/wx-bill/reconcile/run | 新增接口 | 运维补账入口,与定时任务同逻辑同锁 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 对账汇总分页列表 GET /v3/admin/payment/wx-bill/summaries
|
||||
|
||||
- **使用场景**:主视图台账。每行 = 一个商户 + 一个账单日期 + 一种账单类型的对账结论(总笔数/总金额/对平/差异/结果)。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:只读。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.2 逐笔原始账单记录 GET /v3/admin/payment/wx-bill/records
|
||||
|
||||
- **使用场景**:汇总行「明细」抽屉,看该商户该日微信账单原始逐笔(交易账单/资金账单统一出参,billType 区分)。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:只读。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.3 差异分页列表 GET /v3/admin/payment/wx-bill/diffs
|
||||
|
||||
- **使用场景**:「看差异」抽屉 / 差异工作台。支持按日期范围、差异类型、处理状态、商户号、账单类型过滤。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:只读。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.4 差异详情 GET /v3/admin/payment/wx-bill/diffs/{diffId}
|
||||
|
||||
- **使用场景**:差异行点开的详情(本地金额 vs 账单金额、差异说明、处理人/处理时间/备注)。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:只读。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.5 处理差异 POST /v3/admin/payment/wx-bill/diffs/{diffId}/handle
|
||||
|
||||
- **使用场景**:财务人工核实差异后标记「已确认」或「已忽略」并留备注。**只做标记,不自动调账**(调账动作走财务调账域,不在本接口)。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:**否**。仅 PENDING 状态可处理;重复处理报 582404(已处理不可重复处理),天然防重。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.6 手动触发对账补跑 POST /v3/admin/payment/wx-bill/reconcile/run
|
||||
|
||||
- **使用场景**:运维补账——定时任务失败 / 账单迟到后,手动对指定账单日期补跑一次。与 internal 定时任务**同逻辑、同一把分布式锁**(key=wx-bill-reconcile:{billDate})。
|
||||
- **认证**:需管理后台 JWT。
|
||||
- **幂等性**:**是**(业务侧)。重跑零重复:原始记录按键集只插缺失、汇总查到即覆盖、差异按指纹去重。但**同一账单日期对账执行中时**(锁被占用)不重复执行,返回提示文案且 data=null。
|
||||
- **限流**:无。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 对账汇总分页列表(Query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
|
||||
| billDateFrom | Date | 否 | 账单日期起(yyyy-MM-dd) | — |
|
||||
| billDateTo | Date | 否 | 账单日期止(yyyy-MM-dd) | — |
|
||||
| mchId | String | 否 | 微信商户号(精确匹配) | — |
|
||||
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW,非法值报参数校验错误 |
|
||||
| reconcileStatus | String | 否 | 对账结果 | 仅 OK / HAS_DIFF / FETCH_FAILED,非法值报参数校验错误 |
|
||||
|
||||
### 4.2 逐笔原始账单记录(Query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
|
||||
| billDate | Date | 否 | 账单日期(yyyy-MM-dd,单日) | — |
|
||||
| mchId | String | 否 | 微信商户号(精确匹配) | — |
|
||||
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW |
|
||||
|
||||
### 4.3 差异分页列表(Query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| page | Integer | 否 | 页码,默认 1 | 最小 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 20 | 1-100 |
|
||||
| billDateFrom | Date | 否 | 账单日期起(yyyy-MM-dd) | — |
|
||||
| billDateTo | Date | 否 | 账单日期止(yyyy-MM-dd) | — |
|
||||
| mchId | String | 否 | 微信商户号(精确匹配) | — |
|
||||
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW |
|
||||
| diffType | String | 否 | 差异类型 | 仅 LOCAL_MISSING / BILL_MISSING / AMOUNT_MISMATCH / STATE_MISMATCH / FETCH_FAILED |
|
||||
| handleStatus | String | 否 | 处理状态 | 仅 PENDING / CONFIRMED / IGNORED |
|
||||
|
||||
### 4.4 差异详情(Path)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| diffId | Long | 是 | 差异ID(path) |
|
||||
|
||||
### 4.5 处理差异(Path + Body)
|
||||
|
||||
Path:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| diffId | Long | 是 | 差异ID(path) |
|
||||
|
||||
请求体:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| handleStatus | String | 是 | 处理状态 | 仅 CONFIRMED(已确认)/ IGNORED(已忽略),**不能传 PENDING** |
|
||||
| remark | String | 否 | 处理备注 | 最长 255 字符 |
|
||||
|
||||
### 4.6 手动触发对账补跑(Body)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| billDate | Date | 是 | 账单日期(yyyy-MM-dd) | 建议传 T-1 或更早(微信当日账单 T+1 上午才生成) |
|
||||
| mchId | String | 否 | 微信商户号 | 可空 = 全部已配置商户 |
|
||||
| billType | String | 否 | 账单类型 | 仅 TRADE / FUND_FLOW;可空 = 交易+资金都跑 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
统一 Result 包装;分页为 PageResult(records / total / page / pageSize)。
|
||||
|
||||
> ⚠️ **Long 主键字符串化**:id(汇总/记录/差异)与 localTransactionId 均以 **JSON 字符串**返回(防 JS 精度丢失),前端按字符串处理,回传 path 参数时原样带回即可。handledBy 为普通数字。
|
||||
|
||||
### 5.1 对账汇总行 WxBillSummaryRespVO
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 汇总ID |
|
||||
| mchId | String | 微信商户号 |
|
||||
| billDate | Date | 账单日期(yyyy-MM-dd) |
|
||||
| billType | String | 账单类型:TRADE / FUND_FLOW |
|
||||
| totalCount | Integer | 账单总笔数 |
|
||||
| totalAmount | BigDecimal | 账单总金额(元) |
|
||||
| matchedCount | Integer | 对平笔数 |
|
||||
| diffCount | Integer | 差异笔数 |
|
||||
| reconcileStatus | String | 对账结果:OK / HAS_DIFF / FETCH_FAILED |
|
||||
| createTime | DateTime | 创建时间(yyyy-MM-dd HH:mm:ss) |
|
||||
|
||||
### 5.2 账单原始记录行 WxBillRecordRespVO
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 记录ID |
|
||||
| mchId | String | 微信商户号 |
|
||||
| billDate | Date | 账单日期 |
|
||||
| billType | String | 账单类型:TRADE / FUND_FLOW |
|
||||
| wxTransactionId | String | 微信支付单号(交易账单有,资金账单可空) |
|
||||
| outTradeNo | String | 商户单号(关联本地支付流水) |
|
||||
| fundFlowId | String | 资金流水单号(资金账单有,交易账单可空) |
|
||||
| tradeTime | DateTime | 交易/记账时间 |
|
||||
| tradeType | String | 交易类型/业务类型(如 JSAPI) |
|
||||
| tradeState | String | 交易状态/收支方向(如 SUCCESS) |
|
||||
| amount | BigDecimal | 交易金额(元) |
|
||||
| payerAmount | BigDecimal | 用户实付(元) |
|
||||
| feeAmount | BigDecimal | 手续费(元) |
|
||||
|
||||
### 5.3 差异行 / 差异详情 WxBillDiffRespVO
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 差异ID |
|
||||
| mchId | String | 微信商户号 |
|
||||
| billDate | Date | 账单日期 |
|
||||
| diffType | String | 差异类型(见 6.2) |
|
||||
| billType | String | 账单类型:TRADE / FUND_FLOW |
|
||||
| outTradeNo | String | 商户单号 |
|
||||
| wxTransactionId | String | 微信支付单号 |
|
||||
| localAmount | BigDecimal | 本地金额(元),本地缺失类差异可空 |
|
||||
| billAmount | BigDecimal | 账单金额(元),账单缺失类差异可空 |
|
||||
| localTransactionId | String(Long) | 本地支付流水ID,本地缺失类差异可空 |
|
||||
| diffDetail | String | 差异说明(人读文案) |
|
||||
| handleStatus | String | 处理状态:PENDING / CONFIRMED / IGNORED |
|
||||
| handleRemark | String | 处理备注,未处理为空 |
|
||||
| handledBy | Long(数字) | 处理人ID,未处理为空 |
|
||||
| handledAt | DateTime | 处理时间,未处理为空 |
|
||||
| createTime | DateTime | 创建时间 |
|
||||
|
||||
处理差异接口的响应体同为 WxBillDiffRespVO(处理后的最新状态)。
|
||||
|
||||
### 5.4 手动触发对账执行结果 WxBillReconcileRunRespVO
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| billDate | Date | 对账账单日期 |
|
||||
| merchantCount | Integer | 参与对账的商户数 |
|
||||
| diffCount | Integer | 本次对账差异总笔数(含历史未清) |
|
||||
| fetchFailedCount | Integer | 账单拉取失败的商户类型数 |
|
||||
|
||||
> ⚠️ **锁占用特例**:当日该账单日期对账正在执行中时,本接口返回 code=0、msg="当日对账正在执行中,请稍后重试"、data=null——属正常跳过,非错误,前端按提示文案展示即可。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 billType(账单类型)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| TRADE | 交易账单 | 微信 tradebill,逐笔交易(含支付/退款) |
|
||||
| FUND_FLOW | 资金账单 | 微信 fundflowbill,逐笔资金收支(含手续费/结算) |
|
||||
|
||||
### 6.2 diffType(差异类型)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| LOCAL_MISSING | 本地缺失 | 账单有该单(SUCCESS)但本地无成功流水——**回调丢失,高危**,优先处理 |
|
||||
| BILL_MISSING | 账单缺失 | 本地有成功流水但账单无该单——可疑(本地虚单/未结算) |
|
||||
| AMOUNT_MISMATCH | 金额不符 | 两边都有但金额不一致 |
|
||||
| STATE_MISMATCH | 状态不符 | 账单状态非 SUCCESS(REFUND/CLOSED)但本地仍 SUCCESS |
|
||||
| FETCH_FAILED | 拉取失败 | 该商户该类型账单下载/申请失败,根本没对上,需重跑 |
|
||||
|
||||
### 6.3 handleStatus(处理状态)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| PENDING | 待处理 | 默认状态,仅此状态可调用处理接口 |
|
||||
| CONFIRMED | 已确认 | 人工已核实确认 |
|
||||
| IGNORED | 已忽略 | 人工核实后忽略(如已线下解决) |
|
||||
|
||||
### 6.4 reconcileStatus(对账结果)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| OK | 对平 | 全部对平,0 差异 |
|
||||
| HAS_DIFF | 有差异 | 对出差异,需人工处理 |
|
||||
| FETCH_FAILED | 拉取失败 | 账单没拉到,根本没对上,需重跑 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 400 | 参数校验失败 | 枚举字段传非法值 / 必填缺失 / remark 超 255 字符(HTTP 200 + Result.error(400),msg 为具体校验文案) |
|
||||
| 582400 | 微信账单申请失败 | 微信侧拒绝 / 无当日账单(对账执行期,管理端查询不直接触发) |
|
||||
| 582401 | 微信账单下载失败 | 账单下载票/文件拉取失败(对账执行期) |
|
||||
| 582402 | 微信账单解析失败 | 账单文件解析失败(对账执行期) |
|
||||
| 582403 | 对账差异记录不存在 | 差异详情 / 处理差异时 diffId 查无此记录 |
|
||||
| 582404 | 该差异已处理,不可重复处理 | 处理差异时该差异已非 PENDING |
|
||||
| 582405 | 找不到商户配置 | 手动补跑指定的 mchId 无微信支付商户配置 |
|
||||
| 582406 | 对账汇总记录不存在 | 汇总记录查无(预留) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(对账汇总列表 + 差异处理)
|
||||
|
||||
请求:
|
||||
|
||||
GET /v3/admin/payment/wx-bill/summaries?billDateFrom=2026-09-01&billDateTo=2026-09-30&reconcileStatus=HAS_DIFF&page=1&pageSize=20
|
||||
Authorization: Bearer <token>
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "1890000000000000001",
|
||||
"mchId": "1246532201",
|
||||
"billDate": "2026-09-30",
|
||||
"billType": "TRADE",
|
||||
"totalCount": 25,
|
||||
"totalAmount": 12800.00,
|
||||
"matchedCount": 24,
|
||||
"diffCount": 1,
|
||||
"reconcileStatus": "HAS_DIFF",
|
||||
"createTime": "2026-10-01 08:30:00"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
处理差异请求:
|
||||
|
||||
POST /v3/admin/payment/wx-bill/diffs/1890000000000000009/handle
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
```json
|
||||
{
|
||||
"handleStatus": "CONFIRMED",
|
||||
"remark": "已核实回调丢失,手动补登记"
|
||||
}
|
||||
```
|
||||
|
||||
处理差异响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "1890000000000000009",
|
||||
"mchId": "1246532201",
|
||||
"billDate": "2026-09-30",
|
||||
"diffType": "LOCAL_MISSING",
|
||||
"billType": "TRADE",
|
||||
"outTradeNo": "HL20260930120000001234",
|
||||
"wxTransactionId": "4200001234202609301234567890",
|
||||
"localAmount": null,
|
||||
"billAmount": 500.00,
|
||||
"localTransactionId": null,
|
||||
"diffDetail": "账单SUCCESS本地无成功流水,疑似支付回调丢失",
|
||||
"handleStatus": "CONFIRMED",
|
||||
"handleRemark": "已核实回调丢失,手动补登记",
|
||||
"handledBy": 1,
|
||||
"handledAt": "2026-10-01 10:00:00",
|
||||
"createTime": "2026-10-01 08:30:00"
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(差异列表空结果 + 手动补跑锁占用)
|
||||
|
||||
空差异页请求(对平的日期段):
|
||||
|
||||
GET /v3/admin/payment/wx-bill/diffs?billDateFrom=2026-09-01&billDateTo=2026-09-30&handleStatus=PENDING&page=1&pageSize=20
|
||||
|
||||
响应(空页为正常结果,非错误):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": { "records": [], "total": 0, "page": 1, "pageSize": 20 },
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
手动补跑(当日对账执行中)请求:
|
||||
|
||||
POST /v3/admin/payment/wx-bill/reconcile/run
|
||||
Content-Type: application/json
|
||||
|
||||
```json
|
||||
{ "billDate": "2026-09-30" }
|
||||
```
|
||||
|
||||
响应(锁被占用,data=null,按 msg 提示展示):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "当日对账正在执行中,请稍后重试"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(重复处理差异,582404)
|
||||
|
||||
请求(对一条已 CONFIRMED 的差异再次处理):
|
||||
|
||||
POST /v3/admin/payment/wx-bill/diffs/1890000000000000009/handle
|
||||
Content-Type: application/json
|
||||
|
||||
```json
|
||||
{ "handleStatus": "IGNORED", "remark": "重复操作" }
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{ "code": 582404, "msg": "该差异已处理,不可重复处理", "data": null }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 适用:查询/处理**已由对账任务落库**的数据——每日 T-1 定时任务(早晨)跑完后,对应账单日期的汇总/记录/差异才可见;手动补跑成功后立即可见。
|
||||
- 不适用:
|
||||
- 当日(T 日)账单——微信当日账单 T+1 上午才生成,对当日日期查/跑只会得到 FETCH_FAILED 或空;
|
||||
- 期望接口实时向微信拉账单展示——records 展示的是**落库快照**,不是实时微信数据。
|
||||
- 特殊:
|
||||
- LOCAL_MISSING(本地缺失)= 回调丢失高危差异,建议财务优先处理;
|
||||
- 差异**处理只改标记不改账**——确认/忽略不会触发任何资金或订单侧动作;
|
||||
- FETCH_FAILED 类汇总/差异的正确处理动作是**重跑**(手动补跑接口),不是人工确认。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口,无修改前版本。本组 6 端点全部为首次交付,无旧路径、无字段变更。
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:否(纯新增端点 + 纯新增表,无既有接口/字段改动)。
|
||||
- **前端是否必须同步上线**:否(不接入不影响任何既有功能;接入后提供「微信商户对账」视图)。
|
||||
- **回滚方式**:revert PR #8703 即可下线 6 端点;三张新表(wx_bill_record / wx_bill_diff / wx_bill_summary)为独立新表,不回滚也不影响既有功能。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **Long 字符串化**:id / localTransactionId 为 JSON 字符串(见 §5 开头),表格 key、路由参数、处理接口 path 回传时**不要 Number() 转换**。
|
||||
- **处理差异非幂等但防重**:重复处理报 582404,前端提交后可禁按钮防连点,收到 582404 时刷新该行为宜。
|
||||
- **手动补跑是重操作**:逐商户下载微信账单 + 全量比对,锁 TTL 30 分钟;触发后建议稍后刷新汇总列表看结果,不要连续点击(锁占用会返回 data=null 提示)。
|
||||
- **枚举值严格校验**:Query 中的 billType / reconcileStatus / diffType / handleStatus 传非法值会被参数校验拦截(HTTP 200 + code 400),下拉框请只渲染 §6 列出的值。
|
||||
- 出参中可空字段(如资金账单的 wxTransactionId、交易账单的 fundFlowId、未处理差异的 handleRemark / handledBy / handledAt)返回 null,展示需做空值兜底。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**: [#8690](https://git.1814.love/wx/HL/issues/8690)
|
||||
- **PR**: [#8703](https://git.1814.love/wx/HL/pulls/8703)
|
||||
- **Merge commit**: [faeed6e0a7](https://git.1814.love/wx/HL/commit/faeed6e0a7)
|
||||
- **后端负责人**: @yst
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8693"
|
||||
title: "fin_advance 抽象化:预支详情三件套反转 + 接通团期级预支进支付管理(#8693)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "3dddf2de5000943503cd67e6cd3b6e05200333e6"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-02"
|
||||
status_note: "【反转 #8680 台账 225】#8680 昨天新增的 GET /admin/finance/advances/{id} 详情出参里 orderId/orderNo 两字段,本 PR 反转改为 bizType/refId/refNo 三件套(同日开发阶段、前端尚未消费该两字段,按用户拍板直接改不留冗余)。根因:fin_advance 原本直挂 order_id/order_no 是订单级专用结构,团期级预支(order_id 空)审批通过后被显式跳过推送导致钱付不出去(T27-2859 实证)。本 PR 把 fin_advance 重构为 biz_type+ref_id+ref_no+payee_ref_type 抽象关联(对齐同域 fin_reimburse 范式),接通团期级预支进支付管理。影响两接口:①详情接口出参 orderId/orderNo→bizType/refId/refNo(teamNo 口径变化);②出纳队列 ADVANCE 行新增 refNo 字段(additive)。前端交付(2026-10-02,与后端「前端尚未消费」自述相反,抽屉实际读 orderNo 属破坏点):LedgerDetailDrawer ADVANCE 归属行改按 bizType 分派(订单级「订单号」读 refNo+「团号」读 teamNo 可 null 兜底;团期级「团号」读 refNo,teamNo 同值不重复行),advance.js JSDoc 钉反转口径+ADVANCE_BIZ_TYPES,spec 13 例全绿,提交 3dddf2de。"
|
||||
updated_at: "2026-10-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:fin_advance 抽象化,接通团期级司导预支进支付管理(管理后台)
|
||||
|
||||
> ⚠️ **反转说明**:本 changelog 反转 **台账 225(#8680)** 昨天推送的详情出参 `orderId`/`orderNo` 两字段,改为 `bizType`/`refId`/`refNo` 三件套。两字段同日开发阶段、前端尚未消费,按用户拍板直接改不留冗余。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
团期级司导预支(挂在出团批次下、不挂具体订单,如 T27-2859 给整个团派车司机预支油费)审批通过后,**进不了支付管理,钱付不出去**。
|
||||
|
||||
根因(两层):
|
||||
|
||||
1. **直接根因**:`fin_advance` 财务执行单表直挂 `order_id`/`order_no`,是订单级专用结构,无法表达「不挂订单」的团期级预支。
|
||||
2. **结构根因**:订单侧 `approveAdvance` 对团期级预支**显式跳过**财务推送(`if orderId==null → 跳过`),导致审批通过后没有任何财务执行单生成。
|
||||
|
||||
本次把 `fin_advance` 重构为 **`biz_type` + `ref_id` + `ref_no` + `payee_ref_type` 抽象关联**(与同域 `fin_reimburse` 报账执行单的 biz_* 范式对齐),让一张表同时承接订单级和团期级预支,并接通团期级进支付管理的推送链路。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET /admin/finance/advances/{id} | 出参 `orderId`/`orderNo` → `bizType`/`refId`/`refNo`;`teamNo` 口径变化 | 🔴 修改接口(反转 #8680) |
|
||||
| 2 | GET /admin/finance/cashier/advance/queue | 行出参新增 `refNo` 字段 | ✅ additive |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 GET /admin/finance/advances/{id}(详情,修改)
|
||||
|
||||
按 `fin_advance.advance_id` 查单笔预支执行单详情。出参的「归属业务对象」由「订单ID/订单号」改为「bizType/refId/refNo 三件套」,以支持订单级与团期级两种来源。
|
||||
|
||||
### 3.2 GET /admin/finance/cashier/advance/queue(出纳预支队列,additive)
|
||||
|
||||
出纳预支待付/已付台账列表。每行新增 `refNo` 字段,展示该笔预支归属的业务单号(订单号或团号快照)。
|
||||
|
||||
## 4. 入参
|
||||
|
||||
两接口入参均无变化(详情 `id` path 参数;队列 `pageNo`/`pageSize`/`tab` 等查询参数不变)。
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 详情 `FinAdvanceDetailRespVO`(修改部分)
|
||||
|
||||
| 字段 | 类型 | 说明 | 变化 |
|
||||
|---|---|---|---|
|
||||
| ~~orderId~~ | — | ~~订单ID~~ | 🔴 **删除**,由 `refId` 承接 |
|
||||
| ~~orderNo~~ | — | ~~订单号~~ | 🔴 **删除**,由 `refNo` 承接 |
|
||||
| bizType | String | 归属业务类型:`ADVANCE_ORDER` 订单级 / `ADVANCE_GROUP_BATCH` 团期级 | ✅ 新增 |
|
||||
| refId | Long(string) | 归属业务对象ID:`bizType=ADVANCE_ORDER`→订单ID;`ADVANCE_GROUP_BATCH`→出团批次ID | ✅ 新增 |
|
||||
| refNo | String | 归属业务单号快照:订单号 或 团号(展示用,冻结不追溯) | ✅ 新增 |
|
||||
| teamNo | String | 团号。`ADVANCE_ORDER`→按 refId 反查 order_main;`ADVANCE_GROUP_BATCH`→**直接=refNo**(团号即归属,不再反查) | 🟡 口径变化 |
|
||||
|
||||
其余字段(id/advanceNo/orderAdvanceId/payeeStaffId/payeeName/advanceType/amount/purpose/fundAccountId/payFlowId/paidAt/status/operatorName/createTime)不变。
|
||||
|
||||
### 5.2 队列 `AdvanceQueueRowRespVO`(additive 部分)
|
||||
|
||||
| 字段 | 类型 | 说明 | 变化 |
|
||||
|---|---|---|---|
|
||||
| refNo | String | 归属业务单号(订单号/团号快照,`fin_advance.ref_no`)。`bizNo` 保持执行单号不变 | ✅ 新增 |
|
||||
|
||||
> 所有 Long 型 ID 均字符串化(`@JsonSerialize(ToStringSerializer)`),前端按 string 处理。
|
||||
|
||||
## 6. 枚举/数据字典
|
||||
|
||||
### bizType(预支归属业务类型)
|
||||
| 值 | 含义 | refId 指向 | refNo 快照 |
|
||||
|---|---|---|---|
|
||||
| ADVANCE_ORDER | 订单级司导预支 | order_main.order_id | order_no 订单号 |
|
||||
| ADVANCE_GROUP_BATCH | 团期级司导预支 | order_group_batch.group_batch_id | batch_no 团号 |
|
||||
|
||||
### payeeRefType(收款人多态类型,仅后端落库,详情/队列出参不透出)
|
||||
| 值 | payeeStaffId 指向 |
|
||||
|---|---|
|
||||
| ORDER_ASSIGNMENT | order_staff_assignment.assignment_id(订单人员配置) |
|
||||
| BATCH_STAFF | 资源域 staff.staff_id(人员主档,团期级预支候选取自 order_batch_staff) |
|
||||
|
||||
### status(预支执行单状态,不变)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| APPROVED | 已审批(待付款) |
|
||||
| PAID | 已付款 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发 |
|
||||
|---|---|---|
|
||||
| 599500 | 预支单不存在 | 详情 id 不存在或已软删(不变) |
|
||||
| 599505 | 预支快照非法 | 推送时 bizType/refId/refNo/payeeRefType 为空或枚举非法(后端内部校验,前端无感) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:订单级预支详情(bizType=ADVANCE_ORDER)
|
||||
```http
|
||||
GET /admin/finance/advances/2104861782621462530
|
||||
→ 200
|
||||
{
|
||||
"id": "2104861782621462530",
|
||||
"advanceNo": "YZ-202609290001",
|
||||
"orderAdvanceId": "2104861625016287233",
|
||||
"bizType": "ADVANCE_ORDER",
|
||||
"refId": "2100743225424621570",
|
||||
"refNo": "HL20260918082629372",
|
||||
"teamNo": "26-8707",
|
||||
"payeeStaffId": "2100747615736897537",
|
||||
"payeeName": "刘大山",
|
||||
"advanceType": "CATERING",
|
||||
"amount": 260.0,
|
||||
"purpose": "满洲里中俄边境午餐代垫",
|
||||
"fundAccountId": "1962000000000008001",
|
||||
"payFlowId": "2105436186380242945",
|
||||
"paidAt": "2026-10-01 00:00:00",
|
||||
"status": "PAID",
|
||||
"operatorName": "金卫",
|
||||
"createTime": "2026-09-29 17:12:10"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:团期级预支详情(bizType=ADVANCE_GROUP_BATCH)
|
||||
```http
|
||||
GET /admin/finance/advances/{id}
|
||||
→ 200
|
||||
{
|
||||
"bizType": "ADVANCE_GROUP_BATCH",
|
||||
"refId": "2104950790684889089",
|
||||
"refNo": "T27-2859",
|
||||
"teamNo": "T27-2859",
|
||||
...
|
||||
}
|
||||
```
|
||||
> 团期级 `teamNo` 直接等于 `refNo`(团号即归属,不再反查 order_main)。
|
||||
|
||||
### 8.3 异常:id 不存在
|
||||
```http
|
||||
GET /admin/finance/advances/999999999999999999
|
||||
→ {"code":599500,"message":"预支单不存在","data":null,"success":false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 详情出参不再有 `orderId`/`orderNo`:要跳订单维度预支列表,用 `bizType=ADVANCE_ORDER` 时取 `refId` 作为订单ID;团期级(`ADVANCE_GROUP_BATCH`)无订单概念。
|
||||
- `refNo` 是**快照字段**(冻结不追溯),仅作展示;不做关联查询键。
|
||||
- `teamNo` 对团期级 = `refNo`(团号),对订单级 = 反查 order_main(订单无团号 → null)。
|
||||
- 队列行 `bizNo` 保持执行单号(YZ- 前缀)不变,新增 `refNo` 专用于展示归属业务单号。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 详情接口出参
|
||||
|
||||
| 字段 | 修改前(#8680) | 修改后(本 PR) |
|
||||
|---|---|---|
|
||||
| 归属业务对象 | `orderId` + `orderNo`(仅订单级) | `bizType` + `refId` + `refNo`(订单级/团期级通用) |
|
||||
| 团号 teamNo | 按 orderId 反查 order_main | 订单级反查 order_main;团期级 = refNo 直出 |
|
||||
|
||||
### 队列接口出参
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 归属业务单号 | 无(只有 bizNo 执行单号) | 新增 `refNo`(订单号/团号) |
|
||||
|
||||
## 11. 影响评估/回滚
|
||||
|
||||
- **前端影响**:详情出参 `orderId`/`orderNo` 被删。**同日开发阶段、前端尚未消费这两字段**(#8680 昨天刚合,前端按台账 225 的对接尚未上线消费),按用户拍板直接改不留冗余,无破坏性。队列 `refNo` 是 additive,旧前端零影响。
|
||||
- **数据迁移**:存量订单级执行单由 Flyway `V20261001_131` 自动回填 `biz_type=ADVANCE_ORDER`/`ref_id=order_id`/`ref_no=order_no`,部署时自动跑,无需手工干预。`order_id`/`order_no` 列已物理删除。
|
||||
- **回滚**:代码层 revert PR 即可;DDL 层 `order_id`/`order_no` 已 DROP 不可回滚(开发阶段接受)。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- Long 型 ID 全部是 string,前端勿按 number 解析。
|
||||
- `bizType` 是判断归属业务类型的**唯一权威字段**,前端勿再用 `orderId` 是否为空判断订单级/团期级。
|
||||
- `refId`/`teamNo`/`operatorName` 可能为 null(反查为空降级),前端做空值兜底展示。
|
||||
- 本接口为只读查询,鉴权走网关 `/admin/**` 常规 JWT。
|
||||
|
||||
## 13. 关联/联系人
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8693
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8698
|
||||
- merge commit:2ccca1b7b4
|
||||
- 反转对象:台账 225(#8680,PR #8682)
|
||||
- Flyway:V20261001_131__fin_advance_abstract_ref.sql
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,510 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8246"
|
||||
title: "流团审批放开团期管理员,批准后各户退款进退款审批中心二审——GB-ADM-062 审批人集合与批后退款行为、GB-ADM-061 canApprove 取值同步变化"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "jw 2026-10-02 定案,照 #8436 退单审批的做法:一是流团审批(GB-ADM-062 同意 / 拒绝)在管理员之外放行团期管理员 GROUP_BATCH_MANAGER,不新增权限码;二是同意流团后各户退款单停在 PENDING 进退款审批中心二审,不再以流团审批人身份自动放行。审批中心列表与流团详情的 canApprove 与端点同一道判定,团期管理员在待审批流团上由 false 变 true。两项都受 nacos 开关控制(默认开):group-batch.disband.refund-second-review 关掉即回到改前自动放行,并同时收回团期管理员的流团审批权;group-batch.acl.allow.disband-approver-group-batch-manager 关掉只收回团期管理员审批权。路径、入参、出参字段名与类型零变化,错误码不变(589547 文案「仅管理员可处理流团审批」未改,与 #8436 对 589530 的处理一致);变的是审批人集合、canApprove 取值与批后退款单状态,属行为 / 语义变化,按 jw 2026-09-18 口径推送。已合并 dev-v3(PR #8734,merge commit c4a410a94)并部署测试服。hl-ui v2.1 审批中心页已对团期管理员开放(canAccess = isAdmin || isGroupBatchManager),同意 / 拒绝按钮按 canApprove 显隐,前端零改动,frontend_status 记 not_required。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 流团审批放开团期管理员与批后退款二审(管理后台)
|
||||
|
||||
> **服务**: hl-order-service-v3(端口 8086/8186)
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
团期管理员能发起流团,却批不了流团:流团审批的角色门只放管理员。退单审批已在 #8436 放开团期管理员,并把批后退款改成进退款审批中心二审;流团这次照同一做法处理。
|
||||
|
||||
本次两处行为变化:
|
||||
|
||||
- 流团审批(同意 / 拒绝)在管理员之外放行团期管理员;审批中心列表与流团详情的 `canApprove` 随之变化。
|
||||
- 同意流团后,各户的退款单停在 `PENDING`,进退款审批中心由有退款审核权的人二审;改前是以流团审批人身份自动审核通过。这一条对所有审批人生效,不只团期管理员。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | GB-ADM-062 同意流团 | POST | `/v3/admin/order/group-batch/disband/{approvalId}/approve` | 修改接口 | 审批人集合加团期管理员;批后各户退款单停在 PENDING 进二审。入参、出参、错误码零变化 |
|
||||
| 2 | GB-ADM-062 拒绝流团 | POST | `/v3/admin/order/group-batch/disband/{approvalId}/reject` | 修改接口 | 审批人集合加团期管理员。入参、出参、错误码零变化 |
|
||||
| 3 | GB-ADM-061 团期审批中心列表 | GET | `/v3/admin/order/group-batch/approvals/page` | 修改接口 | 流团行 `canApprove` 对团期管理员由 false 变 true(待审批行);字段零变化 |
|
||||
| 4 | GB-ADM-061 流团申请详情 | GET | `/v3/admin/order/group-batch/disband/{approvalId}` | 修改接口 | `canApprove` 同上;字段零变化 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. GB-ADM-062 同意流团 `POST /v3/admin/order/group-batch/disband/{approvalId}/approve`
|
||||
|
||||
**VO**: `DisbandApprovalRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期审批中心「流团」行或流团详情页点「同意」。同意后才真正执行流团:团期置 CANCELLED,全团子订单取消,已付户按已付全额建退款单,释放配车占用。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| approvalId | path | Long | 是 | 正整数,流团申请单 ID | 不存在返 589545 |
|
||||
| remark | body | String | 否 | ≤ 512 字 | 批复备注;整个 body 可省略 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| approvalStatus | String | 同意后为 `APPROVED` |
|
||||
| approvedById / approvedByName | Long / String | 本次审批人;**团期管理员现在也会出现在这里** |
|
||||
| estimatedRefundAmount | BigDecimal | 提交时的预估退款合计(既有字段,未改) |
|
||||
| actualRefundAmount | BigDecimal | 实际进退款的合计,由对账定稿(既有字段,未改)。**现在是「已建退款单、待二审」的金额,不是已退出的钱** |
|
||||
| canApprove | Boolean | 已处理后恒为 false |
|
||||
| 其余字段 | — | 与流团详情相同,未改 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/group-batch/disband/2105977953445969921/approve HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <团期管理员 token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"remark": "确认无法成团"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalId": "2105977953445969921",
|
||||
"groupBatchId": "2105974855696547842",
|
||||
"batchNo": "T26-7048",
|
||||
"batchName": "11月26日海拉尔-额尔古纳4日团",
|
||||
"approvalStatus": "APPROVED",
|
||||
"approvalStatusName": "已通过",
|
||||
"reason": "临近出团报名不足 6 户,无法成团",
|
||||
"affectedOrderCount": 2,
|
||||
"participantCount": 4,
|
||||
"estimatedRefundAmount": 3360.0,
|
||||
"actualRefundAmount": 3360.0,
|
||||
"applicantId": "2102259564525301761",
|
||||
"applicantName": "gbm8154test",
|
||||
"approvedById": "2102259564525301761",
|
||||
"approvedByName": "gbm8154test",
|
||||
"approveRemark": "确认无法成团",
|
||||
"canApprove": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
团里没有已付户时不建任何退款单,`actualRefundAmount` 为 `0.00`。退款单建单失败的户不计入 `actualRefundAmount`,由对账 Job 兜底补建;接口本身仍返回成功(流团已提交,不回滚)。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalStatus": "APPROVED",
|
||||
"affectedOrderCount": 1,
|
||||
"estimatedRefundAmount": 0.0,
|
||||
"actualRefundAmount": 0.0,
|
||||
"canApprove": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589547,
|
||||
"message": "仅管理员可处理流团审批",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589547 | 当前角色不是管理员 / 超级管理员 / 团期管理员(如定制师、财务);或有请求但网关未透传角色;或团期管理员放行开关已关 |
|
||||
| 589545 | 流团申请不存在 |
|
||||
| 589546 | 申请已被处理(并发时后到者) |
|
||||
| 589544 | 团期已确认或更靠后,不可批复流团 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 团期管理员按「任一团期管理员」放行,系统里还没有「本团团期管理员」关系,与 #8436 退单一致。
|
||||
- 不禁止自审:团期管理员可以批自己提交的申请;`applicant*` 与 `approvedBy*` 分开落库。
|
||||
- 批后各户退款单为 `PENDING`,`calculatedAmount` = 该户已付全额(流团按已付全额退,不扣违约金),审核人在退款审批中心不填金额直接同意即按此额退。
|
||||
- 退款审核端点 `POST /v3/admin/refund/review` 仍禁止团期管理员(581008),所以团期管理员批了流团也退不出钱,钱的出口由退款审核人把关。
|
||||
|
||||
### 2. GB-ADM-062 拒绝流团 `POST /v3/admin/order/group-batch/disband/{approvalId}/reject`
|
||||
|
||||
**VO**: `DisbandApprovalRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期审批中心「流团」行或流团详情页点「拒绝」。只改申请单状态,团期继续正常招募,订单与钱一律不动。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| approvalId | path | Long | 是 | 正整数,流团申请单 ID | 不存在返 589545 |
|
||||
| remark | body | String | 是 | 非空白,≤ 512 字 | 拒绝原因,写进团期时间线 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| approvalStatus | String | 拒绝后为 `REJECTED` |
|
||||
| approvedById / approvedByName | Long / String | 本次审批人;团期管理员现在也会出现在这里 |
|
||||
| approveRemark | String | 拒绝原因 |
|
||||
| canApprove | Boolean | 已处理后恒为 false |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/group-batch/disband/2105980238163030017/reject HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <团期管理员 token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"remark": "本周还有两户在咨询,继续招募到下周一再定"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalId": "2105980238163030017",
|
||||
"groupBatchId": "2105980235310923777",
|
||||
"batchNo": "T26-9826",
|
||||
"approvalStatus": "REJECTED",
|
||||
"approvalStatusName": "已拒绝",
|
||||
"approvedByName": "gbm8154test",
|
||||
"approveRemark": "本周还有两户在咨询,继续招募到下周一再定",
|
||||
"canApprove": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无降级分支:拒绝只写申请单一行和一条时间线,失败即整体回滚并返回错误码。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589546,
|
||||
"message": "该流团申请已处理,不可重复操作",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589547,
|
||||
"message": "仅管理员可处理流团审批",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589547 | 同「同意流团」 |
|
||||
| 589545 | 流团申请不存在 |
|
||||
| 589546 | 申请已被处理 |
|
||||
| 400 | `remark` 为空或超长 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 拒绝后同一团期可以再次提交流团。提交接口有防重窗口,拒绝后立刻重提会返回 100502「请勿重复提交」,隔几秒再提即可(既有行为,本次未改)。
|
||||
- 审批人范围与「同意流团」完全一致,共用同一道角色门。
|
||||
|
||||
### 3. GB-ADM-061 团期审批中心列表 `GET /v3/admin/order/group-batch/approvals/page`
|
||||
|
||||
**VO**: `GroupBatchApprovalItemRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期审批中心页的统一列表(流团 + 退单两类)。前端按行的 `canApprove` 决定是否显示「同意 / 拒绝」。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| bizType | query | String | 否 | `DISBAND` / `WITHDRAW` | 业务类型 |
|
||||
| approvalStatus | query | String | 否 | `PENDING` / `APPROVED` / `REJECTED` / `CANCELLED` | 审批状态 |
|
||||
| groupBatchId | query | Long | 否 | 正整数 | 按团期筛 |
|
||||
| batchName | query | String | 否 | — | 团期名称关键字 |
|
||||
| pageNum | query | Integer | 否 | ≥ 1,默认 1 | 页码 |
|
||||
| pageSize | query | Integer | 否 | 默认 20 | 每页条数 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| records[].canApprove | Boolean | **本次取值变化**:流团行 = 待审批 + 团期仍可流团 + 当前人过 GB-ADM-062 的角色门。团期管理员在待审批流团行上由 false 变 true;退单行口径不变 |
|
||||
| records[].approvedByName | String | 审批人,团期管理员现在也会出现 |
|
||||
| 其余字段 | — | 未改 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/approvals/page?pageNum=1&pageSize=20&bizType=DISBAND&approvalStatus=PENDING HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <团期管理员 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"approvalId": "2105977252741349378",
|
||||
"bizType": "DISBAND",
|
||||
"bizTypeName": "流团",
|
||||
"groupBatchId": "2105974855696547842",
|
||||
"batchNo": "T26-7048",
|
||||
"batchName": "11月26日海拉尔-额尔古纳4日团",
|
||||
"approvalStatus": "PENDING",
|
||||
"approvalStatusName": "待审批",
|
||||
"affectedOrderCount": 2,
|
||||
"participantCount": 4,
|
||||
"estimatedRefundAmount": 3360.0,
|
||||
"reason": "临近出团报名不足 6 户,无法成团",
|
||||
"applicantName": "gbm8154test",
|
||||
"canApprove": true
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有匹配的申请时返回空页。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589507,
|
||||
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589507 | 无 `group-batch:view` 权限码(读端点判权码,与审批角色门无关,本次未改) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `canApprove` 整页只算一次,与行数无关。
|
||||
- 定制师、财务等角色在流团行上仍为 false;管理员 / 超级管理员仍为 true。
|
||||
- 团期管理员放行开关关掉时,团期管理员在流团行上回到 false。
|
||||
|
||||
### 4. GB-ADM-061 流团申请详情 `GET /v3/admin/order/group-batch/disband/{approvalId}`
|
||||
|
||||
**VO**: `DisbandApprovalRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心点进流团详情;详情页的「同意 / 拒绝」按钮按 `canApprove` 显隐。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| approvalId | path | Long | 是 | 正整数,流团申请单 ID | 不存在返 589545 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| canApprove | Boolean | **本次取值变化**,口径同列表:待审批 + 团期仍可流团 + 过角色门。团期管理员由 false 变 true |
|
||||
| 其余字段 | — | 未改 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/disband/2105980238163030017 HTTP/1.1
|
||||
Host: api.test.1814.love
|
||||
Authorization: Bearer <团期管理员 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalId": "2105980238163030017",
|
||||
"groupBatchId": "2105980235310923777",
|
||||
"batchNo": "T26-9826",
|
||||
"batchName": "12月10日海拉尔-额尔古纳4日团",
|
||||
"approvalStatus": "PENDING",
|
||||
"approvalStatusName": "待审批",
|
||||
"reason": "临近出团报名不足 6 户,无法成团",
|
||||
"affectedOrderCount": 2,
|
||||
"participantCount": 4,
|
||||
"estimatedRefundAmount": 3360.0,
|
||||
"applicantName": "gbm8154test",
|
||||
"canApprove": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
申请已处理(通过 / 拒绝 / 撤销)或团期已不可流团时,`canApprove` 为 false,其余字段照常返回。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"approvalId": "2105980238163030017",
|
||||
"approvalStatus": "REJECTED",
|
||||
"approvalStatusName": "已拒绝",
|
||||
"canApprove": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589545,
|
||||
"message": "流团申请不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
| 错误码 | 触发条件 |
|
||||
|---|---|
|
||||
| 589545 | 申请不存在 |
|
||||
| 589507 | 无 `group-batch:view` 权限码 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `canApprove` 与 GB-ADM-062 走同一个判定方法,不会出现「显示了按钮、点下去 589547」。
|
||||
- 缺角色时会打一条 `GB_APPROVAL_ROLE_CLAIM_MISSING` WARN,`canApprove` 返回 false。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 前端按 `canApprove` 显隐「同意 / 拒绝」,**不要按角色自己判**:团期管理员能否审批受 nacos 开关控制,角色相同、结果可能不同。
|
||||
- 同意流团的响应里 `actualRefundAmount` 现在表示「已建退款单、待二审」的合计,不代表钱已退出。要看每户退款进度,查退款审批中心(`GET /v3/admin/refund/application/page`)或订单退款列表。
|
||||
- 退款审批中心会多出流团产生的 `PENDING` 退款单,申请人类型为 `SYSTEM`,原因文案是流团原因。审核人可直接同意(按 `calculatedAmount` 即已付全额退),也可改额或拒绝。
|
||||
- 团期管理员不能审核退款(581008),流团退款的二审要由财务 / 管理员等有退款审核权的人处理。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- **零 schema 变更**,无 Flyway 迁移。
|
||||
- 同意流团的写入集合不变:申请单、团期、子订单、时间线、配车释放 outbox 照旧;退款单照旧由取消事件建出。
|
||||
- 唯一变化是退款单的落库状态:改前建单后同一流程里自动写一条 `refund_review`(审核人 = 流团审批人)并把 `refund_application.status` 推到 `APPROVED`;改后只建 `PENDING` 单,`refund_review` 零行、`reviewed_at` 为空,等退款审批中心处理。
|
||||
- 流团退款对账建单时写入的 `calculated_amount` 由「按退款政策算」改为「该户已付全额」,与通用建单路径写同一个数。
|
||||
- 拒绝流团的写入不变:只改申请单一行 + 一条时间线。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 两个 nacos 开关,默认都开:
|
||||
- `group-batch.disband.refund-second-review`:关掉时,退款回到改前自动放行,每次同意都打 `GB_DISBAND_REFUND_SECOND_REVIEW_OFF` WARN;**同时收回团期管理员的流团审批权**(否则团期管理员一个人就能把整团的钱自动退出去)。
|
||||
- `group-batch.acl.allow.disband-approver-group-batch-manager`:关掉时只收回团期管理员的流团审批权,二审保留;团期管理员被拒时打 `GB_DISBAND_APPROVER_GBM_DISABLED` WARN。
|
||||
- 流团这对开关与退单(#8436)那对开关互不牵动,两类审批分别回滚。
|
||||
- 开关取不到(容器未绑定)时按更严处理:不放团期管理员。
|
||||
- 未付户随流团取消,不建退款单(既有行为)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 团期管理员同意 / 拒绝流团 | 589547 | 成功,审批人记为团期管理员 |
|
||||
| 定制师 / 财务同意 / 拒绝流团 | 589547 | 589547(不变) |
|
||||
| 管理员同意 / 拒绝流团 | 成功 | 成功(不变) |
|
||||
| 同意流团后已付户的退款单 | `APPROVED`,带 1 条审核记录(审核人 = 流团审批人) | `PENDING`,0 条审核记录,`calculated_amount` = 已付全额 |
|
||||
| 审批中心 / 流团详情 `canApprove`(团期管理员,待审批流团) | false | true |
|
||||
| 审批中心 / 流团详情 `canApprove`(定制师) | false | false(不变) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端**:零改动。审批中心页已对团期管理员开放,按钮按 `canApprove` 显隐,后端放开后按钮自动出现。
|
||||
- **财务 / 退款审核人**:退款审批中心多出流团退款单,需要人工审核后才会退款,流团退款到账时间会因此延后。这是本次的目的。
|
||||
- **回滚**:改 nacos 开关即可,不需要发版。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 发起流团 GB-ADM-060:判权(`group-batch:manage` 权限码)与行为不变。
|
||||
- 退单审批 GB-ADM-072 ~ 075:判定、开关、二审都不变,#8436 的那对开关不受本次影响。
|
||||
- 退款审核端点 `POST /v3/admin/refund/review`:仍禁止团期管理员。
|
||||
- 单笔取消订单、C 端申请退款等其他建退款单的路径:不变。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
部署:dev-v3 @ `c4a410a94`,2026-10-02 19:01 部署 hl-order-service-v3。运行字节探针在 8086 / 8186 两个实例上都命中本次新增的 `GB_DISBAND_APPROVER_GBM_DISABLED` 与 `GB_DISBAND_REFUND_SECOND_REVIEW_OFF`;进程 jar 与磁盘 jar 为同一 inode。
|
||||
|
||||
判权一律用低权限角色声明取证:团期管理员用 TEST 上当前角色即团期管理员的 `gbm8154test`;不用超级管理员。
|
||||
|
||||
| 场景 | 改前(`1f8b1dacd`,团期 `T26-0659`) | 改后(`c4a410a94`,团期 `T26-7048` / `T26-3437` / `T26-9826`) |
|
||||
|---|---|---|
|
||||
| 团期管理员同意 / 拒绝真实待审批流团 | 589547 / 589547 | 拒绝成功(`T26-7048` 申请 `2105977252741349378`);同意成功(申请 `2105977953445969921`) |
|
||||
| 定制师同意 / 拒绝 | 589547 / 589547 | 589547 / 589547,申请单仍 PENDING |
|
||||
| 管理员同意 | 成功 | 成功(申请 `2105977508052828161`) |
|
||||
| 已付户退款单(全款 3360.00) | `APPROVED`,`refund_review` 1 行 | 团期管理员批、管理员批两例都是 `PENDING`,`calculated_amount=3360.00`,`refund_review` 0 行,`reviewed_at` 为空 |
|
||||
| 退款审批中心 PENDING 列表 | — | 两张流团退款单都在列 |
|
||||
| 审批中心 `canApprove`(团期管理员 / 定制师) | false / — | true / false |
|
||||
| 流团详情 `canApprove`(团期管理员 / 定制师 / 管理员) | — | true / false / true;拒绝后 false |
|
||||
| 团期管理员调退款审核端点 | — | 581008,钱的出口仍挡住团期管理员 |
|
||||
|
||||
全部调用 HTTP 200;审批单 `actual_refund_amount` 两例都定稿为 3360.00。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 工单:wx/HL#8246
|
||||
- PR:wx/HL#8734(merge commit `c4a410a94`)
|
||||
- 参照:#8436 退单审批放开团期管理员 + 退款二审
|
||||
- 代码:`WithdrawApprovalGuard#assertDisbandApproverRole`、`GroupBatchDisbandApprovalService#approveInTx`、`GroupBatchAclToggle`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 后端:jw
|
||||
- 前端:无需改动(hl-ui v2.1 已按 `canApprove` 显隐)
|
||||
- 关联工单:#8436、#8253、#7294、#7609
|
||||
@@ -0,0 +1,520 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8659"
|
||||
title: "房务价格日历与库存口径统一:候选页与控房表增加日历状态,扣减拒绝按原因分三码"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务配房接口调整:价格日历与库存口径统一、扣减失败分码
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3、hl-resource-service
|
||||
> **Issue**: #8659
|
||||
> **日期**: 2026-10-02
|
||||
> **影响范围**: 管理后台房务配房流程,涉及候选酒店页、控房表、配房失败反馈
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **候选酒店页 `inventoryStatus` 扩大语义**:日历已售罄(`SOLD_OUT`)映射为 `FULL`,日历已关闭(`CLOSED`)或无日历行映射为 `CLOSED`;非 `AVAILABLE` 时 `available=0`、`unlimited=false`(即使库存数本身大于 0)。**前端无需改代码**:经 hl-ui v2.1 核实,候选弹窗(`PickHotelModal.vue`)已按 `inventoryStatus` 三值禁用非 `AVAILABLE` 选项、显示「满房」「已关」,且取值用 `pickFirst` 读取不会被 `available=0` 误判为缺省继续读旧的 `stock`。
|
||||
- **控房表新增 `calendarStatus` / `calendarStatusName`**:价格日历状态原值与中文名。纯新增字段,不读不影响现有解析;**前端需在控房表加一列展示 `calendarStatusName`**,房务才能在剩余数大于 0 时看出该格「已关闭」或「已售罄」、扣减必被拒(这是本次唯一的前端动作)。
|
||||
- **扣减失败时按原因分三种错误码**(原来统一报 808901):
|
||||
- `808906`「该日该房型未配置价格日历」(新增)
|
||||
- `808907`「该日该房型已关闭售卖或已售罄(状态:{0})」(新增,`{0}` 为状态中文名)
|
||||
- `808901`「房型库存不足」(文案收窄,现仅表示日历可售但库存不够;旧文案与此不同,见下)
|
||||
- 以上均由前端通用拦截器按 `message` 自动弹出,调用方无需按 `code` 分支即可正确展示。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
资源侧价格日历状态(可售 `AVAILABLE` / 已售罄 `SOLD_OUT` / 已关闭 `CLOSED`)与库存数是两个独立维度。读侧(候选页、控房表)曾只看库存、不读状态,与写侧(库存扣减按 `status=AVAILABLE` 谓词)产生口径断裂:房务在候选页看到「可订」而提交配房被拒,甚至产生"该加房量"的错误判断,实际原因是日历已关房。本次统一口径:读侧添加日历状态、扣减时按真实原因分码。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 候选酒店页 | GET | `/v3/admin/hotel-candidates` | 修改 | roomTypes[].inventoryStatus 取值语义扩大 |
|
||||
| 2 | 控房表 | GET | `/v3/admin/order/house-console/room-control` | 修改 | 新增 calendarStatus / calendarStatusName |
|
||||
| 3 | 逐晚提交配房 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | 修改 | 扣减拒绝返回 808906 / 808907 / 808901(原统一 808901) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
本节示例 JSON 的字段名、类型、错误码与文案逐一取自源码;ID、日期等取值为说明用的构造值。所有接口统一返回 `Result` 信封(`code` / `message` / `data` / `traceId` / `success`),示例省略 `traceId`;业务失败与入参校验失败均为 HTTP 200,靠 `code` 区分。
|
||||
|
||||
### 1. 候选酒店页 `GET /v3/admin/hotel-candidates`
|
||||
|
||||
**VO**: `HotelCandidateQueryReqVO → HotelCandidateRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务/定制师为某个订单的某一晚选酒店时查看候选酒店列表及房型库存。本次改动只影响候选房型 `roomTypes[].inventoryStatus`(及联动的 `available`/`unlimited`)的取值口径,请求参数与响应结构均未变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Query | Long | ✅ | - | 订单 ID |
|
||||
| dayNumber | Query | Integer | ❌ | ≥1 | 第几天(从 1 开始,`stayDate = departDate + dayNumber - 1`),与 stayDate 二选一,直接传 stayDate 优先 |
|
||||
| stayDate | Query | LocalDate | ❌ | - | 入住日期;直接指定优先于 dayNumber 推算 |
|
||||
| city | Query | String | ❌ | - | 城市代码 |
|
||||
| keyword | Query | String | ❌ | - | 关键词非空时突破单城限定,跨城/省按酒店名/城市/省份/地址匹配;为空维持单城行为 |
|
||||
| limit | Query | Integer | ❌ | 1~50,默认 30 | 返回候选条数上限,池内/定制师优先排序后取前 N |
|
||||
| roomCategory | Query | String | ❌ | 字典 room_category | 非空白时参与房型过滤,matched 房型只在同大类内选取 |
|
||||
| roomCount | Query | Integer | ❌ | ≥1 | 需要的房间数;不传退化为 available>0 的旧行为 |
|
||||
| preferredHotelId | Query | Long | ❌ | - | 定制师指定的优先酒店 ID |
|
||||
| requirementId | Query | Long | ❌ | - | 住宿需求 ID;传入后该需求 days JSON 里所有 hotelId 作为定制师指定 |
|
||||
|
||||
#### 出参 `Result<HotelCandidateRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| stayDate | LocalDate | 入住日期 |
|
||||
| city | String | 城市代码 |
|
||||
| productType | String | 产品类型(CORE/GROUP/CUSTOM,响应专有字段,无同名入参) |
|
||||
| candidates[] | List<Candidate> | 候选酒店列表 |
|
||||
| candidates[].roomTypes[] | List<RoomTypeOption> | 该酒店当日真实房型列表 |
|
||||
| candidates[].roomTypes[].available | Integer | **语义变化**。`unlimited=true` 时为 NULL(表不限);否则为当日可用房数。`inventoryStatus` 非 `AVAILABLE` 时恒为 `0`(即使库里 stock 列大于 0) |
|
||||
| candidates[].roomTypes[].unlimited | Boolean | **语义变化**。resource 端 stock 列为 NULL 时才为 `true`;`inventoryStatus` 非 `AVAILABLE` 时恒为 `false`(工单 #8659 前,日历已关闭/售罄但 stock=NULL 的房型也会显示「不限」,现改为显示不可订) |
|
||||
| candidates[].roomTypes[].inventoryStatus | String | **修改**。`AVAILABLE`=日历可售且有余量或不限库存;`FULL`=日历可售但余量为 0 或日历已售罄;`CLOSED`=未配置价格日历、日历已关闭或日历状态字典外脏值(工单 #8659)。`FULL`/`CLOSED` 时价格字段(protocolPrice/settlementPrice/basePrice)照常返回,只有库存字段归零 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/hotel-candidates?orderId=100001&dayNumber=1&city=hailar&roomCount=2
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"stayDate": "2026-10-05",
|
||||
"city": "hailar",
|
||||
"productType": "CORE",
|
||||
"candidates": [
|
||||
{
|
||||
"hotelId": "200001",
|
||||
"hotelName": "海拉尔假日酒店",
|
||||
"roomTypes": [
|
||||
{
|
||||
"roomTypeId": "300001",
|
||||
"name": "豪华大床房",
|
||||
"roomCategory": "KING",
|
||||
"available": 7,
|
||||
"unlimited": false,
|
||||
"stock": 7,
|
||||
"protocolPrice": "328.00",
|
||||
"inventoryStatus": "AVAILABLE"
|
||||
},
|
||||
{
|
||||
"roomTypeId": "300002",
|
||||
"name": "标准双床房",
|
||||
"roomCategory": "TWIN",
|
||||
"available": 0,
|
||||
"unlimited": false,
|
||||
"stock": 5,
|
||||
"protocolPrice": "280.00",
|
||||
"inventoryStatus": "CLOSED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
上例第二个房型 `stock=5`(库里仍有余量)但日历已关闭,`inventoryStatus=CLOSED`、`available` 归零——这正是本次改动要修的口径断裂:改前 `available` 会原样显示库里的 5。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 该订单该日无酒店或筛选无匹配:`candidates=[]`。
|
||||
- 日历状态字典外脏值按 `CLOSED` 处理(不单列「异常」状态)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "orderId 不能为空",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `inventoryStatus` 判定日历状态优先于库存数:日历 `CLOSED` 或 `SOLD_OUT` 时,即使 resource 库存列数字大于 0,`available`/`unlimited` 仍归零,不能据库存字段反推是否可订。
|
||||
- `limit` 默认 30、上限 50,候选列表本身是截断后的结果,不代表该城市全部酒店。
|
||||
|
||||
### 2. 控房表 `GET /v3/admin/order/house-console/room-control`
|
||||
|
||||
**VO**: `HouseRoomControlListReqVO → HouseRoomControlRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务在房务控制台查看某个城市(或某酒店)、某日期区间的库存占用详情及团组用房明细。本次新增 `calendarStatus` / `calendarStatusName` 两个字段;前端在控房表加一列展示 `calendarStatusName` 后,房务无需逐行点开即可看到日历状态。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| cityCode | Query | String | ❌ | ≤32 字 | 城市编码;与 hotelId 都不传则查全部酒店 |
|
||||
| hotelId | Query | Long | ❌ | - | 酒店 ID;传了则只查这一家,优先于 cityCode |
|
||||
| dateFrom | Query | LocalDate | ✅ | - | 入住夜起(含) |
|
||||
| dateTo | Query | LocalDate | ✅ | 与 dateFrom 跨度 ≤62 天 | 入住夜止(含) |
|
||||
| onlyWithRemain | Query | Boolean | ❌ | - | true 时只返回剩余为不限或 >0 的行 |
|
||||
|
||||
#### 出参 `Result<HouseRoomControlRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| stockTrackingEnabled | Boolean | resource 全局库存追踪开关;false 时行照常返回、已用取实际值,但调房量会被拒(808312) |
|
||||
| rows[] | List<Row> | 按(酒店, 入住夜, 房型)升序 |
|
||||
| rows[].hotelId / hotelName / cityName | - | 酒店 ID / 酒店名 / 城市中文名 |
|
||||
| rows[].roomTypeId / roomTypeName | - | 房型 ID / 房型名 |
|
||||
| rows[].stayDate | LocalDate | 入住夜 |
|
||||
| rows[].totalRooms | Integer | 控房总数 = 剩余 + 已用;NULL 表不限量 |
|
||||
| rows[].usedRooms | Integer | 已用(resource stock_used;库存开关关闭时仍取实际值) |
|
||||
| rows[].remainRooms | Integer | 剩余(resource stock);NULL 表不限量 |
|
||||
| rows[].assignedRooms | Integer | 已分配:扣库存的配房行 + 已确认扣库存的团期计划行间数合计 |
|
||||
| rows[].protocolPrice / settlementPrice | BigDecimal(字符串) | 控房价(协议价)/ 结算价 |
|
||||
| rows[].calendarStatus | String | **新增**。价格日历状态原值:`AVAILABLE / SOLD_OUT / CLOSED`,字典外历史值原样返回;该格无日历行时为 NULL。非 `AVAILABLE` 时扣减必被拒,与剩余数无关(工单 #8659) |
|
||||
| rows[].calendarStatusName | String | **新增**。价格日历状态中文名:可售 / 已售罄 / 已关闭;字典外值显示「状态异常(原值)」;无日历行时为 NULL |
|
||||
| rows[].usages[] | List<Usage> | 团组用房明细(散客配房行或团期计划行,本表只列扣库存行) |
|
||||
| rows[].usages[].orderId / teamNo / batchNo | - | 订单 ID(团期计划行为 NULL)/ 团号 / 团期批次号(散客订单为 NULL) |
|
||||
| rows[].usages[].roomCount / roomSource / roomSourceLabel / confirmStatusLabel | - | 用房间数 / 房源(STOCK)/ 房源标签 / 确认状态标签 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/house-console/room-control?cityCode=hailar&dateFrom=2026-10-01&dateTo=2026-10-31
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"stockTrackingEnabled": true,
|
||||
"rows": [
|
||||
{
|
||||
"hotelId": "100001",
|
||||
"hotelName": "海拉尔草原酒店",
|
||||
"cityName": "海拉尔",
|
||||
"roomTypeId": "300001",
|
||||
"roomTypeName": "豪华双床房",
|
||||
"stayDate": "2026-10-01",
|
||||
"totalRooms": 12,
|
||||
"usedRooms": 5,
|
||||
"remainRooms": 7,
|
||||
"assignedRooms": 5,
|
||||
"protocolPrice": "320.00",
|
||||
"settlementPrice": "300.00",
|
||||
"calendarStatus": "AVAILABLE",
|
||||
"calendarStatusName": "可售",
|
||||
"usages": [
|
||||
{
|
||||
"orderId": "1930000000000000001",
|
||||
"teamNo": "HL20261001A",
|
||||
"batchNo": null,
|
||||
"roomCount": 3,
|
||||
"roomSource": "STOCK",
|
||||
"roomSourceLabel": "控房",
|
||||
"confirmStatusLabel": "已确认"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hotelId": "100002",
|
||||
"hotelName": "海拉尔雅园宾馆",
|
||||
"cityName": "海拉尔",
|
||||
"roomTypeId": "300005",
|
||||
"roomTypeName": "标准大床房",
|
||||
"stayDate": "2026-10-01",
|
||||
"totalRooms": 8,
|
||||
"usedRooms": 2,
|
||||
"remainRooms": 6,
|
||||
"assignedRooms": 3,
|
||||
"protocolPrice": "280.00",
|
||||
"settlementPrice": "260.00",
|
||||
"calendarStatus": "CLOSED",
|
||||
"calendarStatusName": "已关闭",
|
||||
"usages": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 区间内无库存数据:`rows=[]`,`stockTrackingEnabled` 仍照常返回开关实际值。
|
||||
- 该格无日历行时 `calendarStatus`/`calendarStatusName` 为 NULL(不是「状态异常」,无行与脏值是两种不同情况)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "dateFrom 不能为空",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
日期跨度超 62 天或起止颠倒不走参数校验码,由 Manager 本地校验后返业务码 808313。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `calendarStatus=CLOSED` 或 `SOLD_OUT` 时,`remainRooms` 数字再大也**无法扣减**(扣减 UPDATE 只认 `status=AVAILABLE`);房务需在此处调整日历或释放库存,不能指望后续配房流程放行。
|
||||
- 该表不隐藏 `remainRooms`,即使日历已关闭仍显示实际 stock,控房表目的是查看与调整库存,不把实际数字藏起来。
|
||||
- `stockTrackingEnabled=false` 时各行仍按实际值返回 `usedRooms`/`remainRooms`,但调房量会被拒(808312),不能据此误判为"关闭追踪=不限量"。
|
||||
|
||||
### 3. 逐晚提交配房 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`
|
||||
|
||||
**VO**: `AssignmentSubmitReqVO → AssignmentSubmitRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务为一个住宿需求批量提交配房方案(一次可提交多晚 × 多组房间)。本次修改:扣减失败时根据真实原因(日历无行、日历不可售、库存不足)返回不同错误码,不再统一报 808901。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
|
||||
| items | Body | List<AssignmentItemReqVO> | ✅ | 非空 | 配房项列表(批量) |
|
||||
| items[].dayNumber | Body | Integer | ✅ | ≥1 | 第几天(Day1=1) |
|
||||
| items[].hotelId | Body | Long | ✅ | - | 酒店 ID |
|
||||
| items[].roomTypeId | Body | Long | ✅ | - | 房型 ID |
|
||||
| items[].roomCategory | Body | String | ✅ | 字典 room_category | 房型字典 code(如 STANDARD) |
|
||||
| items[].roomCount | Body | Integer | ✅ | ≥1 | 间数 |
|
||||
| items[].deductInventory | Body | Boolean | ✅ | - | 是否扣减资源酒店房型库存;true=占用系统库存,false=仅保存配房快照不扣库存 |
|
||||
| items[].protoPrice | Body | BigDecimal | ❌ | ≥0 | 协议价快照;不传按所选房型当日资源协议价兜底 |
|
||||
| items[].settlementPrice | Body | BigDecimal | ❌ | ≥0 | 结算价快照;不传按资源结算价兜底,再兜底协议价 |
|
||||
| items[].settleType | Body | String | ❌ | cash\|sign\|company | 支付方式快照;不传取酒店资源配置 |
|
||||
| items[].breakfast | Body | String | ❌ | INCLUDED/EXCLUDED/PENDING | 早餐;不传按待确认存空 |
|
||||
| items[].syncProtocolPrice / syncSettlementPrice / syncSettleType | Body | Boolean | ❌ | - | 是否把本项对应快照同步写回 resource;默认 false 不同步 |
|
||||
| items[].remark | Body | String | ❌ | - | 备注 |
|
||||
| items[].replaceReason | Body | String | ❌ | ≤256 字 | 替换原因;仅该天已有旧行被本项替换时落库 |
|
||||
|
||||
#### 出参 `Result<AssignmentSubmitRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| successCount | Integer | 成功条数 |
|
||||
| failCount | Integer | 失败条数。当前实现恒为 `0`——扣减失败走整笔拒绝(见下),不会出现"部分成功部分失败"的响应 |
|
||||
| items[] | List<Item> | 每条配房结果 |
|
||||
| items[].dayNumber | Integer | 第几天 |
|
||||
| items[].assignmentId | Long(JSON 字符串) | 配房 ID |
|
||||
| items[].arrange | String | 配房状态,取值仅 `inquiring`(询价中)/ `confirmed`(已确认) |
|
||||
| items[].deductInventory | Boolean | 本次配房是否扣减了库存 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"hotelId": 100001,
|
||||
"roomTypeId": 300001,
|
||||
"roomCategory": "STANDARD",
|
||||
"roomCount": 2,
|
||||
"deductInventory": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"successCount": 1,
|
||||
"failCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"assignmentId": "1970000000000000001",
|
||||
"arrange": "inquiring",
|
||||
"deductInventory": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无特殊降级。批量提交中任意一项触发库存扣减失败(808906/808907/808901)会导致**整次提交被拒绝**,不落库、不产生部分成功的配房记录;需要调整后整批重新提交。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808906,
|
||||
"message": "该日该房型未配置价格日历",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
其他错误:
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 808907 | 该日该房型已关闭售卖或已售罄(状态:{0}) | 日历 status 为 CLOSED 或 SOLD_OUT,`{0}` 为状态中文名 |
|
||||
| 808901 | 房型库存不足 | 日历 status=AVAILABLE 但剩余数不够 |
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
|
||||
| 808116 | 订单未抢单, 请先抢单再配房 | 需求无人持有(源码原文逗号为半角) |
|
||||
| 808110 | 需求不属于当前用户 | 当前用户不是持有人(超管同样拒绝) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `deductInventory=true` 的项触发库存扣减,三种拒绝原因分别报 808906 / 808907 / 808901;批量提交中任一项被拒,整次提交失败,不做部分落库。
|
||||
- `deductInventory=false` 的项不做库存校验,正常返回 200。
|
||||
- `roomCategory` 为必填字段,传空或不传直接触发参数校验失败(`code=400`,HTTP 状态仍为 200)。
|
||||
- 防重 3 秒,同一需求 3 秒内重复提交被拦截。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
**通俗说法**:配房前看候选页或控房表,若日历状态显示「已关闭」或「已售罄」,不能提交配房;提交时若被拒,先按错误码判断原因——库存不足就加库存,日历问题就调日历。
|
||||
|
||||
### 示例对比
|
||||
|
||||
| 场景 | 旧行为 | 新行为 |
|
||||
|------|--------|--------|
|
||||
| 日历已关闭、stock=20 | 候选页显示可订、余 20 间;提交配房返 808901「库存不足」(误导) | 候选页 inventoryStatus=CLOSED、available=0;提交配房返 808907「已关闭」(准确);控房表 calendarStatus=CLOSED 可直观查看 |
|
||||
| 日历无行 | 候选页显示可订或不显示(看评分);提交被拒 808901 | 候选页 inventoryStatus=CLOSED、available=0;提交配房返 808906「未配置日历」(准确指示资源侧缺陷) |
|
||||
| 日历可售、stock=0 | 候选页显示 FULL、available=0;提交被拒 808901 | 候选页显示 FULL、available=0;提交被拒 808901(同前) |
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
扣减仍走库存表 UPDATE 逻辑,本次仅改返回码与日历状态展示,无 DDL 或表结构变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 错误码 808906 / 808907 / 808901 的优先级:先判日历有无(808906)→再判日历状态(808907)→最后判库存(808901)。
|
||||
- 控房表的 `remainRooms` 与日历状态 `CLOSED` 同时出现时,表示日历被关了但库存数还在,房务调整库存须先对日历解冻(不归房务接口管)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5 枚举
|
||||
|
||||
### inventoryStatus(HotelCandidateRespVO.RoomTypeOption)
|
||||
|
||||
**类型**: `String`
|
||||
|
||||
| 值 | 条件 | 前端表现 |
|
||||
|----|------|--------|
|
||||
| `AVAILABLE` | 日历 status=AVAILABLE 且库存>0(或 NULL 不限) | 可订、显示余量 |
|
||||
| `FULL` | 日历 status=AVAILABLE 但库存=0,或日历 status=SOLD_OUT | 满房、显示「已满」 |
|
||||
| `CLOSED` | 日历 status=CLOSED 或无日历行 | 关闭、显示「不可订」 |
|
||||
|
||||
### calendarStatus(HouseRoomControlRowRespVO)
|
||||
|
||||
**类型**: `String`
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `AVAILABLE` | 可售 |
|
||||
| `SOLD_OUT` | 已售罄 |
|
||||
| `CLOSED` | 已关闭 |
|
||||
| NULL | 无日历行 |
|
||||
| 其他 | 字典外的历史脏数据(原样返回) |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| 候选页 inventoryStatus 逻辑 | 仅看库存数;SOLD_OUT→FULL、CLOSED→CLOSED(但都显示有库存或空库存) | 优先看日历状态;SOLD_OUT→FULL、CLOSED→CLOSED(且 available=0) |
|
||||
| 控房表字段 | calendarStatus / calendarStatusName 无 | **新增**,展示日历原值与中文名 |
|
||||
| 扣减失败错误码 | 统一 808901(三种原因混合) | 分码:808906(无日历)、808907(日历不可售)、808901(库存不足) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端改动只有一处:控房表加「日历状态」列**(hl-ui v2.1 实查结论):
|
||||
- 候选弹窗 `src/views/housekeeper/components/PickHotelModal.vue` 已按 `inventoryStatus` 三值分支渲染禁用态与文案(`disabled: !rt.unlimited && rt.inventoryStatus !== 'AVAILABLE'`,CLOSED 显示「已关」/FULL 显示「满房」),取库存数用 `pickFirst()` 辅助函数正确处理 `available=0` 而不误判为缺省继续回退读旧字段,三值语义收紧不影响该组件现有行为。
|
||||
- 控房表新增的 `calendarStatus`/`calendarStatusName`/`usages[]` 均为纯新增字段,hl-ui v2.1 当前对该查询结果的消费代码未读取这些字段名,新增不影响现有解析;**需要新增一列展示 `calendarStatusName`**(为 NULL 时显示「—」),否则剩余数大于 0 而日历已关闭的格子在页面上看不出来。
|
||||
- 配房提交的错误码拆分(808906/808907/808901)均由 `src/api/housekeeper/assignment.js` 所在模块走通用拦截器按 `message` 弹窗展示,调用方代码未对 808xxx 做按值分支(`useHousekeeperPlacementAdjustment.js:71` 注释确认),拆分前后前端展示路径一致。
|
||||
- **触发频率变化**:`inventoryStatus=CLOSED` 的触发条件由「仅库存为 0」扩大为「日历关闭/售罄/无日历行 或 库存为 0」,该状态出现频率会提升,但因前端已走统一的三值禁用逻辑,不需要代码改动去适配。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 小程序端接口不变。
|
||||
- 订单确认、支付、发票等下游流程无改动。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服环境,2026-09-30~10-02。
|
||||
|
||||
```
|
||||
候选页(GET /v3/admin/hotel-candidates)
|
||||
关闭日期场景:keyword 查询返回 inventoryStatus=CLOSED, available=0 ✓
|
||||
重新开放:状态切回 AVAILABLE, available 恢复实际库存数 ✓
|
||||
|
||||
控房表(GET /v3/admin/order/house-console/room-control)
|
||||
关闭日期:calendarStatus=CLOSED, calendarStatusName=已关闭 ✓
|
||||
重新开放:calendarStatus=AVAILABLE, calendarStatusName=可售 ✓
|
||||
|
||||
配房扣减(POST /v3/admin/order/hotel-requirements/{id}/assignments)
|
||||
提交已关闭日期的房型:返回 808907「已关闭或已售罄」✓
|
||||
重新开放后提交:返回 200, stock 更新 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- **Issue**: [#8659](https://git.1814.love:8443/wx/HL/issues/8659)
|
||||
- **PR**: [#8704](https://git.1814.love:8443/wx/HL/pulls/8704)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
**关联工单**: #8659
|
||||
**同批变更**: #8662(旧接口删除与权限补漏)
|
||||
**后端负责人**: @wx
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8662"
|
||||
title: "删除旧住宿需求提交接口(PUT /v3/admin/order/{id}/hotel-requirement)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 删除旧住宿需求提交接口
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8662
|
||||
> **日期**: 2026-10-02
|
||||
> **影响范围**: 管理后台订单住宿需求提交流程
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 路由 `PUT /v3/admin/order/{id}/hotel-requirement` 已删除,服务端无此路由映射。
|
||||
- **替代接口**:`POST /v3/admin/order/{id}/adjustment/submit`,请求体 `{"updates":{"hotelRequirement":{days,specialTags,remark}}}`,响应 `{success}`。
|
||||
- **权限对齐**:旧接口零权限校验,任何后台账号可修改任意订单需求;新接口校验订单归属(管理员、超管、本单定制师放行,其他后台角色返回 581008;房务返回 581045)。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
旧接口 `PUT /v3/admin/order/{id}/hotel-requirement` 于 #4515 标注为废弃,继任者为 `POST /v3/admin/order/{id}/adjustment/submit`。源码删除说明(Controller 类 javadoc、`API-SPEC.html` §3.1)记载的旧接口缺陷:
|
||||
|
||||
1. **无权限校验**:该端点不校验操作人,任何登录后台的账号都能改写任意订单的住宿需求,不要求调用者是该单定制师。
|
||||
2. **DONE_ADJUST 分支继承原认领房务**:已完成版需求再调整时(`status=DONE` → 重提),服务端按 `order_hotel_requirement` 旧行 `is_active=0` + 新行 `version+1` 落库,新行直接复制原 `claimer_*`(沿用原房控、不重新入抢单池),这一继承行为与权限校验无关,继任接口同样保留(见六.6)。
|
||||
|
||||
继任接口已在服务层加入 `OrderViewGuard.assertOrderAccessible()` 的归属校验(管理员/超管放行,本单定制师放行,其他后台角色 581008,房务管理员 581045)。`API-SPEC.html` §3.1 删除说明与 hl-ui v2.1 代码核查一致确认:管理后台视图层此前已零调用旧接口(均已改走 `adjustment/submit`),故本次删除对前端无需额外改动。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 住宿需求提交(旧) | PUT | `/v3/admin/order/{id}/hotel-requirement` | 删除 | 改用 adjustment/submit |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
本接口已删除。下表记录的是**删除前**的契约,仅供前端清理调用点之用。字段名、类型、错误码逐一取自删除前源码。服务端已无该路由映射,调用不会返回本表所述的正常响应或错误码,而将返回 HTTP 404(路由不存在)。
|
||||
|
||||
### 1. 住宿需求提交(旧) `PUT /v3/admin/order/{id}/hotel-requirement`
|
||||
|
||||
**VO**: `HotelRequirementReqVO → HotelRequirementRespVO`(均已删除)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:定制师提交或修改订单的住宿需求(酒店偏好、特殊要求、入住日期等)。现改为 `POST /v3/admin/order/{id}/adjustment/submit`。
|
||||
|
||||
#### 入参(删除前)
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 订单 ID |
|
||||
| days | Body | List<DayReq> | ✅ | 非空、按 dayNumber 排序 | 逐晚配房需求 |
|
||||
| days[].dayNumber | Body | Integer | ✅ | ≥1 | 第几晚 |
|
||||
| days[].stayDate | Body | LocalDate | ✅ | - | 入住日期 |
|
||||
| days[].city | Body | String | ✅ | - | 城市代码 |
|
||||
| days[].customerSelfBooked | Body | Boolean | ❌ | 默认 false | 客人自订该晚酒店 |
|
||||
| days[].segments | Body | List<SegmentReq> | ❌ | - | 房间需求段(非自订晚通常需 ≥1 段) |
|
||||
| days[].segments[].roomCategory | Body | String | ✅ | TWIN / KING / ... | 房型分类 |
|
||||
| days[].segments[].roomCount | Body | Integer | ✅ | ≥1 | 间数 |
|
||||
| specialTags | Body | List<String> | ❌ | - | 特殊标签(e.g.「协议酒店」「靠近景区」) |
|
||||
| remark | Body | String | ❌ | ≤500 字 | 特殊要求备注 |
|
||||
|
||||
#### 出参(删除前) `Result<HotelRequirementRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requirementId | Long | 需求行 ID |
|
||||
| version | Integer | 版本号(首版=1) |
|
||||
| status | String | 需求状态(PENDING / DONE_ADJUST 等) |
|
||||
|
||||
#### 请求示例(删除前)
|
||||
|
||||
```json
|
||||
{
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"stayDate": "2026-10-05",
|
||||
"city": "hailar",
|
||||
"segments": [
|
||||
{
|
||||
"roomCategory": "KING",
|
||||
"roomCount": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"specialTags": ["协议酒店"],
|
||||
"remark": "靠近景区"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例(删除前)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"requirementId": "1930000000000000001",
|
||||
"version": 1,
|
||||
"status": "PENDING"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应(删除前)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "days 不能为空",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定;前端移除调用点。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,删除后返回 HTTP 404,前端不得依赖任何响应体判断,调用点一律移除。
|
||||
- 替代接口经 `OrderViewGuard.assertOrderAccessible()` 校验归属,管理员/超管/本单定制师放行,其他后台角色 581008,房务 581045。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 迁移路径
|
||||
|
||||
| 旧接口 | 新接口 | payload 转换 |
|
||||
|--------|--------|-------------|
|
||||
| `PUT /v3/admin/order/{id}/hotel-requirement` | `POST /v3/admin/order/{id}/adjustment/submit` | 旧 request body 的 `days` / `specialTags` / `remark` 改为嵌套:`{"updates":{"hotelRequirement":{days,specialTags,remark}}}` |
|
||||
|
||||
### 权限变化
|
||||
|
||||
| 角色 | 旧接口 | 新接口 |
|
||||
|------|--------|--------|
|
||||
| 本单定制师 | 200 放行 | 200 放行 |
|
||||
| 其他后台定制师 | 200 放行(**缺陷**) | 581008 拒绝 |
|
||||
| 房务 | 200 放行(**缺陷**) | 581045 拒绝 |
|
||||
| 管理员 / 超管 | 200 放行 | 200 放行 |
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 前端提交 | 写入位置 | 行为 |
|
||||
|----------|----------|------|
|
||||
| 旧接口已删除 | - | 无(服务端零路由映射) |
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 服务端已无该路由映射,调用返回 HTTP 404(`Not Found`)。
|
||||
- 调用点一律移除,无需保留兼容代码。
|
||||
|
||||
---
|
||||
|
||||
## 六.5 枚举
|
||||
|
||||
不适用(接口已删除)。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| 路由存在 | ✅ 存在 | ❌ 已删除,返回 404 |
|
||||
| 权限校验 | ❌ 无,任何账号可修改任意订单 | ✅ 按定制师归属校验,非该单定制师返回 581008 |
|
||||
| DONE_ADJUST 继承行为 | 旧行 `is_active=0` + 新行 `version+1`,复制原 `claimer_*` | 行为不变——继任接口走同一套 `adjustment/submit` 事务逻辑,继承规则与权限校验是两回事,本次改动只补了权限、未改这条继承规则 |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端无需改动**:经 hl-ui v2.1 核实,`src/api/orderV2.js` 中的 `putHotelRequirement` 函数定义仍在(标注 `@deprecated`),但全仓库内已无任何调用点(grep 零命中);`API-SPEC.html` §3.1 的删除说明同样记载"管理后台视图层已零调用(均已改走 §6.2)",两处结论一致。该函数是死代码,本次后端删除路由不会让任何现用页面失效。
|
||||
- 如需清理,可删除 `putHotelRequirement` 这一处未使用的函数定义本身,但这不影响任何现有页面的可用性,不构成阻塞项。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 新接口 `POST /v3/admin/order/{id}/adjustment/submit` 保留且功能完整。
|
||||
- 房务配房流程无改动(房务走 house 域的 `HouseAssignmentAdminController`,不涉及本接口)。
|
||||
- 小程序端、H5 端接口无改动。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服环境,2026-09-30~10-02。
|
||||
|
||||
```
|
||||
PUT /v3/admin/order/{id}/hotel-requirement
|
||||
非 owner 定制师角色调用:HTTP 404 ✓(路由已删除,非权限拒绝)
|
||||
URL 转至新接口 POST /v3/admin/order/{id}/adjustment/submit 后:
|
||||
非 owner 定制师角色:返回 581008 无权查看此订单 ✓
|
||||
房务角色:返回 581045 房务角色无权查看订单详情,房务仅可配房 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- **Issue**: [#8662](https://git.1814.love:8443/wx/HL/issues/8662)
|
||||
- **PR**: [#8705](https://git.1814.love:8443/wx/HL/pulls/8705)
|
||||
- **继任接口文档**: `docs/order-v3/api/API-SPEC.html` §3.1(本端点删除说明与历史存档)、§6.2(继任端点 `adjustment/submit`)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
**关联工单**: #8662
|
||||
**同批修改**: 询房预览权限补漏
|
||||
**后端负责人**: @wx
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8662"
|
||||
title: "询房预览接口补房务读守卫"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 询房预览接口补房务读守卫
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8662
|
||||
> **日期**: 2026-10-02
|
||||
> **影响范围**: 管理后台房务询房话术预览功能
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 接口 `POST /v3/admin/order/inquiry/preview` 新增房务读守卫。
|
||||
- **非房务角色**(定制师、管理员、其他后台角色)调用返回 **808090**「未登录或非房务角色,无权操作」。
|
||||
- **房务角色**(房务管理员、超管)放行,功能无改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
二期房务功能收口中,#8390 统一给 16 个旧只读端点(日历 / 房务详情 / 酒店视图 / 转单候选 / 待办 / 月度对账 / 旧抢单池 / 订单房间)挂上房务读守卫,唯独询房话术预览这一个接口漏过,导致定制师、运营等非房务角色能调通,拿到酒店联系人与微信(源码:`HouseReadGuard.java` 类 javadoc)。本次补上后,受此守卫覆盖的端点共 17 个。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 询房话术预览 | POST | `/v3/admin/order/inquiry/preview` | 修改 | 新增房务读守卫,非房务角色返回 808090 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 询房话术预览 `POST /v3/admin/order/inquiry/preview`
|
||||
|
||||
**VO**: `InquiryPreviewReqVO → InquiryPreviewRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务在配房弹窗点击「询房」时预览即将发往酒店的话术(所见即所发),确认无误后复制到企业微信。本次修改:非房务角色被拒。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| hotelId | Body | Long | ✅ | - | 酒店 ID;按此取 resource 联系人渲染文案 |
|
||||
| orderId | Body | Long | ❌ | - | 订单 ID(取团号);`assignmentId` 有效时以其所属订单为准 |
|
||||
| assignmentId | Body | Long | ❌ | - | 配房 ID;存在且与 `hotelId` 匹配时,日期/房型/间数/支付方式优先取该配房快照 |
|
||||
| stayDate | Body | LocalDate | ❌ | - | 入住日期;`assignmentId` 命中时被快照值覆盖 |
|
||||
| roomCount | Body | Integer | ❌ | ≥1 | 房间数;`assignmentId` 命中时被快照值覆盖;都缺省时按 1 间渲染 |
|
||||
| roomCategory | Body | String | ❌ | 字典 room_category | 房型类别;`assignmentId` 未命中时用于查房型中文名,查不到则原样回退为传入的 code |
|
||||
|
||||
#### 出参 `Result<InquiryPreviewRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| messageBody | String | 渲染后的固定格式订房确认话术(模板见下) |
|
||||
| contactName | String | 联系人姓名,resource 按 hotelId 带出,前端只读回显 |
|
||||
| contactWechat | String | 联系人微信号,resource 按 hotelId 带出,前端只读回显 |
|
||||
|
||||
话术固定模板(`InquiryMessageTemplate.SEND_BASE`,占位符按 `Map` 渲染,缺失值替换为空串):
|
||||
|
||||
```
|
||||
呼籁旅行 - 订房确认书:
|
||||
团号:${teamNo}
|
||||
日期:${stayDate}
|
||||
房型:${roomTypeName}${roomCount}间
|
||||
备注:${tags}
|
||||
1.${paymentText},价格保密。
|
||||
2.${breakfastText}${invoiceText}
|
||||
3.核房电话:${phone}
|
||||
辛苦确认后回复 @${replyContacts}
|
||||
```
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"hotelId": 1900000001,
|
||||
"orderId": 1900000000,
|
||||
"stayDate": "2026-04-28",
|
||||
"roomCount": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"messageBody": "呼籁旅行 - 订房确认书:\n团号:HL20261001A\n日期:4.28\n房型:待补充1间\n备注:协议酒店\n1.领队前台现付,价格保密。\n2.含早含发票\n3.核房电话:0470-8888888\n辛苦确认后回复 @王前台",
|
||||
"contactName": "王前台",
|
||||
"contactWechat": "hailar_holiday"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
上例未传 `roomCategory`、也未传 `assignmentId`,`roomTypeName` 按规则取不到任何来源,渲染为空缺省文案(源码常量 `EMPTY_VALUE_TEXT`)——`${roomTypeName}${roomCount}间` 模板不插空格,故渲染结果是该空缺省文案与 `1间` 的无分隔拼接(见上方 JSON 示例的 `messageBody`),前端如需展示分隔需自行处理,后端不改模板。日期按 `M.d` 格式渲染(无补零),`2026-04-28` → `4.28`。ID、团号、酒店联系人等取值均为说明用的构造值。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 酒店联系信息查询抛异常(`loadHotelExtended` 捕获全部 `RuntimeException`):静默降级,`contactName`/`contactWechat` 返回**空字符串 `""`(不是 NULL)**,`messageBody` 仍正常渲染,缺省字段分别落空值常量(`EMPTY_VALUE_TEXT`)/空标签常量(`EMPTY_TAG_TEXT`)/现付默认文案。
|
||||
- `assignmentId` 传了但查不到记录、或与 `hotelId` 不匹配:静默降级为按请求参数 + 资源数据重新生成(不报错,仅记一条 `log.warn`),不是 assignment 快照。
|
||||
- `assignmentId` 命中但与请求里的 `orderId` 不一致:忽略请求 `orderId`,改用该配房记录的真实 `orderId`(同样静默降级,仅记日志)。
|
||||
- 不落库,房务可反复调用,无状态。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808090,
|
||||
"message": "未登录或非房务角色,无权操作",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
其他错误:
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | hotelId 不能为空 | hotelId 未传 |
|
||||
| 400 | 房间数最小为 1 | roomCount 传了但 < 1 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只校验角色(房务管理员/超管放行),**不校验订单归属**:房务可预览任意订单的询房话术,与 #8390 覆盖的其余 16 个只读端点行为一致。
|
||||
- 该守卫只拦「有角色但非房务」;零角色账号(网关未透传 `X-Admin-Role`)按既有口径仍放行,不受本次改动影响(`HouseReadGuard.java` 类 javadoc,#7609 G-2 定案)。
|
||||
- 预览不落库,无副作用,可反复调用。
|
||||
- `contactWechat` 仅供复制,前端不做交互(不拨电话、不主动跳转)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 权限对照
|
||||
|
||||
| 角色 | 改前 | 改后 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 房务管理员 | 200 放行 | 200 放行 | 无改动 |
|
||||
| 超管 | 200 放行 | 200 放行 | 无改动 |
|
||||
| 定制师(CUSTOMIZER) | 200 放行(缺陷) | 808090 拒绝 | **新增限制** |
|
||||
| 其他后台角色(如 ADMIN) | 200 放行(缺陷) | 808090 拒绝 | **新增限制** |
|
||||
| 零角色账号(网关未透传 `X-Admin-Role`) | 放行 | 放行 | 无改动(#7609 G-2 口径,本次刻意不收) |
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
不落库,本接口无数据写入。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 错误码 808090 与所有同域房务读端点保持一致,可统一处理。
|
||||
- 权限守卫受 Nacos 开关 `group-batch.acl.enforce.house-read-role` 控制。测试服 2026-09-30~10-02 期间该开关为开启状态(实测 ADMIN/CUSTOMIZER 均返回 808090,见「八、测试环境已验证」);生产环境以当时的配置为准,前端按本文档的错误码契约接即可,无需关心开关本身的开关状态。
|
||||
|
||||
---
|
||||
|
||||
## 六.5 枚举
|
||||
|
||||
不适用(接口无新增枚举)。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| 权限校验 | ❌ 无房务守卫,任何角色可调 | ✅ 新增房务读守卫,非房务返回 808090 |
|
||||
| 功能逻辑 | 预览话术、返回联系人 | 不变(仅权限改动) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **前端无需改动**:经 hl-ui v2.1 核实,该接口的封装函数(`src/api/housekeeper/inquiry.js` 的 `previewInquiry`)仅有一处调用——`src/views/housekeeper/components/useHousekeeperInquiryCopy.js`,位于房务专属视图目录下,本就只在房务角色登录后的界面里被触达。非房务角色(定制师等)侧没有调用这个接口的代码,本次收紧权限不会让任何现有页面报错。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 房务配房流程(使用此接口的场景)功能不变。
|
||||
- 小程序端、H5 端接口无改动。
|
||||
- #8390 已覆盖的其余 16 个旧只读端点本身无行为变化,本次只是把本端点补入同一套守卫。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服环境,2026-09-30~10-02,经网关实测,与修前基线逐字段比对(基线快照:`p50_ac3_room_manager.json`/`p50_ac3_super_admin.json`;本轮:`p6_ac3_roommanager.json`/`p6_ac3_superadmin.json`/`p6_ac3_admin.json`/`p6_ac3_consultant.json`)。
|
||||
|
||||
```
|
||||
POST /v3/admin/order/inquiry/preview
|
||||
房务管理员(ROOM_MANAGER):200,messageBody/contactName/contactWechat 与修前基线逐字节相同 ✓
|
||||
超管(SUPER_ADMIN):200,messageBody/contactName/contactWechat 与修前基线逐字节相同 ✓
|
||||
管理员(ADMIN):808090 未登录或非房务角色,无权操作 ✓
|
||||
定制师(CUSTOMIZER):808090 未登录或非房务角色,无权操作 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- **Issue**: [#8662](https://git.1814.love:8443/wx/HL/issues/8662)
|
||||
- **PR**: [#8705](https://git.1814.love:8443/wx/HL/pulls/8705)
|
||||
- **背景工单**: [#8390](https://git.1814.love:8443/wx/HL/issues/8390)(原覆盖 16 个读端点统一守卫,本次 #8662 补上第 17 个——即本端点)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
**关联工单**: #8662
|
||||
**同批删除**: 旧住宿需求提交接口
|
||||
**后端负责人**: @wx
|
||||
@@ -0,0 +1,360 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8665"
|
||||
title: "配房删改与单日确认并发收口:三个写口新增 808932 并发冲突码"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 配房删改与单日确认并发收口:新增 808932 并发状态码
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8665
|
||||
> **日期**: 2026-10-02
|
||||
> **影响范围**: 管理后台房务配房操作:删除、修改、单日确认三个写口,并发交错时新增返回错误码 808932
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **新增错误码 808932**(房务状态已被并发修改,请刷新后重试):删除配房、修改配房、单日确认三个写口在并发交错时返回,不再产生孤儿应付台账行。
|
||||
- **请求/响应结构无变化**:三个接口的方法、路径、入参字段均未变,仅错误码表新增一条。
|
||||
- **808932 与其他业务错误码走同一套契约**:HTTP 200 + `code` + `message`,按 `code` 区分即可,无需为该码新增专门的前端处理分支;hl-ui v2.1 现有的通用业务错误拦截器(`src/utils/request.js` 的业务错误分支 + `errorBus.js`)会把后端返回的 `message` 原样呈现,不要求前端硬编码该文案。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
配房行的删除与修改两个写口(`DELETE /assignments/{id}` 与 `PUT /assignments/{id}`)原无互斥锁,与单日确认(`confirmDayPersist`)并发交错时,可能产生「配房行已软删,但应付台账仍留下可付款行」的孤儿记录。工单 #8665 为三个写口补充行级锁定读与版本控制,在并发修改被检测时返回 808932,整体回滚包括该行的任何台账产出。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 删除配房 | DELETE | `/v3/admin/order/assignments/{id}` | 修改 | 新增错误码 808932(并发冲突) |
|
||||
| 2 | 修改配房 | PUT | `/v3/admin/order/assignments/{id}` | 修改 | 新增错误码 808932(并发冲突) |
|
||||
| 3 | 单日确认 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm` | 修改 | 新增错误码 808932(并发冲突) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
本节示例 JSON 的字段名、类型、错误码与文案逐一取自源码;ID、日期等取值为说明用的构造值。所有接口统一返回 `Result` 信封(`code` / `message` / `data` / `traceId` / `success`),示例省略 `traceId`;业务失败与入参校验失败均为 HTTP 200,靠 `code` 区分。
|
||||
|
||||
### 1. 删除配房 `DELETE /v3/admin/order/assignments/{id}`
|
||||
|
||||
**VO**: `无请求体 → Void`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务管理员在管理后台删除已配置的某条配房行,通常在需要重新调整房型或取消某晚房务时使用。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 配房行 ID |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (无数据体) | - | 删除成功时 `Result.data` 为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
DELETE /v3/admin/order/assignments/2105709698592440321
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 若配房行不存在(已被删除或 ID 无效):返回业务错误码 808120「配房不存在」。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808932,
|
||||
"message": "房务状态已被并发修改,请刷新后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **触发 808932 的场景**:该配房行在读取后被并发确认(单日确认的保留行流程)或被并发修改(改配房),版本对不上或已被软删,带版本谓词的软删(`softDeleteWithVersion`)影响 0 行。
|
||||
- **建议的前端处理**:收到 808932 时提示用户刷新该订单的配房列表后重试;无需为该码单独编码重试逻辑,交由通用错误提示呈现即可。
|
||||
- **串行无 808932**:若删除在单日确认完全提交之后才发起,行已稳定,返回 200;若确认在删除完全提交之后才查询候选,会命中更早的 808118(该天无可确认的询房中候选),不会到达锁定复读分支。
|
||||
|
||||
### 2. 修改配房 `PUT /v3/admin/order/assignments/{id}`
|
||||
|
||||
**VO**: `AssignmentUpdateReqVO → Void`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务管理员修改已配置配房行的协议价、结算价、支付方式、备注、早餐、酒店主数据快照同步开关,不支持修改酒店、房型、间数(需要换酒店/换房型走 §2.3b 调整配房端点,或删除后用 §2.2 重新创建)。EXCEPTION 异常态下的配房仍允许走本接口改价,用于异常桶的人工处置(如与酒店协商退款后的补偿调整)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | - | 配房行 ID |
|
||||
| protoPrice | Body | BigDecimal | ❌ | ≥0.00 | 协议价 |
|
||||
| settlementPrice | Body | BigDecimal | ❌ | ≥0.00 | 结算价 |
|
||||
| settleType | Body | String | ❌ | 正则 `cash\|sign\|company` | 结算方式 |
|
||||
| syncProtocolPrice | Body | Boolean | ❌ | - | 是否同步协议价到酒店主数据快照 |
|
||||
| syncSettlementPrice | Body | Boolean | ❌ | - | 是否同步结算价到酒店主数据快照 |
|
||||
| syncSettleType | Body | Boolean | ❌ | - | 是否同步结算方式到酒店主数据快照 |
|
||||
| remark | Body | String | ❌ | 无长度校验注解 | 备注 |
|
||||
| syncHotelSnapshot | Body | Boolean | ❌ | - | 是否整体同步酒店主数据快照 |
|
||||
| breakfast | Body | String | ❌ | 正则(`HouseBreakfast.VALUE_REGEX`,即 INCLUDED/EXCLUDED/PENDING) | 早餐 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (无数据体) | - | 修改成功时 `Result.data` 为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"settlementPrice": "320.50",
|
||||
"remark": "与酒店协商调整"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 若配房行不存在:返回 808120「配房不存在」。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808932,
|
||||
"message": "房务状态已被并发修改,请刷新后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **触发 808932 的场景**:该配房行在读取后被并发删除或被并发修改(版本漂移),带 `@Version` 校验的 `updateById` 影响 0 行。
|
||||
- **EXCEPTION 态改价**:本接口不经过「需求级写锁 + EXCEPTION 写闸」(`assertWritableForAssignment`)那条校验链,因此配房所属需求处于 EXCEPTION(异常态)时仍可调用本接口改价,用于异常桶的人工处置;并发删除/修改仍会返 808932。
|
||||
- **建议的前端处理**:收到 808932 时提示用户刷新该配房行详情后重试,无需额外编码特殊逻辑。
|
||||
|
||||
### 3. 单日确认 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm`
|
||||
|
||||
**VO**: `AssignmentDayConfirmReqVO → Void`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务确认某个住宿需求的某一晚配房方案,触发应付台账推送和库存更新。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
|
||||
| dayNumber | Path | Integer | ✅ | 超出该需求行程晚数上界返 808102,无下界校验注解 | 第几晚(从 1 开始计数) |
|
||||
| keepAssignmentIds | Body | List<Long> | ❌ | 非空时每个 id 须属于该晚询房中候选集,否则返 808123 | 保留的配房行 ID 清单(待翻 CONFIRMED);整个请求体、本字段均可省略,或传空数组——两者语义相同,均表示保留该晚**全部**询房中候选(全部翻 CONFIRMED),不传则不会软删任何候选行;显式传非空列表时,列表外的该晚候选行才会被软删 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| (无数据体) | - | 确认成功时 `Result.data` 为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"keepAssignmentIds": [2105709698592440321]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 若该晚无询房中的候选(已被删除或已确认):返回 808118「该天无可确认的询房中候选, 请先配房再确认」。
|
||||
- 若该需求所属订单已取消或处于异常处置中(`house_status=EXCEPTION`):返回 808119「订单已取消或异常处置中, 该需求不可新增或确认配房, 请在异常桶处理」。
|
||||
- 若 `keepAssignmentIds` 非空且其中存在不属于该晚询房中候选的 id:返回 808123「保留的配房行不属于该天询房中候选(可能配房已更新), 请刷新后重试」。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808932,
|
||||
"message": "房务状态已被并发修改,请刷新后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **触发 808932 的场景**:确认流程中保留行(`keepAssignmentIds` 对应的候选行)在候选列表读取之后被并发删除或修改,锁定复读时发现该行已不存在、已非询房中状态、版本不一致,或翻 CONFIRMED 的 `updateById` 影响 0 行。
|
||||
- **整体回滚**:落选候选先软删、保留候选再逐行锁定复读,任一保留行复读发现不一致(抛 808932)时整个事务回滚——包括同一事务内已执行的落选行软删——该确认涉及的应付台账均不推送,确保终态一致,不留半截状态。
|
||||
- **建议的前端处理**:收到 808932 时提示用户刷新该晚配房列表后重试,无需额外编码特殊逻辑。
|
||||
- **串行与并发的区别**:若删除完全提交在确认发起之前,确认查询该晚候选时已无询房中的行,会命中更早的 808118(候选预检),不会到达锁定复读分支(808932 仅针对确认读取候选之后、锁定复读之前这段并发窗口);两条路径的共同效果一致:均不推送应付台账。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 三个接口的请求方式、路径、参数均**无变化**,仅错误码表补充。
|
||||
- 808932 与其他业务错误码(如 808118、808119、808120、808123)一样走 HTTP 200 + 业务码的行为,前端按 `code` 区分即可;hl-ui v2.1 的通用业务错误拦截器会将后端 `message` 原样呈现,不要求为 808932 单独编码处理分支。
|
||||
- 单日确认(接口 3)请求体可以整体省略:省略或 `keepAssignmentIds` 传空数组语义相同,均表示保留该晚全部询房中候选;若需要落选部分候选行,必须显式传入要保留的 id 列表。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 无表结构变更、无 Flyway 迁移(本次合并仅涉及 Service/测试代码与 API 文档,对比 #8665 合并提交的文件清单确认无 `db/migration` 变更)。
|
||||
- 配房表 `house_hotel_assignment` 的 `version` 列与实体上的 `@Version` 注解系已有能力(早于本次改动),本次复用它为删除、修改、单日确认三个写口补齐「锁定读(`SELECT...FOR UPDATE`)+ 带版本更新/软删」的组合:先用锁定读拿到最新行与其 `version`,再执行带版本谓词的写操作(`updateById` 走 MyBatis-Plus 全局注册的乐观锁拦截器自动拼 `version` 条件;软删复用既有的 `softDeleteWithVersion(id, version)`),任一写操作影响 0 行即判定为并发冲突并抛 808932,整体事务回滚。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- **离线与网络异常**:808932 不涉及网络/超时,纯业务并发码,离线时前端仍会收到错误响应。
|
||||
- **灰度与开关**:本次改动无灰度开关,所有测试环境已包含该并发收口逻辑。
|
||||
- **下游链路**:应付台账推送、库存扣减、其他异步链路仅当确认成功(code=200)才执行,808932 回滚后不产生任何账务记录。
|
||||
|
||||
---
|
||||
|
||||
## 六.5 枚举
|
||||
|
||||
不涉及新增枚举值;配房状态(INQUIRING/CONFIRMED/EXCEPTION 等)无变化。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 并发行为对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| 删除配房并发于确认 | 可能双 200,留下孤儿应付台账行 | 后提交方返 808932,回滚,无台账产出 |
|
||||
| 修改配房并发于确认 | 可能按过期快照推台账 | 后提交方返 808932,回滚,无台账产出 |
|
||||
| 单日确认保留行被删 | 静默跳过已删行,继续推台账 | 锁定复读检测到行已删,返 808932,回滚,无台账产出 |
|
||||
| 返回的错误码 | 无 808932 | 新增 808932(仅在并发窗口内返回) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。新增错误码不影响其他业务流程,正常序列的删除、修改、确认仍返回 200。
|
||||
- **前端是否必须同步上线**:否。hl-ui v2.1 现有的通用业务错误拦截器会把后端返回的 `message` 原样呈现,808932 走的是这条通用路径,不需要新增前端代码。
|
||||
- **前端 workaround 清理点**:无。本次改动前 808932 这个码不存在,故前端不会有针对它的既有 workaround 需要清理。
|
||||
|
||||
**影响范围说明**:
|
||||
- 前端 hl-ui:请求/响应字段无改动;808932 走通用业务错误展示路径,后端返回的 `message` 即为用户看到的提示文案,无需前端硬编码该文案。
|
||||
- 管理后台网关:路由无变更,仅错误响应码增加,不影响路由规则和鉴权。
|
||||
- 其他服务:本次改动是 order-v3 内部的行级锁定读 + 乐观锁版本校验,不新增分布式锁、不改 Feign 契约;finance、resource 等消费方零感知。
|
||||
- 数据一致性:通过锁定读 + 版本控制,堵住了孤儿应付台账的产生根源,后续应付台账取消、对账等链路无需额外补丁。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- EXCEPTION 异常态下调用修改配房(接口 2)改价仍被允许——该接口本就不经过 `assertWritableForAssignment` 这条 EXCEPTION 写闸——未受本次新增版本控制的影响;本次改动只新增 808932 这一个并发冲突码,不改变任何既有的状态校验逻辑。
|
||||
- 其他写口(如转房 `changeId`、取消级联删除等)未涉及本次改动,仍按既有逻辑。
|
||||
- 只读接口(查询配房列表、查询单日候选等)逻辑无变化。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署状态**:
|
||||
- hl-order-service-v3 @cd82a19ab(origin/dev-v3 的祖先,#8665 的合并提交 f8379ddfc3 已包含)
|
||||
- hl-gateway @71def6dc5(网关落后 dev-v3 156 个提交,本次无路由变化,不影响)
|
||||
- 部署时刻:2026-10-01 22:50:33
|
||||
|
||||
**测试链路 A:确认第 1 晚 → 删除该行**
|
||||
- 操作序列:`POST /confirm` 返回 200 → `DELETE /assignments/{id}` 返回 200
|
||||
- 终态:配房行 `deleted_at` 非空;应付台账该行已软删(无活跃 NORMAL 记录);日历库存回复
|
||||
- 结论:PASS(两步均 200,无 808932)
|
||||
|
||||
**测试链路 B:删除该行 → 确认第 2 晚**
|
||||
- 操作序列:`DELETE /assignments/{id}` 返回 200 → `POST .../days/2/confirm` 返回 808118(严格串行,无并发窗口)
|
||||
- 终态:应付台账对该行零产出(`fin_payable_line WHERE source_ref_id=<该行id>` 为 0 行)
|
||||
- 结论:PASS——判据是「第二步不再为该行产生台账行」,不要求第二步返回 200;完全串行操作下,confirmDay 在进入锁定复读之前先按该晚候选集过滤,发现该晚询房中候选集已为空,命中更早的 808118(该天无可确认的询房中候选),而不是 #8665 新增的 808932(锁定复读检查需要先有候选才会走到)。
|
||||
|
||||
**并发窗口说明**:
|
||||
- 808932 是为「删除/修改读取之后、带版本的写操作之前」这段时间窗口的并发交错设计的,靠锁定读 + 乐观锁版本校验堵住。
|
||||
- 完全串行操作(一方提交完成后另一方才查询)会先命中 808118(无候选)或 808120(配房不存在),不会到达锁定复读检查点;这两条路径的共同效果都是零台账产出。
|
||||
- 测试环境部署状态:hl-order-service-v3 对 origin/dev-v3 零落后,#8665 的合并提交已在部署字节内,以上两条测试链路均为该部署字节下的实测结果。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- API 规范:`docs/order-v3/api/API-SPEC-HOUSE-V1.1.html`(v1.1.20,2026-10-01)
|
||||
- §2.2b 单日确认 / §2.3 修改配房 / §2.4 删除配房错误码表新增 808932
|
||||
- §11.9 内部接口 + 跨服务(808900-808999)全表补 808932「房务状态已被并发修改,请刷新后重试」
|
||||
- 工单正文:Gitea #8665(设计、验收、口径定案)
|
||||
- 单测覆盖:
|
||||
- `HouseAssignmentServiceTest#confirmDayPersist_keepUpdateAffectsZeroRows_throws808932AndNoPayablePush`:confirmDayPersist 中某 keep 行 updateById 影响 0 行时抛 808932,整体回滚,零推台账,落选不回补库存
|
||||
- `HouseAssignmentServiceTest#delete_softDeleteAffectsZeroRows_throws808932NoPayableNoRestore`:delete() 在 softDeleteWithVersion 返 0 时抛 808932,不碰台账、不回补库存
|
||||
- `HouseAssignmentServiceTest#updateTx_updateAffectsZeroRows_throws808932NoPayableRewrite`:updateTx() 乐观锁写库影响 0 行时抛 808932,不判台账、不作废不重推
|
||||
- `HouseAssignmentDeleteConfirmDayOrderingIT`:固定时序交错 IT,覆盖「确认快照之后删配房提交→确认返 808932」与「删配房持行锁期间确认进入事务→确认被挡住随后 808932」两条交错,均断言无孤儿台账
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,834 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8684"
|
||||
title: "预支相关 8 个接口出参新增 canRevoke:当前操作人点「撤回」能否成功,与撤回接口共用同一个判定"
|
||||
consumer: "admin"
|
||||
author: "jw(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "41468f5297fc1d5c04b49772b76af73d2fd0c3bf"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-02"
|
||||
status_note: "已合并 dev-v3(806058c66)并部署 TEST,自签低权限 token 经网关实测:三个列表逐角色取值、联表 created_by 探针、开关关闭→还原往返(md5 逐字节还原,渲染期间拒绝日志零新增)、按 canRevoke 抽样真实撤回。前端待改两处撤回按钮:订单详情预支弹窗 AdvanceModal.vue 与团期财务页签 FinanceTab.vue 的 v-if 改为 a.canRevoke;审批中心本来没有撤回按钮。前端已交付(2026-10-02):两处撤回按钮 v-if 均改读 a.canRevoke,不再自拼 status+角色;存量行无键 undefined 不误显;spec FinanceTab 3 例+AdvanceModal 新建 2 例全绿,提交 41468f52。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 预支列表出参新增 canRevoke
|
||||
|
||||
**服务**: hl-order-service-v3
|
||||
**PR**: `#8723`(已合入 `dev-v3`,合并提交 `806058c66`)
|
||||
**Issue**: #8684
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
🟢 **8 个接口的出参新增一个字段 `canRevoke`(Boolean)**:当前操作人现在点「撤回」能不能成功。其余入参、出参、判权、错误码全部不变。
|
||||
|
||||
🔴 **同一行,不同人看到的值不同。** 不要缓存后跨账号复用,也不要拿它当这笔预支本身的属性。
|
||||
|
||||
🟢 **前端只需把撤回按钮的显示条件改成 `a.canRevoke`**,不用自己判断角色、申请人和开关。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
#8517 之后,撤回待审批预支(`DELETE /v3/admin/order/advance/:advanceId`)只放行申请人本人和超管、管理员,其余返回 `585009`。但列表只返回申请人姓名,没有申请人账号 ID,也没有「能不能撤」的标记,前端只能按「待审批」显示撤回按钮:非申请人也能看到,点了才提示 `585009`。
|
||||
|
||||
本单在出参里补 `canRevoke`,由后端按撤回接口的同一套规则算好。撤回接口本身的判权不变。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 本单预支列表 | GET | `/v3/admin/order/:orderId/advances` | 修改 | `records[]` 新增 `canRevoke` |
|
||||
| 2 | 预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 修改 | `records[]` 新增 `canRevoke` |
|
||||
| 3 | 团期预支记录(财务页签) | GET | `/v3/admin/order/group-batch/:groupBatchId/advances` | 修改 | 每行新增 `canRevoke` |
|
||||
| 4 | 发起订单级预支 | POST | `/v3/admin/order/:orderId/advance` | 修改 | 返回体新增 `canRevoke` |
|
||||
| 5 | 预支审批通过 | PUT | `/v3/admin/order/advance/:advanceId/approve` | 修改 | 返回体新增 `canRevoke`(恒 `false`) |
|
||||
| 6 | 预支审批驳回 | PUT | `/v3/admin/order/advance/:advanceId/reject` | 修改 | 返回体新增 `canRevoke`(恒 `false`) |
|
||||
| 7 | 发起团期级预支 | POST | `/v3/admin/order/group-batch/:groupBatchId/advance` | 修改 | 返回体新增 `canRevoke` |
|
||||
| 8 | 核单汇总快照 | GET | `/v3/admin/order/:orderId/settlement/summary` | 修改 | `advanceSummary.records[]` 新增 `canRevoke`(只含已通过,恒 `false`) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
**`canRevoke` 取值规则**(8 个接口相同,按顺序判,命中即返回):
|
||||
|
||||
| # | 条件 | 取值 |
|
||||
|---|---|---|
|
||||
| 1 | 这笔预支不是待审批(`status ≠ SUBMITTED`) | `false` |
|
||||
| 2 | 没有请求上下文(定时任务、消息消费等) | `false` |
|
||||
| 3 | 当前角色是团期管理员 `GROUP_BATCH_MANAGER` | `false`(撤回接口先返回 `581008`,不受开关影响) |
|
||||
| 4 | 当前角色是 `SUPER_ADMIN` / `ADMIN` | `true` |
|
||||
| 5 | 当前账号就是申请人(创建人账号 ID 相等) | `true` |
|
||||
| 6 | 其余:开关 `advance.acl.enforce.role-guard` 为 `true`(默认) | `false` |
|
||||
| 6' | 其余:开关已关闭 | `true`(撤回接口回到 #8517 之前的行为) |
|
||||
|
||||
创建人账号 ID 为空的存量行,只有第 4 条能得到 `true`。
|
||||
|
||||
### 1. 本单预支列表 `GET /v3/admin/order/:orderId/advances`
|
||||
|
||||
**VO**: `PageParam` → `Result<PageResult<OrderAdvanceRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
订单详情的「预支」弹窗。前端据 `canRevoke` 决定每一行显不显示「撤回」按钮。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | ✅ | 订单 ID | **不变** |
|
||||
| page | Query | Integer | ❌ | ≥1 | **不变** |
|
||||
| pageSize | Query | Integer | ❌ | ≥1 | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records[].canRevoke | Boolean | 🆕 当前操作人点撤回能否成功,规则见上表 |
|
||||
| 其余字段 | — | **不变**(`id` / `status` / `createdByName` 等) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2100743225424621570/advances?page=1&pageSize=20 HTTP/1.1
|
||||
Authorization: Bearer <本单定制师 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
本单定制师看:自己申请的待审批为 `true`,管理员申请的待审批为 `false`,已通过的为 `false`。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2105886435083206657",
|
||||
"orderId": "2100743225424621570",
|
||||
"teamNo": "26-8707",
|
||||
"payeeName": "刘大山",
|
||||
"payeeRole": "LEADER",
|
||||
"payeeRoleText": "导游",
|
||||
"advanceType": "TICKET",
|
||||
"amount": 180.0,
|
||||
"purpose": "呼伦贝尔大草原景区门票代垫",
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"createdByName": "admin",
|
||||
"canRevoke": true
|
||||
},
|
||||
{
|
||||
"id": "2105886437624999938",
|
||||
"orderId": "2100743225424621570",
|
||||
"teamNo": "26-8707",
|
||||
"payeeName": "刘大山",
|
||||
"advanceType": "CATERING",
|
||||
"amount": 120.0,
|
||||
"purpose": "额尔古纳湿地午餐代垫",
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"createdByName": "jw",
|
||||
"canRevoke": false
|
||||
}
|
||||
],
|
||||
"total": 2,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无预支时 `records` 为空数组(不变)。开关 `advance.acl.enforce.role-guard` 关闭时,非团期管理员看待审批行都为 `true`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权不变:非本单定制师、财务等无权角色仍返回 `581008`。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581008,
|
||||
"message": "无权查看此订单",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 每行单独计算,同一页里可以有 `true` 也有 `false`。
|
||||
- 车务管理员能看这个列表,但看所有行都是 `false`。
|
||||
|
||||
---
|
||||
|
||||
### 2. 预支审批列表 `GET /v3/admin/order/advance-approvals/page`
|
||||
|
||||
**VO**: `AdvanceApprovalPageReqVO` → `Result<PageResult<AdvanceApprovalPageItemRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
「财务管理 → 预支审批」列表。页面目前没有撤回按钮,字段供后续使用。只有超管、管理员、财务能进(不变)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| status | Query | String | ❌ | `SUBMITTED` / `APPROVED` / `REJECTED` / `PAID` | **不变** |
|
||||
| page | Query | Integer | ❌ | ≥1 | **不变** |
|
||||
| pageSize | Query | Integer | ❌ | ≥1 | **不变** |
|
||||
| 其余筛选项 | Query | — | ❌ | — | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records[].canRevoke | Boolean | 🆕 规则见上表;财务看别人申请的为 `false` |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/advance-approvals/page?status=SUBMITTED&page=1&pageSize=20 HTTP/1.1
|
||||
Authorization: Bearer <财务 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2105886435083206657",
|
||||
"orderId": "2100743225424621570",
|
||||
"orderNo": "HL20260918082629372",
|
||||
"teamNo": "26-8707",
|
||||
"scope": "ORDER",
|
||||
"amount": 180.0,
|
||||
"status": "SUBMITTED",
|
||||
"createdByName": "admin",
|
||||
"canRevoke": false
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无数据时 `records` 为空数组(不变)。开关关闭时财务看待审批行都为 `true`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权不变:非超管 / 管理员 / 财务返回 `585008`。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585008,
|
||||
"message": "仅财务或管理员可查看预支审批、审批或驳回预支",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 本接口多查了一列申请人账号 ID 用于计算,**不出参**。
|
||||
- 团期级行(`scope=GROUP_BATCH`)同样按规则计算。
|
||||
|
||||
---
|
||||
|
||||
### 3. 团期预支记录(财务页签) `GET /v3/admin/order/group-batch/:groupBatchId/advances`
|
||||
|
||||
**VO**: `List<GroupBatchAdvanceItemVO>`(继承 `OrderAdvanceRespVO`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期详情「财务」页签的预支记录。前端据 `canRevoke` 决定显不显示「撤回」。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | 团期 ID | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| [].canRevoke | Boolean | 🆕 规则见上表;团期管理员恒为 `false` |
|
||||
| 其余字段 | — | **不变**(`scope` / `orderNo` / `teamNo` 等) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/2104839654727618562/advances HTTP/1.1
|
||||
Authorization: Bearer <管理员 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": [
|
||||
{
|
||||
"id": "2105589347598368769",
|
||||
"scope": "GROUP_BATCH",
|
||||
"scopeName": "团期级",
|
||||
"orderId": null,
|
||||
"orderNo": null,
|
||||
"amount": 5000.0,
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"createdByName": "金卫",
|
||||
"canRevoke": true
|
||||
}
|
||||
],
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无预支时返回空数组(不变)。开关关闭时,非团期管理员看待审批行都为 `true`,团期管理员仍为 `false`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权不变。团期不存在:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589500,
|
||||
"message": "团期不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 原来前端写的 `a.status === 'SUBMITTED' && !isGroupBatchManager` 可以整体换成 `a.canRevoke`。
|
||||
- 子订单级行与团期级行规则相同。
|
||||
|
||||
---
|
||||
|
||||
### 4. 发起订单级预支 `POST /v3/admin/order/:orderId/advance`
|
||||
|
||||
**VO**: `CreateAdvanceReqVO` → `Result<OrderAdvanceRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
订单详情「发起预支」。返回体里 `canRevoke` 对发起人本人为 `true`(刚建的待审批,自己可以撤)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | ✅ | 订单 ID | **不变** |
|
||||
| payeeStaffId | Body | Long | ✅ | 本单人员 | **不变** |
|
||||
| advanceType | Body | String | ✅ | 字典 `advance_type` | **不变** |
|
||||
| amount | Body | BigDecimal | ✅ | >0,不超可用上限 | **不变** |
|
||||
| purpose | Body | String | ❌ | — | **不变** |
|
||||
| voucherUrl | Body | String | ❌ | — | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.canRevoke | Boolean | 🆕 发起人本人为 `true` |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"payeeStaffId": "2100747615736897537",
|
||||
"advanceType": "TICKET",
|
||||
"amount": 180.00,
|
||||
"purpose": "呼伦贝尔大草原景区门票代垫"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105886435083206657",
|
||||
"orderId": "2100743225424621570",
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"amount": 180.0,
|
||||
"canRevoke": true
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权与校验不变。金额超可用上限:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585004,
|
||||
"message": "预支金额超过可用余额上限",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只是返回体多一个字段,创建逻辑不变。
|
||||
|
||||
---
|
||||
|
||||
### 5. 预支审批通过 `PUT /v3/admin/order/advance/:advanceId/approve`
|
||||
|
||||
**VO**: `Result<OrderAdvanceRespVO>`(无请求体)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心「通过」。审批后状态是已通过,`canRevoke` 恒为 `false`。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| advanceId | Path | Long | ✅ | 预支 ID | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.canRevoke | Boolean | 🆕 恒 `false` |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/advance/2105886444679774210/approve HTTP/1.1
|
||||
Authorization: Bearer <财务 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105886444679774210",
|
||||
"status": "APPROVED",
|
||||
"statusText": "已通过",
|
||||
"approvedBy": "yaosutu",
|
||||
"canRevoke": false
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权不变。预支不存在(财务 / 管理员):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585000,
|
||||
"message": "预支记录不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 审批逻辑与出纳待付款的生成不变。
|
||||
|
||||
---
|
||||
|
||||
### 6. 预支审批驳回 `PUT /v3/admin/order/advance/:advanceId/reject`
|
||||
|
||||
**VO**: `RejectAdvanceReqVO` → `Result<OrderAdvanceRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
审批中心「驳回」。驳回后状态是已驳回,`canRevoke` 恒为 `false`。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| advanceId | Path | Long | ✅ | 预支 ID | **不变** |
|
||||
| reason | Body | String | ✅ | 驳回原因 | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.canRevoke | Boolean | 🆕 恒 `false` |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"reason": "门票已由地接社统一采购,无需个人垫付"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2104861622562627585",
|
||||
"status": "REJECTED",
|
||||
"statusText": "已驳回",
|
||||
"rejectReason": "门票已由地接社统一采购,无需个人垫付",
|
||||
"canRevoke": false
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权不变。预支不存在(财务 / 管理员):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 585000,
|
||||
"message": "预支记录不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 驳回逻辑不变。
|
||||
|
||||
---
|
||||
|
||||
### 7. 发起团期级预支 `POST /v3/admin/order/group-batch/:groupBatchId/advance`
|
||||
|
||||
**VO**: `CreateGroupBatchAdvanceReqVO` → `Result<OrderAdvanceRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期财务页签「发起预支」。返回体里 `canRevoke` 对发起人本人为 `true`;团期管理员发起的为 `false`(团期管理员撤回会先被 `581008` 拦下)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | 团期 ID | **不变** |
|
||||
| payeeStaffId | Body | Long | ✅ | 团期人员 | **不变** |
|
||||
| advanceType | Body | String | ✅ | 字典 `advance_type` | **不变** |
|
||||
| amount | Body | BigDecimal | ✅ | >0,不超团期统一池上限 | **不变** |
|
||||
| purpose | Body | String | ❌ | — | **不变** |
|
||||
| voucherUrl | Body | String | ❌ | — | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data.canRevoke | Boolean | 🆕 发起人本人为 `true`,团期管理员为 `false` |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"payeeStaffId": "2100747615736897537",
|
||||
"advanceType": "CATERING",
|
||||
"amount": 600.00,
|
||||
"purpose": "满洲里套娃广场团餐代垫"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
示例值(发起人本人调用):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105890177461317634",
|
||||
"orderId": null,
|
||||
"status": "SUBMITTED",
|
||||
"statusText": "待审批",
|
||||
"amount": 600.0,
|
||||
"canRevoke": true
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
判权与校验不变。团期不存在:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 589500,
|
||||
"message": "团期不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只是返回体多一个字段,创建逻辑与额度池不变。
|
||||
|
||||
---
|
||||
|
||||
### 8. 核单汇总快照 `GET /v3/admin/order/:orderId/settlement/summary`
|
||||
|
||||
**VO**: `Result<SettlementSummaryRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
核单页的汇总快照。`advanceSummary.records` 与预支列表共用同一个 VO,所以一并带上 `canRevoke`。这里只列已通过的预支,恒为 `false`。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| orderId | Path | Long | ✅ | 订单 ID | **不变** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| advanceSummary.records[].canRevoke | Boolean | 🆕 恒 `false`(只含已通过) |
|
||||
| 其余字段 | — | **不变** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2100743225424621570/settlement/summary HTTP/1.1
|
||||
Authorization: Bearer <管理员 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"settled": false,
|
||||
"orderId": "2100743225424621570",
|
||||
"teamNo": "26-8707",
|
||||
"advanceSummary": {
|
||||
"approvedAmount": "90.00",
|
||||
"records": [
|
||||
{
|
||||
"id": "2105886444679774210",
|
||||
"status": "APPROVED",
|
||||
"statusText": "已通过",
|
||||
"amount": 90.0,
|
||||
"canRevoke": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有已通过的预支时 `records` 为空数组(不变)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
本接口没有业务错误码,订单不存在时也返回 `200` 和 `settled=false` 的空壳(行为不变)。网关层未带 token(TEST 2026-10-02 实打):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "缺少有效的 Authorization 头",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 前端不需要用这里的 `canRevoke`。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 撤回按钮显示条件改成 `a.canRevoke`,不要再自己拼「待审批 + 角色 + 是否团期管理员」。
|
||||
- `canRevoke` 跟着当前登录账号和当前角色变;切换角色后要重新拉列表。
|
||||
- `canRevoke=true` 只表示列表渲染那一刻能撤;点击时若已被别人审批,仍会返回 `585005`(状态不允许),照常提示即可。
|
||||
- 撤回接口的判权与错误码不变:非申请人、非管理员 `585009`,团期管理员 `581008`。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 零 DDL、零数据迁移、零写入。
|
||||
- 取值用预支记录既有的创建人账号 ID(提交时自动写入);审批中心的联表查询多 select 这一列,不出参。
|
||||
- 发起、审批、驳回的写库逻辑不变。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 定时任务、消息消费等无请求上下文的场景算出来恒为 `false`(这些场景不渲染列表)。
|
||||
- 计算 `canRevoke` 不打 `ADVANCE_ACL_DENY` 日志;只有真调撤回被拒时才打。
|
||||
- 缺角色的 token 若正好是申请人本人,`canRevoke=true`,与撤回接口一致(#8517 有意的设计)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 场景 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 非申请人、非管理员看待审批行 | 按钮显示,点了 `585009` | `canRevoke=false`,前端可隐藏 |
|
||||
| 申请人本人 / 管理员看待审批行 | 按钮显示,能撤 | `canRevoke=true` |
|
||||
| 团期管理员看团期页签 | 前端用 `!isGroupBatchManager` 自己藏 | `canRevoke=false` |
|
||||
| 非待审批行 | 前端按状态藏 | `canRevoke=false` |
|
||||
| 开关关闭 | 前端不知道开关状态 | 非团期管理员都为 `true`,跟撤回接口一致 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否,纯新增字段。
|
||||
- **前端是否必须同步上线**:否。不改前端时行为与改前相同(按钮照旧显示);改了才能隐藏点不了的按钮。
|
||||
- **回滚**:revert PR #8723 后重新部署 order-v3。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 撤回接口 `DELETE /v3/admin/order/advance/:advanceId` 的判权与返回:不变。
|
||||
- 各接口的入参、判权、其余出参:不变。
|
||||
- 出纳付款、核单计算:不变。
|
||||
- 小程序端:无影响。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**环境**:TEST(`https://api.test.1814.love`) **验证时间**:2026-10-02 13:03~13:30
|
||||
**构建身份**:order-v3 部署 `dev-v3 @ 806058c66`(本单合并提交),12:57 完成。零写入判据:部署后连查 6 次订单级预支列表,每行都带 `canRevoke` 键(旧字节没有这个键)。
|
||||
**身份**:自签 token 直打网关,用 TEST 真实账号 ID 配对应角色;TEST 上没有在用的财务账号,财务用 `role=FINANCE` 的自签 token。
|
||||
|
||||
### 8.1 造数
|
||||
|
||||
在待出发订单 `HL20260918082629372`(定制师 1001)上:C1 定制师申请门票 180(待审批)、C2 管理员 jw 申请餐费 120(待审批)、C3 定制师申请门票 90 后由财务审批(已通过)。另有该单既有的已付款、已驳回各一笔。
|
||||
|
||||
### 8.2 三个列表逐角色取值
|
||||
|
||||
| 列表 | 查看人 | C1(1001 申请,待审批) | C2(jw 申请,待审批) | 已通过 / 已付款 / 已驳回 |
|
||||
|---|---|---|---|---|
|
||||
| 订单级 | 本单定制师 1001 | `true` | `false` | 均 `false` |
|
||||
| 订单级 | 管理员 | `true` | `true` | 均 `false` |
|
||||
| 订单级 | 车务管理员 | `false` | `false` | 均 `false` |
|
||||
| 审批中心 | 财务 | `false` | `false` | 已通过 `false` |
|
||||
| 审批中心 | 管理员 | `true` | `true` | 已通过 `false` |
|
||||
|
||||
团期财务页签(团期级待审批一笔,jw 申请):财务 `false`、团期管理员 `false`、管理员 `true`。
|
||||
联表探针:财务角色、账号 ID 设为 1001 查审批中心,C1 为 `true`、C2 为 `false`,证明审批中心带出了创建人。
|
||||
核单汇总:只含 C3,`canRevoke=false`。
|
||||
|
||||
### 8.3 按 canRevoke 抽样真实撤回
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| 财务、其他定制师撤 C1;车务、定制师 1001 撤 C2(均为 `false`) | 均 `585009`,未删除 |
|
||||
| 团期管理员撤 C1 | `581008` |
|
||||
| 定制师 1001 撤 C1、管理员撤 C2(均为 `true`) | 均 `200`,已软删 |
|
||||
|
||||
### 8.4 nacos 回滚开关往返
|
||||
|
||||
| 态 | 订单级:定制师 / 车务看 C2 | 团期页签:财务 / 团期管理员 | 审批中心:财务看 C1、C2、团期级 |
|
||||
|---|---|---|---|
|
||||
| A 默认 | `false` / `false` | `false` / `false` | 全 `false` |
|
||||
| B 关闭(10.5 秒生效) | `true` / `true` | `true` / `false` | 全 `true` |
|
||||
| C 还原(7.7 秒生效) | `false` / `false` | `false` / `false` | 全 `false` |
|
||||
|
||||
- 发布带 `casMd5`,还原写在 `finally` 里;还原后 md5 与原值同为 `c2206934960057f70b7173159046dc54`。
|
||||
- 两个实例的 `ADVANCE_ACL_DENY` 计数在三态的列表渲染前后都是 0;随后 4 次被拒的真实撤回让计数各 +2,证明计数有效。
|
||||
|
||||
### 8.5 回归(零写入)
|
||||
|
||||
不存在的预支 ID 调审批 / 驳回 / 撤回:定制师、车务 `585008` / `585008` / `585009`;财务 `585000` / `585000` / `585009`;管理员三个 `585000`;团期管理员三个 `581008`。与 #8517 验收读数一致,前后表行数不变。
|
||||
|
||||
### 本地证据
|
||||
|
||||
| 项 | 读数 |
|
||||
|---|---|
|
||||
| 相关 16 个测试类定向 | 265/0/0/0 |
|
||||
| 一致性矩阵 | `canRevoke` 与撤回守卫在 144 种角色 × 账号 × 创建人 × 开关组合下逐条一致 |
|
||||
| 变异 | 删掉审批中心联表那一列 → 1 例红;让 `canRevoke` 对财务放宽 → 2 例红;已还原 |
|
||||
| order-v3 全量(有 Docker,两半) | 1115 个可执行测试类全部有报告;红 2 个类、`hl-finance` 红 25 个类,在基底 `3bad2ad27` 上读数与用例名逐条一致,本单零新增 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue `#8684`;PR `#8723`
|
||||
- 前置:Issue `#8517`(撤回判权本身)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8684](https://git.1814.love/wx/HL/issues/8684)
|
||||
- **PR**: [#8723](https://git.1814.love/wx/HL/pulls/8723)
|
||||
- **Merge commit**: [806058c66](https://git.1814.love/wx/HL/commit/806058c66)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @jw
|
||||
@@ -0,0 +1,317 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8687"
|
||||
title: "删除团期抢单池旧列表接口(GET /v3/admin/order/grab-pool/group-batches)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 删除团期抢单池旧列表接口
|
||||
|
||||
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: #8687
|
||||
> **日期**: 2026-10-02
|
||||
> **影响范围**: 管理后台团期抢单池旧列表页的数据来源
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 路由 `GET /v3/admin/order/grab-pool/group-batches`(`HouseGroupGrabAdminController.listGrabPool`)已删除,服务端不再有该路由映射。调用时 HTTP 状态仍为 200(HL 业务失败统一走 200),响应体业务码 `code=404`:`{"code":404,"message":"接口不存在: GET /v3/admin/order/grab-pool/group-batches","data":null,"success":false}`。
|
||||
- **替代接口**:`GET /v3/admin/order/house-allocation/group-batches`(`HouseAllocationListAdminController.listGroupBatches`,#8375/#8491 已上线);复刻旧列表口径传 `status=pendingClaim`(不需要再额外传 `batchStatus=RESOURCE_PREPARING`,两者同源于 `GroupBatchService.requirementConfirmableStatuses()`)。
|
||||
- 同一控制器下 4 个团级写口(整团认领 `claim` / 释放 `release` / 接管 `takeover` / 转交 `transfer`)路径与行为均不变。
|
||||
- hl-ui(`origin/v2.1`)的 `grab-pool-group.js` 对旧 GET 路径零调用(该文件自身注释已记载两个读口随 #8375 改版下线),无需前端联动改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
旧接口 `listGrabPool` 在 #8375 房务配房列表改版后已标 `@Deprecated`,继任者 `GET /v3/admin/order/house-allocation/group-batches` 自 #8375/#8491 起已承载同一批团期列表展示(待整团认领 + 已整团认领合表)。本次(#8687)删除旧路由与其专属 VO(`HouseGroupGrabPoolPageReqVO`、`HouseGroupGrabPoolItemRespVO`),以及服务层 `HouseGroupGrabService.listGrabPool` 与配套查询 DTO `HouseGrabPoolQuery`。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期抢单池列表(旧) | GET | `/v3/admin/order/grab-pool/group-batches` | 删除 | 改用 house-allocation/group-batches |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
本接口已删除。下表记录的是**删除前**的契约,供前端清理调用点、核对与替代接口的字段映射。字段名、类型、校验文案均取自删除前源码;服务端已无该路由映射,调用返回 HTTP 200 + 业务码 `code=404`(`接口不存在: GET /v3/admin/order/grab-pool/group-batches`)。
|
||||
|
||||
### 1. 团期抢单池列表(旧) `GET /v3/admin/order/grab-pool/group-batches`
|
||||
|
||||
**VO**: `HouseGroupGrabPoolPageReqVO → PageResult<HouseGroupGrabPoolItemRespVO>`(均已随本单删除)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除前:房务在团期抢单池页查看未被整团认领、需求已整体确认、阶段为「资源准备中」的团期(整团一行)。现改为调用 `GET /v3/admin/order/house-allocation/group-batches?status=pendingClaim`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| keyword | Query | String | ❌ | ≤32 字 | 团期号 / 产品名模糊搜索 |
|
||||
| productId | Query | Long | ❌ | - | 产品 ID 精确过滤 |
|
||||
| batchStatus | Query | String | ❌ | 只接受 `RESOURCE_PREPARING` | 传其它值 400 |
|
||||
| departDateFrom | Query | LocalDate | ❌ | - | 出发日下界(含) |
|
||||
| departDateTo | Query | LocalDate | ❌ | - | 出发日上界(含) |
|
||||
| page | Query | Integer | ❌ | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 每页条数 |
|
||||
| sortBy | Query | String | ❌ | 默认 `departDate,asc`,可切 `createTime,desc` | 排序 |
|
||||
|
||||
#### 出参
|
||||
|
||||
`Result<PageResult<HouseGroupGrabPoolItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | List<HouseGroupGrabPoolItemRespVO> | 行列表 |
|
||||
| total | int | 总条数 |
|
||||
| page | int | 回显页码 |
|
||||
| pageSize | int | 回显每页条数 |
|
||||
| records[].groupBatchId | String(Long 转字符串) | 团期主订单 ID |
|
||||
| records[].batchNo | String | 运营团期号 |
|
||||
| records[].productId | String(Long 转字符串) | 产品 ID |
|
||||
| records[].productName | String | 产品名快照 |
|
||||
| records[].batchName | String | 班期名快照 |
|
||||
| records[].batchLabel | String | 第 N 期快照 |
|
||||
| records[].batchStatus | String | 团期阶段 code(本接口恒为 `RESOURCE_PREPARING`) |
|
||||
| records[].batchStatusLabel | String | 团期阶段中文(恒为「资源准备中」) |
|
||||
| records[].departDate | LocalDate | 出发日期 |
|
||||
| records[].endDate | LocalDate | 结束日期 |
|
||||
| records[].enrollDeadline | LocalDate | 报名截止日 |
|
||||
| records[].enrolledRooms | Integer | 已报名房数 |
|
||||
| records[].enrolledPeople | Integer | 已报名人数 |
|
||||
| records[].activeOrderCount | Integer | 活跃子订单数(排除 CANCELLED) |
|
||||
| records[].hotelOrderCount | Integer | 已放行到房务的需房户数 |
|
||||
| records[].hotelReady | Boolean | 团期酒店资源是否已就绪 |
|
||||
| records[].daysToDepart | Integer | 今天到出发日天数 |
|
||||
| records[].urgencyLevel | String | 紧急度 code(NORMAL/URGENT/CRITICAL) |
|
||||
| records[].urgencyLabel | String | 紧急度中文 |
|
||||
| records[].createTime | LocalDateTime | 团期创建时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/grab-pool/group-batches?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
字段值取自 2026-10-02 11:13 测试服 `house-allocation/group-batches` 实测返回中的一行(该团当时阶段为 `RESOURCE_PREPARING`),按旧接口的 20 个字段裁剪展示;容器层 `total`/`page`/`pageSize` 为示例用值。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"groupBatchId": "2104838272570245121",
|
||||
"batchNo": "T26-7574",
|
||||
"productId": "2101499109901778946",
|
||||
"productName": "jw测试产品",
|
||||
"batchName": "11月10日阿尔山温泉雪国5日游",
|
||||
"batchLabel": "4",
|
||||
"batchStatus": "RESOURCE_PREPARING",
|
||||
"batchStatusLabel": "资源准备中",
|
||||
"departDate": "2026-11-10",
|
||||
"endDate": "2026-11-16",
|
||||
"enrollDeadline": "2026-11-09",
|
||||
"enrolledRooms": 0,
|
||||
"enrolledPeople": 0,
|
||||
"activeOrderCount": 0,
|
||||
"hotelOrderCount": 0,
|
||||
"hotelReady": false,
|
||||
"daysToDepart": 39,
|
||||
"urgencyLevel": "NORMAL",
|
||||
"urgencyLabel": "正常",
|
||||
"createTime": "2026-09-29 15:38:45"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
接口已删除,无空数据或降级形态可约定。删除后任何入参都返回 HTTP 200 + `{"code":404,"message":"接口不存在: GET /v3/admin/order/grab-pool/group-batches","data":null,"success":false}`(2026-10-02 部署 `ca1b590d7` 后实测)。删除前,无命中记录时实测返回 `{"records":[],"total":0,"page":1,"pageSize":5}`(2026-10-02 11:12,提交 `a65c53ebbc`)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "batchStatus 只接受 RESOURCE_PREPARING",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
| code | message | 触发 |
|
||||
|------|---------|------|
|
||||
| 400 | keyword 长度不能超过 32 字 / batchStatus 只接受 RESOURCE_PREPARING / page 必须大于等于 1 / pageSize 最大 100 | 删除前的入参校验 |
|
||||
| 808090 | 未登录或非房务角色,无权操作 | 删除前:非 ROOM_MANAGER / SUPER_ADMIN 访问(零角色 token 放行) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 服务端已无该路由映射,调用返回 HTTP 200 + 业务码 `code=404`;调用点一律移除。
|
||||
- 替代接口 `GET /v3/admin/order/house-allocation/group-batches` 的 `status=pendingClaim` 分支复刻本接口口径(未被整团认领 + 需求已整体确认 + 阶段在可确认集合内,当前该集合只有 `RESOURCE_PREPARING`,单源 `GroupBatchService.requirementConfirmableStatuses()`),不需要额外传 `batchStatus`。
|
||||
- 替代接口的 `batchStatus` 若显式传值,接受团期九态任一(不再锁定 `RESOURCE_PREPARING`),旧接口「传其它值 400」的收紧校验不再复现。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload |
|
||||
|------|---------|
|
||||
| ❌ 团期抢单池列表 | `GET /v3/admin/order/grab-pool/group-batches` → 路由已删除,业务码 `code=404`(接口不存在) |
|
||||
| ✅ 团期抢单池列表 | `GET /v3/admin/order/house-allocation/group-batches?status=pendingClaim` |
|
||||
|
||||
### 字段迁移
|
||||
|
||||
- 响应容器从 `PageResult`(`records`/`total`/`page`/`pageSize`)换成 `HouseAllocationGroupPageRespVO`(`list`/`total`/`stats`),字段名不同,且新容器不回显 `page`/`pageSize`。
|
||||
- 旧 20 个行字段在新响应行 `HouseAllocationGroupRespVO` 中逐一同名存在(见六.6),可按原字段名直接取值。
|
||||
- 新增的 `requirementConfirmed`/`houseClaimerId`/`houseClaimerName`/`houseClaimedAt`/`isMine`/`canStartAllocation`/`taskKind`/`taskKindLabel`/`unreadCount`/`readOnly`/`readOnlyReason` 是旧接口没有的扩展字段(旧池列表语义上只展示未认领团,没有认领人信息)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本次清单仅涉及只读 GET 的删除,不涉及任何写入路径。
|
||||
|
||||
| 前端提交 | 写入位置 | 行为 |
|
||||
|----------|----------|------|
|
||||
| 无 | 无 | 本接口为只读,不涉及写入 |
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 路由已从服务端删除,调用返回 HTTP 200 + 业务码 `code=404`(`接口不存在`);调用点一律移除,不要按这个返回体做分支判断。
|
||||
- 同控制器 4 个写口(claim/release/takeover/transfer)未受影响,路径、错误码(808090 等)、角色门全部不变。
|
||||
- 替代接口的角色门与旧接口等价:`ROOM_MANAGER` / `SUPER_ADMIN` 放行,零角色 token(网关未透传 `X-Admin-Role`)放行,其余角色 808090。
|
||||
|
||||
---
|
||||
|
||||
## 六.5 枚举
|
||||
|
||||
### 团期阶段 batchStatus(`GroupBatchStatus`)
|
||||
|
||||
**所属字段**: 旧接口 `HouseGroupGrabPoolPageReqVO.batchStatus`(入参,仅接受 `RESOURCE_PREPARING` 一值)/ 替代接口 `HouseAllocationGroupPageReqVO.batchStatus`(入参,接受全部九态)与两者行内 `batchStatus`/`batchStatusLabel`
|
||||
**类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `RECRUITING` | 招募中 | 旧接口传此值 400,替代接口可传 |
|
||||
| `RESOURCE_PREPARING` | 资源准备中 | 两接口唯一共同的「可认领」阶段 |
|
||||
| `MATERIAL_PREPARING` | 物料准备中 | 旧接口传此值 400,替代接口可传 |
|
||||
| `PENDING_DEPARTURE` | 待出发 | 同上 |
|
||||
| `TRAVELLING` | 出行中 | 同上 |
|
||||
| `PENDING_REVIEW` | 待核单 | 同上(#8516 由 `TRIP_FINISHED` 改名,旧值仍兼容) |
|
||||
| `REVIEWING` | 核单中 | 同上 |
|
||||
| `SETTLED` | 已结算 | 同上 |
|
||||
| `CANCELLED` | 已取消 | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前(旧接口) | 改后(替代接口) |
|
||||
|------|------|------|
|
||||
| 响应容器 | `PageResult`:`records`/`total`/`page`/`pageSize` | `HouseAllocationGroupPageRespVO`:`list`/`total`/`stats`,不回显 page/pageSize |
|
||||
| `groupBatchId`/`batchNo`/`productId`/`productName`/`batchName`/`batchLabel`/`batchStatus`/`batchStatusLabel`/`departDate`/`endDate`/`enrollDeadline`/`enrolledRooms`/`enrolledPeople`/`activeOrderCount`/`hotelOrderCount`/`hotelReady`/`daysToDepart`/`urgencyLevel`/`urgencyLabel`/`createTime` | 有(20 字段) | 同名字段原样保留 |
|
||||
| `requirementConfirmed`/`houseClaimerId`/`houseClaimerName`/`houseClaimedAt`/`isMine`/`canStartAllocation`/`taskKind`/`taskKindLabel`/`unreadCount`/`readOnly`/`readOnlyReason` | 无 | 新增 11 个字段 |
|
||||
| 入参 `batchStatus` 取值范围 | 仅 `RESOURCE_PREPARING`,其余 400 | 团期九态任一 |
|
||||
| 入参 `scope`/`status` | 无(隐含只看未认领 + RESOURCE_PREPARING) | 新增,`status=pendingClaim` 复刻旧默认口径 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 调用旧路由 | 返回分页列表 | 路由已删除,HTTP 200 + 业务码 `code=404`(接口不存在) |
|
||||
| 查看待整团认领的团期 | 固定只看 `RESOURCE_PREPARING` 一个阶段 | 固定看 `requirementConfirmableStatuses()`(当前等价,单源可变) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是。路由删除,仍调用旧路径的代码会收到业务码 `code=404`(接口不存在)。
|
||||
- **前端是否必须同步上线**: 否——经 hl-ui `origin/v2.1` 核查,`grab-pool-group.js` 对本路径零调用(该文件自身注释记载两个读口已随 #8375 改版下线),现网没有调用点需要跟随本单改动。
|
||||
- **残留调用点清理**: 若历史分支仍保留对旧路径的调用、或按 `records`/`page`/`pageSize` 解析响应的代码,需改为按 `list`/`stats` 解析替代接口。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 调用 `GET /v3/admin/order/grab-pool/group-batches` 的代码(现网 hl-ui 已零调用)。
|
||||
- **零影响**:
|
||||
- 同控制器 4 个写口:`POST .../group-batches/{groupBatchId}/claim`、`.../release`、`.../takeover`、`.../transfer`
|
||||
- 替代接口 `GET /v3/admin/order/house-allocation/group-batches` 与 `GET /v3/admin/order/house-allocation/households`
|
||||
- 小程序与 H5
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
测试服环境,2026-10-02,经网关调用。
|
||||
|
||||
```
|
||||
删除前(hl-order-service-v3 提交 a65c53ebbc)
|
||||
GET /v3/admin/order/grab-pool/group-batches?page=1&pageSize=5
|
||||
→ HTTP 200,code 200,records 为空(当时测试数据里没有满足旧池条件的团),11:12 实测
|
||||
GET /v3/admin/order/house-allocation/group-batches?scope=all&page=1&pageSize=5(替代接口)
|
||||
→ HTTP 200,code 200,total=8,11:13 实测;三节响应示例的字段值取自这次返回的其中一行
|
||||
|
||||
删除后(hl-order-service-v3 提交 ca1b590d7,13:25 部署,两个实例均已重启)
|
||||
GET /v3/admin/order/grab-pool/group-batches?page=1&pageSize=5
|
||||
→ HTTP 200,{"code":404,"message":"接口不存在: GET /v3/admin/order/grab-pool/group-batches","data":null,"success":false}
|
||||
GET /v3/admin/order/house-allocation/group-batches?scope=all&page=1&pageSize=5(同一 token)
|
||||
→ HTTP 200,code 200,total=8,第 1 页 5 个 groupBatchId 及顺序与删除前相同;
|
||||
只有 isMine / readOnly / readOnlyReason 不同,这三个字段随查看者账号变化,两次请求用的账号不同
|
||||
POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/release、/claim、/release
|
||||
→ 均 HTTP 200,code 200,库里团级认领人按 原认领人 → 空 → admin → 空 变化;
|
||||
最后用 /takeover 把认领人还原为原认领人
|
||||
```
|
||||
|
||||
验证身份:超管测试账号。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 继任端点源码:`HouseAllocationListAdminController.listGroupBatches` / `HouseAllocationListService.pageGroupBatches`
|
||||
- 前置变更:#8375(房务去掉抢单池,配房并入房务管家订单列表,继任端点上线)、#8491(房务控制台;其 I-24 删除了另外 4 个旧抢单池读口,本单删除的是剩下的这一个)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
- **Issue**: [#8687](https://git.1814.love:8443/wx/HL/issues/8687)
|
||||
|
||||
### 联系人
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8699"
|
||||
title: "往来账·财务调账——调账单(TZ)+页内审批+应收/应付入账联动(#8699)"
|
||||
consumer: "admin"
|
||||
author: "yst(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "427e30804f31dbc61fad0c0be9f2117e8a5eb78d"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-10-02"
|
||||
status_note: "往来账「财务调账」(SRS 3.6.2,原型 fin-adjust)后端落地,原标二期提前本期实现。新增 /admin/finance/adjusts 5 端点:新建调账单(TZ-单号)/分页/详情/批准/驳回。挂团+改单团利润的应收/应付跨团调整集中口,金额正=调增/负=红字冲减,批准后顺数据流写台账、全留痕不删原记录、不动账户结存。应付侧写 fin_payable_line 调账行(可继续走付款申请),应收侧改 order-v3 查询叠加让应收台账数字真实变化(B 方案)。审批=页内单步批准/驳回(企微多级后期统一接);改单团利润只留痕标记;阈值 FINADJUST_APPROVE_MIN 从后端参数读取回传提示。部署测试服行为级验证全过:应收叠加 receivable 5360→5860(+500)精确、应付调增/调减、不挂团占位 UNGROUPED、驳回、596110/596103/596102 校验全对。前端交付(2026-10-02):新建 finance/adjust 页(hiddenRoute 先行,sys_menu 未下挂;useListPage 单表,金额负红/UNGROUPED 显「不挂团」/行无 *Name 本地映射中文;查看详情抽屉含 reviewLogs+needMultiLevelApprove 仅提示;PENDING 行批准 dialog/驳回 opinion 必填 596109 前置)+AdjustCreateModal(目标联动单位来源:应付 SupplierPickerModal 带 partyId/应收手填客户名;应收 teamNo 条件必填 596110 前置;金额禁 0 596103 前置)+api/finance/adjust.js,spec 12 例全绿,提交 427e3080。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# finance:往来账·财务调账——调账单(TZ)+页内审批+应收/应付入账联动(管理后台)
|
||||
|
||||
> ✅ **additive 纯新增接口组**:新增 `/admin/finance/adjusts` 5 端点,旧前端不受影响。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
往来账「财务调账」是**挂团 + 改单团利润的应收/应付跨团调整集中口**,调账单号 `TZ` 前缀。用于账对不上时的差异平账:金额正=调增 / 负=红字冲减,批准后顺数据流写入应付款 / 应收台账,**全留痕不删原记录、不动账户结存**。
|
||||
|
||||
此前仅有原型 + 设计稿(SRS 标二期),后端零实现。本期落地完整后端:调账单 CRUD + 页内审批 + 应收/应付入账联动。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST /admin/finance/adjusts | 新建调账单(生成 TZ 单号,PENDING) | ✅ 新增 |
|
||||
| 2 | GET /admin/finance/adjusts/page | 调账单分页(kw/target/status 筛选) | ✅ 新增 |
|
||||
| 3 | GET /admin/finance/adjusts/{id} | 调账单详情(含审批留痕 + 多级提示标记) | ✅ 新增 |
|
||||
| 4 | POST /admin/finance/adjusts/{id}/approve | 批准 → 入账联动 → POSTED | ✅ 新增 |
|
||||
| 5 | POST /admin/finance/adjusts/{id}/reject | 驳回 → REJECTED | ✅ 新增 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
统一前缀 `POST/GET /admin/finance/adjusts/**`(业务调用**不带**服务前缀,网关按 `/admin/finance/**` 路由到 order-v3)。
|
||||
|
||||
## 4. 入参
|
||||
|
||||
### 4.1 新建调账单(POST /)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| adjustTarget | String | 是 | `RECEIVABLE` 应收 / `PAYABLE` 应付 |
|
||||
| partyId | Long | 否 | 单位ID(应付=供应商ID / 应收=客户ID) |
|
||||
| partyName | String | 是 | 单位名称(快照) |
|
||||
| teamNo | String | 见说明 | 团号。应收:与 orderId **至少填一个**(否则 596110);应付:可空(空则入账占位 UNGROUPED) |
|
||||
| orderId | Long | 见说明 | 订单ID(应收订单级定位;应付可空) |
|
||||
| amount | BigDecimal | 是 | 调账金额(正=调增 / 负=红字冲减,**禁 0** 否则 596103) |
|
||||
| accountPeriod | Date | 是 | 插入账期(yyyy-MM-dd;批准时校验未封账否则 596104) |
|
||||
| profitFlag | Boolean | 是 | 是否改单团利润(true=修改 / false=不修改,**只留痕标记,不跨域回写**) |
|
||||
| remark | String | 是 | 备注(调账原因/依据) |
|
||||
|
||||
### 4.2 分页(GET /page)
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| pageNo / pageSize | 分页 |
|
||||
| kw | 关键词(模糊 调账单号/单位名/团号) |
|
||||
| adjustTarget | `RECEIVABLE` / `PAYABLE` 精确筛选 |
|
||||
| status | `PENDING` / `POSTED` / `REJECTED` 精确筛选 |
|
||||
|
||||
### 4.3 批准 / 驳回(POST /{id}/approve | /{id}/reject)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| opinion | String | 驳回必填 | 审批意见(批准可空 / **驳回必填** 否则 596109) |
|
||||
|
||||
## 5. 出参
|
||||
|
||||
### 5.1 调账单行(AdjustRowRespVO)/ 详情(AdjustDetailRespVO)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | Long(string) | 调账单ID |
|
||||
| adjustNo | String | 调账单号(TZ-yyyyMMddNNNN) |
|
||||
| adjustTarget | String | RECEIVABLE / PAYABLE |
|
||||
| partyId / partyName | Long(string) / String | 单位 |
|
||||
| teamNo | String | 团号(可空) |
|
||||
| orderId | Long(string) | 订单ID(可空) |
|
||||
| amount | BigDecimal | 调账金额(带符号) |
|
||||
| accountPeriod | Date | 插入账期 |
|
||||
| profitFlag | Boolean | 是否改单团利润 |
|
||||
| remark | String | 备注 |
|
||||
| status | String | PENDING / POSTED / REJECTED |
|
||||
| postedFlow | String | 入账关联流水描述(批准后回填,如「应付款·某供应商·某团」「应收台账·26-2827」) |
|
||||
| reviewByName / reviewTime / reviewRemark | | 审批最终态 |
|
||||
| createTime | LocalDateTime | 创建时间 |
|
||||
|
||||
详情额外:
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| needMultiLevelApprove | Boolean:金额绝对值 ≥ 后端参数 `FINADJUST_APPROVE_MIN`(默认 5000)时为 true,前端据此提示「需多级审批」(本期后端仍单步批准,仅提示) |
|
||||
| reviewLogs | 审批留痕列表(action/operatorName/opinion/fromStatus/toStatus/createTime) |
|
||||
|
||||
### 5.2 批准 / 驳回响应(AdjustReviewRespVO)
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| status | 操作后状态(POSTED / REJECTED) |
|
||||
| postedFlow | 入账关联流水(批准时) |
|
||||
| needMultiLevelApprove | 多级审批提示标记(批准时) |
|
||||
|
||||
## 6. 枚举/数据字典
|
||||
|
||||
### adjustTarget(调整目标)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| RECEIVABLE | 应收(客户侧) |
|
||||
| PAYABLE | 应付(供应商侧) |
|
||||
|
||||
### status(调账单状态)
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 待审批 |
|
||||
| POSTED | 已入账(批准生效) |
|
||||
| REJECTED | 已驳回 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发 |
|
||||
|---|---|---|
|
||||
| 596101 | 调账单不存在 | id 不存在/已软删 |
|
||||
| 596102 | 调账单非待审批状态,不可操作 | 对已 POSTED/REJECTED 单再批准/驳回 |
|
||||
| 596103 | 调账金额不允许为 0 | amount=0 |
|
||||
| 596104 | 插入账期已封账 | accountPeriod 落已封账期/无开账期 |
|
||||
| 596105 | 调整目标非法 | adjustTarget 非 RECEIVABLE/PAYABLE |
|
||||
| 596106 | 单位缺失 | partyName 空 |
|
||||
| 596107 | 调账单号生成冲突 | TZ 取号撞号重试耗尽(重试即可) |
|
||||
| 596109 | 驳回原因不能为空 | 驳回未填 opinion |
|
||||
| 596110 | 应收调账必须挂团或挂订单 | RECEIVABLE 且 teamNo/orderId 双空 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型:新建应收调账单(挂订单 +500)→ 批准
|
||||
```http
|
||||
POST /admin/finance/adjusts
|
||||
{"adjustTarget":"RECEIVABLE","partyName":"宋家辉","teamNo":"26-2827","orderId":2105709330353520641,
|
||||
"amount":500,"accountPeriod":"2026-10-02","profitFlag":false,"remark":"尾款差额补差"}
|
||||
→ 200 {"id":"2105837196957446145","adjustNo":"TZ-202610020001","status":"PENDING",...}
|
||||
|
||||
POST /admin/finance/adjusts/2105837196957446145/approve {"opinion":"同意"}
|
||||
→ 200 {"status":"POSTED","postedFlow":"应收台账·26-2827","needMultiLevelApprove":false}
|
||||
# 效果:该订单应收台账 receivableAmount 5360→5860(+500)、balanceAmount 0→500
|
||||
```
|
||||
|
||||
### 8.2 应付调增(挂团 +300)
|
||||
```http
|
||||
POST /admin/finance/adjusts
|
||||
{"adjustTarget":"PAYABLE","partyName":"草原行车队","teamNo":"26-8875","amount":300,
|
||||
"accountPeriod":"2026-10-02","profitFlag":true,"remark":"包车加班费补差"}
|
||||
→ 批准后 postedFlow="应付款·草原行车队·26-8875"
|
||||
# 效果:fin_payable_line 追加调增行,可继续走付款申请/审批/出纳
|
||||
```
|
||||
|
||||
### 8.3 应付调减不挂团(-100)→ 占位 UNGROUPED
|
||||
```http
|
||||
POST /admin/finance/adjusts
|
||||
{"adjustTarget":"PAYABLE","partyName":"某供应商","amount":-100,
|
||||
"accountPeriod":"2026-10-02","profitFlag":false,"remark":"多付红字冲减"}
|
||||
→ 批准后 postedFlow="应付款·某供应商·UNGROUPED"
|
||||
# 效果:fin_payable_line 追加 REDUCE 负向行;不挂团时团号占位 UNGROUPED
|
||||
```
|
||||
|
||||
### 8.4 异常:应收双空
|
||||
```http
|
||||
POST /admin/finance/adjusts
|
||||
{"adjustTarget":"RECEIVABLE","partyName":"某客户","amount":50,
|
||||
"accountPeriod":"2026-10-02","profitFlag":false,"remark":"x"}
|
||||
→ {"code":596110,"message":"应收调账必须挂团或挂订单","success":false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **应付侧**:调账行写入 `fin_payable_line`(来源 FIN_ADJUST),进应付款「按团号/按供应商」列表,**可继续走付款申请/审批/出纳支付**;不挂团时团号占位 `UNGROUPED`,会在按团列表出现一个 UNGROUPED 行,**前端可识别该值显示「不挂团」**。
|
||||
- **应收侧**:调账不写新表行,由 order-v3 查询时**实时叠加**进应收台账数字(receivableAmount + balanceAmount)。挂订单按订单级叠加、挂团(order_id 空)按团维度叠加;负调账超额时 balanceAmount **可为负**(负值即「冲多了」信号,前端原样展示,不兜底)。
|
||||
- **改单团利润** profitFlag 仅留痕标记,后端**不跨域回写**订单/团利润。
|
||||
- **阈值**:needMultiLevelApprove 仅提示,本期后端仍单步批准;企微多级审批后期统一接。
|
||||
- 全留痕不删原记录,审批轨迹进 reviewLogs。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
新增接口组,无「修改前」。
|
||||
|
||||
## 11. 影响评估/回滚
|
||||
|
||||
- additive 纯新增,旧前端零影响。
|
||||
- 回滚:删除调账域代码 + DROP fin_adjust/fin_adjust_review_log 即可(涉 DDL 回滚需谨慎,建议保留表只回滚代码)。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- Long 型 ID 全部是 string,前端勿按 number 解析。
|
||||
- 应收调账**必须挂团或挂订单**(596110),否则静默不进台账——前端表单应引导填其一。
|
||||
- 应付调账不挂团会出现 UNGROUPED 占位行,前端需做识别展示。
|
||||
- 金额带符号:正=调增 / 负=红字冲减,禁 0。
|
||||
- 驳回必须填驳回原因(596109)。
|
||||
|
||||
## 13. 关联/联系人
|
||||
- Issue:https://git.1814.love/wx/HL/issues/8699
|
||||
- PR:https://git.1814.love/wx/HL/pulls/8710
|
||||
- merge commit:a65c53ebbc7dd0d5646a827dce8ea29f730055fb
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8708"
|
||||
title: "二期未支付订单自动取消时刻由创建后 2h 恢复为 24h,接口契约不变"
|
||||
consumer: "multiple"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "接口路径、入参、出参、错误码均不变;变化的只是二期待支付订单被系统自动取消的时刻:由创建后约 2h 恢复为创建后 24h,与已展示的支付截止时刻 expiryTime 一致。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 二期订单:未支付订单自动取消时刻恢复为创建后 24h
|
||||
|
||||
> **服务**: hl-order-service-v3(取消判定与扫描任务)、hl-user-service(定时任务调度)
|
||||
> **关联 Issue**: #8708
|
||||
> **关联 PR**: #8729(合并提交 89c87fd217)
|
||||
> **部署状态**: 测试环境已部署并实测,见「五、实测验证」
|
||||
> **影响范围**: 小程序订单详情/列表与发起支付、管理后台订单详情与线下收款登记——只影响订单何时被自动取消,不改任何字段
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**改前**:二期订单对外展示的支付截止时刻是创建后 24h(`expiryTime = 创建时刻 + expiryMinutes`,`expiryMinutes` 为 1440),但系统在创建后约 2h 就把未支付订单自动取消。2~24h 之间,客户看到的截止时刻还没到,订单却已是 CANCELLED:小程序无法再发起支付,管理后台也无法登记线下收款。
|
||||
|
||||
**改后**:系统按同一个截止时刻取消。`创建时刻 + expiryMinutes` 到达后,在下一次每分钟扫描中取消,取消原因为「订单超时未支付,系统自动取消」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
**根因**:自动取消原来只靠 RocketMQ 延迟消息触发。RocketMQ 4.x 延迟消息最长 2h,24h 窗口的过期消息实际在约 2h 后投递并取消订单;测试服 SYSTEM_AUTO_CANCEL 记录的延迟全部是 120 分钟。
|
||||
|
||||
**后果**(二期尚未上生产,影响面限于测试服数据):
|
||||
|
||||
- 客户在创建后 2~24h 之间无法支付;
|
||||
- 取消会连带房务释放:测试服有 8 单取消后配房被软删并回补库存;
|
||||
- 客户在取消前发起、取消后才到账的款落入 `CANCELLED_PAID`,需要人工退款。
|
||||
|
||||
---
|
||||
|
||||
## 二、后端改动
|
||||
|
||||
- 窗口 ≤ 120 分钟的订单仍由延迟消息触发取消;消费延迟消息时增加到期判定,截止时刻未到不取消。
|
||||
- 新增每分钟一次的扫描任务:order-v3 `OrderExpiryJob`,由 user-service 定时任务经内部接口 `POST /v3/internal/jobs/order-expiry/run` 触发,按「创建时刻 + expiryMinutes ≤ 当前时刻」取消。窗口超过 120 分钟的订单靠它取消;延迟消息丢失时,它也是兜底。
|
||||
- 该内部接口经网关访问一律返回 403,前端不可调用,也无需调用。
|
||||
- 到期判定与小程序展示的 `expiryTime` 共用同一个计算(`OrderPayWindow`):展示的截止时刻就是系统取消的依据。
|
||||
|
||||
---
|
||||
|
||||
## 三、对前端的影响
|
||||
|
||||
**前端无需改动。** 接口路径、入参、出参、错误码均不变。
|
||||
|
||||
### 可观察到的行为变化
|
||||
|
||||
| 端 | 接口 | 改前 | 改后 |
|
||||
|---|---|---|---|
|
||||
| 小程序 | 订单详情、订单列表(order-v3 内部端点 `GET /v3/internal/mp/order/{orderId}`、`GET /v3/internal/mp/order/list`,经小程序服务转发) | 创建约 2h 后订单变为 CANCELLED,`expiryTime` 不再返回 | 截止时刻到达前订单保持 PENDING_PAY,`expiryTime` 持续返回 |
|
||||
| 小程序 | 发起支付(order-v3 内部端点 `POST /v3/internal/mp/order/{orderId}/payment/unified-order`,经小程序服务转发) | 2~24h 之间订单已取消,返回 520104「订单当前状态不允许支付」 | 截止时刻到达前可正常发起支付 |
|
||||
| 管理后台 | `GET /v3/admin/order/{id}` | 创建约 2h 后为 CANCELLED | 截止时刻到达前为 PENDING_PAY |
|
||||
| 管理后台 | `POST /v3/admin/order/{orderId}/payment/manual-receipt` | 2~24h 之间订单已取消,无法登记 | 截止时刻到达前可正常登记;登记后订单离开待支付,不再被自动取消 |
|
||||
|
||||
### 字段口径(均未变化)
|
||||
|
||||
- 小程序详情/列表的 `expiryTime`:仅订单为 PENDING_PAY 时返回,值为 `创建时刻 + expiryMinutes`(`expiryMinutes` 为空时按 1440 计);其他状态返回 null。
|
||||
- 管理后台创单响应的 `expiryMinutes`:仍为 1440。
|
||||
|
||||
---
|
||||
|
||||
## 四、覆盖范围与边界
|
||||
|
||||
- 范围:二期 order-v3 的待支付订单。一期 v2 订单不受影响。
|
||||
- 已支付或已登记线下收款的订单离开待支付,不会被自动取消。
|
||||
- 撤销线下收款后回到待支付的订单,系统不自动取消,需人工处理(与改前一致)。
|
||||
- 取消时刻:截止时刻之后的下一次每分钟扫描,按设计在截止时刻后 2 分钟内。
|
||||
- 按产品配置不同支付窗口,不在本次范围。
|
||||
|
||||
---
|
||||
|
||||
## 五、实测验证(测试环境)
|
||||
|
||||
- **部署后存量**:创建早于部署时刻 24h 以上、仍待支付的订单经扫描全部取消(剔除撤销收款后回到待支付的单,计数为 0)。阳性对照单部署前为超期待支付,部署后转为 CANCELLED,状态日志新增一条 SYSTEM_AUTO_CANCEL 记录。
|
||||
- **新建订单**:管理后台新建两张核心订单,创建后 132 分钟两单仍为 PENDING_PAY(改前此时已被取消)。对其中一张登记线下收款,返回 code 200,订单转为已付定金,状态日志中没有自动取消记录。
|
||||
|
||||
---
|
||||
|
||||
## 关联
|
||||
|
||||
- 工单 #8708,PR #8729
|
||||
@@ -0,0 +1,563 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8711"
|
||||
title: "房务转房与退给酒店:招募中放开,源团期阶段栅栏按动作分码(#8711)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "团期招募中时,「向酒店取消」放开、「转出」保持拒绝。源团期阶段不符时错误码统一用 808327 并随文案标出阶段。列表行新增两字段标示当前行能否转出,前端据此控制转出按钮与提示。"
|
||||
updated_at: "2026-10-02"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务:转房与取消,招募中场景分权,源团期栅栏细化(管理后台)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **关联 Issue**: #8711
|
||||
> **关联 PR**: #8730
|
||||
> **部署状态**: 测试服验证通过(commit 704ecdd887)
|
||||
> **影响范围**: 房务控制台「退团转房」页面(I-17/I-20/I-21 三端点)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **I-21 向酒店取消**:源团期招募中时也允许办理(此前返回 808323 拒绝)。
|
||||
- **I-20 转房**:源团期招募中时被拒,改返新码 **808327**(文案统一为「退团房所在团期当前阶段(招募中)不允许处理」)。
|
||||
- **I-17 列表**:每行新增 `transferAllowed`(Boolean)与 `transferBlockedReason`(String),指示该行是否允许转出与置灰理由。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期成团前处于招募中,此时转房打破户数基线而重新成团,与计划配置窗口冲突。旧流程让两个动作都被拒;本次改为**招募中只放开「取消」、保持拦「转出」**:
|
||||
- **向酒店取消**(全量退团)不改库存配置,只通知酒店降间,允许执行;
|
||||
- **转出**(转给别户/别团)改动源目标两处计划基线,禁止执行。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 退团转房列表 | GET | `/v3/admin/order/house-console/room-transfers` | 修改接口 | 行新增 `transferAllowed` 与 `transferBlockedReason` 字段(additive) |
|
||||
| 2 | 转出 | POST | `/v3/admin/order/house-console/room-transfers/{id}/transfer` | 修改接口 | 源团期招募中返回 808327;错误码文案含源团期阶段中文 |
|
||||
| 3 | 向酒店取消 | POST | `/v3/admin/order/house-console/room-transfers/{id}/cancel-hotel` | 修改接口 | 源团期招募中放开;不再返回 808323;阶段不符返回 808327 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 退团转房列表 `GET /v3/admin/order/house-console/room-transfers`
|
||||
|
||||
**VO**: `HouseRoomTransferPageRespVO → HouseRoomTransferRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务控制台「退团转房」页面加载列表与汇总。房务查看待处理转房行、其状态与无法转出时的原因。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| pageNo | Query | Integer | ✅ | ≥1 | 页码 |
|
||||
| pageSize | Query | Integer | ✅ | 1~100 | 每页条数 |
|
||||
| status | Query | String | ❌ | PENDING / TRANSFERRED / CANCELLED | 状态筛选;缺省 PENDING |
|
||||
| groupBatchId | Query | Long(string) | ❌ | - | 团期 ID 过滤(来源团期) |
|
||||
| cityName | Query | String | ❌ | - | 城市名称过滤 |
|
||||
| risk | Query | String | ❌ | NORMAL / NEAR / OVERDUE / NO_DEADLINE | 风险过滤 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | List[HouseRoomTransferRespVO] | 分页行 |
|
||||
| total | Long | 总行数 |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
| summary | Object | 待处理汇总(以间为单位) |
|
||||
| summary.pendingRooms | Integer | 待处理总间数 |
|
||||
| summary.overdueRooms | Integer | 已过免费取消期限的间数 |
|
||||
| summary.nearRooms | Integer | 临近期限的间数 |
|
||||
| summary.noDeadlineRooms | Integer | 未设期限的间数 |
|
||||
|
||||
**行数据关键字段**(HouseRoomTransferRespVO):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(string) | 转房行 ID |
|
||||
| sourceType | String | 来源类型:ORDER(常规单)/ GROUP_BATCH(团期) |
|
||||
| sourceOrderId | Long(string) | 源订单 ID(团期来源时为离团子单) |
|
||||
| sourceGroupBatchId | Long(string) | 源团期 ID(仅团期来源时有值) |
|
||||
| teamNo | String | 源订单团号(常规单来源时取 order_main.team_no;团期来源时为 null) |
|
||||
| sourceBatchNo | String | 源团期批次号(仅团期来源时有值) |
|
||||
| stayDate | Date | 入住日期(yyyy-MM-dd) |
|
||||
| cityName | String | 城市名 |
|
||||
| hotelName | String | 酒店名 |
|
||||
| roomTypeName | String | 房型名 |
|
||||
| roomCount | Integer | 原始间数 |
|
||||
| remainingCount | Integer | 剩余待处理间数 |
|
||||
| status | String | 状态:PENDING(待处理)/ TRANSFERRED(已转出)/ CANCELLED(已取消) |
|
||||
| statusLabel | String | 状态中文 |
|
||||
| cancelDays | Integer | 免费取消提前天数(未设为 null) |
|
||||
| deadlineAt | LocalDateTime | 免费取消截止时刻(未设为 null) |
|
||||
| risk | String | 风险码:NORMAL / NEAR / OVERDUE / NO_DEADLINE(仅 PENDING 有意义) |
|
||||
| riskLabel | String | 风险中文 |
|
||||
| readOnly | Boolean | 是否对当前操作人只读:源单由他人处理,或源团期两个动作都不允许 |
|
||||
| readOnlyReason | String | 只读理由 |
|
||||
| **transferAllowed** | **Boolean** | **✨ 新增:是否可转出(I-20)。PENDING 行仅源团期资源准备中为 true,招募中为 false(仍可向酒店取消),其余阶段为 false。已处理行同样计算但不作展示依据** |
|
||||
| **transferBlockedReason** | **String** | **✨ 新增:转出置灰提示。可转出时为 null;招募中为「所在团期招募中,仅可向酒店取消」;其余不可处理阶段与 readOnlyReason 相同** |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/house-console/room-transfers?pageNo=1&pageSize=20&status=PENDING&groupBatchId=2105934717486563329
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2105935060039565313",
|
||||
"sourceType": "GROUP_BATCH",
|
||||
"sourceOrderId": "2105934624691712001",
|
||||
"sourceGroupBatchId": "2105934717486563329",
|
||||
"teamNo": null,
|
||||
"sourceBatchNo": "T5-260928-01",
|
||||
"stayDate": "2027-05-12",
|
||||
"cityName": "海拉尔",
|
||||
"hotelName": "阿尔善国际维景度假温泉酒店",
|
||||
"roomTypeName": "高级套房",
|
||||
"roomCount": 2,
|
||||
"remainingCount": 2,
|
||||
"status": "PENDING",
|
||||
"statusLabel": "待处理",
|
||||
"cancelDays": 3,
|
||||
"deadlineAt": "2027-05-11 18:00:00",
|
||||
"risk": "NORMAL",
|
||||
"riskLabel": "正常",
|
||||
"readOnly": false,
|
||||
"readOnlyReason": null,
|
||||
"transferAllowed": false,
|
||||
"transferBlockedReason": "所在团期招募中,仅可向酒店取消"
|
||||
},
|
||||
{
|
||||
"id": "2105935060047953921",
|
||||
"sourceType": "GROUP_BATCH",
|
||||
"sourceOrderId": "2105934624699100225",
|
||||
"sourceGroupBatchId": "2105934717486563329",
|
||||
"teamNo": null,
|
||||
"sourceBatchNo": "T5-260928-01",
|
||||
"stayDate": "2027-05-13",
|
||||
"cityName": "海拉尔",
|
||||
"hotelName": "阿尔善国际维景度假温泉酒店",
|
||||
"roomTypeName": "标准间",
|
||||
"roomCount": 3,
|
||||
"remainingCount": 2,
|
||||
"status": "PENDING",
|
||||
"statusLabel": "待处理",
|
||||
"cancelDays": null,
|
||||
"deadlineAt": null,
|
||||
"risk": "NO_DEADLINE",
|
||||
"riskLabel": "未设期限",
|
||||
"readOnly": false,
|
||||
"readOnlyReason": null,
|
||||
"transferAllowed": false,
|
||||
"transferBlockedReason": "所在团期招募中,仅可向酒店取消"
|
||||
}
|
||||
],
|
||||
"total": 3,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"summary": {
|
||||
"pendingRooms": 7,
|
||||
"overdueRooms": 0,
|
||||
"nearRooms": 2,
|
||||
"noDeadlineRooms": 5
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
当无符合条件的行时,`records` 为空数组,`total=0`;`summary` 按 `status=PENDING` 的全库统计(不受筛选影响)。接口 GET 查询无降级;异常时返回 HTTP 500 + 500001。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "分页参数不合法",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `transferAllowed=false` 时前端应禁用「转出」按钮,显示 `transferBlockedReason` 作置灰提示,但保留「向酒店取消」按钮可用。
|
||||
- `transferAllowed=true` 时 `transferBlockedReason` 恒为 null;前端可隐藏转出提示。
|
||||
- `readOnly=true` 时两个按钮都禁用,提示来自 `readOnlyReason`(源单由他人处理);此时 `transferAllowed` 同样为 false 但提示词不同(后者指阶段,前者指权限)。
|
||||
- 已处理行(status=TRANSFERRED / CANCELLED)虽然 `transferAllowed` 同样计算,但不作展示依据;前端按 `status` 判断是否展示操作区域。
|
||||
|
||||
---
|
||||
|
||||
### 2. 转出 `POST /v3/admin/order/house-console/room-transfers/{id}/transfer`
|
||||
|
||||
**VO**: `HouseRoomTransferSaveReqVO → HouseRoomTransferRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务把待处理的退团房间转给另一张常规订单或另一个团期。只有源团期处于资源准备中时可转,招募中及其他阶段被拒。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long(string) | ✅ | 存在且 status=PENDING | 转房行 ID |
|
||||
| targetType | Body | String | ✅ | ORDER / GROUP_BATCH | 目标类型(常规单/团期) |
|
||||
| targetOrderId | Body | Long(string) | 条件 | 当 targetType=ORDER 时必填 | 目标订单 ID |
|
||||
| targetRequirementId | Body | Long(string) | 条件 | 当 targetType=ORDER 时必填 | 目标需求 ID |
|
||||
| targetGroupBatchId | Body | Long(string) | 条件 | 当 targetType=GROUP_BATCH 时必填 | 目标团期 ID |
|
||||
| roomCount | Body | Integer | ✅ | ≥1,≤剩余待处理间数 | 转出间数 |
|
||||
| hotelConfirmNo | Body | String | ✅ | - | 酒店确认号 |
|
||||
| proofFileIds | Body | List[String] | ✅ | 非空列表 | 凭证文件 ID 列表 |
|
||||
| remark | Body | String | ❌ | - | 备注 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(string) | 转房行 ID(与入参相同) |
|
||||
| status | String | 更新后状态:PENDING(部分转出)或 TRANSFERRED(全部转出) |
|
||||
| remainingCount | Integer | 更新后剩余待处理间数 |
|
||||
| targetType | String | 目标类型 |
|
||||
| targetOrderId | Long(string) | 目标订单 ID(可为 null) |
|
||||
| targetTeamNo | String | 目标订单团号(可为 null) |
|
||||
| targetGroupBatchId | Long(string) | 目标团期 ID(可为 null) |
|
||||
| targetBatchNo | String | 目标团期批次号(可为 null) |
|
||||
| handledAt | LocalDateTime | 处理时间 |
|
||||
| handlerName | String | 处理人姓名 |
|
||||
|
||||
(其余字段同 I-17 出参)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"targetType": "GROUP_BATCH",
|
||||
"targetGroupBatchId": "2105928497547640834",
|
||||
"roomCount": 2,
|
||||
"hotelConfirmNo": "HX20270513002",
|
||||
"proofFileIds": ["1007", "1008"],
|
||||
"remark": "退团户要求转给该团期同城"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105935060047953921",
|
||||
"status": "PENDING",
|
||||
"remainingCount": 1,
|
||||
"targetType": "GROUP_BATCH",
|
||||
"targetOrderId": null,
|
||||
"targetGroupBatchId": "2105928497547640834",
|
||||
"targetBatchNo": "T3-260928-01",
|
||||
"handledAt": "2026-10-02 14:30:20",
|
||||
"handlerName": "房务A"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据场景(成功返回处理后的行)。无降级;异常时返回 HTTP 500 + 500001。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808327,
|
||||
"message": "退团房所在团期当前阶段(招募中)不允许处理",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**可能的错误码**:
|
||||
|
||||
| 错误码 | 含义 | 触发条件 |
|
||||
|--------|------|---------|
|
||||
| 808320 | 转房记录不存在 | 行 ID 不存在或已软删 |
|
||||
| 808321 | 该房间已处理 | 行 status ≠ PENDING,或并发下被先抢 |
|
||||
| 808322 | 目标不可转入 | 不同城市、不同入住日期、无团号、或无转入空房 |
|
||||
| 808323 | 目标团期当前阶段({0})不能转入,仅资源准备中可转入 | 仅 GROUP 目标阶段不符时出现;{0} 为**目标**团期状态中文 |
|
||||
| 808324 | 转出间数超过剩余 {0} 间 | 转出间数 > 源计划剩余间数,或 > 目标缺口 |
|
||||
| 808325 | 请填写酒店确认号并上传凭证 | hotelConfirmNo 或 proofFileIds 缺失 |
|
||||
| 808326 | 只有原单处理人可以处理退团房间 | 操作人不是源单房务处理人 |
|
||||
| 808327 | 退团房所在团期当前阶段({0})不允许处理 | 源团期阶段不符(招募中、物料准备中、已出发等);{0} 为**源**团期状态中文 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 源团期只在「资源准备中」允许转出;「招募中」返回 808327;「物料准备中」或更后期阶段同样返回 808327,文案显示实际阶段中文。
|
||||
- 目标团期(仅 GROUP_BATCH)只在「资源准备中」允许转入;其他阶段返回 808323,文案显示**目标**阶段中文。
|
||||
- 目标团期不存在返回 808322(不走 808323)。
|
||||
- 转出后,如果 remainingCount=0 则行 status 变为 TRANSFERRED;否则保持 PENDING。
|
||||
- 幂等键为「操作人 + 行 ID + 转出摘要」,3 秒内重复提交相同目标和间数只记一次。
|
||||
|
||||
---
|
||||
|
||||
### 3. 向酒店取消 `POST /v3/admin/order/house-console/room-transfers/{id}/cancel-hotel`
|
||||
|
||||
**VO**: `HouseRoomTransferCancelReqVO → HouseRoomTransferRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务不再转房,直接向酒店通知取消,退款给客户。源团期处于资源准备中或招募中时均允许执行;其他阶段(物料准备中、已出发等)被拒。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long(string) | ✅ | 存在且 status=PENDING | 转房行 ID |
|
||||
| cancelFee | Body | BigDecimal | ✅ | ≥0,精确到 2 位小数 | 取消费用(元;0 表示免费取消) |
|
||||
| proofFileIds | Body | List[String] | ✅ | 非空列表 | 凭证文件 ID 列表(酒店回执) |
|
||||
| remark | Body | String | ❌ | - | 备注 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(string) | 转房行 ID(与入参相同) |
|
||||
| status | String | 更新后状态:CANCELLED |
|
||||
| cancelReason | String | 取消原因:HOTEL_CANCELLED(人工向酒店取消) |
|
||||
| cancelFee | BigDecimal(string) | 取消费用 |
|
||||
| handledAt | LocalDateTime | 处理时间 |
|
||||
| handlerName | String | 处理人姓名 |
|
||||
|
||||
(其余字段同 I-17 出参)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"cancelFee": "0.00",
|
||||
"proofFileIds": ["1007"],
|
||||
"remark": "酒店同意免费取消,按规则办理退订"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2105935060039565313",
|
||||
"status": "CANCELLED",
|
||||
"cancelReason": "HOTEL_CANCELLED",
|
||||
"cancelFee": "0.00",
|
||||
"handledAt": "2026-10-02 13:45:30",
|
||||
"handlerName": "房务A"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空数据场景(成功返回处理后的行)。无降级;异常时返回 HTTP 500 + 500001。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 808327,
|
||||
"message": "退团房所在团期当前阶段(物料准备中)不允许处理",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**可能的错误码**:
|
||||
|
||||
| 错误码 | 含义 | 触发条件 |
|
||||
|--------|------|---------|
|
||||
| 808320 | 转房记录不存在 | 行 ID 不存在或已软删 |
|
||||
| 808321 | 该房间已处理 | 行 status ≠ PENDING,或并发下被先抢 |
|
||||
| 808325 | 请填写酒店确认号并上传凭证 | proofFileIds 缺失或为空 |
|
||||
| 808326 | 只有原单处理人可以处理退团房间 | 操作人不是源单房务处理人 |
|
||||
| 808327 | 退团房所在团期当前阶段({0})不允许处理 | 源团期阶段不符(物料准备中、已出发等);{0} 为**源**团期状态中文 |
|
||||
|
||||
本接口不返回 808323(无目标概念)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 源团期在「资源准备中」或「招募中」均允许取消;「物料准备中」、「已出发」等阶段返回 808327。
|
||||
- 取消成功后,行 status 变为 CANCELLED,cancelReason=HOTEL_CANCELLED。
|
||||
- 源计划对应间数被软删(plan_deleted_at 标记);如有剩余间数,生成新计划记录「新间数=原间数-取消间数」。
|
||||
- 幂等键为「操作人 + 行 ID」,3 秒内重复提交只记一次。
|
||||
- 取消费用为 0 时表示酒店同意免费取消;>0 时表示需客户或预留从团费扣除。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 常规订单来源的行
|
||||
|
||||
常规单退团产生的转房行,sourceType=ORDER,sourceOrderId 是离团子单,teamNo 从 order_main 反查。
|
||||
|
||||
### 团期来源的行
|
||||
|
||||
团期成团过程中由房务释放的转房行,sourceType=GROUP_BATCH,sourceOrderId 为离团子单(可为 null),teamNo 恒为 null(团期无团号),sourceGroupBatchId + sourceBatchNo 标识来源团期。
|
||||
|
||||
### transferAllowed 与 transferBlockedReason 联用
|
||||
|
||||
前端根据 transferAllowed 控制转出按钮:
|
||||
- `true`:按钮可点,transferBlockedReason 为 null,不显示置灰提示;
|
||||
- `false` + `transferBlockedReason="所在团期招募中,仅可向酒店取消"`:转出禁用,显示该提示,保留取消按钮可用;
|
||||
- `false` + `transferBlockedReason` 为其他值(如"物料准备中"等):整行只读,两个按钮都禁用。
|
||||
|
||||
### 源/目标团期阶段判定
|
||||
|
||||
- **I-21 向酒店取消**:源团期 `RESOURCE_PREPARING` || `RECRUITING` 放行;其余返回 808327;
|
||||
- **I-20 转出**:仅源团期 `RESOURCE_PREPARING` 放行;其余返回 808327;目标团期(仅 GROUP_BATCH)仅 `RESOURCE_PREPARING` 放行,其余返回 808323;
|
||||
- **I-17 列表**:`transferAllowed` 与 `transferBlockedReason` 由业务侧编排返回(无存储),实时计算。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 无新增或删除字段;
|
||||
- 无表结构变更、无 Flyway。源团期阶段判定在服务层完成:源团期 `RECRUITING` 时 I-21 向酒店取消照常写 `house_room_transfer`(状态转 CANCELLED、原因 HOTEL_CANCELLED),I-20 转出在写库前即返回 808327、不落任何行;
|
||||
- I-17 列表行返回的 `transferAllowed` 与 `transferBlockedReason` 由业务侧实时计算,无存储(源团期状态通过 order_group_batch.batch_status 关联查询);
|
||||
- 操作日志(`house_operation_log`)记录处理人、操作类型、摘要,支持二期团期转房审计。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
### 6.1 业务边界
|
||||
|
||||
**I-17 列表**:
|
||||
- `transferAllowed=false` 时前端应禁用「转出」按钮,显示 `transferBlockedReason` 作置灰提示,但保留「向酒店取消」按钮可用;
|
||||
- `transferAllowed=true` 时 `transferBlockedReason` 恒为 null;前端可隐藏转出提示;
|
||||
- `readOnly=true` 时两个按钮都禁用,提示来自 `readOnlyReason`(源单由他人处理);此时 `transferAllowed` 同样为 false 但提示词不同;
|
||||
- 已处理行(status=TRANSFERRED / CANCELLED)虽然 `transferAllowed` 同样计算,但不作展示依据;前端按 `status` 判断是否展示操作区域。
|
||||
|
||||
**I-20 转出**:
|
||||
- 源团期只在「资源准备中」允许转出;「招募中」返回 808327;「物料准备中」或更后期阶段同样返回 808327,文案显示实际阶段中文;
|
||||
- 目标团期(仅 GROUP_BATCH)只在「资源准备中」允许转入;其他阶段返回 808323,文案显示**目标**阶段中文;
|
||||
- 目标团期不存在返回 808322(不走 808323);
|
||||
- 转出后,如果 remainingCount=0 则行 status 变为 TRANSFERRED;否则保持 PENDING;
|
||||
- 幂等键为「操作人 + 行 ID + 转出摘要」,3 秒内重复提交相同目标和间数只记一次。
|
||||
|
||||
**I-21 向酒店取消**:
|
||||
- 源团期在「资源准备中」或「招募中」均允许取消;「物料准备中」、「已出发」等阶段返回 808327;
|
||||
- 取消成功后,行 status 变为 CANCELLED,cancelReason=HOTEL_CANCELLED;
|
||||
- 源计划对应间数被软删(plan_deleted_at 标记);如有剩余间数,生成新计划记录「新间数=原间数-取消间数」;
|
||||
- 幂等键为「操作人 + 行 ID」,3 秒内重复提交只记一次;
|
||||
- 取消费用为 0 时表示酒店同意免费取消;>0 时需客户或从团费扣除;
|
||||
- 本接口不返回 808323(无目标概念)。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| I-21 源团期招募中 | 返回 808323「目标团期已确认」(误导) | 放开执行;操作成功 |
|
||||
| I-20 源团期招募中 | 返回 808323「目标团期已确认」 | 返回 808327「源团期阶段…招募中…不允许」(指向源侧) |
|
||||
| I-20 源团期物料准备中 | 返回 808323「已确认」 | 返回 808327 + 源团期实际阶段中文 |
|
||||
| I-17 行字段 | 无转出可行性标记 | 新增 transferAllowed + transferBlockedReason |
|
||||
| 错误码 808323 使用 | 同时用于源、目标阶段不符 | 仅用于 GROUP 目标阶段不符,文案明确含「目标」 |
|
||||
| 错误码 808327 | 无此码 | 新增,用于源团期阶段不符,文案明确含「源」与实际阶段 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
**前端**:
|
||||
- 需适配 I-17 行数据新增的两字段:transferAllowed 控制转出按钮状态,transferBlockedReason 作置灰提示文案;
|
||||
- I-20、I-21 错误码文案调整,需更新各 toast/提示的映射(808323 专用于「目标团期阶段」,808327 专用于「源团期阶段」);
|
||||
- 招募中场景下转出被拒(808327)与向酒店取消成功的对比体验,前端按新码分流处理。
|
||||
|
||||
**后端**:
|
||||
- 代码层修改集中在 HouseRoomTransferManager 业务判定与 HouseRoomTransferRespVO 返回值构造,无 DB 迁移;
|
||||
- 源团期状态 == 招募中时,I-21 放行、I-20 拦;由 GroupBatchStatus 枚举与 RECRUITING 常量驱动,存存逻辑一致;
|
||||
- 错误文案由 HouseConsoleErrorCode 808323 与 808327 的 `{0}` 占位符承载,无需前端约定新码段。
|
||||
|
||||
**回滚**:
|
||||
- PR revert 即可恢复旧逻辑(代码无迁移);新VO字段 transferAllowed / transferBlockedReason 前端如若忽视不显示,旧 UI 仍可用(字段补齐不减少现有消费)。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- I-18(修改期限)、I-19(候选目标)、I-22(异常检查)三个端点不受影响;
|
||||
- 历史转房行数据不回填新字段(列表行是实时计算,非持久化);
|
||||
- 常规单来源的转房行逻辑无变化(仅团期来源的招募中场景放开);
|
||||
- 其他团期阶段(资源准备中、物料准备中、已出发等)的行为不变。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署与验证**:
|
||||
- commit 704ecdd887;部署状态 `STATE=ok`;
|
||||
- 测试端点:network 路由验证 / HTTP 状态码验证 / 业务返回码验证。
|
||||
|
||||
**AC-4 通过**:I-21 源团期 RECRUITING 下取消成功
|
||||
- 前置:T5 团期先 RESOURCE_PREPARING 后成团变 RECRUITING;3 条 PENDING 行;
|
||||
- 请求:`POST /v3/admin/order/house-console/room-transfers/2105935060039565313/cancel-hotel` body `{cancelFee:"0.00", proofFileIds:[1007], ...}`;
|
||||
- 响应:HTTP 200,行 status=CANCELLED。
|
||||
|
||||
**AC-5 通过**:I-20 源团期 RECRUITING 下转出返回 808327
|
||||
- 前置:同上 T5 RECRUITING;
|
||||
- 请求:`POST /v3/admin/order/house-console/room-transfers/2105935060047953921/transfer` 转给常规单;
|
||||
- 响应:HTTP 200 code=808327 msg="退团房所在团期当前阶段(招募中)不允许处理"。
|
||||
|
||||
**AC-9 通过**:I-20 目标侧 808323 文案含目标团期阶段
|
||||
- 前置:源 T5 RESOURCE_PREPARING,目标 T3 RECRUITING;
|
||||
- 请求:`POST .../transfer` targetType=GROUP_BATCH;
|
||||
- 响应:HTTP 200 code=808323 msg="目标团期当前阶段(招募中)不能转入,仅资源准备中可转入"(文案含**目标**);
|
||||
- I-21 同源行同时刻调用返回 200(无目标概念,不返回 808323)。
|
||||
|
||||
**AC-10 通过**:列表行字段
|
||||
- RECRUITING 期间三行:transferAllowed=false,transferBlockedReason="所在团期招募中,仅可向酒店取消";
|
||||
- 重新成团回 RESOURCE_PREPARING 后:剩余两行 transferAllowed=true,transferBlockedReason=null。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 后端对接文档:`API-SPEC-HOUSE` v1.1.21 §12.10(I-20 转房)与 §12.11(I-21 取消);
|
||||
- 错误码说明:`HouseConsoleErrorCode` 808320-808327;
|
||||
- VO 定义:`HouseRoomTransferRespVO` / `HouseRoomTransferSaveReqVO` / `HouseRoomTransferCancelReqVO`;
|
||||
- 业务实现:`HouseRoomTransferManager` 与 `HouseRoomTransferQueryManager`。
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **Issue**: https://git.1814.love/wx/HL/issues/8711
|
||||
- **PR**: https://git.1814.love/wx/HL/pulls/8730
|
||||
- **部署日期**: 2026-10-02
|
||||
- **测试报告**: `D:/work2/_scratch/0928-house-proto/t20/bug-transfer-orphan/api-test/report.md`
|
||||
文件差异内容过多而无法显示
加载差异
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户