比较提交
509
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
d5941cb36c | ||
|
|
f49d448d1d | ||
|
|
41b7152d8d | ||
|
|
43cc42cecb | ||
|
|
1cb2149e08 | ||
|
|
301a05d5cb | ||
|
|
255427ca51 | ||
|
|
686a38cd2c | ||
|
|
c7e99515de | ||
|
|
2b37f6124b | ||
|
|
5954565ec5 | ||
|
|
ce13244c9e | ||
|
|
07dac023cf | ||
|
|
7dd6fc52c6 | ||
|
|
a43f11f767 | ||
|
|
13c0027073 | ||
|
|
dbc1737725 | ||
|
|
6e96521aa7 | ||
|
|
8f2f40c670 | ||
|
|
87c8da5c27 | ||
|
|
8ff0f5accc | ||
|
|
97e4adfdfe | ||
|
|
b3a59759a6 | ||
|
|
5b1659a048 | ||
|
|
3888c46f94 | ||
|
|
0a082cd3bb | ||
|
|
61f23b9190 | ||
|
|
20d782c02b | ||
|
|
b651f41f67 | ||
|
|
b89cb95b3b | ||
|
|
3de756a5f3 | ||
|
|
0dc29ef073 | ||
|
|
bc82060cab | ||
|
|
ccbe202637 | ||
|
|
77ba6986b1 | ||
|
|
edcf5a27ca | ||
|
|
6ed33870b9 | ||
|
|
1df4ac1a01 | ||
|
|
6d40278133 | ||
|
|
0ab6e12bbb | ||
|
|
7724cc0d3a | ||
|
|
d9d9026194 | ||
|
|
037748dcd2 | ||
|
|
1f77becb70 | ||
|
|
318917e6cf | ||
|
|
5aad9d2ed3 | ||
|
|
54d03ac956 | ||
|
|
63b70a9aec | ||
|
|
ed32d5bfc8 | ||
|
|
96797a8e93 | ||
|
|
b91e01f563 | ||
|
|
aea6b4091d | ||
|
|
f2c9864c02 | ||
|
|
f3b433a034 | ||
|
|
236f2517bd | ||
|
|
03d3a05116 | ||
|
|
0c5c848630 | ||
|
|
2a878856b8 | ||
|
|
fd0296c10f | ||
|
|
9bbf8efddb | ||
|
|
fb963d2133 | ||
|
|
31b047daf9 | ||
|
|
c7aec1217b | ||
|
|
09e2753f5b | ||
|
|
eaa784994d | ||
|
|
1815d4008f | ||
|
|
83cbc282aa | ||
|
|
532afb1c97 | ||
|
|
88d4cd5a8a | ||
|
|
d3568c4ec1 | ||
|
|
92549d7697 | ||
|
|
cbc05a9650 | ||
|
|
ab87358753 | ||
|
|
df0179375f | ||
|
|
be4f1ddd78 | ||
|
|
6f1945966e | ||
|
|
162c0ca6d8 | ||
|
|
0bd37e49aa | ||
|
|
e559045701 | ||
|
|
946290b259 | ||
|
|
93b4742dd7 | ||
|
|
bc0ae2c3f8 | ||
|
|
32374f6be7 | ||
|
|
2a01ff4c79 | ||
|
|
146576f3b3 | ||
|
|
7515b06fd9 | ||
|
|
d1b8a3ef15 | ||
|
|
849c4987dd | ||
|
|
67e50e3d1e | ||
|
|
6257f89d90 | ||
|
|
42a6367528 | ||
|
|
15f43e9cf9 | ||
|
|
c46f388641 | ||
|
|
31e2bc4fec | ||
|
|
68627376f1 | ||
|
|
ed8448c625 | ||
|
|
bddb222604 | ||
|
|
e11300080b | ||
|
|
d9ecc04a93 | ||
|
|
6d6ee1ced1 | ||
|
|
98094ec861 | ||
|
|
9e5b45e7a5 | ||
|
|
53b8c8b115 | ||
|
|
7d96056258 | ||
|
|
ed801356d9 | ||
|
|
81914a2244 | ||
|
|
dd53a9131c | ||
|
|
0458bbaa31 | ||
|
|
4cf844e182 | ||
|
|
57d49a7210 | ||
|
|
fab5a63922 | ||
|
|
7e9f743fe5 | ||
|
|
573ff6e2e3 | ||
|
|
d13fe33798 | ||
|
|
e0cefb1c07 | ||
|
|
f1d36b8522 | ||
|
|
c765f2b285 | ||
|
|
55944610fb | ||
|
|
f4999c9848 | ||
|
|
39988e5380 | ||
|
|
fe28a296e9 | ||
|
|
129c8eafd9 | ||
|
|
da656908cc | ||
|
|
5d65d2c241 | ||
|
|
f9634974c8 | ||
|
|
50c1cb9b20 | ||
|
|
cde332a840 | ||
|
|
0e32011617 | ||
|
|
985240047c | ||
|
|
53e36e9fdf | ||
|
|
7d68720e9b | ||
|
|
36178799e6 | ||
|
|
c005372652 | ||
|
|
faca68852a | ||
|
|
3de5eecbe7 | ||
|
|
eae2998409 | ||
|
|
10037c3266 | ||
|
|
ba20cd65b7 | ||
|
|
1a7103fed3 | ||
|
|
3506d7980c | ||
|
|
4b062a298a | ||
|
|
d9a38d433d | ||
|
|
4861573da1 | ||
|
|
e21f97ab92 | ||
|
|
34a657af60 | ||
|
|
e942c1f872 | ||
|
|
0c90b14bc5 | ||
|
|
0e1780b8b9 | ||
|
|
dc65788d5d | ||
|
|
6ccdd10866 | ||
|
|
c5d914d073 | ||
|
|
072645cc30 | ||
|
|
aff12f1333 | ||
|
|
28c627b8e1 | ||
|
|
234527639f | ||
|
|
8de86f6680 | ||
|
|
0b8d673b20 | ||
|
|
884d6a42b9 | ||
|
|
268ca6e116 | ||
|
|
e4c66f3fe0 | ||
|
|
d7a9137f08 | ||
|
|
7ee03a8399 | ||
|
|
34a65ef657 | ||
|
|
319751c781 | ||
|
|
ff9310192c | ||
|
|
ea9d7ed769 | ||
|
|
8941096a45 | ||
|
|
a4f4889e09 | ||
|
|
1cd17d2dfc | ||
|
|
b7b0e45f64 | ||
|
|
24fd4d7e3b | ||
|
|
83fcfd8eb3 | ||
|
|
eaf6c7f3cd | ||
|
|
8ad3ce4111 | ||
|
|
f7fbc5dacf | ||
|
|
4836fc9229 | ||
|
|
558425c4f8 | ||
|
|
c88cc66ffd | ||
|
|
98e35c6096 | ||
|
|
93aac89a3e | ||
|
|
037b82eaaa | ||
|
|
19ee4919ee | ||
|
|
f041ab68c9 | ||
|
|
1a12466b30 | ||
|
|
de1292823a | ||
|
|
ede5dd5c30 | ||
|
|
23d659a74b | ||
|
|
b9f588737e | ||
|
|
bc140004b6 | ||
|
|
e0be4ce0d0 | ||
|
|
8819ddbf24 | ||
|
|
69070e1d39 | ||
|
|
500f824518 | ||
|
|
6cbed28e94 | ||
|
|
799981cbe8 | ||
|
|
e27954e833 | ||
|
|
c8e3769e3f | ||
|
|
c1155e138d | ||
|
|
92f129c52c | ||
|
|
0277561bc3 | ||
|
|
c9be57e4b2 | ||
|
|
402e9a0444 | ||
|
|
9fedff29d7 | ||
|
|
f01e19f62c | ||
|
|
1d4ffe4ad3 | ||
|
|
2d52ce1513 | ||
|
|
49a9fb9fc9 | ||
|
|
5561ff5b26 | ||
|
|
50b65f0d41 | ||
|
|
996996c6f7 | ||
|
|
10340d0104 | ||
|
|
a3bc9a3e17 | ||
|
|
76dcf10531 | ||
|
|
3d9ec7021d | ||
|
|
b05004cebb | ||
|
|
71b96ad0d8 | ||
|
|
5b026fd920 | ||
|
|
e7cc66b833 | ||
|
|
a22e9c1ae3 | ||
|
|
597096129b | ||
|
|
d06d8ed6b4 | ||
|
|
9ec621d8ec | ||
|
|
e10e9a0b69 | ||
|
|
529ea1cbef | ||
|
|
805bd176f9 | ||
|
|
a9e26fa47b | ||
|
|
43d55d0a4d | ||
|
|
9965b110fb | ||
|
|
e6febaa22b | ||
|
|
ccb71ead90 | ||
|
|
cc06bb796a | ||
|
|
a6a18e9943 | ||
|
|
45e2af81f8 | ||
|
|
4b866a0371 | ||
|
|
3c32adb73e | ||
|
|
f85aa74eb5 | ||
|
|
3e00271082 | ||
|
|
b658d8671d | ||
|
|
a47eeeedcb | ||
|
|
bfa877f3db | ||
|
|
79468ab61f | ||
|
|
1e9f9ad881 | ||
|
|
b30dee3d22 | ||
|
|
b5cb91defd | ||
|
|
2a24c42ba5 | ||
|
|
7907e2aaa6 | ||
|
|
3f0c4037fd | ||
|
|
65d5980a7d | ||
|
|
a5c7d90b8d | ||
|
|
a63cadb822 | ||
|
|
c3060cc10a | ||
|
|
2f8156e0ff | ||
|
|
f0443f4368 | ||
|
|
01bb12416e | ||
|
|
75dea54f2f | ||
|
|
b661ff7e89 | ||
|
|
41acbe58bb | ||
|
|
9618b1f163 | ||
|
|
56be7eedae | ||
|
|
df6bee83b8 | ||
|
|
56c5498167 | ||
|
|
3d4e3967e7 | ||
|
|
ee75e0ca36 | ||
|
|
8144e7d197 | ||
|
|
3c50202413 | ||
|
|
8f9153e630 | ||
|
|
6e457dad42 | ||
|
|
d564ea71e7 | ||
|
|
1750cb69ab | ||
|
|
0df61a7b61 | ||
|
|
38960ac9d3 | ||
|
|
efa63eca69 | ||
|
|
fd965deb4f | ||
|
|
60350ddecf | ||
|
|
70f9e64dcb | ||
|
|
b52da8dd2f | ||
|
|
cbad66ecc7 | ||
|
|
a9185984e7 | ||
|
|
0e9a44568f | ||
|
|
2c00668800 | ||
|
|
26ff684c82 | ||
|
|
b1d70a9020 | ||
|
|
77a1c4a780 | ||
|
|
a6cc4feb93 | ||
|
|
63e3b0c964 | ||
|
|
7c93c5b89d | ||
|
|
d5a7d8ac61 | ||
|
|
41822427f7 | ||
|
|
4436a5061e | ||
|
|
9e8f405d62 | ||
|
|
9f5731aabb | ||
|
|
0182ffff67 | ||
|
|
80088ae7d1 | ||
|
|
fc1cf246c3 | ||
|
|
e747c7f334 | ||
|
|
28deca3a56 | ||
|
|
40c4c956cb | ||
|
|
f95ebe79f9 | ||
|
|
7e80f1371d | ||
|
|
a3153e9684 | ||
|
|
a2866a89cf | ||
|
|
43d69d38d1 | ||
|
|
c674675b44 | ||
|
|
1a7ce36aea | ||
|
|
d720016808 | ||
|
|
c948be3f1c | ||
|
|
bd14ac0d07 | ||
|
|
d553718b36 | ||
|
|
8c8edd364b | ||
|
|
a770215188 | ||
|
|
9b43fcc0d9 | ||
|
|
82080d7c4a | ||
|
|
a66f2acd86 | ||
|
|
52da3f6e7f | ||
|
|
f7313ec8e7 | ||
|
|
b86916bc8c | ||
|
|
d894fc3072 | ||
|
|
3c93927031 | ||
|
|
8ff78d13a7 | ||
|
|
8160c0c7e1 | ||
|
|
b23de77d92 | ||
|
|
76b2e4cc0b | ||
|
|
59932b6048 | ||
|
|
321154e5b1 | ||
|
|
9f1b885bfa | ||
|
|
20032ec600 | ||
|
|
208a8ace7d | ||
|
|
e41093b371 | ||
|
|
d4dbe654d3 | ||
|
|
3eac7ce2a7 | ||
|
|
ba2a6d16cd | ||
|
|
ea706e839a | ||
|
|
1f87918282 | ||
|
|
2c04507372 | ||
|
|
8ab53749fa | ||
|
|
ab83944191 | ||
|
|
a0c5c92ba4 | ||
|
|
6939e6d6b8 | ||
|
|
979ecdf8f1 | ||
|
|
2c0f1c3ee3 | ||
|
|
e9ae6a5ae8 | ||
|
|
fc47873ffa | ||
|
|
d621d37b5c | ||
|
|
362e104708 | ||
|
|
663b93a641 | ||
|
|
8a0d38493f | ||
|
|
49cd72a481 | ||
|
|
bc50894a09 | ||
|
|
05fa996c27 | ||
|
|
b538ff365c | ||
|
|
9bbb232afe | ||
|
|
5a6f8675b6 | ||
|
|
cee040f814 | ||
|
|
3df368d9c1 | ||
|
|
4b90d75330 | ||
|
|
a47582056d | ||
|
|
36ba58c0bb | ||
|
|
29ba67669a | ||
|
|
4b02859399 | ||
|
|
4620642b9f | ||
|
|
81f054d3a3 | ||
|
|
e4d3cda828 | ||
|
|
46401617c2 | ||
|
|
5af7b5b09c | ||
|
|
e71ce5cef6 | ||
|
|
a8f1e490db | ||
|
|
228a744dec | ||
|
|
ab1318cef0 | ||
|
|
c9f428dee6 | ||
|
|
987ae18e2f | ||
|
|
d7992de4c1 | ||
|
|
a2b1b821a5 | ||
|
|
409b3087cc | ||
|
|
c8e19dc870 | ||
|
|
3e02d420c4 | ||
|
|
f77b9740c3 | ||
|
|
0ab287215b | ||
|
|
f84deb7464 | ||
|
|
75be110438 | ||
|
|
09ac87b11c | ||
|
|
6bebad8b50 | ||
|
|
61ed527bf2 | ||
|
|
150e10f8b9 | ||
|
|
4fdf27bb6e | ||
|
|
fb9de38a5f | ||
|
|
f8d3db6f80 | ||
|
|
934f62f6da | ||
|
|
a6a11e2325 | ||
|
|
c11dac542b | ||
|
|
1954b38ccc | ||
|
|
ea1b16a68d | ||
|
|
ebcaf76a31 | ||
|
|
cf3c1bc722 | ||
|
|
489ef6f535 | ||
|
|
5ec7ea3277 | ||
|
|
68afb81766 | ||
|
|
618a6fa5bc | ||
|
|
1c5746a977 | ||
|
|
e7467fc9bb | ||
|
|
f23f62a5c4 | ||
|
|
2efa038750 | ||
|
|
56ee91cb60 | ||
|
|
0be57f0e61 | ||
|
|
f47e9fb458 | ||
|
|
05ed56a8aa | ||
|
|
ac303c9d49 | ||
|
|
09cfc2f644 | ||
|
|
9fb1d5a9fc | ||
|
|
ef1f72e195 | ||
|
|
f9199f75b9 | ||
|
|
e0dcf2e1af | ||
|
|
44cacffd9b | ||
|
|
38e1eaa76c | ||
|
|
1da3b00fba | ||
|
|
8fc78a8e7a | ||
|
|
5eb9fc1c6f | ||
|
|
809e2a1b40 | ||
|
|
c2dcf9efa4 | ||
|
|
213c2b40e0 | ||
|
|
17a6397a10 | ||
|
|
c5fbc558fe | ||
|
|
bc24709a5a | ||
|
|
384ecdf6f6 | ||
|
|
1179623e25 | ||
|
|
9dc0577cf8 | ||
|
|
790be50fa9 | ||
|
|
64c41a1aae | ||
|
|
e67da05974 | ||
|
|
ce61dbddea | ||
|
|
0cd5ee479e | ||
|
|
d29253481c | ||
|
|
808680ddde | ||
|
|
d4f83f6d6e | ||
|
|
51ddac08de | ||
|
|
1a326b0b12 | ||
|
|
1453fae332 | ||
|
|
71c06434ab | ||
|
|
04818b47d2 | ||
|
|
1061cef20d | ||
|
|
0456209b4e | ||
|
|
f061e1b7e0 | ||
|
|
2d7ffb0cb1 | ||
|
|
d934d7887f | ||
|
|
da0a49796a | ||
|
|
d5dc7e7854 | ||
|
|
221f48266b | ||
|
|
1baf0ae2b7 | ||
|
|
979b07dc8e | ||
|
|
d37df2feab | ||
|
|
7c33a382bc | ||
|
|
a7a92a4828 | ||
|
|
694c1c2ee6 | ||
|
|
d39345076e | ||
|
|
2106e5be43 | ||
|
|
d5f234455c | ||
|
|
459f5075a3 | ||
|
|
b54b294ed5 | ||
|
|
3fbd2a9fa8 | ||
|
|
b3f88c29fa | ||
|
|
7d133e4276 | ||
|
|
ec5758d597 | ||
|
|
2b79b32b70 | ||
|
|
aa399de161 | ||
|
|
a973ff3ec5 | ||
|
|
f38c79e150 | ||
|
|
a0946839d8 | ||
|
|
ab3770f9f9 | ||
|
|
4f91e2ff4e | ||
|
|
3a0da889ca | ||
|
|
0f88bf9f5a | ||
|
|
5d531063d9 | ||
|
|
2817285cf4 | ||
|
|
f3aacfe3c2 | ||
|
|
9889228594 | ||
|
|
1a9cab348a | ||
|
|
24e3181976 | ||
|
|
8c14545452 | ||
|
|
f2c5f91f90 | ||
|
|
43f840d3bf | ||
|
|
411833d156 | ||
|
|
29ac2669cb | ||
|
|
f78430a11f | ||
|
|
99cfc46cf8 | ||
|
|
45b074eac4 | ||
|
|
a3d9a5fe1a | ||
|
|
fca672aaf1 | ||
|
|
82f257b950 | ||
|
|
9b383becdf | ||
|
|
52b00bfda5 | ||
|
|
4d15ce2d14 | ||
|
|
80ae44d8f2 | ||
|
|
614c9a10e6 | ||
|
|
8b1b3cd1cb | ||
|
|
7b8c70f514 | ||
|
|
2322a0cd49 | ||
|
|
6ba07a470a | ||
|
|
0176a17de7 | ||
|
|
28f40db70e | ||
|
|
d3e7e69489 | ||
|
|
a38678b311 | ||
|
|
57a08dd935 | ||
|
|
5b73c99779 | ||
|
|
9066cfccde | ||
|
|
9a63a84f1a | ||
|
|
8398fd26a0 | ||
|
|
35054ebff9 | ||
|
|
c36a33ac59 | ||
|
|
4708a1f98e | ||
|
|
c59f59584e |
@@ -0,0 +1,73 @@
|
||||
# C 端用户自主取消订单(小程序端)
|
||||
|
||||
> 端类型:**小程序端**(v3)|变更类型:**新增接口**|服务:hl-mp-service(Feign → hl-order-service-v3)
|
||||
> Epic #3540 | PR #3549 |日期:2026-06-06
|
||||
|
||||
## 接口地址
|
||||
|
||||
`POST /mp/order/{orderId}/cancel`
|
||||
|
||||
## 接口介绍
|
||||
|
||||
小程序 C 端用户自主取消**自己**的订单。仅出行前可取消,按退款政策(距出发天数阶梯)退款,取消后订单进入 `CANCELLED` 终态、不可恢复。
|
||||
|
||||
## 本次修改点
|
||||
|
||||
新增接口。此前小程序无自主取消能力(取消只能走后台定制师)。本接口固定 `POLICY` 退款模式(C 端不暴露退款模式选择),并校验订单归属当前登录用户。
|
||||
|
||||
## 入参
|
||||
|
||||
- 路径参数:`orderId`(Long,订单 ID,必填)
|
||||
- 请求头:`Authorization`(登录 token,必填)
|
||||
- 请求体(可选,可不传):
|
||||
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| reason | String | 否 | 取消原因 |
|
||||
|
||||
**入参示例**
|
||||
```
|
||||
POST /mp/order/2063151085698134017/cancel
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{ "reason": "行程有变,需要取消" }
|
||||
```
|
||||
(body 也可整体不传。)
|
||||
|
||||
## 出参
|
||||
|
||||
`Result<Void>`
|
||||
|
||||
**出参示例**
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
## 可取消状态(调用约束)
|
||||
|
||||
| 订单状态 | 是否可取消 |
|
||||
|---|---|
|
||||
| PENDING_PAY 待支付 | ✅ |
|
||||
| CUSTOMIZING 定制中 | ✅ |
|
||||
| PENDING_DEPARTURE 待出行 | ✅ |
|
||||
| TRAVELLING 出行中 | ❌(行程中只能联系客服终止行程) |
|
||||
| COMPLETED 已完成 | ❌ |
|
||||
| CANCELLED 已取消 | ❌ |
|
||||
|
||||
- 退款模式固定 `POLICY`,按退款政策阶梯计算退款金额(已付订金时退款,未支付时无退款)。
|
||||
- 仅可取消归属当前登录用户的订单(非本人订单返回 581037)。
|
||||
|
||||
## 错误码
|
||||
|
||||
| code | message | 触发 |
|
||||
|---|---|---|
|
||||
| 581017 | 当前订单状态不允许取消 | 出行中 / 已完成 / 已取消订单调用 |
|
||||
| 581037 | 无权取消该订单 | 取消非本人(userId 不匹配)订单 |
|
||||
| 401 | 缺少有效的 Authorization 头 | 未携带 token |
|
||||
|
||||
## 关联
|
||||
|
||||
- Epic:https://git.1814.love:8443/wx/HL/issues/3540
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3549
|
||||
- 后端负责人:腰苏图(订单 v3)
|
||||
@@ -0,0 +1,33 @@
|
||||
# 🔴 安全热修:小程序相册接口横向越权(IDOR)修复 — 小程序
|
||||
|
||||
> 变更类型:安全修复(后端归属校验加固,**正常使用无影响**,无契约变更)
|
||||
> 端类型:小程序(相册)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-order-service-v2(相册域)
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3911
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
|
||||
`api-contract-audit` 审计小程序 BFF 时发现:相册的 4 个接口存在**横向越权(IDOR)**——后端收到当前用户 userId 后未做归属校验,任意登录用户改 URL 里的 orderId/folderId/albumFileId 即可读取/操作他人订单的相册。本次已修复:后端强制校验"相册所属订单归属当前登录用户",越权一律返回"不存在"。
|
||||
|
||||
涉及接口(小程序相册):
|
||||
- 订单文件夹列表、文件夹文件列表、文件下载地址获取、文件夹公开授权(写)。
|
||||
|
||||
## 前端影响:无需改动
|
||||
|
||||
- **正常使用完全不受影响**:用户访问自己订单的相册照常工作。
|
||||
- 仅"访问他人订单相册"这种越权请求会被拒(返回资源不存在)——前端正常逻辑不会触发。
|
||||
- 接口路径、入参、返回结构均未变。
|
||||
|
||||
> 如果前端有任何调试/测试代码用了非当前用户的 orderId/folderId 访问相册,请改为只访问当前用户自己的资源(之前能拿到是 bug,现已封堵)。
|
||||
|
||||
## 校验
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3911(squash 合并 dev-v3,via api-contract-audit workflow) |
|
||||
| 部署 | 已部署测试服,order-v2 双实例健康(滚动重启 uptime 刷新) |
|
||||
| 测试 | 相册模块单测全绿(新增越权拒绝反例:非本人订单/null-userId/订单缺失 + happy path + 防穿透 verify) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,85 @@
|
||||
# 小程序 BFF 接口契约审计修复(金额 String + 字段映射 + 可见性 + 缓存)— 修改接口 — 小程序
|
||||
|
||||
> 变更类型:⚠️ 多项修复(金额类型 / 字段补全 / 可见性收紧 / 缓存行为,多数前端正向受益)
|
||||
> 端类型:小程序(hl-mp-service BFF)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-mp-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3915
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
`api-contract-audit` 全量审计小程序 BFF 40 个核心 Controller,对抗复核实锤 100 项契约偏差。mp 是聚合层,本次修复 **mp BFF 自身的 67 处**;另有约 40 项根因在下游服务(订单/用户/资源服务),列在文末「待修清单」,会在后续审计中处理。已合并 dev-v3、部署测试服、双实例健康。
|
||||
|
||||
最影响前端的是第 1 节(金额→字符串)和第 2 节(原本恒 null 的字段现已修复)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 金额字段统一为 JSON 字符串
|
||||
|
||||
多个 mp 响应 VO 的金额(BigDecimal)此前输出成 JSON 数字,本次统一改为**字符串**(与平台口径一致)。涉及:退款(退款预览/进度/详情)、发票、订单详情/列表、票务、价格明细(含按人群/优惠/附加费的逐项明细)、保费、推荐产品起价等。
|
||||
|
||||
> 前端处理:相关金额字段一律按**字符串**接收;若做过数字运算需先转数值。非金额(里程/经纬度/评分)不变。
|
||||
|
||||
---
|
||||
|
||||
## 2. 原本恒为 null 的字段已修复(前端可正常取值)
|
||||
|
||||
以下字段此前因 BFF 与下游字段名不一致(反序列化失败)或 BFF 未填充而恒为 null,现已修复:
|
||||
|
||||
| 模块 | 字段 |
|
||||
|---|---|
|
||||
| 相册文件 | thumbnailUrl、fileName、fileSize、createdAt |
|
||||
| 相册订单 | coverUrl、fileCount |
|
||||
| 发票详情 | invoiceTitle、taxNumber、createdAt |
|
||||
| 合同 | 状态日志 *Label、出行人证件类型标签 |
|
||||
| 出行人 | race、roomGroupNo(订单出行人)、travelerTypeLabel |
|
||||
| 到达计划 | updateTime |
|
||||
|
||||
> 前端处理:这些字段现在会返回真实值,可直接使用(之前拿到的是 null)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 行为收紧(前端需配合)
|
||||
|
||||
### 3.1 下架资源详情不再返回(可见性收紧)
|
||||
景点 / 酒店 / 餐厅 / 活动 详情接口在 BFF 层补了「是否上架」校验,**下架(status=0)的资源详情不再返回**给小程序(之前会原样返回)。前端访问已下架资源详情会得到「不存在/不可见」,请正常处理空态。
|
||||
|
||||
### 3.2 浏览量统计 / 错误不再被缓存
|
||||
- 探索/百科详情的「浏览量+1」此前在缓存命中时不执行(统计失真),现已修复(浏览量正确累加)。
|
||||
- BFF 缓存不再缓存下游降级错误(避免 TTL 内持续返回陈旧错误)。
|
||||
- 前端无感,仅数据更准确。
|
||||
|
||||
### 3.3 定制需求状态字典修正
|
||||
定制(/mp/custom)相关接口的状态字典此前文档值有误,实际枚举为 `PENDING/PROCESSING/REPLIED/CONVERTED/CANCELLED`,请前端按此映射。
|
||||
|
||||
---
|
||||
|
||||
## 4. 待修清单(根因在下游服务,本次未修,后续处理)
|
||||
|
||||
以下问题审计已发现,但根因在下游服务,不在本 PR;记录在此供前端知晓(暂仍按现状):
|
||||
|
||||
- **订单服务(order-v2)相关 ~21 项**:部分字段缺失/状态机/错误码等(其中相册 4 个越权 P0 已由 PR #3911 安全热修修复)。
|
||||
- **用户服务相关 ~11 项**:出行人默认删除守卫、token 轮转、资料完善校验、足迹 HOTEL 等 — 将在用户服务审计时在源头修复。
|
||||
- **资源服务相关 ~3 项**:百科推荐排序、足迹等 — 将在资源服务审计时修复。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端 Action 清单
|
||||
|
||||
1. 第 1 节金额字段按字符串解析。
|
||||
2. 第 2 节字段现在有值,可接入使用。
|
||||
3. 下架资源详情做空态处理(第 3.1)。
|
||||
4. 定制状态映射用真实枚举(第 3.3)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3915(squash 合并 dev-v3,via api-contract-audit + mp-internal-fix workflow) |
|
||||
| 部署 | 已部署测试服,mp 双实例健康(滚动重启 uptime 刷新);公开端点 /mp/banner/active 等返 code:200 |
|
||||
| 测试 | 963 单测,本次改动 add 0 新失败(另有 3 个 dev-v3 预存红与本次无关,已单独反馈后端) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,37 @@
|
||||
# 用户服务接口契约审计修复(mp 侧字段/契约对齐)— 修改接口 — 小程序
|
||||
|
||||
> 变更类型:字段/契约对齐(无破坏性删除),小程序侧影响较小
|
||||
> 端类型:小程序(用户中心/首页配置/收藏足迹/出行人 OCR)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-user-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3924
|
||||
> 说明:本文件随 user-service 契约审计一并产出,**暂缓推送**(与小程序侧统一节奏)。admin 侧见 changelogs-v2/2026-06/17_3924_*.md。
|
||||
|
||||
---
|
||||
|
||||
## 关键说明
|
||||
|
||||
用接口契约语义审计工作流扫描 hl-user-service 全部 Controller,mp(小程序 BFF)侧修复一批字段名/null 语义/响应结构与 Swagger 契约不符项。已合并 dev-v3、部署测试服、本地全量 2793 单测零新增回归。下面列 mp 侧对接相关变更。
|
||||
|
||||
---
|
||||
|
||||
## 1. 收藏 / 足迹(FavoriteRespVO / FootprintRespVO)
|
||||
|
||||
- 响应 VO 字段名/缺字段与契约对齐;金额类字段(如有)补 `@JsonSerialize(ToStringSerializer)` 输出字符串。
|
||||
- 字段语义不变,前端按现有字段名接收即可;如此前对金额字段做数字运算,改为先转数值。
|
||||
|
||||
## 2. 首页配置(MpHomeConfig / MpHomeScreen2VO)
|
||||
|
||||
- 响应结构与 Swagger 契约对齐(字段名/层级稳定化),无破坏性删除。
|
||||
|
||||
## 3. 出行人 OCR / 内容安全(MpTravelerOcr / MpContentSecurity)
|
||||
|
||||
- 入参校验(`@Valid` / 校验分组)与错误码规范化;正常路径返回结构不变。
|
||||
|
||||
## 4. 站内信(InternalMpMessage / 未读数 UnreadCountRespVO)
|
||||
|
||||
- 未读数等响应改强类型 VO,字段名不变。
|
||||
|
||||
---
|
||||
|
||||
> 待小程序侧统一接线时一并对照本文件;如需提前对接某一节请单独知会。
|
||||
@@ -0,0 +1,227 @@
|
||||
# 【修改接口·小程序端】✨ 到达计划出行人类型中文名 travelerTypeName 新增字段 (#3951)
|
||||
|
||||
> **PR**: #3954 | **服务**: hl-order-service-v3 / hl-mp-service | **更新时间**: 2026-06-18
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序到达计划接口返回的出行人对象(ArrivalPlanTravelerSimpleVO)原先只包含 `travelerType` 英文枚举值,前端展示出行人类型标签时需自行维护一套映射表。本次新增 `travelerTypeName` 字段,由后端查数据字典 `traveler_type` 派生中文名直接下发,小程序侧可零配置展示类型标签。原 `travelerType` 字段保留不变,属纯新增、非破坏性变更。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 获取到达计划(一期路径) | GET | /mp/order/{orderId}/arrival | 新增出参字段 | 出行人对象新增 `travelerTypeName` |
|
||||
| 2 | 获取到达计划(v3 路径) | GET | /mp/v3/order/arrival/{orderId} | 新增出参字段 | 出行人对象新增 `travelerTypeName` |
|
||||
|
||||
> 以上两个路径均经网关路由至 hl-mp-service,数据源为 order-v3 侧透传。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 获取到达计划(一期路径)
|
||||
|
||||
- **使用场景**:小程序出行人填写或出行信息确认页,加载出行人到达计划详情。
|
||||
- **认证**:需要微信登录态 JWT(C 端 token)。
|
||||
- **幂等性**:是(只读)。
|
||||
- **限流**:无。
|
||||
|
||||
入参无变化。出参中 `travelers[]` 数组每个 `ArrivalPlanTravelerSimpleVO` 元素新增 `travelerTypeName` 字段(String)。
|
||||
|
||||
### 3.2 获取到达计划(v3 路径)
|
||||
|
||||
- **使用场景**:同上,v3 版本接口路径,功能与 3.1 等价。
|
||||
- **认证**:需要微信登录态 JWT(C 端 token)。
|
||||
- **幂等性**:是(只读)。
|
||||
- **限流**:无。
|
||||
|
||||
入参无变化。出参中 `travelers[]` 数组每个 `ArrivalPlanTravelerSimpleVO` 元素新增 `travelerTypeName` 字段(String)。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 接口 | 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| GET /mp/order/{orderId}/arrival | orderId | String(Long) | 是 | 路径参数,订单 ID |
|
||||
| GET /mp/v3/order/arrival/{orderId} | orderId | String(Long) | 是 | 路径参数,订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
均为 GET 接口,无请求体。入参无变化。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 ArrivalPlanTravelerSimpleVO 字段
|
||||
|
||||
| 字段 | 类型 | 变更 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | String | 不变 | 出行人记录 ID |
|
||||
| name | String | 不变 | 出行人姓名(脱敏后) |
|
||||
| travelerType | String | 不变 | 出行人类型枚举值,见 §6 |
|
||||
| travelerTypeName | String | **新增** | 出行人类型中文名,由数据字典 `traveler_type` 派生 |
|
||||
|
||||
> 其余字段视接口版本可能含到达信息、证件信息等,本次仅新增 `travelerTypeName`,其余字段不变。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 travelerType(数据字典:traveler_type)
|
||||
|
||||
**所属字段**:`travelerType`(出参,保留不变)与 `travelerTypeName`(出参,新增中文名) | **类型**:`String`
|
||||
|
||||
| 枚举值 | 中文名(travelerTypeName) | 说明 |
|
||||
|--------|--------------------------|------|
|
||||
| `ADULT` | 成人 | 成年旅客 |
|
||||
| `CHILD` | 儿童 | 儿童旅客(含独立占位) |
|
||||
| `YOUNG_CHILD` | 小童 | 小童旅客(不占位或半占位) |
|
||||
| `BABY` | 幼童 | 婴幼儿(不占位) |
|
||||
|
||||
> 字典降级说明:若数据字典 `traveler_type` 中对应 key 缺失,后端使用枚举内置 label 兜底,小程序侧无需处理。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次为纯新增字段,无新增错误码。原有错误码不变。
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 581201 | 订单不存在 | orderId 无效 |
|
||||
| 401 | 未认证 | 未携带或微信 JWT 过期 |
|
||||
| 403 | 无权限 | 当前用户无权查看该订单的到达计划 |
|
||||
|
||||
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||
|
||||
### 8.1 典型成功 — 获取到达计划(含新字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /mp/v3/order/arrival/2067178767255560193
|
||||
Authorization: Bearer <mp-token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": "2067178767255560193",
|
||||
"travelers": [
|
||||
{
|
||||
"id": "2067178767255560201",
|
||||
"name": "张*明",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人"
|
||||
},
|
||||
{
|
||||
"id": "2067178767255560202",
|
||||
"name": "张*",
|
||||
"travelerType": "YOUNG_CHILD",
|
||||
"travelerTypeName": "小童"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "ok",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况 — 仅含 BABY 类型出行人
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /mp/order/2067178767255560194/arrival
|
||||
Authorization: Bearer <mp-token>
|
||||
```
|
||||
|
||||
响应(含 BABY 类型,travelerTypeName 正常返回):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": "2067178767255560194",
|
||||
"travelers": [
|
||||
{
|
||||
"id": "2067178767255560211",
|
||||
"name": "李*强",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人"
|
||||
},
|
||||
{
|
||||
"id": "2067178767255560212",
|
||||
"name": "李小宝",
|
||||
"travelerType": "BABY",
|
||||
"travelerTypeName": "幼童"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "ok",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败 — 订单不存在
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /mp/v3/order/arrival/9999999999999999999
|
||||
Authorization: Bearer <mp-token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581201,
|
||||
"data": null,
|
||||
"message": "订单不存在",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 适用:用户已登录且有权限访问该订单,处于任何订单状态均可查询到达计划(只读接口)。
|
||||
- 适用:四种出行人类型(ADULT / CHILD / YOUNG_CHILD / BABY)均有对应 `travelerTypeName` 中文名。
|
||||
- 特殊边界:若数据字典维护缺失某枚举值,`travelerTypeName` 降级返回枚举内置中文名,不会返回 null,小程序无需做 null 保护。
|
||||
- 特殊边界:`travelerType` 原字段值不变,若小程序已有本地映射逻辑,可继续保留或切换为直接展示 `travelerTypeName`,两者等价。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| VO | 字段 | 改前 | 改后 |
|
||||
|----|------|------|------|
|
||||
| ArrivalPlanTravelerSimpleVO | travelerTypeName | 不存在 | **新增** String,出行人类型中文名 |
|
||||
| ArrivalPlanTravelerSimpleVO | travelerType | 原样返回英文枚举值 | 保留不变 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 出行人类型展示 | 小程序自行维护 ADULT→成人 等映射表 | 后端直接下发 travelerTypeName,小程序可直接渲染 |
|
||||
| 字典缺失兜底 | 无 | 降级用枚举内置 label,小程序无感知 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**否**,纯新增字段,原字段不变、原结构不变。
|
||||
- **前端是否必须同步上线**:**否**,旧小程序代码不读新字段也不会出错,可按需对接。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #3954 并重新部署 hl-order-service-v3 及 hl-mp-service,返回字段恢复为无 travelerTypeName 的旧结构。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 前端 workaround 清理点:若小程序已有本地 `travelerType → 中文` 映射对象/函数,上线后可切换为直接读取 `travelerTypeName`,原映射逻辑可清理。
|
||||
- `travelerTypeName` 由后端数据字典派生,字典修改后立即生效(无需小程序发版),字典当前值为:ADULT=成人 / CHILD=儿童 / YOUNG_CHILD=小童 / BABY=幼童。
|
||||
- `/mp/order/{orderId}/arrival`(一期路径)与 `/mp/v3/order/arrival/{orderId}`(v3 路径)行为一致,都已包含新字段,小程序按当前接入的路径对接即可。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#3951](https://git.1814.love:8443/wx/HL/issues/3951)
|
||||
- **PR**: [#3954](https://git.1814.love:8443/wx/HL/pulls/3954)
|
||||
- **Merge commit**: [16d77c9a5](https://git.1814.love:8443/wx/HL/commit/16d77c9a5)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,316 @@
|
||||
# 【修改接口·小程序端】出行人类型改为由出生日期自动派生,birthday 必填、travelerType 入参移除 (#3976)
|
||||
|
||||
> **PR**: #3981 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-18
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序客户出行人补全接口(batch-edit)原先要求客户端传入 `travelerType`(出行人类型枚举值)。本次调整将出行人类型的计算权收归后端:**客户端只需传 `birthday`(出生日期),后端按年龄段自动派生 `travelerType`**,入参不再接受 `travelerType` 字段(传了也会被忽略)。
|
||||
|
||||
`birthday` 同步从选填升级为**必填**,不传报 400。响应 VO 不变,`travelerType` / `travelerTypeName` 仍正常返回,展示层无需改动。
|
||||
|
||||
> 本次为破坏性入参变更:移除 `travelerType` 入参 + `birthday` 改必填。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 客户出行人补全 | POST | /v3/internal/mp/order/{id}/traveler/batch-edit | 入参破坏性变更 | 出行人对象移除 `travelerType`,`birthday` 改必填 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 客户出行人补全(POST /v3/internal/mp/order/{id}/traveler/batch-edit)
|
||||
|
||||
- **使用场景**:小程序用户自助补全出行信息时,批量 upsert 出行人列表(id=null 新增,id 有值则更新)。
|
||||
- **认证**:需要微信登录态 JWT(C 端 token)。
|
||||
- **幂等性**:否(写入操作)。
|
||||
- **限流**:无。
|
||||
|
||||
入参中每个出行人对象移除 `travelerType` 字段,`birthday` 改为必填。响应 VO 不变。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | String(Long) | 是 | 路径参数,订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
请求体外层结构示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"travelers": [{}]
|
||||
}
|
||||
```
|
||||
|
||||
**出行人对象字段表**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | String(Long) | 否 | null 或不传=新增;有值=更新已有出行人 |
|
||||
| name | String | 否 | 出行人姓名 |
|
||||
| gender | String | 否 | 性别字典码(1=男 / 2=女 / 0=未知) |
|
||||
| birthday | String | **是** | 出生日期,格式 yyyy-MM-dd,不能晚于今天。**本次改为必填** |
|
||||
| idType | String | 否 | 证件类型枚举值,见第 6 节 |
|
||||
| idNo | String | 否 | 证件号码(明文) |
|
||||
| nationality | String | 否 | 国籍 |
|
||||
| race | String | 否 | 民族 |
|
||||
| phone | String | 否 | 手机号 |
|
||||
| emergencyContact | String | 否 | 紧急联系人 |
|
||||
| emergencyPhone | String | 否 | 紧急联系人电话 |
|
||||
| roomGroupNo | Integer | 否 | 房间分组编号(团期订单使用) |
|
||||
| travelerType(已移除) | — | — | 入参已移除,传入将被忽略;后端按 birthday 自动派生 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
响应 VO 结构**不变**,`travelerType` 和 `travelerTypeName` 仍正常返回。
|
||||
|
||||
### 5.1 响应列表元素(TravelerVO 关键字段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 出行人记录 ID |
|
||||
| orderId | String | 所属订单 ID |
|
||||
| travelerType | String | 出行人类型枚举值(后端由 birthday 派生后返回) |
|
||||
| travelerTypeName | String | 出行人类型中文名(成人 / 儿童 / 小童 / 幼童) |
|
||||
| name | String | 出行人姓名 |
|
||||
| birthday | String | 出生日期(yyyy-MM-dd) |
|
||||
| idType | String | 证件类型枚举值 |
|
||||
| idTypeName | String | 证件类型中文名 |
|
||||
| idCardMasked | String | 证件号(脱敏后) |
|
||||
| gender | String | 性别字典码 |
|
||||
| nationality | String | 国籍 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 travelerType 派生规则(后端自动计算,前端仅需理解出参含义)
|
||||
|
||||
| 年龄段(按 birthday 计算) | travelerType 枚举值 | travelerTypeName |
|
||||
|--------------------------|-------------------|-----------------|
|
||||
| 0-1 岁(未满 2 周岁) | BABY | 幼童 |
|
||||
| 2-6 岁(未满 7 周岁) | YOUNG_CHILD | 小童 |
|
||||
| 7-17 岁(未满 18 周岁) | CHILD | 儿童 |
|
||||
| 18 岁及以上 | ADULT | 成人 |
|
||||
|
||||
### 6.2 idType(证件类型字典:id_card_type)
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| ID_CARD | 身份证 | 中国居民身份证 |
|
||||
| PASSPORT | 护照 | 中外护照 |
|
||||
| BIRTH_CERT | 出生证明 | 婴幼儿出生医学证明 |
|
||||
| HK_MACAU | 港澳通行证 | 港澳居民来往内地通行证 |
|
||||
| TAIWAN | 台胞证 | 台湾居民来往大陆通行证 |
|
||||
| MILITARY | 军官证 | 军人证件 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 400 | 出生日期不能为空 | 出行人对象未传 birthday 或传 null |
|
||||
| 400 | 出生日期不能晚于今天 | birthday 传入了未来日期 |
|
||||
| 581201 | 订单不存在 | orderId 无效 |
|
||||
| 581200 | 出行人不存在 | 传了无效的出行人 id(更新场景) |
|
||||
| 401 | 未认证 | 未携带或微信 JWT 过期 |
|
||||
| 403 | 无权限 | 当前用户无权操作该订单的出行人 |
|
||||
|
||||
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||
|
||||
### 8.1 典型成功 — 补全两位出行人(成人 + 儿童,含 birthday,不传 travelerType)
|
||||
|
||||
请求:POST /v3/internal/mp/order/2067178767255560193/traveler/batch-edit,Authorization: Bearer <mp-token>
|
||||
|
||||
请求体示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"travelers": [
|
||||
{
|
||||
"id": null,
|
||||
"name": "张三",
|
||||
"gender": "1",
|
||||
"birthday": "1990-05-20",
|
||||
"idType": "ID_CARD",
|
||||
"idNo": "110101199005201234"
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"name": "张小宝",
|
||||
"gender": "1",
|
||||
"birthday": "2019-08-10",
|
||||
"idType": "BIRTH_CERT",
|
||||
"idNo": "P110101201908101234"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": "2067178767255560201",
|
||||
"orderId": "2067178767255560193",
|
||||
"travelerType": "ADULT",
|
||||
"travelerTypeName": "成人",
|
||||
"name": "张三",
|
||||
"birthday": "1990-05-20",
|
||||
"idType": "ID_CARD",
|
||||
"idTypeName": "身份证",
|
||||
"idCardMasked": "110***********1234",
|
||||
"gender": "1"
|
||||
},
|
||||
{
|
||||
"id": "2067178767255560202",
|
||||
"orderId": "2067178767255560193",
|
||||
"travelerType": "CHILD",
|
||||
"travelerTypeName": "儿童",
|
||||
"name": "张小宝",
|
||||
"birthday": "2019-08-10",
|
||||
"idType": "BIRTH_CERT",
|
||||
"idTypeName": "出生证明",
|
||||
"idCardMasked": "P1101012019****1234",
|
||||
"gender": "1"
|
||||
}
|
||||
],
|
||||
"message": "ok",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况 — 幼童(birthday 未满 2 周岁,派生 BABY)
|
||||
|
||||
请求体示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"travelers": [
|
||||
{
|
||||
"id": null,
|
||||
"name": "李小婴",
|
||||
"gender": "2",
|
||||
"birthday": "2025-06-01",
|
||||
"idType": "BIRTH_CERT",
|
||||
"idNo": "P110101202506011234"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": "2067178767255560210",
|
||||
"orderId": "2067178767255560193",
|
||||
"travelerType": "BABY",
|
||||
"travelerTypeName": "幼童",
|
||||
"name": "李小婴",
|
||||
"birthday": "2025-06-01",
|
||||
"idType": "BIRTH_CERT",
|
||||
"idTypeName": "出生证明",
|
||||
"idCardMasked": "P1101012025****1234",
|
||||
"gender": "2"
|
||||
}
|
||||
],
|
||||
"message": "ok",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败 — birthday 缺失,报出生日期不能为空
|
||||
|
||||
请求体示例(缺少 birthday):
|
||||
|
||||
```json
|
||||
{
|
||||
"travelers": [
|
||||
{
|
||||
"name": "王五",
|
||||
"gender": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"data": null,
|
||||
"message": "出生日期不能为空",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 适用:用户已登录且有权限操作该订单,且订单处于可编辑出行人的状态时可调用。
|
||||
- 不适用:订单已完成(COMPLETED)或已取消(CANCELLED)后,出行人信息不可再写入。
|
||||
- 特殊边界:birthday 为整周岁当天(如刚满 2 岁、7 岁、18 岁生日当天)时,当天即按新档位计算(满龄升档)。
|
||||
- 特殊边界:如果小程序端表单中仍有 travelerType 字段的存储或传入,不影响功能(后端忽略),建议同步清理。
|
||||
- 特殊边界:birthday 只允许不晚于今天的日期,传入未来日期(包括明天)报 400。
|
||||
- 特殊边界:birthday 格式须为 yyyy-MM-dd,格式错误报 400。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比(入参)
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| travelerType(入参) | 选填,由客户端传入,控制出行人类型 | 已移除,传入被忽略 |
|
||||
| birthday(入参) | 选填 | 必填,不传报 400 |
|
||||
|
||||
### 10.2 字段级对比(出参,不变)
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| travelerType(出参) | 返回客户端传入值 | 返回后端按 birthday 派生值(语义不变) |
|
||||
| travelerTypeName(出参) | 返回中文名 | 不变 |
|
||||
| birthday(出参) | 原样返回 | 不变 |
|
||||
|
||||
### 10.3 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 出行人类型来源 | 客户端传入 travelerType,后端直接存储 | 后端按 birthday 自动派生,客户端无需传 |
|
||||
| birthday 必填性 | 选填,不传不报错 | 必填,不传报 400 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:是,birthday 改必填属破坏性变更。旧版小程序表单若未传 birthday,保存时报 400。
|
||||
- **小程序是否必须同步上线**:是。调用 batch-edit 接口时,必须确保每个出行人对象带 birthday;原有 travelerType 入参代码可同步清理。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #3981 并重新部署 hl-order-service-v3,恢复 travelerType 入参有效 + birthday 选填的旧行为。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 出行人类型选择器如果仅用于控制 travelerType 入参,本次可直接移除,出行人类型由 birthday 派生后在响应中正常回显,展示无需调整。
|
||||
- birthday 格式严格为 yyyy-MM-dd(如 1990-05-20),不接受时间戳或其他格式。
|
||||
- 响应中的 travelerType / travelerTypeName 仍正常返回,出行人卡片上的类型标签展示逻辑无需改动。
|
||||
- 年龄以服务器当天日期(北京时间)计算,生日当天视为满周岁。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#3976](https://git.1814.love:8443/wx/HL/issues/3976)
|
||||
- **PR**: [#3981](https://git.1814.love:8443/wx/HL/pulls/3981)
|
||||
- **Merge commit**: [8209c5702](https://git.1814.love:8443/wx/HL/commit/8209c5702)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,44 @@
|
||||
# 【修改接口·小程序端】到达计划自驾时段 selfDrivePeriodLabel 文案口径含时间范围
|
||||
|
||||
> **PR**: #4061 | **服务**: hl-order-service-v3(经 mp-service 透传)| **更新时间**: 2026-06-19
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
大交通枚举改造(#4061)统一了自驾时段 `SelfDrivePeriod` 枚举的中文 label 口径,含时间范围。到达计划(mp)出参 `ArrivalPlanRespVO.selfDrivePeriodLabel` 随之变化。前端直接显示该 label,**无需改代码**,只是显示文案更详细了。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 到达计划查询 | GET | /v3/internal/mp/order/arrival/{orderId} | 出参 selfDrivePeriodLabel 文案口径变化 |
|
||||
|
||||
(同枚举也用于 mp 创建/更新/批量接口的回显出参)
|
||||
|
||||
## 3. 出参变化
|
||||
|
||||
| 字段 | 类型 | 变更 | 说明 |
|
||||
|---|---|---|---|
|
||||
| selfDrivePeriodLabel | String | 文案口径调整 | 自驾时段中文,由「上午/下午/晚上」改为含时间范围 |
|
||||
|
||||
## 4. 枚举 / 数据字典
|
||||
|
||||
| selfDrivePeriod 码值 | 改前 label | 改后 label |
|
||||
|---|---|---|
|
||||
| MORNING | 上午 | **上午(06:00-12:00)** |
|
||||
| AFTERNOON | 下午 | **下午(12:00-18:00)** |
|
||||
| EVENING | 晚上 | **晚上(18:00-24:00)** |
|
||||
|
||||
## 5. 影响评估
|
||||
|
||||
- **破坏向后兼容**:否。`selfDrivePeriod` 码值不变,仅 `selfDrivePeriodLabel` 展示文案更详细。
|
||||
- **前端是否需改**:否,前端直接展示 label 即可(显示内容自动变详细)。**例外**:若前端有按 label 文案做精确匹配/判断的逻辑(不推荐),需改为按 `selfDrivePeriod` 码值判断。
|
||||
|
||||
## 6. 注意事项
|
||||
|
||||
- 自驾时段判断逻辑请用 `selfDrivePeriod` 码值(MORNING/AFTERNOON/EVENING),不要依赖 `selfDrivePeriodLabel` 文案。
|
||||
|
||||
## 7. 关联 / 联系人
|
||||
|
||||
- **Issue**: [#4058](https://git.1814.love:8443/wx/HL/issues/4058)
|
||||
- **PR**: [#4061](https://git.1814.love:8443/wx/HL/pulls/4061)
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,65 @@
|
||||
# 【修改接口·小程序端】客户出行人补全:订单确认后禁止 mp 端编辑(新增门禁 + 错误码 581145)(CR)
|
||||
|
||||
> **PR**: #4154 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-21
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序客户出行人补全接口(batch-edit)此前**未在后端强制限制可编辑的订单阶段**:客户端只要订单属于本人,任意阶段都能提交编辑。本次安全/一致性收紧:**仅订单确认前(待支付 / 定制中)允许经小程序编辑出行人;订单确认后(待出行起)及已完成 / 已取消一律拒绝**,返回新错误码 `581145`。
|
||||
|
||||
收紧原因:订单确认后修改出行人 PII(姓名 / 手机 / 生日等)会误触发后端「合同作废重签 + 退保重投保」链路,造成非预期的合同与保单churn。证件号此前已在签约后冻结,本次把整个编辑动作按订单阶段收口。
|
||||
|
||||
> 本次为行为收紧(破坏性变更面向「确认后仍调用」的旧逻辑)。按产品生命周期,出行人补全本就发生在「待补全信息」窗口(定制中阶段),正常流程不受影响。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 客户出行人补全 | POST | /v3/internal/mp/order/{id}/traveler/batch-edit | 行为收紧 | 订单非「待支付/定制中」阶段调用直接拒绝(581145);入参 / 出参结构不变 |
|
||||
|
||||
## 3. 关键说明
|
||||
|
||||
- **放行阶段(白名单)**:`order_status ∈ {PENDING_PAY 待支付, CUSTOMIZING 定制中}`。
|
||||
- **拒绝阶段**:`PENDING_DEPARTURE 待出行(确认后)/ TRAVELLING 出行中 / COMPLETED 已完成 / CANCELLED 已取消`,以及任何其他状态(白名单 fail-closed)。
|
||||
- 拒绝时**整单不写入**(事务回滚),返回 `code=581145`。
|
||||
- 入参字段、出参 VO 结构**完全不变**,仅新增前置阶段校验。
|
||||
- 本门禁**只约束小程序(mp)路径**;定制师 / admin 后台路径不受限(如需确认后变更出行人,请走定制师后台)。
|
||||
|
||||
## 4. 入参 / 出参
|
||||
|
||||
入参(请求体 travelers[] + 路径 id)与响应 VO **均不变**,详见既有文档(如 #3976 出行人补全契约)。本次仅在订单阶段不满足时提前抛 581145。
|
||||
|
||||
## 5. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 | 本次 |
|
||||
|------|------|----------|------|
|
||||
| **581145** | 订单已确认,出行人信息不可再经小程序修改,如需变更请联系定制师 | 订单 status 不在 {待支付, 定制中} 白名单(即确认后 / 已完成 / 已取消)时调用 batch-edit | **新增** |
|
||||
| 581102 | 订单不存在,无法编辑出行人 | orderId 无效 | 不变 |
|
||||
| 581122 | 订单不属于当前用户 | 当前登录 userId 与订单归属不一致(横向越权拦截) | 不变 |
|
||||
|
||||
## 6. 前端处理建议
|
||||
|
||||
- 出行人编辑入口建议按订单状态控制:仅在「待支付 / 定制中(待补全信息)」展示可编辑表单;订单确认后置为只读。
|
||||
- 若仍调用 batch-edit 命中 581145,按 message 文案提示用户「订单已确认,如需修改出行人请联系定制师」,不要静默失败。
|
||||
- 正常按生命周期编辑(确认前补全)的前端流程**无需改动**。
|
||||
|
||||
## 7. 业务边界
|
||||
|
||||
- 适用:订单 `order_status = PENDING_PAY` 或 `CUSTOMIZING`,且出行人属本人订单(581122 越权拦截照旧)。
|
||||
- 不适用:订单确认后(PENDING_DEPARTURE 起)/ 已完成 / 已取消 —— 一律 581145 拒绝。
|
||||
- 证件号(idNo)在合同已签(contract_status=SIGNED)时本就冻结(581111),本次门禁覆盖范围更靠前(按订单阶段整体拦截)。
|
||||
|
||||
## 8. 影响评估 / 回滚
|
||||
|
||||
- **向后兼容**:对「确认前编辑」的正常流程兼容;对「确认后仍调用 batch-edit」的旧行为为破坏性收紧(此前会误触发重签,属应修缺陷)。
|
||||
- **小程序是否必须同步上线**:建议同步——确认后隐藏 / 禁用编辑入口 + 处理 581145 文案;不同步也不会造成数据错误(后端已兜底拒绝)。
|
||||
- **回滚**:revert PR #4154 重新部署 hl-order-service-v3,恢复不限阶段编辑的旧行为。
|
||||
|
||||
## 9. 关联 / 联系人
|
||||
|
||||
### 9.1 链接
|
||||
|
||||
- **PR**: [#4154](https://git.1814.love:8443/wx/HL/pulls/4154)(CR 发现,无关联 Issue)
|
||||
|
||||
### 9.2 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,241 @@
|
||||
# 发票补齐 - 小程序端申请 + 可见性过滤 + 签名下载(小程序端)
|
||||
|
||||
> Issue: [#4174](https://git.1814.love:8443/wx/HL/issues/4174) / [#4182](https://git.1814.love:8443/wx/HL/issues/4182)
|
||||
> PR: [#4181](https://git.1814.love:8443/wx/HL/pulls/4181)(mp 端申请+可见性)/ [#4184](https://git.1814.love:8443/wx/HL/pulls/4184)(签名下载)
|
||||
> 日期: 2026-06-21
|
||||
> 服务: hl-order-service-v3(invoice 域)
|
||||
> 端类型: 小程序端
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序发票模块本次补齐三项改动:
|
||||
|
||||
- **申请门槛收紧**(PR4 / Issue #4174):原 collab 域 POST /v3/internal/mp/order/{orderId}/invoice/apply 路径不变,但实现从 collab 迁入 invoice 域,门槛由宽松(可叠加多张)改为严格(一单一票 + 订单必须 COMPLETED + 金额不超订单总价)。
|
||||
- **出参可见性过滤**(PR4):GET /v3/internal/mp/order/{orderId}/invoice/list 和 GET /v3/internal/mp/order/invoice/{invoiceId}/detail 出参中,fileUrl 字段现在仅在 ISSUED / PUSHED 状态时返回;REQUESTED 状态返回 null(处理中,前端勿展示 PDF 链接)。
|
||||
- **新增签名下载端点**(#4184 / Issue #4182):GET /v3/internal/mp/order/invoice/{invoiceId}/download,返回 1 小时有效期 OSS 签名 URL,供前端调 wx.openDocument 预览或下载发票 PDF。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | POST /v3/internal/mp/order/{orderId}/invoice/apply | 修改接口(行为收紧) | 申请门槛由宽松改严格:一单一票 + 订单 COMPLETED + 金额不超总价;resp status 字段值从 APPLIED 改为 REQUESTED |
|
||||
| 2 | GET /v3/internal/mp/order/{orderId}/invoice/list | 修改接口(字段行为变化) | fileUrl 字段:REQUESTED 状态由有值改为 null;ISSUED / PUSHED 保持有值 |
|
||||
| 3 | GET /v3/internal/mp/order/invoice/{invoiceId}/detail | 修改接口(字段行为变化) | fileUrl 字段同上:REQUESTED 返 null,ISSUED / PUSHED 有值 |
|
||||
| 4 | GET /v3/internal/mp/order/invoice/{invoiceId}/download | 新增接口 | 返回发票 PDF 的 OSS 签名下载 URL(1 小时有效期),含 IDOR 鉴权 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
认证:所有接口走小程序 JWT 认证,Header 携带 Authorization: Bearer token(小程序 openId → userId)。
|
||||
幂等性:apply 非幂等(一单一票,重复提交报 581511);download 幂等(每次生成新签名 URL)。
|
||||
限流:网关全局限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 接口 | 参数名 | 类型 | 必填 | 说明 |
|
||||
|------|--------|------|------|------|
|
||||
| POST /{orderId}/apply | orderId | Long | 是 | 路径参数,订单 ID |
|
||||
| GET /{orderId}/invoice/list | orderId | Long | 是 | 路径参数,订单 ID |
|
||||
| GET /invoice/{invoiceId}/detail | invoiceId | Long | 是 | 路径参数,发票 ID |
|
||||
| GET /invoice/{invoiceId}/download | invoiceId | Long | 是 | 路径参数,发票 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
**POST /v3/internal/mp/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| amount | String | 是 | 开票金额(字符串,单位元),不超过订单总价 |
|
||||
| invoiceType | String | 是 | 发票类型,枚举见 6 节 |
|
||||
| titleType | String | 是 | 抬头类型:PERSONAL / COMPANY |
|
||||
| title | String | 是 | 发票抬头(姓名或公司名) |
|
||||
| taxNo | String | 条件必填 | 税号(titleType=COMPANY 时必填) |
|
||||
| bankName | String | 条件必填 | 开户行(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| bankAccount | String | 条件必填 | 银行账号(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registAddress | String | 条件必填 | 注册地址(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registPhone | String | 条件必填 | 注册电话(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| email | String | 条件必填 | 收票邮箱(invoiceType=ELECTRONIC 时必填) |
|
||||
| remark | String | 否 | 备注 |
|
||||
|
||||
GET 接口无请求体。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**POST /v3/internal/mp/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 新建发票 ID(Long 序列化) |
|
||||
| status | String | 固定为 REQUESTED |
|
||||
|
||||
注意:status 值此前旧实现返回 APPLIED(collab 域),新实现返回 REQUESTED(invoice 域)。请前端对齐新值。
|
||||
|
||||
**GET /v3/internal/mp/order/{orderId}/invoice/list(单条字段,变化部分)**
|
||||
|
||||
| 字段 | 原来行为 | 现在行为 |
|
||||
|------|---------|---------|
|
||||
| fileUrl | REQUESTED / ISSUED / PUSHED 均有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 |
|
||||
|
||||
其余字段不变(id / orderId / status / statusName / amount / invoiceType / title / taxNo / createTime 等)。
|
||||
|
||||
**GET /v3/internal/mp/order/invoice/{invoiceId}/detail(变化部分)**
|
||||
|
||||
| 字段 | 原来行为 | 现在行为 |
|
||||
|------|---------|---------|
|
||||
| fileUrl | 同上,恒有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 |
|
||||
|
||||
**GET /v3/internal/mp/order/invoice/{invoiceId}/download(新增接口)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| fileUrl | String | OSS 签名下载 URL(1 小时有效期,格式为 HTTPS 带签名参数的 URL) |
|
||||
| pdfName | String | PDF 文件名(可选,用于 wx.openDocument 的 name 参数) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**InvoiceStatus - 发票状态(影响 fileUrl 可见性)**
|
||||
|
||||
| 枚举值 | 中文名 | fileUrl 是否可见 |
|
||||
|--------|--------|----------------|
|
||||
| REQUESTED | 待开票 | null(处理中,前端勿展示 PDF 链接) |
|
||||
| ISSUED | 已开票 | 有值 |
|
||||
| PUSHED | 已推送 | 有值 |
|
||||
| VOIDED | 已作废 | null |
|
||||
|
||||
**InvoiceType - 发票类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
| ELECTRONIC | 电子发票 |
|
||||
|
||||
**TitleType - 抬头类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| PERSONAL | 个人 |
|
||||
| COMPANY | 公司 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581510 | INVOICE_ORDER_NOT_COMPLETED | 订单状态非 COMPLETED,不可申请开票 |
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581512 | INVOICE_AMOUNT_EXCEED | 开票金额超过订单总价 |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头 / 专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行 / 银行账号 / 注册地址 / 注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 电子发票时邮箱为必填 |
|
||||
| 581517 | INVOICE_NOT_DOWNLOADABLE | 发票尚未开具(REQUESTED / VOIDED 状态),暂时无法下载 |
|
||||
| 581518 | INVOICE_FORBIDDEN | 无权访问该发票(IDOR 防护,含发票不存在场景) |
|
||||
| 581519 | INVOICE_SIGNED_URL_FAILED | 获取签名下载 URL 失败,请稍后重试(hl-user-service 暂时不可用) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:申请开票
|
||||
|
||||
POST /v3/internal/mp/order/1234567890123456789/invoice/apply
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
### 8.2 边界情况:列表中 REQUESTED 状态 fileUrl 为 null
|
||||
|
||||
GET /v3/internal/mp/order/1234567890123456789/invoice/list 响应示例(某张待开票发票):
|
||||
|
||||
|
||||
### 8.3 业务失败:REQUESTED 状态发票不可下载
|
||||
|
||||
GET /v3/internal/mp/order/invoice/111/download
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- 申请:订单状态必须为 COMPLETED;无有效发票(一单一票);金额不超订单总价
|
||||
- 下载:发票状态为 ISSUED 或 PUSHED;且该发票属于当前登录用户的订单
|
||||
|
||||
**不适用:**
|
||||
- 订单 COMPLETED 之前不可申请
|
||||
- 已有有效发票,不可重复申请
|
||||
- REQUESTED / VOIDED 状态发票不可下载(报 581517)
|
||||
- 访问他人订单的发票报 581518(IDOR 防护,不返回 404 以避免泄露存在性)
|
||||
|
||||
**特殊边界:**
|
||||
- 签名 URL 有效期 1 小时。前端如果缓存了 fileUrl,每次打开下载前须重新调 download 接口获取最新签名 URL,不可长期复用。
|
||||
- wx.openDocument 下载后端直接返回签名 URL,前端无需中转。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### POST apply 申请门槛变化
|
||||
|
||||
| 项目 | 变更前(旧 collab 实现) | 变更后(invoice 域) |
|
||||
|------|----------------------|-------------------|
|
||||
| 一单一票限制 | 无(可叠加多张) | 有(existsActiveByOrderId 拦截) |
|
||||
| 金额校验 | 已开+本次 <= 总价 | 本次金额 <= 总价(一单一票前提下等价) |
|
||||
| 响应 status 值 | APPLIED | REQUESTED |
|
||||
|
||||
### GET list / detail fileUrl 可见性变化
|
||||
|
||||
| 发票状态 | 变更前 fileUrl | 变更后 fileUrl |
|
||||
|---------|--------------|--------------|
|
||||
| REQUESTED | 有值(OSS 公链) | null(前端显示处理中) |
|
||||
| ISSUED | 有值 | 有值(不变) |
|
||||
| PUSHED | 有值 | 有值(不变) |
|
||||
| VOIDED | 有值 | null |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- 破坏兼容性(重要):fileUrl 字段在 REQUESTED 状态下由有值变为 null。前端如果之前有展示 REQUESTED 状态下 PDF 链接的逻辑(点击打开 OSS 文件),现在会读到 null,需要做 null 判断,展示「处理中」状态文案。
|
||||
- apply status 值变化:从 APPLIED 改为 REQUESTED。前端如果有对 status 值做 hardcode 判断(if status == APPLIED),需要更新。
|
||||
- 前端同步上线建议:fileUrl null 判断 + apply 响应 status 对齐必须在本次版本同步更新。
|
||||
- 回滚方案:回滚旧版本,fileUrl 可见性恢复,download 端点返回 404。apply 的 status 回退为 APPLIED(如旧版本有此值)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. fileUrl null 判断:list / detail 接口读到 fileUrl 为 null 时,前端应显示「处理中」或「待开票」而不是空链接。
|
||||
2. 签名 URL 勿缓存超过 1 小时:download 返回的签名 URL 有效期 1 小时,前端每次用前须重新调接口,不可全局缓存复用。
|
||||
3. apply 响应 status 值变更:旧值 APPLIED 已废弃,现在返回 REQUESTED。如前端有 switch/if 判断该值,须对齐。
|
||||
4. download IDOR 防护:发票不存在或不属于当前用户均返回 581518(INVOICE_FORBIDDEN),不返回 404,避免存在性枚举攻击。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue(小程序端 mp): https://git.1814.love:8443/wx/HL/issues/4174
|
||||
- Issue(签名下载): https://git.1814.love:8443/wx/HL/issues/4182
|
||||
- PR(mp 端 #4181): https://git.1814.love:8443/wx/HL/pulls/4181
|
||||
- PR(签名下载 #4184): https://git.1814.love:8443/wx/HL/pulls/4184
|
||||
- 后端负责人: yaosutu
|
||||
@@ -0,0 +1,340 @@
|
||||
# 售后申诉 — 小程序端新增接口
|
||||
|
||||
- **端类型**:小程序端
|
||||
- **变更类型**:新增接口
|
||||
- **日期**:2026-06-22
|
||||
- **关联 Issue**:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- **关联 PR**:#4185(小程序端接口)/ #4162 #4165 #4168 #4187(基础能力)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
v3 全新售后服务中心(com.hulalv.aftersale)上线。小程序端用户可发起投诉或申诉、查看自己的工单列表与详情、以及在工单未处置前随时撤回。
|
||||
|
||||
新增 4 个接口(含 1 个原有但归入本域):发起工单、我的工单列表、工单详情、撤回工单。
|
||||
|
||||
本次为全新域,不影响任何已有接口。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 编号 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | POST | /v3/mp/aftersale/ticket | 发起投诉/申诉工单 |
|
||||
| 2 | GET | /v3/mp/aftersale/ticket/my | 我的工单列表(按订单分页) |
|
||||
| 3 | GET | /v3/mp/aftersale/ticket/{id} | 工单详情 |
|
||||
| 4 | POST | /v3/mp/aftersale/ticket/{id}/withdraw | 撤回工单 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| 认证方式 | JWT Bearer Token(C 端 Token,Gateway 解析后注入 userId) |
|
||||
| userId 传递 | Gateway 解析 JWT 后注入到请求属性,后端从 HttpServletRequest 取;前端无需传 userId 字段 |
|
||||
| 限流 | Gateway 默认限流策略 |
|
||||
| 路径前缀 | /v3/mp/aftersale/ticket |
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 POST /v3/mp/aftersale/ticket — 发起工单
|
||||
|
||||
请求体(JSON):
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| orderId | Long | 是 | — | 订单 ID |
|
||||
| category | String | 是 | COMPLAINT / APPEAL | 大类:投诉/申诉 |
|
||||
| type | String | 是 | 见 §六枚举 | 细分类型,必须与 category 匹配 |
|
||||
| title | String | 是 | 最多 100 字 | 工单标题 |
|
||||
| description | String | 是 | 最多 2000 字 | 诉求描述 |
|
||||
| targetRefId | Long | 条件必填 | — | 申诉时必填:关联退款申请 ID;投诉时可不传(不传则用 orderId) |
|
||||
| claimType | String | 否 | REFUND / RECTIFY / EXPLANATION | 用户诉求类型,仅供参考 |
|
||||
| claimAmount | Number | 否 | — | 用户期望金额,仅供参考,无约束力 |
|
||||
| attachments | Array<String> | 否 | 最多 10 个 | 附件 URL 列表(图片/视频) |
|
||||
|
||||
### 4.2 GET /v3/mp/aftersale/ticket/my — 我的工单列表
|
||||
|
||||
Query 参数:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| orderId | Long | 是 | — | 订单 ID,只查该订单下的工单 |
|
||||
| pageNo | Integer | 否 | >= 1 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 1-50 | 每页条数,默认 20 |
|
||||
|
||||
### 4.3 GET /v3/mp/aftersale/ticket/{id} — 工单详情
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
注:后端校验工单归属,非本人工单返回 586014。
|
||||
|
||||
### 4.4 POST /v3/mp/aftersale/ticket/{id}/withdraw — 撤回工单
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
无请求体。后端校验:1. 工单归属(非本人返回 586014);2. 状态(只有 SUBMITTED / PROCESSING 可撤回,否则返回 586013)。
|
||||
|
||||
---
|
||||
|
||||
## 五、出参字段
|
||||
|
||||
### 5.1 发起工单 — 返回工单 ID
|
||||
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000001"}
|
||||
```
|
||||
|
||||
data 为 String(Long),即新建工单的 ID。
|
||||
|
||||
### 5.2 我的工单列表 — PageResult 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 5,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 工单详情及列表行 — TicketMpRespVO 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 工单 ID |
|
||||
| orderId | String(Long) | 关联订单 ID |
|
||||
| category | String | 大类枚举值:COMPLAINT / APPEAL |
|
||||
| categoryLabel | String | 大类中文名:投诉 / 申诉 |
|
||||
| type | String | 细分类型枚举值,见 §六 |
|
||||
| typeLabel | String | 细分类型中文名 |
|
||||
| title | String | 工单标题 |
|
||||
| description | String | 诉求描述 |
|
||||
| claimType | String | 用户诉求类型(可选) |
|
||||
| claimAmount | Number | 用户期望金额(可选) |
|
||||
| attachments | Array<String> | 附件 URL 列表 |
|
||||
| status | String | 工单状态枚举值,见 §六 |
|
||||
| statusLabel | String | 工单状态中文名 |
|
||||
| resolutionRemark | String | 处置说明(RESOLVED / CLOSED 后可见,用于展示给用户) |
|
||||
| canWithdraw | Boolean | 是否可撤回(SUBMITTED / PROCESSING 且非终态时为 true) |
|
||||
| createdAt | String(ISO 8601) | 创建时间 |
|
||||
| updatedAt | String(ISO 8601) | 更新时间 |
|
||||
|
||||
注:C 端视图隐藏处置细节(decision / resolutionType / resolutionAmount / approvalNo / linkedRefundId),仅展示 status 和 resolutionRemark。
|
||||
|
||||
### 5.4 撤回工单
|
||||
|
||||
```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### AftersaleCategory — 工单大类
|
||||
|
||||
| 枚举值 | 中文名 | 场景 |
|
||||
|--------|--------|------|
|
||||
| COMPLAINT | 投诉 | 对已发生的服务质量不满 |
|
||||
| APPEAL | 申诉 | 对某个处理结果不服,求复核 |
|
||||
|
||||
### AftersaleTicketType — 细分类型(type 字段)
|
||||
|
||||
| 枚举值 | 中文名 | 归属大类 |
|
||||
|--------|--------|----------|
|
||||
| ITINERARY | 行程 | COMPLAINT |
|
||||
| HOTEL | 酒店 | COMPLAINT |
|
||||
| VEHICLE | 车 | COMPLAINT |
|
||||
| GUIDE | 导游 | COMPLAINT |
|
||||
| OTHER | 其他 | COMPLAINT |
|
||||
| REFUND_REJECTED | 退款被拒 | APPEAL |
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议 | APPEAL |
|
||||
|
||||
注:type 必须与 category 匹配,否则返回 586012。
|
||||
|
||||
### AftersaleTicketStatus — 工单状态
|
||||
|
||||
| 枚举值 | 中文名 | 终态 | 说明 |
|
||||
|--------|--------|------|------|
|
||||
| SUBMITTED | 已提交 | 否 | 等待受理 |
|
||||
| PROCESSING | 处理中 | 否 | 客服受理中 |
|
||||
| RESOLVED | 已处置 | 否 | 处置方案落定(退款类等待到账) |
|
||||
| CLOSED | 已关闭 | 是 | 流程终结 |
|
||||
| WITHDRAWN | 已撤回 | 是 | 用户主动撤回 |
|
||||
|
||||
canWithdraw = true 的条件:status 为 SUBMITTED 或 PROCESSING。
|
||||
|
||||
### claimType — 用户诉求类型(参考值,无强制约束)
|
||||
|
||||
| 常见值 | 中文名 |
|
||||
|--------|--------|
|
||||
| REFUND | 退款 |
|
||||
| RECTIFY | 整改 |
|
||||
| EXPLANATION | 要个说法 |
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 586009 | 工单不存在 | 传入 id 找不到工单 |
|
||||
| 586011 | 同一对象已存在活跃工单 | 防重复提交(同订单下同 target 已有进行中工单) |
|
||||
| 586012 | category 与 type 不匹配 | 如 COMPLAINT 传了 REFUND_REJECTED |
|
||||
| 586013 | 撤回时状态非法 | 终态或 RESOLVED 工单不可撤回 |
|
||||
| 586014 | 非本人工单 | 查看/撤回他人工单 |
|
||||
| 586015 | 申诉必须关联退款申请 | APPEAL 类工单未传 targetRefId |
|
||||
| 586016 | 申诉关联退款状态不合法 | 关联的退款申请状态不允许申诉 |
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功 — 发起投诉工单
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer {mp_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1800000000000099,
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级投诉",
|
||||
"description": "订单挂四星酒店实际入住三星,要求退差价",
|
||||
"claimType": "REFUND",
|
||||
"claimAmount": 600.00,
|
||||
"attachments": ["https://oss.example.com/img1.jpg"]
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000001"}
|
||||
```
|
||||
|
||||
### 8.2 典型成功 — 发起申诉工单(退款被拒)
|
||||
|
||||
```
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer {mp_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1800000000000099,
|
||||
"category": "APPEAL",
|
||||
"type": "REFUND_REJECTED",
|
||||
"targetRefId": 9876543210,
|
||||
"title": "退款申请被拒申诉",
|
||||
"description": "退款申请被拒,但供应商确实未提供服务,要求重新审核"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000002"}
|
||||
```
|
||||
|
||||
### 8.3 边界情况 — 查询工单详情(已处置,展示处置说明)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "1895000000000001",
|
||||
"orderId": "1800000000000099",
|
||||
"category": "COMPLAINT",
|
||||
"categoryLabel": "投诉",
|
||||
"type": "HOTEL",
|
||||
"typeLabel": "酒店",
|
||||
"title": "酒店降级投诉",
|
||||
"description": "订单挂四星酒店实际入住三星,要求退差价",
|
||||
"claimType": "REFUND",
|
||||
"claimAmount": 600.00,
|
||||
"attachments": ["https://oss.example.com/img1.jpg"],
|
||||
"status": "CLOSED",
|
||||
"statusLabel": "已关闭",
|
||||
"resolutionRemark": "经核实酒店确实降级,已退差价 500 元",
|
||||
"canWithdraw": false,
|
||||
"createdAt": "2026-06-22T09:00:00",
|
||||
"updatedAt": "2026-06-22T15:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 业务失败 — 申诉未传 targetRefId
|
||||
|
||||
```json
|
||||
{"code": 586015, "msg": "申诉必须关联一笔有效的退款申请"}
|
||||
```
|
||||
|
||||
### 8.5 业务失败 — 撤回已 CLOSED 工单
|
||||
|
||||
```json
|
||||
{"code": 586013, "msg": "撤回时工单状态非法(只有 SUBMITTED / PROCESSING 可撤回)"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
适用:
|
||||
- 同一订单下同一 targetRef 只能有一个活跃工单(SUBMITTED / PROCESSING / RESOLVED),否则返回 586011
|
||||
- 申诉(APPEAL)类工单必须关联一笔有效的退款申请(targetRefId 必填)
|
||||
- SUBMITTED / PROCESSING 状态的工单可撤回,canWithdraw=true
|
||||
|
||||
不适用:
|
||||
- 终态工单(CLOSED / WITHDRAWN)不可撤回
|
||||
- 非本人工单不可查看/撤回
|
||||
|
||||
特殊边界:
|
||||
- 工单从 PROCESSING 撤回后,若该工单已提交企微 OA,后端会自动撤回 OA 审批(异步,不影响前端响应)
|
||||
- attachments 最多 10 个,超出返回参数校验错误
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
本次为全新域新增接口,无旧接口对比。
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
本次为全新域,不影响任何已有小程序接口,可独立上线。
|
||||
|
||||
回滚方案:回滚后端服务版本即可。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
1. 发起工单返回值是 String(Long):data 字段为新建工单的 ID,是字符串而非数字。
|
||||
2. C 端视图隐藏处置细节:resolutionAmount / decision / approvalNo 等字段在 C 端不返回,不要在小程序页面展示。resolutionRemark 是给用户看的处置说明,可展示。
|
||||
3. canWithdraw 字段已由后端计算:前端直接用该字段判断是否显示撤回按钮,无需自行判断状态。
|
||||
4. 所有 ID 为字符串:id / orderId 均序列化为字符串,前端不要转 Number。
|
||||
5. category=APPEAL 时:targetRefId 必填(关联退款申请 ID),targetRefType 后端自动设为 REFUND_APPLICATION。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- PR #4185(小程序端接口):https://git.1814.love:8443/wx/HL/pulls/4185
|
||||
- PR #4162(售后基建):https://git.1814.love:8443/wx/HL/pulls/4162
|
||||
- PR #4165(受理/处置 + OA 接入):https://git.1814.love:8443/wx/HL/pulls/4165
|
||||
- PR #4168(OA 回调内部接口):https://git.1814.love:8443/wx/HL/pulls/4168
|
||||
- PR #4187(退款驱动联动):https://git.1814.love:8443/wx/HL/pulls/4187
|
||||
- 后端负责人:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,341 @@
|
||||
# 售后工单接入 wx 内容安全机审 — 修改接口(小程序端)
|
||||
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-22
|
||||
- **Issue**:[#4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[#4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[8882eb156](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
用户在小程序发起售后工单(投诉/申诉)时,若附带图片或视频,前端需先通过微信内容安全 `msgSecCheck` 接口对媒体文件做内容审核,得到微信返回的 `traceId` 后,连同创建工单请求一并透传给后端。后端不直接调用微信安全接口,只存储 traceId,等待微信异步回调后更新审核状态。无图/无视频的工单跳过机审,直接置为 `APPROVED`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 字段 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ⚠️ 入参新增 | `POST /v3/mp/aftersale/ticket` | `mediaTraceIds` | 新增可选字段,机审 traceId 数组,最多 12 个 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/{id}` | `auditStatus` | 工单 wx 内容安全机审状态 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/list` | `auditStatus` | 工单列表每条记录新增机审状态 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 发起售后工单
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `POST /v3/mp/aftersale/ticket` |
|
||||
| 接口描述 | 用户发起售后工单(投诉/申诉),支持附件 + 机审 traceId 透传 |
|
||||
| 认证 | 需要用户 JWT(小程序登录 token) |
|
||||
| 幂等性 | 非幂等,每次调用创建一条新工单 |
|
||||
| 限流 | 无独立限流(受网关全局限流) |
|
||||
|
||||
### 3.2 工单详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/{id}` |
|
||||
| 接口描述 | 获取单条售后工单详情 |
|
||||
| 认证 | 需要用户 JWT,仅可查自己的工单 |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
### 3.3 工单列表
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/list` |
|
||||
| 接口描述 | 查询当前用户的售后工单列表(分页) |
|
||||
| 认证 | 需要用户 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
`GET /v3/mp/aftersale/ticket/{id}`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-------|------|------|------|
|
||||
| id | Long | 是 | 工单 ID |
|
||||
|
||||
### 4.2 请求体字段(POST /v3/mp/aftersale/ticket)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验 | 说明 |
|
||||
|-------|------|------|------|------|
|
||||
| orderId | Long | 是 | 不能为空 | 关联订单 ID |
|
||||
| category | String | 是 | 枚举:COMPLAINT / APPEAL | 工单分类:投诉 / 申诉 |
|
||||
| type | String | 是 | 枚举,见 §6 | 具体类型(如 HOTEL / GUIDE / PRICE) |
|
||||
| title | String | 是 | max=100 | 工单标题 |
|
||||
| description | String | 是 | max=2000 | 问题描述 |
|
||||
| attachments | List\<String\> | 否 | max=10 | 附件 URL 列表(图片/视频) |
|
||||
| **mediaTraceIds** | **List\<String\>** | **否** | **max=12** | **微信内容安全 traceId 数组(前端透传,后端只存)** |
|
||||
|
||||
> `mediaTraceIds` 与 `attachments` 一一对应关系由前端维护。后端不校验对应关系,仅存储用于异步机审回流。无附件时可不传或传空数组。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### TicketMpRespVO(工单详情/列表单条)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| id | Long | 工单 ID |
|
||||
| orderId | Long | 关联订单 ID |
|
||||
| orderNo | String | 订单编号 |
|
||||
| category | String | 工单分类枚举:COMPLAINT / APPEAL |
|
||||
| type | String | 工单类型 |
|
||||
| title | String | 标题 |
|
||||
| description | String | 描述 |
|
||||
| status | String | 工单状态:SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN |
|
||||
| statusName | String | 工单状态中文名 |
|
||||
| canWithdraw | Boolean | 是否可撤回 |
|
||||
| **auditStatus** | **String** | **wx 内容安全机审状态,取值见 §6** |
|
||||
| attachments | List\<String\> | 附件 URL |
|
||||
| createdAt | String | 创建时间(ISO 8601) |
|
||||
| updatedAt | String | 更新时间(ISO 8601) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### auditStatus — wx 内容安全机审状态
|
||||
|
||||
| 值 | 含义 | 触发条件 |
|
||||
|----|------|---------|
|
||||
| `PENDING` | 机审中 | 创建工单时有 mediaTraceIds(异步等待微信回调) |
|
||||
| `APPROVED` | 已通过 | 无 mediaTraceIds 的工单直接置此(免审);微信回调结果合规也置此 |
|
||||
| `MANUAL_REVIEW` | 人工复审 | 微信机审认为需人工介入 |
|
||||
| `REJECTED` | 已驳回 | 微信机审判定内容违规 |
|
||||
|
||||
> **重要**:`PENDING` 状态为异步,工单创建成功后微信回调可能在数秒到数分钟内到达。前端建议在工单详情页轮询或基于页面刷新展示最新状态,不要把 `PENDING` 当作错误。
|
||||
|
||||
### category — 工单分类
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `COMPLAINT` | 投诉 |
|
||||
| `APPEAL` | 申诉 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|-------|---------|------|---------|
|
||||
| 581001 | 400 | 工单关联订单不存在 | orderId 无效 |
|
||||
| 581002 | 400 | 工单已存在,不可重复提交 | 同一订单同分类已有活跃工单 |
|
||||
| 581003 | 400 | 订单状态不允许发起售后 | 订单未处于可售后状态 |
|
||||
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
|
||||
| 200-002 | 403 | 无权操作 | 工单不属于当前用户 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——带图片机审
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"mediaTraceIds": [
|
||||
"trace_abc123def456",
|
||||
"trace_xyz789uvw012"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654321,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "PENDING",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"createdAt": "2026-06-22T10:30:00",
|
||||
"updatedAt": "2026-06-22T10:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `auditStatus` 初始为 `PENDING`,等待微信异步回调后更新为 `APPROVED` / `MANUAL_REVIEW` / `REJECTED`。
|
||||
|
||||
### 8.2 边界情况——无附件工单(直接 APPROVED)
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"attachments": [],
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654322,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "APPROVED",
|
||||
"attachments": [],
|
||||
"createdAt": "2026-06-22T10:35:00",
|
||||
"updatedAt": "2026-06-22T10:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 无 mediaTraceIds 或传空数组时,`auditStatus` 直接为 `APPROVED`,无需等待回调。
|
||||
|
||||
### 8.3 业务失败——订单已有活跃工单
|
||||
|
||||
**请求**(同一订单同分类已存在未关闭工单)
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "GUIDE",
|
||||
"title": "导游服务差",
|
||||
"description": "导游态度恶劣。",
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 581002,
|
||||
"msg": "工单已存在,不可重复提交",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 订单处于可售后状态(已出行、出行中、已完成等,具体由后端校验)
|
||||
- 有图片/视频附件时前端必须先完成 `wx.msgSecCheck` 得到 traceId 再调此接口
|
||||
- 无附件或不需要机审时,`mediaTraceIds` 可省略或传空数组
|
||||
|
||||
**不适用场景**:
|
||||
- 已取消订单不可发起售后
|
||||
- 同一订单同分类已有活跃(非 CLOSED/WITHDRAWN)工单时,不可重复发起
|
||||
|
||||
**特殊边界**:
|
||||
- `mediaTraceIds` 最多 12 个,超出返回参数校验错误
|
||||
- `auditStatus` 为 `PENDING` 时工单照常流转(客服可正常处理),机审结果不阻塞工单流程
|
||||
- `auditStatus` 为 `REJECTED` 时,仅作展示标记,工单处理流程由客服决定是否关闭
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
**CreateTicketReqVO(入参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| mediaTraceIds | 不存在 | 新增,List\<String\>,可选,max=12 |
|
||||
|
||||
**TicketMpRespVO(出参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| auditStatus | 不存在 | 新增,String,wx 机审状态 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| 带图片发起工单 | 无机审,无审核状态 | 存 traceId,初始 auditStatus=PENDING,微信异步回调更新 |
|
||||
| 无图片发起工单 | 无机审状态字段 | auditStatus 直接置 APPROVED |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| 破坏兼容性 | 否(入参新增可选字段,旧版不传兼容;出参新增字段,旧版忽略) |
|
||||
| 前端同步上线 | 建议同步:入参传 mediaTraceIds 发挥机审能力;出参展示 auditStatus |
|
||||
| 回滚方案 | 后端回滚 PR #4203 即可,无 DDL 数据损失风险(DDL 只加列不删) |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. `mediaTraceIds` 由前端在调用微信 `wx.msgSecCheck` 后获得,后端不直接调微信安全接口,只做存储和转发。
|
||||
2. `auditStatus=PENDING` 是异步状态,不代表工单异常,客服后台可正常处理。
|
||||
3. 历史工单(此 PR 上线前创建的)`auditStatus` 字段为 `null`,前端展示时建议视 `null` 等同于 `APPROVED` 处理。
|
||||
4. 微信机审结果 `REJECTED` 不自动关闭工单,仅打标记,由客服决定后续处理。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **关联 Issue**:[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,31 @@
|
||||
# 【修正·小程序】售后工单 mp 端接口加归属校验(防横向越权)
|
||||
|
||||
> 模块:order-v3 售后工单(aftersale)小程序端
|
||||
> 类型:安全加固(每日审查驱动,新模块上线前修正)
|
||||
> 影响端点:`/v3/mp/aftersale/ticket/**`(我的工单列表 / 详情 / 撤回)
|
||||
> 关联:售后工单中心 PR1-5(#4162/#4165/#4168/#4173/#4185)的每日审查修正
|
||||
> 测试服已部署验证(9443 实测端点 200)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
售后工单 mp 端三个用户入口此前缺少归属校验,任意登录用户传他人 `ticketId`/`orderId` 即可读取或撤回他人工单(横向越权 IDOR)。本次加固:**经订单归属校验当前用户是否为下单人**,非本人操作返回错误码 **586014(TICKET_NOT_OWN)**。
|
||||
|
||||
正常业务流程下,小程序用户只操作自己订单的售后工单,**不会触发**该错误;前端无需改动,仅需对 586014 做兜底提示(如「无权访问该工单」)。
|
||||
|
||||
## 1. 受影响端点与行为
|
||||
|
||||
| 端点 | 修正 |
|
||||
|------|------|
|
||||
| `GET /v3/mp/aftersale/ticket/my`(我的工单列表) | 新增按当前用户校验 `orderId` 归属;端点入参不变,仅服务端加校验 |
|
||||
| `GET /v3/mp/aftersale/ticket/{id}`(工单详情) | 校验工单所属订单归属当前用户,非本人抛 586014 |
|
||||
| `POST /v3/mp/aftersale/ticket/{id}/withdraw`(撤回) | 同上归属校验,非本人不可撤回(且不再触发他人 OA 审批撤销) |
|
||||
|
||||
## 2. 附带修正:撤回工单不再消失
|
||||
|
||||
撤回(WITHDRAWN)此前被误当软删处理,导致撤回后的工单在「我的工单」列表/详情中**永久不可见**。本次修正后,**撤回是可见终态**,撤回的工单仍可在列表与详情中查看(状态显示「已撤回」)。
|
||||
|
||||
## 3. 错误码
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 586014 | 无权操作该售后工单(非本人订单)|
|
||||
@@ -0,0 +1,222 @@
|
||||
# 退款申诉(新增)+ 统一售后工单接口下线(小程序端)
|
||||
|
||||
- 端类型:小程序端
|
||||
- 变更类型:新增接口(4)+ 删除接口(统一售后工单 mp 端)
|
||||
- 关联 Issue:#4250 PR:#4251 #4254
|
||||
- 日期:2026-06-23
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
售后体系重构:原"投诉 + 申诉统一售后工单"方向作废,拆为**投诉**与**申诉**两套独立能力。
|
||||
- **申诉**:用户对"退款被拒"或"退款金额"不服时发起,**小程序入口仍是「退款申诉」**。申诉记录单独存(不再混入退款申请表)。
|
||||
- **关键变化**:申诉**复用既有退款审批**——发起申诉后系统自动建一笔 PENDING 退款申请,由管理员在退款审批里复核(通过则退款 / 驳回则结束);申诉本身不再有独立审批。
|
||||
- 原统一售后工单接口(`/v3/mp/aftersale/ticket*`)**全部下线**。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST | `/v3/mp/refund/appeal` | 🆕 新增(发起申诉)|
|
||||
| 2 | GET | `/v3/mp/refund/appeal/my` | 🆕 新增(我的申诉,分页)|
|
||||
| 3 | GET | `/v3/mp/refund/appeal/{id}` | 🆕 新增(申诉详情)|
|
||||
| 4 | POST | `/v3/mp/refund/appeal/{id}/withdraw` | 🆕 新增(撤回申诉)|
|
||||
| 5 | ALL | `/v3/mp/aftersale/ticket*` | ❌ 删除(统一售后工单下线,调用返回 404)|
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 发起申诉 `POST /v3/mp/refund/appeal`
|
||||
用户对一笔退款申请发起申诉。两种类型:`REFUND_REJECTED`(退款被拒,原退款 status=REJECTED 才可申诉)/ `REFUND_AMOUNT_DISPUTE`(退款金额异议,原退款 status=REFUNDED 求补差)。
|
||||
> 发起成功后:① 落一条申诉记录(PENDING)② **自动建一笔 PENDING 退款申请**走退款审批 ③ 订单进入「售后中」。
|
||||
|
||||
### 2. 我的申诉 `GET /v3/mp/refund/appeal/my`
|
||||
当前登录用户的申诉分页列表。
|
||||
|
||||
### 3. 申诉详情 `GET /v3/mp/refund/appeal/{id}`
|
||||
单条申诉详情,含关联的两笔退款(来源退款 + 申诉触发的退款)。
|
||||
|
||||
### 4. 撤回申诉 `POST /v3/mp/refund/appeal/{id}/withdraw`
|
||||
仅 `PENDING`(审批中)状态可撤回;撤回会一并取消申诉触发的那笔 PENDING 退款申请。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口1 create(`@RequestBody`,登录态 userId 由网关注入)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| refundApplicationId | long | 是 | 来源退款申请 ID(被申诉的那笔退款)|
|
||||
| appealType | string | 是 | 申诉类型:`REFUND_REJECTED` / `REFUND_AMOUNT_DISPUTE` |
|
||||
| appealReason | string | 是 | 申诉理由(≤2000 字)|
|
||||
| appealAmount | string(decimal) | 否 | 期望退款金额(金额异议时填)|
|
||||
| mediaTraceIds | string[] | 否 | 凭证图片机审 traceId 列表(前端上传后透传,≤12)|
|
||||
|
||||
### 接口2 my
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| pageNo | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||||
|
||||
### 接口3 detail / 接口4 withdraw
|
||||
| 参数 | 位置 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | path | long | 申诉 ID |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口1 create `Result<Long>`
|
||||
`data` = 新建申诉 ID(字符串化 Long)。
|
||||
|
||||
### 接口2 my `Result<PageResult<AppealMpRespVO>>`;接口3 detail `Result<AppealMpRespVO>`
|
||||
`PageResult`:`{ records[], total, page, pageSize }`。`AppealMpRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string(Long) | 申诉 ID |
|
||||
| orderId | string(Long) | 订单 ID |
|
||||
| refundApplicationId | string(Long) | 来源退款申请 ID |
|
||||
| appealType | string | 申诉类型(枚举值,见⑥)|
|
||||
| appealTypeLabel | string | 申诉类型中文名 |
|
||||
| appealReason | string | 申诉理由 |
|
||||
| appealAmount | string(decimal) | 申诉退款金额 |
|
||||
| appealStatus | string | 申诉状态(枚举值,见⑥)|
|
||||
| appealStatusLabel | string | 申诉状态中文名 |
|
||||
| applicantName | string | 申请人姓名 |
|
||||
| reviewedAt | datetime | 审批完成时间(可空)|
|
||||
| createTime | datetime | 创建时间 |
|
||||
| sourceRefund | object | 来源退款信息(被申诉的原退款),见下 |
|
||||
| triggeredRefund | object | 申诉触发的退款信息(申诉建单后自动生成的退款),见下 |
|
||||
|
||||
`sourceRefund` / `triggeredRefund` 结构(关联退款信息):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refundApplicationId | string(Long) | 退款申请 ID |
|
||||
| status | string | 退款状态(PENDING/APPROVED/REJECTED/REFUNDING/REFUNDED/CANCELLED/ABNORMAL)|
|
||||
| actualAmount | string(decimal) | 实际退款金额(可空)|
|
||||
|
||||
### 接口4 withdraw `Result<Void>`
|
||||
`data` = null。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**申诉类型 appealType**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| REFUND_REJECTED | 退款被拒(原退款被驳回后申诉)|
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议(已退款但金额有异议,求补差)|
|
||||
|
||||
**申诉状态 appealStatus**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 审批中(申诉触发的退款申请待退款审批)|
|
||||
| REJECTED | 已驳回(退款审批驳回)|
|
||||
| REFUNDED | 退款到账(申诉成功,退款已退)|
|
||||
| WITHDRAWN | 已撤回 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码(段位 530600-530699)
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 530601 | 申诉必须关联一笔退款申请 |
|
||||
| 530602 | 关联的退款申请不存在 |
|
||||
| 530603 | 退款申请当前状态不满足申诉条件 |
|
||||
| 530604 | 该退款申请已有处理中的申诉,请勿重复提交 |
|
||||
| 530605 | 申诉退款金额超出可退余额 |
|
||||
| 530606 | 申诉记录不存在 |
|
||||
| 530607 | 无权操作此申诉 |
|
||||
| 530608 | 当前申诉状态不允许撤回,仅待审核状态可撤回 |
|
||||
| 530609 | 申诉类型无效 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:发起申诉(退款被拒)
|
||||
请求 `POST /v3/mp/refund/appeal`:
|
||||
```json
|
||||
{ "refundApplicationId": 20001, "appealType": "REFUND_REJECTED",
|
||||
"appealReason": "退款审核不合理,申请复核", "appealAmount": "300.00",
|
||||
"mediaTraceIds": [] }
|
||||
```
|
||||
响应:`{ "code":200, "message":"成功", "data":"40001", "success":true }`
|
||||
|
||||
### 典型:我的申诉
|
||||
请求 `GET /v3/mp/refund/appeal/my?pageNo=1&pageSize=10`,响应(节选一项):
|
||||
```json
|
||||
{ "code":200, "success":true, "data": { "total":1, "records":[
|
||||
{ "id":"40001", "orderId":"10001", "appealType":"REFUND_REJECTED", "appealTypeLabel":"退款被拒",
|
||||
"appealStatus":"PENDING", "appealStatusLabel":"审批中", "appealAmount":"300.00",
|
||||
"sourceRefund": { "refundApplicationId":"20001", "status":"REJECTED", "actualAmount":null },
|
||||
"triggeredRefund": { "refundApplicationId":"20009", "status":"PENDING", "actualAmount":null } }
|
||||
] } }
|
||||
```
|
||||
|
||||
### 异常:重复申诉
|
||||
对同一退款申请已有处理中申诉时再发起:
|
||||
```json
|
||||
{ "code":530604, "message":"该退款申请已有处理中的申诉,请勿重复提交", "success":false }
|
||||
```
|
||||
|
||||
### 异常:调用已下线的工单接口
|
||||
请求 `POST /v3/mp/aftersale/ticket`(旧统一售后工单接口):
|
||||
```json
|
||||
{ "code":404, "message":"Not Found" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 申诉**复用退款审批**:发起后自动建 PENDING 退款申请,管理员在退款审批里复核——**前端不要再调用任何"申诉审批"接口**(已无独立申诉审批)。
|
||||
- 申诉状态跟随其触发的退款申请:退款到账→REFUNDED,退款审批驳回→REJECTED。
|
||||
- 撤回仅 PENDING 可撤,且会取消触发的 PENDING 退款。
|
||||
- 申诉发起即把订单标记「售后中」,终结(REFUNDED/REJECTED/WITHDRAWN 且无其他活跃售后)后回切。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 申诉入口 | 统一售后工单 `POST /v3/mp/aftersale/ticket`(category=APPEAL)| `POST /v3/mp/refund/appeal` |
|
||||
| 我的申诉 | `/v3/mp/aftersale/ticket/my` | `/v3/mp/refund/appeal/my` |
|
||||
| 详情/撤回 | `/v3/mp/aftersale/ticket/{id}` `/{id}/withdraw` | `/v3/mp/refund/appeal/{id}` `/{id}/withdraw` |
|
||||
| 审批 | 申诉独立 OA 审批 | 无(复用退款审批)|
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:`/v3/mp/aftersale/ticket*` 已删,调用返回 404,前端涉及"退款申诉"的页面**必须切到新申诉接口**。
|
||||
- 投诉接口不在此列(投诉走 C 端经 BFF 的现有通道,无变化)。
|
||||
- 回滚:后端回滚 PR #4251 #4254。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段(appealAmount / actualAmount)均为**字符串**,前端按字符串处理防精度丢失。
|
||||
- Long 型 ID 均字符串化返回(防 JS 精度)。
|
||||
- 上传凭证图片仍走既有 wx 内容安全机审通道(mediaTraceIds 透传)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4250
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4251 ・ https://git.1814.love:8443/wx/HL/pulls/4254
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(申诉接口 200 / 工单接口 404)。
|
||||
@@ -0,0 +1,336 @@
|
||||
# 发票申请票种修正(最终契约)— 小程序端
|
||||
|
||||
> 变更类型:修改接口(破坏性)
|
||||
> 端类型:小程序端
|
||||
> 日期:2026-06-23 | Issue:#4296 | PR:#4292 / #4302 | 服务:hl-order-service-v3
|
||||
|
||||
> ⚠️ **破坏性变更**:`invoiceType` 删除 `ELECTRONIC`、`email` 改为始终必填 + 格式校验、专票限公司抬头 + 必填五项、删除 `mailAddress` 字段。小程序开票页与详情页均需同步改动,上线时前后端须同步发布。
|
||||
|
||||
> **勘误(截至 PR #4311 收口)**:抬头字段统一 `titleName`、金额 `amount` 改 JSON number(单位元,非分/x100)、票种仅 2 值(`VAT_NORMAL`/`VAT_SPECIAL`)、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。
|
||||
|
||||
---
|
||||
|
||||
## 接口背景
|
||||
|
||||
发票模型经 PR #4292 + #4302 多轮修正后定稿:删除「电子发票」票种(业务决策统一走普票),专票收严字段校验(限公司抬头 + 必填银行注册信息),email 改为始终必填(全系统电子交付),删除纸质邮寄字段 `mailAddress`。
|
||||
|
||||
本次涉及小程序两个接口:初次申请 `POST /v3/internal/mp/order/{orderId}/invoice/apply` 和重新开票 `POST /v3/internal/mp/invoice/{id}/reissue`,入参字段矩阵相同,出参亦做同步修正(删 `mailAddress`,invoiceType 去 ELECTRONIC)。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 说明 |
|
||||
|---|---------|------|
|
||||
| 1 | ⚠️ 申请入参删枚举值 | `invoiceType` 删除 `ELECTRONIC`,只保留 `VAT_NORMAL` / `VAT_SPECIAL` |
|
||||
| 2 | ⚠️ 申请入参删字段 | 删除 `mailAddress`(纸质邮寄地址) |
|
||||
| 3 | ⚠️ 申请入参校验收严 | `email` 改为始终必填 + 邮箱格式校验 |
|
||||
| 4 | ⚠️ 申请入参校验新增 | `taxNo`:单位抬头或专票时必填 |
|
||||
| 5 | ⚠️ 申请入参校验新增 | 专票:`bankName` / `bankAccount` / `registAddress` / `registPhone` 四项必填 |
|
||||
| 6 | ⚠️ 申请入参新增错误码 | `581523` 专票只能开给单位 |
|
||||
| 7 | ⚠️ 详情出参删字段 | `GET /v3/internal/mp/order/{orderId}/invoice/detail` 出参删除 `mailAddress` |
|
||||
| 8 | ⚠️ 列表出参枚举收窄 | `GET /v3/internal/mp/order/{orderId}/invoice/list` 出参 invoiceType 不再出现 ELECTRONIC |
|
||||
| 9 | 重开接口同步 | `POST /v3/internal/mp/invoice/{id}/reissue` 入参字段矩阵与申请接口保持一致 |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 小程序申请接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/order/{orderId}/invoice/apply` |
|
||||
| **接口名** | 小程序客户自主申请发票 |
|
||||
| **描述** | 客户在订单完成后自主提交开票申请,支持普票/专票,全电子交付 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
### 小程序重开接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/invoice/{id}/reissue` |
|
||||
| **接口名** | 小程序重新开票 |
|
||||
| **描述** | 旧发票作废后,客户重新提交开票申请;入参字段矩阵与 apply 接口相同 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 路径参数
|
||||
|
||||
**apply 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `orderId` | string(Long 雪花 ID) | 是 | 订单 ID |
|
||||
|
||||
**reissue 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `id` | string(Long 雪花 ID) | 是 | 被重开的旧发票 ID |
|
||||
|
||||
### 请求体字段(两接口共用)
|
||||
|
||||
| 字段名 | 类型 | 必填条件 | 说明 |
|
||||
|--------|------|---------|------|
|
||||
| `invoiceType` | string | 始终必填 | 发票类型:`VAT_NORMAL`(普票)/ `VAT_SPECIAL`(专票);**不含 ELECTRONIC** |
|
||||
| `titleType` | string | 始终必填 | 抬头类型:`COMPANY`(单位)/ `PERSONAL`(个人);**VAT_SPECIAL 只能 COMPANY** |
|
||||
| `titleName` | string | 始终必填 | 发票抬头 |
|
||||
| `taxNo` | string | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时必填 | 纳税人识别号 |
|
||||
| `bankName` | string | `invoiceType=VAT_SPECIAL` 时必填 | 开户银行 |
|
||||
| `bankAccount` | string | `invoiceType=VAT_SPECIAL` 时必填 | 银行账号 |
|
||||
| `registAddress` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册地址 |
|
||||
| `registPhone` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册电话 |
|
||||
| `amount` | number | 始终必填 | 开票金额,JSON number,单位**元**(如 `986.00`),须 > 0 且不超过订单总金额 |
|
||||
| `email` | string | 始终必填 | 收件邮箱,须通过邮箱格式校验 |
|
||||
| `remark` | string | 选填 | 备注 |
|
||||
|
||||
> ⚠️ `mailAddress` 字段**已删除**,不再接收。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
### apply / reissue 接口出参
|
||||
|
||||
返回结构:`Result<Long>`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `data` | string | 新建/重开发票 ID,字符串雪花 ID |
|
||||
|
||||
### 发票详情出参变化(GET /v3/internal/mp/order/{orderId}/invoice/detail)
|
||||
|
||||
| 字段名 | 变化 | 说明 |
|
||||
|--------|------|------|
|
||||
| `mailAddress` | **已删除** | 原出参中此字段已移除,前端不应再渲染邮寄地址 |
|
||||
| `invoiceType` | 值范围收窄 | 只会出现 `VAT_NORMAL` / `VAT_SPECIAL`,不再出现 `ELECTRONIC` |
|
||||
| `email` | 始终有值 | 原可能为 null,现始终有值 |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 | 适用抬头 | 专票字段要求 |
|
||||
|--------|--------|---------|------------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 | COMPANY / PERSONAL | 专票四项均不需要 |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 | **仅 COMPANY** | taxNo + bankName + bankAccount + registAddress + registPhone 全必填 |
|
||||
|
||||
> ~~`ELECTRONIC`~~ 已删除,不得传入。
|
||||
|
||||
### 抬头类型(titleType)
|
||||
|
||||
| 枚举值 | 中文名 | 可选票种 |
|
||||
|--------|--------|---------|
|
||||
| `COMPANY` | 单位 | VAT_NORMAL / VAT_SPECIAL |
|
||||
| `PERSONAL` | 个人 | 只能 VAT_NORMAL,选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `581510` | 订单未完成,不可开票 | 订单状态不是 COMPLETED |
|
||||
| `581511` | 一单一票,已有有效发票 | 已有 REQUESTED / ISSUED / PUSHED 状态发票 |
|
||||
| `581512` | 开票金额超订单总额 | `amount` > 订单 `orderAmount` |
|
||||
| `581513` | 发票类型非法 | `invoiceType` 不是 VAT_NORMAL 或 VAT_SPECIAL |
|
||||
| `581514` | 公司抬头或专票须填税号 | 单位抬头或专票时 `taxNo` 为空 |
|
||||
| `581515` | 专票须填银行及注册信息 | 专票时任一四项为空 |
|
||||
| `581516` | 收件邮箱不能为空 | HTTP 400,入参层 @NotBlank 校验;格式错误返回 400「邮箱格式不正确」 |
|
||||
| `581523` | 专票只能开给单位 | `invoiceType=VAT_SPECIAL` 且 `titleType=PERSONAL` |
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 / 越权 | 非订单归属用户操作 |
|
||||
|
||||
---
|
||||
|
||||
## 示例
|
||||
|
||||
### 典型成功(增值税普通发票,个人抬头)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000001/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "张三",
|
||||
"taxNo": null,
|
||||
"amount": 986.00,
|
||||
"email": "zhangsan@qq.com",
|
||||
"remark": "旅游报销"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456789"
|
||||
}
|
||||
```
|
||||
|
||||
### 典型成功(增值税专用发票,公司抬头,全字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000002/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "某某科技有限公司",
|
||||
"taxNo": "91310000XXXXXXXXXX",
|
||||
"bankName": "招商银行上海支行",
|
||||
"bankAccount": "1234567890123456",
|
||||
"registAddress": "上海市浦东新区XX路XX号",
|
||||
"registPhone": "021-88888888",
|
||||
"amount": 2980.00,
|
||||
"email": "finance@company.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456790"
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(专票个人抬头,错误码 581523)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000003/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "李四",
|
||||
"taxNo": null,
|
||||
"amount": 500.00,
|
||||
"email": "lisi@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581523,
|
||||
"msg": "增值税专用发票只能开给单位,个人抬头不可选专票",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 客户在小程序「我的订单 - 订单详情」中点「申请发票」,订单状态为 COMPLETED 时可用
|
||||
- 旧发票被作废后,客户在小程序发起重新申请(reissue 接口)
|
||||
|
||||
**不适用场景**
|
||||
- 订单未完成(非 COMPLETED 状态),返回 581510
|
||||
- 已有有效发票,须先由财务作废后才能 reissue,不能再次 apply
|
||||
- 管理后台代客申请走 admin 专属接口
|
||||
|
||||
**特殊边界**
|
||||
- 金额单位:`amount` 为 JSON number,单位**元**(如 `986.00`),前端输入框以元为单位直接提交,无需 x100
|
||||
- 一单一票:同一订单至多一张有效发票
|
||||
- 专票抬头联动:选专票后,抬头类型需强制为单位,个人选项需禁用或隐藏
|
||||
- 详情回显:`mailAddress` 字段已不存在,前端不应渲染邮寄地址块
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
### 发票类型枚举
|
||||
|
||||
| 旧值 | 新值 |
|
||||
|------|------|
|
||||
| VAT_NORMAL(保留) | VAT_NORMAL(保留) |
|
||||
| VAT_SPECIAL(保留) | VAT_SPECIAL(保留) |
|
||||
| ELECTRONIC(电子发票) | **已删除** |
|
||||
|
||||
### email 字段校验
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 条件必填(ELECTRONIC 时才必填) | **始终必填 + 邮箱格式校验** |
|
||||
|
||||
### mailAddress 字段
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 申请入参可选填;详情/列表出参包含此字段 | **申请入参已删除;详情出参已删除** |
|
||||
|
||||
### 专票限制
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| titleType 无限制 | 专票只能 COMPANY(个人返回 581523) |
|
||||
| bankName 等无强制要求 | 专票 taxNo + 银行四项全部必填(返回 581515) |
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
### 小程序开票页必须改动
|
||||
|
||||
| 模块 | 必须改动 |
|
||||
|------|---------|
|
||||
| 发票类型选择 | 去掉「电子发票」,只保留「增值税普通发票」/「增值税专用发票」 |
|
||||
| 专票表单区块 | 新增开户行、银行账号、注册地址、注册电话 4 个必填项,仅选专票时显示 |
|
||||
| 抬头类型联动 | 选专票时隐藏/禁用「个人」选项 |
|
||||
| email 输入框 | 改为必填(加红星),增加邮箱格式校验 |
|
||||
| mailAddress 输入框 | 移除,不再渲染邮寄地址块 |
|
||||
| 详情页 | 移除邮寄地址展示区,invoiceType 显示只有普票/专票两种文案 |
|
||||
|
||||
### 回滚方案
|
||||
|
||||
后端回滚至 PR #4292 前,ELECTRONIC 重新有效,email 恢复条件必填,mailAddress 重新可用。小程序需同步回滚开票页逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `ELECTRONIC` 已从枚举删除,小程序开票选项只有两项,禁止出现「电子发票」
|
||||
2. 重开接口(reissue)入参字段矩阵与 apply 完全相同,改动同步适用
|
||||
3. 专票五项(taxNo + 开户行 + 账号 + 注册地址 + 注册电话)在专票场景下全必填,缺一返回 581515
|
||||
4. 详情页 `mailAddress` 字段已消失,老版本小程序读到 undefined 需做好空值守卫(不报错)
|
||||
5. `amount` 为 JSON number,单位**元**(如 `986.00`),小程序输入框直接传元值,无需 x100
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue(功能)** | [#4290 发票申请票种修正](https://git.1814.love:8443/wx/HL/issues/4290) |
|
||||
| **Issue(小程序)** | [#4296 小程序发票申请同步修正](https://git.1814.love:8443/wx/HL/issues/4296) |
|
||||
| **PR(票种删 ELECTRONIC)** | [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) |
|
||||
| **PR(专票限公司/email 必填/删 mailAddress)** | [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,217 @@
|
||||
# 发票申请删 amount · 门槛放宽(小程序端)
|
||||
|
||||
- **接口**:POST /v3/internal/mp/order/{orderId}/invoice/apply
|
||||
- **变更类型**:修改接口,破坏性变更(申请入参删 amount;申请门槛扩展;新增错误码 581524)
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4353](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
小程序端发票申请接口(POST /v3/internal/mp/order/{orderId}/invoice/apply)本次同步两项改动:
|
||||
|
||||
1. **申请入参删 amount**:开票金额不再前端传入,开票时后端按规则自动计算(正常单=应收总额;取消单=净实收)。
|
||||
2. **申请门槛放宽**:原门槛仅允许 COMPLETED(已完成)订单申请;放宽为定制中及以后均可申请,同时支持已取消但净实收 > 0 的订单申请。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | 申请入参 amount | 必填字段,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 2 | 申请门槛(正常订单) | 订单状态必须 COMPLETED | 状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请 |
|
||||
| 3 | 申请门槛(取消订单) | 不允许 | CANCELLED 且净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 4 | 不满足门槛的错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED | ✨ 新增 581524 INVOICE_ORDER_NOT_APPLICABLE(更精确语义) |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | POST |
|
||||
| 路径 | /v3/internal/mp/order/{orderId}/invoice/apply |
|
||||
| 描述 | 客户自主申请开票(申请阶段不填金额,开票时后端自动算) |
|
||||
| 认证 | Bearer JWT(小程序用户) |
|
||||
| 幂等性 | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| orderId | string(Long 雪花) | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段(MpInvoiceApplyReqVO,⚠️ amount 已删)
|
||||
|
||||
当前完整字段列表(无 amount):
|
||||
|
||||
| 字段 | 类型 | 必填条件 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| invoiceType | string | 始终必填 | 发票类型,只有 VAT_NORMAL / VAT_SPECIAL 两值 |
|
||||
| titleType | string | 始终必填 | 抬头类型:COMPANY(单位)/ PERSONAL(个人);VAT_SPECIAL 只能 COMPANY |
|
||||
| titleName | string | 始终必填 | 发票抬头(企业全称或个人姓名) |
|
||||
| taxNo | string | titleType=COMPANY 或 invoiceType=VAT_SPECIAL 时必填 | 纳税人识别号 |
|
||||
| bankName | string | invoiceType=VAT_SPECIAL 时必填 | 开户银行名称 |
|
||||
| bankAccount | string | invoiceType=VAT_SPECIAL 时必填 | 银行账号 |
|
||||
| registAddress | string | invoiceType=VAT_SPECIAL 时必填 | 注册地址 |
|
||||
| registPhone | string | invoiceType=VAT_SPECIAL 时必填 | 注册电话 |
|
||||
| email | string | 始终必填 | 收件邮箱,需通过邮箱格式校验 |
|
||||
| remark | string | 选填 | 申请备注 |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应:成功返回新建发票 ID(字符串,雪花 ID)。
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 发票类型(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
|
||||
### 6.2 抬头类型(titleType)
|
||||
|
||||
| code | 说明 | 限制 |
|
||||
|------|------|------|
|
||||
| COMPANY | 单位 | 普票/专票均可 |
|
||||
| PERSONAL | 个人 | 只能选 VAT_NORMAL;选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
### 6.3 订单状态与可申请关系
|
||||
|
||||
| 订单状态(code) | 中文 | 可申请? | 说明 |
|
||||
|----------------|------|---------|------|
|
||||
| CUSTOMIZING | 定制中 | 是 | 放宽新增 |
|
||||
| PENDING_DEPARTURE | 待出行 | 是 | 放宽新增 |
|
||||
| TRAVELLING | 出行中 | 是 | 放宽新增 |
|
||||
| COMPLETED | 已完成 | 是 | 原有 |
|
||||
| CANCELLED | 已取消 | 条件是 | 净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 其他状态 | - | 否 | 报 581524 |
|
||||
|
||||
### 6.4 开票时金额自动计算规则
|
||||
|
||||
| 订单类型 | 金额计算公式 |
|
||||
|----------|-------------|
|
||||
| 正常单(非 CANCELLED) | 应收总额 = 订单总价 + 增项 - 优惠 |
|
||||
| 取消单(CANCELLED) | 净实收 = 已付金额 - 已退金额 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头/专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行/银行账号/注册地址/注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 收件邮箱为必填或格式错误(HTTP 400) |
|
||||
| 581518 | INVOICE_FORBIDDEN | 无权访问该订单(IDOR 防护) |
|
||||
| 581523 | INVOICE_VAT_SPECIAL_PERSONAL_FORBIDDEN | 专票只能开给单位 |
|
||||
| 581524 | INVOICE_ORDER_NOT_APPLICABLE | ✨ 新增:订单状态不在可申请集合内,或取消单净实收为 0 |
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——普票申请(无 amount 字段)
|
||||
|
||||
请求
|
||||
|
||||
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.2 边界情况——订单处于定制中提前申请
|
||||
|
||||
说明:订单状态 CUSTOMIZING(定制中),本次放宽后允许申请。开票金额将在财务执行开票操作时按当时应收总额计算。
|
||||
|
||||
请求(结构同 8.1,orderId 为定制中订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.3 业务失败——取消订单净实收为 0 被拒
|
||||
|
||||
说明:订单已取消,实付 1000 元且已全额退款,净实收 = 0,无法申请开票。
|
||||
|
||||
请求(结构同 8.1,orderId 为该取消订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
适用:
|
||||
- 订单状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请
|
||||
- CANCELLED 且净实收(已付 - 已退)> 0 时可申请
|
||||
|
||||
不适用:
|
||||
- PAID(已支付但未进入定制阶段)等其他状态报 581524
|
||||
- CANCELLED 且净实收 = 0 报 581524
|
||||
- 订单不属于当前登录用户,报 581518(IDOR 防护)
|
||||
|
||||
特殊边界:
|
||||
- 一单一票:同一订单只允许一张有效发票(REQUESTED/ISSUED/PUSHED 状态),重复申请报 581511
|
||||
- 开票金额由后端在财务执行 issue 操作时自动计算,小程序端申请时不确定最终金额
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
| 字段 / 规则 | 变更前 | 变更后 |
|
||||
|-------------|--------|--------|
|
||||
| 申请入参 amount | 必填,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 正常订单申请门槛 | 仅 COMPLETED | CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED |
|
||||
| 取消订单申请 | 不允许 | 净实收 > 0 时可申请 |
|
||||
| 不满足门槛错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED(已废弃) | 581524 INVOICE_ORDER_NOT_APPLICABLE(新增) |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:是
|
||||
|
||||
- 小程序申请发票表单必须删除金额输入框,不再传 amount 字段
|
||||
- 若现有流程中有基于 amount 的前端金额展示或校验逻辑,须同步移除
|
||||
- 不满足门槛的错误提示由旧 581510 改为 581524,前端若 hardcode 了 581510 的处理逻辑需同步更新
|
||||
|
||||
前端同步上线:申请表单删 amount 必须与后端同期上线。
|
||||
|
||||
回滚方案:回滚后端至 #4360 前版本,amount 字段恢复必填,申请门槛收回到仅 COMPLETED,581524 不再存在。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **amount 字段已删**:小程序申请发票表单必须移除金额输入框,不传 amount,传入后端会忽略。
|
||||
2. **申请门槛放宽的注意点**:定制中等状态提前申请,开票时金额由财务执行 issue 操作时后端按当时应收总额计算,小程序无法在申请时预知最终开票金额。
|
||||
3. **581524 vs 旧 581510**:新错误码 581524 取代了旧的 581510,含义更精确(包含取消单净实收为 0 的场景)。若前端有 581510 的 hardcode 判断需更新为 581524。
|
||||
4. **开票金额由财务操作确定**:小程序端只负责申请,金额由后端在财务开票(PUT issue)时写入,前端无法在申请时展示最终金额。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4353 发票模块门槛放宽与开票规则优化](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,58 @@
|
||||
# 订单详情「服务标准」Tab 退费说明(refundNotes)修复——现可正确返回数据
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **变更类型**:缺陷修复(接口结构不变,仅恢复数据流)
|
||||
- **日期**:2026-06-02
|
||||
- **Issue**:[#3354](https://git.1814.love:8443/wx/HL/issues/3354)
|
||||
- **PR**:[#3355](https://git.1814.love:8443/wx/HL/pulls/3355)
|
||||
- **Commit**:[db61c2d](https://git.1814.love:8443/wx/HL/commit/db61c2da1193be0e0954dacdfd8f154e3ba5d912)
|
||||
- **后端负责人**:wx
|
||||
|
||||
---
|
||||
|
||||
## ① 背景
|
||||
|
||||
订单详情「服务标准」Tab 接口(`GET /v3/admin/order/{id}/service-standard`,#3340 已上线的聚合结构)中,`refundNotes`(退费说明)字段此前一直返回空数组,即使绑定资源已配置退费说明。
|
||||
|
||||
根因是**资源退费说明根本存不进库**:资源服务自定义的字段自动填充处理器只填一期命名字段(createdAt/updatedAt),漏填二期命名字段(createTime),导致退费说明保存接口报错(create_time 不能为空),全表 0 条存活记录 → 下单冻快照时节点 `refundNote` 恒 null → 服务标准 Tab 退费说明永远为空。
|
||||
|
||||
本次修复后端自动填充处理器,退费说明可正常保存并随订单快照冻结。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
**接口结构无任何变化**(`refundNotes` 字段在 #3340 已定义)。修复后行为:
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更内容 |
|
||||
|------|------|------|----------|
|
||||
| 订单服务标准 | GET | `/v3/admin/order/{id}/service-standard` | `refundNotes` 现对「绑定了退费说明的 SCENIC/ACTIVITY 行程节点」正确返回数据(修复前恒为空) |
|
||||
|
||||
`refundNotes[]` 结构(不变,复述):
|
||||
|
||||
```
|
||||
refundNotes: [
|
||||
{
|
||||
sourceName, // 来源点位名称,如「呼和诺尔草原旅游区」
|
||||
intro, // 退费说明备注
|
||||
items: [
|
||||
{ title, amount, unitLabel, settleScope, settleScopeLabel, remark, effectiveFrom, effectiveTo }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
测试服实测(订单 2061673442020651009):`refundNotes` = 呼和诺尔草原旅游区 → [成人未参加 ¥44 /人(按人), 整团未到 ¥100 /团(按团)],`settleScopeLabel` 字典中文已正确解析。
|
||||
|
||||
---
|
||||
|
||||
## ③ 前端动作
|
||||
|
||||
**无需改动**。#3340 前端已对接 `refundNotes` 结构,仅需知悉:之前看到的退费说明为空是后端缺陷,现已修复,对已配置退费说明的资源会正常返回。
|
||||
|
||||
---
|
||||
|
||||
## ④ 备注
|
||||
|
||||
- 同一 Tab 的「出团注意事项」(`notices`)能否有数据,取决于**产品是否配置了「服务标准模板」**(产品域 Step5 补充信息配置项)。当前测试库暂无产品配置该模板,故 `notices` 仍为空;这属于数据配置,非接口缺陷,与本次修复无关。
|
||||
- 「行程」(`itinerary`)一直正常。
|
||||
@@ -0,0 +1,182 @@
|
||||
# 二期 v3:订单详情「服务标准」Tab 接口返回真实成品数据 + notices 新增 title 字段
|
||||
|
||||
> **服务**: hl-order-service-v3(产品侧 hl-product-service-v2 配合改造,前端无感)
|
||||
> **端**: 管理后台
|
||||
> **接口**: `GET /v3/admin/order/{id}/service-standard`(订单详情 - 服务标准 Tab)
|
||||
> **类型**: ✏️ 修改接口(出参 `notices[]` 新增 `title` 字段 + 接口由恒空改为返回真实成品)
|
||||
> **日期**: 2026-06-02
|
||||
> **关联**: Issue #3363 / PR #3364(前序 #3340 建接口、#3353 改只读快照)
|
||||
|
||||
---
|
||||
|
||||
## 一句话结论
|
||||
|
||||
1. 该接口此前对**所有订单恒返 `data: null`**(产品侧没生产服务标准成品)。本次产品侧补上生产端,**新下单的订单**会返回完整服务标准成品(标题 / 简介 / 服务承诺 / 行程 / 退费说明)。
|
||||
2. 出参 `notices[]`(服务承诺条目)**每条新增 `title` 字段**。
|
||||
|
||||
> ⚠️ 仅对**部署之后新创建**的订单生效。部署前的老订单仍返 `null`(其快照未冻入成品)。开发阶段老订单可忽略。
|
||||
|
||||
---
|
||||
|
||||
## 出参结构(完整,自包含)
|
||||
|
||||
`data` 类型 `ServiceStandardVO`,`data` 为 `null` 表示该订单无服务标准成品。
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `title` | string | 标题,固定为 `"出团服务标准·" + 产品名`;产品名为空时退化为 `"出团服务标准"` | 否 |
|
||||
| `subtitle` | string | 副标题 | **本期恒 null** |
|
||||
| `intro` | string | 服务标准简介 | 是(产品未配服务标准模板时为 null) |
|
||||
| `notices` | NoticeItem[] | 服务承诺条目(扁平,无分组) | 是(无源时为空数组 `[]`) |
|
||||
| `itinerary` | DayVO[] | 行程逐天列表 | 是(无行程时空数组) |
|
||||
| `refundNotes` | RefundNoteGroup[] | 退费说明分组(按行程节点聚合) | 是(无退费说明时空数组) |
|
||||
|
||||
### NoticeItem(服务承诺条目)
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `title` | string | **本次新增**。条目主文案标题(如「专业司机」) | 是 |
|
||||
| `content` | string | 正文(如「持有 A1 驾照,8 年以上驾龄」) | 是 |
|
||||
| `remark` | string | 备注 / 灰色二级说明 | 是 |
|
||||
| `color` | string | 文案颜色 `#RRGGBB` | 是 |
|
||||
| `contactName` | string | 联系人 | 是 |
|
||||
| `phone` | string | 手机号 | 是 |
|
||||
|
||||
### DayVO(行程天)
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `dayNumber` | integer | 天序号(从 1 起) | 否 |
|
||||
| `dayTitle` | string | 天标题(如「第一天-接机」) | 是 |
|
||||
| `remark` | string | 当天备注 | **本期恒 null** |
|
||||
| `itineraryNode` | ItineraryNode[] | 当天点位列表 | 是 |
|
||||
|
||||
### ItineraryNode(行程点位)
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `nodeName` | string | 点位名称(如「呼和诺尔草原旅游区」「早餐」) | 否 |
|
||||
| `description` | string | 点位描述 | 是 |
|
||||
| `contactName` | string | 联系人 | **本期恒 null** |
|
||||
| `phone` | string | 手机号 | **本期恒 null** |
|
||||
|
||||
### RefundNoteGroup(退费说明分组)
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `sourceName` | string | 来源点位名称(取行程节点名) | 否 |
|
||||
| `intro` | string | 退费说明备注(如「苔藓为赠送项目,不退费」) | 是 |
|
||||
| `items` | RefundItem[] | 退费明细列表 | 是 |
|
||||
|
||||
### RefundItem(退费明细条目)
|
||||
|
||||
| 字段 | 类型 | 说明 | 可空 |
|
||||
|---|---|---|---|
|
||||
| `title` | string | 展示标题(如「成人未参加」) | 是 |
|
||||
| `amount` | number | 退费金额(赠送项目为 0) | 是 |
|
||||
| `unitLabel` | string | 展示文案:`/人` `/团` `/辆` | 是 |
|
||||
| `settleScope` | string | 结算粒度枚举(见下) | 是 |
|
||||
| `settleScopeLabel` | string | 结算粒度中文(冻结即定格) | 是 |
|
||||
| `remark` | string | 备注 | 是 |
|
||||
| `effectiveFrom` | string(date) | 规则生效起日,`null` = 无限制 | 是 |
|
||||
| `effectiveTo` | string(date) | 规则生效止日,`null` = 无限制 | 是 |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### settleScope(结算粒度)
|
||||
|
||||
| 枚举值 | settleScopeLabel(中文) | 配套 unitLabel |
|
||||
|---|---|---|
|
||||
| `PER_PERSON` | 按人 | /人 |
|
||||
| `PER_TEAM` | 按团 | /团 |
|
||||
| `PER_VEHICLE` | 按车 | /辆 |
|
||||
|
||||
> `settleScopeLabel` 在下单时随快照冻结,定格当时字典中文,资源后改不影响老订单。
|
||||
|
||||
---
|
||||
|
||||
## 请求示例
|
||||
|
||||
```
|
||||
GET /v3/admin/order/2061762043987247105/service-standard
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
## 响应示例(真实,测试服 dev-v3 实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"title": "出团服务标准·测试核心产品-单档-固定订金",
|
||||
"subtitle": null,
|
||||
"intro": "全程贴心服务保障",
|
||||
"notices": [
|
||||
{
|
||||
"title": "专业司机",
|
||||
"content": "持有A1驾照,8年以上驾龄",
|
||||
"remark": "仅限指定时段",
|
||||
"color": "#FF6600",
|
||||
"contactName": "李师傅",
|
||||
"phone": "13800000000"
|
||||
}
|
||||
],
|
||||
"itinerary": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"dayTitle": "第一天-接机",
|
||||
"remark": null,
|
||||
"itineraryNode": [
|
||||
{ "nodeName": "海拉尔接机", "description": null, "contactName": null, "phone": null },
|
||||
{ "nodeName": "早餐", "description": "含(酒店)", "contactName": null, "phone": null }
|
||||
]
|
||||
}
|
||||
],
|
||||
"refundNotes": [
|
||||
{
|
||||
"sourceName": "呼和诺尔草原旅游区",
|
||||
"intro": "苔藓为赠送项目,不退费",
|
||||
"items": [
|
||||
{
|
||||
"title": "成人未参加", "amount": 44.0, "unitLabel": "/人",
|
||||
"settleScope": "PER_PERSON", "settleScopeLabel": "按人",
|
||||
"remark": "凭票退", "effectiveFrom": null, "effectiveTo": null
|
||||
},
|
||||
{
|
||||
"title": "整团未到", "amount": 100.0, "unitLabel": "/团",
|
||||
"settleScope": "PER_TEAM", "settleScopeLabel": "按团",
|
||||
"remark": null, "effectiveFrom": null, "effectiveTo": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 响应示例(老订单 / 无成品)
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界 / 注意事项
|
||||
|
||||
- `data: null` 是合法返回,表示该订单无服务标准成品(部署前老订单 / 产品未配服务标准)。
|
||||
- `intro`、`notices` 来源于产品「服务标准模板」:产品未绑模板时,`intro` 为 `null`、`notices` 为 `[]`,但 `title` / `itinerary` / `refundNotes`(有各自数据源时)仍正常返回。
|
||||
- `subtitle`、`DayVO.remark`、`ItineraryNode.contactName/phone` 本期固定为 `null`(预留字段)。
|
||||
- 成品在下单时随产品快照整体冻结,资源 / 产品后续修改不影响已下单订单。
|
||||
|
||||
---
|
||||
|
||||
## 关联
|
||||
|
||||
- Issue: https://git.1814.love:8443/wx/HL/issues/3363
|
||||
- PR: https://git.1814.love:8443/wx/HL/pulls/3364
|
||||
- 前序: #3340(建接口)/ #3353(改只读快照)
|
||||
@@ -0,0 +1,155 @@
|
||||
# 二期 v3:订单进度模型重构 —— 派生线性 6 步 + 嵌套 progressStepper
|
||||
|
||||
> **服务**: hl-order-service-v3 | **端**: 管理后台
|
||||
> **接口**: `GET /v3/admin/order`(列表)、`GET /v3/admin/order/{id}`(详情)、`POST /v3/admin/order`(创建)
|
||||
> **Issue**: #3365 / #3368 | **PR**: #3366(扁平)+ #3369(嵌套)
|
||||
> **日期**: 2026-06-03
|
||||
> **影响**: ⚠️ **破坏性**——删字段 + 改结构。订单进度展示统一为「派生线性 6 步 + 嵌套 progressStepper」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
订单进度此前两套打架:列表用 8 步 `flowStep`,详情用 10 节点 `progressStepper`,同一订单两页进度刻度对不上。本次统一为**派生双层模型**(底层一份数据派生两视图),并修正了核单/结算判定 bug。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 变更 | 说明 |
|
||||
|---|---|
|
||||
| ❌ 删 `flowItems` | 列表不再返回 |
|
||||
| ❌ 删 `progress`("X/8") | 列表不再返回 |
|
||||
| 🔄 `flowStep` 语义改 | 8 步指针 → **线性 6 步**当前步序号 |
|
||||
| 🔄 `flowStepTotal` | 8 → **6** |
|
||||
| 🆕 `flowStepCode` | 当前步英文枚举(列表+详情) |
|
||||
| 🆕 `flowStepStatus` | 当前步状态(列表+详情) |
|
||||
| 🆕 `currentSubFlows`(列表) | 当前步=资源准备时的 4 子流程数组 |
|
||||
| 🔄 `progressStepper`(详情) | 扁平 9 节点 → **嵌套 6 主节点**,加 `code`/`isCurrent`/`subFlows`,删 `parallel` |
|
||||
|
||||
> `orderStatus`/`orderStatusName`/`flowStatus`/`flowStatusName` **不变**。
|
||||
|
||||
---
|
||||
|
||||
## 三、出参字段(进度部分)
|
||||
|
||||
### 列表 `GET /v3/admin/order`(`data.records[]`,当前步散字段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `flowStep` | Integer | 当前第几步(1-6;待支付=0;已取消=null) |
|
||||
| `flowStepTotal` | Integer | 总步数,固定 **6** |
|
||||
| `flowStepCode` | String | 当前步英文枚举(见枚举表) |
|
||||
| `flowDisplayText` | String | 当前步中文名 |
|
||||
| `flowStepStatus` | String | 当前步状态:`DONE`/`PROCESSING`/`WAITING` |
|
||||
| `currentSubFlows` | Array\<SubFlow\> | **仅当前步=资源准备时非空**,否则 `null` |
|
||||
|
||||
### 详情 `GET /v3/admin/order/{id}`(`data.main`)
|
||||
|
||||
列表那 6 个散字段**全有**,外加完整 `progressStepper`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `progressStepper` | Array\<Node\> | 完整 6 主节点(嵌套) |
|
||||
|
||||
**Node 结构**:`step`(Integer) / `code`(String) / `name`(String) / `status`(String) / `isCurrent`(Boolean) / `subFlows`(Array\<SubFlow\>,仅资源准备非空)
|
||||
**SubFlow 结构**:`code`(String) / `name`(String) / `status`(String) / `label`(String)
|
||||
|
||||
---
|
||||
|
||||
## 四、枚举 / 数据字典
|
||||
|
||||
### 主步 `code`(6 步)
|
||||
|
||||
| code | name | 完成判定 |
|
||||
|---|---|---|
|
||||
| `PROFILE` | 补全信息 | 已支付 |
|
||||
| `RESOURCE` | 资源准备 | 配房完成(降级,见业务边界) |
|
||||
| `CONFIRM` | 确认 | 订单已确认 |
|
||||
| `DEPART` | 出行 | 已出行 |
|
||||
| `REVIEW` | 核单 | 已核单 |
|
||||
| `SETTLE` | 结算 | 已结算 |
|
||||
|
||||
### 子流程 `code`(资源准备下,4 条)
|
||||
|
||||
| code | name |
|
||||
|---|---|
|
||||
| `HOTEL` | 配房 |
|
||||
| `VEHICLE` | 配车 |
|
||||
| `GUIDE` | 领队 |
|
||||
| `PHOTOGRAPHER` | 摄影 |
|
||||
|
||||
### `status`(主步 + 子流程通用)
|
||||
|
||||
| status | 含义 |
|
||||
|---|---|
|
||||
| `DONE` | 已完成 |
|
||||
| `PROCESSING` | 处理中 |
|
||||
| `WAITING` | 待开始 |
|
||||
|
||||
### `flowStep` 序号
|
||||
|
||||
`0`=待支付(步骤条未开始)|`1-6`=对应主步|`null`=已取消(前端不画进度条)
|
||||
|
||||
---
|
||||
|
||||
## 五、示例
|
||||
|
||||
### 列表(定制中、配房完成、配车进行中)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"orderStatus": "CUSTOMIZING", "orderStatusName": "定制中",
|
||||
"flowStep": 2, "flowStepTotal": 6,
|
||||
"flowStepCode": "RESOURCE", "flowDisplayText": "资源准备", "flowStepStatus": "PROCESSING",
|
||||
"currentSubFlows": [
|
||||
{"code":"HOTEL","name":"配房","status":"DONE","label":"已完成"},
|
||||
{"code":"VEHICLE","name":"配车","status":"PROCESSING","label":"处理中"},
|
||||
{"code":"GUIDE","name":"领队","status":"WAITING","label":"待开始"},
|
||||
{"code":"PHOTOGRAPHER","name":"摄影","status":"WAITING","label":"待开始"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 详情 progressStepper(同一订单)
|
||||
|
||||
```jsonc
|
||||
"progressStepper": [
|
||||
{"step":1,"code":"PROFILE","name":"补全信息","status":"DONE","isCurrent":false,"subFlows":null},
|
||||
{"step":2,"code":"RESOURCE","name":"资源准备","status":"PROCESSING","isCurrent":true,"subFlows":[
|
||||
{"code":"HOTEL","name":"配房","status":"DONE","label":"已完成"},
|
||||
{"code":"VEHICLE","name":"配车","status":"PROCESSING","label":"处理中"},
|
||||
{"code":"GUIDE","name":"领队","status":"WAITING","label":"待开始"},
|
||||
{"code":"PHOTOGRAPHER","name":"摄影","status":"WAITING","label":"待开始"}
|
||||
]},
|
||||
{"step":3,"code":"CONFIRM","name":"确认","status":"WAITING","isCurrent":false,"subFlows":null},
|
||||
{"step":4,"code":"DEPART","name":"出行","status":"WAITING","isCurrent":false,"subFlows":null},
|
||||
{"step":5,"code":"REVIEW","name":"核单","status":"WAITING","isCurrent":false,"subFlows":null},
|
||||
{"step":6,"code":"SETTLE","name":"结算","status":"WAITING","isCurrent":false,"subFlows":null}
|
||||
]
|
||||
```
|
||||
|
||||
### 已取消订单
|
||||
|
||||
```jsonc
|
||||
{ "flowStep": null, "flowStepCode": null, "flowDisplayText": "已取消",
|
||||
"flowStepStatus": null, "currentSubFlows": null }
|
||||
// 详情 progressStepper 返回空数组 []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、业务边界 / 前端注意
|
||||
|
||||
1. **当前步两个口子都能拿**:列表用 `flowStep`;详情用 `isCurrent==true` 的节点(和 `flowStep` 指向同一步)。
|
||||
2. **⚠️ 降级现状**:资源准备步现**只判配房**——配车/领队/摄影的子状态回写链路尚未接通,所以 `VEHICLE` 子流程恒 `PROCESSING`、`GUIDE`/`PHOTOGRAPHER` 恒 `WAITING`,暂不会变 `DONE`。资源准备只要配房 `DONE` 即整步完成。回写接通后会纳入 4 线全 DONE。
|
||||
3. **⚠️ 进度展示请用 `flowStep`/`flowDisplayText`,不要用 `flowStatusName`**——`flowStatus` 在"定制中"阶段恒为 `AWAITING_PROFILE`(待补全信息)不流转,用它展示进度会一直显示"待补全信息",不准。
|
||||
4. `currentSubFlows`/`subFlows` 只在资源准备步有值,其余步为 `null`,前端渲染需判空。
|
||||
|
||||
---
|
||||
|
||||
## 七、关联
|
||||
|
||||
- **Issue**: [#3365](https://git.1814.love:8443/wx/HL/issues/3365)、[#3368](https://git.1814.love:8443/wx/HL/issues/3368)
|
||||
- **PR**: [#3366](https://git.1814.love:8443/wx/HL/pulls/3366)(派生 6 步 + 修 reviewStatus bug)、[#3369](https://git.1814.love:8443/wx/HL/pulls/3369)(嵌套 progressStepper)
|
||||
- **设计文档**: ORDER-STATE-MACHINE V1.1 §2.2、API-SPEC v5.56
|
||||
@@ -0,0 +1,124 @@
|
||||
# 【需求·管理后台】资源默认收费 + 收费=否禁价格日历 + 退费说明接入(景区 / 活动 / 酒店)
|
||||
|
||||
> PR: #3392 #3395(工单 #3389) 服务: hl-resource-service | 更新时间: 2026-06-03
|
||||
> 存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「资源管理 / 景区·游玩项目·酒店」编辑页
|
||||
> 状态: 已合并 dev-v3 + 测试服部署双实例 + API 实测通过
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
来自景区编辑页的 3 点反馈,后端已处理(部分是前端没接已实现的接口):
|
||||
|
||||
1. **默认收费**:景区 / 游玩项目 / 酒店新建时「是否收费」默认改为 **收费(1)**。前端开关初始态请置为「收费」开。
|
||||
2. **收费=否 → 没有价格日历**:前端在「是否收费=否」时**隐藏价格日历区**;后端已加兜底——给收费=否的景区 / 活动设价会被拒(错误码 **390901**)。
|
||||
3. **价格日历不做合并接口**:基本信息与价格日历仍是两个接口,前端保存时**分两次调**(先存基本信息,再批量设价)。
|
||||
4. **退费说明**:后端早已实现(`/admin/refund-note`,覆盖景区+活动),**前端编辑页缺这块 UI**,本文补齐接口契约请接入。
|
||||
|
||||
服务资源 ServiceItem 的「是否收费」是 `isPaid`+真实定价语义(另有定价校验),**不在本次默认收费范围**,保持现状。
|
||||
|
||||
## 1. 默认收费(景区 / 活动 / 酒店)
|
||||
|
||||
新建资源不传 `isCharged` 时,后端默认 **1=收费**;前端显式传 `0` 仍尊重为「否」。
|
||||
|
||||
| 资源 | 字段 | 新建接口 | 默认值 |
|
||||
|------|------|---------|--------|
|
||||
| 景区 Scenic | `isCharged` | POST `/admin/scenic/spot` | 1=收费 |
|
||||
| 游玩项目 Activity | `isCharged` | POST `/admin/activity/item` | 1=收费 |
|
||||
| 酒店 Hotel | `isCharged` | POST `/admin/hotel/item` | 1=收费 |
|
||||
|
||||
前端动作:新建表单「是否收费」开关初始置「收费(开)」。编辑回显仍读后端返回的 `isCharged`。
|
||||
|
||||
## 2. 收费=否 → 无价格日历
|
||||
|
||||
### 2.1 前端
|
||||
「是否收费=否」时**隐藏价格日历区**(不展示、不允许设价)。
|
||||
|
||||
### 2.2 后端兜底(防脏数据)
|
||||
对收费=否(`isCharged=0`)的景区 / 活动调批量设价接口,后端直接拒绝:
|
||||
|
||||
```bash
|
||||
# 收费=否的景区设价 → 被拦截
|
||||
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{"startDate":"2026-07-01","endDate":"2026-07-03","costPrice":100,"status":1}' \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/{scenicId}/prices"
|
||||
```
|
||||
|
||||
响应(测试服实测):
|
||||
|
||||
```json
|
||||
{ "code": 390901, "message": "资源未设为收费,不可设置价格日历" }
|
||||
```
|
||||
|
||||
- 仅拦**批量设价** `PUT /spot/{id}/prices`;查询、改可售状态、清除价格不受影响。
|
||||
- 酒店价格日历是 room-type 级,本期不做后端兜底,前端按 2.1 隐藏即可。
|
||||
|
||||
## 3. 价格日历保存方式(分两次调,不合并接口)
|
||||
|
||||
经确认**不新增合并接口**。前端编辑保存时分两次调用现有接口:
|
||||
|
||||
1. 先存基本信息:`PUT /admin/scenic/spot/{scenicId}`
|
||||
2. 再批量设价:`PUT /admin/scenic/spot/{scenicId}/prices`(仅在收费=是时调)
|
||||
|
||||
## 4. 退费说明接入(景区 / 活动)
|
||||
|
||||
> ⚠️ **本节已废弃(2026-06-03 更新)**:退费说明**已改为融合进资源编辑接口**(不再走独立 `/admin/refund-note`),并扩展支持**服务 SERVICE**。前端请按新 changelog `03_3406_退费说明融合进资源编辑接口-景区活动服务-管理后台.md` 接入,**勿按下方独立接口接**。下方内容仅作历史留存。
|
||||
|
||||
后端模块早已上线(PR #3273 等),前端编辑页缺 UI,请接入。仅覆盖 **SCENIC / ACTIVITY** 两类。
|
||||
|
||||
### 4.1 接口表
|
||||
|
||||
| 操作 | 方法 / 路径 | 参数 |
|
||||
|------|------------|------|
|
||||
| 查询 | GET `/admin/refund-note` | query: `resourceType`(SCENIC/ACTIVITY) + `resourceId`。未配置返 `data:null`(不报错) |
|
||||
| 保存(upsert) | PUT `/admin/refund-note` | body: RefundNoteSaveReqVO(按 resourceType+resourceId upsert 整块) |
|
||||
| 删除(软删) | DELETE `/admin/refund-note` | query: `resourceType` + `resourceId`(幂等,未配置也返 code:200 data:false) |
|
||||
|
||||
### 4.2 保存请求体(RefundNoteSaveReqVO)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| resourceType | String | 是 | SCENIC / ACTIVITY |
|
||||
| resourceId | Long | 是 | 关联资源 ID |
|
||||
| intro | String | 否 | 资源级备注(≤255 字),如「苔藓为赠送项目,不退费」 |
|
||||
| items | List | 是 | 退费明细,至少 1 条、最多 50 条 |
|
||||
|
||||
items[] 每项(RefundNoteItemVO):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| title | String | 是 | 展示标题,如「成人未参加」 |
|
||||
| amount | BigDecimal | 是 | 退费金额(赠送项目填 0) |
|
||||
| unitLabel | String | 否 | 展示文案 /人 /团 /辆 |
|
||||
| settleScope | String | 否 | 结算粒度 PER_PERSON / PER_TEAM / PER_VEHICLE |
|
||||
| settleScopeLabel | String | — | 结算粒度中文名(出参后端拼,入参可省) |
|
||||
| remark | String | 否 | 备注,如「仅限儿童」 |
|
||||
| effectiveFrom | LocalDate | 否 | 规则生效起日(null=无限制) |
|
||||
| effectiveTo | LocalDate | 否 | 规则生效止日(null=无限制) |
|
||||
|
||||
### 4.3 示例
|
||||
|
||||
```bash
|
||||
# 查询景区退费说明
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
"https://api.test.1814.love:9443/admin/refund-note?resourceType=SCENIC&resourceId=3001000000000000019"
|
||||
|
||||
# 保存
|
||||
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"resourceType": "SCENIC",
|
||||
"resourceId": 3001000000000000019,
|
||||
"intro": "苔藓为赠送项目,不退费",
|
||||
"items": [
|
||||
{"title":"成人未参加","amount":44.00,"unitLabel":"/人","settleScope":"PER_PERSON","remark":"仅限成人"}
|
||||
]
|
||||
}' \
|
||||
"https://api.test.1814.love:9443/admin/refund-note"
|
||||
```
|
||||
|
||||
查询响应(RefundNoteRespVO):`noteId / resourceType / resourceId / intro / items[] / createTime / updateTime`,`noteId` 字符串透传防精度丢失,`settleScopeLabel` 后端回填中文。
|
||||
|
||||
## 5. 前端动作清单
|
||||
|
||||
1. 景区 / 活动 / 酒店新建表单「是否收费」开关默认置「收费」。
|
||||
2. 「是否收费=否」时隐藏价格日历区(编辑页 + 新建页)。
|
||||
3. 保存仍分两次调(基本信息 + 价格日历),价格日历仅收费=是时调。
|
||||
4. 景区 / 活动编辑页接入「退费说明」模块(GET/PUT/DELETE `/admin/refund-note`)。
|
||||
@@ -0,0 +1,559 @@
|
||||
# 【新增接口·管理后台 + 小程序】图标库模块 11 接口(全模块首次推送 changelog)
|
||||
|
||||
> **PR**: #3396(模块主体)+ #3398 / #3399(mp 端点公开化)+ #3401(上线后审计加固)
|
||||
> **服务**: hl-user-service | **更新时间**: 2026-06-03
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「图标库」管理页(分类 + 图标 CRUD + SVG 上传解析);小程序 / 前端 图标渲染(公开只读查询)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
图标库是后台统一维护的一套 SVG 图标,结构类似数据字典:一级「图标分类」(`icon_category`) + 二级「图标项」(`icon_item`),图标项归属某分类编码 `categoryCode`。
|
||||
|
||||
**SVG 直接存库(不走 OSS)**:图标内容是 SVG 字符串,直存数据库 `svg_content`(MEDIUMTEXT,单图标后端限 256KB)。前端拿到 `svgContent` 直接渲染。
|
||||
|
||||
**新增图标分两步**:先调 §3.5 `POST /admin/icon/parse-svg` 上传 `.svg` 文件,后端读成字符串并做安全校验后**只返回字符串、不落库**;前端拿到 `svgContent` 再连同 `color` / `size` 等填进保存表单调 §3.6 `POST /admin/icon/item` 保存。
|
||||
|
||||
**mp 查询公开无需 token**:§3.10 / §3.11 是 UI 参考数据(同字典),无需登录即可访问,只返回 `status=ACTIVE` 的分类与图标,且分类被禁用时其图标也不再返回。
|
||||
|
||||
**SVG 安全校验**:上传 / 保存的 SVG 经服务端 XXE-safe 解析校验,拒绝含 `<script>` / `<foreignObject>` 元素、`on*` 事件属性、`javascript:` 外链的内容;前端渲染时仍建议做一次 sanitize 作纵深防御。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
运营在后台「图标库」页统一维护 SVG 图标,供小程序 / 前端按分类拉取渲染(如分类入口图标、标签图标等)。
|
||||
|
||||
管理后台「图标库」页提供:
|
||||
- 分类维护(列表 / 分页 / 新增 / 编辑 / 删除)
|
||||
- 图标维护(分页 / 详情 / 新增 / 编辑 / 删除)
|
||||
- 上传 SVG 文件解析为字符串
|
||||
|
||||
小程序 / 前端:按分类拉启用图标列表渲染(公开只读)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
### 管理后台(`/admin/icon/**`,需 admin JWT)
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|----------|------|
|
||||
| 1 | POST | `/admin/icon/category` | 首推 | 保存分类(id 空=新建,非空=修改) |
|
||||
| 2 | GET | `/admin/icon/category/page` | 首推 | 分类分页(status / keyword 过滤) |
|
||||
| 3 | GET | `/admin/icon/category/list` | 首推 | 启用分类列表(下拉用) |
|
||||
| 4 | DELETE | `/admin/icon/category/{id}` | 首推 | 删除分类(其下有图标项时拒删) |
|
||||
| 5 | POST | `/admin/icon/parse-svg` | 首推 | 上传 SVG 文件解析为字符串(不落库) |
|
||||
| 6 | POST | `/admin/icon/item` | 首推 | 保存图标(id 空=新建,非空=修改) |
|
||||
| 7 | GET | `/admin/icon/item/page` | 首推 | 图标分页(categoryCode / keyword 过滤) |
|
||||
| 8 | GET | `/admin/icon/item/{id}` | 首推 | 图标详情(含 svgContent) |
|
||||
| 9 | DELETE | `/admin/icon/item/{id}` | 首推 | 删除图标 |
|
||||
|
||||
### 小程序 / 前端(`/mp/icon/**`,公开无需 token)
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|----------|------|
|
||||
| 10 | GET | `/mp/icon/categories` | 首推 | 启用分类列表 |
|
||||
| 11 | GET | `/mp/icon/items` | 首推 | 某分类下启用图标列表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 保存分类
|
||||
|
||||
**POST** `/admin/icon/category`
|
||||
|
||||
- **使用场景**:图标库分类新增 / 编辑
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:否(同 `categoryCode` 重复新建抛 210701)
|
||||
|
||||
**Body 入参**(`IconCategorySaveReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | Long | ❌ | — | 空=新建,非空=修改 |
|
||||
| `categoryCode` | string | ✅ | `@Size(max=64)` | 分类编码(全局唯一,创建后不可改) |
|
||||
| `categoryName` | string | ✅ | `@Size(max=100)` | 分类名称 |
|
||||
| `sort` | int | ❌ | — | 排序号(升序),默认 0 |
|
||||
| `status` | string | ❌ | `@Pattern(ACTIVE\|DISABLED)` | 状态,默认 ACTIVE |
|
||||
| `remark` | string | ❌ | `@Size(max=255)` | 备注 |
|
||||
|
||||
**出参**:`Result<Long>`(`data` = 分类 ID)
|
||||
|
||||
**典型示例 请求**:
|
||||
```
|
||||
POST /admin/icon/category
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{ "categoryCode": "weather", "categoryName": "天气", "sort": 1 }
|
||||
```
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{ "code": 200, "data": 2062087054098898945, "message": "成功" }
|
||||
```
|
||||
|
||||
**异常 响应**(编码重复):
|
||||
```json
|
||||
{ "code": 210701, "message": "图标分类编码已存在", "data": null }
|
||||
```
|
||||
|
||||
**错误码**:400 参数校验 / **210701** 分类编码已存在 / **210702** 分类不存在(修改时 id 无效)/ 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.2 分类分页
|
||||
|
||||
**GET** `/admin/icon/category/page`
|
||||
|
||||
- **使用场景**:分类管理列表页
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是(只读)
|
||||
|
||||
**Query 入参**(`IconCategoryPageReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `page` | int | ❌ | 1 | 页码 |
|
||||
| `pageSize` | int | ❌ | 20 | 每页条数 |
|
||||
| `status` | string | ❌ | — | 状态精确过滤(ACTIVE / DISABLED) |
|
||||
| `keyword` | string | ❌ | — | 模糊匹配分类编码 / 名称 |
|
||||
|
||||
**出参**:`Result<PageResult<IconCategoryRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `records[].id` | string | 分类 ID(Long 序列化为 String) |
|
||||
| `records[].categoryCode` | string | 分类编码 |
|
||||
| `records[].categoryName` | string | 分类名称 |
|
||||
| `records[].sort` | int | 排序号 |
|
||||
| `records[].status` | string | 状态(ACTIVE / DISABLED) |
|
||||
| `records[].remark` | string | 备注 |
|
||||
| `records[].createTime` | datetime | 创建时间 |
|
||||
| `total` / `page` / `pageSize` | int | 分页元数据 |
|
||||
|
||||
**错误码**:400 参数校验 / 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.3 启用分类列表
|
||||
|
||||
**GET** `/admin/icon/category/list`
|
||||
|
||||
- **使用场景**:图标新增页的分类下拉
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是
|
||||
|
||||
**入参**:无
|
||||
|
||||
**出参**:`Result<List<IconCategoryRespVO>>`(仅 `status=ACTIVE`,按 sort 升序)
|
||||
|
||||
**错误码**:401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.4 删除分类
|
||||
|
||||
**DELETE** `/admin/icon/category/{id}`
|
||||
|
||||
- **使用场景**:删除空分类
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是
|
||||
|
||||
**路径入参**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `id` | Long | ✅ | 分类 ID |
|
||||
|
||||
**出参**:`Result<Void>`(`data: null`)
|
||||
|
||||
**异常 响应**(分类下仍有图标项):
|
||||
```json
|
||||
{ "code": 210703, "message": "该分类下存在图标项,无法删除", "data": null }
|
||||
```
|
||||
|
||||
**错误码**:**210702** 分类不存在 / **210703** 分类下存在图标项不可删 / 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.5 上传 SVG 解析(保存图标前置步骤)
|
||||
|
||||
**POST** `/admin/icon/parse-svg`
|
||||
|
||||
- **使用场景**:新增图标前上传 `.svg` 文件,拿到 SVG 字符串回填表单
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是(仅解析,不落库)
|
||||
|
||||
**入参**(multipart/form-data):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `file` | file | ✅ | SVG 文件(`.svg`) |
|
||||
|
||||
**出参**:`Result<IconSvgParseRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `svgContent` | string | 解析出的 SVG 字符串(回填保存表单) |
|
||||
| `fileName` | string | 原始文件名 |
|
||||
| `size` | long | 文件字节数 |
|
||||
|
||||
**典型示例 请求**:
|
||||
```
|
||||
POST /admin/icon/parse-svg
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data; boundary=...
|
||||
|
||||
(form-data: file=@sunny.svg)
|
||||
```
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
|
||||
"fileName": "sunny.svg",
|
||||
"size": 1234
|
||||
},
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
**错误码**:**210753** 文件为空 / **210754** 非合法 SVG(非 XML 或根元素不是 svg)/ **210755** 超 256KB / **210756** 文件读取失败 / **210757** 含不安全内容(脚本 / 事件 / 外链)/ 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.6 保存图标
|
||||
|
||||
**POST** `/admin/icon/item`
|
||||
|
||||
- **使用场景**:图标新增 / 编辑
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:否(同分类 `iconCode` 重复新建抛 210751)
|
||||
|
||||
**Body 入参**(`IconItemSaveReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | Long | ❌ | — | 空=新建,非空=修改 |
|
||||
| `categoryCode` | string | ✅ | `@Size(max=64)` | 所属分类编码(新建校验分类存在;**修改时不可改**) |
|
||||
| `iconCode` | string | ✅ | `@Size(max=64)` | 图标编码(同分类内唯一;**修改时不可改**) |
|
||||
| `name` | string | ✅ | `@Size(max=100)` | 图标名称 |
|
||||
| `svgContent` | string | ✅ | `@Size(max=262144)` | SVG 字符串(来自 §3.5 解析结果,经安全校验) |
|
||||
| `color` | string | ❌ | `@Size(max=16)` | 默认色值 hex,如 `#FFCC00` |
|
||||
| `size` | int | ❌ | — | 默认尺寸(像素) |
|
||||
| `keywords` | string | ❌ | `@Size(max=255)` | 搜索关键词(逗号分隔) |
|
||||
| `sort` | int | ❌ | — | 排序号,默认 0 |
|
||||
| `status` | string | ❌ | `@Pattern(ACTIVE\|DISABLED)` | 状态,默认 ACTIVE |
|
||||
| `remark` | string | ❌ | `@Size(max=255)` | 备注 |
|
||||
|
||||
**出参**:`Result<Long>`(`data` = 图标 ID)
|
||||
|
||||
**典型示例 请求**:
|
||||
```
|
||||
POST /admin/icon/item
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"categoryCode": "weather",
|
||||
"iconCode": "sunny",
|
||||
"name": "晴天",
|
||||
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
|
||||
"color": "#FFCC00",
|
||||
"size": 24,
|
||||
"keywords": "晴,太阳,sunny"
|
||||
}
|
||||
```
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{ "code": 200, "data": 2062087056506441730, "message": "成功" }
|
||||
```
|
||||
|
||||
**异常 响应**(SVG 含脚本 / 事件 / 外链):
|
||||
```json
|
||||
{ "code": 210757, "message": "SVG 含不安全内容(脚本/事件/外链),请清理后重试", "data": null }
|
||||
```
|
||||
|
||||
**错误码**:400 参数校验 / **210702** 分类不存在 / **210751** 同分类图标编码已存在 / **210752** 图标不存在(修改时 id 无效)/ **210754** SVG 非法 / **210755** SVG 超 256KB / **210757** SVG 含不安全内容 / 401 未登录
|
||||
|
||||
> **SaveReqVO 全量快照语义**:修改时请先用 §3.8 详情拉取再整体回填提交,未传的可空字段会被覆盖为空。`categoryCode` / `iconCode` 创建后不可变,修改时被忽略。
|
||||
|
||||
---
|
||||
|
||||
### 3.7 图标分页
|
||||
|
||||
**GET** `/admin/icon/item/page`
|
||||
|
||||
- **使用场景**:图标管理列表页
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是
|
||||
|
||||
**Query 入参**(`IconItemPageReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `page` | int | ❌ | 1 | 页码 |
|
||||
| `pageSize` | int | ❌ | 20 | 每页条数 |
|
||||
| `categoryCode` | string | ❌ | — | 所属分类精确过滤 |
|
||||
| `keyword` | string | ❌ | — | 模糊匹配图标编码 / 名称 / 关键词 |
|
||||
|
||||
**出参**:`Result<PageResult<IconItemRespVO>>`,`records[]` 为完整图标 VO(字段见 §3.8)
|
||||
|
||||
**错误码**:400 参数校验 / 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.8 图标详情
|
||||
|
||||
**GET** `/admin/icon/item/{id}`
|
||||
|
||||
- **使用场景**:详情查看 / 编辑前预填
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是
|
||||
|
||||
**路径入参**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `id` | Long | ✅ | 图标 ID |
|
||||
|
||||
**出参**:`Result<IconItemRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | string | 图标 ID |
|
||||
| `categoryCode` | string | 所属分类编码 |
|
||||
| `iconCode` | string | 图标编码 |
|
||||
| `name` | string | 图标名称 |
|
||||
| `svgContent` | string | SVG 字符串 |
|
||||
| `color` | string | 默认色值 hex |
|
||||
| `size` | int | 默认尺寸(像素) |
|
||||
| `keywords` | string | 搜索关键词 |
|
||||
| `sort` | int | 排序号 |
|
||||
| `status` | string | 状态(ACTIVE / DISABLED) |
|
||||
| `remark` | string | 备注 |
|
||||
| `createTime` / `updateTime` | datetime | 时间 |
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "2062087056506441730",
|
||||
"categoryCode": "weather",
|
||||
"iconCode": "sunny",
|
||||
"name": "晴天",
|
||||
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
|
||||
"color": "#FFCC00",
|
||||
"size": 24,
|
||||
"keywords": "晴,太阳,sunny",
|
||||
"sort": 0,
|
||||
"status": "ACTIVE",
|
||||
"remark": null,
|
||||
"createTime": "2026-06-03 16:00:00",
|
||||
"updateTime": "2026-06-03 16:00:00"
|
||||
},
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
**错误码**:**210752** 图标不存在 / 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.9 删除图标
|
||||
|
||||
**DELETE** `/admin/icon/item/{id}`
|
||||
|
||||
- **使用场景**:删除图标
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是
|
||||
|
||||
**路径入参**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `id` | Long | ✅ | 图标 ID |
|
||||
|
||||
**出参**:`Result<Void>`(`data: null`)
|
||||
|
||||
**错误码**:**210752** 图标不存在 / 401 未登录
|
||||
|
||||
---
|
||||
|
||||
### 3.10【公开】启用分类列表
|
||||
|
||||
**GET** `/mp/icon/categories`
|
||||
|
||||
- **使用场景**:小程序 / 前端拉图标分类
|
||||
- **认证**:**公开,无需 token**
|
||||
- **幂等**:是
|
||||
|
||||
**入参**:无
|
||||
|
||||
**出参**:`Result<List<IconCategorySimpleRespVO>>`(仅 `status=ACTIVE`,按 sort 升序)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `categoryCode` | string | 分类编码 |
|
||||
| `categoryName` | string | 分类名称 |
|
||||
| `sort` | int | 排序号 |
|
||||
|
||||
**典型示例 请求**:
|
||||
```
|
||||
GET /mp/icon/categories
|
||||
(无需 Authorization)
|
||||
```
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "categoryCode": "weather", "categoryName": "天气", "sort": 1 }
|
||||
],
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.11【公开】某分类下启用图标列表
|
||||
|
||||
**GET** `/mp/icon/items`
|
||||
|
||||
- **使用场景**:小程序 / 前端按分类渲染图标
|
||||
- **认证**:**公开,无需 token**
|
||||
- **幂等**:是
|
||||
|
||||
**Query 入参**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `categoryCode` | string | ✅ | 分类编码 |
|
||||
|
||||
**出参**:`Result<List<IconItemSimpleRespVO>>`(仅 `status=ACTIVE` 的图标,且分类须为 ACTIVE)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `iconCode` | string | 图标编码 |
|
||||
| `name` | string | 图标名称 |
|
||||
| `svgContent` | string | SVG 字符串(直接渲染) |
|
||||
| `color` | string | 默认色值 hex |
|
||||
| `size` | int | 默认尺寸(像素) |
|
||||
|
||||
**典型示例 请求**:
|
||||
```
|
||||
GET /mp/icon/items?categoryCode=weather
|
||||
(无需 Authorization)
|
||||
```
|
||||
|
||||
**典型示例 响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"iconCode": "sunny",
|
||||
"name": "晴天",
|
||||
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
|
||||
"color": "#FFCC00",
|
||||
"size": 24
|
||||
}
|
||||
],
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
**错误码**:400 `categoryCode` 缺失
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 状态(`status`)
|
||||
|
||||
**所属字段**:分类 / 图标的 `status` | **类型**:`String` | **必填**:❌(默认 ACTIVE)
|
||||
|
||||
**`@Pattern` 强校验枚举**:
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `ACTIVE` | 启用 |
|
||||
| `DISABLED` | 禁用 |
|
||||
|
||||
> mp 端只返回 ACTIVE 的分类与图标;分类 DISABLED 时其图标在 mp 端也不返回。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码(全模块汇总)
|
||||
|
||||
| code | 含义 | 触发条件 | 涉及接口 |
|
||||
|---|---|---|---|
|
||||
| 400 | 参数校验失败 | 字段缺失 / 长度 / 枚举值不合法 | 所有写入 + §3.11 |
|
||||
| 401 | 未登录 | admin JWT 无效或缺失 | 全部 admin 接口(mp 端公开不需要) |
|
||||
| **210701** | 分类编码已存在 | `categoryCode` 唯一约束 | §3.1 |
|
||||
| **210702** | 分类不存在 | 分类 id / categoryCode 无效 | §3.1 §3.4 §3.6 |
|
||||
| **210703** | 分类下存在图标项,不可删 | 删分类时其下仍有图标 | §3.4 |
|
||||
| **210751** | 同分类图标编码已存在 | `(categoryCode, iconCode)` 唯一约束 | §3.6 |
|
||||
| **210752** | 图标不存在 | 图标 id 无效 | §3.6 §3.8 §3.9 |
|
||||
| **210753** | 上传 SVG 文件为空 | 文件为空 | §3.5 |
|
||||
| **210754** | SVG 内容不合法 | 非合法 XML 或根元素不是 `<svg>` | §3.5 §3.6 |
|
||||
| **210755** | SVG 内容过大 | 超过 256KB | §3.5 §3.6 |
|
||||
| **210756** | SVG 文件读取失败 | 文件流读取异常 | §3.5 |
|
||||
| **210757** | SVG 含不安全内容 | 含 `<script>` / `<foreignObject>` / `on*` 事件 / `javascript:` 外链 | §3.5 §3.6 |
|
||||
|
||||
> 错误码段位 210700-210799 归属图标库模块。
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **两级结构**:分类(`icon_category`)+ 图标(`icon_item`),图标按 `categoryCode` 归属分类
|
||||
- ✅ **SVG 直存库**:`svg_content` MEDIUMTEXT 直存 SVG 字符串,不走 OSS;单图标后端限 256KB
|
||||
- ✅ **上传解析与保存分离**:§3.5 只解析返回字符串不落库;§3.6 才落库
|
||||
- ✅ **唯一约束**:`categoryCode` 全局唯一;`(categoryCode, iconCode)` 联合唯一
|
||||
- ✅ **编码不可变**:图标的 `categoryCode` / `iconCode` 创建后不可改(修改时忽略)
|
||||
- ✅ **SVG 服务端安全校验**:XXE-safe 解析 + 拒绝脚本 / 事件 / 外链
|
||||
- ✅ **mp 公开只读**:§3.10 §3.11 无需 token,只返 ACTIVE;分类禁用其图标也不返
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否(全新模块,无存量接口改动)
|
||||
- **前端是否必须同步上线**:是(新功能,前端按本 changelog 对接管理页 + 渲染端)
|
||||
- **影响已有数据**:无(新建 `icon_category` / `icon_item` 两表)
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **分页参数名**:`page` / `pageSize`(不是 pageNo)
|
||||
- **Long 主键序列化**:`id` 等 Long 字段 JSON 返回为 String,前端不要当 Number 解析
|
||||
- **新增图标流程**:先 §3.5 上传解析拿 `svgContent` → 填表单(补 color / size / 分类 / 编码)→ §3.6 保存
|
||||
- **渲染 SVG**:拿到 `svgContent` 直接内联渲染,**务必先做一次前端 sanitize**(后端已拒明显恶意内容,前端作纵深防御)
|
||||
- **mp 端公开**:§3.10 / §3.11 无需 token;先拉分类再用 `categoryCode` 拉图标
|
||||
- **状态语义**:`ACTIVE` 启用 / `DISABLED` 禁用,mp 端只见 ACTIVE
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **同模块 PR**:#3396(主体)/ #3398 / #3399(mp 公开化)/ #3401(审计加固)
|
||||
- **关联工单**:#3390
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端对接(管理后台 + 小程序)**: 待指派
|
||||
@@ -0,0 +1,175 @@
|
||||
# 【修改接口·管理后台】退费说明融合进资源编辑接口(景区 / 活动 / 服务)
|
||||
|
||||
> **PR**: #3407(工单 #3406)
|
||||
> **服务**: hl-resource-service | **更新时间**: 2026-06-03
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「资源管理 / 景区·游玩项目·服务」编辑页(基本信息)
|
||||
> **状态**: 已合并 dev-v3 + 测试服部署双实例 + API 实测三类通过
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
退费说明**不再走独立的** `/admin/refund-note` 接口,已**融合进资源编辑接口**——前端在景区/活动/服务编辑页里直接编辑退费说明,随资源一起保存、随详情一起回显,**一个接口搞定**。
|
||||
|
||||
- **覆盖三类资源**:景区 SCENIC、游玩项目 ACTIVITY、**服务 SERVICE(本次新增支持)**。
|
||||
- **详情** `GET` 响应新增 `refundNote` 字段(未配置为 `null`)。
|
||||
- **保存** `POST`(新建) / `PUT`(编辑) 请求体新增 `refundNote` 字段,三态语义见 §3.2。
|
||||
- **独立接口 `/admin/refund-note` 仍保留**(向后兼容 + 订单快照内部仍用),但前端**改用融合接口**,不要再调独立接口。
|
||||
|
||||
> 替代说明:本文取代 `03_3389_...` changelog 中「退费说明接入(独立接口)」一节。前端**按本文融合接口接入**,勿按旧文的 `/admin/refund-note` 独立接口接。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
退费说明(resource_refund_note)此前是独立 CRUD 模块(`/admin/refund-note`),前端要单独调一套接口维护。运营反馈应在资源编辑页里直接配,故融合进资源的详情/保存接口。同时把适用资源从「景区+活动」扩展到「景区+活动+**服务**」。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 资源 | 详情(回显 refundNote) | 保存(含 refundNote) |
|
||||
|---|------|------|------|
|
||||
| 1 | 景区 Scenic | GET `/admin/scenic/spot/{scenicId}` | POST `/admin/scenic/spot`、PUT `/admin/scenic/spot/{scenicId}` |
|
||||
| 2 | 游玩项目 Activity | GET `/admin/activity/item/{activityId}` | POST `/admin/activity/item`、PUT `/admin/activity/item/{activityId}` |
|
||||
| 3 | 服务 Service(**新增**) | GET `/admin/service/item/{serviceId}` | POST `/admin/service/item`、PUT `/admin/service/item/{serviceId}` |
|
||||
|
||||
> 列表接口(`/spots`、`/items`)**不返回** refundNote(避免 N+1),退费说明只在单条详情里有。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 详情回显
|
||||
|
||||
三类资源的详情响应(`ScenicSpotVO` / `ActivityVO` / `ServiceItemVO`)新增字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `refundNote` | object \| null | 退费说明;该资源未配置时为 `null` |
|
||||
| `refundNote.noteId` | string | 退费说明 ID(雪花,String 透传防精度丢失) |
|
||||
| `refundNote.intro` | string | 资源级备注 |
|
||||
| `refundNote.items[]` | array | 退费明细(见 §6.2) |
|
||||
| `refundNote.createTime` / `updateTime` | datetime | — |
|
||||
|
||||
### 3.2 保存(三态语义)
|
||||
|
||||
三类资源的新建/编辑请求体新增 `refundNote`(类型 `RefundNoteEmbedReqVO`,见 §6.1)。后端按 `refundNote` 是否传、items 是否为空分三态处理:
|
||||
|
||||
| 入参 refundNote | 行为 |
|
||||
|---|---|
|
||||
| **不传**(字段缺省 / null) | 保持该资源现有退费说明**不动** |
|
||||
| 传了,`items` **为空数组或不传** | **删除**该资源退费说明(清空语义) |
|
||||
| 传了,`items` **非空** | **整块 upsert**(连 intro 一起覆盖) |
|
||||
|
||||
> 编辑接口是字段级更新:只传 `refundNote` 即可单独维护退费说明,不影响其它字段。
|
||||
|
||||
### 3.3 示例(以景区为例,活动/服务同理换路径)
|
||||
|
||||
**保存退费说明**(PUT 只传 refundNote,其它字段不动):
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"refundNote": {
|
||||
"intro": "苔藓为赠送项目,不退费",
|
||||
"items": [
|
||||
{ "title": "成人未参加", "amount": 50.00, "settleScope": "PER_PERSON", "unitLabel": "/人", "remark": "仅限成人" }
|
||||
]
|
||||
}
|
||||
}' \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/3001000000000000019"
|
||||
```
|
||||
|
||||
**详情回显**(GET,data 节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"scenicId": "3001000000000000019",
|
||||
"name": "中俄边境公路(卡线)",
|
||||
"refundNote": {
|
||||
"noteId": "20596378108368322580",
|
||||
"intro": "苔藓为赠送项目,不退费",
|
||||
"items": [
|
||||
{ "title": "成人未参加", "amount": 50.00, "settleScope": "PER_PERSON", "settleScopeLabel": "按人", "unitLabel": "/人", "remark": "仅限成人" }
|
||||
],
|
||||
"createTime": "2026-06-03 17:30:00",
|
||||
"updateTime": "2026-06-03 17:30:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**清空退费说明**(PUT 传空 items):
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{ "refundNote": { "items": [] } }' \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/3001000000000000019"
|
||||
# 之后 GET,data.refundNote 为 null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据结构
|
||||
|
||||
### 6.1 `refundNote` 请求体(`RefundNoteEmbedReqVO`)
|
||||
|
||||
前端只传 intro + items,`resourceType` / `resourceId` 由后端从当前资源上下文回填,前端无需传。
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `intro` | string | ❌ | `@Size(max=255)` | 资源级备注 |
|
||||
| `items` | array | ❌ | `@Size(max=50)` | 退费明细;空/不传=清空 |
|
||||
|
||||
### 6.2 退费明细项(`items[]` / `RefundNoteItemVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `title` | string | ✅ | 展示标题,如「成人未参加」 |
|
||||
| `amount` | BigDecimal | ✅ | 退费金额(赠送项目填 0,不可为负) |
|
||||
| `unitLabel` | string | ❌ | 展示文案 /人 /团 /辆 |
|
||||
| `settleScope` | string | ❌ | 结算粒度 PER_PERSON / PER_TEAM / PER_VEHICLE |
|
||||
| `settleScopeLabel` | string | — | 结算粒度中文(出参后端拼字典 order_refund_settle_scope,入参可省) |
|
||||
| `remark` | string | ❌ | 备注 |
|
||||
| `effectiveFrom` / `effectiveTo` | date | ❌ | 规则生效起止(null=无限制;同传时 from≤to) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **三态语义统一**:不传=不动 / 空 items=删除 / 非空=upsert(三类资源一致)。
|
||||
- ✅ **同事务**:退费说明与资源主表在同一事务保存,主表失败则退费说明回滚。
|
||||
- ✅ **列表不返回 refundNote**:仅单条详情回显,避免列表 N+1。
|
||||
- ✅ **settleScopeLabel 后端拼**:出参按字典 `order_refund_settle_scope` 回填中文,字典异常时 fallback 为 null,不影响主流程。
|
||||
- ✅ **独立接口保留**:`/admin/refund-note`(GET/PUT/DELETE)仍可用,订单快照内部仍取退费说明;前端改用融合接口即可。
|
||||
- ✅ **items 上限 50**:与独立端点对齐。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否(详情新增字段、保存新增可选字段;独立接口未删)。
|
||||
- **前端是否必须同步**:是(要在编辑页接入退费说明子表单,改用融合接口)。
|
||||
- **影响已有数据**:无(存量退费说明照常,独立接口与融合接口读写同一张表)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **改用融合接口**:前端不要再调 `/admin/refund-note` 独立接口(虽保留但即将不维护)。
|
||||
- **清空靠空 items**:要删退费说明就传 `refundNote:{items:[]}`,不是不传(不传=不动)。
|
||||
- **Long 主键 String**:`refundNote.noteId` 为 String,勿当 Number 解析。
|
||||
- **服务也支持了**:服务资源(ServiceItem)本次起可配退费说明,与景区/活动一致。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联
|
||||
|
||||
- 取代:`03_3389_资源默认收费+收费否禁价格日历+退费说明接入-管理后台.md` 的「退费说明(独立接口)」一节。
|
||||
- 后端负责人:@wx
|
||||
- 前端对接(管理后台):mmg
|
||||
@@ -0,0 +1,118 @@
|
||||
# 【新增接口·管理后台】产品 Step5 服务标准 tab 只读预览
|
||||
|
||||
> PR: #3377 服务: hl-product-service-v2 | 更新时间: 2026-06-03
|
||||
> 存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「产品编辑 / Step5 补充信息 / 服务标准 tab」
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
Step5 补充信息新增「服务标准」tab,**一个接口返三块**,均**只读、实时拉取不冻快照**:
|
||||
|
||||
1. 当前绑定模板内容
|
||||
2. 各行程节点的服务标准(来自资源 `service_standard`)
|
||||
3. 各行程节点的退费说明(来自资源退费说明)
|
||||
|
||||
模板的**绑定(写)**走 Step5 保存的 `serviceStandardTemplateId`(见同期《服务标准模板与产品的关联方式》),本接口只负责 tab 内的**只读预览展示**。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
运营在 Step5 绑定模板后,希望一屏看到:当前模板内容、以及各行程节点从资源实时带出的服务标准与退费说明,而不必跳到资源页或下单后才看到。
|
||||
|
||||
## 2. 接口清单
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 鉴权 |
|
||||
|---|------|------|----------|------|
|
||||
| 1 | GET | `/admin/product/item/{id}/service-standard-preview` | 新增 | admin token,产品数据权限(与产品详情同口径) |
|
||||
|
||||
## 3. 请求示例
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <admin-token>" \
|
||||
"https://api.test.1814.love:9443/admin/product/item/2056944461216100353/service-standard-preview"
|
||||
```
|
||||
|
||||
## 4. 响应结构与字段说明
|
||||
|
||||
```
|
||||
{
|
||||
"templateId": Long, // 当前绑定模板 ID,未绑定为 null
|
||||
"template": { ... }, // 绑定模板完整内容,结构同模板详情接口,未绑定为 null
|
||||
"nodes": [ ... ] // 行程节点服务标准+退费说明,仅含有内容的节点,皆空节点不返回
|
||||
}
|
||||
```
|
||||
|
||||
nodes[] 字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| dayNumber | Integer | 第几天 |
|
||||
| nodeName | String | 节点名称(如「呼和诺尔草原旅游区」) |
|
||||
| nodeType | String | 节点类型:SCENIC / ACTIVITY / HOTEL / RESTAURANT / SERVICE ... |
|
||||
| serviceStandard | String | 节点服务标准(来自资源),无则 null |
|
||||
| refundNote | Object | 退费说明,无则 null |
|
||||
|
||||
refundNote 结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| intro | String | 退费说明备注 |
|
||||
| items[].title | String | 条目标题(如「成人未参加」) |
|
||||
| items[].amount | BigDecimal | 退费金额 |
|
||||
| items[].unitLabel | String | 单位文案(如「/人」「/团」) |
|
||||
| items[].settleScope | String | 结算范围枚举:PER_PERSON / PER_TEAM |
|
||||
| items[].settleScopeLabel | String | 结算范围中文(按人 / 按团) |
|
||||
| items[].remark | String | 备注,可空 |
|
||||
| items[].effectiveFrom / effectiveTo | Date | 生效区间,可空 |
|
||||
|
||||
## 5. 真实响应示例(测试服 dev-v3,产品 2056944461216100353)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"templateId": null,
|
||||
"template": null,
|
||||
"nodes": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"nodeName": "巴音温泉",
|
||||
"nodeType": "SERVICE",
|
||||
"serviceStandard": "提供24小时管家服务,含接送站、行程咨询、紧急联络。",
|
||||
"refundNote": null
|
||||
},
|
||||
{
|
||||
"dayNumber": 2,
|
||||
"nodeName": "呼和诺尔草原旅游区",
|
||||
"nodeType": "SCENIC",
|
||||
"serviceStandard": "景区内提供免费讲解、母婴室、医疗点;请听从工作人员安排,注意草原防火。",
|
||||
"refundNote": {
|
||||
"intro": "退费为旅游项目门票退费",
|
||||
"items": [
|
||||
{ "title": "成人未参加", "amount": 44.0, "unitLabel": "/人", "settleScope": "PER_PERSON", "settleScopeLabel": "按人", "remark": "凭票根", "effectiveFrom": null, "effectiveTo": null },
|
||||
{ "title": "整团未到", "amount": 100.0, "unitLabel": "/团", "settleScope": "PER_TEAM", "settleScopeLabel": "按团", "remark": null, "effectiveFrom": null, "effectiveTo": null }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dayNumber": 2,
|
||||
"nodeName": "黄河湿地漂流",
|
||||
"nodeType": "ACTIVITY",
|
||||
"serviceStandard": "漂流配备专业教练与救生装备,全程安全护航;12岁以下需成人陪同。",
|
||||
"refundNote": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:示例中节点 `serviceStandard` 为测试服联调写入的样例数据。若资源未配置 `service_standard`,该字段返回 `null`(详见《资源服务标准字段》一文,运营在资源上配置后即自动带出)。
|
||||
|
||||
## 6. 测试服实测(dev-v3)
|
||||
|
||||
12 个产品调用全部 `code=200`。模板块、退费说明块用真数据验证通过;节点服务标准块在景区 / 游玩项目 / 服务各配一条 `service_standard` 后,三类节点均正确返回上方示例值。
|
||||
|
||||
## 7. 前端动作
|
||||
|
||||
1. 进入 Step5 服务标准 tab 调本接口。
|
||||
2. 渲染三块:`template` 模板内容、`nodes[].serviceStandard` 节点服务标准、`nodes[].refundNote` 节点退费说明,均只读。
|
||||
3. `template` / `serviceStandard` / `refundNote` 均可能为 `null`,按需隐藏对应区块。
|
||||
4. 绑定(写)走 supplement 的 `serviceStandardTemplateId`,预览(读)走本接口,两者配合。
|
||||
@@ -0,0 +1,72 @@
|
||||
# 【破坏性·硬切】性别字段全系统统一为数据字典编码 1=男/2=女/0=未知
|
||||
|
||||
- **端类型**:管理后台 + 小程序(跨 5 服务)
|
||||
- **变更类型**:⚠️ 破坏性契约变更(编码值改变,前端须同步切换)
|
||||
- **日期**:2026-06-03
|
||||
- **来源**:前端《性别字段编码统一-后端对接需求.md》
|
||||
- **Issue**:[#3380](https://git.1814.love:8443/wx/HL/issues/3380)
|
||||
- **PR**:[#3381](https://git.1814.love:8443/wx/HL/pulls/3381)(资源人员先行 [#3379](https://git.1814.love:8443/wx/HL/pulls/3379))
|
||||
- **后端负责人**:wx
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(前端必读)
|
||||
|
||||
1. **本篇取代** 同日早些的《性别字段编码统一-后端对接答复》中「order v2/v3 出行人、司机、合同暂不要动」的分批建议——wx 决定**一次性整体硬切**,后端已全部改完并部署测试服。
|
||||
2. **最终统一口径**:全系统对内/对前端/DB 一律 **数据字典 `gender` 编码 String `'1'`=男 / `'2'`=女 / `'0'`=未知**。前端建唯一事实源 `gender.js` 按此口径,展示走字典 label。
|
||||
3. **破坏点**:原先返回 `MALE/FEMALE/UNKNOWN`(order-v3 出行人)、中文 `男/女`(司机)、`male/female`(合同入参)的接口,**现在一律是 `1/2/0`**。前端解析与提交值必须同步切换,否则性别显示/提交错乱。
|
||||
4. **数据字典已补 `'0'`=未知**(dictValue=0, dictLabel=未知)。注:C 端 `/dict/all` 有缓存,新增项稍后随缓存刷新生效;`/admin/dict/data/gender` 已可见。
|
||||
|
||||
---
|
||||
|
||||
## 1. 各模块编码变化对照(前端按此切换)
|
||||
|
||||
| 模块 | 接口/字段 | 改前 | **改后(统一)** |
|
||||
|------|-----------|------|------------------|
|
||||
| order-v3 出行人 | `gender`(增/改/查/批量编辑/快照) | `MALE`/`FEMALE`/`UNKNOWN` | **`1`/`2`/`0`** |
|
||||
| order-v2 出行人 | `gender` | 混存(1/2/MALE/male) | **`1`/`2`**(后端已归一化收口) |
|
||||
| 合同创建入参 | `gender` | `male`/`female` | **`1`/`2`**(出站 12301 后端自动转中文「男/女」) |
|
||||
| 车队司机 | `gender`(增/改/查/H5提交/导入) | 中文 `男`/`女` | **`1`/`2`**(Excel 仍填中文,后端导入边界自动转码) |
|
||||
| 资源人员 | `gender` | 注释 0/1 错乱 | **`1`/`2`**(#3379 已对齐,Integer 类型) |
|
||||
| 用户资料 | `gender` | `MALE`/`FEMALE` | **`1`/`2`/`0`** |
|
||||
| 小程序出行人 | `gender` | 已 1/2 | `1`/`2`(不变,BFF 透传跟随 order-v3) |
|
||||
| 数据字典 `gender` | dictValue | `1`/`2` | `1`/`2` + **新增 `0`=未知** |
|
||||
|
||||
---
|
||||
|
||||
## 2. 第三方边界(前端无感,后端自动 adapter 转换)
|
||||
|
||||
以下转换在后端出/入站边界完成,前端**统一只看到 `1/2/0`**:
|
||||
|
||||
| 边界 | 后端转换 |
|
||||
|------|----------|
|
||||
| 12301 合同/团队报告 | 对内 `1/2` ↔ 报文 中文「男/女」/ sex `0男1女` |
|
||||
| 保游保险 | 对内 `1/2` → 保游 `1男/0女` |
|
||||
| 身份证解析 | 自动得 `1/2`(奇男偶女) |
|
||||
| Excel 导入(司机) | 中文「男/女」→ `1/2`(前端导出模板提示填 1/2 或保持中文均可) |
|
||||
| OCR 证件识别 | 各证件 中文/Male/Female → `1/2`,无法识别 `0` |
|
||||
|
||||
---
|
||||
|
||||
## 3. 前端动作清单
|
||||
|
||||
1. 建 `src/constants/gender.js`:`GENDER = { MALE:'1', FEMALE:'2', UNKNOWN:'0' }`,`GENDER_LABEL_MAP`、`GENDER_OPTIONS`,以**字典 `gender`** 为唯一事实源。
|
||||
2. 移除各模块原有的本地 option/转换:尤其 `ContractCreateDrawer.vue:260` 那段 `'1'/'2' → male/female` 硬转**整段删除**,合同入参直接传 `1/2`。
|
||||
3. 团队报告 `TeamReportTab.vue` 的 `sex` 0/1:后端报文内部仍用 sex,但 admin 接口对前端暴露的出行人性别统一 `gender` `1/2`,按 gender 展示。
|
||||
4. 司机 `DriverEditModal.vue` 性别下拉 value 由中文 `男/女` 改为 `1/2`。
|
||||
5. 身份证解析回填直接用 `1/2`(`parseGenderFromIdCard` 已是该口径)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 / 风险
|
||||
|
||||
- 5 服务测试服已部署(双实例),4 个存量迁移脚本随启动跑通:order_traveler / insured_person(MALE/male→1, FEMALE/female→2)、fleet_driver(_pending)(男/女→1/2)、字典补 0。
|
||||
- 437 项单测全绿(order-v3 111 / order-v2 142 / fleet 33 / user 105 / mp 46)。
|
||||
- ⚠️ **存量数据迁移上正式环境由 wx 单独确认后执行**(本篇仅测试服)。
|
||||
- ⚠️ 这是破坏性硬切,**前后端需同一发布窗口上线**;前端切换前测试服性别相关功能会短暂不一致,属预期。
|
||||
|
||||
---
|
||||
|
||||
## 5. 顺带修复
|
||||
|
||||
order-v2 保险被保人 `gender` 此前从不写入(`InsuredPersonVO.gender` 恒 null),本次补持久化,保险详情现可正确返回被保人性别。
|
||||
@@ -0,0 +1,90 @@
|
||||
# 「性别」字段编码统一——后端对接答复(含资源人员性别注释缺陷修复)
|
||||
|
||||
- **端类型**:管理后台(跨多服务)
|
||||
- **变更类型**:对接口径答复 + 缺陷修复(接口结构不变,仅纠正契约注释与常量)
|
||||
- **日期**:2026-06-03
|
||||
- **来源**:前端《性别字段编码统一-后端对接需求.md》
|
||||
- **PR**:[#3379](https://git.1814.love:8443/wx/HL/pulls/3379)
|
||||
- **Commit**:[3c2af3a](https://git.1814.love:8443/wx/HL/commit/3c2af3ac8a75f7202d52527ee35f6a9d13c34b8e)
|
||||
- **后端负责人**:wx
|
||||
|
||||
---
|
||||
|
||||
## 关键说明(请前端先读)
|
||||
|
||||
1. **最终统一口径已定**:全系统对内/对前端契约的「性别」统一走**数据字典 `gender` 编码**,即 **`'1'`=男 / `'2'`=女**(字符串),并新增/对齐**未知态**。前端按字典 label 展示即可,**不要再自己映射**。
|
||||
2. **本轮不强制前端立即全切**:全量统一是跨 5 服务 + 存量数据迁移的大改造,将另起独立工单分批推进。本篇先给你**确认口径 + 现状真相 + 后续路线**,并已先行修掉一个会导致性别错配的真实缺陷(资源人员)。
|
||||
3. **外部对接不受影响**:12301 报文要中文「男/女」、保游保险要 `0/1`、身份证解析得 `1/2`,这些都收敛在后端出/入站 adapter 内,不外泄到对前端契约。
|
||||
|
||||
---
|
||||
|
||||
## 1. 后端真实现状矩阵(实证,比你看到的更复杂)
|
||||
|
||||
| 模块 | 字段名 | 类型 | **后端真实编码** | 规范性 |
|
||||
|------|--------|------|------------------|--------|
|
||||
| 数据字典 `gender` | — | — | **`'1'`=男 / `'2'`=女**(`sys_dict_data`,user-service 管理,C 端 `GET /dict/all`) | 标准锚点 |
|
||||
| order-v2 出行人 | `gender` | String | 透传混存:身份证反推写 `1/2`、mp 传 `1/2`、DB 注释却写 `MALE/FEMALE`、前端可能传 `male/female` | 最脏 |
|
||||
| order-v2 团队报告 | **`sex`** | Integer | `0`=男 / `1`=女(对接 12301 报文口径) | 独立 |
|
||||
| order-v3 出行人 | `gender` | String | `MALE/FEMALE/UNKNOWN`(有枚举校验) | 规范但口径不同 |
|
||||
| 合同创建入参 | `gender` | String | `male/female`(小写,不持久化,出站转 12301 中文「男/女」) | 独立 |
|
||||
| 车队司机 | `gender` | String | 中文 `男`/`女`(`VARCHAR(4)`) | 待统一 |
|
||||
| 资源人员 | `gender` | Integer | 入参强制 `1`=男 / `2`=女(本轮修正注释,见第 3 节) | 本轮已对齐字典 |
|
||||
| mp `MpTravelerRequest` | `gender` | String | `1/2` | — |
|
||||
| 身份证解析 `IdCardParser` | — | — | `1/2`(奇男偶女) | — |
|
||||
| 保游保险 API | — | — | `0`=女 / `1`=男(外部约束) | 外部 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 回答前端 4 个 Open Questions
|
||||
|
||||
| # | 你的问题 | 后端答复(事实) |
|
||||
|---|----------|------------------|
|
||||
| 1 | 数据字典 `gender` 当前 code 到底是什么? | **`'1'`=男 / `'2'`=女**(`sys_dict_data` 实证,归 user-service)。既不是 `0/1`,也不是 `MALE/FEMALE`。这就是全系统将统一到的锚点。 |
|
||||
| 2 | 各接口能否统一到同一字段名 + 编码?有无存量迁移成本? | 对内/对前端契约**能**统一为字段名 `gender`、值 `'1'/'2'`;外部 12301/保游走 adapter。**有存量迁移成本**:order v2/v3 出行人、车队司机(中文)需归一化 + 历史数据回填,故分批做。 |
|
||||
| 3 | 是否需要过渡期(后端同时接受新旧编码)? | **需要**。order-v2 出行人是历史脏数据,后端会先做「兼容读归一化 + 规范写」再切,统一改造期间后端将兼容旧编码入参,前端可分模块切换。 |
|
||||
| 4 | 身份证解析得到的性别按哪套回填? | 当前 `IdCardParser` 已返 `1/2`,**正好就是字典口径**,前端回填直接用 `'1'/'2'` 即可。 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 本轮缺陷修复:资源人员性别注释/常量错配(PR #3379)
|
||||
|
||||
**问题**:资源人员 `gender` 实际运行值由入参校验 `@Min(1)@Max(2)` 强制为 **1=男 / 2=女**,服务层透传存储与返回;但接口契约注释写反了:
|
||||
|
||||
| 位置 | 修复前 | 修复后 |
|
||||
|------|--------|--------|
|
||||
| `StaffVO.gender`(出参注释) | `0=女 1=男` ❌ | `1=男 2=女`(对齐字典与实际) |
|
||||
| `StaffConstants`(死常量) | `GENDER_FEMALE=0 / GENDER_MALE=1` ❌ | `GENDER_MALE=1 / GENDER_FEMALE=2` |
|
||||
|
||||
**前端影响**:若你之前按 `StaffVO` 出参注释「0=女 1=男」解析资源人员性别,**`value=2`(女性)会无法映射 → 丢失/错配**。现注释已纠正,资源人员性别即「1=男 / 2=女」,与字典一致。
|
||||
|
||||
> 说明:本次为**纯契约注释与常量纠正,零运行行为变更**(返回值本就是 1/2,未变)。无需前端配合改提交值;仅请按「1=男 / 2=女」解析资源人员性别。
|
||||
|
||||
**接口**(结构不变):
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 资源人员新增 | POST | `/admin/staff` | `gender` 入参 `1`=男 / `2`=女(一直如此,本次仅修注释) |
|
||||
| 资源人员详情/列表 | GET | `/admin/staff/**` | `StaffVO.gender` 返回 `1`=男 / `2`=女 |
|
||||
|
||||
测试服已部署(双实例 health UP),无行为变化故无需 API 回归。
|
||||
|
||||
---
|
||||
|
||||
## 4. 后续统一改造路线(另起独立工单,前端先知悉)
|
||||
|
||||
统一到字典口径 `'1'/'2'` 的大改造将分批进行,预计涉及:
|
||||
|
||||
1. **order-v3 出行人**:现 `MALE/FEMALE/UNKNOWN` → `1/2/0`,需同步改 `TravelerService` 的 `GENDER_ENUMS` 校验集 + 出行人快照三层 VO + 存量数据迁移。
|
||||
2. **order-v2 出行人**:透传脏数据先做兼容读归一化,再规范写为 `1/2`;连带团队报告 `sex`、合同 `male/female`、保险的 adapter 收敛到出/入站。
|
||||
3. **车队司机**:中文 `男/女` → `1/2`,存量迁移。
|
||||
4. **数据字典**:`gender` 视需要补「未知」项(如 `'0'`=未知),供 `UNKNOWN` 态落地。
|
||||
|
||||
> 每批改造会单独发 changelog,明确该模块切换窗口与是否需要前端同步发布。**在对应模块改造 changelog 发出前,请前端不要单方面改动该模块的性别提交值。**
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端当前可执行动作(对应需求文档第四节)
|
||||
|
||||
1. **可立即做**:按本篇口径建立 `src/constants/gender.js`,以**字典 `gender`(`'1'`=男 / `'2'`=女)** 为唯一事实源;资源人员性别按「1=男 / 2=女」解析(本轮已修正后端注释)。
|
||||
2. **暂不要动**:order v2/v3 出行人、车队司机、合同的提交值——等对应模块统一改造 changelog 发出后再按窗口切换。
|
||||
3. 团队报告 `sex`(`0/1`,对接 12301)属外部报文口径,统一改造时后端会在 BFF 层对前端暴露统一 `gender`,届时另行通知。
|
||||
@@ -0,0 +1,57 @@
|
||||
# 【排查答复·管理后台】景区「服务标准」编辑页不回显 —— 后端实测正常,请前端检查保存与回填
|
||||
|
||||
> PR: 无(纯排查答复,后端未改动) 服务: hl-resource-service | 更新时间: 2026-06-03
|
||||
> 存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「资源管理 / 景区」编辑表单 `serviceStandard` 字段
|
||||
> 关联: 本目录 `03_资源服务标准字段-管理后台.md`(字段契约原文)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
前端反馈:景区编辑页「服务标准」框是空的,疑似不回显。**经测试服 API 实测,后端写入 / 回显链路 100% 正常**,问题不在后端。
|
||||
|
||||
- 反馈的景区「中俄边境公路(卡线)」`serviceStandard` 在数据库里**原本就是 null(从没成功保存过值)**,所以编辑页显示空——这是「没数据」,不是「有数据不回显」。
|
||||
- 后端编辑接口是**字段级 PATCH**:请求体没带 `serviceStandard` 时会跳过该字段、保持原值。该景区一直是 null,**强烈指向前端保存时没把 `serviceStandard` 提交上来**。
|
||||
- 我已往该景区写入了一段示例服务标准文本,**前端直接刷新这条景区编辑页即可看到回显**(眼见后端回显正常)。
|
||||
|
||||
## 1. 现象
|
||||
|
||||
景区编辑页 → 基本信息 → 「服务标准」输入框为空(placeholder「请输入服务标准(点位级,最长10000字)」)。
|
||||
|
||||
## 2. 后端排查结论(测试服实测)
|
||||
|
||||
| 环节 | 结论 |
|
||||
|------|------|
|
||||
| 写入 | 创建 / 编辑接口均写 `serviceStandard`,正常 ✅ |
|
||||
| 回显 | 详情 / 列表响应均回填 `serviceStandard`,正常 ✅ |
|
||||
| 数据现状 | 该景区 DB 原值 `serviceStandard: null`(未配置) |
|
||||
| round-trip 实测 | `PUT` 写入文本 → `GET` 回显,写入值与回显值**完全一致** ✅ |
|
||||
|
||||
实测命令(测试服,真 admin token):
|
||||
|
||||
```bash
|
||||
# 写入
|
||||
curl -X PUT -H "Authorization: Bearer <admin-token>" -H "Content-Type: application/json" \
|
||||
-d '{"serviceStandard":"卡线点位服务标准:1.带队提前30分钟到集合点清点人数;……"}' \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/3001000000000000019"
|
||||
|
||||
# 回显
|
||||
curl -H "Authorization: Bearer <admin-token>" \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/3001000000000000019"
|
||||
# 响应 data.serviceStandard 即为刚写入的文本
|
||||
```
|
||||
|
||||
## 3. 请前端检查(两点)
|
||||
|
||||
1. **保存提交**:景区编辑「保存」时,请求体(`PUT /admin/scenic/spot/{scenicId}`)里**是否带了 `serviceStandard` 字段**。后端字段级 PATCH,没带就不会更新,这是当前最可能的原因。
|
||||
2. **详情回显**:进入编辑页时,是否读取了详情响应里的 `serviceStandard` 回填到输入框。后端详情 / 列表均已返回该字段(未配置为 null)。
|
||||
|
||||
> 自查办法:在该景区「服务标准」框填几个字 → 保存 → 重新进入。存不上 = 保存请求漏了字段;能存上 = 之前只是没人填过。
|
||||
|
||||
## 4. 字段契约(详见关联 changelog)
|
||||
|
||||
| 项 | 值 |
|
||||
|----|----|
|
||||
| 字段名 | `serviceStandard`(String,≤10000 字,可空) |
|
||||
| 编辑接口 | `PUT /admin/scenic/spot/{scenicId}`(字段级 PATCH,只传该字段即可单独更新) |
|
||||
| 详情 / 列表 | 响应均含 `serviceStandard`,回填即可 |
|
||||
|
||||
游玩项目 / 服务 / 酒店同理,完整 4 类资源接口见 `03_资源服务标准字段-管理后台.md`。
|
||||
@@ -0,0 +1,118 @@
|
||||
# 【说明补齐·管理后台】服务标准模板与产品的关联方式(产品 Step5)
|
||||
|
||||
> PR: #3375 服务: hl-product-service-v2 | 更新时间: 2026-06-03
|
||||
> 存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「产品编辑 / Step5 补充信息」(服务标准模板绑定)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
前端反馈「服务标准模板怎么和产品关联」一直没有同步说明,本文补齐,含完整接口、字段、请求/响应示例。
|
||||
|
||||
- 服务标准模板是一份**统一维护的全局模板库**,产品通过**纯引用一个模板 ID**(`serviceStandardTemplateId`)与它关联,不复制模板正文。
|
||||
- 所有产品(含私人定制)的服务标准都**实时按模板 ID 查询展示**,模板改了已绑定产品实时跟随。
|
||||
- 原私人定制完成设计冻快照的机制(`service_standard_snapshot`)已废除(PR #3375),**接口结构不变,前端无需改动**。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
产品编辑 Step5「补充信息」里需要给产品绑定一份服务标准模板。绑定关系存在产品的 `serviceStandardTemplateId` 字段(纯引用,可空)。前端需要:拉模板下拉、预览模板内容、保存绑定、回显当前绑定。
|
||||
|
||||
## 2. 接口清单
|
||||
|
||||
| # | 用途 | 方法 | 路径 |
|
||||
|---|------|------|------|
|
||||
| 1 | 启用模板下拉 | GET | `/admin/product/service-standard-template/enabled` |
|
||||
| 2 | 模板详情(预览) | GET | `/admin/product/service-standard-template/{id}` |
|
||||
| 3 | 保存绑定 | PUT | `/admin/product/item/{id}/supplement` |
|
||||
|
||||
## 3. 详细用法与示例
|
||||
|
||||
### 3.1 拉模板下拉(接口 1)
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <admin-token>" \
|
||||
"https://api.test.1814.love:9443/admin/product/service-standard-template/enabled"
|
||||
```
|
||||
|
||||
响应(只返 id + name,供下拉选择):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "id": "2061281473171775490", "name": "6字段服务标准模板" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 预览选中模板内容(接口 2)
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <admin-token>" \
|
||||
"https://api.test.1814.love:9443/admin/product/service-standard-template/2061281473171775490"
|
||||
```
|
||||
|
||||
响应(含分组 sections 与每条 6 字段):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "2061281473171775490",
|
||||
"name": "6字段服务标准模板",
|
||||
"intro": "全国统一的服务标准",
|
||||
"applicableScope": "本周期本产品全部出团人群",
|
||||
"sections": [
|
||||
{
|
||||
"title": "用车服务",
|
||||
"items": [
|
||||
{
|
||||
"title": "专业司机",
|
||||
"content": "持有A1驾照,8年以上驾龄",
|
||||
"remark": "提供接送指引",
|
||||
"color": "#FF6600",
|
||||
"contactName": "李师傅",
|
||||
"phone": "13800000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "ENABLED",
|
||||
"statusLabel": "启用"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
模板条目 6 字段说明:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| title | String | 条目标题,如「专业司机」 |
|
||||
| content | String | 条目正文 |
|
||||
| remark | String | 备注,可空 |
|
||||
| color | String | 标题色值 `#RRGGBB`,可空 |
|
||||
| contactName | String | 联系人,可空 |
|
||||
| phone | String | 联系电话,可空 |
|
||||
|
||||
### 3.3 保存绑定(接口 3)
|
||||
|
||||
Step5 保存时在 supplement 请求体里带上 `serviceStandardTemplateId` 即可(解绑传 `null`):
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Authorization: Bearer <admin-token>" -H "Content-Type: application/json" \
|
||||
-d '{"serviceStandardTemplateId": 2061281473171775490}' \
|
||||
"https://api.test.1814.love:9443/admin/product/item/2056938821550747650/supplement"
|
||||
```
|
||||
|
||||
> 注:supplement 请求体含 Step5 全量字段,此处仅示意服务标准模板字段,实际提交按 Step5 既有结构整体提交。
|
||||
|
||||
### 3.4 回显当前绑定
|
||||
|
||||
产品 / Step5 读取接口返回的 `serviceStandardTemplateId` 即当前绑定(未绑定为 `null`),前端据此回填下拉选中项。
|
||||
|
||||
## 4. 前端动作
|
||||
|
||||
1. 进入 Step5 服务标准区,用接口 1 拉下拉。
|
||||
2. 选中某模板,用接口 2 拉详情做内容预览。
|
||||
3. 保存时把 `serviceStandardTemplateId` 随接口 3(supplement)整体提交。
|
||||
4. 回显时读 Step5 数据里的 `serviceStandardTemplateId` 回填下拉。
|
||||
|
||||
无破坏性变更,按需接入即可。
|
||||
@@ -0,0 +1,64 @@
|
||||
# 【说明补齐·管理后台】资源服务标准字段 service_standard(景区/游玩项目/服务/酒店)
|
||||
|
||||
> PR: 无(字段此前已落地,本次补齐说明) 服务: hl-resource-service | 更新时间: 2026-06-03
|
||||
> 存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「资源管理 / 景区·游玩项目·服务·酒店」(编辑表单新增字段)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
前端反馈资源上新增的「服务标准」字段 `serviceStandard` 没有同步说明,本文补齐,含字段位置、请求示例、字段去向。
|
||||
|
||||
- `serviceStandard` 是**点位级服务标准**,配置在资源本体上(String,最长 10000 字,可空)。
|
||||
- 下游(订单 / 小程序行程节点、产品 Step5 服务标准 tab 节点预览)**实时按 resourceId 拉取展示,不冻快照**,资源改了已引用处同步更新。
|
||||
- 编辑接口是**字段级 PATCH**:请求体只传 `serviceStandard` 即可单独更新该字段,其它字段为 null 时跳过、不受影响。
|
||||
|
||||
## 1. 字段背景
|
||||
|
||||
为支持订单/小程序行程节点、产品 Step5 服务标准 tab 展示「每个点位的服务标准」,在 4 类资源上增加了 `serviceStandard` 字段,由运营在资源编辑页维护。
|
||||
|
||||
## 2. 字段所在接口
|
||||
|
||||
`serviceStandard` 已在以下 4 类资源的创建 / 编辑请求体与详情 / 列表响应里:
|
||||
|
||||
| # | 资源 | 创建 | 编辑(字段级 PATCH) |
|
||||
|---|------|------|------|
|
||||
| 1 | 景区 Scenic | POST `/admin/scenic/spot` | PUT `/admin/scenic/spot/{scenicId}` |
|
||||
| 2 | 游玩项目 Activity | POST `/admin/activity/item` | PUT `/admin/activity/item/{activityId}` |
|
||||
| 3 | 服务 ServiceItem | POST `/admin/service/item` | PUT `/admin/service/item/{serviceId}` |
|
||||
| 4 | 酒店 Hotel | POST `/admin/hotel/item` | PUT `/admin/hotel/item/{hotelId}` |
|
||||
|
||||
字段约束:`@Size(max=10000)`,可空。
|
||||
|
||||
## 3. 详细用法与示例
|
||||
|
||||
### 3.1 单独更新某资源的服务标准(PATCH)
|
||||
|
||||
编辑接口逐字段判空,只传 `serviceStandard` 即可,不影响其它字段。以景区为例(测试服实测):
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Authorization: Bearer <admin-token>" -H "Content-Type: application/json" \
|
||||
-d '{"serviceStandard": "景区内提供免费讲解、母婴室、医疗点;请听从工作人员安排,注意草原防火。"}' \
|
||||
"https://api.test.1814.love:9443/admin/scenic/spot/3001000000000000010"
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "...": "...", "serviceStandard": "景区内提供免费讲解、母婴室、医疗点;请听从工作人员安排,注意草原防火。" } }
|
||||
```
|
||||
|
||||
游玩项目 / 服务 / 酒店同理,把路径换成对应资源即可(详见接口表)。
|
||||
|
||||
### 3.2 详情回显
|
||||
|
||||
资源详情 / 列表响应里都带 `serviceStandard` 字段,编辑页回填即可(未配置为 null)。
|
||||
|
||||
## 4. 字段去向(配了之后哪里展示)
|
||||
|
||||
- 订单 / 小程序行程节点:按节点 resourceId 实时取资源 `serviceStandard` 展示(点位级服务标准)。
|
||||
- 产品编辑 Step5 服务标准 tab:`GET /admin/product/item/{id}/service-standard-preview` 的 `nodes[].serviceStandard` 即来自此字段(详见《产品 Step5 服务标准 tab 只读预览》)。
|
||||
|
||||
## 5. 前端动作
|
||||
|
||||
1. 在景区 / 游玩项目 / 服务 / 酒店的编辑表单加 `serviceStandard`(服务标准,多行文本,≤10000 字)输入项。
|
||||
2. 创建 / 编辑提交时带上该字段;详情回显读 `serviceStandard` 回填。
|
||||
3. 可空,无破坏性变更,按需接入。
|
||||
@@ -0,0 +1,241 @@
|
||||
# 【新增接口·管理后台】内部员工站内信收件箱 + SSE 实时推送(房务/车务可用)
|
||||
|
||||
> **PR**: #3424 #3425(工单 #3414)
|
||||
> **服务**: hl-user-service + hl-gateway | **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「通知中心 / 顶部消息铃铛 / 自定义推送」
|
||||
> **状态**: 已合并 dev-v3 + 测试服双实例部署 + API 全链路实测通过(含 SSE 实时推送过网关 9443)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
原站内信只能发给 **C 端微信客人**,**内部员工(管理后台账号 admin_user:房务管家、车务调度、运营等)收不到站内信、只能收企微**。本次补齐:
|
||||
|
||||
1. **内部员工有了自己的站内信收件箱**(`/admin/message/*`,与 C 端 `/mp/message` 完全独立)。
|
||||
2. **SSE 实时推送**:前端用 `EventSource` 连一条长连接,后端产生新站内信时**实时下发**,刷新顶部铃铛角标(无需轮询)。
|
||||
3. **自定义推送可选内部员工**:原「自定义推送」选人只能搜 C 端客人,现可**按角色 / 按人**选内部员工发站内信。
|
||||
|
||||
> 前端需要做三块:① 顶部消息**铃铛 + 未读角标** ② 站内信**收件箱页**(列表/分类/已读/删除)③ **EventSource 客户端**(连接 + 收到事件刷新角标)。下面给全套接口契约 + SSE 对接细节。
|
||||
|
||||
### 🔔 入口位置(重点,别漏)
|
||||
|
||||
当前管理后台「通知中心」只有 通知配置 / 消息分类管理 / 通知日志 / 自定义推送,**缺一个让登录员工看「发给自己」站内信的入口**。请加两个入口(指向同一套 `/admin/message/*` 接口):
|
||||
|
||||
1. **全局顶部铃铛**(所有页面常驻):红点未读数 = `unread-count`,由 SSE 实时刷新;点击下拉显示最近几条 + 「查看全部」。
|
||||
2. **「我的消息」页**:**平铺收件箱**——分页列表(`list`)+ 单条/全部已读 + 删除(内部员工站内信统一 SYSTEM 分类,**不需要分类 tab**)。
|
||||
|
||||
⚠️ **菜单是 DB 驱动(`sys_menu` 表)**:本系统后台菜单存在 `sys_menu`,每行 `component` 指向前端组件(如「自定义推送」→ `notification/CustomPush`)。所以加「我的消息」要**两步一起做**:① 在 `sys_menu`「通知中心」(menu_id=950) 下加一行(`component` 指向新页面如 `notification/MyMessages`,可经 系统管理→菜单管理 加或随前端发版)② 实现该前端组件。**只加菜单行不建组件 = 点开空白页**,务必同步。
|
||||
|
||||
> 注意区分:现有「自定义推送」是**发**消息(运营发给员工/客人),本次新增的是**收**消息(员工看自己的站内信),是两个不同入口。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口清单
|
||||
|
||||
| # | 用途 | 方法 / 路径 |
|
||||
|---|------|------------|
|
||||
| 1 | **SSE 实时长连接** | GET `/ws/admin-msg/stream`(text/event-stream,见 §3) |
|
||||
| 2 | 收件箱列表(分页,可按分类) | GET `/admin/message/list` |
|
||||
| 3 | 未读总数(铃铛角标) | GET `/admin/message/unread-count` |
|
||||
| 4 | 分类未读汇总 | GET `/admin/message/categories` |
|
||||
| 5 | 单条标已读 | PUT `/admin/message/{id}/read` |
|
||||
| 6 | 全部标已读 | PUT `/admin/message/read-all` |
|
||||
| 7 | 某分类标已读 | PUT `/admin/message/category/{categoryCode}/read` |
|
||||
| 8 | 删除单条 | DELETE `/admin/message/{id}` |
|
||||
| 9 | 自定义推送选内部员工(搜人) | GET `/admin/notification/custom-push/admins` |
|
||||
| 10 | 自定义推送(发内部员工站内信) | POST `/admin/notification/custom-push`(channel=ADMIN_INAPP,见 §5) |
|
||||
|
||||
> 全部走管理后台 admin token(顶部铃铛/收件箱用当前登录员工身份,后端从网关注入的 X-Admin-Id 取人,前端无需传 adminId)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 背景
|
||||
|
||||
房务/车务等业务需要给「内部员工」实时通知(到岗/到人),而站内信此前是 C 端专属。本次给内部员工建独立收件箱 `admin_message`(按 adminId 维度),并用 SSE 做实时角标刷新。SSE 仅作「有新消息了」的轻量信令,正文仍走收件箱列表接口拉取,DB 为唯一真相源(即便 SSE 偶尔断线也不丢消息,重连后拉未读对账即可)。
|
||||
|
||||
---
|
||||
|
||||
## 3. SSE 实时推送对接(重点)
|
||||
|
||||
### 3.1 建立连接
|
||||
|
||||
前端用浏览器原生 `EventSource` 连接。**EventSource 不能自定义请求头**,所以 token 走 query 参数 `?token=`:
|
||||
|
||||
```javascript
|
||||
const token = /* 当前登录 admin token */;
|
||||
const es = new EventSource(`https://api.test.1814.love:9443/ws/admin-msg/stream?token=${token}`);
|
||||
```
|
||||
|
||||
- 鉴权:网关校验 token 通过后注入身份,未带 token / 无效 token 返 401。
|
||||
- 缓冲:后端已下发 `X-Accel-Buffering: no` 关闭 nginx 反代缓冲,前端无需处理。
|
||||
- 保活:服务端每 25 秒发 `:ping` 心跳,浏览器自动忽略。
|
||||
- 重连:`EventSource` 断线自动重连;**重连后建议主动调一次 `/admin/message/unread-count` 对账**(防断线期间漏推)。
|
||||
|
||||
### 3.2 事件类型
|
||||
|
||||
| event | data | 说明 |
|
||||
|-------|------|------|
|
||||
| `connected` | `ok` | 连接建立确认(可忽略,仅表示连上了) |
|
||||
| `message` | JSON(见下) | **有新站内信**,按此刷新角标 |
|
||||
| (`:ping` 注释行) | — | 心跳,浏览器自动忽略 |
|
||||
|
||||
`message` 事件的 data 是轻量信令:
|
||||
|
||||
```json
|
||||
{ "adminId": 1001, "unreadCount": 2, "title": "realtime through nginx" }
|
||||
```
|
||||
|
||||
- `unreadCount`:该员工当前未读总数 → **直接用它刷新铃铛角标**。
|
||||
- `title`:新消息标题 → 可选弹个 toast 提示。
|
||||
- 正文/列表:收到 `message` 后**调 `/admin/message/list` 拉最新一页**展示,不在 SSE 里传全文。
|
||||
|
||||
### 3.3 前端处理示例
|
||||
|
||||
```javascript
|
||||
es.addEventListener('message', (e) => {
|
||||
const sig = JSON.parse(e.data);
|
||||
setBadge(sig.unreadCount); // 刷新铃铛角标
|
||||
toast(`新消息:${sig.title}`); // 可选
|
||||
if (inboxOpen) reloadInboxList(); // 收件箱打开着就刷新列表
|
||||
});
|
||||
es.onerror = () => { /* EventSource 会自动重连;重连后 fetchUnreadCount() 对账 */ };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 收件箱接口详情
|
||||
|
||||
### 4.1 列表 GET `/admin/message/list`
|
||||
|
||||
Query:`pageNo`(默认1) / `pageSize`(默认20,≤100) / `categoryCode`(可选,按分类过滤)
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <adminToken>" \
|
||||
"https://api.test.1814.love:9443/admin/message/list?pageNo=1&pageSize=20"
|
||||
```
|
||||
|
||||
响应 `data`:`{ records: AdminMessageRespVO[], total, ... }`(标准分页)。
|
||||
|
||||
### 4.2 未读数 / 分类汇总
|
||||
|
||||
```bash
|
||||
# 铃铛角标
|
||||
GET /admin/message/unread-count -> data: 2 (整数)
|
||||
# 分类未读汇总
|
||||
GET /admin/message/categories -> data: [{categoryCode, categoryName, unreadCount}, ...]
|
||||
```
|
||||
|
||||
> ⚠️ **内部员工站内信分类统一为 SYSTEM**(不用 C 端 订单/行程/促销 分类)。所以 `categories` 只会返回一条 `SYSTEM`,**「我的消息」做平铺列表即可,不需要分类 tab**(`category/{code}/read` 也基本用不到,标已读直接用 `read-all` 或单条)。
|
||||
|
||||
### 4.3 标已读 / 删除
|
||||
|
||||
```bash
|
||||
PUT /admin/message/{id}/read # 单条已读
|
||||
PUT /admin/message/read-all # 全部已读
|
||||
PUT /admin/message/category/{categoryCode}/read # 某分类已读
|
||||
DELETE /admin/message/{id} # 删除(软删)
|
||||
```
|
||||
|
||||
> 进入某条消息 / 某分类时调对应「已读」,未读数随即下降;越权防护:只能操作自己的消息(后端按 adminId 限定)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 自定义推送选内部员工
|
||||
|
||||
### 5.1 搜人 GET `/admin/notification/custom-push/admins`
|
||||
|
||||
Query:`keyword`(姓名模糊,可空) / `roleKey`(按角色过滤,可空)
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <adminToken>" \
|
||||
"https://api.test.1814.love:9443/admin/notification/custom-push/admins?keyword=张"
|
||||
```
|
||||
|
||||
响应 `data`:`AdminPushTargetVO[]`(`adminId`/`name`/`username`/`roleKey`/`roleName`),供选人下拉。
|
||||
|
||||
> 🔴 **2026-06-05 前端接线 bug(mmg 必看)**:测试环境「自定义推送」页选「站内信(内部员工)」+「指定员工」后,**「选择员工」下拉显示「无数据」**。查测试服 nginx 访问日志实证:该页面浏览器**只调了 `/admin/role/all`(给「按角色」用),对本接口 `/admin/notification/custom-push/admins` 零调用**。即下拉 UI 建了但**没接搜索接口**。后端实测 200、返 20 个员工,**后端没问题**。
|
||||
> - **修法**:把「指定员工」的选择员工下拉接成**远程搜索**——输入时调 `GET /admin/notification/custom-push/admins?keyword={输入值}`,用返回的 `adminId` 作 option value、`name`(无则 username) 作 label,最终提交 §5.2 的 `adminIds`。
|
||||
> - ⚠️ **字段名和 C 端不一样别照搬**:C 端选人接口 `/custom-push/users` 返 `id`/`realName`/`phone`,本员工接口返的是 **`adminId`/`name`/`username`**。若克隆 C 端组件,必须改字段映射(`id→adminId`、`realName→name`),否则即便接上了也会因取不到字段而「无数据」。
|
||||
|
||||
### 5.2 发内部员工站内信 POST `/admin/notification/custom-push`
|
||||
|
||||
在原「自定义推送」请求上**新增**:`channel="ADMIN_INAPP"` + 两种目标类型:
|
||||
|
||||
```bash
|
||||
# 按指定员工
|
||||
curl -X POST -H "Authorization: Bearer <adminToken>" -H 'Content-Type: application/json' \
|
||||
-d '{"channel":"ADMIN_INAPP","targetType":"ADMIN_SPECIFIC","adminIds":[1001,1002],
|
||||
"title":"系统维护通知","content":"今晚 22:00 维护","categoryCode":"SYSTEM"}' \
|
||||
"https://api.test.1814.love:9443/admin/notification/custom-push"
|
||||
|
||||
# 按角色(roleKeys 如 house_lead/house_keeper)
|
||||
# body: {"channel":"ADMIN_INAPP","targetType":"ADMIN_BY_ROLE","roleKeys":["house_lead"], "title":..,"content":..}
|
||||
```
|
||||
|
||||
响应:`{ totalCount, successCount, failCount }`。被推的员工立即在 SSE 收到 `message` 事件、收件箱多一条。
|
||||
|
||||
> 说明 1:**消息分类对内部员工固定 SYSTEM**,后端强制写 SYSTEM,前端**「消息分类」下拉对内部员工可隐藏/不传**(传了也会被忽略为 SYSTEM)。
|
||||
> 说明 2:按角色(ADMIN_BY_ROLE)当前覆盖**已绑企微的同角色活跃员工**;要覆盖未绑企微的,用 ADMIN_SPECIFIC 指定人。
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据结构
|
||||
|
||||
### 6.1 AdminMessageRespVO(收件箱条目)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `messageId` | string | 消息 ID(雪花,String 透传防精度丢失) |
|
||||
| `categoryCode` | string | 分类编码(SYSTEM/ORDER/TRIP/PROMO …) |
|
||||
| `title` | string | 标题 |
|
||||
| `content` | string | 正文 |
|
||||
| `link` | string | 跳转链接(可空) |
|
||||
| `bizId` / `bizType` | string | 业务关联(可空) |
|
||||
| `isRead` | int | **0=未读 1=已读**(数字,非布尔) |
|
||||
| `createTime` | long | 毫秒时间戳 |
|
||||
|
||||
### 6.2 AdminPushTargetVO(选人)
|
||||
|
||||
`adminId`(string) / `name`(企微真名优先,回退用户名) / `username` / `roleKey` / `roleName`
|
||||
|
||||
### 6.3 SSE message 信令
|
||||
|
||||
`adminId`(number) / `unreadCount`(number) / `title`(string)
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **与 C 端站内信完全隔离**:内部员工走 `admin_message`,C 端客人走 `user_message`,互不影响。
|
||||
- ✅ **DB 为真相源,SSE 仅信令**:SSE 丢一条不丢数据(重连拉未读对账)。
|
||||
- ✅ **多实例安全**:经 Redis Pub/Sub 跨实例广播,员工连任意实例都能实时收到。
|
||||
- ✅ **越权防护**:收件箱所有读写按当前 adminId 限定,只能看/操作自己的消息。
|
||||
- ✅ **房务/车务事件**:内部员工类事件(房务组长/房务组/认领人等)开站内信后也会落员工收件箱(与企微并行)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估
|
||||
|
||||
- **破坏性**:无(全为新增接口 + 新增 channel/targetType,C 端站内信与原自定义推送 C 端路径零改动)。
|
||||
- **前端是否必须同步**:是(铃铛 + 收件箱页 + EventSource 客户端 + 自定义推送选人加「内部员工」选项)。
|
||||
- **存量数据**:无影响(新表新建)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **SSE token 走 query**(`?token=`),EventSource 不能带 Authorization 头;后端已处理 nginx 缓冲,前端正常用 `EventSource` 即可。
|
||||
2. **SSE 是信令不是正文**:收到 `message` 用 `unreadCount` 刷角标,正文调 `/admin/message/list` 拉。
|
||||
3. **重连对账**:EventSource 自动重连,重连后调一次 `unread-count` 防漏推。
|
||||
4. **isRead 是数字 0/1**(不是布尔),`messageId`/`adminId` 出参注意是 String 透传防精度。
|
||||
5. **未读角标双来源**:登录/刷新页用 `unread-count` 拉一次初值,之后由 SSE `message` 事件增量刷新。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联
|
||||
|
||||
- 工单 #3414;PR #3424(主功能)+ #3425(SSE 过 nginx 缓冲修复)
|
||||
- 后端负责人:@wx
|
||||
- 前端对接(管理后台):mmg
|
||||
@@ -0,0 +1,96 @@
|
||||
# 【后端修复 + 前端优化·管理后台】退费说明保存 500 已修 + 提交确认弹窗字段显示
|
||||
|
||||
> **PR**: #3429(后端修复)| **关联工单**: #3428
|
||||
> **服务**: hl-resource-service | **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「编辑景区 / 活动 → 退费说明」tab;前端列表行「确认提交修改」弹窗
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **后端 bug 已修(前端可重测)**:景区/活动「退费说明」保存**带「生效起 / 生效止」日期**的退费明细时报 500,现已修复并部署测试服。接口契约**未变**,前端无需改调用方式,重新走原保存流程即可。
|
||||
2. **前端待优化(§2)**:列表「确认提交修改」弹窗里 `refundNote` 字段显示为英文字段名 + 值「[复杂对象]」,普通运营看不懂,建议改为中文标签「退费说明」+ 可读摘要。
|
||||
3. **测试数据已补**:已给测试库 **66 个景区**全部补充退费说明示例数据(每个 3 条明细,含带日期明细),方便前端联调展示。
|
||||
|
||||
---
|
||||
|
||||
## 1. 后端修复说明
|
||||
|
||||
### 现象
|
||||
「编辑景区 → 退费说明」填入带「生效起 / 生效止」日期的退费明细,点保存 → 500:
|
||||
```
|
||||
服务器内部错误[MyBatisSystemException]: org.apache.ibatis.type.TypeException:
|
||||
Could not set parameters for mapping: Para...
|
||||
```
|
||||
不填日期则保存成功。这也是此前测试库退费说明(带日期)一直存不进的原因。
|
||||
|
||||
### 根因
|
||||
退费明细数组(`items`)以 JSON 列存库(MyBatis-Plus JacksonTypeHandler),明细条目含 `effectiveFrom` / `effectiveTo`(LocalDate)。MP 默认的 JacksonTypeHandler ObjectMapper 未注册 JavaTimeModule,序列化 Java 8 时间类型抛异常,在写库参数阶段表现为「Could not set parameters」→ 500。
|
||||
|
||||
### 修复
|
||||
hl-resource-service 给 JacksonTypeHandler 配置注册了 JavaTimeModule 的 ObjectMapper(日期以 ISO 字符串 `yyyy-MM-dd` 存储),纯加性修复,不改接口契约。
|
||||
|
||||
### 受影响接口(契约不变,仅恢复正常保存)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| PUT | `/admin/refund-note` | 退费说明独立保存(upsert) |
|
||||
| PUT | `/admin/scenic/spot/{scenicId}` | 景区编辑(融合 refundNote 字段) |
|
||||
| PUT | `/admin/activity/...`(编辑) | 活动编辑(融合 refundNote 字段) |
|
||||
|
||||
### 测试服实测(已通过)
|
||||
- 带生效起止日期保存 → **200**,回读确认 `effectiveFrom/effectiveTo` 正确落库、`settleScopeLabel` 正常拼装(如「按人」)
|
||||
- 不带日期保存 → **200**(不回归)
|
||||
|
||||
---
|
||||
|
||||
## 2. 前端待优化:「确认提交修改」弹窗字段显示
|
||||
|
||||
### 现状(截图)
|
||||
列表行编辑提交时的「确认提交修改」弹窗,对 `refundNote` 字段显示为:
|
||||
|
||||
| 字段 | 原值 | 新值 |
|
||||
|---|---|---|
|
||||
| `refundNote` | [复杂对象] | [复杂对象] |
|
||||
|
||||
两个问题:
|
||||
1. **字段名是英文 `refundNote`** —— 运营看不懂,应显示中文「退费说明」。
|
||||
2. **值显示「[复杂对象]」** —— 退费说明是结构化对象(整体介绍 + 明细数组),无法直接 diff,建议给个可读摘要而非「复杂对象」。
|
||||
|
||||
### 期望
|
||||
|
||||
| 字段 | 期望显示 |
|
||||
|---|---|
|
||||
| 字段名 | 「退费说明」(中文标签,建议前端维护字段 key → 中文名映射表) |
|
||||
| 值 | 可读摘要,如「整体介绍 + N 条明细」,或直接提示「退费说明已修改(点开退费说明 tab 查看)」,避免「复杂对象」字样 |
|
||||
|
||||
> 说明:`refundNote` 是后端字段 key,后端不下发中文标签;字段名→中文、复杂对象的友好展示属前端渲染逻辑。其他类似的结构化对象字段(如未来的多媒体、行程节点等)建议一并按此约定处理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试数据
|
||||
|
||||
已通过 `PUT /admin/refund-note` 给测试库 **全部 66 个景区**补充退费说明示例数据:
|
||||
- 整体介绍:「退改规则如下,赠送项目不退费,具体以下方明细为准。」
|
||||
- 明细 3 条:出发前 7 天以上(0/人,全额退款)/ 出发前 3-6 天(30/人,带生效起止 2026-01-01~2026-12-31)/ 出发前 48 小时内(100/人,不可退)
|
||||
|
||||
前端可直接在景区编辑页「退费说明」tab 看到回显数据联调。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端动作
|
||||
|
||||
1. **重测退费说明保存**:带生效起止日期的明细现可正常保存(原 500 已修)。
|
||||
2. **优化提交确认弹窗**(§2):`refundNote` → 中文「退费说明」;值「[复杂对象]」→ 可读摘要。
|
||||
3. 联调可用测试库已补的 66 个景区退费说明数据。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
- **后端修复 PR**:#3429(Closes #3428)
|
||||
- **退费说明模块接口契约**:见同目录退费说明相关 changelog
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:待指派
|
||||
@@ -0,0 +1,175 @@
|
||||
# 【新增接口·管理后台】图标库 新增 parse-svg-text(支持第三方图标库直传 SVG 字符串校验)
|
||||
|
||||
> **PR**: #3437 | **关联工单**: #3434
|
||||
> **服务**: hl-user-service | **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「图标库」图标新增/编辑页——新增图标的「来源」除「上传 SVG 文件」外,新增「从第三方图标库选取」路径
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **新输入路径**:前端反馈,除了上传 `.svg` 文件,运营还可以从前端集成的**第三方图标库**(iconfont / 图标组件库等)直接选取图标,此时前端**已经拿到 SVG 字符串**,无需"上传文件 → 后端解析"这一步。
|
||||
|
||||
2. **两条保存路径都支持,二选一**:
|
||||
|
||||
| 路径 | 调用 | 适用 |
|
||||
|---|---|---|
|
||||
| A. 直接保存 | `POST /admin/icon/item`(`svgContent` 直接传字符串) | 第三方库字符串**本就能直接保存**——保存接口的 `svgContent` 一直是字符串字段,且后端保存时第一步即做同款安全校验 |
|
||||
| B. 先预校验再保存(**推荐**) | `POST /admin/icon/parse-svg-text` 预校验 → 填表单 → `POST /admin/icon/item` 保存 | 与「上传文件」路径(`parse-svg` → 保存)UX 对称,**错误早暴露**:含 `<script>`/DOCTYPE 等浏览器能渲染、后端会拒的内容,选取后立即报错,不必等填完整张表单点保存才发现 |
|
||||
|
||||
3. **安全口径完全一致**:`parse-svg-text` 与文件路径 `parse-svg`、以及保存接口共用**同一套**服务端校验(XXE-safe 解析 + 拒 `<script>`/`<foreignObject>`/`on*` 事件属性/`javascript:` 外链 + ≤256KB)。`parse-svg-text` **仅校验、不落库**;落库仍走保存接口(保存时二次校验,纵深防御)。前端内联渲染 SVG 时**仍建议先做一次 sanitize**。
|
||||
|
||||
4. 本次**纯加性**:不改动任何已有端点(`parse-svg` / `item` / 查询等契约全部不变)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
图标库新增图标原流程(上传文件):
|
||||
|
||||
```
|
||||
上传 .svg 文件 → POST /admin/icon/parse-svg(解析+校验,返回字符串)→ 回填表单 → POST /admin/icon/item(保存)
|
||||
```
|
||||
|
||||
新增「第三方库选取」路径,前端已持有字符串,对称流程:
|
||||
|
||||
```
|
||||
第三方库选取(拿到 SVG 字符串)→ POST /admin/icon/parse-svg-text(校验,返回字符串)→ 回填表单 → POST /admin/icon/item(保存)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|----------|------|
|
||||
| 1 | POST | `/admin/icon/parse-svg-text` | **新增** | 校验前端直传的 SVG 字符串(来自第三方库),仅校验不落库 |
|
||||
|
||||
> 网关路由 `/admin/icon/**` 已是通配,本端点自动覆盖,无需前端额外处理路由。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 校验前端直传的 SVG 字符串
|
||||
|
||||
**POST** `/admin/icon/parse-svg-text`
|
||||
|
||||
- **使用场景**:从第三方图标库选取图标(已是字符串)后,保存前做合法性 + 安全校验
|
||||
- **认证**:管理后台 JWT
|
||||
- **幂等**:是(仅校验,不落库)
|
||||
|
||||
**Body 入参**(`IconSvgParseTextReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `svgContent` | string | ✅ | `@NotBlank` + `@Size(max=262144)` | 第三方库选取的原始 SVG 字符串(UTF-8 文本) |
|
||||
|
||||
**出参**:`Result<IconSvgParseRespVO>`(与 `parse-svg` 复用同一响应 VO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `svgContent` | string | 校验通过的 SVG 字符串(原样回填保存表单) |
|
||||
| `fileName` | string | **恒为 `null`**(非文件来源,仅文件上传路径才有值) |
|
||||
| `size` | long | 内容**字节数(UTF-8 编码)**,非字符数 |
|
||||
|
||||
**典型示例 请求**:
|
||||
```bash
|
||||
curl -X POST "https://api.test.1814.love:9443/admin/icon/parse-svg-text" \
|
||||
-H "Authorization: Bearer <adminToken>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"svgContent":"<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>"}'
|
||||
```
|
||||
|
||||
**典型示例 响应**(测试服实测):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"svgContent": "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\"/></svg>",
|
||||
"fileName": null,
|
||||
"size": 56
|
||||
},
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
**异常 响应**(SVG 含脚本/事件/外链,测试服实测):
|
||||
```json
|
||||
{ "code": 210757, "message": "SVG 含不安全内容(脚本/事件/外链),请清理后重试", "data": null }
|
||||
```
|
||||
|
||||
**异常 响应**(svgContent 为空/纯空白,@NotBlank 拦截):
|
||||
```json
|
||||
{ "code": 400, "message": "SVG 内容不能为空", "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 保存图标(已有接口,未变,此处仅提示第三方库路径如何用)
|
||||
|
||||
**POST** `/admin/icon/item`
|
||||
|
||||
第三方库选取的字符串可**直接**作为 `svgContent` 传入保存,无需先上传成文件。保存接口本就对 `svgContent` 做同款安全校验(见 §6 错误码)。完整字段契约见图标库模块首推 changelog `03_3390_图标库模块...` §3.6,本次未改动。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端动作(mmg)
|
||||
|
||||
1. **图标新增/编辑页加"来源"选择**:现有「上传 SVG 文件」之外,加「从第三方图标库选取」。
|
||||
2. **第三方库选取后(推荐路径 B)**:拿到 SVG 字符串 → 调 `POST /admin/icon/parse-svg-text` 预校验 → 成功则用返回的 `svgContent` 回填表单(同上传文件路径的回填逻辑)→ 失败则按 §6 错误码提示用户更换图标。
|
||||
3. **或直接保存(路径 A)**:若不想加预校验步骤,可把第三方库字符串直接放进 `POST /admin/icon/item` 的 `svgContent`,保存接口会做同款校验并返回对应错误码。
|
||||
4. **渲染仍建议 sanitize**:后端已拒明显恶意内容,前端内联渲染(`v-html`/`innerHTML`)时再做一次 sanitize 作纵深防御。
|
||||
|
||||
---
|
||||
|
||||
## 5. 枚举 / 数据字典
|
||||
|
||||
无新增枚举。本端点不涉及状态/类型字段。
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误码
|
||||
|
||||
| code | 含义 | 触发条件 | 涉及接口 |
|
||||
|---|---|---|---|
|
||||
| 400 | 参数校验失败 | `svgContent` 为空/纯空白(`@NotBlank`)或超 `@Size(262144)` | §3.1 |
|
||||
| 401 | 未登录 | admin JWT 无效或缺失 | §3.1 |
|
||||
| **210754** | SVG 内容不合法 | 非合法 XML 或根元素不是 `<svg>` | §3.1 §3.2 |
|
||||
| **210755** | SVG 内容过大 | 超过 256KB(按 UTF-8 字节) | §3.1 §3.2 |
|
||||
| **210757** | SVG 含不安全内容 | 含 `<script>` / `<foreignObject>` / `on*` 事件 / `javascript:` 外链 | §3.1 §3.2 |
|
||||
|
||||
> `parse-svg-text` 的错误码是文件路径 `parse-svg` 的子集(无 `210753`「文件为空」/`210756`「文件读取失败」——这两个仅文件上传场景有;字符串为空由 `@NotBlank` 在网关层返 400)。错误码段位 210700-210799 归属图标库模块。
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证(测试服实测,已通过)
|
||||
|
||||
| 用例 | 输入 | 结果 |
|
||||
|---|---|---|
|
||||
| 合法字符串 | 标准 SVG | http200 / code200,`svgContent` 回显、`fileName=null`、`size=56` |
|
||||
| 含 `<script>` | 带脚本的 SVG | http200 / **code210757** 拒 |
|
||||
| 空白串 | `" "` | http200 / **code400** SVG内容不能为空 |
|
||||
| 中文 SVG | 含 `<title>晴天</title>` | http200 / code200,`size=83` > 字符数 79(证明按 UTF-8 字节计) |
|
||||
|
||||
- 单测 `IconItemServiceTest` 36 全绿(含 script/事件/外链/foreignObject/超限/UTF-8 字节数等正反路径)
|
||||
- hl-user-service 双实例(8081 + 8181)均已部署新代码、health UP
|
||||
|
||||
---
|
||||
|
||||
## 8. 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否(纯新增端点,无任何已有接口改动)
|
||||
- **前端是否必须同步上线**:是(新输入路径,前端按 §4 接入「第三方库选取」)
|
||||
- **影响已有数据**:无
|
||||
|
||||
---
|
||||
|
||||
## 9. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3437(Closes #3434)
|
||||
- **图标库模块首推 changelog**:`03_3390_图标库模块-新增接口-管理后台+小程序.md`(含分类/图标/上传解析全量契约)
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:mmg
|
||||
@@ -0,0 +1,96 @@
|
||||
# 【批量修复·管理后台 + 小程序】wx 近5天代码审计 batch 修复(前端可见的校验收紧 + 行为修正)
|
||||
|
||||
> **PR**: #3442 | **关联工单**: #3440
|
||||
> **服务**: hl-product-service-v2 / hl-resource-service / hl-fleet-service / hl-user-service / hl-order-service-v2 | **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 多处后端 bug 修复,本篇仅列**前端可观察到的变化**(校验收紧 / 展示行为修正);纯内部修复不在此列
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
本批修复源自一次代码审计,多为后端内部纠错(无前端影响)。下面**只列前端需要知道的 4 处**:
|
||||
|
||||
1. **几处入参新增字典校验**(传非法值会从"静默落库"变成 **400 报错**)——前端表单须传字典值。
|
||||
2. **内部员工站内信列表 `pageSize` 上限 100**(超过返 400)。
|
||||
3. **服务类(SERVICE)资源的退费说明现在会进产品行程聚合 / 订单快照**——前端行程/订单详情可能多展示出 SERVICE 节点的退费说明。
|
||||
4. **小程序小蒙马"最早可售日"修正**——已售罄(满团)但出发日在未来的班期不再被当作"最早可售日"。
|
||||
|
||||
---
|
||||
|
||||
## 1. 入参校验收紧(传非法值 → 400)
|
||||
|
||||
| # | 接口 | 字段 | 新校验 | 说明 |
|
||||
|---|------|------|--------|------|
|
||||
| 1 | 司机档案新增/编辑(admin) | `gender` | 仅 `1`=男 / `2`=女(可空) | 原仅长度校验,可存"男/0/AB"脏值;现非字典值返 400 |
|
||||
| 2 | 出行人保存(mp/admin TravelerRequest) | `gender` | 仅 `0`=未知 / `1`=男 / `2`=女 | 原无范围校验,可存 '3'/'99';现非字典值返 400 |
|
||||
| 3 | 车型价格日历 设置/改状态(admin) | `status` | 仅 `AVAILABLE` / `CLOSED` | 原无字典校验;现非字典值返 400 |
|
||||
|
||||
> 前端动作:上述表单提交前确保 `gender`/`status` 传字典值(与全系统性别字典 1=男/2=女/0=未知、价格日历两态一致)。若此前已按字典传值,则**无影响**。
|
||||
|
||||
**示例(gender 非法)**:
|
||||
```
|
||||
POST /admin/fleet/driver body 含 "gender":"男"
|
||||
→ { "code": 400, "message": "性别只能 1=男/2=女" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 内部员工站内信列表 pageSize 上限 100
|
||||
|
||||
**GET** `/admin/message/list`
|
||||
|
||||
补了入参校验,`pageSize` 超过 100 返 400(原校验未生效可超量查)。
|
||||
|
||||
**测试服实测**:
|
||||
```
|
||||
GET /admin/message/list?page=1&pageSize=10 → code 200(正常)
|
||||
GET /admin/message/list?page=1&pageSize=100000 → code 400「每页条数不能超过100」
|
||||
```
|
||||
|
||||
> 另:内部员工站内信目前**仅 SYSTEM 一类**(#3427 已固定),`categoryCode` 传 HOUSE/ORDER 过滤结果恒空、`markCategoryRead` 传 HOUSE/ORDER 恒更新 0 行;建议列表 `categoryCode` 留空查全部。Swagger 文档已同步标注。
|
||||
|
||||
---
|
||||
|
||||
## 3. SERVICE(服务类)资源退费说明进入行程聚合 / 订单快照
|
||||
|
||||
服务类资源(service_item)此前已支持配置退费说明(#3407),但**产品行程节点聚合接口漏填了 SERVICE 的退费说明**,导致:admin 服务详情能看到退费说明,但它进不了产品行程聚合 → 冻不进订单快照 → 司机行程单/订单详情看不到。
|
||||
|
||||
本次已补齐:**SERVICE 资源的退费说明现在与 SCENIC/ACTIVITY 一样,会随产品行程聚合下发,并在下单时冻进订单快照**。
|
||||
|
||||
> 前端动作:产品行程详情 / 订单详情中,挂在 SERVICE 节点上的资源现在可能多出 `refundNote`(退费说明)字段,按既有 SCENIC/ACTIVITY 的退费说明渲染逻辑展示即可。
|
||||
|
||||
---
|
||||
|
||||
## 4. 小程序小蒙马"最早可售日"修正
|
||||
|
||||
小蒙马产品的"最早可售日"(`findEarliestAvailableBatchDate`)原仅按"出发日在未来 + 报名窗口未关"判定,**不看库存**,导致已售罄(持久 FULL)但出发日在未来的班期被错误当成"最早可售日"。
|
||||
|
||||
已修正为**按展示态 == 报名中(含库存判定)**取最早可售班期。
|
||||
|
||||
> 前端动作:无需改动,小程序展示的"最早可售日"会自动变准(排除满团班期)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 其他(无前端影响,仅备案)
|
||||
|
||||
- product-v2:复制产品时 GROUP 班期状态重置为"报名中"(原沿用源终态致复制出已满/已取消班期)。
|
||||
- product-v2:admin 增加行程天 / 上下架去掉了误伤连续操作的幂等节流(原同管理员 5 秒内第二次操作被"请勿重复提交"拦),不影响接口契约。
|
||||
- resource:`ServiceUpdateRequest.vehicleType` 长度上限统一为 32(原有矛盾的 32/500 双重校验,实际一直生效 32)。
|
||||
- order-v2:删除一个 0 调用死方法(无行为变化)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证
|
||||
|
||||
- 各模块编译 + 相关单测通过(product-v2 +37 / resource +2 含售罄班期不选、复制重置、SERVICE refundNote 填充等回归)。
|
||||
- 5 服务部署测试服 + 双实例 health UP;API 实测 pageSize 上限 400 已生效。
|
||||
|
||||
---
|
||||
|
||||
## 7. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3442(Closes #3440)
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台 + 小程序)**:mmg
|
||||
@@ -0,0 +1,52 @@
|
||||
# 【后端调整·管理后台】退款详情/列表 actualAmount 字段语义调整(审计 followup)
|
||||
|
||||
> **PR**: #3446 | **关联工单**: #3445
|
||||
> **服务**: hl-order-service-v2(退款)| **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「退款申请」详情 + 列表的 `actualAmount`(实退/应退额)字段
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
本批为代码审计 followup 修复,绝大多数为后端内部纠错(无前端影响)。**前端唯一需知**:退款 VO 的 `actualAmount` 字段返回规则调整。
|
||||
|
||||
---
|
||||
|
||||
## 1. actualAmount 返回规则调整
|
||||
|
||||
### 原行为
|
||||
退款详情/列表 VO 仅在 `REFUNDED`(已退款)状态返回 `actualAmount`,其余状态一律返回 `null`。导致管理端「确认实退」弹窗在 `APPROVED`(已审批待执行)状态下拿不到应退额、无法预填。
|
||||
|
||||
### 新行为
|
||||
按"是否已确定应退/会退款"分组:
|
||||
|
||||
| 状态 | actualAmount |
|
||||
|---|---|
|
||||
| `APPROVED`(已审批)/ `APPEAL_APPROVED`(申诉通过)/ `REFUNDING`(退款中)/ `REFUNDED`(已退款)| **返回金额**(应退/实退额)|
|
||||
| `PENDING` / `APPEALING` / `REJECTED` / `APPEAL_REJECTED` / `CANCELLED` | `null`(未确定或不退款)|
|
||||
|
||||
### 前端动作
|
||||
- 「确认实退」弹窗可用 `APPROVED`/`APPEAL_APPROVED`/`REFUNDING` 状态下的 `actualAmount` 预填金额。
|
||||
- 字段语义:在未到账状态它表示"应退额",到账后(`REFUNDED`)表示"实退额";展示文案可按状态区分(如未到账标"应退"、到账标"实退")。
|
||||
- 接口路径/结构未变,仅 `actualAmount` 在更多状态下有值。
|
||||
|
||||
---
|
||||
|
||||
## 2. 其他(无前端影响,仅备案)
|
||||
|
||||
本批同时处理了一批后端技术债与规范修复,**均不改变接口契约**,前端无需关注:
|
||||
- 退款 failReason 回填改走 PaymentService 封装(分层规范);资源退费说明存在性校验抽 Checker(分层规范)。
|
||||
- 景区/活动/服务的新建/编辑接口内部把字典翻译(label)的远程调用移出数据库事务(性能,响应结构不变)。
|
||||
- 收费=否时旧价格日历保留不清理(按产品决策,防误删)。
|
||||
- 图标库 SVG 安全校验加固(更严格地拒绝危险 SVG,正常图标不受影响)。
|
||||
- 备品计费口径归一、错误码/常量去重、死代码清理等。
|
||||
|
||||
---
|
||||
|
||||
## 3. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3446(Closes #3445)
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:mmg
|
||||
@@ -0,0 +1,165 @@
|
||||
# 【前端待建·管理后台】服务标准模板管理页(后端 CRUD 已就绪,前端缺管理页 + 菜单)
|
||||
|
||||
> **PR**: 无(后端接口此前已随 #3147/#3253 系列落地,本篇补发完整管理 CRUD 契约 + 提前端补管理页)
|
||||
> **服务**: hl-product-service-v2 | **更新时间**: 2026-06-04
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「行程管理 → 服务标准模板」(**当前缺失**,需前端新建管理页 + 菜单)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **现状**:管理后台找不到「服务标准模板」菜单。排查确认 —— **后端管理 CRUD 接口完全就绪**(`/admin/product/service-standard-template`,含分页/详情/启用下拉/保存/删除,测试库已有 1 条模板数据),但**前端 v2.1 没有建独立管理页**,`sys_menu` 也没建菜单。
|
||||
2. **前端现有的只是"选模板"**:`api/product/service-standard.js` 仅有 `getEnabled`(启用下拉) / `detail`(详情) / `preview`(产品预览),用于**产品编辑 Step5「补充信息」里挑一个已有模板**(`ServiceStandardPanel.vue`)+ 订单详情展示(`ServiceStandardTab.vue`)。**没有分页/保存/删除调用,没有管理页,没有路由**。
|
||||
3. **对照「装备建议组合」**:装备建议组合是完整的 —— 前端有 `views/product/equipment-template/index.vue` + `EditModal.vue`,且 `sys_menu` 有菜单 `/product/equipment-template`(挂在「行程管理」下)。**服务标准模板缺的就是这一套**。
|
||||
4. **请前端补**:参照装备建议组合,新建服务标准模板管理页(列表 + 新增/编辑弹窗),路由建议 `/product/service-standard-template`,菜单挂「行程管理」下。菜单加好后即可在导航看到。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
服务标准模板(出团服务说明书):运营维护一批"服务标准"模板,产品在 Step5 补充信息里**绑定一个模板**(用 §2.3 启用下拉选)。模板本身的增删改查需要一个独立管理页(类似装备建议组合 / 报价公式那种列表管理页)。
|
||||
|
||||
模板结构是两层:**分组 Section(如"出行服务")→ 条目 Item(主文案 + 正文 + 备注 + 颜色 + 联系人 + 电话)**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单(后端均已就绪,前端待对接)
|
||||
|
||||
| # | 方法 | 路径 | 说明 | 前端现状 |
|
||||
|---|------|------|------|---------|
|
||||
| 1 | GET | `/admin/product/service-standard-template/page` | 分页(名称模糊 + 状态过滤)| ❌ 未调用 |
|
||||
| 2 | GET | `/admin/product/service-standard-template/{id}` | 详情(含分组+条目)| ✅ 已有 `getServiceStandardTemplateDetail` |
|
||||
| 3 | GET | `/admin/product/service-standard-template/enabled` | 启用模板下拉(产品绑定用)| ✅ 已有 `getServiceStandardTemplateEnabled` |
|
||||
| 4 | POST | `/admin/product/service-standard-template` | 保存(id 空=新增 / 非空=修改)| ❌ 未调用 |
|
||||
| 5 | DELETE | `/admin/product/service-standard-template/{id}` | 软删(已绑定产品不受影响)| ❌ 未调用 |
|
||||
|
||||
> 管理页主要需补 **#1 分页、#4 保存、#5 删除** 三个调用(#2/#3 已存在可复用)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 分页查询
|
||||
|
||||
**GET** `/admin/product/service-standard-template/page`
|
||||
|
||||
- **认证**:管理后台 JWT | **幂等**:是
|
||||
|
||||
**Query 入参**(`ServiceStandardTemplatePageReqVO`,继承分页参数):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `page` | int | ❌ | 页码,默认 1 |
|
||||
| `pageSize` | int | ❌ | 每页条数,默认 20 |
|
||||
| `name` | string | ❌ | 模板名称模糊匹配 |
|
||||
| `status` | string | ❌ | 状态过滤(ENABLED / DISABLED,留空=不过滤)|
|
||||
|
||||
**出参**:`Result<PageResult<ServiceStandardTemplateRespVO>>`,`records[]` 为完整模板 VO(结构见 §3.2)。
|
||||
|
||||
### 3.2 详情
|
||||
|
||||
**GET** `/admin/product/service-standard-template/{id}`
|
||||
|
||||
**出参**:`Result<ServiceStandardTemplateRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | Long | 模板 ID |
|
||||
| `name` | string | 模板名称 |
|
||||
| `intro` | string | 顶部提示语 |
|
||||
| `applicableScope` | string | 适用对象 |
|
||||
| `sections[]` | array | 分组列表(见下)|
|
||||
| `isDefault` | boolean | 是否默认模板 |
|
||||
| `sortOrder` | int | 排序(越小越靠前)|
|
||||
| `status` | string | ENABLED / DISABLED |
|
||||
| `statusLabel` | string | 状态中文(启用 / 停用)|
|
||||
| `createTime` / `updateTime` | datetime | 时间 |
|
||||
|
||||
**`sections[]` 单项**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `title` | string | 分组标题(如"出行服务")|
|
||||
| `items[]` | array | 条目列表(见下)|
|
||||
|
||||
**`items[]` 单项(6 字段)**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `title` | string | 主文案(如"接送站服务")|
|
||||
| `content` | string | 正文 |
|
||||
| `remark` | string | 备注 / 灰色二级说明 |
|
||||
| `color` | string | 文案颜色 #RRGGBB(可空)|
|
||||
| `contactName` | string | 联系人(可空)|
|
||||
| `phone` | string | 手机号(可空)|
|
||||
|
||||
### 3.3 启用模板下拉
|
||||
|
||||
**GET** `/admin/product/service-standard-template/enabled`(不分页)
|
||||
|
||||
**出参**:`Result<List<ServiceStandardTemplateSimpleRespVO>>`,仅 `{id, name}`,仅返回 ENABLED。供产品 Step5 绑定服务标准时选择(前端已用)。
|
||||
|
||||
### 3.4 保存(新增 / 修改)
|
||||
|
||||
**POST** `/admin/product/service-standard-template`
|
||||
|
||||
- **认证**:管理后台 JWT | **幂等**:是(@Idempotent 5s)
|
||||
|
||||
**Body 入参**(`ServiceStandardTemplateSaveReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | Long | ❌ | — | 空=新增 / 非空=修改 |
|
||||
| `name` | string | ✅ | @NotBlank | 模板名称 |
|
||||
| `intro` | string | ❌ | — | 顶部提示语 |
|
||||
| `applicableScope` | string | ❌ | — | 适用对象 |
|
||||
| `sections` | array | ✅ | @NotNull | 分组+条目(见下)|
|
||||
| `isDefault` | boolean | ❌ | — | 是否默认模板 |
|
||||
| `sortOrder` | int | ❌ | — | 排序 |
|
||||
| `status` | string | ❌ | — | ENABLED / DISABLED,留空默认 ENABLED |
|
||||
|
||||
**`sections[]` 单项**:`title`(✅ @NotBlank 分组标题)+ `items[]`
|
||||
|
||||
**`items[]` 单项**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `title` | string | ✅ | @NotBlank,≤200 | 主文案 |
|
||||
| `content` | string | ❌ | ≤2000 | 正文 |
|
||||
| `remark` | string | ❌ | ≤2000 | 备注 |
|
||||
| `color` | string | ❌ | #RRGGBB | 文案颜色 |
|
||||
| `contactName` | string | ❌ | ≤100 | 联系人 |
|
||||
| `phone` | string | ❌ | — | 手机号 |
|
||||
|
||||
**出参**:`Result<Long>`(data = 模板 ID)
|
||||
|
||||
```bash
|
||||
curl -X POST "https://api.test.1814.love:9443/admin/product/service-standard-template" \
|
||||
-H "Authorization: Bearer {adminToken}" -H "Content-Type: application/json" \
|
||||
-d '{"name":"草原出团服务说明书","intro":"出团前请仔细阅读","sections":[{"title":"出行服务","items":[{"title":"接送站服务","content":"提供机场/车站免费接送","contactName":"李经理","phone":"13800000000"}]}],"status":"ENABLED"}'
|
||||
# 返回: {"code":200,"data":1001,"message":"成功"}
|
||||
```
|
||||
|
||||
### 3.5 删除
|
||||
|
||||
**DELETE** `/admin/product/service-standard-template/{id}` → `Result<Void>`,软删,已绑定产品不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端动作(mmg)
|
||||
|
||||
1. **新建管理页**:参照「装备建议组合」(`views/product/equipment-template/index.vue` + `EditModal.vue`),建服务标准模板列表页 + 新增/编辑弹窗,编辑弹窗支持「分组 → 条目」两层动态增删(条目 6 字段:主文案/正文/备注/颜色/联系人/电话)。
|
||||
2. **补 API 调用**:在 `api/product/service-standard.js` 增 `page` / `save` / `delete`(`enabled` / `detail` 已有)。
|
||||
3. **加菜单**:在「系统管理 → 菜单管理」加菜单,父级=「行程管理」,路由/组件 `/product/service-standard-template`(与装备建议组合 `/product/equipment-template` 并列)。
|
||||
4. 测试库已有 1 条模板数据可直接联调列表/详情。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
- **后端管理接口**:`AdminServiceStandardTemplateController` @ `/admin/product/service-standard-template`(已部署测试服,`/page` 实测 code:200)
|
||||
- **相关 changelog**:`28_3147_产品服务标准模块...`、`29_服务标准_模板改表结构6字段...`、`03_服务标准模板与产品关联说明`
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:mmg
|
||||
@@ -0,0 +1,62 @@
|
||||
# 【前端待办清单·管理后台】内部员工站内信:「我的消息」页未建(404) + 选员工下拉未接线
|
||||
|
||||
> **关联**: 工单 #3414(PR #3424/#3425/#3427)
|
||||
> **服务**: 前端 hl-ui(**后端 100% 就绪、已部署测试服、API 实测通过,无任何后端待办**)
|
||||
> **提报时间**: 2026-06-05 | **责任方**: mmg
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 总览:后端全好,卡在两个前端待办
|
||||
|
||||
内部员工站内信后端完整交付(收件箱 API + SSE 实时 + 自定义推送选员工),但前端有 **2 处没做完**,导致功能用不起来。逐条实测定性如下,mmg 照清单勾:
|
||||
|
||||
| # | 待办 | 现象(测试环境实测) | 缺什么 |
|
||||
|---|------|---------|--------|
|
||||
| 1 | **「我的消息」收件箱页** | 访问 `/notification/my-messages` → **404 页面不存在**;菜单树里通知中心下**没有「我的消息」菜单** | 菜单 + 路由 + 页面组件 + 顶部铃铛,**全没建** |
|
||||
| 2 | **自定义推送「选择员工」下拉** | 选「站内信(内部员工)」+「指定员工」后下拉**无数据** | 下拉 UI 建了但**没接搜索接口**(访问日志实证零调用) |
|
||||
|
||||
---
|
||||
|
||||
## 1. 「我的消息」收件箱页(完全没建,404)
|
||||
|
||||
实测:直接访问 `https://web.test.1814.love:9443/notification/my-messages` 返回前端 404;`/admin/menu/tree` 里通知中心(menu_id=950)下仍只有 通知配置/消息分类管理/通知日志/自定义推送,**无「我的消息」**。
|
||||
|
||||
### 要做的(参照 04_3414 §入口位置)
|
||||
|
||||
1. **sys_menu 加菜单**:通知中心(950)下加「我的消息」,component 指向新页面组件(建议 `notification/MyMessages`),经「系统管理→菜单管理」加。
|
||||
2. **顶部全局铃铛**:未读红点 = `GET /admin/message/unread-count`,由 SSE 实时刷新。
|
||||
3. **收件箱页组件**(平铺列表,内部员工统一 SYSTEM 分类,**不需要分类 tab**):
|
||||
- 列表 `GET /admin/message/list?pageNo=&pageSize=`
|
||||
- 详情 `GET /admin/message/{id}`(🆕 深链/刷新/铃铛点击时按 ID 拉单条,返回完整 `content`/`link`;纯查询不改已读,不存在/越权返 code=200401「站内信不存在或无权访问」)
|
||||
- 单条/全部已读 `PUT /admin/message/{id}/read`、`PUT /admin/message/read-all`
|
||||
- 删除 `DELETE /admin/message/{id}`
|
||||
4. **SSE 客户端**:`new EventSource('https://api.test.1814.love:9443/ws/admin-msg/stream?token={admin token}')`,收到 `message` 事件刷新角标(详见 04_3414 §3)。
|
||||
|
||||
> 后端这些接口均已部署测试服 + 实测 200。光加菜单不建组件 = 点开 404(现状就是)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 自定义推送「选择员工」下拉(建了但没接线)
|
||||
|
||||
详见 `05_3414_选择员工下拉无数据-前端未接searchAdmins接口-管理后台.md`。要点:
|
||||
|
||||
- 「指定员工」下拉接成远程搜索:输入调 `GET /admin/notification/custom-push/admins?keyword={输入}`,`adminId` 作 value、`name` 作 label。
|
||||
- ⚠️ 字段名和 C 端不同:员工接口返 `adminId`/`name`,**别照搬 C 端的 `id`/`realName`**,克隆组件须改字段映射。
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端状态(无需改动,已全部就绪)
|
||||
|
||||
- 收件箱:`/admin/message/*`(列表/未读数/已读/删除)✅ 实测 200
|
||||
- **🆕 单条详情:`GET /admin/message/{id}`**(PR #3490,2026-06-05 合 dev-v3 + 部署测试服)✅ 实测:命中返完整 VO(含 content/link);不存在/越权返 code=200401 —— 给「我的消息」详情页深链/刷新用
|
||||
- SSE 实时:`/ws/admin-msg/stream`(过网关 + nginx 实测收到事件)✅
|
||||
- 选员工:`/admin/notification/custom-push/admins` ✅ 实测返 20 员工
|
||||
- 发站内信:`POST /admin/notification/custom-push`(ADMIN_INAPP)✅ 实测落库 + SSE 推送
|
||||
|
||||
---
|
||||
|
||||
## 4. 关联
|
||||
|
||||
- 主功能 + 完整接口契约:`04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md`
|
||||
- 选员工下拉 bug 详情:`05_3414_选择员工下拉无数据-前端未接searchAdmins接口-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
@@ -0,0 +1,87 @@
|
||||
# 【后端已修 + 前端待办·管理后台】自定义推送「选择用户」下拉:客人名字 undefined(后端已修) + 企业微信通道搜错数据源(前端待接)
|
||||
|
||||
> **关联**: 工单 #3414(内部员工站内信)| **服务**: hl-user-service(后端已改)+ 前端 hl-ui
|
||||
> **提报时间**: 2026-06-05 | **后端**: @wx | **前端待接**: mmg
|
||||
> **位置**: 管理后台 → 通知中心 → 自定义推送 → 推送目标「指定用户」→ 选择用户下拉
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 总览:两个独立问题
|
||||
|
||||
「自定义推送」选「指定用户」时,**选择用户**下拉有两处问题,一处后端已修、一处需前端接:
|
||||
|
||||
| # | 问题 | 归属 | 状态 |
|
||||
|---|------|------|------|
|
||||
| 1 | 选「站内信」/「短信」通道,客人下拉显示「**undefined 手机号**」 | 后端 | ✅ **已修 + 部署测试服实测** |
|
||||
| 2 | 选「企业微信」/「站内信(内部员工)」通道,下拉搜出来的是**客人(带手机号)**而非内部员工 | 前端 | ⏳ 待 mmg 接 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 客人名字 undefined(后端已修,PR #3511)
|
||||
|
||||
**现象**:选「站内信」或「短信」通道 + 指定用户,客人下拉显示「undefined 13700009999」。
|
||||
|
||||
**根因**:后端 `/custom-push/users` 只回填了 `realName`,而 C 端客人绝大多数没填真实姓名(realName 为空)→ 前端取 realName 即 undefined。
|
||||
|
||||
**已修**:realName 为空时回退昵称,并新增 `nickname` 字段。测试服实测:
|
||||
|
||||
```
|
||||
GET /admin/notification/custom-push/users?keyword=1
|
||||
→ [{ "id": "2059154700023836674", "realName": "游客9999", "nickname": "游客9999", "phone": "13700009999", "avatarUrl": "..." }, ...]
|
||||
```
|
||||
|
||||
**前端**:无需改动,继续展示 `realName` 即可(现已永不为空,客人显示昵称如「游客9999」)。如需更明确可改展示 `realName`(真名优先)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 企业微信 / 站内信(内部员工) 通道要按通道切数据源(前端待接)
|
||||
|
||||
**现象**:选「企业微信」通道(页面提示「企业微信仅支持推送给内部员工」)或「站内信(内部员工)」通道,「选择用户」下拉里却列出**客人**(带手机号、名字 undefined)。
|
||||
|
||||
**根因**(测试服 nginx 访问日志实证):这两个通道下,前端**仍调 `/custom-push/users`(C 端客人搜索)**,没有按通道切到员工搜索。
|
||||
|
||||
**前端改法**:按「推送通道」决定调哪个搜索接口 + 字段映射:
|
||||
|
||||
| 推送通道 | 选人调用接口 | 返回字段(label / value) |
|
||||
|---------|-------------|--------------------------|
|
||||
| 站内信(C端)/ 短信 | `GET /admin/notification/custom-push/users?keyword=` | label=`realName`(已回退昵称),value=`id`,副文本 `phone` |
|
||||
| **企业微信 / 站内信(内部员工)** | `GET /admin/notification/custom-push/admins?keyword=` | label=`name`(企微真名回退 username),value=`adminId`,副文本 `roleName`,**无手机号** |
|
||||
|
||||
⚠️ 字段名不同,克隆组件务必改映射:客人是 `id`/`realName`/`phone`;员工是 `adminId`/`name`/`username`/`roleName`。
|
||||
|
||||
提交推送时:
|
||||
- 企业微信:`{channel:"WECHAT_WORK"... 或现有企微 channel, targetType:"ADMIN_SPECIFIC", adminIds:[...]}`(内部员工 adminId)
|
||||
- 站内信(内部员工):`{channel:"ADMIN_INAPP", targetType:"ADMIN_SPECIFIC", adminIds:[...]}` 或 `ADMIN_BY_ROLE + roleKeys`
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端状态
|
||||
|
||||
- `/custom-push/users`(客人搜索):✅ 已修 name 回退 + 部署测试服实测返昵称
|
||||
- `/custom-push/admins`(员工搜索):✅ 一直正常(返 adminId/name/username/roleKey/roleName,无手机号)
|
||||
- 后端两个接口都就绪,问题 2 纯前端按通道切接口。
|
||||
|
||||
---
|
||||
|
||||
## 4. 企业微信推送给员工——后端补完,现已可用(PR #3515 / #3520)
|
||||
|
||||
之前选「企业微信 + 指定员工」点发送会报 **「不支持的目标类型: ADMIN_SPECIFIC」**(后端没接住企微+员工目标),且企微消息**丢标题、没链接**。已修复并部署测试服:
|
||||
|
||||
- ✅ **企业微信 + 指定员工(`ADMIN_SPECIFIC`) / 按角色(`ADMIN_BY_ROLE`)** 现已支持,提交 `{channel:"WECHAT_WORK", targetType:"ADMIN_SPECIFIC", adminIds:[...]}` 或 `{... targetType:"ADMIN_BY_ROLE", roleKeys:[...]}`。
|
||||
- ✅ 企微消息现在 = **标题 + 正文 + 跳转链接**(之前只有正文)。
|
||||
- 返回 `{totalCount, successCount, failCount}`:未绑企微的员工无法收企微,计入 `failCount`(属正常,前端可据此提示「N 人未绑企微未送达」)。
|
||||
|
||||
**前端无需改逻辑**(请求体已对,按 §2 切到 `/admins` 选人即可),这里是告知该通道已可正常用。
|
||||
|
||||
## 5. ⚠️ 「跳转链接」输入框文案待改(前端)
|
||||
|
||||
当前「跳转链接」输入框提示语 **「站内信时生效,如 /pages/product/list」已过时**:
|
||||
|
||||
1. 现在**企业微信推送也会带上这个链接**了,不只是站内信 → 提示语建议改成「站内信 / 企业微信生效」。
|
||||
2. ⚠️ 企微是**纯文本**消息,里面 `/pages/...` 这种**相对路径点不动**(那是小程序路径)。要在企微里能点,必须填**完整 `https://` 网址**。建议提示语引导填完整 URL,或对企业微信通道单独提示。
|
||||
|
||||
## 6. 关联
|
||||
|
||||
- 主功能:`04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md`
|
||||
- 选员工下拉历史 bug:`05_3414_选择员工下拉无数据-前端未接searchAdmins接口-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
@@ -0,0 +1,85 @@
|
||||
# 【前端 BUG·管理后台】自定义推送「选择员工」下拉无数据 = 前端未接 searchAdmins 接口
|
||||
|
||||
> **关联**: 工单 #3414(内部员工站内信,PR #3424/#3425/#3427)
|
||||
> **服务**: 前端 hl-ui(后端无改动)| **提报时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「通知中心 / 自定义推送」→ 站内信(内部员工) → 指定员工
|
||||
> **类型**: 前端接线 bug(mmg 修),**后端已实测正常无需改动**
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
测试环境「自定义推送」页,选「**站内信(内部员工)**」通道 + 「**指定员工**」后,「**选择员工**」下拉显示「**无数据**」。
|
||||
|
||||
**根因 = 前端把下拉 UI 画了,但没接后端搜索接口。** 后端 `GET /admin/notification/custom-push/admins` 实测 **200、返 20 个员工**,后端没问题。
|
||||
|
||||
---
|
||||
|
||||
## 1. 实证(测试服 nginx 访问日志)
|
||||
|
||||
在该页面操作时,浏览器对内部接口的调用**只有一条 `GET /admin/role/all`**(给「按角色」选项用的),对搜员工的 `/admin/notification/custom-push/admins` **零调用**。即「选择员工」远程搜索框没绑后端接口。
|
||||
|
||||
后端实测对照(Python 直调,均 200 有数据):
|
||||
|
||||
```bash
|
||||
# 空关键词返前 20 个员工
|
||||
GET /admin/notification/custom-push/admins?keyword= -> 200, 20 条
|
||||
# 按姓名/用户名模糊
|
||||
GET /admin/notification/custom-push/admins?keyword=admin -> 200, 2 条
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 前端修法(mmg)
|
||||
|
||||
### 2.1 「指定员工」选择员工下拉 → 接成远程搜索
|
||||
|
||||
输入时调用(带当前 admin token):
|
||||
|
||||
```
|
||||
GET /admin/notification/custom-push/admins?keyword={输入值}
|
||||
```
|
||||
|
||||
响应 `data`:员工数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 用途 |
|
||||
|---|---|---|
|
||||
| `adminId` | string | **option 的 value**(雪花 String,提交时收集成 `adminIds`)|
|
||||
| `name` | string | **option 的 label**(企微真名,可能为空则用 `username`)|
|
||||
| `username` | string | 登录名(label 兜底)|
|
||||
| `roleKey` / `roleName` | string | 角色(可展示副文本)|
|
||||
|
||||
提交推送时(§见 04_3414 §5.2):`{channel:"ADMIN_INAPP", targetType:"ADMIN_SPECIFIC", adminIds:[选中的 adminId...], title, content}`。
|
||||
|
||||
### 2.2 ⚠️ 字段名和 C 端不一样,别照搬
|
||||
|
||||
若克隆 C 端「选择用户」组件(`/custom-push/users`),注意字段名**不同**:
|
||||
|
||||
| 角色 | 接口 | 返回字段 |
|
||||
|---|---|---|
|
||||
| C 端客人 | `/custom-push/users` | `id` / `realName` / `phone` / `avatarUrl` |
|
||||
| **内部员工** | `/custom-push/admins` | **`adminId` / `name` / `username`** / roleKey / roleName |
|
||||
|
||||
克隆组件**必须改字段映射**(`id→adminId`、`realName→name`),否则就算接上接口,也会因取不到字段继续「无数据」。
|
||||
|
||||
### 2.3 「按角色」目标
|
||||
|
||||
「按角色」当前已接 `/admin/role/all` 加载角色列表(正常)。选角色后提交:`{channel:"ADMIN_INAPP", targetType:"ADMIN_BY_ROLE", roleKeys:[选中的 roleKey...], title, content}`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端状态(无需改动)
|
||||
|
||||
- `/admin/notification/custom-push/admins`(搜员工):已部署测试服,实测 200 有数据。
|
||||
- `/admin/notification/custom-push`(发站内信):已部署,ADMIN_SPECIFIC / ADMIN_BY_ROLE 实测通过。
|
||||
- 内部员工站内信分类固定 SYSTEM(后端强制,前端「消息分类」对内部员工可隐藏)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 关联
|
||||
|
||||
- 主功能 changelog:`04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md`(§5 选内部员工 + §3 SSE)
|
||||
- 后端负责人:@wx
|
||||
- 前端对接(本 bug 修复):**mmg**
|
||||
@@ -0,0 +1,79 @@
|
||||
# 【后端调整·管理后台】资源价格日历「成本价」字段改名为「协议价」(6 类资源,⚠️破坏性字段改名)
|
||||
|
||||
> **PR**: #3467 | **关联工单**: #3463
|
||||
> **服务**: hl-resource-service(价格日历)+ hl-order-service-v3(配房 Feign 契约)| **更新时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台 6 类资源(酒店/景点/活动/车辆/向导/服务)价格日历的「成本价」字段
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(破坏性变更,前端必须同步改)
|
||||
|
||||
资源价格日历里维护的「成本价」实质是跟供应商签的**协议结算价**,本次统一口径,把 6 类资源价格日历的 **字段名 `costPrice` → `protocolPrice`**(DB 列 `cost_price` → `protocol_price` 同步改)。
|
||||
|
||||
**这是破坏性变更**:接口请求/响应的 JSON key 从 `costPrice` 变成 `protocolPrice`。**前端 6 类资源价格日历页面(设置/查询/展示)必须同步把 `costPrice` 改成 `protocolPrice`,否则设置价格传不进、查询取价为 undefined。**
|
||||
|
||||
已部署测试服并 API 实测通过(写 `protocolPrice` → 查回 `protocolPrice`,旧 `costPrice` 已彻底消失)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 字段改名(6 类资源全部)
|
||||
|
||||
| 资源 | 接口前缀 | 原字段 | 新字段 |
|
||||
|---|---|---|---|
|
||||
| 酒店(房型) | `/admin/hotel/room-type/{id}/prices` | `costPrice` | `protocolPrice` |
|
||||
| 景点 | `/admin/scenic/.../prices` | `costPrice` | `protocolPrice` |
|
||||
| 活动 | `/admin/activity/.../prices` | `costPrice` | `protocolPrice` |
|
||||
| 车辆 | `/admin/vehicle/.../prices` | `costPrice` | `protocolPrice` |
|
||||
| 向导 | `/admin/staff/.../prices` | `costPrice` | `protocolPrice` |
|
||||
| 服务 | `/admin/service/.../prices` | `costPrice` | `protocolPrice` |
|
||||
|
||||
涉及的接口:① 批量设置价格日历(PUT,请求体 `costPrice`→`protocolPrice`)② 查询价格日历(GET,响应每日 VO 的 `costPrice`→`protocolPrice`)。
|
||||
|
||||
## 2. 展示文案变化
|
||||
|
||||
- **酒店/景点/活动/服务**:标签「成本价」→「协议价」(Swagger / 校验提示 / 错误码消息)
|
||||
- **车辆**:文案仍叫「日租价」(保留,仅 JSON 字段名变 `protocolPrice`)
|
||||
- **向导**:文案仍叫「日薪」(保留,仅 JSON 字段名变 `protocolPrice`)
|
||||
|
||||
## 3. 未变(不影响前端)
|
||||
|
||||
- 字段类型不变(仍 BigDecimal,元);接口路径不变;其余字段(status/stock/stockUsed/remark/date)不变
|
||||
- 挂牌价 `basePrice` 字段不动(本次不涉及)
|
||||
- 内部价格查询接口(产品报价用)字段名 `price` 不变,product 侧无影响
|
||||
|
||||
## 4. 请求/响应示例(以酒店房型为例)
|
||||
|
||||
设置(PUT `/admin/hotel/room-type/{roomTypeId}/prices`):
|
||||
|
||||
```json
|
||||
{
|
||||
"startDate": "2026-07-01",
|
||||
"endDate": "2026-07-31",
|
||||
"protocolPrice": 580.00,
|
||||
"status": "AVAILABLE",
|
||||
"stock": 10
|
||||
}
|
||||
```
|
||||
|
||||
查询(GET `/admin/hotel/room-type/{roomTypeId}/prices?year=2026&month=7`)响应每日项:
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
"protocolPrice": 580.00,
|
||||
"status": "AVAILABLE",
|
||||
"stock": 10,
|
||||
"stockUsed": 0,
|
||||
"remark": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3467(Closes #3463)
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:mmg —— 6 类资源价格日历页面 `costPrice` → `protocolPrice` 全量替换
|
||||
@@ -0,0 +1,80 @@
|
||||
# 【后端修复·管理后台】产品详情回显补 serviceStandardTemplateId(Step5 服务标准 tab 刷新后绑定不显)
|
||||
|
||||
> **PR**: #3484 | **关联工单**: 无(小 bug 修复)
|
||||
> **服务**: hl-product-service-v2(产品详情回显)| **更新时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「产品编辑 → 补充信息(Step5) → 服务标准」tab 的模板绑定回显
|
||||
> **前端动作**: **无需改代码**,重新测试即可(前端读取逻辑本就正确)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
现象是「选了服务标准模板,保存后刷新页面,下拉变空、预览显示『未绑定模板』,像是没保存上」。
|
||||
|
||||
**实测真相**:绑定**早就保存成功**了(`product.service_standard_template_id` 在库里有值,预览接口也能返回完整模板内容)。问题在**详情回显接口漏返该字段**——保存写进去了,但编辑页重新加载时读不到,所以下拉回显不出来。
|
||||
|
||||
本次为纯后端修复,**接口路径/结构未变,仅在 `supplement` 段新增一个字段**,前端无破坏性变更。
|
||||
|
||||
---
|
||||
|
||||
## 1. 根因
|
||||
|
||||
工单 #3147 引入 `serviceStandardTemplateId`(服务标准模板绑定)时,漏改了详情回显 VO:
|
||||
|
||||
- 保存接口 `PUT /admin/product/item/{id}/supplement` 已把它写入 **product 主表** ✅
|
||||
- 但详情接口 `GET /admin/product/item/{id}` 的响应 VO `ProductDetailRespVO.SupplementVO` **缺这个字段**(兄弟字段 `guaranteeTemplateId`/`faqTemplateId`/`safetyTemplateId` 都在,唯独漏了它)
|
||||
- 前端从 `data.supplement.serviceStandardTemplateId` 读取(读取位置正确),后端不返 → 永远 `undefined` → 下拉空、预览「未绑定」
|
||||
|
||||
## 2. 修复
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `ProductDetailRespVO.SupplementVO` | 新增字段 `Long serviceStandardTemplateId` |
|
||||
| `ProductQueryService.getProductDetail` | 从 product 主表显式回填进 `supplement` 段(缺 supplement 段时跳过,不回归) |
|
||||
|
||||
## 3. 接口响应变化(前端关注)
|
||||
|
||||
`GET /admin/product/item/{id}` 的 `data.supplement` 段**新增一个字段**:
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `serviceStandardTemplateId` | Long(雪花,JSON 透传字符串) | 当前绑定的服务标准模板 ID;未绑定为 `null` |
|
||||
|
||||
请求/响应示例(测试服已验证):
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/admin/product/item/2045345825172639746" \
|
||||
-H "Authorization: Bearer {admin_token}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"supplement": {
|
||||
"serviceStandardTemplateId": "2061281473171775490",
|
||||
"guaranteeTemplateId": "2044407984976486401",
|
||||
"...": "其余字段不变"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 前端动作
|
||||
|
||||
- **无需改代码**:前端 `productEdit.js` 已正确读取 `sp.serviceStandardTemplateId` 并回填到下拉,本次后端补返该字段后即自动生效。
|
||||
- **请重新测试**:产品编辑页 → 补充信息 → 服务标准 → 选模板 → 保存 → **刷新页面**,下拉应保持选中、模板内容预览应正常渲染。
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3484(已合并 dev-v3,已部署测试服并 API 验证回显成功)
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台)**:mmg
|
||||
|
||||
---
|
||||
|
||||
## 备注(后端内部,前端无需关注)
|
||||
|
||||
另发现一个潜在 bug:「取消绑定」(下拉清空 → null)因 MyBatis-Plus `updateById` 默认忽略 null 字段,清不掉旧值。改全局 `FieldStrategy` 对 partial-update 有风险,**未纳入本 PR**,后续以定向 `LambdaUpdateWrapper` 单独修。
|
||||
@@ -0,0 +1,94 @@
|
||||
# 【字段移除·管理后台+小程序】图标库 删除「尺寸(px)」字段(icon_item.size)
|
||||
|
||||
> **PR**: #3487 | **关联工单**: #3485
|
||||
> **服务**: hl-user-service | **更新时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「图标库」图标新增/编辑弹窗 + 小程序图标渲染——图标项移除「默认尺寸(px)」字段
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **删除原因**:图标渲染尺寸应由前端使用场景的 CSS / 容器决定,图标库内存一个「默认尺寸 px」属冗余,实际不被消费,前端反馈无用,删除。
|
||||
|
||||
2. **删的是哪个 `size`(务必区分,别误删别的)**:
|
||||
- ❌ **已删**:图标项的「默认尺寸 px」——数据库列 `icon_item.size`,对应 admin 编辑弹窗那个红框「尺寸(px)」输入框,以及各响应里的 `size` 字段。
|
||||
- ✅ **保留不动**:`POST /admin/icon/parse-svg` 和 `POST /admin/icon/parse-svg-text` 返回的 `size` 字段——那是**上传 SVG 的字节数(UTF-8)**,跟「尺寸 px」毫无关系,**继续返回,前端别误删**。
|
||||
|
||||
3. **破坏性**:DB 删列 + 出入参删字段。admin 保存接口若仍传 `size` 会被**静默忽略**(不报错);admin/mp 查询响应**不再返回** `size` 字段。前端需删除编辑弹窗的尺寸输入框、并移除对响应 `size` 的读取。
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|----------|------|
|
||||
| 1 | POST | `/admin/icon/item` | **入参删字段** | 请求体 `IconItemSaveReqVO` 移除 `size`,再传将被忽略 |
|
||||
| 2 | GET | `/admin/icon/item/page` | **出参删字段** | 响应 `records[].size` 不再返回 |
|
||||
| 3 | GET | `/admin/icon/item/{id}` | **出参删字段** | 响应 `data.size` 不再返回 |
|
||||
| 4 | GET | `/mp/icon/items` | **出参删字段** | 小程序图标列表 `data[].size` 不再返回 |
|
||||
|
||||
> `parse-svg` / `parse-svg-text` 的 `size`(SVG 字节数)**不在本次变更内,保持原样**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 受影响响应结构(前后对比)
|
||||
|
||||
**之前**(图标项含 size):
|
||||
```json
|
||||
{
|
||||
"iconCode": "tree-pine", "name": "松树",
|
||||
"color": "#000000", "size": 24,
|
||||
"svgContent": "<svg ...>...</svg>"
|
||||
}
|
||||
```
|
||||
|
||||
**现在**(已删 size):
|
||||
```json
|
||||
{
|
||||
"iconCode": "tree-pine", "name": "松树",
|
||||
"color": "#000000",
|
||||
"svgContent": "<svg ...>...</svg>"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 前端动作(mmg)
|
||||
|
||||
1. **管理后台「图标库」新增/编辑弹窗**:删除「尺寸(px)」输入框(截图红框那个),保存时不再提交 `size`。
|
||||
2. **管理后台图标列表/详情**:移除任何对响应 `size` 字段的读取/展示。
|
||||
3. **小程序图标渲染**:若此前从 `/mp/icon/items` 读 `size` 设定图标宽高,改为由小程序自身样式/容器决定尺寸(本就该如此)。
|
||||
4. **不受影响**:`parse-svg` / `parse-svg-text` 的 `size`(上传字节数)继续可用,无需改动。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证(测试服实测,已通过)
|
||||
|
||||
| 用例 | 结果 |
|
||||
|---|---|
|
||||
| `GET /admin/icon/item/page?page=1&pageSize=3` | code200,total=222,`records[]` 字段无 `size` |
|
||||
| `GET /admin/icon/item/{id}` | code200,`data` 无 `size` |
|
||||
| `GET /mp/icon/items?categoryCode=<某分类>` | code200,列表项无 `size` |
|
||||
| Flyway `V20260605_003` DROP COLUMN | 迁移成功,user-service 双实例正常启动并返真数据 |
|
||||
|
||||
- 单测 `IconItemServiceTest` 等 Icon* 77 项全绿
|
||||
- hl-user-service 双实例(8081 + 8181)均已部署新代码、health UP
|
||||
|
||||
---
|
||||
|
||||
## 5. 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:是(删列 + 删字段)。但 `size` 此前无消费方,删除对实际业务无影响。
|
||||
- **前端是否必须同步上线**:建议同步(删弹窗输入框 + 停读 `size`);前端不改也不会报错(多传被忽略、读 `size` 得 undefined)。
|
||||
- **影响已有数据**:列删除,原有 `size` 值丢弃(本就无用)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3487(Closes #3485)
|
||||
- **图标库模块首推 changelog**:`03_3390_图标库模块-新增接口-管理后台+小程序.md`
|
||||
- **后端负责人**:@wx
|
||||
- **前端对接(管理后台 + 小程序)**:mmg
|
||||
@@ -0,0 +1,45 @@
|
||||
# 【后端修复·管理后台】取消绑定服务标准模板清不掉旧值(updateById 忽略 null)
|
||||
|
||||
> **PR**: #3489 | **关联工单**: 无(PR #3484 deferred 项收口 / 工单 #3147 followup)
|
||||
> **服务**: hl-product-service-v2(Step5 补充信息保存)| **更新时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「产品编辑 → 补充信息(Step5) → 服务标准」tab 的「取消绑定」
|
||||
> **前端动作**: **无需改代码**,「取消绑定」现在能真正生效,可重新测试
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
接 PR #3484(修复绑定回显)的遗留项。现象:服务标准模板下拉**清空(取消绑定)后保存,旧绑定清不掉**,刷新仍显示原模板。
|
||||
|
||||
接口路径/结构未变,纯后端行为修复。
|
||||
|
||||
## 1. 根因
|
||||
|
||||
`PUT /admin/product/item/{id}/supplement` 用 MyBatis-Plus `updateById` 持久化 `serviceStandardTemplateId`,默认 `FieldStrategy.NOT_NULL` 会**忽略 null 字段**——取消绑定时该列被跳过,旧值残留。
|
||||
|
||||
## 2. 修复
|
||||
|
||||
仅在 `serviceStandardTemplateId == null`(取消绑定)时追加一次定向清列(绑定/改绑路径零改动):
|
||||
|
||||
- `ProductMapper.clearServiceStandardTemplateId(productId)` 用 `LambdaUpdateWrapper.set(column, null)` 显式置空(与既有 `clearApprovalFields` 同款)
|
||||
|
||||
## 3. 前端动作
|
||||
|
||||
- **无需改代码**:下拉本就 `clearable`,清空后保存即触发后端清列。
|
||||
- **请重新测试**:服务标准 tab → 清空模板下拉 → 保存 → 刷新,应显示「未绑定模板」。
|
||||
|
||||
## 4. 验证(测试服已实测)
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| 取消绑定(templateId=null)后查详情 | `supplement.serviceStandardTemplateId = null` ✅ |
|
||||
| 重新绑定后查详情 | 恢复为原模板ID ✅ |
|
||||
| 其余 Step5 字段(费用/人群优惠/保险等) | 完全不受影响 ✅ |
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
- **本次 PR**:#3489(已合并 dev-v3,已部署测试服并 API 实测取消绑定生效)
|
||||
- **前序 PR**:#3484(绑定回显修复)
|
||||
- **后端负责人**:@wx | **前端对接(管理后台)**:mmg
|
||||
@@ -0,0 +1,319 @@
|
||||
# 【删除接口·管理后台】订单详情大交通 Tab 删除冗余接口 `GET /v3/admin/order/{id}/transport-plans`
|
||||
|
||||
> **服务**: hl-order-service-v3 | **端**: 管理后台
|
||||
> **接口**: `GET /v3/admin/order/{id}/transport-plans`(已彻底删除,调用将 404)
|
||||
> **替代接口**: `GET /v3/admin/order/{id}/transport-plan/list`(保留,功能一致)
|
||||
> **Issue**: [#3535](https://git.1814.love:8443/wx/HL/issues/3535) | **PR**: [#3536](https://git.1814.love:8443/wx/HL/pulls/3536)
|
||||
> **日期**: 2026-06-05
|
||||
> **影响**: ⚠️ **破坏性**——旧接口已删除,前端必须切换 URL,其余代码零改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
订单详情大交通 Tab 此前存在两个返回同一份数据的接口:
|
||||
|
||||
| 接口 | 所属域 | 说明 |
|
||||
|------|--------|------|
|
||||
| `GET /v3/admin/order/{id}/transport-plans` | core 域 | 底层 delegate 到 traveler 域,纯代理,无自身逻辑 |
|
||||
| `GET /v3/admin/order/{id}/transport-plan/list` | traveler 域 | 数据权威来源,直接查询大交通批次表 |
|
||||
|
||||
2026-05-18 推送的 changelog(`18_#2521_transport-plan-list-path.md`)已将 `/transport-plan/list` 标记为正式接口;但 core 域的 `/transport-plans` 未同步下线,导致前端联调时两个路径并存、容易误用。
|
||||
|
||||
本次将 core 域冗余接口彻底删除,大交通数据源收敛到 traveler 域唯一路径。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|----------|------|
|
||||
| 1 | GET | `/v3/admin/order/{id}/transport-plans` | ❌ **删除接口** | 接口已彻底移除,调用返回 404 |
|
||||
| 2 | GET | `/v3/admin/order/{id}/transport-plan/list` | ✅ **保留(替代接口)** | 功能 100% 一致,返回结构完全相同 |
|
||||
|
||||
> **前端改动量**:只改 URL 一行,入参、出参、枚举、错误码均无变化。
|
||||
|
||||
---
|
||||
## 三、接口详情
|
||||
|
||||
### 替代接口:`GET /v3/admin/order/{id}/transport-plan/list`
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 认证 | 需要管理后台 JWT(Header: `Authorization: Bearer <token>`) |
|
||||
| 幂等性 | 查询接口,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `id` | Long | ✅ | 订单 ID(雪花 ID,需字符串传输防 JS 精度丢失) |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体(GET 接口)。
|
||||
|
||||
---
|
||||
|
||||
## 五、出参字段
|
||||
|
||||
**响应类型**:`Result<List<TransportPlanVO>>`
|
||||
|
||||
外层结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | `200` 表示成功 |
|
||||
| `data` | Array | 大交通批次列表;订单无批次时返回 `[]`,不会返回 `null` |
|
||||
| `msg` | String | 状态消息 |
|
||||
|
||||
**TransportPlanVO 字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 批次 ID(雪花 ID,字符串格式) |
|
||||
| `orderId` | String | 订单 ID(雪花 ID,字符串格式) |
|
||||
| `direction` | String | 方向,枚举值见六 |
|
||||
| `transportType` | String | 交通类型,枚举值见六 |
|
||||
| `transportNo` | String | 航班号 / 车次号;SELF_DRIVE 时为 null |
|
||||
| `carrier` | String | 航司 / 铁路公司;SELF_DRIVE 时为 null |
|
||||
| `departStation` | String | 出发站 |
|
||||
| `arriveStation` | String | 到达站 |
|
||||
| `departTime` | String | 出发时间(yyyy-MM-dd HH:mm:ss);SELF_DRIVE 时为 null |
|
||||
| `arriveTime` | String | 到达时间(yyyy-MM-dd HH:mm:ss);SELF_DRIVE 时为 null |
|
||||
| `selfDrivePeriod` | String | 仅 SELF_DRIVE 有值:时段枚举,见六;其他类型为 null |
|
||||
| `selfDriveEta` | String | 仅 SELF_DRIVE 有值:预计抵达时间(yyyy-MM-dd HH:mm:ss);其他类型为 null |
|
||||
| `travelers` | Array | 关联出行人列表,元素见下方;永远非空,至少含一条 |
|
||||
| `remark` | String | 备注;无备注时为 null |
|
||||
|
||||
**travelers 元素结构**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 出行人 ID(雪花 ID,字符串格式) |
|
||||
| `name` | String | 出行人姓名 |
|
||||
|
||||
**排序规则**:`direction ASC, departTime ASC, id ASC`(先按进/出方向,再按出发时间早晚,再按 ID 顺序)
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### direction(方向)
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|--------|------|
|
||||
| `ARRIVAL` | 抵达(进程) |
|
||||
| `DEPARTURE` | 离开(离程) |
|
||||
|
||||
### transportType(交通类型)
|
||||
|
||||
| 枚举值 | 含义 | 备注 |
|
||||
|--------|------|------|
|
||||
| `FLIGHT` | 飞机 | transportNo/carrier/departTime/arriveTime 有值;selfDrivePeriod/selfDriveEta 为 null |
|
||||
| `TRAIN` | 火车 | 同上 |
|
||||
| `SELF_DRIVE` | 自驾 | transportNo/carrier/departTime/arriveTime 为 null;selfDrivePeriod/selfDriveEta 有值 |
|
||||
|
||||
### selfDrivePeriod(自驾时段,仅 SELF_DRIVE 有值)
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|--------|------|
|
||||
| `MORNING` | 上午 |
|
||||
| `AFTERNOON` | 下午 |
|
||||
| `EVENING` | 晚上 |
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| `200` | 成功 | 正常返回(含空列表) |
|
||||
| `401` | 未授权 | JWT 缺失或过期,网关拦截 |
|
||||
| `581100` | 订单不存在 | Path id 对应订单不存在 |
|
||||
|
||||
> 订单存在但无大交通批次时,返回 `code: 200, data: []`,不返回 581100。
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功(机票 + 火车组合)
|
||||
|
||||
```
|
||||
GET /v3/admin/order/1234567890123456/transport-plan/list
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": "9876543210000001",
|
||||
"orderId": "1234567890123456",
|
||||
"direction": "ARRIVAL",
|
||||
"transportType": "FLIGHT",
|
||||
"transportNo": "CA8888",
|
||||
"carrier": "中国国际航空",
|
||||
"departStation": "北京首都国际机场",
|
||||
"arriveStation": "丽江三义国际机场",
|
||||
"departTime": "2026-08-01 07:30:00",
|
||||
"arriveTime": "2026-08-01 10:45:00",
|
||||
"selfDrivePeriod": null,
|
||||
"selfDriveEta": null,
|
||||
"travelers": [
|
||||
{"id": "1111111111111111", "name": "张三"},
|
||||
{"id": "2222222222222222", "name": "李四"}
|
||||
],
|
||||
"remark": null
|
||||
},
|
||||
{
|
||||
"id": "9876543210000002",
|
||||
"orderId": "1234567890123456",
|
||||
"direction": "DEPARTURE",
|
||||
"transportType": "TRAIN",
|
||||
"transportNo": "G1234",
|
||||
"carrier": "中国铁路",
|
||||
"departStation": "丽江站",
|
||||
"arriveStation": "北京南站",
|
||||
"departTime": "2026-08-08 14:00:00",
|
||||
"arriveTime": "2026-08-09 06:30:00",
|
||||
"selfDrivePeriod": null,
|
||||
"selfDriveEta": null,
|
||||
"travelers": [
|
||||
{"id": "1111111111111111", "name": "张三"},
|
||||
{"id": "2222222222222222", "name": "李四"}
|
||||
],
|
||||
"remark": "卧铺车厢 9 车"
|
||||
}
|
||||
],
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(自驾进程 + 订单无出程批次)
|
||||
|
||||
```
|
||||
GET /v3/admin/order/1234567890123457/transport-plan/list
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": "9876543210000010",
|
||||
"orderId": "1234567890123457",
|
||||
"direction": "ARRIVAL",
|
||||
"transportType": "SELF_DRIVE",
|
||||
"transportNo": null,
|
||||
"carrier": null,
|
||||
"departStation": "北京",
|
||||
"arriveStation": "丽江",
|
||||
"departTime": null,
|
||||
"arriveTime": null,
|
||||
"selfDrivePeriod": "AFTERNOON",
|
||||
"selfDriveEta": "2026-08-01 18:00:00",
|
||||
"travelers": [
|
||||
{"id": "3333333333333333", "name": "王五"}
|
||||
],
|
||||
"remark": null
|
||||
}
|
||||
],
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
> 订单无出程批次时,仅返回进程数据,data 数组长度为 1,不报错。
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
```
|
||||
GET /v3/admin/order/9999999999999999/transport-plan/list
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581100,
|
||||
"data": null,
|
||||
"msg": "订单不存在"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 管理后台订单详情「行程安排 - 接送站」Tab 加载大交通批次列表
|
||||
- 前端展示进程/离程分组时,按 `direction` 过滤本接口数据即可,无需分两次请求
|
||||
|
||||
**不适用场景**:
|
||||
- 小程序端(小程序有独立接口,不走 `/v3/admin/*`)
|
||||
- 批量查询多订单的大交通(本接口仅支持单订单)
|
||||
|
||||
**特殊边界**:
|
||||
- 订单存在但大交通批次为空时,返回 `data: []`(非 null),前端直接判断数组长度即可
|
||||
- `travelers` 字段永远非空(批次创建时必须关联至少一名出行人),前端不需要判空
|
||||
- SELF_DRIVE 类型时,`transportNo`/`carrier`/`departTime`/`arriveTime` 必为 null,前端渲染需按 `transportType` 分支处理
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比(接口级)
|
||||
|
||||
### 接口路径对比
|
||||
|
||||
| 维度 | 旧(已删除) | 新(保留) |
|
||||
|------|-------------|-----------|
|
||||
| 路径 | `GET /v3/admin/order/{id}/transport-plans` | `GET /v3/admin/order/{id}/transport-plan/list` |
|
||||
| 响应结构 | `Result<List<TransportPlanVO>>` | `Result<List<TransportPlanVO>>` |
|
||||
| 出参字段 | 与新接口完全一致 | 与旧接口完全一致 |
|
||||
| 入参 | Path `id`(Long) | Path `id`(Long) |
|
||||
| 存活状态 | ❌ 已删除(调用 404) | ✅ 正常可用 |
|
||||
|
||||
> 路径差异:复数 `transport-plans` vs 单数 `transport-plan/list`,返回内容 100% 相同。
|
||||
|
||||
### 行为对比
|
||||
|
||||
| 行为 | 旧接口 | 新接口 |
|
||||
|------|--------|--------|
|
||||
| 实际数据来源 | delegate 到 /transport-plan/list | 直接查询大交通批次表 |
|
||||
| 响应内容 | 与新接口一致 | 权威数据源 |
|
||||
| 可用性 | ❌ 已删除 | ✅ 正常 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
**是否破坏向后兼容**:是。旧路径 `GET /v3/admin/order/{id}/transport-plans` 已不存在,调用返回 404。
|
||||
|
||||
**前端是否必须同步改动**:是。前端若仍调用旧路径将收到 404,功能完全不可用。改动量极小:仅需将 URL 字符串中 `transport-plans` 改为 `transport-plan/list`,入参/出参/错误码处理代码均无需变更。
|
||||
|
||||
**影响范围**:仅管理后台订单详情大交通 Tab 的列表查询调用点。
|
||||
|
||||
**回滚方案**:如需回滚,在后端恢复 core 域的 `/transport-plans` 接口即可,前端无需改动。回滚不影响数据(数据始终在 traveler 域,两接口只是入口不同)。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
1. **URL 拼写区分**:`transport-plans`(复数,旧的,已删)vs `transport-plan/list`(单数+list,新的,保留)。容易看漏,建议全局搜索替换。
|
||||
2. **历史 changelog 矛盾已修正**:2026-05-18 的 `18_#2521_transport-plan-list-path.md` 曾将 `/transport-plan/list` 标记为正式接口,但 `/transport-plans` 当时未实际下线导致两者并存。本次 PR #3536 已彻底删除旧路径,以本文档为准。
|
||||
3. **不影响其他大交通接口**:新增(`/transport-plan/add`)、编辑(`/transport-plan/{planId}/edit`)、删除(`/transport-plan/{planId}/delete`)均不受影响,只有查询列表接口有此变更。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- **Issue**: [#3535](https://git.1814.love:8443/wx/HL/issues/3535)
|
||||
- **PR**: [#3536](https://git.1814.love:8443/wx/HL/pulls/3536)
|
||||
- **历史关联 changelog**: `changelogs-v2/2026-05/18_#2521_transport-plan-list-path.md`(大交通列表路径迁移,本次与其收尾呼应)
|
||||
- **后端负责人**: yst
|
||||
@@ -0,0 +1,100 @@
|
||||
# 【前端 BUG·管理后台】任务看板打开弹「获取看板详情/任务/成员失败」红 toast = 前端竞态重复发请求 + 取消被误报错
|
||||
|
||||
> **关联**: 无工单(前端 bug 走 changelog 通知 mmg)
|
||||
> **服务**: 前端 hl-ui(**后端零改动**)| **提报时间**: 2026-06-05
|
||||
>
|
||||
> **存放目录**: `changelogs-v2/2026-06/`
|
||||
> **影响范围**: 管理后台「任务看板」`/task/boards`(看板列表 / 看板详情视图)
|
||||
> **类型**: 前端竞态 bug(mmg 修),**后端实测全 200 健康无需改动**
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
打开任务看板时弹出红色错误 toast「**获取看板详情失败 / 获取看板任务失败 / 获取看板成员失败**」,**但看板数据其实正常加载出来了**(公司看板、各状态列、任务卡片都在)。
|
||||
|
||||
**根因 = 前端竞态,不是后端故障。** 看板组件挂载时对**同一个数据接口重复发请求**(`tasks` 发 3 次、`members` 发 2 次、`detail` 视时序也会重发),多余的在飞请求被前端 `AbortController` **主动取消**(浏览器 `net::ERR_ABORTED`)。前端 `.catch()` **没有把「请求被取消」和「请求真失败」区分开**,于是把取消也当成失败弹了红 toast。
|
||||
|
||||
后端侧:凡是被允许跑完的请求**全部返回 200**(看板列表/详情/任务/成员/状态 + token refresh,curl 80+ 次 + 真浏览器多轮复现,零服务端失败)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 实证(真浏览器 DevTools Network,刚登录 token 全新,硬刷新复现)
|
||||
|
||||
打开 `/task/boards`(公司看板 `2022226454376366081`)后的请求序列:
|
||||
|
||||
| # | 请求 | 结果 |
|
||||
|---|---|---|
|
||||
| 1 | `GET /admin/task/boards`(看板列表) | **200** ✅ |
|
||||
| 2 | `GET /admin/task/board/{id}`(看板详情,含 statuses) | **200** ✅ |
|
||||
| 3 | `GET /admin/task/board/{id}/tasks` | **FAILED `net::ERR_ABORTED`** ❌ |
|
||||
| 4 | `GET /admin/task/board/{id}/tasks` | **200** ✅(同一接口重发,成功)|
|
||||
| 5 | `GET /admin/task/board/{id}/members` | **200** ✅ |
|
||||
| 6 | `GET /admin/task/board/{id}/tasks` | **FAILED `net::ERR_ABORTED`** ❌ |
|
||||
| 7 | `GET /admin/task/board/{id}/members` | **200** ✅(同一接口重发)|
|
||||
|
||||
要点:
|
||||
- `tasks` 接口被触发 **3 次**(2 次被取消、1 次成功);`members` 触发 **2 次**;`detail` 偶发重发被取消时就弹「获取看板详情失败」。
|
||||
- `net::ERR_ABORTED` 是**客户端主动取消**(不是 4xx/5xx,服务端从没收到失败结果),典型来自 axios cancel / `AbortController.abort()` / 组件重渲染或重复触发同一 fetch。
|
||||
- 浏览器 console **0 个 error**(说明取消已被 catch 捕获),但 catch 里仍调用了错误 toast。
|
||||
|
||||
后端实测对照(均 200,有数据):
|
||||
|
||||
```
|
||||
GET /admin/task/boards -> 200, 看板列表
|
||||
GET /admin/task/board/2022226454376366081 -> 200, 详情含 4 个 statuses
|
||||
GET /admin/task/board/2022226454376366081/tasks -> 200, 任务分组
|
||||
GET /admin/task/board/2022226454376366081/members -> 200, []
|
||||
GET /admin/task/board/2022226454376366081/statuses-> 200, 4 条
|
||||
POST /admin/auth/refresh (真 refreshToken) -> 200(连打 30 次全 200)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 前端修法(mmg)
|
||||
|
||||
### 2.1 必修:catch 里放过「被取消」的请求,别弹错误 toast
|
||||
|
||||
错误处理里先判断是否为取消,是则**静默 return**,不弹 toast:
|
||||
|
||||
```js
|
||||
import axios from 'axios'
|
||||
// ...
|
||||
.catch((e) => {
|
||||
// 请求被取消(组件重渲染/重复触发抢占)是预期行为,不是错误
|
||||
if (axios.isCancel(e) || e?.code === 'ERR_CANCELED' || e?.name === 'CanceledError') return
|
||||
ElMessage.error('获取看板任务失败') // 只有真失败才提示
|
||||
})
|
||||
```
|
||||
|
||||
三处都要加:获取看板详情 / 获取看板任务 / 获取看板成员的 catch。
|
||||
|
||||
### 2.2 根治:去掉对同一接口的重复触发
|
||||
|
||||
`tasks` 发 3 次、`members` 发 2 次,说明同一 fetch 被多个来源同时触发(常见:`onMounted` 触发一次 + `watch(boardId)` 触发一次 + 默认「优先级/状态」筛选 radio 又触发一次)。建议:
|
||||
|
||||
- 数据加载**只保留单一触发源**(推荐由 `watch(boardId, { immediate: true })` 统一触发,去掉 `onMounted` 里的重复拉取);
|
||||
- 或对每类请求维护一个 `AbortController`,重发前先 `abort()` 旧的——**但这种「主动取消」必须配合 §2.1,把取消视为正常、不报错**。
|
||||
|
||||
### 2.3 注意(与本 bug 无关、但同页常见)
|
||||
|
||||
看板有实时通道(`/ws/task/info` + `/admin/task/board/ws-doc`)。若 WS 推送后又触发整页重新拉取,也会放大上面的「重复请求 + 取消」现象,一并按 §2.1/§2.2 处理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端状态(无需改动)
|
||||
|
||||
- 任务看板全部读接口(list / detail / tasks / members / statuses)实测 200,数据正确。
|
||||
- `POST /admin/auth/refresh`:网关已在 `JwtAuthFilter.SKIP_URLS` 白名单放行,带过期 access token + 真 refreshToken 实测 200,20 并发全 200。
|
||||
|
||||
### 3.1 附注:你截图里 `refresh` 的 502 是「另一桩、瞬时」
|
||||
|
||||
你之前截图 Network 里 `refresh` 显示 **502**,那是**独立的瞬时事件**:当时会话 access token 已过期 → 前端自动调 `/admin/auth/refresh` 续签 → 恰好撞上测试服 user-service(双实例 8081/8181)部署/重启抖动,网关 LB 轮到正在重启的实例返 502。**当前 30+ 次 refresh 全 200,复现不出,不是看板代码缺陷**,无需前端改动;若再遇到,刷新页面即恢复。
|
||||
|
||||
---
|
||||
|
||||
## 4. 关联
|
||||
|
||||
- 后端负责人:@wx(已实测确认后端健康,无改动)
|
||||
- 前端对接(本 bug 修复):**mmg**
|
||||
- 复现路径:登录管理后台 → 任务看板(`/task/boards`)→ 打开「公司看板」即弹失败 toast,但数据正常。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 【后端已上线·前端可加一列·管理后台】发送日志(通知日志):新增「信息内容」content 字段 + 覆盖更多发送通道
|
||||
|
||||
> **关联**: 通知中心 / 发送日志(接前篇 `05_通知发送日志富化-通道列+发送对象列-管理后台.md`)
|
||||
> **服务**: hl-user-service(后端已改 + 部署测试服 + boot 验证)| 前端 hl-ui
|
||||
> **提报时间**: 2026-06-05 | **后端**: @wx | **前端待接**: mmg
|
||||
> **位置**: 管理后台 → 通知中心 → 发送日志(推送日志)列表
|
||||
> **后端 PR**: #3529(已合并 dev-v3 + 部署测试服 + 双实例 boot 验证)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. `GET /admin/notification/logs` 每条记录新增 `content` 字段(**信息内容** = 实际发出的消息正文),前端可加一列直接展示。
|
||||
2. 发送日志此前「明显不全」:很多发送(短信验证码、任务企微通知等)走的是**绕开通知中心的旁路**,没进发送日志。本次已统一补记,前端**无需改动即会看到更多类型的发送记录**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 新增字段 `content`(信息内容)
|
||||
|
||||
| 字段 | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `content` | 实际发出的消息正文 | 站内信=「标题\n正文」;短信/小程序/公众号/企微=渲染后文本 |
|
||||
|
||||
前端可在发送日志列表加一列「信息内容」展示 `content`(可空——历史旧数据该列为 null,新产生的记录才有)。
|
||||
|
||||
**实测**(测试服,自定义推送站内信):
|
||||
|
||||
```
|
||||
GET /admin/notification/logs?channel=INAPP&pageNum=1&pageSize=1
|
||||
→ records[0].content = "发送日志content验证\n这是一条用于验证content列落库的测试站内信"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 发送日志现已覆盖更多发送通道(前端无需改动,记录变多)
|
||||
|
||||
后端把过去**绕开通知中心、不进发送日志**的旁路发送统一补记进 `notification_send_log`,前端日志页会**自动多出这些记录**:
|
||||
|
||||
| 现已记录的发送 | 通道(channel / channelLabel) | event_code |
|
||||
|----------------|------------------------------|------------|
|
||||
| C 端登录/注册短信验证码 | SMS / 短信 | `SMS_VERIFY_CODE` |
|
||||
| 管理端(定制师)登录验证码短信 | SMS / 短信 | `SMS_ADMIN_LOGIN` |
|
||||
| 客户投诉通知短信 | SMS / 短信 | `SMS_COMPLAINT` |
|
||||
| 任务指派/评论/订单待办 企微通知 | WEWORK / 企业微信 | `WECHAT_MQ` |
|
||||
| 通用企微 Feign 直发(审批/提醒等) | WEWORK / 企业微信 | `WECHAT_DIRECT` |
|
||||
| 外部联系人新增/流失 企微提醒 | WEWORK / 企业微信 | `WECHAT_EXTERNAL` |
|
||||
|
||||
> 这些 event_code 是新值,前端若对 event_code 做了**白名单/枚举映射**,记得放开(否则可能显示成未知事件);若直接展示 `channelLabel` + `content` 则无需改。
|
||||
|
||||
---
|
||||
|
||||
## 3. 暂未覆盖(后端 Phase 2,告知避免误判「还是不全」)
|
||||
|
||||
- **合同签署/催签短信**:由第三方电子合同平台(腾讯电子签 / 12301)下发,发送在 order 服务、日志表在 user 服务,需跨服务日志通道,单独一期补。
|
||||
- **车务司机驳回短信**:当前为 mock 未真发,接通真实短信网关时纳入。
|
||||
|
||||
这两类目前**不会**出现在发送日志里,属已知待补,非 bug。
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端 TODO(小)
|
||||
|
||||
- 发送日志列表加一列「信息内容」→ 取 `content`(可空)。
|
||||
- 若 event_code 有枚举校验,补上 `SMS_VERIFY_CODE / SMS_ADMIN_LOGIN / SMS_COMPLAINT / WECHAT_MQ / WECHAT_DIRECT / WECHAT_EXTERNAL`。
|
||||
|
||||
## 5. 关联
|
||||
|
||||
- 前篇(通道中文名 + 发送对象):`05_通知发送日志富化-通道列+发送对象列-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
@@ -0,0 +1,54 @@
|
||||
# 【后端已上线·前端补一枚 event_code·管理后台】合同签署短信现已进「发送日志」
|
||||
|
||||
> **关联**: 接 `05_发送日志补信息内容content列+覆盖更多发送通道-管理后台.md`(#3529 的 Phase 2 收尾)
|
||||
> **服务**: hl-user-service + hl-order-service-v2 + hl-order-service-v3(均已部署测试服 + 双实例 boot 验证)
|
||||
> **提报时间**: 2026-06-05 | **后端**: @wx | **前端待接**: mmg
|
||||
> **位置**: 管理后台 → 通知中心 → 发送日志
|
||||
> **后端 PR**: #3534(已合并 dev-v3 + 部署 user/order-v2/order-v3 + 6 实例 boot 验证)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
上一篇里标为「Phase 2 暂未覆盖」的**合同签署短信**,现在**已经会进发送日志**了。
|
||||
|
||||
合同签署短信由第三方电子合同平台(腾讯电子签 / 12301)下发,发送在 order 服务、日志表在 user 服务,过去看不到;本次加了跨服务补记,现已统一进 `notification_send_log`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前端唯一要做的事:补一枚 event_code
|
||||
|
||||
若发送日志页对 `event_code` 做了**白名单/枚举映射**,补上 **`CONTRACT_SIGN_SMS`**(否则该类记录的事件名可能显示成未知)。直接展示 `channelLabel` + `content` 则无需改。
|
||||
|
||||
合同签署短信在发送日志里的样子:
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| 通道 channelLabel | 短信 |
|
||||
| 信息内容 content | 合同签署短信(创建首发) / 合同签署短信(重发) / 合同签署短信(C端重发) |
|
||||
| 发送对象 | 签署人手机号 |
|
||||
| 关联业务 bizId / bizType | 合同编号 / CONTRACT |
|
||||
| event_code | `CONTRACT_SIGN_SMS` |
|
||||
|
||||
覆盖 4 个发送场景:合同**创建首发**、管理端**发送签署链接**、管理端**重发**、C 端**重发**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 发送日志 event_code 全集(截至本次)
|
||||
|
||||
前端 event_code 白名单建议放开这些(都是后端已落库的旁路发送类型):
|
||||
|
||||
`SMS_VERIFY_CODE`(C端验证码)、`SMS_ADMIN_LOGIN`(管理端登录码)、`SMS_COMPLAINT`(投诉短信)、`WECHAT_MQ`(任务/订单企微)、`WECHAT_DIRECT`(企微Feign直发)、`WECHAT_EXTERNAL`(外部联系人企微)、**`CONTRACT_SIGN_SMS`(合同签署短信,本次新增)**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 仍未覆盖(唯一)
|
||||
|
||||
- **车务司机驳回短信**:当前为 mock 未真发,接通真实短信网关时再纳入。
|
||||
|
||||
至此发送日志完整性除车务外全部收口(通知中心 6 通道 + 6 处旁路缺口 + 合同跨服务补记)。
|
||||
|
||||
## 4. 关联
|
||||
|
||||
- 上一篇:`05_发送日志补信息内容content列+覆盖更多发送通道-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,145 @@
|
||||
# 【后端已上线·前端待加两列·管理后台】通知发送日志:补「通道」中文名 +「发送对象」解析名(不再显示「未知」)
|
||||
|
||||
> **关联**: 通知中心 / 自定义推送(#3414 系列收尾)| **服务**: hl-user-service(后端已改并部署测试服)+ 前端 hl-ui
|
||||
> **提报时间**: 2026-06-05 | **后端**: @wx | **前端待接**: mmg
|
||||
> **位置**: 管理后台 → 通知中心 → 发送日志(推送日志)列表
|
||||
> **后端 PR**: #3526(富化字段)+ #3527(发送对象按通道判主体修正),均已合并 dev-v3 + 部署测试服 + API 实测通过
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
之前「推送日志」页**信息不全**:站内信 / 短信等都混在一起,看不出**是哪种通道**,「发送对象」列大量显示「**未知**」。
|
||||
|
||||
后端在**原接口 `GET /admin/notification/logs` 的每条记录里新增 3 个字段**(不改请求、不改分页结构,纯增量),前端只需把这两列展示出来即可:
|
||||
|
||||
| 新增字段 | 含义 | 示例值 |
|
||||
|----------|------|--------|
|
||||
| `channelLabel` | 通道中文名(直接展示,不用前端再映射) | 站内信 / 站内信(内部员工) / 短信 / 企业微信 / 小程序订阅 / 公众号 |
|
||||
| `recipientName` | **发送对象**展示名(已后端解析:员工名 / 客人昵称) | 游客9999、wx、admin |
|
||||
| `recipientType` | 发送对象类型(可做标签) | 员工 / 客人 / 其它 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 前端要做的事(两列)
|
||||
|
||||
在发送日志列表里加(或补全)两列:
|
||||
|
||||
| 列 | 取字段 | 说明 |
|
||||
|----|--------|------|
|
||||
| **通道** | `channelLabel` | 后端已返中文,直接展示。也可保留 `channel`(英文码)做筛选值 |
|
||||
| **发送对象** | `recipientName` | 后端已解析好名字;可在前面挂个 `recipientType`(员工/客人)小标签 |
|
||||
|
||||
> `recipientName` **后端保证非空**:解析不到名字时回退原始标识(手机号 / openid / id),绝不再显示「未知」。
|
||||
> 另有 `recipient` 字段是**原始标识**(手机号 / openid / 企微 id / adminId),如要展示副文本(如客人手机号)可用它。
|
||||
|
||||
---
|
||||
|
||||
## 2. 「发送对象」按通道判主体(#3527 修正点,重要)
|
||||
|
||||
一条日志可能**同时带 `adminId`(操作人)和 `userId`(客人)**——比如管理员手动给客人发站内信 / 短信。后端**按通道**决定「发送对象」到底是谁,前端无需关心,直接用 `recipientName` / `recipientType` 即可:
|
||||
|
||||
| 通道(channel) | 发送对象主体 | recipientType |
|
||||
|----------------|-------------|---------------|
|
||||
| `INAPP` 站内信、`SMS` 短信、`MINIAPP` 小程序订阅、`OA` 公众号 | **客人**(userId 解析昵称/真名) | 客人 |
|
||||
| `ADMIN_INAPP` 内部员工站内信、`WEWORK` 企业微信 | **内部员工**(adminId 解析用户名/企微名) | 员工 |
|
||||
|
||||
> 修正前的 bug:C 端通道(站内信/短信)误把**操作人**(如 wx)显示成发送对象;现已正确显示**客人**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 通道筛选下拉(channel 取值)
|
||||
|
||||
发送日志的「通道」筛选项 `channel` 取值与中文对应如下(`GET /admin/notification/logs?channel=` 传**英文码**):
|
||||
|
||||
| channel(传给后端) | channelLabel(展示) |
|
||||
|--------------------|----------------------|
|
||||
| `INAPP` | 站内信 |
|
||||
| `ADMIN_INAPP` | 站内信(内部员工) |
|
||||
| `SMS` | 短信 |
|
||||
| `WEWORK` | 企业微信 |
|
||||
| `MINIAPP` | 小程序订阅 |
|
||||
| `OA` | 公众号 |
|
||||
|
||||
⚠️ 企业微信筛选请传 **`WEWORK`**(日志通道列存的是 `WEWORK`,不是 `WECHAT_WORK`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口与实测响应
|
||||
|
||||
**请求**(不变,新增字段在响应里):
|
||||
|
||||
```
|
||||
GET /admin/notification/logs?channel=INAPP&pageNum=1&pageSize=20
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
**响应**(`data.records[]` 每条,节选三个新字段 + 关键原有字段):
|
||||
|
||||
```json
|
||||
// 客人站内信:同时有 adminId(操作人) 和 userId(客人),发送对象=客人
|
||||
{
|
||||
"id": "2062106713514754049",
|
||||
"eventCode": "CUSTOM_PUSH",
|
||||
"channel": "INAPP",
|
||||
"channelLabel": "站内信",
|
||||
"userId": "2059154700023836674",
|
||||
"adminId": "2021059720172838914",
|
||||
"recipient": "2059154700023836674",
|
||||
"recipientName": "游客9999",
|
||||
"recipientType": "客人",
|
||||
"status": 0,
|
||||
"createTime": "2026-06-03 17:38:47"
|
||||
}
|
||||
|
||||
// 短信:发送对象=客人,recipient 为手机号
|
||||
{
|
||||
"channel": "SMS",
|
||||
"channelLabel": "短信",
|
||||
"userId": "2025607151170445314",
|
||||
"adminId": "2033349912397299714",
|
||||
"recipient": "18547062756",
|
||||
"recipientName": "(该客人昵称/真名)",
|
||||
"recipientType": "客人",
|
||||
"status": 0
|
||||
}
|
||||
|
||||
// 内部员工站内信:发送对象=员工
|
||||
{
|
||||
"channel": "ADMIN_INAPP",
|
||||
"channelLabel": "站内信(内部员工)",
|
||||
"userId": null,
|
||||
"adminId": 1001,
|
||||
"recipientName": "admin",
|
||||
"recipientType": "员工",
|
||||
"status": 0
|
||||
}
|
||||
|
||||
// 企业微信:发送对象=员工
|
||||
{
|
||||
"channel": "WEWORK",
|
||||
"channelLabel": "企业微信",
|
||||
"userId": null,
|
||||
"adminId": "2021059720172838914",
|
||||
"recipientName": "wx",
|
||||
"recipientType": "员工",
|
||||
"status": 0
|
||||
}
|
||||
```
|
||||
|
||||
> `status`:0=成功,1=失败,2=已过滤,3=已跳过(关联字典 `notification_send_status`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 后端状态
|
||||
|
||||
- ✅ 6 个通道(站内信 / 内部员工站内信 / 短信 / 企业微信 / 小程序订阅 / 公众号)日志均有 `channelLabel`、`recipientName`、`recipientType`,测试服逐通道实测通过。
|
||||
- ✅ 「发送对象」按通道判主体(C 端通道显客人、内部通道显员工),已修正操作人误显问题(#3527)。
|
||||
- ✅ 解析采用批量查询(adminId→用户名、userId→昵称),无 N+1。
|
||||
- 前端只是**加两列 + 调筛选取值**,无逻辑负担。
|
||||
|
||||
## 6. 关联
|
||||
|
||||
- 自定义推送选人 / 企微通道:`05_3414_自定义推送选人下拉-客人名字undefined已修+企微通道需切员工数据源-管理后台.md`
|
||||
- 内部员工站内信主功能:`04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
@@ -0,0 +1,137 @@
|
||||
# 补全出行信息聚合接口(管理后台)
|
||||
|
||||
> 端类型:**管理后台**(v3)|变更类型:**新增接口**|服务:hl-order-service-v3
|
||||
> Issue #3539 | PR #3547 |日期:2026-06-06
|
||||
|
||||
## 接口地址
|
||||
|
||||
`PUT /v3/admin/order/{id}/traveler-info`
|
||||
|
||||
## 接口介绍
|
||||
|
||||
订单详情「补全出行信息」弹窗专用接口。一次提交完成:出行人**全量同步**(增/改/删)+ 订单紧急联系人 + 客户备注更新,单事务原子。
|
||||
|
||||
替代原先"前端编排 batch-edit + 多次 delete + PUT 订单"的多接口方案。弹窗打开时先调 `GET /v3/admin/order/{id}/traveler/list` 回填,编辑(含前端本地删除)后整体提交本接口。
|
||||
|
||||
## 本次修改点
|
||||
|
||||
新增接口。配套新错误码 581108。不影响现有 `traveler/batch-edit` `traveler/add` `traveler/{tid}/delete` `PUT /v3/admin/order/{id}` 及小程序端接口。
|
||||
|
||||
## 入参
|
||||
|
||||
路径参数:`id`(Long,订单 ID,必填)
|
||||
|
||||
请求体 `TravelerInfoSaveReqVO`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| travelers | List\<TravelerItem\> | 是 | 出行人**全集**(≤30),见下表。提交即该订单完整出行人列表 |
|
||||
| emergencyContactName | String | 否 | 紧急联系人姓名(null=保留原值) |
|
||||
| emergencyContactPhone | String | 否 | 紧急联系人电话(null=保留原值) |
|
||||
| customerRemark | String | 否 | 客户备注(null=保留原值) |
|
||||
|
||||
`TravelerItem`(travelers 数组元素):
|
||||
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| id | String | 否 | 出行人 ID。**有值=更新该人,null=新增**;DB 中存在但本次未提交的 → **删除** |
|
||||
| name | String | 条件 | 姓名(新增时 name/idType/idNo 至少 1 个非空;不可含数字等非法字符) |
|
||||
| gender | String | 否 | 性别 1=男 / 2=女 / 0=未知 |
|
||||
| birthday | LocalDate | 否 | 出生日期 yyyy-MM-dd |
|
||||
| idType | String | 否 | 证件类型 ID_CARD / PASSPORT / BIRTH_CERT |
|
||||
| idNo | String | 否 | 证件号(明文传,DB 加密;与 birthday 需一致) |
|
||||
| nationality | String | 否 | 国籍(默认中国) |
|
||||
| race | String | 否 | 民族(默认汉族) |
|
||||
| phone | String | 否 | 出行人手机(明文传,DB 加密) |
|
||||
| emergencyContact | String | 否 | 该出行人紧急联系人姓名 |
|
||||
| emergencyPhone | String | 否 | 该出行人紧急联系人电话 |
|
||||
| roomGroupNo | Integer | 否 | 同住分组号 |
|
||||
| travelerType | String | 条件 | ADULT / CHILD / YOUNG_CHILD / BABY(**新增时必填**,更新时 null 保留原值) |
|
||||
|
||||
**入参示例**
|
||||
```json
|
||||
{
|
||||
"travelers": [
|
||||
{"id": "2062826419733336066", "name": "张大人", "gender": "1", "birthday": "1990-03-07",
|
||||
"idType": "ID_CARD", "idNo": "110101199003078531", "phone": "13800138001", "travelerType": "ADULT"},
|
||||
{"id": null, "name": "张小孩", "gender": "2", "birthday": "2018-05-12",
|
||||
"idType": "ID_CARD", "idNo": "110101201805129524", "phone": "13800138002", "travelerType": "CHILD"}
|
||||
],
|
||||
"emergencyContactName": "紧急王",
|
||||
"emergencyContactPhone": "13900139000",
|
||||
"customerRemark": "客户希望安排素食"
|
||||
}
|
||||
```
|
||||
|
||||
## 出参
|
||||
|
||||
`Result<TravelerInfoSaveRespVO>`
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| createdCount | Integer | 本次新增出行人数 |
|
||||
| updatedCount | Integer | 本次更新出行人数 |
|
||||
| deletedCount | Integer | 本次删除出行人数 |
|
||||
| completedCount | Integer | 当前资料完整(COMPLETED)的出行人数 |
|
||||
| pendingCount | Integer | 当前资料待完善(PENDING)的出行人数 |
|
||||
| allCompleted | Boolean | 是否全部补全(人数=声明数 且 全部 COMPLETED) |
|
||||
|
||||
**出参示例**
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"createdCount": 1, "updatedCount": 1, "deletedCount": 0,
|
||||
"completedCount": 2, "pendingCount": 0, "allCompleted": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 状态规则(调用约束)
|
||||
|
||||
接口行为随订单状态变化,前端需按返回错误码处理:
|
||||
|
||||
| 订单状态 | 新增 | 删除/更新 | 副作用 |
|
||||
|---|---|---|---|
|
||||
| 待支付 | ❌ 581116 | ✅ | 无 |
|
||||
| 定制中(待补全/资源准备/待确认) | ✅ | ✅ | 无 |
|
||||
| 待出行 / 出行中 | ✅ | ✅ | 后端**自动**作废旧合同重签 + 退旧保单重投(前端无需额外调用) |
|
||||
| 已完成 / 已取消 | ❌ 581108 | ❌ 581108 | 整单拒绝编辑 |
|
||||
|
||||
- 通用:订单至少保留 1 位成人,删除最后 1 位成人返回 581107。
|
||||
- 紧急联系人/备注:传 null 保留原值;传值则更新。
|
||||
- 出行人全部补全后,订单流程自动从「待补全信息」推进到「资源准备」。
|
||||
|
||||
## 枚举
|
||||
|
||||
| 枚举 | 取值 | 含义 |
|
||||
|---|---|---|
|
||||
| travelerType | ADULT / CHILD / YOUNG_CHILD / BABY | 成人 / 儿童 / 小童 / 婴儿 |
|
||||
| idType | ID_CARD / PASSPORT / BIRTH_CERT | 身份证 / 护照 / 出生证 |
|
||||
| gender | 1 / 2 / 0 | 男 / 女 / 未知 |
|
||||
|
||||
## 错误码
|
||||
|
||||
| code | message | 触发 |
|
||||
|---|---|---|
|
||||
| 581102 | 订单不存在,无法编辑出行人 | 订单 ID 不存在 |
|
||||
| 581108 | 当前订单状态不允许编辑出行信息(已完成 / 已取消) | 订单已完成/已取消 |
|
||||
| 581116 | 当前订单状态禁止新增出行人 | 待支付订单提交了新增项 |
|
||||
| 581107 | 出行人是订单最后 1 位成人,禁止删除 | 删除后无任何成人 |
|
||||
| 581110 | 出行人 ID 不属于该订单 | 更新项 id 不属于该订单 |
|
||||
| 581111 | 证件号被已签合同冻结 | 已签合同订单尝试改证件号 |
|
||||
| 581118 | 新增出行人缺少必填字段(id=null 时 name/idType/idNo 必填,且 travelerType 必填) | 新增项缺必填 |
|
||||
| 581119 | 出行人证件号重复 | 同次提交出现重复证件号 |
|
||||
| 581104 | 同住分组号超出订单家庭数上限 | roomGroupNo 超过订单家庭数 |
|
||||
| 581112 / 581113 / 581114 | 证件号 / 手机 / 生日格式错误 | 字段格式校验不通过 |
|
||||
| 100702 | 姓名含非法字符 | 姓名含数字等非法字符 |
|
||||
| 400 | 出行人列表不能为空 | travelers 为空数组 |
|
||||
| 401 | 缺少有效的 Authorization 头 | 未携带 token |
|
||||
|
||||
> 幂等:同一订单 3 秒内重复提交会被拒绝(防重复点击/网络重试),前端避免短时间重复提交。
|
||||
|
||||
## 关联
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/3539
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3547
|
||||
- 后端负责人:腰苏图(订单 v3)
|
||||
@@ -0,0 +1,107 @@
|
||||
# 订单取消(出行前)新增 3 种退款模式(管理后台)
|
||||
|
||||
> 端类型:**管理后台**(v3)|变更类型:**修改接口**|服务:hl-order-service-v3
|
||||
> Epic #3540 | PR #3550 |日期:2026-06-06
|
||||
|
||||
## 接口地址
|
||||
|
||||
`POST /v3/admin/order/{id}/cancel/pre-trip`
|
||||
|
||||
## 接口介绍
|
||||
|
||||
定制师在后台对**出行前**订单(PENDING_PAY / CUSTOMIZING / PENDING_DEPARTURE)执行取消。取消后订单进入 `CANCELLED` 终态,已付金额按所选退款模式发起退款。
|
||||
|
||||
## 本次修改点
|
||||
|
||||
入参 `OrderCancelPreTripReqVO` **新增 2 个字段** `refundMode` + `refundAmount`,支持 3 种退款模式:
|
||||
|
||||
- `POLICY`(默认):按退款政策阶梯(距出发天数)计算退款金额。
|
||||
- `FULL_DEPOSIT`:订金全额退,**无视政策**。
|
||||
- `PARTIAL`:部分退,金额由 `refundAmount` 指定。
|
||||
|
||||
定制师操作即放行退款(无二级审批),退款单据自动进入执行。出参结构 `OrderCancelPreTripRespVO` 不变。新增错误码 581038 / 581039。
|
||||
|
||||
## 入参
|
||||
|
||||
路径参数:`id`(Long,订单 ID,必填)
|
||||
|
||||
请求体 `OrderCancelPreTripReqVO`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| cancelReason | String | 是 | 取消原因(前端可下拉 + 自由填写) |
|
||||
| cancelDetail | String | 否 | 详细说明 |
|
||||
| refundMode | String 枚举 | 否(默认 POLICY) | 退款模式:`POLICY` / `FULL_DEPOSIT` / `PARTIAL` |
|
||||
| refundAmount | BigDecimal | **PARTIAL 时必填** | 部分退金额,须 `0 < refundAmount ≤ 已付金额` |
|
||||
|
||||
**入参示例**
|
||||
|
||||
按政策退(默认):
|
||||
```json
|
||||
{ "cancelReason": "客户临时有事无法出行", "refundMode": "POLICY" }
|
||||
```
|
||||
订金全额退:
|
||||
```json
|
||||
{ "cancelReason": "我方资源无法满足", "refundMode": "FULL_DEPOSIT" }
|
||||
```
|
||||
部分退:
|
||||
```json
|
||||
{ "cancelReason": "协商部分退款", "refundMode": "PARTIAL", "refundAmount": 500.00 }
|
||||
```
|
||||
|
||||
## 出参
|
||||
|
||||
`Result<OrderCancelPreTripRespVO>`
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| refundApplicationId | Long | 退款申请 ID |
|
||||
| refundAmount | BigDecimal | 本次退款金额(按所选模式算定) |
|
||||
| pendingApprovalMsg | String | 提示文案 |
|
||||
| newStatus | String | 取消后订单状态,固定 `CANCELLED` |
|
||||
|
||||
**出参示例**
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"refundApplicationId": 2063151109765050369,
|
||||
"refundAmount": 500.00,
|
||||
"pendingApprovalMsg": "退款已发起",
|
||||
"newStatus": "CANCELLED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 枚举
|
||||
|
||||
| 枚举 | 取值 | 含义 |
|
||||
|---|---|---|
|
||||
| refundMode | POLICY | 按退款政策阶梯计算 |
|
||||
| refundMode | FULL_DEPOSIT | 订金全额退(无视政策) |
|
||||
| refundMode | PARTIAL | 部分退(用 refundAmount) |
|
||||
| newStatus | CANCELLED | 已取消(终态) |
|
||||
|
||||
## 错误码
|
||||
|
||||
| code | message | 触发 |
|
||||
|---|---|---|
|
||||
| 581017 | 当前订单状态不允许取消 | 非出行前状态(出行中 / 已完成 / 已取消)调用 |
|
||||
| 581038 | 退款模式非法,仅支持 POLICY/FULL_DEPOSIT/PARTIAL | refundMode 传了非法值 |
|
||||
| 581039 | 部分退金额非法:须 > 0 且 ≤ 已付金额 | PARTIAL 缺金额 / ≤0 / 超过已付金额 |
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| refundMode | 无此入参,固定按政策退 | 新增,可选 POLICY / FULL_DEPOSIT / PARTIAL |
|
||||
| refundAmount | 无此入参 | 新增,PARTIAL 模式必填 |
|
||||
| 出参 | 同 | 不变 |
|
||||
|
||||
> 兼容性:`refundMode` 不传等价于 `POLICY`,旧调用方零改动可继续工作。
|
||||
|
||||
## 关联
|
||||
|
||||
- Epic:https://git.1814.love:8443/wx/HL/issues/3540
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3550
|
||||
- 后端负责人:腰苏图(订单 v3)
|
||||
@@ -0,0 +1,243 @@
|
||||
# 【⚠️修改接口·管理后台】终止行程接口改造(资源级退款计算) (#3556)
|
||||
|
||||
> **PR**: #3557 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-06
|
||||
> ⚠️ **破坏性变更**:入参 / 出参结构均调整,前端必须同步改造。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
终止行程原先只让定制师手填一个返还金额,既不准确也没真正进入核单结算。现改为**资源级退款计算**:定制师在弹框里选择「停在第几天」并逐项标记资源是否已用,提交时后端按实际成交价重新核算退款额并写入核单返还。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 终止行程 | POST | `/v3/admin/order/{id}/terminate` | 修改接口 | 入参从「手填返还额」改为「资源已用判定 + 调整额」;出参补退款明细字段 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 终止行程
|
||||
|
||||
- **使用场景**:出行中订单提前终止时调用,配合「终止退款预览」接口(见同日新增接口 changelog)使用
|
||||
- **认证**:需要管理端 JWT(`Authorization` 头)
|
||||
- **幂等性**:是(同一订单二次调用因状态已变为「已完成」返回 581018)
|
||||
- **限流**:无
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | String | ✅ | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|------|------|------|------|----------|
|
||||
| cancelReason | String | ✅ | 终止原因(调整金额的说明也写在此处,不再单列字段) | 非空 |
|
||||
| endDayNumber | Integer | ✅ | 停在第几天 | 1 ≤ 值 ≤ 行程总天数 |
|
||||
| rooms | Array<{refId, used}> | ✅ | 住宿各行已用判定 | — |
|
||||
| tickets | Array<{refId, used}> | ✅ | 门票各行已用判定 | — |
|
||||
| vehicles | Array<{refId, dayNumber, used}> | ✅ | 用车各行已用判定(同车跨天靠 dayNumber 区分) | — |
|
||||
| adjustAmount | BigDecimal | ❌ | 人工调整额(正=多退,负=少退),默认 0 | — |
|
||||
|
||||
> `rooms[].refId` / `tickets[].refId` / `vehicles[].refId` 取自「终止退款预览」接口返回的 `refId`。
|
||||
> 请求**只需传**资源是否已用(`used`)+ 调整额,**不传任何金额单价**——退款额由后端按预览同源的成交价重新核算(防篡改)。保险不在入参,由后端自动按锁定不退处理。
|
||||
|
||||
### 4.2.1 rooms / tickets 行(ResourceUsedItem)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| refId | String | ✅ | 资源记录 ID |
|
||||
| used | Boolean | ✅ | 该项是否已使用(true=已用不退 / false=未用可退) |
|
||||
|
||||
### 4.2.2 vehicles 行(VehicleUsedItem)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| refId | String | ✅ | 用车记录 ID |
|
||||
| dayNumber | Integer | ✅ | 第几天 |
|
||||
| used | Boolean | ✅ | 当天该车是否已使用 |
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 响应字段(`data`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| terminateRefundId | String | 终止退款记录 ID |
|
||||
| baselineRefund | BigDecimal | 系统按已用资源算出的建议退款额 |
|
||||
| adjustAmount | BigDecimal | 人工调整额(回显入参) |
|
||||
| finalRefund | BigDecimal | 最终退款额(= baselineRefund + adjustAmount,最低 0) |
|
||||
| settlementRefundId | String | 核单返还记录 ID(已写入核单 Step5 返还,供财务复核展示) |
|
||||
| newStatus | String | 终止后订单状态(`COMPLETED`=已完成) |
|
||||
| newFlowStatus | String | 终止后流程状态(`PENDING_REVIEW`=待核单) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 newStatus(订单状态)
|
||||
|
||||
**所属字段**:`newStatus` | **类型**:`String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `COMPLETED` | 已完成 | 终止后订单进入完成态,转入核单流程 |
|
||||
|
||||
### 6.2 newFlowStatus(流程状态)
|
||||
|
||||
**所属字段**:`newFlowStatus` | **类型**:`String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING_REVIEW` | 待核单 | 终止后等待核单录入与结算复核 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非「出行中」时调用(含二次终止) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
```
|
||||
POST /v3/admin/order/60001234567890/terminate
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户中途高反送医终止;林芝段酒店已付全款不可退,调整 -300",
|
||||
"endDayNumber": 3,
|
||||
"rooms": [
|
||||
{"refId": "96011", "used": true},
|
||||
{"refId": "96014", "used": false}
|
||||
],
|
||||
"tickets": [
|
||||
{"refId": "97001", "used": true}
|
||||
],
|
||||
"vehicles": [
|
||||
{"refId": "98001", "dayNumber": 1, "used": true},
|
||||
{"refId": "98001", "dayNumber": 2, "used": true},
|
||||
{"refId": "98001", "dayNumber": 3, "used": true}
|
||||
],
|
||||
"adjustAmount": -300.00
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"success": true,
|
||||
"data": {
|
||||
"terminateRefundId": "9610000001",
|
||||
"baselineRefund": 2264.00,
|
||||
"adjustAmount": -300.00,
|
||||
"finalRefund": 1964.00,
|
||||
"settlementRefundId": "9620000001",
|
||||
"newStatus": "COMPLETED",
|
||||
"newFlowStatus": "PENDING_REVIEW"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**场景说明**:不填调整额(默认 0),退款额 = 基线额。
|
||||
|
||||
**请求(片段)**:
|
||||
```json
|
||||
{ "cancelReason": "客户主动终止", "endDayNumber": 5, "rooms": [], "tickets": [], "vehicles": [] }
|
||||
```
|
||||
|
||||
**响应(片段)**:
|
||||
```json
|
||||
{ "code": 200, "data": { "baselineRefund": 0.00, "adjustAmount": 0.00, "finalRefund": 0.00, "newStatus": "COMPLETED", "newFlowStatus": "PENDING_REVIEW" } }
|
||||
```
|
||||
|
||||
### 8.3 业务失败(异常)
|
||||
|
||||
**场景说明**:订单非出行中(如已确认待出发或已终止过)。
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用场景**:订单当前状态 = 出行中(TRAVELLING)
|
||||
- ❌ **不适用场景**:未出发订单请走「取消订单」流程;已终止/已完成订单二次调用返回 581018
|
||||
- ⚠️ **特殊边界**:
|
||||
- `finalRefund` 最低为 0,不会出现负数
|
||||
- 退款额最终进入核单返还,由财务复核环节确认放款
|
||||
- 保险恒不退,无需在入参体现
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
**入参**:
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| cancelReason | ✅ 终止原因 | ✅ 终止原因(+ 调整说明并入此处) |
|
||||
| returnAmount | ✅ 手填返还金额(必填) | ❌ **删除**(改由后端按资源核算) |
|
||||
| returnRemark | ✅ 返还备注(必填) | ❌ **删除**(说明并入 cancelReason) |
|
||||
| endDayNumber | — | ✅ **新增**,停在第几天 |
|
||||
| rooms / tickets / vehicles | — | ✅ **新增**,各资源行已用判定 |
|
||||
| adjustAmount | — | ✅ **新增**(可选),人工调整额 |
|
||||
|
||||
**出参**:
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| settlementRefundId | ✅(此前恒为 null) | ✅ 真实核单返还记录 ID |
|
||||
| returnAmount | ✅ 返还额(回显入参) | ❌ **删除** |
|
||||
| newStatus | ✅ | ✅(不变) |
|
||||
| terminateRefundId | — | ✅ **新增** |
|
||||
| baselineRefund / adjustAmount / finalRefund | — | ✅ **新增**,退款明细金额 |
|
||||
| newFlowStatus | — | ✅ **新增** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 退款额来源 | 定制师手填一个数 | 按已用资源 + 实际成交价由后端核算 |
|
||||
| 退款是否进核单 | 否(仅记录,未落账) | 是(写入核单返还,财务复核放款) |
|
||||
| 配合接口 | 无 | 需先调「终止退款预览」拿资源清单 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:是。入参删除 `returnAmount` / `returnRemark`,新增多个必填字段;出参删除 `returnAmount`。前端按旧契约调用会校验失败。
|
||||
- **前端是否必须同步上线**:是。终止行程页面需改造为「预览拉清单 → 选结束天 + 勾选已用 → 提交」流程。
|
||||
- **前端 workaround 清理点**:若此前有「手填返还额」输入框,应替换为资源勾选界面;旧的 returnAmount/returnRemark 入参与回显逻辑可删除。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #3557
|
||||
- **回滚后清理**:无需数据迁移(新功能,无历史数据依赖)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- ⚠️ 本接口为**破坏性改动**,前端务必在后端上线同期改造,否则终止行程功能不可用。
|
||||
- 必须配合同日「终止退款预览」新增接口使用:预览拿全部资源清单 → 前端本地按结束天标已用/可退 → 提交时只回传 `used` 判定与 `adjustAmount`。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#3556](https://git.1814.love:8443/wx/HL/issues/3556)
|
||||
- **PR**: [#3557](https://git.1814.love:8443/wx/HL/pulls/3557)
|
||||
- **Merge commit**: [773dcdacf](https://git.1814.love:8443/wx/HL/commit/773dcdacf)
|
||||
- **配套新增接口**: 终止退款预览(同日 changelog)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 终止行程弹框页面
|
||||
@@ -0,0 +1,159 @@
|
||||
# 【✨新增接口·管理后台】终止行程·退款预览 (#3556)
|
||||
|
||||
> **PR**: #3557 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-06
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
出行中订单因特殊原因需要提前终止行程时,需要给定制师一个「按已用资源算应退多少」的界面。本接口提供终止退款弹框所需的**全部资源清单 + 行程时间轴信息**,定制师选择「停在第几天」后,由前端本地标记每项资源已用/未用并实时算出退款额,最终在终止提交接口落账。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 终止退款预览 | POST | `/v3/admin/order/{id}/terminate/refund-preview` | 新增接口 | 返回终止退款弹框所需资源清单 + 行程信息 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 终止退款预览
|
||||
|
||||
- **使用场景**:出行中订单点击「终止行程」打开退款弹框时调用一次,拿到整趟全部资源清单
|
||||
- **认证**:需要管理端 JWT(`Authorization` 头)
|
||||
- **幂等性**:是(只读查询,不落库)
|
||||
- **限流**:无
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | String | ✅ | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无请求体。前端**一次性**拿到整趟全部资源,「停在第几天」的选择与已用/未用标记、退款金额计算全部在前端本地完成,**不需要每选一天再调本接口**。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 响应字段(`data`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| paidAmount | BigDecimal | 已付金额(公司账上已收,前端算退款基线用) |
|
||||
| tripDays | Integer | 行程总天数(画时间轴用,如 7 天 6 晚 = 7) |
|
||||
| departDate | String(date) | 出发日期(`yyyy-MM-dd`,第 N 天日期 = 出发日 + N−1) |
|
||||
| tripCurrentDay | Integer | 当前已出行到第几天(时间轴默认选中点) |
|
||||
| rooms | Array<ResourceItem> | 住宿资源清单(每晚每组房一行) |
|
||||
| tickets | Array<ResourceItem> | 门票资源清单(每景点每天一行) |
|
||||
| vehicles | Array<ResourceItem> | 用车资源清单(**整车已按天展开,每天一行**) |
|
||||
| insurance | Array<ResourceItem> | 保险清单(整单一行,锁定不退) |
|
||||
|
||||
### 5.2 ResourceItem 字段(rooms / tickets / vehicles / insurance 各行通用)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| refId | String | 资源记录 ID(提交终止时按此回传已用判定;用车多天共用同一 refId,靠 dayNumber 区分;保险可能为 null) |
|
||||
| name | String | 资源名称(酒店名 / 景点名 / 车型 / 保险产品名) |
|
||||
| dayNumber | Integer | 对应行程第几天(保险为 null) |
|
||||
| dealPrice | BigDecimal | 成交单价(住宿=每间每晚价 / 门票=每张价 / 用车=每天每辆日租 / 保险=保费) |
|
||||
| quantity | Integer | 数量(住宿=房间数 / 门票=张数 / 用车=车辆数 / 保险=1) |
|
||||
| totalAmount | BigDecimal | 本行小计(= dealPrice × quantity) |
|
||||
| locked | Boolean | 是否锁定不退(保险恒 true,其余 false) |
|
||||
| lockedReason | String | 锁定原因(如「保险已生效不退」;未锁定为 null) |
|
||||
|
||||
> 响应**不含**「是否已用」标记与「退款合计」——这些由前端按所选结束天(`dayNumber ≤ 结束天` 默认已用,之后默认可退)实时计算。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本接口无枚举字段。资源分类通过 `rooms` / `tickets` / `vehicles` / `insurance` 四个数组区分,无独立 `resourceType` 字段。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非「出行中」时调用本接口 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
```
|
||||
POST /v3/admin/order/60001234567890/terminate/refund-preview
|
||||
Authorization: Bearer {token}
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"success": true,
|
||||
"data": {
|
||||
"paidAmount": 6000.00,
|
||||
"tripDays": 7,
|
||||
"departDate": "2026-05-12",
|
||||
"tripCurrentDay": 3,
|
||||
"rooms": [
|
||||
{"refId": "96011", "name": "拉萨瑞吉度假酒店·大床房", "dayNumber": 1, "dealPrice": 1280.00, "quantity": 1, "totalAmount": 1280.00, "locked": false, "lockedReason": null},
|
||||
{"refId": "96014", "name": "拉萨瑞吉度假酒店·大床房", "dayNumber": 4, "dealPrice": 1280.00, "quantity": 1, "totalAmount": 1280.00, "locked": false, "lockedReason": null}
|
||||
],
|
||||
"tickets": [
|
||||
{"refId": "97001", "name": "布达拉宫门票", "dayNumber": 2, "dealPrice": 200.00, "quantity": 2, "totalAmount": 400.00, "locked": false, "lockedReason": null}
|
||||
],
|
||||
"vehicles": [
|
||||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 1, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null},
|
||||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 2, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null},
|
||||
{"refId": "98001", "name": "丰田赛那 商务车", "dayNumber": 3, "dealPrice": 600.00, "quantity": 1, "totalAmount": 600.00, "locked": false, "lockedReason": null}
|
||||
],
|
||||
"insurance": [
|
||||
{"refId": null, "name": "安联境内旅行险·尊享版", "dayNumber": null, "dealPrice": 256.00, "quantity": 1, "totalAmount": 256.00, "locked": true, "lockedReason": "保险已生效不退"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**场景说明**:某天住两个酒店(同一 dayNumber 出现多行)。
|
||||
|
||||
**响应(rooms 片段)**:
|
||||
```json
|
||||
"rooms": [
|
||||
{"refId": "96021", "name": "林芝希尔顿·双床房", "dayNumber": 2, "dealPrice": 980.00, "quantity": 1, "totalAmount": 980.00, "locked": false, "lockedReason": null},
|
||||
{"refId": "96022", "name": "林芝工布庄园·大床房", "dayNumber": 2, "dealPrice": 880.00, "quantity": 1, "totalAmount": 880.00, "locked": false, "lockedReason": null}
|
||||
]
|
||||
```
|
||||
|
||||
### 8.3 业务失败(异常)
|
||||
|
||||
**场景说明**:订单非出行中(如已确认待出发)时调用。
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用场景**:订单当前状态 = 出行中(TRAVELLING)
|
||||
- ❌ **不适用场景**:未出发(待支付 / 待完善 / 已确认待出发)→ 返回 581018;这些状态请走「取消订单」流程,不走终止退款
|
||||
- ⚠️ **特殊边界**:
|
||||
- 保险行恒 `locked=true`,不参与退款
|
||||
- 同一天可有多行(多酒店 / 多门票 / 多车),按 refId 区分
|
||||
- 用车一段车按行程天数展开为多行,多行共用同一 refId
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#3556](https://git.1814.love:8443/wx/HL/issues/3556)
|
||||
- **PR**: [#3557](https://git.1814.love:8443/wx/HL/pulls/3557)
|
||||
- **Merge commit**: [773dcdacf](https://git.1814.love:8443/wx/HL/commit/773dcdacf)
|
||||
- **关联 PR(预览状态校验加固)**: [#3559](https://git.1814.love:8443/wx/HL/pulls/3559)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 终止行程弹框页面
|
||||
@@ -0,0 +1,51 @@
|
||||
# 【产品决策·前端合并·管理后台】「通知日志」与「发送日志」合并为一个页面、一个接口
|
||||
|
||||
> **决策**: wx 2026-06-06 —— 通知日志就是发送日志,没区别,不要两个页面、不要两个接口。
|
||||
> **服务**: hl-user-service(后端无需改,统一接口已就绪)| 前端 hl-ui
|
||||
> **提报时间**: 2026-06-06 | **后端**: @wx | **前端待接**: mmg
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
目前后台有**两个日志页 + 两个接口**,要合并成**一个**:
|
||||
|
||||
| 现状 | 页面 | 接口 | 数据表 |
|
||||
|------|------|------|--------|
|
||||
| 保留 ✅ | 发送日志 | `GET /admin/notification/logs` | `notification_send_log`(单一真相源) |
|
||||
| 下线 ❌ | 通知日志 | `GET /admin/monitor/notification-logs` | `notification_log`(旧,monitor) |
|
||||
|
||||
**前端只保留一个日志页,统一用 `GET /admin/notification/logs`**;下线「通知日志」那个页(`/admin/monitor/notification-logs`)。
|
||||
|
||||
## 1. 为什么能合:发送日志已是超集
|
||||
|
||||
旧「通知日志」里能看到的东西(验证码短信、投诉短信、任务/外部联系人企微通知、合同签署短信……)后端已经全部补进了 `notification_send_log`(见 #3526 / #3527 / #3529 / #3534)。所以**统一走发送日志接口,看到的只多不少**。
|
||||
|
||||
## 2. 筛选/字段映射(旧 → 新)
|
||||
|
||||
| 旧「通知日志」 | 新「发送日志」`/admin/notification/logs` |
|
||||
|----------------|------------------------------------------|
|
||||
| 分页 `page` / `pageSize` | `pageNum` / `pageSize` |
|
||||
| 通知类型 `notificationType` | 事件编码 `eventCode`(如 SMS_VERIFY_CODE / WECHAT_MQ / CONTRACT_SIGN_SMS) |
|
||||
| 发送状态 `sendStatus` | `status`(0成功/1失败/2跳过/3已禁用) |
|
||||
| 用户名 `userName`(按名搜) | `userId`(按 ID 搜)+ 列表已回填 `recipientName`(发送对象名)展示 |
|
||||
| 开始/结束时间 | `startTime` / `endTime` |
|
||||
| 通道筛选(旧页没有) | `channel`(INAPP/ADMIN_INAPP/SMS/WEWORK/MINIAPP/OA) |
|
||||
| **服务 `serviceName`** | ✅ **已补**(PR #3554,已部署):`serviceName`(来源服务,如 hl-user-service / hl-order-service-v3),旧迁移前的历史行为 null |
|
||||
|
||||
列展示用发送日志这套:**通道(channelLabel) / 发送对象(recipientName+recipientType) / 信息内容(content) / 状态 / 失败原因 / 事件(eventCode) / 服务(serviceName) / 时间**。详情无需单独接口——列表行已含全部字段(content/recipient/paramsJson 等)。
|
||||
|
||||
## 3. 后端说明(不用动,给你交底)
|
||||
|
||||
- `notification_send_log` + `/admin/notification/logs` 就是统一的那一个,**后端不改**。
|
||||
- `notification_log`(旧表)**不删**:它还在喂**定制师看板的客户增长统计**(新客户添加/客户流失 → `DesignerDashboardService` / `/internal/monitor/notification-log/customer-stats`)。只是「日志列表页」不再读它。
|
||||
- `GET /admin/monitor/notification-logs` 这个旧列表接口,前端切走后即闲置;先**别急着调它**,后端晚点确认无其它调用方再清。
|
||||
|
||||
## 4. 一处差异(知会,避免误判)
|
||||
|
||||
旧页会显示「被部门白名单过滤掉、根本没发出去」的外部联系人事件(失败原因「不在允许部门范围: 33」那种)。新「发送日志」只记**真实发送**,默认不收录这类**没真发**的过滤记录。如果你们确实要在统一页里也看到这类被过滤事件,后端可以补记成「状态=跳过」,告诉我即可(默认不记,避免噪音)。
|
||||
|
||||
## 5. 关联
|
||||
|
||||
- 发送日志富化系列:`05_通知发送日志富化-通道列+发送对象列-管理后台.md`、`05_发送日志补信息内容content列+覆盖更多发送通道-管理后台.md`、`05_合同签署短信进发送日志-CONTRACT_SIGN_SMS-管理后台.md`
|
||||
- 后端:@wx | 前端:**mmg**
|
||||
@@ -0,0 +1,167 @@
|
||||
# 团期级 staff 两级配置(团期共享 + 订单专属)
|
||||
|
||||
> 端类型:**管理后台**(v3)|变更类型:**新增接口 + 修改接口**|服务:hl-order-service-v3
|
||||
> Issue #3560 | PR #3567 |日期:2026-06-07
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
团期产品(小蒙马等)同一团期(groupBatchId)下多个订单**共享同一批出团团队**(领队/摄影/司导)。原先 staff 只能逐订单配置(`order_staff_assignment`),同团期 N 个订单要重复配 N 次。
|
||||
|
||||
本次新增**团期级**配置:按团期配一次,后端自动扇出到团内所有订单;同时保留**订单级专属**配置,两级叠加。前端形成两个配置入口:
|
||||
- 独立的「团期 staff 配置页」(按 groupBatchId 配,本次新增接口)
|
||||
- 订单详情「staff 配置」(订单专属,沿用原接口,出参新增 source 字段区分来源)
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 接口 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET /v3/admin/group-batch/{groupBatchId}/staff` | 新增 | 查团期 staff 配置列表 |
|
||||
| `PUT /v3/admin/group-batch/{groupBatchId}/staff` | 新增 | 全量保存团期配置 + 扇出团内订单 |
|
||||
| `GET /v3/admin/order/{id}/staff` | 修改 | 出参新增 `source` 字段(GROUP_BATCH/ORDER) |
|
||||
| `DELETE /v3/admin/order/{id}/staff/{staffAssignmentId}` | 行为变更 | 团期来源行(source=GROUP_BATCH)禁止在订单侧删除(582109) |
|
||||
| `PUT /v3/admin/order/{id}/staff/{staffAssignmentId}` | 行为变更 | 团期来源行禁止在订单侧编辑(582109) |
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 接口 A:查询团期 staff 配置
|
||||
`GET /v3/admin/group-batch/{groupBatchId}/staff`
|
||||
打开「团期 staff 配置页」时调用,回填当前团期已配的共享 staff。
|
||||
|
||||
### 接口 B:保存团期 staff 配置(全量 + 扇出)
|
||||
`PUT /v3/admin/group-batch/{groupBatchId}/staff`
|
||||
**全量覆盖语义**:传入列表即为最终状态,后端软删旧配置后插入新配置。保存成功后**异步**扇出到团内所有活跃订单的 `order_staff_assignment`(source=GROUP_BATCH),订单专属行(source=ORDER)不受影响。
|
||||
|
||||
### 接口 C:订单 staff 列表(出参加字段)
|
||||
`GET /v3/admin/order/{id}/staff` 出参每项新增 `source`,前端据此区分「团期共享」(置灰、不可在订单侧增删改)与「订单专属」(可增删改)。
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口 A(GET)
|
||||
| 参数 | 位置 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|---|
|
||||
| groupBatchId | path | Long | 是 | 团期批次 ID |
|
||||
|
||||
### 接口 B(PUT)
|
||||
路径参数:`groupBatchId`(Long,必填)
|
||||
|
||||
请求体 `BatchStaffConfigReqVO`:
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| staffList | List\<Item\> | 是 | staff 配置**全集**(全量覆盖,传空数组=清空团期配置) |
|
||||
|
||||
`Item`:
|
||||
| 字段 | 类型 | 必填 | 含义 |
|
||||
|---|---|---|---|
|
||||
| staffId | Long | 是 | 用户域员工 ID |
|
||||
| staffRole | String | 是 | 员工角色(见枚举) |
|
||||
| sortOrder | Integer | 否 | 展示排序(默认 0) |
|
||||
| remark | String | 否 | 备注(≤500) |
|
||||
|
||||
> 注:`staffName` / `staffPhone` / `avatarUrl` 由后端 Feign 反查冻结快照,**前端不传**。
|
||||
|
||||
**入参示例**
|
||||
```json
|
||||
{
|
||||
"staffList": [
|
||||
{"staffId": 40001, "staffRole": "LEADER", "sortOrder": 0, "remark": "首席领队"},
|
||||
{"staffId": 40002, "staffRole": "PHOTOGRAPHER", "sortOrder": 1, "remark": "跟拍摄影"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口 A:`Result<List<BatchStaffItemVO>>`
|
||||
### 接口 B:`Result<BatchStaffConfigRespVO>`
|
||||
|
||||
`BatchStaffConfigRespVO`:
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| staffList | List\<BatchStaffItemVO\> | 保存后当前团期配置快照 |
|
||||
| affectedOrderCount | int | 扇出影响的活跃订单数(已触发异步写入) |
|
||||
|
||||
`BatchStaffItemVO`:
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| id | Long | 记录 ID(batch_staff_id) |
|
||||
| staffId | Long | 用户域员工 ID |
|
||||
| staffRole | String | 员工角色 |
|
||||
| staffName | String | 员工姓名(快照) |
|
||||
| staffPhone | String | 员工手机(脱敏前 3 后 4) |
|
||||
| avatarUrl | String | 头像 URL(快照) |
|
||||
| sortOrder | Integer | 展示排序 |
|
||||
| remark | String | 备注 |
|
||||
|
||||
### 接口 C:`GET /order/{id}/staff` 出参 `StaffAssignmentVO` 新增字段
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| source | String | **新增**。GROUP_BATCH=团期共享(不可在订单侧改) / ORDER=订单专属 |
|
||||
|
||||
**接口 B 出参示例**
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"staffList": [
|
||||
{"id": 770001, "staffId": 40001, "staffRole": "LEADER", "staffName": "刘领队",
|
||||
"staffPhone": "138****6677", "avatarUrl": "https://oss/40001.jpg", "sortOrder": 0, "remark": "首席领队"}
|
||||
],
|
||||
"affectedOrderCount": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
| 枚举 | 取值 | 含义 |
|
||||
|---|---|---|
|
||||
| staffRole | LEADER / DRIVER / PHOTOGRAPHER / OTHER | 领队 / 司机 / 摄影师 / 其他 |
|
||||
| source | GROUP_BATCH / ORDER | 团期共享 / 订单专属 |
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| code | message | 触发 |
|
||||
|---|---|---|
|
||||
| 582109 | 团期共享 staff 不可在订单侧增删改,请到团期配置页操作 | 对 source=GROUP_BATCH 的行调订单侧 DELETE/PUT staff |
|
||||
| 400 | 参数校验失败 | staffId/staffRole 缺失等 |
|
||||
| 401 | 缺少有效的 Authorization 头 | 未携带 token |
|
||||
|
||||
## ⑧ 示例(典型 / 边界 / 异常)
|
||||
|
||||
- **典型**:团期配「领队+摄影」→ 保存返回 affectedOrderCount=3(团内 3 个活跃订单已扇出)。
|
||||
- **边界**:staffList 传 `[]` → 清空该团期配置,并扇出删除团内订单的所有 GROUP_BATCH 行(订单专属行保留)。
|
||||
- **异常**:在订单详情对一个「团期共享」的领队点删除 → 返回 582109,前端应禁用团期行的删除/编辑按钮。
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- **全量覆盖**:保存即最终状态,未在列表中的旧团期 staff 会被软删并从团内订单移除。
|
||||
- **扇出范围**:仅团内**活跃**订单(已完成 / 已取消订单不被扇出刷新,保留历史团队记录)。
|
||||
- **两级叠加**:订单详情 staff = 团期共享(GROUP_BATCH) + 订单专属(ORDER),同一员工去重。
|
||||
- **下单自动写入**:团期已配 staff 后新下单的订单,创建时自动带上团期 staff。
|
||||
- **异步最终一致**:扇出为异步,保存接口返回后团内订单的更新可能有秒级延迟(affectedOrderCount 表示已触发的订单数)。
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 团期共享配置 | 无,同团期逐单重复配 | 团期配一次自动扇出团内 |
|
||||
| order/{id}/staff 出参 | 无 source | 每项带 source(GROUP_BATCH/ORDER) |
|
||||
| 订单侧删/改团期行 | 可删(会造成与团期不一致) | 禁止(582109),引导去团期页 |
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **存量兼容**:`order_staff_assignment` 加 source 列默认 ORDER,存量行全部视为订单专属,原订单 staff 展示不变。
|
||||
- **前端必改**:订单 staff 列表需按 source 渲染(团期行置灰禁操作);新增团期配置页对接 A/B 接口。
|
||||
- **回滚**:接口为新增 + 加字段,回滚后前端忽略 source 字段即可,无破坏性。
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 团期配置页的「团期信息」(团期号/出发日期/产品)由**产品服务 admin 接口**提供,本接口只管 staff。
|
||||
- 保存后 affectedOrderCount 是「已触发异步写入」数,非「已写完」数;前端如需精确状态可重查订单。
|
||||
- staffPhone 出参已脱敏(前 3 后 4),列表展示直接用即可。
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/3560
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3567
|
||||
- 后端负责人:腰苏图(订单 v3)
|
||||
@@ -0,0 +1,330 @@
|
||||
# 订单详情「记录 Tab」时间线接口 13 字段重构 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:⚠️ 破坏性变更(字段删除 / 改名 / 新增)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-order-service-v3
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单详情页「记录」Tab 懒加载接口,返回订单状态流转与操作时间线节点列表。
|
||||
|
||||
本次对返回值 `LogTimelineVO` 进行全面重构:旧版 6 字段语义模糊、状态值为英文裸枚举、操作人字段名拼写不一;新版扩展为 13 字段,状态标签全部中文化,引入 `changeType` 区分两类节点样式,新增 `id` 字段满足 list key 需要。
|
||||
|
||||
**前端必须同步迁移**。旧字段(`action`、`operator`、`fromStatus`、`toStatus`)已从响应体移除或改名,直接使用旧字段的代码将取不到值。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更类型 | 旧字段 | 新字段 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | ⚠️ 删除 | `action` | — | 已拆分为 `title`(事件名)+ `content`(内容描述),原字段不再返回 |
|
||||
| 2 | ⚠️ 改名 | `operator` | `operatorName` | 操作人姓名,仅字段名变更,值语义不变 |
|
||||
| 3 | ⚠️ 改名+中文化 | `fromStatus` | `fromStatusLabel` | 状态来源,英文枚举("PENDING_PAY")→ 中文标签("待支付") |
|
||||
| 4 | ⚠️ 改名+中文化 | `toStatus` | `toStatusLabel` | 状态目标,英文枚举("PROCESSING")→ 中文标签("定制中") |
|
||||
| 5 | 保留 | `occurredAt` | `occurredAt` | 事件发生时间,无变化 |
|
||||
| 6 | 保留 | `amount` | `amount` | 金额,无变化 |
|
||||
| 7 | ✨ 新增 | — | `id` | 节点唯一 ID(String 雪花),list key 用此字段 |
|
||||
| 8 | ✨ 新增 | — | `title` | 中文事件名(原 action 前半段,如支付订金) |
|
||||
| 9 | ✨ 新增 | — | `content` | 事件内容文本("¥500" / "拉萨 瑞吉酒店" / "2 位出行人"),可空 |
|
||||
| 10 | ✨ 新增 | — | `amountKind` | 金额类型枚举(DEPOSIT/FULL/REFUND/RETURN/SETTLEMENT),可空 |
|
||||
| 11 | ✨ 新增 | — | `relatedType` | 关联单类型(PAYMENT/REFUND/SETTLEMENT/CONTRACT),可空 |
|
||||
| 12 | ✨ 新增 | — | `relatedId` | 关联单 ID(String),与 relatedType 配对,可空 |
|
||||
| 13 | ✨ 新增 | — | `eventType` | 英文事件码(机器字段,前端做图标判断,**不直接展示给用户**) |
|
||||
| 14 | ✨ 新增 | — | `changeType` | 节点类型(STATUS / DATA),前端据此区分节点渲染样式 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项目 | 值 |
|
||||
|---|---|
|
||||
| 请求方法 | GET |
|
||||
| 路径 | /v3/admin/order/{id}/status-log |
|
||||
| 接口名 | 订单详情 Tab 时间线 |
|
||||
| 认证 | 需要 Bearer Token(管理后台 JWT) |
|
||||
| 幂等性 | 查询接口,天然幂等 |
|
||||
| 限流 | 无特殊限流,跟随全局网关默认限流 |
|
||||
| 加载时机 | 懒加载(切换到记录 Tab 时触发) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Long | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体(GET 请求)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**返回类型**:Result<List<LogTimelineVO>>
|
||||
|
||||
| 字段名 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | String | 否 | 节点唯一 ID(雪花 String,防 JS 精度丢失),list key 用此字段 |
|
||||
| occurredAt | String (ISO 8601) | 否 | 事件发生时间,如 2026-06-01T14:30:00 |
|
||||
| title | String | 否 | 中文事件名,如支付订金、配房完成、出行前取消 |
|
||||
| content | String | 是 | 事件内容描述,如 Y500、拉萨瑞吉酒店/林芝工布庄园希尔顿、2位出行人;DATA 节点通常有值,STATUS 节点可空 |
|
||||
| operatorName | String | 否 | 操作人姓名,如李雯;系统自动触发时为系统 |
|
||||
| fromStatusLabel | String | 是 | 状态流转来源(中文),如待支付;仅 changeType=STATUS 节点有值,DATA 节点为 null |
|
||||
| toStatusLabel | String | 是 | 状态流转目标(中文),如定制中;仅 changeType=STATUS 节点有值,DATA 节点为 null |
|
||||
| amount | BigDecimal | 是 | 金额,如 500.00;无金额时为 null |
|
||||
| amountKind | String | 是 | 金额类型枚举值,见枚举节;无金额时为 null |
|
||||
| relatedType | String | 是 | 关联单类型(PAYMENT/REFUND/SETTLEMENT/CONTRACT),可空 |
|
||||
| relatedId | String | 是 | 关联单 ID(String),与 relatedType 配对;可空 |
|
||||
| eventType | String | 否 | 英文事件码,前端做图标/样式判断用,不直接展示给用户 |
|
||||
| changeType | String | 否 | 节点类型(STATUS / DATA),前端据此区分节点渲染样式 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### changeType — 节点类型
|
||||
|
||||
| 枚举值 | 含义 | 前端渲染说明 |
|
||||
|---|---|---|
|
||||
| STATUS | 状态流转节点 | 展示 fromStatusLabel → toStatusLabel 箭头;amount / amountKind 可能有值 |
|
||||
| DATA | 状态内数据变更节点 | 无状态箭头;fromStatusLabel / toStatusLabel 为 null;展示 title + content |
|
||||
|
||||
### amountKind — 金额类型
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
| DEPOSIT | 订金 |
|
||||
| FULL | 全款 |
|
||||
| REFUND | 退款金额 |
|
||||
| RETURN | 退还金额 |
|
||||
| SETTLEMENT | 结算金额 |
|
||||
|
||||
### relatedType — 关联单类型
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
| PAYMENT | 关联支付单 |
|
||||
| REFUND | 关联退款单 |
|
||||
| SETTLEMENT | 关联结算单 |
|
||||
| CONTRACT | 关联合同单 |
|
||||
|
||||
### title 取值示例(中文,后端生成,不限于此列表)
|
||||
|
||||
创建订单 / 支付订金 / 支付全款 / 确认订单 / 开始出行 / 返团完成 / 出行前取消 / 出行中终止 / 超时自动取消 / 流程推进 / 补全出行信息 / 新增出行人 / 删除出行人 / 批量导入出行人 / 订单信息调整 / 支付尾款 / 配房完成 / 配导游完成 / 配摄影完成 / 配车完成 / 退款到账 / 核单提交 / 结算确认
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 说明 | 前端处理建议 |
|
||||
|---|---|---|---|
|
||||
| 200 | 200 | 成功,data 为节点列表(无记录时为空数组 []) | 正常渲染,空数组时展示空状态占位 |
|
||||
| ORDER_NOT_FOUND(4040xxx) | 200 | 订单不存在 | 提示订单不存在 |
|
||||
| 401 | 401 | Token 无效或过期 | 跳转登录 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功 — 状态流转节点(支付订金)
|
||||
|
||||
**请求**
|
||||
|
||||
```
|
||||
GET /v3/admin/order/1798000000000000001/status-log
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "1798000000000000099",
|
||||
"occurredAt": "2026-06-01T14:30:00",
|
||||
"title": "支付订金",
|
||||
"content": "¥500",
|
||||
"operatorName": "李雯",
|
||||
"fromStatusLabel": "待支付",
|
||||
"toStatusLabel": "定制中",
|
||||
"amount": 500.00,
|
||||
"amountKind": "DEPOSIT",
|
||||
"relatedType": "PAYMENT",
|
||||
"relatedId": "1798000000000000098",
|
||||
"eventType": "DEPOSIT_PAID",
|
||||
"changeType": "STATUS"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况 — 数据变更节点(配房完成,fromStatusLabel / toStatusLabel 为 null)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "1798000000000000103",
|
||||
"occurredAt": "2026-06-02T09:15:00",
|
||||
"title": "配房完成",
|
||||
"content": "拉萨 瑞吉度假酒店 / 林芝 工布庄园希尔顿",
|
||||
"operatorName": "系统",
|
||||
"fromStatusLabel": null,
|
||||
"toStatusLabel": null,
|
||||
"amount": null,
|
||||
"amountKind": null,
|
||||
"relatedType": null,
|
||||
"relatedId": null,
|
||||
"eventType": "ROOM_ASSIGNED",
|
||||
"changeType": "DATA"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> changeType=DATA 节点:fromStatusLabel / toStatusLabel 恒为 null,前端不渲染状态箭头。
|
||||
|
||||
### 8.3 业务失败 — 订单不存在
|
||||
|
||||
**请求**
|
||||
|
||||
```
|
||||
GET /v3/admin/order/9999999999999999999/status-log
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 4040001,
|
||||
"msg": "订单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**
|
||||
|
||||
- 订单详情页「记录」Tab 懒加载,展示完整时间线。
|
||||
- 支持所有订单状态(待支付 / 定制中 / 已确认 / 出行中 / 已完成 / 已取消)查看记录。
|
||||
|
||||
**不适用场景**
|
||||
|
||||
- 不适用于小程序端(该接口为 /v3/admin/*,仅管理后台消费)。
|
||||
- 不适用于批量查询(接口只支持单订单)。
|
||||
|
||||
**特殊边界**
|
||||
|
||||
- 新创建订单若尚无任何操作记录,返回 data: [](空数组),前端 Tab 需有空状态占位。
|
||||
- eventType 为机器字段,后端随业务迭代可能新增新值;前端图标映射表需留 fallback 分支(未知 eventType 显示默认图标),不能写死枚举列表。
|
||||
- DATA 类型节点的 content 在极少数情况下可能为 null,前端展示时须做空判断。
|
||||
- amount 字段仅在有金额关联时有值;DATA 类节点(如配房完成)的 amount 恒为 null。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 旧字段名 | 旧类型 | 旧值示例 | 新字段名 | 新类型 | 新值示例 | 变更类型 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| action | String | "PAY: 支付成功,¥500" | 已删除(拆分) | — | title=支付订金 content=¥500 | ⚠️ 删除 |
|
||||
| operator | String | "李雯" | operatorName | String | "李雯" | ⚠️ 改名 |
|
||||
| fromStatus | String | "PENDING_PAY" | fromStatusLabel | String | "待支付" | ⚠️ 改名+语义变 |
|
||||
| toStatus | String | "PROCESSING" | toStatusLabel | String | "定制中" | ⚠️ 改名+语义变 |
|
||||
| occurredAt | LocalDateTime | "2026-06-01T14:30:00" | occurredAt | LocalDateTime | 不变 | 保留 |
|
||||
| amount | BigDecimal | 500.00 | amount | BigDecimal | 不变 | 保留 |
|
||||
| — | — | — | id | String | "1798000000000000099" | ✨ 新增 |
|
||||
| — | — | — | title | String | "支付订金" | ✨ 新增 |
|
||||
| — | — | — | content | String | "¥500" | ✨ 新增 |
|
||||
| — | — | — | amountKind | String | "DEPOSIT" | ✨ 新增 |
|
||||
| — | — | — | relatedType | String | "PAYMENT" | ✨ 新增 |
|
||||
| — | — | — | relatedId | String | "1798000000000000098" | ✨ 新增 |
|
||||
| — | — | — | eventType | String | "DEPOSIT_PAID" | ✨ 新增 |
|
||||
| — | — | — | changeType | String | "STATUS" | ✨ 新增 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 旧版 | 新版 |
|
||||
|---|---|---|
|
||||
| 状态标签语言 | 英文枚举如 PENDING_PAY,前端需自行映射为中文 | 中文标签如待支付,前端直接展示,无需映射 |
|
||||
| 事件描述 | 单个 action 字段拼接如 PAY: 支付成功,¥500,前端需解析 | title(名称)+ content(详情)分离,直接使用 |
|
||||
| list key | 无唯一标识,只能用数组 index | id 字段(雪花 String),可作稳定 list key |
|
||||
| 节点类型区分 | 无,前端无法区分状态流转节点与数据变更节点 | changeType(STATUS/DATA),前端可差异化渲染 |
|
||||
| 金额关联信息 | 仅 amount | amount + amountKind + relatedType + relatedId |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 迁移
|
||||
|
||||
### 破坏兼容
|
||||
|
||||
此次变更为**破坏性变更**,以下旧字段已从响应体移除或改名:
|
||||
|
||||
| 旧字段 | 状态 | 前端必须修改的地方 |
|
||||
|---|---|---|
|
||||
| action | 已删除 | 展示事件名的地方改用 title;展示内容的地方改用 content(可空) |
|
||||
| operator | 已改名为 operatorName | 所有读取 operator 的地方改为 operatorName |
|
||||
| fromStatus | 已改名为 fromStatusLabel | 所有读取 fromStatus 的地方改为 fromStatusLabel;值已中文化,无需再做枚举映射 |
|
||||
| toStatus | 已改名为 toStatusLabel | 所有读取 toStatus 的地方改为 toStatusLabel;值已中文化,无需再做枚举映射 |
|
||||
|
||||
### 前端同步迁移清单
|
||||
|
||||
1. **list key**:将用 index 作为 list key 的地方改为使用 id 字段。
|
||||
2. **事件名展示**:action → 改用 title(直接字符串,后端已生成中文)。
|
||||
3. **事件内容展示**:原从 action 拼串截取的内容 → 改用 content(可空,展示前做空判断)。
|
||||
4. **操作人**:operator → operatorName(字段名替换,值语义不变)。
|
||||
5. **状态标签**:fromStatus / toStatus → fromStatusLabel / toStatusLabel;新值已为中文,删除前端原有枚举映射表逻辑。
|
||||
6. **节点样式分支**:新增 changeType 读取逻辑:
|
||||
- changeType === STATUS:渲染状态流转箭头(fromStatusLabel → toStatusLabel)
|
||||
- changeType === DATA:仅渲染 title + content,不渲染状态标签
|
||||
7. **图标映射**:新增对 eventType 的图标选取逻辑;未知 eventType 值使用默认图标(前端不要写死枚举,需留 fallback)。
|
||||
8. **金额区块**:可利用 amountKind 细化金额标签(订金/全款/退款等),relatedType + relatedId 可用于跳转关联单详情。
|
||||
|
||||
### 前端同步上线
|
||||
|
||||
接口破坏兼容,后端与前端**需协调同步上线**,避免新后端 + 旧前端代码同时在线导致字段取不到值。
|
||||
|
||||
### 回滚方案
|
||||
|
||||
若需回滚,后端恢复旧版 LogTimelineVO(6 字段),前端同步回滚字段引用。接口签名变化不向下兼容,必须同步部署。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **fromStatusLabel / toStatusLabel 在 DATA 节点为 null**:前端读取这两个字段前必须判断 changeType === STATUS,避免渲染 null。
|
||||
2. **id 为 String 类型**:雪花 ID 超出 JS Number 精度范围,后端已序列化为字符串。前端直接用字符串处理,不要转 Number,否则精度丢失导致 key 冲突。
|
||||
3. **eventType 不展示给用户**:该字段为机器码,仅用于图标/样式判断逻辑,不应直接渲染在 UI 上。
|
||||
4. **content 可空**:部分节点无内容描述,展示时必须做空判断,避免显示 null 或 undefined。
|
||||
5. **空列表处理**:新订单无操作记录时返回 data: [],记录 Tab 需有空状态占位(如暂无记录)。
|
||||
6. **eventType 可能扩展**:随业务迭代后端可能新增 eventType 值,前端图标映射表必须留 fallback 分支,不能写死枚举列表。
|
||||
7. **删除原有枚举映射**:旧版前端可能维护了 fromStatus / toStatus 英文到中文映射表,新版状态标签已由后端生成中文,该映射表可以删除,避免冗余维护。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| 父 Issue | https://git.1814.love:8443/wx/HL/issues/3561 |
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3574 |
|
||||
| Merge Commit | https://git.1814.love:8443/wx/HL/commit/1f93d534fe2073e56f236d6558ba6165706602b5 |
|
||||
| 后端负责人 | yst |
|
||||
@@ -0,0 +1,95 @@
|
||||
# 车务金额字段返回值 number → String — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:⚠️ 破坏性变更(JSON 类型 number → string)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-fleet-service
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
车务(fleet)部分接口的金额字段过去序列化为 JSON **数字**(如 `1000.00`)。平台约定金额一律以 **String** 下发前端(Java 内部仍是 BigDecimal),避免 JS 浮点精度问题、与其它模块口径统一。
|
||||
|
||||
本次给车务 3 个已上线接口的金额字段补上 `@JsonSerialize(ToStringSerializer)`,返回值由 **number 改为 string**(如 `1000.00` → `"1000.00"`)。**前端读取这些字段时必须按字符串处理**(如需计算先 `parseFloat`/`Number()`,展示直接用字符串)。
|
||||
|
||||
> 注:全局 Jackson 只把超出 JS 安全范围的 Long 雪花转 String,**不会自动转 BigDecimal**,所以金额需逐字段显式标注,此前这 3 处漏标导致泄成数字。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 字段 | 旧类型(JSON) | 新类型(JSON) | 示例 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 车型型号分页 / 详情 | `basePrice` 起步价 | number | **string** | `1000.00` → `"1000.00"` |
|
||||
| 2 | 车型价格日历查询 | `dayPrice` 当日租赁价 | number | **string** | `800.00` → `"800.00"` |
|
||||
| 3 | 司机详情·保险块 | `annualPremium` 年保费 | number | **string** | `3600.00` → `"3600.00"` |
|
||||
| 4 | 司机详情·保险块 | `perDayRate` 行程日费率 | number | **string** | `50.00` → `"50.00"` |
|
||||
|
||||
> 仅这 4 个金额字段受影响;其它非金额数字字段(座位数 seats、排序 sortOrder、各类 count)不变。
|
||||
|
||||
---
|
||||
|
||||
## 3. 受影响接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 受影响字段 |
|
||||
|---|---|---|---|
|
||||
| 车型型号分页 | GET | `/admin/fleet/vehicle-types/models/page` | basePrice |
|
||||
| 车型价格日历查询 | GET | `/admin/fleet/vehicle-types/{vehicleModelId}/pricing-calendar` | dayPrice |
|
||||
| 司机详情 | GET | `/admin/fleet/drivers/{driverId}` | insurance.annualPremium / insurance.perDayRate |
|
||||
|
||||
---
|
||||
|
||||
## 4. 示例(车型型号分页)
|
||||
|
||||
**请求**
|
||||
|
||||
```
|
||||
GET /admin/fleet/vehicle-types/models/page?pageNo=1&pageSize=5
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应(注意 basePrice 现为带引号的字符串)**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2057378612593823745",
|
||||
"vehicleTypeId": "2057378611889180674",
|
||||
"modelName": "丰田普拉多",
|
||||
"seats": 7,
|
||||
"basePrice": "1000.00",
|
||||
"alias": "",
|
||||
"sortOrder": 1
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端迁移
|
||||
|
||||
| 字段 | 前端处理 |
|
||||
|---|---|
|
||||
| basePrice / dayPrice / annualPremium / perDayRate | 读到的是字符串 `"1000.00"`。**展示**:直接用。**参与计算**:先 `Number(x)` / `parseFloat(x)` 再算,结果再格式化回字符串。**编辑回填**:表单控件按字符串赋值即可。 |
|
||||
|
||||
- 若前端原先把这些字段当 number 直接做算术(`a.basePrice + b.basePrice`),现在会变成字符串拼接,**必须改为先转数字**。
|
||||
- 提交(保存)入参不受影响——入参金额前端按原样传,后端独立计算/解析。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3592 |
|
||||
| 部署 | 已部署测试服并实测:`"basePrice":"1000.00"`(网关 9443 + admin token 实测带引号) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,130 @@
|
||||
# 车辆档案「车队归属 / 使用性质」改数据字典下拉 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:🟡 增强(字段取值改字典驱动,字段名/类型不变)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-fleet-service + hl-user-service(字典种子)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
车辆档案(新增 / 编辑)表单里两个字段过去是写死的:
|
||||
- `fleet`(车队归属)原是后端 `@Pattern` 写死枚举 own/coopA/coopB;
|
||||
- `regCertUsage`(使用性质)原是纯自由文本输入框。
|
||||
|
||||
本次把这两个字段改为**数据字典驱动**,前端应改为**下拉选择框**,选项从通用字典接口实时拉取。这样合作车队、使用性质都能在字典后台增减,无需改代码发版。
|
||||
|
||||
> 字段名、类型、提交入参格式**全不变**,前端只是把输入控件换成下拉、并从字典接口取选项。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 字段 | 位置 | 旧控件 | 新控件 | 字典 type | 存的值 |
|
||||
|---|---|---|---|---|---|
|
||||
| `fleet` 车队归属 | 车辆档案新增/编辑表单 | 写死下拉 own/coopA/coopB | 字典下拉 | `fleet_attribution` | dictValue(own/coopA/coopB code) |
|
||||
| `regCertUsage` 使用性质 | 车辆档案新增/编辑表单 | 自由文本框 | 字典下拉 | `vehicle_reg_cert_usage` | dictValue(中文:营运租赁等) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 字典选项怎么取(通用接口,已有)
|
||||
|
||||
| 项目 | 值 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | `/admin/dict/data/{dictType}` |
|
||||
| 认证 | Bearer Token(管理后台 JWT) |
|
||||
|
||||
前端渲染下拉:用 `dictValue` 作为选项的 value(提交给后端的值)、`dictLabel` 作为显示文案。已按 `sortOrder` 升序返回。
|
||||
|
||||
### 3.1 车队归属 `fleet_attribution`
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /admin/dict/data/fleet_attribution
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": [
|
||||
{"dictValue": "own", "dictLabel": "自有", "sortOrder": 10, "status": "ACTIVE"},
|
||||
{"dictValue": "coopA", "dictLabel": "合作车队A", "sortOrder": 20, "status": "ACTIVE"},
|
||||
{"dictValue": "coopB", "dictLabel": "合作车队B", "sortOrder": 30, "status": "ACTIVE"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 使用性质 `vehicle_reg_cert_usage`
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /admin/dict/data/vehicle_reg_cert_usage
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": [
|
||||
{"dictValue": "营运租赁", "dictLabel": "营运租赁", "sortOrder": 10, "status": "ACTIVE"},
|
||||
{"dictValue": "非营运", "dictLabel": "非营运", "sortOrder": 20, "status": "ACTIVE"},
|
||||
{"dictValue": "旅游客运", "dictLabel": "旅游客运", "sortOrder": 30, "status": "ACTIVE"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 使用性质的 value=label(都是中文),照抄行驶证「使用性质」栏;车队归属 value 是英文 code、label 是中文,**提交给保存接口的是 dictValue(own/coopA/coopB),展示用 dictLabel**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 保存接口入参(不变,仅举例)
|
||||
|
||||
车辆新增 `POST /admin/fleet/vehicles` / 编辑 `PUT /admin/fleet/vehicles/{vehicleId}`,body 里 `fleet` 传选中的 dictValue、`regCertUsage` 传选中的 dictValue:
|
||||
|
||||
```json
|
||||
{
|
||||
"plate": "蒙A-66666",
|
||||
"vehicleModelId": "...",
|
||||
"fleet": "own",
|
||||
"regCertUsage": "营运租赁",
|
||||
"...": "..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端迁移清单
|
||||
|
||||
1. **车队归属**:输入控件改为下拉,选项 `GET /admin/dict/data/fleet_attribution`;提交 `fleet` = 选中项 `dictValue`;列表/详情回显时用 `dictValue` 在字典里查 `dictLabel` 显示(也可直接缓存该字典做映射)。
|
||||
2. **使用性质**:自由文本框改为下拉,选项 `GET /admin/dict/data/vehicle_reg_cert_usage`;提交 `regCertUsage` = 选中项 `dictValue`(中文)。
|
||||
3. **存量数据兼容**:历史车辆的 `fleet`/`regCertUsage` 值与字典 value 一致(own/coopA/coopB、营运租赁等),回显正常;若遇到字典里没有的历史脏值,下拉做「未匹配则原样显示」兜底。
|
||||
4. 后端已去掉写死枚举校验,**字典后台新增的合作车队(如 coopC)/ 新使用性质会自动出现在下拉**,前端无需改代码。
|
||||
|
||||
---
|
||||
|
||||
## 6. 附带:错误码提示已全中文化(无需前端改动,仅告知)
|
||||
|
||||
后端把车务错误提示里的英文术语换成了中文,前端若直接透传后端 `message` 展示会更友好:
|
||||
|
||||
| 旧提示 | 新提示 |
|
||||
|---|---|
|
||||
| VIN 已存在 | 车架号已存在 |
|
||||
| token 无效 / 已过期 / 已使用 | 邀请链接无效 / 已过期 / 已使用 |
|
||||
| OCR 识别失败 | 证件识别失败 |
|
||||
| 车型大类 key 已存在 | 车型大类标识已存在 |
|
||||
|
||||
> 另:车辆档案错误码编号文档此前与代码不一致,已按代码对齐——「车架号已存在」实际码 **600107**(非文档旧标的 600101)、「车型不存在」**600108**。前端按 `message` 文案处理即可,一般无需硬编码错误码。
|
||||
|
||||
---
|
||||
|
||||
## 7. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3596 |
|
||||
| 部署 | 已部署测试服并实测:两字典经网关 9443 各返 3 项(fleet_attribution / vehicle_reg_cert_usage) |
|
||||
| 字典维护 | 系统管理 → 字典管理,type=`fleet_attribution` / `vehicle_reg_cert_usage` |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,82 @@
|
||||
# 在线接口文档恢复显示任务看板等接口分组 — 文档修复 — 管理后台兼小程序
|
||||
|
||||
> 变更类型:📄 文档修复(接口契约零变化,仅在线文档可见性恢复)
|
||||
> 端类型:管理后台(hl-user-service)+ 小程序(hl-mp-service)
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-user-service、hl-mp-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3597
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(务必先看)
|
||||
|
||||
1. **这些接口一直都存在,一直可以正常调用** —— 本次 **没有新增、修改或删除任何接口**,没有任何字段 / 路径 / 鉴权变化。
|
||||
2. 问题是**在线接口文档(Knife4j / Swagger)的扫描包配置过窄**,导致一批接口分组在文档页面**不显示**。前端如果只看在线文档,会误以为「任务看板等接口没有 / 被删了」。
|
||||
3. 本次修复后,这些分组在在线文档中**恢复显示**。前端**无需改任何代码**,刷新文档页面(建议 Ctrl+F5 强刷)即可看到。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与根因
|
||||
|
||||
在线接口文档「用户服务」分组下原本看不到「任务看板管理」「任务管理」等接口。
|
||||
|
||||
根因:`hl-user-service` 的 Swagger 配置 `Knife4jConfig` 中,文档扫描包 `basePackage` 被限定为 `com.hulalv.user.controller` 这一个子包;而该服务的 Controller 实际散落在 `com.hulalv` 下的多个并列子包中(如任务看板在 `com.hulalv.task.controller`),这些子包全部落在扫描范围之外,于是整组接口在文档里不显示(但 Spring 照常注册、接口照常可调用)。
|
||||
|
||||
排查时一并核对了全部 6 个后端服务的 Swagger 配置,发现 `hl-mp-service` 存在同类问题:扫描包 `com.hulalv.mp.controller` 漏掉了 `com.hulalv.mp.complaint.controller`(投诉接口)。其余服务配置正确。
|
||||
|
||||
---
|
||||
|
||||
## 2. 恢复显示的接口分组清单
|
||||
|
||||
### 2.1 hl-user-service(在线文档分组「用户服务」)
|
||||
|
||||
修复后该分组 tag 由原先的「仅 user 子包」扩展到全部 86 个,下表为此前**被漏掉、现恢复显示**的代表性分组:
|
||||
|
||||
| 接口分组(tag) | 端 | 说明 |
|
||||
|---|---|---|
|
||||
| [admin] 任务看板管理 | 管理后台 | **本次反馈点**:看板 CRUD / 状态列 / 成员 |
|
||||
| [admin] 任务管理 | 管理后台 | 任务 CRUD / 评论 / 提醒 |
|
||||
| [admin] MySQL监控 / Redis监控 / RocketMQ监控 / 服务监控 | 管理后台 | 各中间件与服务监控 |
|
||||
| [admin] 操作日志 / 登录日志 / 错误日志 / 消息通知日志 / 企微审批日志 | 管理后台 | 各类审计日志查询 |
|
||||
| [admin] 微信内容安全-违规命中日志 | 管理后台 | 小程序内容安全命中记录 |
|
||||
| [admin] 文件管理 / 通用OSS上传 | 管理后台 | 文件与对象存储 |
|
||||
| [C端] 文件上传 | 小程序 | C 端文件上传 |
|
||||
| [admin] 管理端站内信 / 站内信实时推送 / 通知中心管理 | 管理后台 | 站内信与通知中心 |
|
||||
| [内部] 文件 / 日志接收 / 通知中心 / 登录日志 | 内部 Feign | 服务间内部接口 |
|
||||
|
||||
### 2.2 hl-mp-service(在线文档分组「小程序 BFF」)
|
||||
|
||||
| 接口分组(tag) | 端 | 说明 |
|
||||
|---|---|---|
|
||||
| [C端] 投诉 | 小程序 | 投诉提交 / 查询 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 对前端的影响
|
||||
|
||||
| 项目 | 说明 |
|
||||
|---|---|
|
||||
| 是否需要改代码 | **否**。接口签名、路径、字段、鉴权全部不变 |
|
||||
| 需要做什么 | 刷新在线文档页面(Ctrl + F5 强刷),即可在「用户服务」分组看到任务看板等接口;「小程序 BFF」分组看到投诉接口 |
|
||||
| 是否影响线上调用 | 否。这些接口此前一直可正常调用,本次仅恢复文档可见性 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证
|
||||
|
||||
部署测试服后,直接拉取两个服务的 Swagger JSON 确认(地面真相):
|
||||
|
||||
- `hl-user-service` group「用户服务」:tag 总数 86,已含 `[admin] 任务看板管理`、`[admin] 任务管理` 等。
|
||||
- `hl-mp-service` group「小程序 BFF」:tag 总数 51,已含 `[C端] 投诉`。
|
||||
- 两服务均双实例 health UP(user 8081/8181,mp 8085/8185)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联 / 联系人
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3597 |
|
||||
| Merge Commit | 4976d3c8d |
|
||||
| 关联工单 | 无(在线文档展示问题,经排查为后端 Swagger 扫描配置) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,106 @@
|
||||
# 车辆/司机附件「类目」改数据字典驱动 + 后端撤类目/张数校验 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:🟡 增强(类目改字典驱动)+ ⚠️ 行为变更(后端撤校验,改前端限制)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-fleet-service + hl-user-service(字典种子)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
车辆档案 / 司机档案的「附件类目」(category)过去是后端硬编码枚举(含每类目张数上限),后端做两道校验:类目非法(601002)、张数超上限(601003)。
|
||||
|
||||
本次改为**数据字典驱动**:类目列表 + 各类目张数上限都放字典,前端从字典接口拉取后渲染上传区块、并**由前端按字典里的上限限制单类目上传张数**。**后端不再校验类目合法性、不再校验张数上限**(601002 / 601003 已废弃)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | 类目改字典 | 车辆 6 类、司机 8 类改为字典 `vehicle_attachment_category` / `driver_attachment_category`,可后台增减 |
|
||||
| 2 | 张数上限放字典 `remark` | 每个类目的上限写在字典项的 `remark`(纯数字),**前端读取并限制单类目张数** |
|
||||
| 3 | ⚠️ 后端撤校验 | 后端不再校验类目合法性 + 张数上限,**601002 / 601003 废弃**(不再抛出) |
|
||||
| 4 | 上传区块前端渲染 | 前端从字典动态渲染上传区块(类目名 = `dictLabel`,提交值 = `dictValue`) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 字典怎么取(通用接口,已有)
|
||||
|
||||
| 项目 | 值 |
|
||||
|---|---|
|
||||
| 方法 / 路径 | GET `/admin/dict/data/{dictType}` |
|
||||
| 认证 | Bearer Token(管理后台 JWT) |
|
||||
| 取上限 | 字典项的 `remark` 字段 = 该类目张数上限(字符串数字,`parseInt`)|
|
||||
|
||||
### 3.1 车辆附件类目 `vehicle_attachment_category`
|
||||
|
||||
```
|
||||
GET /admin/dict/data/vehicle_attachment_category
|
||||
```
|
||||
|
||||
| dictValue | dictLabel | remark(张数上限) |
|
||||
|---|---|---|
|
||||
| exterior | 外观照 | 6 |
|
||||
| interior | 内饰照 | 3 |
|
||||
| insurance | 保险单据 | 2 |
|
||||
| inspection | 年检单据 | 2 |
|
||||
| operation_license | 营运证 | 2 |
|
||||
| other | 其他 | 5 |
|
||||
|
||||
### 3.2 司机附件类目 `driver_attachment_category`
|
||||
|
||||
```
|
||||
GET /admin/dict/data/driver_attachment_category
|
||||
```
|
||||
|
||||
| dictValue | dictLabel | remark(张数上限) |
|
||||
|---|---|---|
|
||||
| id_card | 身份证 | 2 |
|
||||
| driver_license | 驾驶证 | 2 |
|
||||
| portrait | 肖像照 | 2 |
|
||||
| health_cert | 健康证 | 2 |
|
||||
| training_cert | 培训证书 | 5 |
|
||||
| award | 荣誉奖励 | 5 |
|
||||
| qualification | 从业资格证 | 3 |
|
||||
| other | 其他 | 5 |
|
||||
|
||||
**响应示例**(节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": [
|
||||
{"dictValue": "exterior", "dictLabel": "外观照", "sortOrder": 10, "status": "ACTIVE", "remark": "6"},
|
||||
{"dictValue": "interior", "dictLabel": "内饰照", "sortOrder": 20, "status": "ACTIVE", "remark": "3"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 保存接口(不变)
|
||||
|
||||
车辆 `POST/PUT /admin/fleet/vehicles`、司机 `POST/PUT /admin/fleet/drivers` 的 `attachments[].category` 字段:传选中类目的 `dictValue`(exterior / id_card …)。字段名、类型、结构全不变;后端只是不再校验该值。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端迁移清单
|
||||
|
||||
1. **上传区块改字典渲染**:车辆 / 司机档案的附件上传区块改为从 `/admin/dict/data/vehicle_attachment_category`(或 driver)拉类目动态渲染,`dictLabel` 作区块标题、`dictValue` 作提交 category。
|
||||
2. **张数限制读字典**:每个类目允许上传的最大张数 = 该字典项 `remark`(`parseInt(remark)`)。**前端负责限制**(达上限禁止再选/上传);后端不再拦。
|
||||
3. **类目可后台增减**:运营在「系统管理 → 字典管理」新增类目,前端会自动多出一个上传区块(无需改前端代码),新类目上限同样读其 `remark`。
|
||||
4. **错误码**:601002 / 601003 已废弃,后端不再返回;前端如有针对这两个码的提示分支可移除。
|
||||
5. **详情分组**:司机详情 `attachments` 仍按 8 个固定字段分组返回(idCard/driverLicense/portrait/healthCert/trainingCert/award/qualification/other),不变。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3598 |
|
||||
| 部署 | 已部署测试服并实测:两字典经网关 9443 各返 6 / 8 项(含 remark 上限) |
|
||||
| 字典维护 | 系统管理 → 字典管理,type=`vehicle_attachment_category` / `driver_attachment_category` |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,77 @@
|
||||
# 准驾车型 / 民族 / 保险公司 / 性别 改数据字典下拉 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:🟡 增强(字段改字典下拉,字段名/类型不变)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-08
|
||||
> 服务:hl-fleet-service + hl-user-service(字典种子)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
车务(车辆 / 司机档案)几个「标准受控词表」字段过去是自由文本手填,本次改为**数据字典驱动**,前端应改为**下拉选择框**,选项从字典接口拉取:
|
||||
|
||||
- **准驾车型**、**民族**、**保险公司** 是新建字典;
|
||||
- **性别** 复用平台已有 `gender` 字典(后端这次放开校验、开始接受「未知」)。
|
||||
|
||||
字段名、类型、提交格式**全不变**,前端只是把输入控件换成下拉。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 字段 | 出现位置 | 旧控件 | 新控件(字典 type) | 提交值 |
|
||||
|---|---|---|---|---|
|
||||
| 准驾车型 `licenseClass`/`type` | 司机驾照信息 | 文本框 | 下拉 `driving_license_class` | dictValue(A1/A2/B1…国标代码)|
|
||||
| 民族 `nation` | 司机基本信息 | 文本框 | 下拉 `nation` | dictValue(中文,如 蒙古族)|
|
||||
| 保险公司 `insurer`(车)/ `company`(司机)| 车辆 / 司机保险 | 文本框 | 下拉 `insurance_company` | dictValue(中文,如 中国人保财险)|
|
||||
| 性别 `gender` | 司机基本信息 | 文本框/单选 | 下拉 `gender`(**平台已有**)| dictValue(1=男/2=女/**0=未知**)|
|
||||
|
||||
> ⚠️ **性别新增「0=未知」**:后端原来只认 1/2,本次放开为 1/2/0,与平台 gender 字典对齐。前端下拉请用全部 3 项。导入 Excel 的「性别」列现支持 `男/女/未知`(也兼容已是 `1/2/0`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 字典怎么取(通用接口,已有)
|
||||
|
||||
GET `/admin/dict/data/{dictType}`(Bearer Token),用 `dictValue` 作选项值、`dictLabel` 作显示文案,按 `sortOrder` 升序。
|
||||
|
||||
| dictType | 项数 | 示例 |
|
||||
|---|---|---|
|
||||
| `driving_license_class` | 16 | A1=A1(大型客车)/ C1=C1(小型汽车)… |
|
||||
| `nation` | 56 | 汉族 / 蒙古族 / 回族 …(value=label=中文)|
|
||||
| `insurance_company` | 10 | 中国人保财险 / 中国平安财险 …(value=label=中文,末项「其他」)|
|
||||
| `gender` | 3 | 1=男 / 2=女 / 0=未知 |
|
||||
|
||||
**响应示例**(准驾车型节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": [
|
||||
{"dictValue": "A1", "dictLabel": "A1(大型客车)", "sortOrder": 10, "status": "ACTIVE"},
|
||||
{"dictValue": "C1", "dictLabel": "C1(小型汽车)", "sortOrder": 60, "status": "ACTIVE"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端迁移清单
|
||||
|
||||
1. **准驾车型**:司机驾照表单的准驾车型改下拉,选项 `GET /admin/dict/data/driving_license_class`;提交 `licenseClass`(或 `license.type`)= 选中 `dictValue`(A1 等代码)。
|
||||
2. **民族**:改下拉,选项 `GET /admin/dict/data/nation`;提交 `nation` = 选中 `dictValue`(中文)。
|
||||
3. **保险公司**:车辆 `insurer`、司机保险 `company` 都改下拉,选项 `GET /admin/dict/data/insurance_company`;提交选中 `dictValue`(中文)。
|
||||
4. **性别**:下拉用 `GET /admin/dict/data/gender`,含 **0=未知**;展示已有性别值时用该字典把 0/1/2 映射为 未知/男/女。
|
||||
5. **存量兼容**:历史值与字典 value 一致(A1、蒙古族、中国人保财险、1/2…),回显正常;遇字典里没有的历史脏值,下拉做「未匹配则原样显示」兜底。
|
||||
6. **可后台增减**:准驾车型/民族/保险公司都能在「系统管理 → 字典管理」增减,前端无需改代码。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3599 |
|
||||
| 部署 | 已部署测试服并实测:准驾 16 / 民族 56 / 保险 10 / 性别 3(含 0=未知),经网关 9443 |
|
||||
| 字典维护 | 系统管理 → 字典管理,type=`driving_license_class` / `nation` / `insurance_company` / `gender` |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,98 @@
|
||||
# 【字段移除·车辆档案】车辆保险登记瘦身 — 删除「保险公司 / 保单号」字段
|
||||
|
||||
> **变更类型**:🟠 字段移除(出入参 + 列表 + 导入列)
|
||||
> **端类型**:管理后台
|
||||
> **服务**:hl-fleet-service
|
||||
> **PR**:https://git.1814.love:8443/wx/HL/pulls/3600
|
||||
> **日期**:2026-06-08
|
||||
> **影响范围**:车辆新增 / 编辑 / 详情 / 列表 / Excel 导入
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
车辆「保险登记」的两个结构化字段 **`insurer`(保险公司)/ `policyNo`(保单号)已删除**。
|
||||
|
||||
原因:保险公司、保单号、险种、保额、起止日等明细,**已全部承载在「保险单」附件图片**(`attachments` 里 `category=insurance` 的那张/那几张)中,结构化字段属重复录入,且不参与任何后端逻辑(不进筛选、不进对账、不进提醒)。
|
||||
|
||||
**车辆保险结构化只保留 `insureDue`(保险到期日)**——它驱动「保险到期提醒」,继续保留且可独立编辑。
|
||||
|
||||
> 注:这同时撤销了 v1.5.11 定的「保险三件套作一张保单整体更新」口径——`insureDue` 现在是独立可编辑字段,续保直接改到期日即可。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前端要改什么
|
||||
|
||||
| 位置 | 改动 |
|
||||
|---|---|
|
||||
| 车辆**新增 / 编辑**表单 | 去掉「保险公司」「保单号」两个输入框;**保留「保险到期日」**;保险单证件继续走「保险单」附件上传 |
|
||||
| 车辆**详情页** | 出参不再返回 `insurer` / `policyNo`;保险公司、保单号看「保险单」附件图片 |
|
||||
| 车辆**列表页** | 列表项不再返回 `insurer`(如有展示该列请移除) |
|
||||
| Excel **批量导入** | 模板去掉「保险公司」「保单号」两列;保险相关只保留「保险到期日」列 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 字段变更明细
|
||||
|
||||
| 字段 | 出现位置 | 变更 |
|
||||
|---|---|---|
|
||||
| `insurer` 保险公司 | 新增入参 / 编辑入参 / 详情出参 / 列表出参 / 导入列 | ❌ **移除** |
|
||||
| `policyNo` 保单号 | 新增入参 / 编辑入参 / 详情出参 / 导入列 | ❌ **移除** |
|
||||
| `insureDue` 保险到期日 | 新增入参 / 编辑入参 / 详情出参 / 列表出参 / 导入列 | ✅ **保留**(独立可编辑) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 详情出参示例(变更后)
|
||||
|
||||
`GET /admin/fleet/vehicles/{vehicleId}`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "<vehicleId>",
|
||||
"plate": "蒙A-88888",
|
||||
"vehicleModelId": "<id>",
|
||||
"modelName": "丰田汉兰达",
|
||||
"seats": 7,
|
||||
"fleet": "own",
|
||||
"vehicleStatus": "idle",
|
||||
"vin": "LFV2A2********01",
|
||||
"regDate": "2022-03-15",
|
||||
"inspectDue": "2027-03-15",
|
||||
"insureDue": "2026-03-14",
|
||||
"regCertNo": "150102A******01",
|
||||
"regCertOwner": "内蒙古驼路旅行社",
|
||||
"regCertUsage": "营运租赁",
|
||||
"regCertFrontUrl": "https://oss.../front.jpg",
|
||||
"regCertBackUrl": "https://oss.../back.jpg",
|
||||
"primaryDriverId": "<driverId>",
|
||||
"primaryDriverName": "王师傅",
|
||||
"attachments": [
|
||||
{ "category": "insurance", "url": "https://oss.../policy.jpg", "mimeType": "image/jpeg", "sortNo": 0 }
|
||||
],
|
||||
"createTime": "2026-01-10T10:00:00+08:00"
|
||||
},
|
||||
"msg": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
> 对比变更前:删去了 `"insurer": "中国人保"` 和 `"policyNo": "PVAA20260001"` 两行。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证
|
||||
|
||||
- `hl-fleet-service` 209 单测全绿。
|
||||
- 已部署测试服(双实例 8087 + 8187 均 UP),经运行态 OpenAPI 实测 `VehicleDetailRespVO` 已无 `insurer` / `policyNo`、仍有 `insureDue`。
|
||||
- DB `fleet_vehicle` 已 DROP COLUMN `insurer` / `policy_no`(Flyway `V20260608_001`,双实例正常启动即迁移成功)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联文档
|
||||
|
||||
| 文档 | 版本 |
|
||||
|---|---|
|
||||
| API-SPEC-FLEET | v1.5.16 |
|
||||
| DATABASE-SCHEMA-FLEET | v1.5.13 |
|
||||
| SRS-FLEET | v1.5(§5.2 保险登记) |
|
||||
@@ -0,0 +1,103 @@
|
||||
# 【新增字段·车辆档案】车辆新增「所有权性质 ownerType」— 区分个人所有 / 公司所有
|
||||
|
||||
> **变更类型**:🟢 新增字段(出入参 + 列表过滤 + 导入列,字典下拉)
|
||||
> **端类型**:管理后台
|
||||
> **服务**:hl-fleet-service + hl-user-service(字典种子)
|
||||
> **PR**:https://git.1814.love:8443/wx/HL/pulls/3601
|
||||
> **日期**:2026-06-08
|
||||
> **影响范围**:车辆新增 / 编辑 / 详情 / 列表 / 列表筛选 / Excel 导入
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
车辆档案新增字段 **`ownerType`(所有权性质)**,字典下拉,取值:
|
||||
|
||||
| dictValue | dictLabel |
|
||||
|---|---|
|
||||
| `company` | 公司所有 |
|
||||
| `personal` | 个人所有 |
|
||||
|
||||
它和已有的 **`fleet`(车队归属 own/coopA/coopB)正交**,别混:
|
||||
|
||||
- `fleet` 回答「**谁在运营这车**」——自有车队 / 合作车队A / 合作车队B。
|
||||
- `ownerType` 回答「**车登记在谁名下**」——个人 / 公司。
|
||||
|
||||
> 原因:原来 `fleet=own`(自有)把「公司自购车」和「司机自带车(个人产权)」混在一起,靠 `regCertOwner`(行驶证所有人)那个文本名字人眼猜。新字段把产权拆清楚。
|
||||
|
||||
**不传默认 `company`(公司所有)**,存量车自动视为公司车。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前端要改什么
|
||||
|
||||
| 位置 | 改动 |
|
||||
|---|---|
|
||||
| 车辆**新增 / 编辑**表单 | 加「所有权性质」下拉,选项从 `GET /admin/dict/data/vehicle_owner_type` 取(`dictValue` 作值、`dictLabel` 作显示);不选默认 `company` |
|
||||
| 车辆**详情 / 列表**出参 | 多了 `ownerType` 字段,用字典把 `company`/`personal` 映射为 公司所有/个人所有 显示 |
|
||||
| 车辆**列表筛选** | `GET /admin/fleet/vehicles/page` 新增可选 query 参数 `ownerType`(`company`/`personal`) |
|
||||
| Excel **批量导入** | 模板可加「所有权性质(company/personal)」列(可选,不填默认 company) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 字典接口
|
||||
|
||||
`GET /admin/dict/data/vehicle_owner_type`(Bearer Token)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "dictValue": "company", "dictLabel": "公司所有", "sortOrder": 10, "status": "ACTIVE" },
|
||||
{ "dictValue": "personal", "dictLabel": "个人所有", "sortOrder": 20, "status": "ACTIVE" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 字段位置(新增车辆入参示例节选)
|
||||
|
||||
`POST /admin/fleet/vehicles`
|
||||
|
||||
```json
|
||||
{
|
||||
"plate": "蒙A-66666",
|
||||
"vehicleModelId": "<id>",
|
||||
"fleet": "own", // 车队归属(运营归属)
|
||||
"ownerType": "company", // 🆕 所有权性质(产权;不传默认 company)
|
||||
"seats": 7,
|
||||
"...": "..."
|
||||
}
|
||||
```
|
||||
|
||||
详情 `GET /admin/fleet/vehicles/{id}` 出参同样多 `ownerType` 一项。
|
||||
|
||||
---
|
||||
|
||||
## 4. 关于「司机自带车自动归个人所有」
|
||||
|
||||
设计上:司机**自带车**审核通过的车应自动归 `personal`(个人所有)。
|
||||
|
||||
⚠️ 但这个自动置值挂在「**自带车 → 自动建车 / 关联**」的路径上,而**该路径目前后端还没实现**(自带车审核当前只在司机档案上记了个车牌字符串,没有自动创建/关联车辆记录;常驻司机 primaryDriverId 也还在后续里程碑)。所以**现阶段**:
|
||||
|
||||
- 字段和默认值已就绪。
|
||||
- 车管在车辆档案里手动建/编辑自带车记录时,**手动选「个人所有」**即可。
|
||||
- 待「自带车→自动建车」路径落地后,会自动置 `personal`(届时再发 changelog 告知)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证
|
||||
|
||||
- `hl-fleet-service` 209 单测全绿。
|
||||
- 已部署测试服(user + fleet 双实例均 UP);经网关 9443 实测字典返 2 项(公司所有/个人所有),运行态 OpenAPI 契约 SaveReq/Detail/列表项均含 `ownerType`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联文档
|
||||
|
||||
| 文档 | 版本 |
|
||||
|---|---|
|
||||
| API-SPEC-FLEET | v1.5.17 |
|
||||
| DATABASE-SCHEMA-FLEET | v1.5.14 |
|
||||
| SRS-FLEET | v1.5(§5.2 车辆档案·所有权性质) |
|
||||
@@ -0,0 +1,77 @@
|
||||
# 【功能落地·车辆/司机】常驻司机 1:1 车侧权威 — primaryDriverId 真实可用 + 详情回显
|
||||
|
||||
> **变更类型**:🟢 功能落地(原文档已描述、后端补实现;出入参字段不变)
|
||||
> **端类型**:管理后台
|
||||
> **服务**:hl-fleet-service
|
||||
> **PR**:https://git.1814.love:8443/wx/HL/pulls/3602
|
||||
> **日期**:2026-06-08
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
「常驻司机」`primaryDriverId` 之前文档已写、但后端是占位(详情 `primaryDriverName` 一直返 null)。本次**真实落地**:
|
||||
|
||||
- **常驻严格 1:1·车侧权威**:一车一常驻司机、一司机一常驻车,权威落 `fleet_vehicle.primary_driver_id`。
|
||||
- 与「派单 N:N」正交——常驻只是派单候选加权,实际派谁开哪台车仍可任意。
|
||||
|
||||
---
|
||||
|
||||
## 1. 前端能用什么了
|
||||
|
||||
| 场景 | 说明 |
|
||||
|---|---|
|
||||
| 车辆**编辑**(`PUT /admin/fleet/vehicles/{id}`)设常驻 | 入参 `primaryDriverId` 传司机 ID = 设为常驻;传 `null`/不传 = **解绑**常驻 |
|
||||
| 设常驻**冲突** | 选的司机已是别车常驻 → 返 **`605022`**「该司机已是其它车辆的常驻司机,请先解绑后再指定」(不自动转移,需先去那台车解绑) |
|
||||
| 车辆**详情**(`GET /admin/fleet/vehicles/{id}`)回显 | `primaryDriverId` / `primaryDriverName` / `primaryDriverPhone`(脱敏)现在**真实返回**(无常驻则三者全 null),不再恒 null |
|
||||
|
||||
---
|
||||
|
||||
## 2. 出参示例(详情,有常驻司机)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "<vehicleId>",
|
||||
"plate": "蒙A-88888",
|
||||
"primaryDriverId": "1234567890",
|
||||
"primaryDriverName": "王师傅",
|
||||
"primaryDriverPhone": "138****1234"
|
||||
},
|
||||
"msg": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
无常驻时:`primaryDriverId` / `primaryDriverName` / `primaryDriverPhone` 均为 `null`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 新增错误码
|
||||
|
||||
| code | msg | 触发场景 |
|
||||
|---|---|---|
|
||||
| 605022 | 该司机已是其它车辆的常驻司机,请先解绑后再指定 | 设常驻时所选司机已是别车常驻 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 尚未包含(下一步)
|
||||
|
||||
「司机**自带车**审核通过 → 自动建车 + 自动设常驻本人 + 自动归个人所有」**未包含**——它依赖「自带车→自动建车」路径,该路径目前后端未实现(自带车审核当前只在司机档案记车牌字符串、未建车辆记录)。该路径落地后会自动完成上述联动,届时另发 changelog。当前车管可手动建车 + 手动设常驻。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证
|
||||
|
||||
- `hl-fleet-service` 211 单测全绿。
|
||||
- 已部署测试服(双实例 8087 + 8187 均 UP,Flyway 加列成功);运行态 OpenAPI 契约详情含 `primaryDriverId`/`primaryDriverName`/`primaryDriverPhone`、编辑入参含 `primaryDriverId`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联文档
|
||||
|
||||
| 文档 | 版本 |
|
||||
|---|---|
|
||||
| API-SPEC-FLEET | v1.5.18(§2.2 / §2.4 / §14.3 错误码 605022) |
|
||||
| DATABASE-SCHEMA-FLEET | v1.5.15(fleet_vehicle.primary_driver_id + uk_primary_driver) |
|
||||
| SRS-FLEET | v1.5(§9.15.4 常驻 1:1 车侧权威) |
|
||||
@@ -0,0 +1,44 @@
|
||||
# 【修改·管理后台+小程序】保险出单改异步,保险状态新增 INSURING(出单中),保单号可能延迟回填
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-order-service-v2 / hl-order-service-v3 / hl-gateway
|
||||
> PR: #3646(已合并 dev-v3) | Issue: #3642 | 日期: 2026-06-10
|
||||
> 影响范围: 管理后台保险列表/订单详情、小程序保险展示的**状态字段**与**保单号字段**
|
||||
|
||||
## ⚠️ 关键说明(前端必看 2 点)
|
||||
|
||||
保游网保险接口升级到 4.2"见费出单",**出单从"同步即时返回保单号"改为部分产品"异步出单"**(保司确认收费后再回调出单结果)。对前端展示有两处影响,均为**向后兼容的字段取值变化**,无字段增删、无结构变化:
|
||||
|
||||
1. **保险状态(insuranceStatus / 保险订单 status)新增取值 `INSURING`(出单中)**。原值域 `NONE / INSURED / CANCELLED / FAILED`,现增加 `INSURING`。请前端在保险状态字典/映射中补这一项,文案建议"出单中",否则该状态会显示为原始码 `INSURING` 或落入 default 分支。
|
||||
2. **保单号(extPolicyNo / policyNo)在异步出单产品下,投保成功瞬间为空**,需等保游回调到达后回填。前端遇保单号为空且状态为 `INSURING` 时,应显示"出单中,保单号生成后展示",不要显示为投保失败。
|
||||
|
||||
> 注:仅"异步出单"产品(保游下单同步响应 MsgCode=80099)会经历 `INSURING` 窗口;"实时出单"产品(MsgCode=10000)仍直接 `INSURED` 并立即带保单号,行为不变。保游本月内逐步切换各保司接口,故 `INSURING` 会在切换后开始出现。
|
||||
|
||||
## 1. 保险状态值域(完整字典)
|
||||
|
||||
| 状态码 | 含义 | 说明 |
|
||||
|--------|------|------|
|
||||
| `NONE` | 无保险 | 订单未生成保险(仅订单主表镜像字段出现) |
|
||||
| `INSURING` | **出单中(新增)** | 异步出单受理成功,等保司回调确认出单 |
|
||||
| `INSURED` | 已投保 | 出单成功,有保单号 |
|
||||
| `CANCELLED` | 已退保 | — |
|
||||
| `FAILED` | 投保失败 | 出单失败,可重投 |
|
||||
|
||||
管理后台订单主表镜像 `insuranceStatus` 聚合优先级:`INSURED > INSURING > CANCELLED > FAILED > NONE`。
|
||||
|
||||
## 2. 受影响接口(取值变化,非结构变化)
|
||||
|
||||
| 端 | 接口 | 变化点 |
|
||||
|----|------|--------|
|
||||
| 管理后台 | 保险订单列表/详情(`/admin/insurance/*`、`/v3/admin/insurance/*`) | status/statusLabel 可能为 INSURING/出单中;policyNo 可能为空 |
|
||||
| 管理后台 | 订单详情-合同保险 Tab | insuranceStatus 可能为 INSURING;保单号可能空 |
|
||||
| 小程序 | 保险详情/保单展示(`/mp/insurance/*`、订单保险块) | status/statusLabel 同上;policyNo 空时 PDF 暂不可下载(显示"出单中") |
|
||||
|
||||
## 3. 无需前端改动的部分
|
||||
|
||||
- 保游回调接收端点(`/insurance/callback/baoyou`、`/v3/insurance/callback/baoyou`)是**后端接收保游通知**的内部端点,前端不调用。
|
||||
- 出单成功回调到达后,后端自动回填保单号 + 翻 INSURED + 触发 PDF 上传 + 发投保成功通知,前端按现有逻辑刷新即可看到。
|
||||
|
||||
## 验证
|
||||
|
||||
测试服已部署(hl-gateway / hl-order-service-v2 / hl-order-service-v3),回调端点经网关 9443 可达(HTTP 200)。`INSURING` 态的真实产生需保游切换异步出单产品后触发。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 【新增·管理后台】车型库计数字段(modelCount/inUseCount)上线 + 型号 sortOrder 改选填 + 文档契约勘误(page 参名/records/成功码 200)
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3650 + #3651(均已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单 | 日期: 2026-06-10
|
||||
> 影响范围: 管理后台**车型管理库**页面(`/admin/fleet/vehicle-types/*` 全部 9 端点);另有 FLEET/HOUSE 文档勘误请前端自查
|
||||
|
||||
## ⚠️ 关键说明(前端必看 3 点)
|
||||
|
||||
1. **计数字段上线了**:车型树/大类分页/型号分页现在直接返回 `modelCount`(大类下型号数)与 `inUseCount`(在役车辆数,大类级+型号级),左栏「N 款 · 在役 N 台」、头部 MiniStat、型号表「在役车辆」列、删除守卫提示「仍有 N 辆在役」**都不用前端自己聚合了**。口径=未软删全量,与删除守卫(600104/600105)同口径,实时查库。
|
||||
2. **新增/编辑型号不用传 sortOrder 了**(原必填,不传会 400):新增不传自动追加同大类末尾(现有最大+1,空则 1);编辑不传保持原值。原型表单本来就没有排序输入,前端按表单四字段(modelName/seats/basePrice/alias)直接提交即可。
|
||||
3. **文档勘误,前端如曾照旧文档实现请自查 3 处**(均为文档错、后端行为一直如此,**后端无变化**):
|
||||
- 分页参数名是 **`page`** 不是 `pageNo`(传 `pageNo` 会被静默忽略、永远返回第 1 页);
|
||||
- 分页响应列表字段是 **`records`** 不是 `list`(§0.6 及车辆/司机/H5/待审/看板各节示例已订正);
|
||||
- 成功码是 **`code: 200`** 不是 `code: 0`(FLEET 130 处、HOUSE 48 处示例已全部订正;判 `code===0` 会把所有成功当失败)。
|
||||
|
||||
## 1. 计数字段:涉及接口与新字段
|
||||
|
||||
| 接口 | 新字段 | 说明 |
|
||||
|------|--------|------|
|
||||
| `GET /admin/fleet/vehicle-types`(车型树) | 大类级 `modelCount`、`inUseCount`;`models[].inUseCount` | 两级计数一次带回 |
|
||||
| `GET /admin/fleet/vehicle-types/page`(大类分页) | `records[].modelCount`、`records[].inUseCount` | 左栏导航/MiniStat 数据源 |
|
||||
| `GET /admin/fleet/vehicle-types/models/page`(型号分页) | `records[].inUseCount` | 型号表「在役车辆」列数据源 |
|
||||
| `POST/PUT` 大类/型号(写接口) | 出参=完整对象(含计数) | 新建时计数=0;编辑返回真实计数 |
|
||||
|
||||
**树接口响应示例**(测试服实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": "1929151481086734337",
|
||||
"typeKey": "suv2",
|
||||
"typeName": "SUV 越野",
|
||||
"icon": "🚙",
|
||||
"sortOrder": 1,
|
||||
"modelCount": 1,
|
||||
"inUseCount": 0,
|
||||
"models": [
|
||||
{ "id": "1929151628176175106", "vehicleTypeId": "1929151481086734337",
|
||||
"modelName": "丰田普拉多", "seats": 7, "basePrice": "100.00",
|
||||
"alias": "普拉多", "sortOrder": 1, "inUseCount": 0 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"msg": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:`models[]` 里也带 `vehicleTypeId` 与 `sortOrder`(旧文档示例漏列,实际一直返回);`basePrice` 是字符串。
|
||||
|
||||
## 2. 新增/编辑型号:sortOrder 选填
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 新增不传 sortOrder | 自动追加同大类末尾(现有最大+1,空大类=1) |
|
||||
| 编辑不传 sortOrder | 保持原值不变 |
|
||||
| 显式传 | 按传入值(行为不变) |
|
||||
|
||||
**新增型号请求示例**(四字段即可,测试服实测返回 sortOrder=2 自动追加):
|
||||
|
||||
```bash
|
||||
curl -X POST "https://api.test.1814.love:9443/admin/fleet/vehicle-types/{typeId}/models" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"modelName":"丰田威尔法","seats":7,"basePrice":"2000.00","alias":"威尔法"}'
|
||||
```
|
||||
|
||||
## 3. 错误码表补全(行为未变,文档原漏列)
|
||||
|
||||
| code | msg | 触发 |
|
||||
|------|-----|------|
|
||||
| 600101 | 车型大类标识已存在 | 新增大类 typeKey 重复 |
|
||||
| 600102 | 车型大类不存在 | 大类 id 无效(原文档误写「车型不存在」) |
|
||||
| 600103 | 车型型号已存在 | 同大类内型号名重复(新增/编辑改名) |
|
||||
| 600104 | 大类下存在型号,不能删除 | 删大类守卫 |
|
||||
| 600105 | 型号被车辆引用,不能删除 | 删型号守卫(实测文案逐字一致) |
|
||||
| 600106 | 车型型号不存在 | 型号 id 无效 |
|
||||
|
||||
另:编辑大类时 `typeKey` 仍需传非空值(后端忽略其值,不传会 400 参数校验);编辑型号 `alias` 不传=保持原值、传 `""` 才清空。
|
||||
|
||||
## 4. 文档版本
|
||||
|
||||
- FLEET API → **v1.5.49 + v1.5.50**(§1 全节契约订正 + 全文成功码勘误),文档站已同步:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
- HOUSE API → **v1.1.5**(48 处成功码勘误,无契约变化)
|
||||
@@ -0,0 +1,52 @@
|
||||
# 【修改·管理后台】车辆档案:列表新增常驻司机字段、详情补 typeKey/准驾/驾龄、keyword 支持搜车型、批量导入大修(CSV/警告档/局部更新)
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3653 + #3656(均已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单 | 日期: 2026-06-10
|
||||
> 影响范围: 管理后台**车辆档案**页面(`/admin/fleet/vehicles/*` 6 端点)+ 全文档级勘误自查项
|
||||
|
||||
## ⚠️ 关键说明(前端必看 4 点)
|
||||
|
||||
1. **车辆列表(§2.1)新增常驻司机三字段**:`primaryDriverId`/`primaryDriverName`/`primaryDriverLicenseType`(无常驻=null)。派单弹窗车卡显示常驻司机、车辆卡准驾徽章不用再反查司机侧。
|
||||
2. **keyword 现在真的能搜车型了**:原来只搜车牌(文档一直写「车牌/车型」但代码只搜车牌,按车型名搜会静默返回空列表),现在车牌 OR 车型名模糊均命中。
|
||||
3. **批量导入行为变化(重要)**:
|
||||
- **更新已有车牌的行=11 个导入列的局部更新,空白列保持原值**——原来会把该车的附件全部清掉、解绑常驻司机、清空 VIN(已修复,放心重导);
|
||||
- **CSV 真的可用了**(原来上传 .csv 直接 500;.xlsx 此前部署缺 POI 依赖实际也不可用,已一并修复);
|
||||
- **新增警告档**:出参多 `warnedCount`/`warnings[{row,plate,field,msg}]`(保险不足 30 天/年检临期或已过期——**行仍然导入**,只是提醒;`warnedCount` 是 inserted+updated 的子集不是加法项);
|
||||
- 文件解析失败现在返回业务错误码 **100001**(原来静默返回成功 0 行)。
|
||||
4. **包络字段勘误(纯文档,后端行为没变)**:响应包络真实字段是 **`message`** 不是文档原来写的 `msg`(完整真形 `{code, data, message, success, traceId}`);如前端有按 `res.msg` 取提示文案的代码请自查(取不到值)。注意:**导入出参 errors/warnings 行内的 `msg` 是真实字段名,不受影响**。
|
||||
|
||||
## 1. 列表/详情新增字段
|
||||
|
||||
| 接口 | 新字段 | 说明 |
|
||||
|------|--------|------|
|
||||
| `GET /admin/fleet/vehicles/page` | `primaryDriverId`(String)/`primaryDriverName`/`primaryDriverLicenseType` | 常驻司机,无=null;司机未填准驾时也是 null(不会给空串) |
|
||||
| `GET /admin/fleet/vehicles/{vehicleId}` | `typeKey`(大类Key,原来文档有代码没返)/`primaryDriverLicenseType`/`primaryDriverYears`(驾龄·年) | 详情主司机块徽章数据源;驾龄未填=null(不会给 0) |
|
||||
|
||||
## 2. 批量导入出参示例(测试服实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"totalRows": 1, "insertedCount": 1, "updatedCount": 0, "errorCount": 0,
|
||||
"errors": [],
|
||||
"warnedCount": 1,
|
||||
"warnings": [ { "row": 2, "plate": "蒙A·T7777", "field": "insureDue", "msg": "保险不足 30 天" } ]
|
||||
},
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 契约细节订正(文档侧,代码行为如此)
|
||||
|
||||
- §2.2 详情「车辆不存在」错误码是 **600110**(原文档误写 404001,前端按 404001 匹配永不命中);
|
||||
- `seats` 恒随车型冗余(入参不收、传了忽略;型号座位数变更自动回刷已绑车辆)——原文档「可选,默认取自车型」有歧义;
|
||||
- 编辑(§2.4)入参必须带 `vehicleModelId`(原示例漏了,照抄会 400);附件保留项(带 id)可以不传 `mimeType` 了(原来会 400);**新增附件 mimeType 必填且白名单 jpeg/png/webp/pdf,不命中报 601005**(新校验);
|
||||
- 附件混插排序修复:编辑时新图插在保留图前面,落库顺序现在与入参数组顺序一致(原来新增项会被挤到末尾);
|
||||
- 601002/601003(类目/张数校验)确认废弃,类目走字典 `vehicle_attachment_category`;
|
||||
- 600109(有未完成派单禁删)为契约先行,守卫随派单模块落地,当前删除不拦。
|
||||
|
||||
## 4. 文档版本
|
||||
|
||||
FLEET API → **v1.5.51 + v1.5.53**,文档站已同步:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html(HOUSE/主文档的 msg→message 同步勘误见各自变更记录)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 【新增·管理后台】车型大类全量列表(不分页) GET /admin/fleet/vehicle-types/list
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3654(已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单(wx 2026-06-10 提出) | 日期: 2026-06-10
|
||||
> 影响范围: 管理后台车型下拉/筛选场景(新接口,纯增量)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
下拉/筛选要全量大类时不用再调分页接口拼页了:新接口一次返回**全部大类**(不分页、不含 models 数组),单条结构与 `GET /admin/fleet/vehicle-types/page` 的 records **完全一致**(含 `modelCount`/`inUseCount` 计数),按 sort_order 升序。
|
||||
|
||||
- 要型号树 → 用 `GET /admin/fleet/vehicle-types`(§1.1,大类+models 全量);
|
||||
- 要分页检索大类 → 用 `/page`(§1.5);
|
||||
- 只要大类轻量全量(下拉) → **用本接口**。
|
||||
|
||||
## 1. 接口定义
|
||||
|
||||
`GET /admin/fleet/vehicle-types/list` —— 入参:无
|
||||
|
||||
**响应示例**(测试服实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "id": "1929151481086734337", "typeKey": "suv2", "typeName": "SUV 越野", "icon": "🚙",
|
||||
"description": "高底盘 / 沙漠越野", "sortOrder": 1, "modelCount": 1, "inUseCount": 0 },
|
||||
{ "id": "1930561885327036417", "typeKey": "mpv", "typeName": "MPV 商务", "icon": "🚐",
|
||||
"description": "", "sortOrder": 2, "modelCount": 0, "inUseCount": 0 }
|
||||
],
|
||||
"message": "成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 文档
|
||||
|
||||
FLEET API → **v1.5.52** 新增 §1.7 小节(§1 章 10 端点),文档站已同步:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,242 @@
|
||||
# 【⚠️修改接口·管理后台】出行中取消旧路径下线:`cancel/on-trip` → `terminate` (#3556)
|
||||
|
||||
> **服务**: hl-order-service-v3 | **更新时间**: 2026-06-11
|
||||
> ⚠️ **破坏性变更(路径级)**:旧端点 `POST /v3/admin/order/{id}/cancel/on-trip` **已删除**,调用返回 404。
|
||||
> 📌 本篇专补「路径下线」这一环(前期 #3556 changelog 只写了新路径的入参/出参,未点明旧路径已废,导致前端联调时仍在调旧 URL 收到 404)。入参/出参完整改造见 #3556 那篇,本篇做自包含汇总。
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
「出行中取消」此前走 `POST /v3/admin/order/{id}/cancel/on-trip`。后端在终止行程改造时把该端点**重命名为 `POST /v3/admin/order/{id}/terminate`**,并改变了语义:
|
||||
|
||||
- 旧:出行中取消 → 订单直接进入 `CANCELLED`(已取消)。
|
||||
- 新:终止行程 → 订单进入 `COMPLETED`(已完成),再在核单子状态(`reviewStatus` / `flowStatus`)中流转结算,**不再直接 CANCELLED**。
|
||||
|
||||
后端代码里 `cancel/on-trip` 路径已**全量删除**(全仓 `git grep` 无残留),任何对旧路径的调用都会 404。前端「出行中取消」分支若仍指向旧 URL,必须改指新 URL。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 操作 | 旧 | 新 | 变更类型 |
|
||||
|---|------|----|----|----------|
|
||||
| 1 | 取消/终止 出行中订单 | `POST /v3/admin/order/{id}/cancel/on-trip` | `POST /v3/admin/order/{id}/terminate` | 路径下线 + 重命名 |
|
||||
| 2 | 退款预览(出行中) | (旧流程无独立预览,或复用 cancel-preview) | `POST /v3/admin/order/{id}/terminate/refund-preview` | 新增(详见 #3556 配套篇) |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 终止行程(替代出行中取消)
|
||||
|
||||
- **路径**:`POST /v3/admin/order/{id}/terminate`
|
||||
- **使用场景**:出行中订单提前终止,定制师调用。需先调「终止退款预览」拿资源清单。
|
||||
- **认证**:管理端 JWT(`Authorization` 头)
|
||||
- **幂等性**:是。订单一旦终止状态变为 `COMPLETED`,二次调用因状态非 `TRAVELLING` 返回 `581018`。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| id | String | ✅ | 订单 ID |
|
||||
|
||||
### 4.2 请求体(`OrderTerminateTripReqVO`)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||
|------|------|:----:|------|------|
|
||||
| cancelReason | String | ✅ | 终止原因(调整额说明也并入此处,不单列字段) | 非空 |
|
||||
| endDayNumber | Integer | ✅ | 停在第几天(截断点,1-based) | ≥ 1 |
|
||||
| rooms | Array<{refId, used}> | ✅ | 住宿各行已用判定 | 列表非 null |
|
||||
| tickets | Array<{refId, used}> | ✅ | 门票各行已用判定 | 列表非 null |
|
||||
| vehicles | Array<{refId, dayNumber, used}> | ✅ | 用车各行已用判定(同车跨天靠 dayNumber 区分) | 列表非 null |
|
||||
| adjustAmount | BigDecimal | ❌ | 人工调整额(±,正=多退 负=少退),默认 0 | — |
|
||||
|
||||
> `refId` 全部取自「终止退款预览」接口返回值。入参**只传 `used` 判定 + `adjustAmount`,不传任何金额单价**——退款额由后端按预览同源成交价权威重算(防篡改)。保险不在入参,后端自动按锁定不退处理。
|
||||
|
||||
**rooms / tickets 行(ResourceUsedItem)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| refId | String | ✅ | 配单记录 ID(住宿=house_hotel_assignment.id;门票=order_scenic_assignment.assignment_id) |
|
||||
| used | Boolean | ✅ | 是否已使用(true=已用不退 / false=未用可退) |
|
||||
|
||||
**vehicles 行(VehicleUsedItem)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| refId | String | ✅ | 配车记录 ID(同段车跨天 refId 相同) |
|
||||
| dayNumber | Integer | ✅ | 第几天(1-based,≥ 1) |
|
||||
| used | Boolean | ✅ | 当天该车是否已使用 |
|
||||
|
||||
## 5. 出参(`Result<OrderTerminateTripRespVO>`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| terminateRefundId | String | 终止退款主表 ID |
|
||||
| baselineRefund | BigDecimal | 系统建议基线 = max(0, paidAmount − usedAmount) |
|
||||
| adjustAmount | BigDecimal | 人工调整额(回显入参,默认 0) |
|
||||
| finalRefund | BigDecimal | 最终返还额 = max(0, baselineRefund + adjustAmount) |
|
||||
| settlementRefundId | String | 核单返还记录 ID(核单模块写入后回填) |
|
||||
| newStatus | String | 终止后订单粗状态(恒 `COMPLETED`) |
|
||||
| newFlowStatus | String | 终止后流程细状态(恒 `PENDING_REVIEW`) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 newStatus
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `COMPLETED` | 已完成 | 终止后订单进入完成态,转入核单流程(**注意:不再是旧的 CANCELLED**) |
|
||||
|
||||
### 6.2 newFlowStatus
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING_REVIEW` | 待核单 | 终止后等待核单录入与结算复核 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 581007 | 订单不存在 | id 无对应订单 |
|
||||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非 `TRAVELLING`(未出发 / 已终止 / 已完成时调用,含二次终止) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**请求**:
|
||||
```
|
||||
POST /v3/admin/order/60001234567890/terminate
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户中途高反送医终止;林芝段酒店已付全款不可退,调整 -300",
|
||||
"endDayNumber": 3,
|
||||
"rooms": [
|
||||
{"refId": "96011", "used": true},
|
||||
{"refId": "96014", "used": false}
|
||||
],
|
||||
"tickets": [
|
||||
{"refId": "97001", "used": true}
|
||||
],
|
||||
"vehicles": [
|
||||
{"refId": "98001", "dayNumber": 1, "used": true},
|
||||
{"refId": "98001", "dayNumber": 2, "used": true},
|
||||
{"refId": "98001", "dayNumber": 3, "used": true}
|
||||
],
|
||||
"adjustAmount": -300.00
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"success": true,
|
||||
"data": {
|
||||
"terminateRefundId": "9610000001",
|
||||
"baselineRefund": 2264.00,
|
||||
"adjustAmount": -300.00,
|
||||
"finalRefund": 1964.00,
|
||||
"settlementRefundId": "9620000001",
|
||||
"newStatus": "COMPLETED",
|
||||
"newFlowStatus": "PENDING_REVIEW"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(不填调整额,默认 0)
|
||||
|
||||
**请求(片段)**:
|
||||
```json
|
||||
{ "cancelReason": "客户主动终止", "endDayNumber": 5, "rooms": [], "tickets": [], "vehicles": [] }
|
||||
```
|
||||
**响应(片段)**:
|
||||
```json
|
||||
{ "code": 200, "data": { "baselineRefund": 0.00, "adjustAmount": 0.00, "finalRefund": 0.00, "newStatus": "COMPLETED", "newFlowStatus": "PENDING_REVIEW" } }
|
||||
```
|
||||
|
||||
### 8.3 异常 —— 调旧路径(前端联调踩的就是这个)
|
||||
|
||||
**请求**:
|
||||
```
|
||||
POST /v3/admin/order/60001234567890/cancel/on-trip
|
||||
```
|
||||
**响应**:
|
||||
```
|
||||
HTTP 404 Not Found
|
||||
```
|
||||
> 旧路径在后端已无任何映射,必须改调 `POST /v3/admin/order/{id}/terminate`。
|
||||
|
||||
### 8.4 异常 —— 订单非出行中
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用**:订单当前粗状态 = `TRAVELLING`(出行中)
|
||||
- ❌ **不适用**:未出发订单走「取消订单」流程(`cancel/pre-trip`);已终止 / 已完成订单二次调用返回 `581018`
|
||||
- ⚠️ `finalRefund` 最低为 0,不会出现负数;退款额进入核单返还,由财务复核放款;保险恒不退
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 路径与语义
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 路径 | `POST /{id}/cancel/on-trip` | `POST /{id}/terminate` |
|
||||
| 退款预览 | 复用取消预览(或无独立预览) | `POST /{id}/terminate/refund-preview`(独立,POST) |
|
||||
| 终止后订单状态 | `CANCELLED`(已取消) | `COMPLETED`(已完成)→ 进核单 `PENDING_REVIEW` |
|
||||
| 入参 VO | `OrderCancelOnTripReqVO` | `OrderTerminateTripReqVO` |
|
||||
| 出参 VO | `OrderCancelOnTripRespVO` | `OrderTerminateTripRespVO` |
|
||||
|
||||
### 10.2 入参字段级
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| cancelReason | ✅ 终止原因 | ✅ 终止原因(+ 调整说明并入) |
|
||||
| returnAmount | ✅ 手填返还额(必填) | ❌ **删除**(改由后端按资源核算) |
|
||||
| returnRemark | ✅ 返还备注(必填) | ❌ **删除**(说明并入 cancelReason) |
|
||||
| endDayNumber | — | ✅ **新增**,停在第几天 |
|
||||
| rooms / tickets / vehicles | — | ✅ **新增**,各资源行已用判定 |
|
||||
| adjustAmount | — | ✅ **新增**(可选),人工调整额 |
|
||||
|
||||
### 10.3 出参字段级
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| returnAmount | ✅ 返还额(回显) | ❌ **删除** |
|
||||
| settlementRefundId | ✅(旧实现恒 null) | ✅ 真实核单返还记录 ID |
|
||||
| newStatus | ✅(值 `已取消`) | ✅(值变 `COMPLETED`) |
|
||||
| terminateRefundId | — | ✅ **新增** |
|
||||
| baselineRefund / adjustAmount / finalRefund | — | ✅ **新增** 退款明细金额 |
|
||||
| newFlowStatus | — | ✅ **新增**(`PENDING_REVIEW`) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**:是。路径、入参、出参、终态全部变化。前端仍调旧路径 → 404;仍按旧 body → 校验失败。
|
||||
- **前端必须同步**:是。「出行中取消」分支需:① 改 URL 为 `/terminate`;② 改 body 为资源勾选模型;③ 终态判断从「已取消」改为「已完成→待核单」。
|
||||
- **回滚**:本路径下线在后端已合入主线(rename commit + #3557),如需回滚需后端 revert,前端无单独回滚动作。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- ⚠️ 旧路径 `cancel/on-trip` 在后端**无任何兼容映射**,不会做 301/308 跳转,直接 404。
|
||||
- 必须配合「终止退款预览」`POST /{id}/terminate/refund-preview` 使用:预览拿资源清单 → 前端本地按结束天标已用/可退 → 提交时只回传 `used` 判定 + `adjustAmount`。
|
||||
- 终止后订单是「已完成(待核单)」而非「已取消」,列表/详情的状态展示需按新枚举对齐。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#3556](https://git.1814.love:8443/wx/HL/issues/3556)
|
||||
- **入参/出参完整改造 changelog(同 Issue)**: `06_3556_终止行程接口改造-修改接口-管理后台.md` + `06_3556_终止退款预览-新增接口-管理后台.md`
|
||||
- **PR**: [#3557](https://git.1814.love:8443/wx/HL/pulls/3557)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 订单详情「出行中取消 / 终止行程」分支
|
||||
@@ -0,0 +1,47 @@
|
||||
# 【修改·管理后台】司机档案:详情脱敏修复、列表新增 7 字段(含赛季计数)、编辑不再必传手机/身份证、批量导入大修(CSV/警告档/局部更新)
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3678(已合并 dev-v3,**测试服已部署并 API 实测 12 项通过**) | 无关联工单 | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**司机档案**页面(`/admin/fleet/drivers/*` 6 端点)
|
||||
|
||||
## ⚠️ 关键说明(前端必看 5 点)
|
||||
|
||||
1. **详情两处脱敏变化(原来泄明文)**:`license.no`(驾照号)与 `emergency.phone`(紧急联系人手机)现在返回**脱敏值**(`150***********1234`/`138****9902`)。**编辑回显直接回传脱敏值即可**——后端识别含 `*` 自动跳过更新保持原值;要真改就传新明文。
|
||||
2. **编辑接口不用再传 phone/idCard 了**(原来 @NotBlank 必填,但前端只有脱敏值,按文档流程必 400):现在编辑可整体省略这两个字段(创建仍必填);传了也会被忽略(创建后不可改,口径不变)。
|
||||
3. **`relatedOrders` 不再返回假数据**:原来吐 3 条硬编码 mock 订单,现在恒返 `[]` + 新字段 `relatedOrdersReady: false`——请按 flag 显示「派单模块上线后展示关联订单」占位文案,**别再把旧假数据当真渲染**。
|
||||
4. **列表新增 7 个字段/参数**:出参 +`residentVehiclePlate`(权威常驻车,反查车侧)/`activeYears`(在册年份数组)/`pendingInvitedAt`/`blacklistReason`/顶层 **`seasonCounts`**(赛季 4 值全局计数,不随筛选变化,5 个 Tab 徽章直接用);入参 +`licenseExpireBefore`/`insuranceAnnualEndBefore` 两个到期被动筛。详情同步 +`residentVehiclePlate`/`genderLabel`/`blacklistReason`,且 `activeYearsJson`(字符串)**改名改型**为 `activeYears`(数字数组)。
|
||||
5. **keyword 语义修正**:姓名模糊 + **手机仅支持 11 位全号精确**(手机是加密列不支持模糊;原文档写「手机模糊」不准确)。
|
||||
|
||||
## 1. 校验行为变化
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| `season` 赛季 | **不再自由可写**:按转移白名单校验(在册→待续签/拉黑、待续签→在册/归档、归档→在册),非法跳转返 **100001**;**黑名单→在册(解封)编辑端点直接拦截**(走专用解封流程) |
|
||||
| `insurance.type=annual` | `policyNo`/`annualPremium`/`annualStart`/`annualEnd` 任一缺失返 **100001**(消息列明缺哪个);`none` 不限;`perTrip` 的保险计划选择待保险侧基础设施(暂仍走 `perDayRate` 手填) |
|
||||
| `insurance.company` | **字段已移除**(出入参都不再有保险公司字段,明细以保险单附件为准;`perDayRate` 保留) |
|
||||
| 附件 `mimeType` | 新增附件必填且白名单 jpeg/png/webp/pdf(违例 **601005**);编辑保留项(带 id)可省略 |
|
||||
| 参数错误码 | 文档原写 400001 的场景实际均为 **100001**(平台 `INVALID_PARAM`),文档已订正 |
|
||||
|
||||
## 2. 批量导入(`POST /admin/fleet/drivers/import`)
|
||||
|
||||
- **CSV 真支持**(原来上传 .csv 直接 500);解析失败/不支持的后缀统一返 **100001**(原来静默返回「成功 0 行」);
|
||||
- **更新已有司机(续签)= 15 个导入列的局部更新,空白列保持原值**——原来会把该司机**附件全软删、标签清空、年保起止清空**(已修复,放心重导);
|
||||
- **新增警告档**:出参 +`warnedCount`/`warnings[{row,name,field,msg}]`(驾照距到期不足 60 天仍导入但提醒);
|
||||
- 注意:导入新增 `保险类型=annual` 的司机会因缺保单子字段进 errors(模板无保单列),annual 新司机请走表单录入。
|
||||
|
||||
**实测响应示例**:
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "totalRows": 1, "insertedCount": 1, "renewedCount": 0, "errorCount": 0, "errors": [],
|
||||
"warnedCount": 1, "warnings": [ { "row": 2, "name": "导入测试乙", "field": "licenseExpire", "msg": "驾照不足 60 天" } ] }, "message": "成功" }
|
||||
```
|
||||
|
||||
## 3. 联动行为(无需前端动作)
|
||||
|
||||
- **软删司机自动解绑其常驻车**:该司机若是某车常驻司机,删除后车辆的 `primaryDriverId` 自动清空(测试服实测生效);
|
||||
- 编辑出参为 `{ "kept", "inserted", "softDeleted", "tagsReplaced" }`(文档原写 attachmentKept 等为误,已按代码订正)。
|
||||
|
||||
## 4. 文档
|
||||
|
||||
FLEET API → **v1.5.54**(§3 全节契约订正 + §14.4 错误码注册表 600205 错位修正),文档站:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,49 @@
|
||||
# 【修改·管理后台】车辆档案:行驶证正/副页图片迁入附件体系,`regCertFrontUrl`/`regCertBackUrl` 字段移除
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087) + hl-user-service(字典种子)
|
||||
> PR: #3679(已合并 dev-v3,**测试服双服务已部署并 API 实测通过**) | 无关联工单(wx 2026-06-11 拍定) | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**车辆档案**新增/编辑表单与详情页的行驶证图片区块
|
||||
|
||||
## ⚠️ 关键说明(前端必改 2 处)
|
||||
|
||||
行驶证正反面图片本质是附件(纯展示、无业务逻辑消费),从车辆主表字段迁入统一附件体系:
|
||||
|
||||
1. **表单(§2.3 新增/§2.4 编辑)**:`regCertFrontUrl`/`regCertBackUrl` 两个独立字段**已移除**(传了会被忽略)。行驶证图片改放 `attachments` 数组,类目:
|
||||
- `reg_cert_front` = 行驶证正面(上限 1 张)
|
||||
- `reg_cert_back` = 行驶证副页(上限 1 张)
|
||||
- 类目从字典 `GET /admin/dict/data/vehicle_attachment_category` 下发(现 8 类,remark=张数上限),编辑走既有 diff 语义(带 id 保留/无 id 新增/未传软删)。
|
||||
2. **详情(§2.2)**:不再返回 `regCertFrontUrl`/`regCertBackUrl`,从 `attachments[category=reg_cert_front/reg_cert_back]` 读取。
|
||||
|
||||
**不变**:行驶证**文本**字段(`regCertNo` 行驶证号/`regCertOwner` 所有人/`regCertUsage` 使用性质)仍是主表结构化字段,表单/详情/批量导入照旧。
|
||||
|
||||
## 1. 实测示例(测试服)
|
||||
|
||||
新增车辆请求(行驶证图随 attachments 提交):
|
||||
|
||||
```json
|
||||
{
|
||||
"plate": "蒙A-66666", "vehicleModelId": "<id>", "fleet": "own",
|
||||
"attachments": [
|
||||
{ "category": "reg_cert_front", "url": "https://oss.../regfront.jpg", "mimeType": "image/jpeg", "sortNo": 0 },
|
||||
{ "category": "reg_cert_back", "url": "https://oss.../regback.jpg", "mimeType": "image/jpeg", "sortNo": 0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详情 `attachments` 实测返回(节选):
|
||||
|
||||
```json
|
||||
"attachments": [
|
||||
{ "id": "...", "category": "reg_cert_front", "url": "https://oss.../regfront.jpg", "mimeType": "image/jpeg", "sortNo": 0 },
|
||||
{ "id": "...", "category": "reg_cert_back", "url": "https://oss.../regback.jpg", "mimeType": "image/jpeg", "sortNo": 0 }
|
||||
]
|
||||
```
|
||||
|
||||
## 2. 存量数据
|
||||
|
||||
已有车辆的两列历史值由 Flyway 自动迁为对应类目附件行(测试服已执行),前端无需任何兼容处理。
|
||||
|
||||
## 3. 文档
|
||||
|
||||
FLEET API → **v1.5.55** / DB → **v1.5.22**,文档站:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
date: 2026-06-11
|
||||
type: api-breaking-rename
|
||||
module: hl-order-service-v3/insurance
|
||||
priority: high
|
||||
status: merged-pending-deploy
|
||||
restart_service: hl-order-service-v3
|
||||
端类型: 管理后台
|
||||
breaking_change: true
|
||||
gitea_issue: 3681
|
||||
gitea_pr: 3682
|
||||
---
|
||||
|
||||
# ⚠️ 破坏性:保险订单列表接口路径 `/orders` 改名为 `/list`
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
管理后台保险前端全量切 v3 后,保险页整体 404。排查实测:网关路由通、order-service-v3 健康、Nacos 注册正常;真因是**前端调 `GET /v3/admin/insurance/list`,而后端原路径是 `GET /v3/admin/insurance/orders`**,路径对不上。本次后端把列表接口路径改名为 `/list` 对齐前端。
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 接口 | 旧路径(已下线) | 新路径 | 方法 |
|
||||
|------|------------------|--------|------|
|
||||
| 保险订单列表(分页) | `~~/v3/admin/insurance/orders~~` | `/v3/admin/insurance/list` | GET |
|
||||
|
||||
> **不加别名**:旧 `/orders` 路径**已删除**,再调用返回 404。
|
||||
> **未受影响**(仍用 `/orders` 前缀,不要误改):
|
||||
> - `GET /v3/admin/insurance/orders/{id}`(保险订单详情)
|
||||
> - `GET /v3/admin/insurance/orders/by-order/{orderId}`(按业务订单查保险列表)
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
- **路径**:`GET /v3/admin/insurance/list`
|
||||
- **作用**:分页查询保险订单列表
|
||||
- **入参 / 出参与原 `/orders` 完全一致,仅路径变化**
|
||||
|
||||
## ④ 入参(query,InsuranceQueryRequest)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | Long | 否 | 关联业务订单 ID |
|
||||
| `policyNo` | String | 否 | 保单号 |
|
||||
| `status` | String | 否 | 保险状态,枚举见 ⑥ |
|
||||
| `insuredName` | String | 否 | 被保人姓名 |
|
||||
| `insuranceProductId` | Long | 否 | 保险产品 ID |
|
||||
| `pageNo` / `pageSize` | Integer | 否 | 标准分页参数 |
|
||||
|
||||
## ⑤ 出参(PageResult<InsuranceOrderVO>)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `insuranceOrderId` | Long | 保险订单 ID |
|
||||
| `orderId` | Long | 关联业务订单 ID |
|
||||
| `planId` | Long | 保险计划 ID |
|
||||
| `extOrderNo` | String | 保游网订单号 |
|
||||
| `extPolicyNo` | String | 保单号(保游网保单号) |
|
||||
| `policyNo` | String | 兼容字段,与 extPolicyNo 同值,**后续废弃,前端请改用 extPolicyNo** |
|
||||
| `productName` / `planName` | String | 保险产品 / 计划名称 |
|
||||
| `premium` | BigDecimal | 保费金额(元) |
|
||||
| `insuredCount` | Integer | 被保人数 |
|
||||
| `coverageStartDate` / `coverageEndDate` | LocalDate | 保障起止日期 |
|
||||
| `status` / `statusLabel` | String | 保险状态 / 状态标签 |
|
||||
| `policyPdfUrl` | String | 保单 PDF 地址(INSURED 状态异步回填,未回填为 null) |
|
||||
| `createTime` | LocalDateTime | 投保时间 |
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
`status` 保险状态:
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `PENDING` | 待生效 |
|
||||
| `EFFECTIVE` | 生效中 |
|
||||
| `EXPIRED` | 已过期 |
|
||||
| `CANCELLED` | 已取消 |
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 场景 | 响应 |
|
||||
|------|------|
|
||||
| 调用已下线的旧路径 `/orders` | **404 Not Found**(路由不存在) |
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型(新路径)
|
||||
|
||||
```
|
||||
GET /v3/admin/insurance/list?pageNo=1&pageSize=20&status=EFFECTIVE
|
||||
→ 200 { "code":200, "data": { "list":[...], "total":..., "pageNo":1, "pageSize":20 } }
|
||||
```
|
||||
|
||||
### 异常(调旧路径,已下线)
|
||||
|
||||
```
|
||||
GET /v3/admin/insurance/orders?pageNo=1&pageSize=20
|
||||
→ 404 (旧路径已删除,必须改用 /list)
|
||||
```
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 仅列表接口改名;详情、按订单查两个接口路径不变。
|
||||
- 入参出参字段、分页结构与改名前完全相同。
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| 维度 | 改名前 | 改名后 |
|
||||
|------|--------|--------|
|
||||
| 列表路径 | `GET /v3/admin/insurance/orders` | `GET /v3/admin/insurance/list` |
|
||||
| 旧路径可用性 | — | **下线,调用 404** |
|
||||
| 入参 / 出参 | InsuranceQueryRequest / PageResult<InsuranceOrderVO> | **不变** |
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- 前端**必须**把列表请求从 `/orders` 改为 `/list`,否则继续 404。
|
||||
- 无后端内部调用方(已 grep mp-service / 其它服务零引用),仅前端直调。
|
||||
- 回滚:如需回滚,把 Controller `@GetMapping("/list")` 改回 `/orders` 即可(无数据变更)。
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- ⚠️ 别把详情 `/orders/{id}` 和按订单 `/orders/by-order/{orderId}` 也改了——这两个**保持 `/orders` 前缀不变**。
|
||||
- 部署后需重启 `hl-order-service-v3`。
|
||||
- 待前端确认:若 `/list` 实际想要的是「保险产品列表」而非「保险订单分页」,需另改 `/products`(本次按订单分页处理)。
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/3681
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3682
|
||||
- 代码:`AdminInsuranceController.java`(`@GetMapping("/list")`)
|
||||
- 负责人:腰苏图
|
||||
@@ -0,0 +1,51 @@
|
||||
# 【新增·管理后台】车辆/司机批量导入:新增「下载导入模板」接口 ×2,车辆导入支持直接绑常驻司机
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3687(已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单(wx 2026-06-11 拍定) | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**车辆档案**与**司机档案**的批量导入功能
|
||||
|
||||
## ⚠️ 关键说明(前端必看 3 点)
|
||||
|
||||
1. **「下载模板」按钮有后端接口了**(原口径「前端静态资源承载模板」作废):
|
||||
- `GET /admin/fleet/vehicles/import/template` → 车辆导入模板.xlsx
|
||||
- `GET /admin/fleet/drivers/import/template` → 司机导入模板.xlsx
|
||||
- **返回 .xlsx 文件流**(`Content-Disposition: attachment`,**不是 Result JSON 包络**),axios 请用 `responseType: 'blob'` + Authorization 头,下载后另存。
|
||||
2. **模板是双 sheet**:Sheet1 数据页只有表头行(与后端解析逐字一致,提示用户勿改表头;特意不放示例行,防止示例数据被原样导入);Sheet2「填写说明」逐列列出必填/格式/字典取值,前端无需再自造说明文案。
|
||||
3. **车辆导入新增第 12 列「常驻司机手机号」**(可选):按手机号反查司机直接绑定常驻(严格 1:1)。司机不存在 → 该行报错「常驻司机手机号未找到对应司机」;司机已是别车常驻 → 该行报错(605022 文案);**更新已有车时该列空白=保持原绑定(不会解绑)**。
|
||||
|
||||
## 1. 接口契约
|
||||
|
||||
| 接口 | 方法 | 返回 |
|
||||
|---|---|---|
|
||||
| `/admin/fleet/vehicles/import/template` | GET | xlsx 文件流(12 列表头 + 填写说明 sheet) |
|
||||
| `/admin/fleet/drivers/import/template` | GET | xlsx 文件流(15 列表头 + 填写说明 sheet) |
|
||||
|
||||
实测响应头(测试服):
|
||||
|
||||
```
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
Content-Disposition: attachment;filename="%E8%BD%A6%E8%BE%86%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx"
|
||||
```
|
||||
|
||||
## 2. 车辆导入带常驻司机实测(测试服)
|
||||
|
||||
CSV 行 `蒙A-TPL99,<modelId>,own,company,,,,,,,营运租赁,13977665544` 导入后,车辆详情:
|
||||
|
||||
```json
|
||||
{ "plate": "蒙A-TPL99", "primaryDriverId": "2064920199034810370", "primaryDriverName": "模板测试司机" }
|
||||
```
|
||||
|
||||
反例(手机号无司机)实测:
|
||||
|
||||
```json
|
||||
{ "errorCount": 1, "errors": [ { "row": 2, "plate": "蒙A-TPL98", "msg": "常驻司机手机号未找到对应司机: 13900009999" } ] }
|
||||
```
|
||||
|
||||
## 3. 顺带勘误
|
||||
|
||||
司机导入表头「性别」列此前文档/Swagger 误写成「性别(男/女/未知)」——**实际解析 key 就是「性别」两个字**,照旧写法该列整列取不到值。以模板端点下发的表头为准(模板与解析同源,不会再漂移)。
|
||||
|
||||
## 4. 文档
|
||||
|
||||
FLEET API → **v1.5.56**(§2.6a/§3.6a 新端点 + §2.6 12 列契约),文档站:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,49 @@
|
||||
# 【新增·管理后台】车辆档案:生命周期状态 serviceStatus(在役/停用)+停用/启用接口,个人车随常驻司机赛季自动联动
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3690(已合并 dev-v3,**测试服已部署并 API 实测 11 步全过**) | 无关联工单(wx 2026-06-11 拍定) | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**车辆档案**列表/详情/操作列
|
||||
|
||||
## ⚠️ 关键说明(前端必看 4 点)
|
||||
|
||||
1. **列表/详情新增字段 `serviceStatus`**:`ACTIVE`(在役) / `DISABLED`(停用)。这是**生命周期状态**,与占用态 `vehicleStatus`(idle/busy/maint)**正交**——停用的车不该出现在派单候选里,列表建议给停用车置灰/标签。
|
||||
2. **新增两个操作接口**(建议放车辆行操作列,仅对**公司车 ownerType=company** 展示):
|
||||
- `POST /admin/fleet/vehicles/{vehicleId}/disable` 停用
|
||||
- `POST /admin/fleet/vehicles/{vehicleId}/enable` 启用
|
||||
- 幂等(重复点不报错);出参 `data: null`。
|
||||
3. **个人车(ownerType=personal)不能手动停用/启用**:调了返回业务码 **600111**「个人自带车生命周期随常驻司机自动联动,不支持手动停用/启用」。**前端对 personal 车请隐藏/禁用这两个按钮**。个人车的 serviceStatus 完全自动:车主常驻司机在册(active)→车在役;司机归档/拉黑→车自动停用;司机回归→车自动恢复(实测全过,见下)。
|
||||
4. **列表新增筛选参数 `serviceStatus`**(可选,`ACTIVE`/`DISABLED`,不传=全部)。
|
||||
|
||||
## 1. 实测示例(测试服)
|
||||
|
||||
个人车随司机赛季联动(实测序列):
|
||||
|
||||
| 司机赛季操作 | 车 serviceStatus |
|
||||
|---|---|
|
||||
| 建个人车绑常驻司机(司机在册) | `ACTIVE` |
|
||||
| 司机 在册→待续签(pending) | `ACTIVE`(不变) |
|
||||
| 司机 待续签→归档 | `DISABLED`(自动停用) |
|
||||
| 司机 归档→在册 | `ACTIVE`(自动恢复) |
|
||||
| 司机 在册→黑名单 | `DISABLED`(自动停用) |
|
||||
|
||||
公司车手动:
|
||||
|
||||
```
|
||||
POST /admin/fleet/vehicles/{id}/disable → { "code": 200, "data": null, "message": "成功" }
|
||||
GET /admin/fleet/vehicles/{id} → { ..., "serviceStatus": "DISABLED" }
|
||||
```
|
||||
|
||||
个人车手动(反例实测):
|
||||
|
||||
```json
|
||||
{ "code": 600111, "message": "个人自带车生命周期随常驻司机自动联动,不支持手动停用/启用" }
|
||||
```
|
||||
|
||||
## 2. 存量数据
|
||||
|
||||
Flyway 自动加列,存量车全部默认 `ACTIVE`(在役),前端无需兼容处理。
|
||||
|
||||
## 3. 文档
|
||||
|
||||
FLEET API → **v1.5.57**(§2.5a 新端点 + §2.1/§2.2 字段),文档站:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
date: 2026-06-11
|
||||
type: dict-data
|
||||
module: hl-user-service/sys_dict + insurance
|
||||
priority: medium
|
||||
status: merged-deployed
|
||||
restart_service: hl-user-service(已部署并实测通过)
|
||||
端类型: 管理后台
|
||||
breaking_change: false
|
||||
gitea_pr: 3692
|
||||
---
|
||||
|
||||
# 保险状态字典 `insurance_status` 补 INSURING(出单中) + 标签对齐列表措辞
|
||||
|
||||
> 关联:保游 4.2 异步出单整改(changelog `10_3642`)引入 INSURING 状态后,字典一直没补;本次为纯字典数据变更,**后端已部署测试服并实测通过,前端零代码改动**(下拉走字典接口自动生效)。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **前端不用改代码**:保险订单管理页筛选下拉走 `GET /admin/dict/data/insurance_status`,刷新页面即可看到新选项(字典接口有 5 分钟 Redis 缓存,测试服已手动清过,立即生效)。
|
||||
2. **下拉标签措辞变了**:原字典标签(待投保/已投保/已取消/失败)与列表行 `statusLabel`(待出单/已承保/已退保/投保失败)是两套话术,本次统一为列表行同款措辞。若有截图/文案依赖旧措辞请同步。
|
||||
3. **筛选 INSURING 直接可用**:列表接口 `GET /v3/admin/insurance/list` 的 `status` 入参无白名单校验,传 `INSURING` 即筛出出单中保单。
|
||||
|
||||
## 1. 字典终态(共 5 项,按状态生命周期排序)
|
||||
|
||||
| dict_value | dict_label(旧 → 新) | sort_order(旧 → 新) | 说明 |
|
||||
|------------|----------------------|----------------------|------|
|
||||
| PENDING | 待投保 → **待出单** | 2 → 1 | 已创建未发起投保 |
|
||||
| **INSURING** | **出单中(新增)** | — → 2 | 保游 4.2 异步出单窗口期,等回调落定 |
|
||||
| INSURED | 已投保 → **已承保** | 1 → 3 | 出单成功 |
|
||||
| CANCELLED | 已取消 → **已退保** | 3 → 4 | 已撤单/退保 |
|
||||
| FAILED | 失败 → **投保失败** | 4 → 5 | 出单失败(可手动重投) |
|
||||
|
||||
## 2. 验证示例(测试服实测)
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/admin/dict/data/insurance_status" \
|
||||
-H "Authorization: Bearer {admin_token}"
|
||||
```
|
||||
|
||||
响应(节选 data,字段同字典接口通用结构):
|
||||
|
||||
```json
|
||||
[
|
||||
{ "dictValue": "PENDING", "dictLabel": "待出单", "sortOrder": 1 },
|
||||
{ "dictValue": "INSURING", "dictLabel": "出单中", "sortOrder": 2 },
|
||||
{ "dictValue": "INSURED", "dictLabel": "已承保", "sortOrder": 3 },
|
||||
{ "dictValue": "CANCELLED", "dictLabel": "已退保", "sortOrder": 4 },
|
||||
{ "dictValue": "FAILED", "dictLabel": "投保失败", "sortOrder": 5 }
|
||||
]
|
||||
```
|
||||
|
||||
筛选出单中保单:
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/v3/admin/insurance/list?status=INSURING&page=1&pageSize=10" \
|
||||
-H "Authorization: Bearer {admin_token}"
|
||||
```
|
||||
|
||||
(前端按现有 `searchForm.status` 传参即可,无需任何调整。)
|
||||
|
||||
## 3. 后端附带变更(与前端无关,备查)
|
||||
|
||||
- 新增 Flyway `V20260611_002`(hl-user-service):整字典幂等补种 + 存量行收敛(原种子只在手工脚本 `sql/dict_business_enums.sql`,未纳入 Flyway,新环境会缺整个字典)。
|
||||
- `InsuranceQueryRequest.status` 的 Swagger 注释由过时值集(PENDING/EFFECTIVE/EXPIRED/CANCELLED)更正为真实值集(PENDING/INSURING/INSURED/CANCELLED/FAILED),v2/v3 双份,纯文档无行为变化。
|
||||
@@ -0,0 +1,45 @@
|
||||
# 【修复·管理后台】车务:司机/车辆列表日期筛选参数报错修复 + 车型大类图标长度放宽 64
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-fleet-service(8087)
|
||||
> PR: #3694 + #3695(文档版本记录)(均已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单(用户即时报修) | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**司机档案/车辆档案列表筛选** + **车型大类新增/编辑**
|
||||
|
||||
## ⚠️ 关键说明(前端必看 2 点)
|
||||
|
||||
1. **列表日期筛选现在能用了(之前一传就报错)**:司机列表 `licenseExpireBefore`/`insuranceAnnualEndBefore`、车辆列表 `insureDueBefore`/`inspectDueBefore` 四个 GET 日期参数,此前传 `2026-07-11` 这类标准格式直接报 `Failed to convert ... LocalDate`(后端缺日期绑定注解),现已修复。**格式仍是 `yyyy-MM-dd`,请求/响应形状零变化,前端无需改任何代码**,之前因报错被屏蔽的筛选交互可以放开。
|
||||
2. **车型大类 `icon` 上限 8→64 字符**:之前传图标组件名(如 `BusOutline`=10 字符)被 400「图标长度不能超过 8」拒(原按单 emoji 设计),现在最长 64,**组件名或 emoji 都可以**;超 64 报 400「图标长度不能超过 64」。
|
||||
|
||||
## 1. 日期筛选修复(4 个参数)
|
||||
|
||||
| 接口 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `GET /admin/fleet/drivers/page` | `licenseExpireBefore` | 驾照到期日 ≤ 该日(含),yyyy-MM-dd |
|
||||
| `GET /admin/fleet/drivers/page` | `insuranceAnnualEndBefore` | 年保到期日 ≤ 该日(含),仅 annual 行命中 |
|
||||
| `GET /admin/fleet/vehicles/page` | `insureDueBefore` | 保险到期日 ≤ 该日(含)——同根因一并修,此前同样报错 |
|
||||
| `GET /admin/fleet/vehicles/page` | `inspectDueBefore` | 年检到期日 ≤ 该日(含)——同上 |
|
||||
|
||||
测试服实测(用户原始报错 URL):
|
||||
|
||||
```
|
||||
GET /admin/fleet/drivers/page?page=1&pageSize=24&season=active&insuranceAnnualEndBefore=2026-07-11&licenseExpireBefore=2026-08-10
|
||||
→ {"code":200,"message":"成功","data":{"records":[],"total":0,...}}
|
||||
// 不再报 Failed to convert property value of type 'java.lang.String' to 'java.time.LocalDate'
|
||||
```
|
||||
|
||||
## 2. 车型大类 icon 放宽
|
||||
|
||||
`POST /admin/fleet/vehicle-types` 与 `PUT /admin/fleet/vehicle-types/{typeId}` 入参 `icon`:≤8 → **≤64**(DB 列同步 `VARCHAR(8)→VARCHAR(64)`,Flyway `V20260611_004` 纯加宽,存量 emoji 图标无损)。
|
||||
|
||||
测试服实测(用户原始报错请求体):
|
||||
|
||||
```
|
||||
PUT /admin/fleet/vehicle-types/2064609312168083458
|
||||
{"typeKey":"mpv","typeName":"商务车","icon":"BusOutline","description":"高级团","sortOrder":99}
|
||||
→ {"code":200,"message":"成功","data":{"id":"2064609312168083458","typeKey":"mpv","typeName":"商务车","icon":"BusOutline","description":"高级团","sortOrder":99,...}}
|
||||
// 列表回读 icon="BusOutline" 一致
|
||||
```
|
||||
|
||||
## 3. 文档版本
|
||||
|
||||
FLEET API → **v1.5.58** / FLEET DB → **v1.5.25**,文档站已同步:https://web.test.1814.love:9443/hl-docs/order-v3/api/API-SPEC-FLEET-V1.5.html
|
||||
@@ -0,0 +1,25 @@
|
||||
# 【修复·管理后台】保险:保游网余额查询「URI is not absolute」报错修复(配置补齐+错误提示改清晰)
|
||||
|
||||
> 存放目录: changelogs-v2/2026-06/
|
||||
> 涉及服务: hl-order-service-v3(8086)
|
||||
> PR: #3696(已合并 dev-v3,**测试服已部署并 API 实测通过**) | 无关联工单(用户即时报修) | 日期: 2026-06-11
|
||||
> 影响范围: 管理后台**保险管理**(`/v3/admin/insurance/*`),另预防性补齐了合同/支付同类配置
|
||||
|
||||
## ⚠️ 关键说明(前端必看 2 点)
|
||||
|
||||
1. **余额查询恢复正常,前端无需改任何代码**:`GET /v3/admin/insurance/balance` 此前报「保游网余额查询失败: URI is not absolute」,根因是保险模块迁二期时测试环境配置漏迁(非代码 bug),已补齐并实测恢复;同因风险面(产品同步/投保/退保/充值/保单下载等所有走保游网的接口)一并解除。
|
||||
2. **错误文案变化(仅异常场景)**:今后若再出现接入配置缺失,返回新错误码 **540411**「保游网接入未配置(baoyou.api.base-url),请检查 Nacos 配置」,不会再出现晦涩的 "URI is not absolute";前端无需针对性处理,照常展示 message 即可。
|
||||
|
||||
## 1. 实测结果(测试服网关)
|
||||
|
||||
```
|
||||
GET /v3/admin/insurance/balance
|
||||
→ {"code":200,"message":"成功","data":0} // 0 = 保游测试账户(apitest251208)真实余额
|
||||
|
||||
GET /v3/admin/insurance/products?page=1&pageSize=5
|
||||
→ {"code":200,"message":"成功","data":{"total":10,...}}
|
||||
```
|
||||
|
||||
## 2. 一并预防性补齐(无行为变化,仅消除同类隐患)
|
||||
|
||||
二期 `hl-order-service-v3` 测试环境同批补齐了以下与一期同源同值的配置段,对应模块此前同样存在「一调就报 URI is not absolute」的隐患:微信支付(wechat.pay)、12301 电子合同(contract.platform)、腾讯电子签(tencent-esign)、保单 PDF 公网域名(oss.domain)、保险出单主体(insurance.entities)。前端如之前在二期页面遇到过合同/支付类「URI is not absolute」报错,可以重试。
|
||||
@@ -0,0 +1,83 @@
|
||||
# 【新增接口·管理后台】司机档案——车管一键批量续签(换季)
|
||||
|
||||
> 服务:hl-fleet-service(8087) | 分支:dev-v3 | PR:#3699 | 已部署测试服并实测通过(2026-06-11)
|
||||
> 契约出处:FLEET API §3.7 | 前端触发位:司机档案列表页「批量续签」按钮(换季时用)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **一键换季动作**:把指定司机(或全部在册 `season=active` 司机)批量转入续签流程——`active → pending` + 为每人生成续签 H5 邀请链接/二维码。**链接需车管手动转发给司机**(本期无自动推送)。
|
||||
2. **非在册司机不报错**:`pending/archived/blacklist` 的司机自动跳过,进 `skipped[]` 给原因,不中断整批。
|
||||
3. `renewSeasonLabel` 必填但**不落库**,仅入审计日志;`remark` 同理。
|
||||
4. 防双击:同参数 3 秒内重复提交返「批量续签处理中,请勿重复提交」。
|
||||
|
||||
## 1. 新增端点
|
||||
|
||||
`POST /admin/fleet/drivers/batch-season-renew`
|
||||
|
||||
### 入参(body)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| driverIds | string[] | 否 | 目标司机ID列表(雪花字符串);**缺省/空数组 = 对全部 season=active 司机全量换季**;含不存在的 ID 整批报 100001 |
|
||||
| renewSeasonLabel | string | 是 | 续签目标赛季标识(如 `2027`);缺失返 400 |
|
||||
| remark | string | 否 | 批量动作备注(入审计日志) |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://api.test.1814.love:9443/admin/fleet/drivers/batch-season-renew" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"driverIds":["2065002428792492033"],"renewSeasonLabel":"2027","remark":"2027 赛季换季"}'
|
||||
```
|
||||
|
||||
### 响应示例(测试服实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"matchedCount": 1,
|
||||
"transitionedCount": 1,
|
||||
"inviteGeneratedCount": 1,
|
||||
"invites": [
|
||||
{
|
||||
"driverId": "2065002428792492033",
|
||||
"driverName": "张师傅",
|
||||
"token": "e4dc8e393bc54224aeb97a346611cb58",
|
||||
"url": "https://h5.hl-fleet.com/onboard?token=e4dc8e393bc54224aeb97a346611cb58",
|
||||
"qrCodeUrl": "data:image/png;base64,iVBORw0KGgo...",
|
||||
"expireAt": "2026-06-25T17:25:20"
|
||||
}
|
||||
],
|
||||
"skipped": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 出参字段 | 说明 |
|
||||
|---|---|
|
||||
| matchedCount | 匹配司机数 |
|
||||
| transitionedCount | 成功转入续签(active→pending)数 |
|
||||
| inviteGeneratedCount | 生成续签邀请数(同事务恒等于 transitionedCount) |
|
||||
| invites[] | 「待发列表」:driverId/driverName/token/url(H5 链接,`?token=` 参数)/qrCodeUrl(**二维码 Base64 DataURL,前端可直接 img src 渲染**)/expireAt(14 天有效) |
|
||||
| skipped[] | 跳过明细:driverId + reason(如 `season=pending≠active(非在册,跳过)`) |
|
||||
|
||||
### 非 active 司机被跳过(实测)
|
||||
|
||||
```json
|
||||
{ "matchedCount": 1, "transitionedCount": 0, "inviteGeneratedCount": 0, "invites": [],
|
||||
"skipped": [ { "driverId": "2065002428792492033", "reason": "season=pending≠active(非在册,跳过)" } ] }
|
||||
```
|
||||
|
||||
## 2. 错误码
|
||||
|
||||
| code | 触发场景 |
|
||||
|---|---|
|
||||
| 400 | renewSeasonLabel 缺失/空白(平台参数校验统一口径) |
|
||||
| 100001 | driverIds 含不存在的司机 ID(消息列出缺失 ID,整批不执行) |
|
||||
|
||||
## 3. 联动效果(前端无需额外调用)
|
||||
|
||||
- 司机列表 §3.1 的 `season` 变 `pending`、`pendingInvitedAt` 有值(卡片可显示「续签链接已发送·{日期}」);
|
||||
- 每位司机自动生成一条 mode=renew 的待审核记录(司机打开链接走 §4 续签 3 步流程,与单个生成链接 §4.1 完全同机制)。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 【新增接口+入参变更·管理后台】司机险走保游网——档案页投保/保单/退保 + 保险块字段变更
|
||||
|
||||
> 服务:hl-fleet-service(8087) + hl-order-service-v3(8086) | 分支:dev-v3 | PR:#3700 | 已部署测试服并实测通过(2026-06-11)
|
||||
> 契约出处:FLEET API §3.3/§3.4 保险块 + §13.7 | 业务拍板:司机险由车管在**司机档案页手动点「投保」**出单(非派车自动、非定时);保险提供方走 **order-v3**
|
||||
|
||||
## ⚠️ 关键说明(含 2 处破坏性变更)
|
||||
|
||||
1. **🔴 破坏性①——司机保存入参 `insurance.perDayRate` 字段删除**(§3.3 新增/§3.4 编辑):perTrip 日费率改走保游网计划费率,不再手填。前端表单请移除该输入项;继续传该字段会被忽略。
|
||||
2. **🔴 破坏性②——`insurance.type=perTrip` 时 `insurancePlanId` 必填**(返 100001):从「司机可选保险计划下拉」(下方接口 1)选计划。`annual`/`none` 传 null;类型从 perTrip 切走时后端自动清空该字段。
|
||||
3. 司机详情 §3.2 `insurance` 块新增回显 `insurancePlanId`(perTrip 有值,字符串雪花)。
|
||||
4. **投保动作会真实调保游网出单、从保游账户真实扣费**——前端联调请只调下拉/保单列表/校验反例,出单正向请约定后用测试计划操作。
|
||||
5. 计划下拉数据源 = 运营在「保险计划打标」端点把计划标为 `DRIVER`/`BOTH`(接口 5,order-v3 管理端);**新同步计划默认 CUSTOMER,司机端不可见**,须显式打标。
|
||||
|
||||
## 1. 司机可选保险计划下拉
|
||||
|
||||
`GET /admin/fleet/drivers/insurance/plan-options`
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": [
|
||||
{ "planId": "2054773833342451714", "planName": "10万计划", "productName": "山河令(太保山东新)", "usageCategory": "DRIVER" }
|
||||
], "message": "成功" }
|
||||
```
|
||||
|
||||
## 2. 司机险投保(档案页「投保」按钮)
|
||||
|
||||
`POST /admin/fleet/drivers/{driverId}/insurance/purchase`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| planId | string(雪花) | 是 | 司机专用计划(接口 1 选取) |
|
||||
| coverageStartDate / coverageEndDate | string(yyyy-MM-dd) | 是 | 保障起止;**「按赛季」「按年」快捷预填由前端实现**(仍可手改);起>止返 100001 |
|
||||
| remark | string | 否 | 缺省后端填「司机险投保: {司机姓名}」 |
|
||||
|
||||
成功返回保单(被保人证件号已脱敏;金额/雪花均字符串):
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": {
|
||||
"insuranceOrderId": "1934567890123456789", "policyNo": "...", "extPolicyNo": "BY-2026-...",
|
||||
"extOrderNo": "...", "totalPremium": "88.00", "status": "INSURED", "statusLabel": "已投保",
|
||||
"coverageStartDate": "2026-07-01", "coverageEndDate": "2027-06-30",
|
||||
"insuredPersons": [ { "name": "周师傅", "idCardNo": "1502**********0017" } ] } }
|
||||
```
|
||||
|
||||
> 异步出单型产品返回 `status=INSURING`(投保中,`extPolicyNo` 暂空),保游回调落定后翻 `INSURED`/`FAILED`,刷新保单列表即可。
|
||||
|
||||
## 3. 司机保单列表
|
||||
|
||||
`GET /admin/fleet/drivers/{driverId}/insurance/policies` → `data` 为接口 2 同构对象数组(createTime 倒序;无保单返 `[]`)。
|
||||
|
||||
## 4. 司机险退保(仅人工误投时用)
|
||||
|
||||
`POST /admin/fleet/drivers/{driverId}/insurance/policies/{insuranceOrderId}/cancel`
|
||||
|
||||
- 保单不属于该司机 → 100001(越权守卫);仅 `INSURED/INSURING` 可退,否则 540202。
|
||||
- 业务拍板:**取消派车不退保、换司机投新单旧单到期自然失效、离职拉黑不自动退**——本端点只服务误投撤销。
|
||||
|
||||
## 5. 保险计划打标(order-v3 管理端·运营用)
|
||||
|
||||
`PUT /v3/admin/insurance/plans/{planId}/usage-category` body:`{"usageCategory":"DRIVER"}`(白名单 CUSTOMER/DRIVER/BOTH,非法返 540223)。
|
||||
配套:计划查询出参(`GET /v3/admin/insurance/products/{productId}/plans` 等)已新增 `usageCategory` 字段,可做打标 UI。客人端选计划**不收紧**(维持全量,拍板)。
|
||||
|
||||
## 6. 错误码汇总
|
||||
|
||||
| code | 触发场景 |
|
||||
|---|---|
|
||||
| 100001 | perTrip 缺 insurancePlanId / 保障起>止 / 退保保单不属于该司机 |
|
||||
| 600205 | 司机不存在 |
|
||||
| 540031 | 该保险计划未标注为司机可用(选了 CUSTOMER 计划) |
|
||||
| 540032 | 该司机该保障期已有生效保单(一天只一份生效,含投保中) |
|
||||
| 540202 | 保单当前状态不可退保 |
|
||||
| 540223 | 计划使用分类非法(打标端点) |
|
||||
| 605601 | 保险服务不可用(order-v3 不通,稍后重试) |
|
||||
|
||||
### 实测反例示例
|
||||
|
||||
```bash
|
||||
# 选 CUSTOMER 计划投保 → 540031
|
||||
curl -k -X POST "https://api.test.1814.love:9443/admin/fleet/drivers/{driverId}/insurance/purchase" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"planId":"2054773833342451714","coverageStartDate":"2026-07-01","coverageEndDate":"2027-06-30"}'
|
||||
# => {"code":540031,"message":"该保险计划未标注为司机可用"}
|
||||
|
||||
# perTrip 不带 insurancePlanId 建司机 → 100001
|
||||
# => {"code":100001,"message":"参数非法: 保险类型为 perTrip 时缺少必填子字段: insurancePlanId(保险方案id,选保游网司机专用计划)"}
|
||||
```
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
date: 2026-06-11
|
||||
type: api-contract-clarify
|
||||
module: hl-order-service-v3/insurance
|
||||
priority: high
|
||||
status: code-already-merged
|
||||
restart_service: hl-order-service-v3
|
||||
端类型: 管理后台
|
||||
breaking_change: false
|
||||
---
|
||||
|
||||
# v3 保险「投保」接口入参契约说明(管理后台保险全量切 v3 联调补充)
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
管理后台保险模块前端已全量指向 v3,投保入参字段名按设计文档(Knife4j / API 文档)硬切。
|
||||
但 v3 投保接口(`/v3/admin/insurance/purchase`)此前**从未推送过入参契约 changelog**(仓库已有的 `#3642` 只说了出单状态/列表详情出参,未涉及投保入参)。
|
||||
本文档把 v3 投保两个接口的**入参契约**完整内联,并明确**老字段名是否仍被接收**这一前端关心的问题。
|
||||
|
||||
> ⚠️ 本文档不涉及"保险页 404"——404 属网关路由 / 服务注册问题(需后端确认测试服 `/v3/admin/insurance/**` 是否已路由到 hl-order-service-v3:8086、order-service-v3 是否已启动),不在接口契约范畴。
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | `/v3/admin/insurance/purchase` | POST | 手动投保(指定方案或产品+计划 + 可选被保人) |
|
||||
| 2 | `/v3/admin/insurance/purchase-by-scheme` | POST | 按方案自动投保(仅 orderId + schemeId) |
|
||||
|
||||
性质:**契约澄清,非破坏性**。字段名与 v2 一致;路径前缀由 `/admin/insurance` 变为 `/v3/admin/insurance`。
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 3.1 手动投保
|
||||
- **路径**:`POST /v3/admin/insurance/purchase`
|
||||
- **Content-Type**:`application/json`
|
||||
- **请求体**:`PurchaseInsuranceRequest`
|
||||
- **返回**:`Result<List<InsuranceOrderDetailVO>>`(创建的保险订单详情列表)
|
||||
|
||||
### 3.2 按方案自动投保
|
||||
- **路径**:`POST /v3/admin/insurance/purchase-by-scheme`
|
||||
- **请求体**:`AutoPurchaseRequest`
|
||||
- **返回**:`Result<Void>`
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 4.1 PurchaseInsuranceRequest(手动投保)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | Long | ✅ | 业务订单 ID |
|
||||
| `schemeId` | Long | 否 | 保险方案 ID(优先用方案匹配;为空则用 `insuranceProductId`+`planId`) |
|
||||
| `insuranceProductId` | Long | 方案为空时必填 | 保险产品 ID |
|
||||
| `planId` | Long | 方案为空时必填 | 保险计划 ID |
|
||||
| `coverageStartDate` | LocalDate | 否 | 保障开始日期,为空时后端按订单 startDate 自动填充 |
|
||||
| `coverageEndDate` | LocalDate | 否 | 保障结束日期,为空时后端按订单 endDate 自动填充 |
|
||||
| `insuredPersons` | List<InsuredPersonItem> | 否 | 被保人列表;**为空时后端按订单出行人自动填充**,传了则以传入为准 |
|
||||
| `remark` | String | 否 | 备注 |
|
||||
|
||||
#### InsuredPersonItem(被保人)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | String | ✅ | 姓名 |
|
||||
| `idCardType` | String | ✅ | 证件类型,枚举见 ⑥;**兼容老字段名 `idType`**(`@JsonAlias`) |
|
||||
| `idCardNo` | String | ✅ | 证件号码(落库自动加密、查询自动解密,前端无感);**兼容老字段名 `idNo`** |
|
||||
| `birthday` | LocalDate | 否 | 出生日期 |
|
||||
| `phone` | String | 否 | 手机号 |
|
||||
|
||||
> ⚠️ **v3 被保人入参无 `gender`(性别)字段**:v2 的 `InsuredPersonItem` 有 `gender`,v3 **没有**。前端从 v2 切 v3 若仍传 `gender`,将被 Jackson 静默忽略(`FAIL_ON_UNKNOWN_PROPERTIES=false`,不报错)。
|
||||
|
||||
### 4.2 AutoPurchaseRequest(按方案投保)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | Long | ✅ | 旅行订单 ID |
|
||||
| `schemeId` | Long | ✅ | 保险方案 ID |
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
`/v3/admin/insurance/purchase` 返回 `List<InsuranceOrderDetailVO>`(新建保险订单详情)。
|
||||
其中 `status` / `statusLabel` / `policyNo` 的**异步出单**语义(可能为 `INSURING`/出单中、`policyNo` 可能为空)见同目录 `#3642 保险出单改异步` changelog。
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
`idCardType` 证件类型(以代码注解为准,完整证件字典与 traveler 模块对齐):
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `ID_CARD` | 身份证 |
|
||||
| `PASSPORT` | 护照 |
|
||||
| `HONG_KONG` | 港澳通行证 |
|
||||
| `TAIWAN` | 台湾通行证 |
|
||||
| `OTHER` | 其他 |
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 场景 | 响应 |
|
||||
|------|------|
|
||||
| 缺 `orderId` | 400 「订单ID不能为空」 |
|
||||
| 被保人缺 `idCardType`(或老名 `idType`) | 400 「证件类型不能为空」 |
|
||||
| 被保人缺 `idCardNo`(或老名 `idNo`) | 400 「证件号码不能为空」 |
|
||||
| 方案与产品+计划均为空 | 业务校验失败(无法定位保险产品) |
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型(推荐:标准字段名)
|
||||
|
||||
```json
|
||||
POST /v3/admin/insurance/purchase
|
||||
{
|
||||
"orderId": "2026270000000000001",
|
||||
"schemeId": "2026279738275856500",
|
||||
"insuredPersons": [
|
||||
{
|
||||
"name": "张三",
|
||||
"idCardType": "ID_CARD",
|
||||
"idCardNo": "110101199001011234",
|
||||
"phone": "13800138000",
|
||||
"birthday": "1990-01-01"
|
||||
}
|
||||
],
|
||||
"remark": "管理后台手动投保"
|
||||
}
|
||||
```
|
||||
|
||||
### 边界(不传被保人 → 后端按订单出行人自动填充)
|
||||
|
||||
```json
|
||||
POST /v3/admin/insurance/purchase
|
||||
{ "orderId": "2026270000000000001", "schemeId": "2026279738275856500" }
|
||||
```
|
||||
|
||||
### 兼容(老字段名 idType/idNo 仍被接收,@JsonAlias 兜底)
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "2026270000000000001",
|
||||
"schemeId": "2026279738275856500",
|
||||
"insuredPersons": [
|
||||
{ "name": "张三", "idType": "ID_CARD", "idNo": "110101199001011234" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 异常(缺证件号 → 400)
|
||||
|
||||
```json
|
||||
请求: { "orderId": "...", "insuredPersons": [ { "name": "张三", "idCardType": "ID_CARD" } ] }
|
||||
响应: { "code": 400, "message": "证件号码不能为空" }
|
||||
```
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- `insuredPersons` 为空 → 后端按订单出行人自动填充被保人。
|
||||
- `coverageStartDate` / `coverageEndDate` 为空 → 后端按订单 startDate / endDate 填充。
|
||||
- 同时传 `idCardType` 和 `idType`(不推荐)→ Jackson 按后出现的覆盖,建议**只传标准名**。
|
||||
- 出单为异步:返回时保险订单可能处于 `INSURING`、`policyNo` 暂空,需轮询 / 回调获取最终保单号(详见 `#3642`)。
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| 维度 | v2(`/admin/insurance`) | v3(`/v3/admin/insurance`) |
|
||||
|------|--------------------------|-----------------------------|
|
||||
| 路径前缀 | `/admin/insurance` | `/v3/admin/insurance` |
|
||||
| `idCardType`/`idCardNo` | ✅(兼容 `idType`/`idNo`) | ✅(兼容 `idType`/`idNo`,**一致**) |
|
||||
| 被保人 `gender` | ✅ 有 | ❌ **无** |
|
||||
| 出单 | 同步 | 异步(INSURING 状态,见 #3642) |
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- 前端切标准字段名 `idCardType`/`idCardNo` **安全**;继续用老名 `idType`/`idNo` 也**不会 400**(兼容未下线)。
|
||||
- 唯一需前端动作:**停止传 `gender`**(v3 无此入参,传了被忽略;如业务需要被保人性别,由后端从出行人档案带,前端无需关心)。
|
||||
- 无代码回滚项(本文档为存量契约说明,未改代码)。
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- **「保险页 404」非本文档范畴**:属网关路由 / 服务注册问题,请后端在测试服核:① `/v3/admin/insurance/**` 是否路由到 hl-order-service-v3(8086);② order-service-v3 是否已启动;③ 小程序侧若经 BFF,`MpInsuranceFeignClient` 的 `name="hl-order-service"`(无 v3 后缀)在测试服注册到的是 v2 还是 v3。
|
||||
- `idCardNo` 已挂加密 TypeHandler,落库加密、查询解密,日志脱敏,前端无感。
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- 来源:管理后台保险全量切 v3 联调反馈(前端 sync-log)
|
||||
- 相关 changelog:`#3642 保险出单改异步`(出参状态)、`changelogs/2026-05/07_fix_insurance_purchase_idcardtype_field_rename.md`(v2 同源字段改名背景)
|
||||
- 代码位置:`hl-order-service-v3/.../insurance/dto/PurchaseInsuranceRequest.java`、`AdminInsuranceController.java:124`
|
||||
- 负责人:腰苏图
|
||||
@@ -0,0 +1,175 @@
|
||||
# 【修改接口·管理后台】车型价格日历——三模式调价(ABS/DELTA/PERCENT)+节假日筛选+互斥校验落地(§9 全块说明)
|
||||
|
||||
> 服务:hl-fleet-service(8087) | 分支:dev-v3 | PR:#3714 | 已部署测试服并 22 项 API 实测通过(2026-06-12)
|
||||
> 契约出处:FLEET API §9.1-§9.4(文档已同 PR 更新至 v1.5.61) | 前端触发位:车管控制台「价格日历」页(page_pricing 原型 BulkPriceSheet)
|
||||
> 本文是价格日历**整块 4 接口的完整说明**(应后端要求全面同步),已上线功能 + 本次新增一并列出,前端可按本文直接对接。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **完全向后兼容,已对接代码零改动**:本次新增字段全部可选。不传 `adjustMode` 默认 `ABS`(固定价),与原行为完全一致。
|
||||
2. **新增三模式调价**(本次 PR):`adjustMode` = `ABS`(设为固定价) / `DELTA`(在各日现价上加减金额) / `PERCENT`(按百分比调整)。`DELTA`/`PERCENT` 时 `dayPrice` 不需要传,改传 `adjustValue`。
|
||||
3. **DELTA/PERCENT 的基准价规则**:命中日已设价 → 用当日现价计算;未设价 → 用车型 `basePrice` 兜底;两者都没有 → **该日跳过不写**(不报错)。计算结果 < 0 一律按 `0.00` 落库。`PERCENT` 结果四舍五入保留两位(HALF_UP)。
|
||||
4. **新增仅节假日筛选**(本次 PR):`holidayOnly=true` 只设置节假日(节假日清单由后端 Nacos 配置维护,**当前为验证占位日期,业务法定节假日清单待车管确认后由后端配置**,前端无需关心数据源)。
|
||||
5. **三筛选互斥**(本次 PR,行为变更):`weekdayOnly` / `weekendOnly` / `holidayOnly` 同时传两个及以上 `true` → 返 `code=400` 参数错(此前是静默 0 命中返成功,**前端若有依赖旧静默行为的逻辑请注意**)。
|
||||
6. **金额字段两位小数**(本次 PR):`dayPrice`/`adjustValue` 传超过 2 位小数返 `code=400`。
|
||||
7. **防双击/并发**(本次 PR):三个写端点(批量设价/批量改状态/区间清除)都加了幂等(同参数 3 秒窗口重复提交返「…处理中,请勿重复提交」)+ 同车型并发互斥锁。前端连点按钮会收到 400 类提示,正常单次操作无感。
|
||||
8. **错误码口径勘正**:参数校验失败统一返业务码 **400**(HTTP 恒 200,错误信息在 `message`)。文档旧版写的 `400001` 为笔误,以 400 为准。
|
||||
9. 所有金额出参为**字符串**(如 `"800.00"`),雪花 ID 出参为字符串,前端勿当数字解析。
|
||||
|
||||
## 1. 查询价格日历(月视图) — 无变更,完整契约
|
||||
|
||||
`GET /admin/fleet/pricing-calendar/{vehicleModelId}?year=2026&month=3`
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| vehicleModelId | path | string | 是 | 车型型号 ID(车型树 models[].id) |
|
||||
| year | query | number | 是 | 年份(2020-2100,越界返 400) |
|
||||
| month | query | number | 是 | 月份(1-12,越界返 400) |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -k "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745?year=2099&month=1" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### 响应示例(测试服实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"year": 2099,
|
||||
"month": 1,
|
||||
"vehicleModelId": "2057378612593823745",
|
||||
"modelName": "丰田普拉多",
|
||||
"prices": [
|
||||
{ "date": "2099-01-01", "dayPrice": "800.00", "status": "AVAILABLE", "remark": "旺季价格" },
|
||||
{ "date": "2099-01-08", "dayPrice": "900.00", "status": "CLOSED", "remark": "临时停租" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 出参字段 | 说明 |
|
||||
|---|---|
|
||||
| prices | **仅返回已设置价格的日期**(未设价日期不出现,空月返回空数组) |
|
||||
| prices[].dayPrice | 当日租赁价,**字符串**两位小数 |
|
||||
| prices[].status | 可售状态:`AVAILABLE`=可售 / `CLOSED`=关闭 |
|
||||
|
||||
## 2. 批量设置价格 — 本次新增三模式/节假日/互斥,完整契约
|
||||
|
||||
`PUT /admin/fleet/pricing-calendar/{vehicleModelId}`
|
||||
|
||||
### 入参(body)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| startDate | string | 是 | 开始日期 yyyy-MM-dd |
|
||||
| endDate | string | 是 | 结束日期,须 ≥ startDate 且总天数 ≤ 366 |
|
||||
| adjustMode | string | 否 | 🆕 调价模式:`ABS`=设为固定价 / `DELTA`=现价加减金额 / `PERCENT`=现价按百分比调;**不传默认 ABS**(向后兼容);非法值返 400 |
|
||||
| dayPrice | string | ABS 必填 | 当日租赁价(¥,≥0,两位小数)。**仅 ABS 模式必填且生效**,DELTA/PERCENT 不需要传 |
|
||||
| adjustValue | string | DELTA/PERCENT 必填 | 🆕 调整值:DELTA=加减金额(可负,如 `-100.00`) / PERCENT=百分比数值(`10`=+10%,`-5`=-5%)。两位小数 |
|
||||
| status | string | 否 | `AVAILABLE` / `CLOSED`,不传默认 AVAILABLE,非法值返 400 |
|
||||
| remark | string | 否 | 备注 |
|
||||
| weekdayOnly | boolean | 否 | 仅工作日(命中周末跳过) |
|
||||
| weekendOnly | boolean | 否 | 仅周末(命中工作日跳过) |
|
||||
| holidayOnly | boolean | 否 | 🆕 仅节假日(命中非节假日跳过,节假日清单后端 Nacos 维护) |
|
||||
| selectedWeekdays | number[] | 否 | 指定星期几(1=周一…7=周日),非空则只设命中星期 |
|
||||
| excludeDates | string[] | 否 | 排除日期列表(区间内这些日期不设置) |
|
||||
|
||||
> ⚠️ `weekdayOnly`/`weekendOnly`/`holidayOnly` **互斥**,≥2 个同时 true 返 400「工作日/周末/节假日筛选不可同时启用」。
|
||||
|
||||
### 请求示例 1——固定价(原有用法,完全不变)
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"startDate":"2099-01-01","endDate":"2099-01-05","dayPrice":"800.00","remark":"旺季价格"}'
|
||||
```
|
||||
|
||||
### 请求示例 2——🆕 全月工作日涨 100 元(DELTA)
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"startDate":"2099-01-01","endDate":"2099-01-31","adjustMode":"DELTA","adjustValue":"100","weekdayOnly":true}'
|
||||
```
|
||||
|
||||
### 请求示例 3——🆕 节假日统一上浮 15%(PERCENT + holidayOnly)
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"startDate":"2099-01-01","endDate":"2099-01-31","adjustMode":"PERCENT","adjustValue":"15","holidayOnly":true}'
|
||||
```
|
||||
|
||||
### 响应
|
||||
|
||||
成功:`{ "code": 200, "message": "成功" }`(无 data)
|
||||
|
||||
### 测试服实测数据点(可作前端联调预期)
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| ABS 800 后 DELTA +100 | 900.00 |
|
||||
| 900 后 PERCENT -10 | 810.00 |
|
||||
| DELTA +100 作用于未设价日(车型 basePrice=1000) | 1100.00 |
|
||||
| DELTA -99999(负穿) | 0.00(兜底) |
|
||||
| holidayOnly=true,区间 11 天含 1 个节假日 | 仅节假日 1 天写入 |
|
||||
| weekdayOnly+weekendOnly 同 true | code=400 |
|
||||
| adjustMode="FOO" / DELTA 缺 adjustValue / ABS 缺 dayPrice / dayPrice 三位小数 | code=400 |
|
||||
|
||||
## 3. 批量修改状态 — 无变更(新增幂等防护),完整契约
|
||||
|
||||
`PUT /admin/fleet/pricing-calendar/{vehicleModelId}/status`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| startDate | string | 是 | 开始日期 yyyy-MM-dd |
|
||||
| endDate | string | 是 | 结束日期 |
|
||||
| status | string | 是 | `AVAILABLE` / `CLOSED`,非法值返 400 |
|
||||
| remark | string | 否 | 备注;**不传则保留各记录原备注** |
|
||||
|
||||
只改区间内**已有记录**的状态(不影响价格,未设价日期不会凭空建记录)。
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745/status" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"startDate":"2099-01-01","endDate":"2099-01-31","status":"CLOSED","remark":"临时停租"}'
|
||||
```
|
||||
|
||||
成功:`{ "code": 200, "message": "成功" }`
|
||||
|
||||
## 4. 清除价格日历 — 无变更(新增幂等防护),完整契约
|
||||
|
||||
`DELETE /admin/fleet/pricing-calendar/{vehicleModelId}?startDate=2099-01-01&endDate=2099-01-31`
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| startDate | query | string | 是 | 开始日期 yyyy-MM-dd |
|
||||
| endDate | query | string | 是 | 结束日期 |
|
||||
|
||||
软删除区间内该车型全部价格记录;删后**可重新设置同日价格**(测试服实测通过)。
|
||||
|
||||
```bash
|
||||
curl -k -X DELETE "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/2057378612593823745?startDate=2099-01-01&endDate=2099-01-31" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
成功:`{ "code": 200, "message": "成功" }`
|
||||
|
||||
## 5. 错误码表(§9 全块)
|
||||
|
||||
| code | message | 触发场景 | 涉及端点 |
|
||||
|---|---|---|---|
|
||||
| 400 | (具体校验消息) | 参数校验失败:必填缺失/格式错/枚举非法/互斥冲突/超两位小数/year·month 越界 | 全部 |
|
||||
| 600500 | 日租价不能为负 | dayPrice < 0(防御兜底,正常会先被 400 拦) | §9.2 |
|
||||
| 600501 | 日期范围非法:开始日期不能晚于结束日期 | startDate > endDate | §9.2 |
|
||||
| 600502 | 日期范围不能超过 366 天 | 区间总天数(含首尾) > 366 | §9.2 |
|
||||
| 600503 | 车型型号不存在 | vehicleModelId 无效/已软删 | §9.1 / §9.2 / §9.3 |
|
||||
| 401 | 未登录 | token 缺失/过期 | 全部 |
|
||||
|
||||
> §9.4 清除接口不校验车型存在性(按契约,清不存在车型的区间等于无操作返 200)。
|
||||
> HTTP 状态码恒 200,业务错误看 `code` 字段。
|
||||
@@ -0,0 +1,43 @@
|
||||
# 【行为变更·司机H5+管理后台】司机审核驳回短信接通知中心真发上线,H5 驳回后原链接可重提
|
||||
|
||||
> 服务:hl-fleet-service(8087) + hl-user-service(8081/8181) | 分支:dev-v3 | PR:#3715(主) #3720 #3722(hotfix) | 工单:#3713
|
||||
> 已部署测试服并 E2E 实测通过(2026-06-12):驳回短信经阿里云真实发出(模板 SMS_507190242),驳回后原链接重提回待审
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **驳回短信从「日志桩」变成「真发」**:此前 `LogStubNotificationServiceImpl` 只打日志、司机实际收不到任何短信;现在车管驳回司机档案申请时,系统经通知中心给司机手机号真实发送阿里云短信(签名「呼籁旅行」)。**测试环境驳回操作会真发短信,联调时请用可控手机号**。
|
||||
2. **H5 行为变更(前端可感知)**:司机被驳回后用**原链接**重新打开/修改/提交,不再返回 `600302「邀请链接已使用」`——init/ocr/submit 全放行;重提成功后该申请单回到待审状态。**hl-ui 司机 H5 若对 600302 有特判文案/拦截页,请确认驳回场景不再命中**(600302 现在仅 approved 终态返回)。
|
||||
3. **短信文案**(阿里云已过审模板,变量 ≤2):`${name}您好,您提交的司机档案审核未通过,原因:${reason},请重新打开原链接修改后提交。`
|
||||
4. ⚠️ **阿里云模板变量 name 是[个人姓名]类型,有姓名规范校验**:实测非规范姓名(如带后缀的测试名)被拒发 `isv.TEMPLATE_PARAMS_ILLEGAL`。蒙古族长名/带「·」的姓名是否在规范内待实测,有拒发风险——拒发时业务不受影响(驳回正常生效),仅短信缺失,`notification_send_log` 留痕 status=1。
|
||||
|
||||
## 1. 管理后台行为变更(无接口契约变化)
|
||||
|
||||
| 场景 | 旧行为 | 新行为 |
|
||||
|---|---|---|
|
||||
| 驳回 `POST /admin/fleet/drivers/pending/{id}/reject` | 仅日志,司机无感 | 落库后异步发短信(姓名+驳回原因,原因超 30 字短信内截断,完整原因仍在申请单详情) |
|
||||
| 司机重提后 | 不可能发生(链接被 600302 拦死) | 申请单**回到待审列表**(reviewStatus=pending),驳回原因/审核人/审核时间清空,可再次审核 |
|
||||
| 同一申请单 60 秒内重复驳回 | — | 第二次短信被幂等抑制(只发一条),驳回本身正常 |
|
||||
|
||||
## 2. 司机 H5 行为变更
|
||||
|
||||
| 场景 | 旧行为 | 新行为 |
|
||||
|---|---|---|
|
||||
| 驳回后打开原链接 | init 可看,但 ocr/提交返回 `600302` | 全流程放行,可修改后重新提交 |
|
||||
| 重提成功 | — | 返回 200 + pendingId(同一条申请单覆盖更新) |
|
||||
| 审核通过(approved)后打开原链接 | `600302` | 不变,仍 `600302`(唯一终态) |
|
||||
|
||||
### 重提请求示例(与首次提交完全一致,无新参数)
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://web.test.1814.love:9443/app/h5/driver-onboard/submit/new" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"token":"{原链接token}","name":"张三","phone":"13812341234","gender":"1"}'
|
||||
```
|
||||
|
||||
## 3. 后端内部变更(前端无需关注,备查)
|
||||
|
||||
1. `NotificationEventMessage` 新增 `phone` 字段(directPhone 收件通道,给非 C 端/非员工的裸手机号收件人发短信,当前仅 SMS 通道消费)。
|
||||
2. 通知中心 SMS 通道支持裸手机号:`phone` 优先,无则按原 `userId` 反查,双空跳过。
|
||||
3. 新增事件码 `DRIVER_REVIEW_REJECTED`(Flyway V20260612_001 种子,模板码 SMS_507190242 已回填)。
|
||||
4. fleet 新增错误码 `605701`(通知中心降级,业务旁路不阻断驳回)。
|
||||
5. 发录入链接短信(DRIVER_ONBOARD_LINK_SENT)**未上线**——当前代码无「发送链接」动作,待重发端点立项时一并落地。
|
||||
@@ -0,0 +1,63 @@
|
||||
# 【新增接口·管理后台】保险管理——保险产品全量列表(不分页),修复方案弹窗 pageSize=200 报错
|
||||
|
||||
> 服务:hl-order-service-v3(8086/8186) | 分支:dev-v3 | PR:#3717 | 已部署测试服并实测通过(2026-06-12)
|
||||
> 背景:保险方案新增弹窗的产品下拉调 `GET /v3/admin/insurance/products?page=1&pageSize=200` 被全局分页上限拦截,页面红 toast「每页条数最大为100」
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **`pageSize` 全平台上限是 100**(PageParam 全局校验,所有分页接口一致),传 >100 一律返 `code=400`「每页条数最大为100」。**请勿用大 pageSize 模拟全量拉取**。
|
||||
2. 需要全部数据的下拉/选择器场景,改用本次新增的**不分页全量接口** `GET /v3/admin/insurance/products/all`。
|
||||
3. 原分页接口 `/products` 行为完全不变,列表页继续用它。
|
||||
|
||||
## 1. 新增端点
|
||||
|
||||
`GET /v3/admin/insurance/products/all`
|
||||
|
||||
### 入参(query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| isOverseas | boolean | 否 | true=仅境外 / false=仅境内 / 不传=全部(方案弹窗「产品类型」单选可直接映射此参数) |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
# 全部产品
|
||||
curl -k "https://web.test.1814.love:9443/v3/admin/insurance/products/all" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
|
||||
# 仅境内(方案弹窗选「境内」时)
|
||||
curl -k "https://web.test.1814.love:9443/v3/admin/insurance/products/all?isOverseas=false" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### 响应示例(测试服实测,当前全量 10 条/境内 8 条)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": [
|
||||
{
|
||||
"insuranceProductId": "2054773831589232641",
|
||||
"productId": "2054773831589232641",
|
||||
"productName": "山河令(太保山东新)",
|
||||
"productCode": "4810725b34654e1e88a7509e5b241c3a"
|
||||
// ...其余字段与分页版 /products 的列表项完全一致(同一 InsuranceProductVO)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 出参 | 说明 |
|
||||
|---|---|
|
||||
| data | **直接是数组**(无 total/page 包装),元素结构与分页版列表项一致,前端可无缝复用渲染逻辑 |
|
||||
|
||||
## 2. 回归确认(测试服实测)
|
||||
|
||||
| 调用 | 结果 |
|
||||
|---|---|
|
||||
| `/products/all` | 200,返回全量 10 条 |
|
||||
| `/products/all?isOverseas=false` | 200,返回境内 8 条 |
|
||||
| `/products?page=1&pageSize=200` | 400「每页条数最大为100」(维持不变,前端请改调 all) |
|
||||
| `/products?page=1&pageSize=100` | 200,分页正常 |
|
||||
@@ -0,0 +1,124 @@
|
||||
# 【新增接口·管理后台】价格日历——§9.5 聚合总览 + §9.6 多车型批量设价(对齐原型,4→6 接口)
|
||||
|
||||
> 服务:hl-fleet-service(8087) | 分支:dev-v3 | PR:#3727 | 已部署测试服并 14 项 API 实测通过(2026-06-12)
|
||||
> 契约出处:FLEET API v1.5.62 §9.5/§9.6(文档站已同步) | 前端触发位:车管控制台「价格日历」整页(page_pricing 原型)
|
||||
> 背景:前端反馈现有价格日历接口拼不出原型页面(原型需要一次拿到全部车型×日期价格矩阵+节假日+统计,且批量改价是多车型多选)——本次补齐,**原有 4 接口零变更**。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **整页数据一次拉取**:新增 `GET /admin/fleet/pricing-calendar/overview`,一次返回[大类→车型→区间价格矩阵+统计]+节假日列表+全局价格区间。「全部大类」月卡片、单大类月热力、季度/半年视图全部用它,**不再需要按车型逐个调 §9.1**。
|
||||
2. **节假日数据源收口后端**:格子上的「假」标记请改用 overview 返回的 `holidays` 数组判断(原型里前端硬编码的 HOLIDAYS_2026 删掉),真相源=后端 Nacos 配置,每年由后端维护、前端零改动。
|
||||
3. **`holidays` 只含请求区间内的节假日**:批量改价弹窗如果选择了超出当前已加载区间的日期(例如页面加载了 1-5 月、批改 10 月国庆),请按所选区间重拉一次 overview 取节假日。
|
||||
4. **月卡片统计口径**:`stats` 按**请求区间**统计(均/低/高)。一次拉多个月时,单月统计请前端从 `prices[]` 按月自聚(原型本来就是前端算的,矩阵数据完备);`yearAvgPrice`(全年均)后端已算好直接用。
|
||||
5. **多车型批量改价**:新增 `PUT /batch-set`,vehicleModelIds 1-50 个多选,**任一车型 ID 不存在整批拒绝**(600503,消息列出全部缺失 ID),三模式(ABS/DELTA/PERCENT)/仅周末/仅节假日等与单车型 §9.2 完全同源。整批一个事务,失败全回滚无半批。
|
||||
6. 600503 消息升级为「车型型号不存在: {ID}」(带具体 ID,错误码不变,按 code 判断的前端无影响)。
|
||||
7. 金额出参一律字符串(如 `"999.00"`),雪花 ID 字符串。
|
||||
|
||||
## 1. §9.5 价格日历聚合总览
|
||||
|
||||
`GET /admin/fleet/pricing-calendar/overview?startDate=2026-01-01&endDate=2026-05-31`
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| startDate | query | string | 是 | 开始日期 yyyy-MM-dd |
|
||||
| endDate | query | string | 是 | 结束日期,须 ≥ startDate 且区间 ≤ 366 天(600501/600502) |
|
||||
|
||||
### 响应示例(测试服实测裁剪)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"startDate": "2026-01-01",
|
||||
"endDate": "2026-05-31",
|
||||
"globalMinPrice": "950.00", // 区间内全部车型最低日价(色阶图例下界;无任何设价=null)
|
||||
"globalMaxPrice": "4500.00", // 区间内全部车型最高日价(色阶图例上界)
|
||||
"holidays": ["2026-01-01", "2026-02-16", "2026-02-17"], // 区间内节假日(格子「假」标记数据源,升序)
|
||||
"types": [ // 车辆大类(sortOrder 升序,仅含有型号的大类)
|
||||
{
|
||||
"vehicleTypeId": "2057378611889180674",
|
||||
"typeKey": "suv2",
|
||||
"typeName": "SUV系列",
|
||||
"icon": "",
|
||||
"models": [
|
||||
{
|
||||
"vehicleModelId": "2057378612593823745",
|
||||
"modelName": "丰田普拉多",
|
||||
"basePrice": "1000.00", // 车型基准价(可 null)
|
||||
"stats": {
|
||||
"avgPrice": "1673.00", // 请求区间内已设价日均价(HALF_UP 两位;无设价=null)
|
||||
"minPrice": "1500.00", // 区间最低
|
||||
"maxPrice": "2250.00", // 区间最高
|
||||
"yearAvgPrice": "1293.00" // startDate 所在自然年的全年均价(卡片「全年均」)
|
||||
},
|
||||
"prices": [ // 区间内已设价日(升序;未设价日不出现)
|
||||
{ "date": "2026-01-01", "dayPrice": "2250.00", "status": "AVAILABLE", "remark": "" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 无设价的车型也会返回(prices=[]、stats 字段全 null),前端展示全部车型无需另调车型树。
|
||||
|
||||
### curl
|
||||
|
||||
```bash
|
||||
curl -k "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/overview?startDate=2026-01-01&endDate=2026-05-31" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
## 2. §9.6 多车型批量设价
|
||||
|
||||
`PUT /admin/fleet/pricing-calendar/batch-set`
|
||||
|
||||
### 入参(body) = §9.2 单车型全部字段 + vehicleModelIds
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| vehicleModelIds | string[] | 是 | 车型型号 ID 列表(1-50 个,雪花字符串);任一不存在整批拒绝 600503;重复自动去重;元素不能为 null |
|
||||
| startDate / endDate | string | 是 | 同 §9.2(≤366 天) |
|
||||
| adjustMode | string | 否 | ABS=固定价 / DELTA=现价加减 / PERCENT=百分比,默认 ABS |
|
||||
| dayPrice | string | ABS 必填 | 同 §9.2 |
|
||||
| adjustValue | string | DELTA/PERCENT 必填 | 同 §9.2(可负;PERCENT 如 10=+10%) |
|
||||
| status / remark / weekdayOnly / weekendOnly / holidayOnly / selectedWeekdays / excludeDates | — | 否 | 全部与 §9.2 同源(三筛选互斥返 400) |
|
||||
|
||||
### 请求示例——原型「批量改价」弹窗:选 2 个车型春节区间上浮 15%
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://web.test.1814.love:9443/admin/fleet/pricing-calendar/batch-set" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"vehicleModelIds":["2057378612593823745","2064998010110029826"],
|
||||
"startDate":"2026-02-16","endDate":"2026-02-22",
|
||||
"adjustMode":"PERCENT","adjustValue":"15","holidayOnly":true}'
|
||||
```
|
||||
|
||||
成功:`{ "code": 200, "message": "成功" }`
|
||||
|
||||
### 错误码
|
||||
|
||||
| code | 触发场景 |
|
||||
|---|---|
|
||||
| 400 | 参数校验(ID 列表空/>50/含 null、三筛选互斥、条件必填、白名单、两位小数) |
|
||||
| 600501 / 600502 | 日期顺序 / 区间超 366 天 |
|
||||
| 600503 | 任一车型不存在,消息列出全部缺失 ID,**整批不执行** |
|
||||
|
||||
### 防护(前端无感知,连点会收到提示)
|
||||
|
||||
- 同参数 3 秒幂等窗口(传参顺序/重复 ID 不影响判重)+ 批量请求间互斥锁
|
||||
- 整批一个事务:任一失败全部回滚,不会出现"改了一半"
|
||||
|
||||
## 3. 测试服实测数据点
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| overview 2099-02 区间(含 2 车型设价) | 200,types 树/stats/globalMin-Max/prices 全正确 |
|
||||
| overview holidays(配置节假日区间) | `["2099-01-15"]` 与 Nacos 配置一致 |
|
||||
| batch-set 2 车型 ABS 999 | 两车型全部写入,overview 立即可见 |
|
||||
| batch-set 含不存在 ID | 600503 消息带缺失 ID,整批未执行 |
|
||||
| 区间 >366 / start>end | 600502 / 600501 |
|
||||
| 原 §9.1 单车型月视图 | 回归正常,零变更 |
|
||||
@@ -0,0 +1,99 @@
|
||||
# 【新增接口+前端回显修正·管理后台】司机编辑「关联车辆」改造——常驻车搜索下拉改绑 + 常开车型走车型库 + 回显修正
|
||||
|
||||
> 服务:hl-fleet-service(8087) | 分支:dev-v3 | PR:#3742 | 工单:#3739
|
||||
> 已部署测试服并端到端实测通过(2026-06-12,过网关 9443 真 admin token,13/13 场景 PASS):绑定/改绑(自动解绑旧车)/解绑/605023 占用拒绝/幂等/600110/onlyResidentUnbound 筛选全部验证,测试数据已还原
|
||||
|
||||
## ⚠️ 关键说明(hl-ui 需改 4 处)
|
||||
|
||||
司机档案「编辑司机 → 关联车辆」这一块现状有三个问题:常驻车车牌是自由文本框、打开编辑不回显(卡片显示"常驻 蒙A-G8888"但编辑框空白)、常开车型大类 key 不回显。根因 + 改法如下:
|
||||
|
||||
1. **常驻车车牌「不回显」是前端读错字段**:司机详情接口**早已返回** `residentVehiclePlate`(权威常驻车,如 `蒙A-G8888`),前端「关联车辆」块当前绑的是另一个字段 `primaryVehiclePlate`(H5 司机自报参考车牌,非权威,多为空)。**请前端把常驻车的回显改读 `residentVehiclePlate`**。
|
||||
|
||||
2. **常驻车车牌改成「车辆搜索下拉 + 改绑」**(本次新增后端端点):选车下拉的数据源用 `GET /admin/fleet/vehicles/page?keyword=蒙A&onlyResidentUnbound=true`(只列**无常驻司机**的空闲车);选定后调**新端点** `PUT /admin/fleet/drivers/{driverId}/resident-vehicle` 绑定。**约束(产品拍定):只能绑当前无常驻司机的车;若目标车已有别的常驻司机,后端返 `605023`,提示去「车辆档案」换绑。**
|
||||
|
||||
3. **常开车型(大类 key + 车型名)改成「车型库下拉」**:车型字典接口**全部现成**,选型号自动带出 `preferredTypeKey`(大类 key)+ `preferredModel`(车型名),两字段随司机保存接口一起提交即可(后端早已接收)。巴特尔这类"只填了车型名没填大类 key"就是因为现在是手填文本框、历史没填,改下拉后从源头补齐。
|
||||
|
||||
4. **`primaryVehiclePlate` 字段保持不变**:它是 H5 司机自报参考车牌,H5/续签仍在用,admin 编辑表单这块**不要再用它绑常驻车**(改用第 2 点的新端点),但也不要删,后端契约不变。
|
||||
|
||||
## 1. 新增接口:改绑司机常驻车
|
||||
|
||||
`PUT /admin/fleet/drivers/{driverId}/resident-vehicle`
|
||||
|
||||
| 入参 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| driverId | path Long | 是 | 司机 ID |
|
||||
| vehicleId | body Long | 否 | 目标车辆 ID(绑定/改绑);**不传或 null = 解绑本司机当前常驻车** |
|
||||
|
||||
**语义**
|
||||
|
||||
| 场景 | 行为 | 返回 |
|
||||
|---|---|---|
|
||||
| 绑定空闲车 | 该车常驻司机 = 本司机 | code 200 |
|
||||
| 改绑到另一台空闲车 | **本司机原常驻车自动解绑** + 新车绑上 | code 200 |
|
||||
| 目标车已是本司机常驻 | 幂等,不重复写 | code 200 |
|
||||
| 目标车被**别的**司机占为常驻 | 拒绝,提示去车辆档案换绑 | **605023** |
|
||||
| 解绑(vehicleId 不传/null) | 清掉本司机当前常驻车 | code 200 |
|
||||
| 目标车不存在 | — | **600110** |
|
||||
| 司机不存在 | — | **600205** |
|
||||
|
||||
> 个人自带车(ownerType=personal)绑定后,生命周期 service_status 会按该司机当前赛季自动联动(active→在役 / archived·blacklist→停用),前端无需处理。
|
||||
|
||||
**请求/响应示例**
|
||||
|
||||
```bash
|
||||
# 绑定/改绑
|
||||
curl -X PUT "https://api.test.1814.love:9443/admin/fleet/drivers/2065272148557021186/resident-vehicle" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"vehicleId":2064998140821319682}'
|
||||
# → {"code":200,"message":"成功","data":null,"success":true}
|
||||
|
||||
# 目标车已被别人占(605023)
|
||||
# → {"code":605023,"message":"目标车辆已有常驻司机(那顺),请到车辆档案换绑","success":false}
|
||||
|
||||
# 解绑
|
||||
curl -X PUT ".../drivers/{driverId}/resident-vehicle" -H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" -d '{"vehicleId":null}'
|
||||
# → {"code":200,...}
|
||||
```
|
||||
|
||||
## 2. 修改接口:车辆分页新增筛选参数(下拉数据源用)
|
||||
|
||||
`GET /admin/fleet/vehicles/page` 新增可选 query 参数:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| onlyResidentUnbound | Boolean | `true` = 仅返回**无常驻司机**的车(常驻车下拉数据源,只显示可绑的空闲车);**不传 / false = 全部(行为不变)** |
|
||||
|
||||
```bash
|
||||
# 常驻车下拉:按车牌搜 + 只列空闲车
|
||||
curl "https://api.test.1814.love:9443/admin/fleet/vehicles/page?keyword=蒙A&onlyResidentUnbound=true&pageSize=20" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
> 当前已绑的那台常驻车(回显值)由司机详情 `residentVehiclePlate` 单独给出,下拉里只需列空闲车供"换车"。
|
||||
|
||||
## 3. 常开车型下拉:用现成车型库接口(无新增)
|
||||
|
||||
| 用途 | 接口 | 返回 |
|
||||
|---|---|---|
|
||||
| 大类下拉 | `GET /admin/fleet/vehicle-types/list` | 全部大类(typeKey + typeName,不分页) |
|
||||
| 大类+型号树 | `GET /admin/fleet/vehicle-types` | 大类含 models[](型号名/座位等) |
|
||||
| 某大类下型号 | `GET /admin/fleet/vehicle-types/models/page?vehicleTypeId={id}` | 型号列表 |
|
||||
|
||||
- 「常开车型大类」下拉选项取 `vehicle-types/list` 的 `typeKey`(值)/`typeName`(显示),选定写入司机保存入参 `preferredTypeKey`。
|
||||
- 「常开车型名」下拉取所选大类下的型号 `modelName`,写入 `preferredModel`。
|
||||
- 两字段随司机新增/编辑接口(`POST /admin/fleet/drivers`、`PUT /admin/fleet/drivers/{driverId}`)正常提交,后端契约不变。
|
||||
|
||||
## 4. 端到端实测记录(测试服,2026-06-12)
|
||||
|
||||
```text
|
||||
腾空 2 台车 → onlyResidentUnbound=true 返 2 台 PASS
|
||||
绑定 A→蒙A-G8888 PASS
|
||||
改绑 A→蒙A-H7777(蒙A-G8888 自动解绑) PASS
|
||||
巴特尔绑已占的蒙A-H7777 → 605023(那顺占用) PASS
|
||||
幂等:A 再绑蒙A-H7777 → 200 PASS
|
||||
车不存在 → 600110 PASS
|
||||
解绑 A → 200,resident 变 null PASS
|
||||
还原:巴特尔→G8888 / D2→H7777 / 全 6 车绑回(空闲=0) PASS
|
||||
合计 13/13 PASS,测试数据已还原
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
# 【行为变更·管理后台+司机H5】司机性别字典化——硬编码删除,渲染/导入/校验全部跟随数据字典 gender
|
||||
|
||||
> 服务:hl-fleet-service(8087) | 分支:dev-v3 | PR:#3741 | 工单:#3734
|
||||
> 已部署测试服并端到端实测通过(2026-06-12):字典加「3=其他」后 ≤60 秒,H5/保存接口即接受 gender=3;删值即恢复拒绝
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **性别的取值从代码写死改为数据字典驱动**:后台「数据字典」里 `gender` 字典(当前 1=男/2=女/0=未知)增删改值后,车管模块的列表渲染、Excel 导入、保存校验在 **60 秒内自动跟随**,不再需要改代码发版。
|
||||
2. **非法性别的报错形态变化(前端需留意)**:原来是参数校验 `code=400`「性别只能 0=未知/1=男/2=女」,现在是业务码 **`code=100001`**「参数非法: 性别编码非法,取值来自字典 gender「男」「女」「未知」,值=X」。**hl-ui 若按 code=400 分支处理表单校验提示,请改为同时兼容 100001**。
|
||||
3. H5 提交 gender 传空串:原被 400 拒,现**视为未填放行**(与不传同效,后台落「未知」缺省)。
|
||||
4. 字典里删掉一个已被司机使用的值后:详情/列表渲染兜底显示「未知」不报错;但**编辑该司机时性别必须改为字典内现有值才能保存**(否则 100001)。
|
||||
|
||||
## 1. 各场景行为对照
|
||||
|
||||
| 场景 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 详情 genderLabel 渲染 | 代码写死 1男/2女/0未知 | 查字典实时渲染(测试服实测「男」),字典改标签 ≤60s 跟随 |
|
||||
| Excel 导入「性别」列 | 仅认 男/女/未知 | 认字典内**任意 ACTIVE 标签**(运营加值即放开),非法值仍行级报错 |
|
||||
| admin 保存 / H5 提交校验 | @Pattern 写死 ^[012]$ | 查字典校验,字典加值后新值自动合法 |
|
||||
| 字典服务(user-service)不可用 | 不依赖 | 自动退回内置 1男/2女/0未知 兜底,**不报错不阻断**(与旧行为完全一致) |
|
||||
|
||||
## 2. 端到端实测记录(测试服)
|
||||
|
||||
```text
|
||||
字典加 3=其他(POST /admin/dict/data) → code 200
|
||||
等 65s(fleet 侧 60s 字典快照过期)
|
||||
H5 提交 gender=3 → code 200(新值生效)
|
||||
删字典值 → 恢复后 gender=3 再次被拒 100001
|
||||
gender=9(字典外) → 100001「取值来自字典 gender「男」「女」「未知」,值=9」
|
||||
```
|
||||
|
||||
## 3. 后端内部变更(备查)
|
||||
|
||||
1. 删除 `GenderDict` 硬编码类;新增 `GenderDictResolver`(60s TTL 快照,Feign 失败退旧快照/内置兜底永不抛)。
|
||||
2. fleet 第三个出向 Feign:`GenderDictFeignClient` → user `GET /internal/dict/data?dictType=gender`(2s/3s 短超时)。
|
||||
3. `DriverSaveReqVO`/`SubmitNewReqVO` 的 gender `@Pattern` 删除,校验下沉 Service 层。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 【修改接口·管理后台】司机档案——全年保险绑定保险订单台账(自动落单+双向关联)
|
||||
|
||||
> 服务:hl-fleet-service(8087) + hl-order-service-v3(8086/8186) | 分支:dev-v3 | PR:#3748/#3754/#3758(空diff作废)/#3762 | 已部署测试服并 8 项端到端实测全过(2026-06-12)
|
||||
> 契约出处:FLEET API v1.5.63 §3.2/§3.3/§3.4 + §13.7.7/§13.7.8 | wx 拍板:「司机的全年保险得和保险订单绑定」
|
||||
> **前端编辑弹窗交互零改动**——年险五件套照旧手填,绑定由后端自动完成。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **编辑/新增司机选「全年保险」保存后,后端自动在保险订单台账生成一张 MANUAL 来源的年保单**(保险管理→保险订单可见),保单号/保费/起止照搬档案录入值,状态=已投保(INSURED)。
|
||||
2. **司机详情 insurance 块新增出参 `insuranceOrderId`**(字符串,关联的台账单 ID;非 annual 或联动未完成时为 null)——前端可用它跳转保险订单详情。联动是**提交后异步**完成(秒级),保存接口响应里不会立即有值,刷新详情即可。
|
||||
3. **再次编辑年险信息(保费/起止/保单号)→ 同一张台账单原地更新**,不会重复开单。
|
||||
4. **保险类型从「全年保险」切走(或删除司机)→ 台账单自动作废(CANCELLED)+ 解除关联**。
|
||||
5. **MANUAL 年保单不能在保险订单页退保**:点退保返 `code=540224`「手工录入的司机年保单请在司机档案中维护」——它没有保游网外部单,生命周期跟司机档案走。
|
||||
6. **保险订单列表/详情新增出参**:`source`(BAOYOU=保游网出单 / MANUAL=手工录入年保)、`bizType`(ORDER=订单险 / DRIVER=司机险)、`bizId`(字符串;DRIVER 行=司机ID)——列表页可按来源/归属区分展示(建议 MANUAL 行隐藏「退保」按钮)。
|
||||
7. **口径**:年险司机在保障期内再走「司机险投保」(保游网按天)会被 `540032` 保障期重叠拦截——年险已覆盖全年,按天投保=重复投保;确需投保请先把档案保险类型切走(台账单自动作废)再投。
|
||||
|
||||
## 1. 司机编辑/新增(§3.3/§3.4) — 入参零变化
|
||||
|
||||
入参与原来完全一致(type=annual + policyNo/annualPremium/annualStart/annualEnd 条件必填),无新增字段。
|
||||
|
||||
## 2. 司机详情(§3.2) — insurance 块新增 1 出参
|
||||
|
||||
```json
|
||||
{
|
||||
"insurance": {
|
||||
"type": "annual",
|
||||
"policyNo": "PICC2026X04",
|
||||
"annualPremium": "3400.00",
|
||||
"annualStart": "2026-01-01",
|
||||
"annualEnd": "2026-12-31",
|
||||
"insurancePlanId": null,
|
||||
"insuranceOrderId": "2068331660551747586" // 🆕 关联台账单ID(字符串;非annual/未联动=null)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 保险订单列表与详情 — 新增 3 出参(测试服实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"insuranceOrderId": "2068331660551747586",
|
||||
"extPolicyNo": "PICC2026X04",
|
||||
"totalPremium": 3400.0,
|
||||
"status": "INSURED",
|
||||
"source": "MANUAL", // 🆕 BAOYOU=保游网出单 MANUAL=手工录入(司机年保档案联动)
|
||||
"bizType": "DRIVER", // 🆕 ORDER=订单险 DRIVER=司机险
|
||||
"bizId": "2065272153565020161" // 🆕 归属业务ID(DRIVER=司机ID,字符串)
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 端到端实测数据点(可作联调预期)
|
||||
|
||||
| 操作 | 结果 |
|
||||
|---|---|
|
||||
| 编辑司机设 annual 保存 | 200;~2 秒后详情 insuranceOrderId 有值;台账出现 MANUAL/DRIVER/INSURED 单 |
|
||||
| 再编辑改保费 3400→3800 | 同一张台账单 totalPremium 更新,单号不变 |
|
||||
| 保险订单页对 MANUAL 单点退保 | code=540224 |
|
||||
| 类型切回「无保险」 | 详情 insuranceOrderId=null;台账单变 CANCELLED |
|
||||
| 删除 annual 司机 | 同上自动作废+解绑 |
|
||||
@@ -0,0 +1,63 @@
|
||||
# 【新增·管理后台】保险订单页投保下单支持「人员类型=司机」+ 出单自动绑定司机全年保险
|
||||
|
||||
> **变更类型**:新增入参 + 新增出参 + 前端交互改造(含 2 个搜索下拉)
|
||||
> **影响端**:管理后台(保险订单页 + 司机档案页)
|
||||
> **关联**:工单 #3760 / PR #3770(已合并 dev-v3,测试服已部署实测通过)
|
||||
> **服务**:hl-fleet-service + hl-order-service-v3
|
||||
> **文档**:API-SPEC-FLEET v1.5.68(§3.8 / §13.7.9)/ SRS §9.17.5
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **保险订单页「投保下单」弹窗需加「人员类型」单选:客人(默认)/ 司机**。客人路径现行为零变化(关联订单 orderId 仍必填);司机路径是新交互(见下)。
|
||||
2. **司机路径不调 v3 投保接口**,改调 fleet 的司机投保端点(`POST /admin/fleet/drivers/{driverId}/insurance/purchase`)并传 **`bindAnnual: true`**——出单成功后该保单自动绑定为司机档案的「全年保险」(一个保单只对应一个司机)。
|
||||
3. **被保人不可编辑**:司机路径下被保人=该司机本人一条,后端按司机档案自动组装,前端隐藏被保人编辑区(不要传 insuredPersons)。
|
||||
4. **两个搜索下拉(顺便解决项,wx 指示)**:
|
||||
- 弹窗「选择司机」:搜索下拉,数据源 `GET /admin/fleet/drivers/page?keyword=`(姓名模糊 + 11 位手机全号精确)
|
||||
- 客人路径「关联订单」:输入框改搜索下拉,数据源 `GET /v3/admin/order/list`(keyword 模糊)
|
||||
5. **保险计划下拉(司机路径)**:用 `GET /admin/fleet/drivers/insurance/plan-options`(只列运营已打标 DRIVER/BOTH 的计划)。**当前测试服该列表为空**——需运营先在保险产品侧打标(`PUT /v3/admin/insurance/plans/{planId}/usage-category`,出参 PlanItem 已有 usageCategory 字段可做打标 UI,见 11_3700 changelog 第 5 点,打标 UI 目前尚未建,前端可一并补)。
|
||||
|
||||
## 1. fleet 司机投保接口加 `bindAnnual` 入参
|
||||
|
||||
`POST /admin/fleet/drivers/{driverId}/insurance/purchase`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| planId | string(雪花) | 是 | 保险计划id(从 plan-options 下拉取) |
|
||||
| coverageStartDate / coverageEndDate | string(date) | 是 | 保障起止(司机路径建议按年预填) |
|
||||
| **bindAnnual** | boolean | 否 | **🆕 true=出单成功后自动绑定为司机全年保险**;缺省 false(司机档案页投保入口现行为零变化) |
|
||||
| remark | string | 否 | 备注 |
|
||||
|
||||
请求示例(保险订单页司机路径):
|
||||
|
||||
```bash
|
||||
curl -X POST "https://api.test.1814.love:9443/admin/fleet/drivers/{driverId}/insurance/purchase" \
|
||||
-H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
|
||||
-d '{"planId":"2054773833342451714","coverageStartDate":"2026-07-01","coverageEndDate":"2027-06-30","bindAnnual":true}'
|
||||
```
|
||||
|
||||
绑定效果(bindAnnual=true 出单受理成功后,后端自动完成,前端无需额外调用):司机档案 `insurance.type` 变 `annual`、保费/起止回填、`insuranceOrderId` 指向新保单、`annualSource` 变 `baoyou`;保单号(extPolicyNo)异步出单时为空,**承保回调后自动补填**。
|
||||
|
||||
## 2. 司机档案 insurance 块新增出参 `annualSource`
|
||||
|
||||
`GET /admin/fleet/drivers/{driverId}` 等 insurance 块:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| **annualSource** | string | 🆕 年保来源:`manual`=车管手填自有年保单(默认)/ `baoyou`=保游网出单自动绑定;仅 type=annual 有意义;只读·入参忽略 |
|
||||
|
||||
**前端按 `annualSource=baoyou` 把档案编辑弹窗的年保四件套(保单号/年保费/起止)置为只读**——绑定态下这 4 个字段入参会被后端忽略(保持绑定值);保单号出单中可能为空串属合法态。若要把 baoyou 年险改成手填:先把类型切走(自动解绑)再切回 annual 手填。
|
||||
|
||||
## 3. 行为与错误码
|
||||
|
||||
| 场景 | 结果 |
|
||||
|---|---|
|
||||
| 选了未标司机可用的计划(CUSTOMER) | `540031` 该保险计划未标注为司机可用(测试服已实测) |
|
||||
| 该司机保障期与已有年险/在途保单重叠 | `540032` 拒绝,提示先在司机档案处理 |
|
||||
| 出单成功但本地绑定失败 | 🆕 `600206` 「保险出单成功但年险绑定失败,请勿重复投保…」——**前端务必原样展示该消息**(保单已生效,重复投保会被拦) |
|
||||
| 出单失败(FAILED)/ 保单被退保(CANCELLED) | 后端自动解绑:档案保险回退 `none`(原 perTrip 计划不会恢复,需人工重选),前端无需处理 |
|
||||
| 重复点击投保 | v3 幂等拦截(短窗内重复请求被拒),按错误提示处理即可 |
|
||||
| 编辑档案时类型从 annual(baoyou) 切走 | 仅解绑(档案与保单脱钩),**不自动退保**——退保仍走保单列表/档案保单页手动操作 |
|
||||
|
||||
## 4. 保险订单列表呈现
|
||||
|
||||
司机年险出单后会以 `bizType=DRIVER`、`source=BAOYOU` 出现在保险订单列表(列表 source/bizType/bizId 出参见 11_3748 / 12_3765 changelog),与游客订单险共存。
|
||||
@@ -0,0 +1,47 @@
|
||||
# 【修改接口·管理后台】司机保单列表补 source 出参——MANUAL 年保单「下载/重传保单」按钮收敛
|
||||
|
||||
> 服务:hl-order-service-v3(8086/8186) + hl-fleet-service(8087) | 分支:dev-v3 | PR:#3765 | 已部署测试服并 4 项实测通过(2026-06-12)
|
||||
> 背景:前端反馈——详情抽屉「下载保单/重传保单」按钮对 MANUAL 年保单仍显示,点击提示「保单生成中」不报错但语义偏。本次按约定把 source 带到按天保单列表 VO。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **司机保单列表(`GET /admin/fleet/drivers/{driverId}/insurance/policies`)每行新增出参 `source`**:`BAOYOU`=保游网出单 / `MANUAL`=手工录入(司机年保档案联动)。
|
||||
2. **前端请按 `source === 'MANUAL'` 隐藏「下载保单」「重传保单」按钮**——MANUAL 单无保游网电子保单,真相载体是司机档案附件里的纸质保单影印件。
|
||||
3. **MANUAL 单保留在保单列表中**(确认结论:不该从列表消失——它是司机的真实保险记录,隐藏整行反而像没保险),仅 PDF 相关操作不可用。
|
||||
4. **后端已加双保险**:即使按钮没藏住/直调接口,下载与重传对 MANUAL 单现在返回明确错误码 **540226**「手工录入的年保单无电子保单,请查看司机档案附件中的纸质保单影印件」(替代原「保单生成中请稍后」误导提示),前端可直接 toast `message`。
|
||||
5. 「分享保单」按订单维度查,MANUAL 单天然不出现,无需处理。
|
||||
|
||||
## 1. 保单列表出参变更(实测示例)
|
||||
|
||||
`GET /admin/fleet/drivers/{driverId}/insurance/policies`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"insuranceOrderId": "2068345678901234567",
|
||||
"policyNo": "PICC2026X04",
|
||||
"extPolicyNo": "PICC2026X04",
|
||||
"extOrderNo": null,
|
||||
"totalPremium": "3400.00",
|
||||
"status": "INSURED",
|
||||
"statusLabel": "已承保",
|
||||
"source": "MANUAL", // 🆕 BAOYOU=保游网出单 MANUAL=手工录入年保;MANUAL 须隐藏下载/重传按钮
|
||||
"coverageStartDate": "2026-01-01",
|
||||
"coverageEndDate": "2026-12-31",
|
||||
"insuredPersons": [ { "name": "巴特尔", "idCardNo": "152***********0002" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2. PDF 入口错误码(后端兜底,实测)
|
||||
|
||||
| 调用 | 结果 |
|
||||
|---|---|
|
||||
| `GET /v3/admin/insurance/policy/{id}/download`(MANUAL 单) | `code=540226` 手工录入的年保单无电子保单… |
|
||||
| `POST /v3/admin/insurance/policy/{id}/retry-upload`(MANUAL 单) | `code=540226` 同上 |
|
||||
| BAOYOU 单两接口 | 行为不变 |
|
||||
|
||||
另:管理端「批量上传缺失保单PDF」已自动排除 MANUAL 单(后端内部优化,前端无感)。
|
||||
@@ -0,0 +1,93 @@
|
||||
# 【新增接口·管理后台+司机H5】司机自助 H5「已发放链接管理」5 端点——列表/导出/作废/重发短信/重新生成
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) + hl-user-service(8081/8181) | 分支:dev-v3 | PR:#3777+#3778(补丁) | 工单:#3775 | 已部署测试服并 13 项 E2E 实测通过(2026-06-12)
|
||||
> 背景:API §4.6 此前未实现——车管发出去的 H5 录入链接看不到、不能作废、不能重发。本次补齐完整生命周期管理,同时把链接 URL 收口 Nacos、H5 init 补状态机出参。
|
||||
> 对应原型:车管控制台「链接管理」页。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **管理后台新增「已发放链接管理」页面**(5 个新端点,见 §1-§5),路径前缀 `/admin/fleet/h5/tokens`。
|
||||
2. **链接格式变了**:所有生成的 H5 链接统一为 `https://hr.1814.love/driver-intake?t={token}`(参数名 **`?t=`**,与短信模板一致;原硬编码 `h5.hl-fleet.com/onboard?token=` 作废)。**H5 前端从页面 URL 读 `t` 参数**后调 init。
|
||||
3. **H5 init(`GET /app/h5/driver-onboard/init`)出参新增 4 字段**(司机 H5 需按 `tokenState` 渲染失效文案,见 §6)。
|
||||
4. **§4.1 生成链接出参新增 `pendingId`**(后续作废/重发/重新生成都以 pendingId 为操作键)。
|
||||
5. **测试服公网已可直达 H5 接口**:nginx 已补 `/app/` 代理(此前 `/app/h5/*` 经 9443 会落到 SPA 页面)。
|
||||
6. **已知外部阻塞(不影响接口联调)**:重发短信链路已全通(事件→通知中心→阿里云真实调用,send_log 落档),但阿里云模板 SMS_507390235 的 token 变量类型(链接参数)容不下 32 位 token 被拒发——**需 wx 在阿里云控制台调整模板变量规范后过审**,代码侧零改动。name 为空拒发已后端兜底(空→「师傅」)。
|
||||
7. 60 秒内对同一链接重复点「重发」:接口返回成功但通知中心幂等拒发第二条(防轰炸),属预期。
|
||||
|
||||
## 1. 已发放链接列表(分页)
|
||||
|
||||
`GET /admin/fleet/h5/tokens?mode=&status=&keyword=&page=1&pageSize=20`
|
||||
|
||||
| 入参 | 必填 | 说明 |
|
||||
|---|---|---|
|
||||
| mode | 否 | new=新招 / renew=续签 |
|
||||
| status | 否 | 派生 6 态:`VOIDED`已作废/`APPROVED`已通过/`REJECTED`已驳回/`SUBMITTED`已提交/`EXPIRED`已过期/`VALID_UNSUBMITTED`有效·未提交 |
|
||||
| keyword | 否 | 模糊匹配 姓名 OR token(**不支持手机号**——加密存储无法模糊) |
|
||||
|
||||
实测出参(行):
|
||||
|
||||
```json
|
||||
{
|
||||
"pendingId": "2065414570750455809", // 操作键(字符串防精度丢失)
|
||||
"mode": "new", "modeLabel": "新招",
|
||||
"token": "182ee9dd884e46d8a628bb98a5695252",
|
||||
"name": "", "phone": null, // 手机脱敏返回(138****2345),未填为 null
|
||||
"createTime": "2026-06-12T20:45:29", "expireAt": "2026-06-19T20:45:29",
|
||||
"submittedAt": null, "reviewStatus": "pending",
|
||||
"status": "VALID_UNSUBMITTED", "statusLabel": "有效·未提交",
|
||||
"url": "https://hr.1814.love/driver-intake?t=182ee9dd884e46d8a628bb98a5695252" // 直接复制发司机
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 导出 CSV
|
||||
|
||||
`GET /admin/fleet/h5/tokens/export?mode=&status=&keyword=`(同条件,不分页)
|
||||
|
||||
返回 `text/csv;charset=UTF-8` 文件流(带 UTF-8 BOM,Excel 直开不乱码),列:`类型,token,姓名,手机(脱敏),生成时间,有效期至,状态,审核状态`,文件名 `h5_tokens_{时间戳}.csv`。前端 blob 下载即可。
|
||||
|
||||
## 3. 作废链接
|
||||
|
||||
`POST /admin/fleet/h5/tokens/{pendingId}/void` body(可选):`{ "reason": "司机信息有误" }`
|
||||
|
||||
- 仅「已通过」不可作废(**600304**);其余状态(含已驳回/已提交)均可作废;重复作废幂等返回成功
|
||||
- 作废后司机端立即失效:H5 init 返 `tokenState=voided`、提交/OCR 被 600304 硬拦(实测联动通过)
|
||||
|
||||
## 4. 重发短信
|
||||
|
||||
`POST /admin/fleet/h5/tokens/{pendingId}/resend` body(可选):`{ "phone": "13808612345" }`(覆盖收件号,生成时没填手机的链接由此补)
|
||||
|
||||
- 仅「有效·未提交」「已驳回」可重发,否则 **600308**;入参与档案手机号均空 → **600307**
|
||||
- **不刷新有效期**(要延长走重新生成)
|
||||
- 出参仅 `{ "maskedPhone": "138****2345" }`——短信异步发出,**接口成功≠短信已到**,到达结果看通知中心发送日志
|
||||
|
||||
## 5. 重新生成
|
||||
|
||||
`POST /admin/fleet/h5/tokens/{pendingId}/regenerate`(无 body)
|
||||
|
||||
- 「已提交」「已通过」不可重新生成(**600309**);其余(含已过期/已作废)可
|
||||
- 旧链接自动作废(审计留痕),返回**全新** pendingId/token/url/expireAt/qrCodeUrl(出参同 §4.1 生成)
|
||||
- **新链接不自动发短信**——车管复制 url 或对新 pendingId 点重发
|
||||
|
||||
## 6. 司机 H5 init 出参新增 4 字段(H5 前端)
|
||||
|
||||
`GET /app/h5/driver-onboard/init?token=`
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| tokenState | `fresh`首次可填/`editable`已提交待审可覆盖重提/`rejected_editable`已驳回可改重提/`expired`已过期/`voided`已作废/`consumed`已通过(终态)/`invalid`无效 |
|
||||
| reviewStatus | pending / approved / rejected |
|
||||
| rejectReason | 驳回原因(驳回相关态才有值,指导司机改哪) |
|
||||
| lastSubmittedAt | 上次提交时间(已提交过才有值) |
|
||||
|
||||
- `tokenValid` 保留向后兼容(= tokenState ∈ fresh/editable/rejected_editable);**修了一个旧 bug**:审核通过后司机再打开,旧实现误返 tokenValid=true(填完 8 步提交才被拦),现在 init 即返 false + `consumed`
|
||||
- H5 请按 tokenState 渲染失效文案(已作废/已过期/已通过 区分展示)
|
||||
|
||||
## 7. 错误码速览
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 600304 | 链接已作废(作废后的 H5 提交/OCR 也返此码) |
|
||||
| 600307 | 无可用手机号 |
|
||||
| 600308 | 仅有效未提交或已驳回的链接可重发 |
|
||||
| 600309 | 已提交或已通过的链接不可重新生成 |
|
||||
| 600402 | 待审核记录不存在 |
|
||||
@@ -0,0 +1,29 @@
|
||||
# 【修改接口·管理后台】H5 录入链接短链化——生成/列表出参新增 shortUrl + 公开短链跳转端点
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) + hl-user-service(8081/8181) | 分支:dev-v3 | PR:#3780 | 工单:#3779 | 已部署测试服并 5 项 E2E 实测通过(2026-06-12)
|
||||
> 背景:阿里云短信模板的「链接参数」变量容不下 32 位 token 致重发短信拒发,wx 拍板短链解决;测试/正式域名按环境配置(Nacos 双键)。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **§4.1 生成 / §4.6.5 重新生成 / §4.6.1 列表行 出参新增 `shortUrl`**(如 `https://web.test.1814.love:9443/s/umB0153d`,8 位短码):管理端「复制链接」建议优先复制 shortUrl(短信同款入口);**老数据行 shortUrl 可能为 null**(历史链接未补码,重发时后端自动懒补),为 null 时降级用 `url` 全链。
|
||||
2. **H5 端零改动**:短链 302 跳回 `{H5页}?t={token}` 长链,H5 仍从 URL 读 `t` 调 init;无效短码跳 `?t=invalid`(init 渲染「链接无效」)。
|
||||
3. **CSV 导出列不变**。
|
||||
4. 重发短信的链路状态:fleet 已发 8 位 code 参数(send_log 实证),**待 wx 在阿里云控制台改模板 SMS_507390235**(固定 URL 换 /s/ 形式+变量 ${token}→${code})过审后短信即可真实出件;过渡期重发接口正常返回、短信渲染失败仅后台留痕。
|
||||
|
||||
## 1. 出参变更示例(实测)
|
||||
|
||||
`POST /admin/fleet/h5/token`
|
||||
|
||||
```json
|
||||
{
|
||||
"pendingId": "2065430143865667585",
|
||||
"token": "32位长token...",
|
||||
"url": "https://hr.1814.love/driver-intake?t={token}", // 全链(二维码/H5 直开用)
|
||||
"shortUrl": "https://web.test.1814.love:9443/s/umB0153d", // 🆕 短链(短信同款,优先复制这个)
|
||||
"expireAt": "2026-06-19T21:45:00", "qrCodeUrl": "data:image/png;base64,..."
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 新公开端点(无鉴权,前端无需对接,知悉即可)
|
||||
|
||||
`GET /s/{code}`(nginx)→`/app/h5/s/{code}`(网关):302 跳 onboard 长链;限流 30 次/60s 防枚举。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 【行为变更·管理后台+司机H5】司机驾驶证号必填(四入口收口)+ 驾驶证等级请补渲染(字段一直在返)
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3783 | 工单:#3781 | 已部署测试服并 8 项 E2E 实测通过(2026-06-12)
|
||||
> 背景:wx——「司机的驾驶证号必填 现有的司机数据都需要补 还有驾驶证等级也没显示」。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **新增司机表单:驾驶证号/准驾车型/驾照有效期 三项改必填**(加 `*` 必填标记,对齐原型;发证机关仍选填)。缺任一项后端返 **400**(message 指明哪项)。
|
||||
2. **编辑司机:新错误码 600207**「驾驶证号不能为空,请在编辑时补录」——只对**库内证号为空的存量档案**触发(编辑时必须补录明文证号才能保存,这就是存量补数机制);证号已有值的司机,编辑回传详情拿到的脱敏值(含 `*`)或干脆不传 license 块都照常放行(=未修改)。前端 toast message 即可,无需特判。
|
||||
3. **驾驶证等级「没显示」是前端渲染缺口,后端字段一直在返,请补渲染**:
|
||||
- 列表行:`licenseType`(如 "A2",测试服 20/20 司机有值)
|
||||
- 详情:`license.type`;编辑表单等级下拉数据源=**字典 driving_license_class**(16 国标类:A1/A2/A3/B1/B2/C1/C2/C3/C4/C5/C6/D/E/F/M/N,sys_dict 已种,labels 如「A1(大型客车)」)
|
||||
4. **批量导入**:新建行(身份证号未命中)驾照号/准驾车型/驾照有效期三列必填,缺→该行报错不阻断他行;**续签行留空=保持原值**(但库内证号也为空时该行报错);模板「填写说明」已同步。
|
||||
5. **司机自助 H5 新招提交**:`licenseNo` 必填(缺失 400)——OCR 识别本就回填证号,正常流程无感;H5 表单驾驶证号输入框请加必填标记。
|
||||
|
||||
## 实测(测试服,8 项)
|
||||
|
||||
| 场景 | 结果 |
|
||||
|---|---|
|
||||
| 创建缺 license 块 / 缺证号 | 400 |
|
||||
| 三件套齐全创建 | 200 |
|
||||
| 存量缺号档案编辑不补号 / 回传脱敏串 | **600207** |
|
||||
| 补明文证号编辑 | 200 |
|
||||
| 证号有值后不传 license 块编辑 | 200(放行) |
|
||||
| H5 submit/new 缺 licenseNo | 400 |
|
||||
|
||||
注:测试服现有 20 个司机证号/等级数据完整,600207 在真实缺号数据出现前不会触发。
|
||||
@@ -0,0 +1,56 @@
|
||||
# 【前端 Bug·管理后台】车辆批量导入:后端成功但前端误报「导入失败」(responseType=blob 读 responseText 抛错)
|
||||
|
||||
> 页面:车务管理 → 车辆档案 → 车辆列表 → 批量导入 | 接口:`POST /admin/fleet/vehicles/import` | 负责:mmg
|
||||
> 严重度:P1(功能可用但误导用户重复导入,撞车牌唯一约束/重复绑常驻)
|
||||
|
||||
## 现象
|
||||
|
||||
用户用最新模板(12 列,含常驻司机手机号)批量导入 5 台车,前端弹「导入失败」,但**实际后端已全部成功**:
|
||||
|
||||
- DevTools Network 里 `import` 请求 Response:`code:200, insertedCount:5, errorCount:0, success:true`
|
||||
- 车辆列表 `共 16 辆` → `共 21 辆`(+5),5 台车 + 常驻司机绑定全部落库正确
|
||||
- Console 报错:`InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest'. The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').`
|
||||
|
||||
## 根因(纯前端)
|
||||
|
||||
导入按钮的 XHR/axios 请求把 `responseType` 设成了 `'blob'`——几乎可以确定是从**同页「下载模板」按钮**的代码复制来的(下载模板返回 .xlsx 文件流,确实需要 `responseType:'blob'`)。但**导入接口返回的是 JSON**(`Content-Type: application/json`,`Result<VehicleImportRespVO>` 包络),前端拿到响应后去读 `responseText` 解析 JSON,而浏览器规定 `responseType` 为 `'blob'` 时禁止访问 `responseText` → 抛 `InvalidStateError` → 被 catch 当成请求失败 → 弹「导入失败」。
|
||||
|
||||
→ HTTP 其实是 200、业务也成功,**纯前端响应解析配置错误**,与后端无关。
|
||||
|
||||
## 后端契约(未变,供前端对照)
|
||||
|
||||
| 端点 | 方法 | 响应类型 | responseType 应设 |
|
||||
|---|---|---|---|
|
||||
| `/admin/fleet/vehicles/import` | POST(multipart 上传文件) | **JSON**(`Result<VehicleImportRespVO>`) | `''` 或 `'json'`(**不要 blob**) |
|
||||
| `/admin/fleet/vehicles/import/template` | GET(下载模板) | **二进制 .xlsx 文件流**(`Content-Disposition` 附件) | `'blob'`(保持) |
|
||||
|
||||
导入成功响应示例(前端应据此渲染导入结果弹窗):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"totalRows": 5,
|
||||
"insertedCount": 5,
|
||||
"updatedCount": 0,
|
||||
"errorCount": 0,
|
||||
"errors": [],
|
||||
"warnedCount": 0,
|
||||
"warnings": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
部分失败时 `errors[]` 含 `{row, plate, msg}`、`warnings[]` 含 `{row, plate, field, msg}`,建议前端按这两个数组渲染「成功 N 条 / 失败 M 条(逐行原因)/ 警告 K 条」明细,而不是只弹一句成功或失败。
|
||||
|
||||
## 修复建议(前端)
|
||||
|
||||
1. **导入请求单独配置**:导入用默认 `responseType`(`''`/`'json'`),不要复用下载模板的 `blob` 配置——两个按钮的 axios 调用分开写。
|
||||
2. **成功判定改看业务结果**:以 `data.errorCount`/`data.errors` 渲染明细,别只凭「请求是否抛异常」判断成败。
|
||||
3. 回归:导入成功(全成)、部分失败(含 errors)、全失败 三种响应都要能正确解析渲染。
|
||||
|
||||
## 临时说明(给已踩坑的同学)
|
||||
|
||||
看到「导入失败」请先刷新车辆列表确认是否其实已导入——若已在列表里,说明是本 Bug,**不要重复导入**(重复上传同车牌会走 UPDATE 不会重复建,但若手动改了车牌重传会造成重复车 + 常驻司机 1:1 冲突报错)。
|
||||
@@ -0,0 +1,46 @@
|
||||
# 【前端缺口·管理后台】车辆档案列表缺「关键词搜索框」——后端 keyword 早已支持,前端补输入框即可
|
||||
|
||||
> 页面:车务管理 → 车辆档案 → 车辆列表 | 接口:`GET /admin/fleet/vehicles/page`(已上线,无需改动) | 负责:mmg
|
||||
> 类型:前端补 UI(纯前端,后端零改动)
|
||||
|
||||
## 现象 / 诉求(wx)
|
||||
|
||||
车辆列表筛选区现在只有 4 个下拉框(所属车队 / 车型大类 / 所有权性质 / 生命周期),**没有文本搜索框**,车多了没法按车牌快速定位。诉求:至少能按车牌搜索。
|
||||
|
||||
## 结论:后端早就支持,是前端没放输入框
|
||||
|
||||
`GET /admin/fleet/vehicles/page` 的 **`keyword`** 入参已上线(API §2.1,v1.5.51 落地):**车牌 OR 车型名称 模糊匹配**(一个框同时搜车牌和车型)。前端只需在筛选区加一个搜索输入框,绑定到 `keyword` 查询参数即可,**后端不用任何改动**。
|
||||
|
||||
测试服实测(admin token 经网关):
|
||||
|
||||
| keyword | 命中 | 说明 |
|
||||
|---|---|---|
|
||||
| `蒙AD2001` | 1 条(蒙AD2001) | 车牌精确 |
|
||||
| `蒙AD1` | 5 条(蒙AD1001~1005) | 车牌前缀 |
|
||||
| `别克` | 5 条(含蒙AD2002/蒙C09E09 等别克GL8) | 车型名模糊 |
|
||||
|
||||
## 前端改法
|
||||
|
||||
筛选区加一个文本输入框(建议放在「所属车队」下拉前,或筛选区最左),placeholder 例如「车牌 / 车型」,绑定 `keyword`:
|
||||
|
||||
```
|
||||
GET /admin/fleet/vehicles/page?pageNo=1&pageSize=24&keyword=蒙AD2001
|
||||
```
|
||||
|
||||
- `keyword` 可选,传空 / 不传 = 不参与过滤(与现有 4 个下拉同口径,一起拼进 query)
|
||||
- 与现有「搜索 / 重置」按钮联动:点搜索带上 keyword,点重置清空
|
||||
- 后端已做 trim + 空白忽略,前端不必额外处理
|
||||
|
||||
## 入参全集(§2.1,供前端对照)
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| keyword | string | 否 | **车牌 / 车型 模糊搜索(本次要加的输入框)** |
|
||||
| fleet | string | 否 | 车队 own/coopA/coopB(已有下拉) |
|
||||
| ownerType | string | 否 | 所有权 company/personal(已有下拉) |
|
||||
| typeKey | string | 否 | 车型大类 key(已有下拉) |
|
||||
| serviceStatus | string | 否 | 生命周期 ACTIVE/DISABLED(已有下拉) |
|
||||
| vehicleStatus | string | 否 | 车辆状态 idle/busy/maint(对应「空闲/在用/维保中」tab) |
|
||||
| pageNo / pageSize | int | 否 | 分页 |
|
||||
|
||||
> 注:司机档案列表(`/admin/fleet/drivers/page`)的 `keyword` 口径不同——姓名模糊 + 手机 11 位全号精确(AES 密文等值,不支持手机模糊),若司机列表也要加搜索框照此说明。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 【后端核实回执·管理后台】保单按钮两处收敛 + 车辆导入弹窗映射——前端 SYNC-20260612-10 全部确认,无接口变更
|
||||
|
||||
> 服务:hl-order-service-v3(8086/8186) + hl-fleet-service(8087) | 对应前端:SYNC-20260612-10(commit f642b064) | 后端无代码改动,本文是核实回执(2026-06-12)
|
||||
> 背景:前端回报「下载/重传保单」真实落点在保险订单台账页(非司机抽屉),已两处按 source 收敛;并修车辆导入结果弹窗字段映射;质疑 blob 归因。后端逐项核实如下。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **前端两处收敛方案后端全部确认正确,双方版本在测试服已对齐**,前端无需再动。
|
||||
2. 此前 changelog 12_3765 写按钮在「详情抽屉」系沿用最初反馈原话,实际落点在台账页——契约指引(按 `source` 收敛)两处通用,结论不变。
|
||||
3. blob/InvalidStateError 议题双方归零关闭(见 §3)。
|
||||
|
||||
## 1. 保单按钮收敛——后端契约逐项核实(均实测)
|
||||
|
||||
| 前端收敛点 | 后端支撑 | 实测结论 |
|
||||
|---|---|---|
|
||||
| 台账页 MANUAL 行隐藏「下载/重传保单」 | `GET /v3/admin/insurance/list` 列表行 VO 自带 `source`(BAOYOU/MANUAL) | 测试服实测每行 source 非 null;直调下载/重传返 **540226** 兜底 |
|
||||
| 台账页 MANUAL 行隐藏「退保」(上批已收敛) | 管理端退保接口拦 MANUAL | 返 **540224**「手工录入的年保单不支持退保」 |
|
||||
| 司机抽屉 MANUAL 行隐藏「退保」+ 加「手工录入」标识 | `GET /admin/fleet/drivers/{driverId}/insurance/policies` 每行 `source` 字段即为此用 | 与 540224 守卫严丝合缝 |
|
||||
|
||||
台账列表实测示例(`GET /v3/admin/insurance/list?page=1&pageSize=10`):
|
||||
|
||||
```json
|
||||
{
|
||||
"insuranceOrderId": "2065360048397193217",
|
||||
"source": "MANUAL", // 每行均返,非 null
|
||||
"bizType": "DRIVER",
|
||||
"status": "CANCELLED" // 该批 CANCELLED 是后端 E2E 清场数据,属正常
|
||||
}
|
||||
```
|
||||
|
||||
注意:**判断按钮显隐请只认 `source`,勿用 `bizType` 当代理**——司机行程险走保游网出单时 `bizType=DRIVER` 但 `source=BAOYOU`,有电子保单,下载/重传按钮应保留。
|
||||
|
||||
## 2. 车辆导入结果弹窗映射——前端重写目标字段与后端 VO 完全一致
|
||||
|
||||
后端 `VehicleImportRespVO` 真实字段(核实于当前 dev-v3):
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `totalRows` | int | 数据总行数 |
|
||||
| `insertedCount` | int | 新增成功数 |
|
||||
| `updatedCount` | int | 更新成功数 |
|
||||
| `errorCount` | int | 失败行数 |
|
||||
| `errors` | array | 失败明细(行号+原因) |
|
||||
| `warnedCount` | int | 警告行数 |
|
||||
| `warnings` | array | 警告明细 |
|
||||
|
||||
前端旧映射(failed/errorRows/success/total)确实全部不存在于后端 VO,「导入成功 0 条」怪象由此而来,重写后正确。司机导入 `DriverImportRespVO` 字段结构同族,如需对照可看测试服 doc.html。
|
||||
|
||||
## 3. blob/InvalidStateError 议题关闭
|
||||
|
||||
- 前端全仓核查无 `responseType:'blob'` 配置;后端侧该归因同样无代码对应物——此前那条「前端Bug_车辆批量导入responseType-blob」changelog 的归因有误,以本回执为准。
|
||||
- 真实病因即 §2 的弹窗字段映射 bug,已修。
|
||||
- 若真机后续仍复现该 console 报错,排查方向是测试服部署的前端构建来源(是否旧构建残留),与双方当前代码无关。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 【环境变更·全员】测试服 API 文档站(doc.html)登录改 Gitea 账号 SSO——登录一次 30 天免登录
|
||||
|
||||
> 站点:https://api.test.1814.love:9443/doc.html | 生效:2026-06-12 | 无 PR(测试服 nginx + oauth2-proxy 配置变更,不涉及业务代码)
|
||||
> 已端到端实测:Gitea 登录→授权→文档加载→二次访问纯 Cookie 免登录
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **文档站专用账号(hulalv)停用**:访问 doc.html 不再弹浏览器账号密码框,改为跳转 Gitea 登录页,**用你自己的 Gitea(git.1814.love)账号登录**。
|
||||
2. **浏览器已登录 Gitea 的话全程无感**:直接跳回文档页,一个键都不用按。
|
||||
3. **首次使用会出现一次「授权 knife4j-api-docs-sso 访问您的帐户?」确认页**:点「应用授权」即可,每个账号只出现这一次。
|
||||
4. **登录一次 30 天免登录**(Cookie 有效期 720h,期间自动静默续期),浏览器重启也不用重新登录。
|
||||
5. **API 调用本身零影响**:/admin/、/mp/、/v3/ 等接口的认证方式不变,仅 doc.html、/webjars/、/v2/api-docs、/swagger-resources 四个文档路径换了认证。
|
||||
|
||||
## 1. 行为对照
|
||||
|
||||
| 场景 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 打开 doc.html | 浏览器弹 Basic Auth 框,输文档专用账号 | 跳 Gitea 登录页(已登录 Gitea 则无感直进) |
|
||||
| 浏览器重启后 | 重新弹框输密码 | Cookie 30 天内免登录 |
|
||||
| 账号管理 | 共享一个 hulalv 账号 | 各自 Gitea 账号(cz/liutao/mmg/wx/yst 均可用) |
|
||||
|
||||
## 2. 遇到问题
|
||||
|
||||
- 跳转后 Gitea 登录不上:确认用的是 git.1814.love 的账号密码(忘记密码可在 Gitea 页面重置)。
|
||||
- 登录后报错或循环跳转:联系 wx(服务端 oauth2-proxy 日志可查)。
|
||||
@@ -0,0 +1,22 @@
|
||||
# 【新增+修改接口·管理后台+司机H5】车务排查批量修复 11 项——待审详情端点/H5 车型下拉源/列表字段 Label 补齐/续签赛季 bug 修复
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3785 | 工单:#3784 | 已部署测试服并 12 项 E2E 实测通过(2026-06-13)
|
||||
> 背景:wx 指派排查「按逻辑应该有的小功能没有」(如输入框该用下拉搜索框),audit workflow 36 agents 五维度扫描+逐条对抗验证,确认 15 项中 11 项契约有据立即修。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. 🆕 **待审核单条详情** `GET /admin/fleet/drivers/pending/{id}`:审核抽屉数据源(证件原图 URL/驾照明细/紧急联系人/自带车块/驳回原因,PII 脱敏),不存在返 600402。**审核页"查看详情"抽屉可以接了**。
|
||||
2. 🆕 **H5 init 下发 `vehicleTypeOptions`**(仅 mode=new):`[{typeKey,typeName,icon,models:[{modelName,seats}]}]`——H5 新招「常开车型」步骤的**唯一合法数据源,禁前端写死大类**;renew 模式不返。
|
||||
3. **待审核列表新增出参**:`drivingYears/licenseClass/licenseNo(脱敏)/licenseExpire`(卡片直显初判)+ `modeLabel/reviewStatusLabel`(新招·续签/待审核·已通过·已驳回,不用再硬编码映射)。
|
||||
4. **司机列表新增出参**:`insuranceTypeLabel`(全年保险/按行程投保/无保险)+ `insuranceAnnualEnd`(年保到期日,仅 annual 行有值);**详情 insurance 块新增** `typeLabel/annualSourceLabel`(手工录入/保游网出单)。
|
||||
5. **司机列表新增入参** `onlyResidentUnbound=true`:仅返回未被任何车设为常驻的司机——**车辆表单选常驻司机的下拉数据源**(避免选了已占用司机保存才报 605022)。
|
||||
6. **车辆新增/编辑 fleet/ownerType 行为变更**:后端经字典归一校验——中文标签(如「公司所有」「自有」)也能传、自动归一为字典值落库;非法值返 **100001**(原任意字符串直落库)。
|
||||
7. **司机编辑新增入参** `blacklistReason`(可选≤256):仅本次把 season 转入 blacklist 时消费并自动落拉黑时间,**拉黑弹窗可以加原因输入框了**;详情/列表 blacklistReason 出参不再恒 null。
|
||||
8. **vehicleSource 行为变更**:仅接受 own/company,其余值 400(admin 编辑+H5 提交两处)。
|
||||
9. **续签赛季 bug 修复(行为变更)**:司机 H5 续签勾「今年休息」(continueSeason=false)后审核通过,season 现在正确落 **archived**(原 bug 无条件落 active);勾继续合作仍落 active+追加在册年。
|
||||
|
||||
## 实测(测试服 12 项)
|
||||
|
||||
详情端点 200+600402 / init 车型选项无 basePrice / 列表新字段 Label 全出 / onlyResidentUnbound 排除准确 / 中文标签归一+脏值 100001 / vehicleSource 脏值 400 / 拉黑原因落库回显 / 续签 false→archived 真链路(H5 提交→审核通过)。
|
||||
|
||||
注:另有 2 项排查发现待 wx 拍板(紧急联系人「关系」字典种子口径 / 司机标签聚合清单端点),拍板后另行通知。
|
||||
@@ -0,0 +1,19 @@
|
||||
# 【模板优化·管理后台】车辆/司机导入模板示例行图片列内嵌占位示例图
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3790 | 工单:#3789 | 已部署测试服并下载实测通过(2026-06-13)
|
||||
> 背景:wx——「下载模板应该有示例图片」。导入模板末 8 列为内联图列(#3769 起支持格内插图),但下载下来的模板示例行(黄色「示例-」行)这些图列是空白格,用户不知道要往这些格里插图、也不知道插完长什么样。
|
||||
|
||||
## ⚠️ 关键说明(前端)
|
||||
|
||||
**前端无需任何改动。** 模板下载端点(`GET /admin/fleet/vehicles/import/template`、`GET /admin/fleet/drivers/import/template`)契约/响应类型/调用方式完全不变——只是后端生成的 .xlsx 内容更友好了:示例行的图片列现在各内嵌一张「占位示例图」。
|
||||
|
||||
## 改了什么
|
||||
|
||||
- **车辆模板**(20 列,末 8 图列:行驶证正面照/副页照/外观照/内饰照/保险单据照/年检单据照/营运证照/其他附件照):黄色示例行的这 8 个图列各内嵌一张占位示例图。
|
||||
- **司机模板**(23 列,末 8 图列:身份证照/驾驶证照/肖像照/健康证照/培训证书照/荣誉奖励照/从业资格证照/其他附件照):同样内嵌。
|
||||
- 占位图是一张通用「图片占位」图标(灰色边框 + 太阳 + 远山,无文字),提示用户「这一列要在格内插入对应证件照片,插完就是覆盖这张占位图的样子」。
|
||||
- **解析行为零变化**:示例行整行仍由首列「示例-」前缀在导入时自动跳过,内嵌的占位图既不会被回导成数据、也不会被抽取入库(双重隔离);用户照常从下一行开始填真实数据、在图列格内插真实照片。
|
||||
|
||||
## 实测(测试服)
|
||||
|
||||
下载车辆/司机模板各得 8 张占位图,锚定在示例行(第 2 行)的图片列上;占位图视觉正常(无乱码)。端点契约不变,零 DDL、零路由、零新端点。
|
||||
@@ -0,0 +1,21 @@
|
||||
# 【行为变更·管理后台】司机待审核列表只回真正提交的记录——过滤未提交空壳 + 已作废链接
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3791 | 已部署测试服并 API 实测通过(2026-06-13)
|
||||
> 背景:wx 反馈「待审核页数据全空」——卡片姓名/身份证/提交时间/照片全空。根因=列表把「司机点了 H5 链接但还没填写提交」的占位空壳、以及已作废链接也捞进了审核队列。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **`GET /admin/fleet/drivers/pending` 行为变更(无需改前端代码,数据自动变干净)**:列表现在只返回**真正提交过且链接未作废**的记录,与「我的链接」派生态 `SUBMITTED` 同口径。两条恒生效基线过滤:
|
||||
- `submitted_at IS NOT NULL`——排除「生成链接但司机未填写提交」的占位空壳(生成链接那一刻就落了一行 pending+未提交的占位行)
|
||||
- `void_status='NORMAL'`——排除已作废链接(作废与审核正交,死链不进审核队列)
|
||||
2. **现象解释**:测试服修复前待审核 tab 有 8 张全空卡片(全是未提交空壳),修复后为 0 条(当前确实没有真正提交待审的司机);已驳回 tab 从 6 条(含 1 条异常空壳)变 5 条真实记录。**待审核 tab 看起来"变空"是正确的**——空壳本就不该出现。
|
||||
3. **「申请详情页」后端已就绪**:审核详情端点 `GET /admin/fleet/drivers/pending/{pendingId}`(PR#3785 已落)返回证件原图 URL / 驾照正副页 / 紧急联系人 / 自带车实拍照 / 备注(PII 脱敏),不存在返 600402。**审核卡片的「查看详情」抽屉可以接这个端点**看全量明细。
|
||||
|
||||
## 实测(测试服)
|
||||
|
||||
修复前: 待审核 8 条全 submittedAt=null 空壳 / 全部状态 15 条含空壳。
|
||||
修复后: 待审核 0 条 / 全部状态 6 条(approved 1+rejected 5)全部 submittedAt 非空 / 已驳回 5 条真实记录(姓名+提交时间齐全)。
|
||||
|
||||
## 影响面
|
||||
|
||||
仅列表查询行为变更,approve/reject/详情端点不受影响;零接口签名变化、零 DDL、零路由。
|
||||
@@ -0,0 +1,30 @@
|
||||
# 【新增接口·管理后台】司机风控拉黑 + 解封两个独立端点——司机卡片可加「拉黑/解封」按钮
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3792 | 已部署测试服并 API 往返实测通过(2026-06-13)
|
||||
> 背景:wx 反馈「司机卡片没有拉黑按钮」。原先拉黑只能走通用编辑端点(PUT 设 season=blacklist),且全量 VO 前端不回传 attachments/tags 会误删、解封更是无任何端点。wx 拍板补独立端点。(车辆拉黑 wx 已撤回·看原型,本次只做司机)
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. 🆕 **拉黑** `POST /admin/fleet/drivers/{driverId}/blacklist`
|
||||
- 入参 body:`{"blacklistReason": "客诉原因..."}`(原因**可选**,≤256;不传后端落空串)
|
||||
- 出参:`{"code":200,"message":"成功"}`
|
||||
- 规则:active/pending/archived 司机均可拉黑;已是黑名单返 **600202**;司机不存在返 **600205**
|
||||
- **司机卡片加「拉黑」按钮**(弹框让操作人填可选原因)调本端点。后端只定向写 season+拉黑原因+拉黑时间三列,**不碰附件/标签**(不再有误删风险)——比之前走编辑端点 PUT 的方案安全,请改用本端点。
|
||||
2. 🆕 **解封** `POST /admin/fleet/drivers/{driverId}/unban`
|
||||
- 入参:无 body(仅路径参数)
|
||||
- 出参:`{"code":200,"message":"成功"}`
|
||||
- 规则:当前必须是黑名单(否则返 **600208**「司机当前不是黑名单状态,无需解封」);解封后 season 回 **active** + 清空拉黑原因/时间
|
||||
- **黑名单 tab 的司机加「解封」按钮**调本端点。
|
||||
3. **拉黑/解封会联动车辆**:拉黑→该司机常驻个人车自动停用(service_status=DISABLED);解封→恢复 ACTIVE。
|
||||
|
||||
## ⚠️ 已知缺口(知会)
|
||||
|
||||
解封的「主管级权限」门禁**暂未加**(hl-fleet 当前无 RBAC 框架、hl-user 未配 fleet 角色),当前任何登录管理员都能解封,待 hl-user 配 supervisor 角色后接入。前端解封按钮可正常对接,权限收紧是后端后续动作。
|
||||
|
||||
## 实测(测试服往返)
|
||||
|
||||
拉黑(中文原因)→200+season=blacklist+原因落库 / 解封→200+season=active+原因清空 / 非黑名单再解封→600208 / 不存在司机→600205 / 重复拉黑→600202。
|
||||
|
||||
## 影响面
|
||||
|
||||
错误码新增 600208;网关 `/admin/fleet/**` 通配已覆盖;零 DDL。原 PR#3785 提到的「编辑端点 blacklistReason 入参」仍保留,但**拉黑按钮请改用本专用端点**(避免全量 VO 误删)。
|
||||
@@ -0,0 +1,23 @@
|
||||
# 【行为变更·管理后台】编辑司机·全年保险「保单号」从手填改为下拉关联该司机已出的保游网保单
|
||||
|
||||
> 服务:hl-fleet-service(8087/8187) | 分支:dev-v3 | PR:#3793 | 已部署测试服(下拉数据源端点 200 实测可达,完整投保流待前端联调)(2026-06-13)
|
||||
> 背景:wx 反馈编辑司机弹窗「保险配置·全年保险」的保单号是手填输入框,应改为从该司机已出的保游网保单里下拉选择关联。wx 拍板:改下拉关联,保留手填兜底。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **编辑弹窗「保单号」输入框 → 下拉/搜索选择器**:
|
||||
- 下拉数据源 = `GET /admin/fleet/drivers/{driverId}/insurance/policies`(司机「我的保单」,已有端点);**仅 status=INSURED(已承保)/INSURING(在途出单中)可选**。
|
||||
- 选中某保单后,把该保单的 `insuranceOrderId` 放进保存入参 `insurance.insuranceOrderId` 提交。
|
||||
- 选中后**保单号/年保费/保障起止改为只读回显**(由后端按保单真值回填),前端不再手敲这四项。
|
||||
2. **保存入参 `DriverInsuranceVO.insuranceOrderId` 已放开**(原「入参忽略」):
|
||||
- `insurance.type=annual` 且传 `insuranceOrderId` → 后端按司机维度反查该保单,回填保单号/保费/起止 + 落 `annualSource=baoyou` + 关联 `insurance_order_id`。
|
||||
- 横向越权守卫:传入的保单不属于该司机 / 状态不可关联 → 返 **600209**「关联的保游网保单不存在、不属于该司机或状态不可关联」。前端正常从该司机下拉选不会触发。
|
||||
3. **手填路径保留兜底**(二选一,insuranceOrderId 优先):不传 `insuranceOrderId`、仅手填 `policyNo`+保费+起止时,仍走原手填路径(`annualSource=manual`,行为零变化),用于线下买的、保游网没有的保单。
|
||||
|
||||
## 实测(测试服)
|
||||
|
||||
下拉数据源端点 `/insurance/policies` code=200 可达;反查回填 + 600209 越权路径由单测覆盖(DriverServiceTest+4 / DriverAnnualPolicyLookupImplTest 5,fleet 全套 714 绿);完整「选保单→回填→保存」端到端需前端接入后联调。
|
||||
|
||||
## 影响面
|
||||
|
||||
错误码新增 600209;**零新增 Feign/DTO/order-v3 端点/Flyway/网关路由**(复用现有按司机查保单 Feign);后端架构用 driver 子域端口 + insurance 子域实现解环。
|
||||
@@ -0,0 +1,38 @@
|
||||
# 【后端已修·管理后台】投保下单「关联订单」下拉报「参数类型错误: id='list'」——后端给订单列表补了 `/list` 别名,前端无需再改
|
||||
|
||||
> 服务:hl-order-service-v3(OrderController) | 分支:dev-v3 | PR:#3798(已合并、已部署测试服双实例 8086/8186、网关 9443 往返实测通过) | 2026-06-13
|
||||
> 背景:前端按上一条 changelog 把保险订单**列表**改成 `/v3/admin/insurance/list` 后,「保险订单」页投保下单弹窗里的**「关联订单」下拉**仍报红条 `参数类型错误: id='list'(需要 Long 类型)`,实际失败请求是 `GET /v3/admin/order/list?page=1&pageSize=20`。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **这是和保险列表不同的另一个调用**:「关联订单」下拉搜的是**业务订单**,调的是 `GET /v3/admin/order/list`(order 模块),不是保险列表。
|
||||
2. **根因**:order 模块的订单列表原本只挂在 RESTful 集合根 `GET /v3/admin/order`(没有 `/list`),详情是 `GET /v3/admin/order/{id}`;前端按「{模块}/list」习惯调 `/order/list` 时,`list` 落到 `/{id}` 上当 Long 解析失败 → 报 `id='list'`。order 模块是各 admin 列表里唯一用集合根的「异类」,所以前端必踩。
|
||||
3. **后端已修(PR #3798)**:给订单列表同时挂载根 + `/list` 两条路径。**前端那条现成的 `GET /v3/admin/order/list` 调用现在直接返回 200,无需任何前端改动**,部署后刷新页面红条即消失。
|
||||
|
||||
## ⚠️ 对上一条 changelog 的修正
|
||||
|
||||
上一条(`13_前端修复_保险订单列表调错路径...`)我说「v3 全站 admin 列表统一 `{模块}/list`」——这对保险/合同/退款/评价/支付成立,但 **order 模块当时是个例外(用集合根)**,所以才有这次的 `/order/list` 报错。本次 PR #3798 把 order 也补上了 `/list`,**现在 `{模块}/list` 这个习惯对订单也成立了**,前端可放心统一用 `{base}/list`。
|
||||
|
||||
## 路径现状(部署后)
|
||||
|
||||
| 用途 | 可用路径 | 说明 |
|
||||
|---|---|---|
|
||||
| 保险订单列表 | `GET /v3/admin/insurance/list` | 上条已说明 |
|
||||
| **业务订单列表(关联订单下拉用)** | `GET /v3/admin/order/list` ✅ 与 `GET /v3/admin/order` ✅ | 本次新增 `/list` 别名,两者等价 |
|
||||
| 业务订单详情 | `GET /v3/admin/order/{id}`(id 为 Long) | 不变 |
|
||||
| 订单关键词搜索 | `GET /v3/admin/order/list?keyword=xxx` | `keyword` 匹配 团号/客户姓名/产品名/订单号 任一字段 LIKE |
|
||||
|
||||
## 实测(测试服网关 https://api.test.1814.love:9443,PR #3798 部署后)
|
||||
|
||||
```
|
||||
GET /v3/admin/order/list?page=1&pageSize=20 → {"code":200,"message":"成功","data":{"records":[],"total":0,...}} ✅ 不再报 id=list
|
||||
GET /v3/admin/order?page=1&pageSize=20 → {"code":200,...} ✅ 回归正常
|
||||
GET /v3/admin/order/list?page=1&pageSize=20&keyword=张 → {"code":200,...} ✅ 关键词搜索可用
|
||||
GET /v3/admin/order/123 → {"code":581007,"message":"订单不存在"} ✅ 数字 id 仍正确解析(详情未受影响)
|
||||
```
|
||||
|
||||
> 注:返回 `records:[] / total:0` 是测试库当前没有该 admin 可见的业务订单(数据条件,非接口问题);有真实订单时「关联订单」下拉即有数据。
|
||||
|
||||
## 影响面
|
||||
|
||||
仅 `OrderController.listOrders` 的 `@GetMapping` 由根改为 `@GetMapping({"", "/list"})`,单 handler 双路径;**零新增方法、零行为变化、无 DDL、无 Flyway、无新增错误码**,网关 `/v3/admin/order/**` 通配已覆盖。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 【前端修复·管理后台】保险订单列表页报「参数类型错误: id='list'」——前端调错路径 `/orders/list`,应改为 `/list`
|
||||
|
||||
> 服务:hl-order-service-v3(保险模块 AdminInsuranceController) | 无 PR(前端调错 URL,后端契约正常无需改) | 测试服网关 9443 实测复现并确认(2026-06-13)
|
||||
> 背景:wx 打开「保险管理 → 保险订单」页(`192.168.100.160:9527/insurance/orders`),页面顶部红条报 **「参数类型错误: id='list'(需要 Long 类型)」**。经网关实测复现,定位为前端列表接口 URL 写错。
|
||||
|
||||
## ⚠️ 关键说明(前端动作)
|
||||
|
||||
1. **根因:前端把保险订单列表调成了 `GET /v3/admin/insurance/orders/list`,这个路径后端不存在。**
|
||||
- 后端只有 `GET /v3/admin/insurance/orders/{id}`(按 ID 查保险订单详情,`{id}` 是 Long 类型)。
|
||||
- 前端那个 `.../orders/list` 请求里的 `list` 被 Spring 当成 `{id}` 去解析成 Long,解析失败 → 全局异常处理器返回「参数类型错误: id='list'(需要 Long 类型)」。
|
||||
- 注意:本项目所有接口**恒返 HTTP 200**,业务错误码在响应体 `code` 字段里。所以 Network 面板看到的是 `200`,但 body 是 `{"code":400,"message":"参数类型错误..."}`——别被 200 误导,要看 body。
|
||||
|
||||
2. **正确的列表路径 = `GET /v3/admin/insurance/list`(不带 `/orders`)。**
|
||||
- 这是 v3 全站 admin 列表的统一惯例 `{模块base}/list`:合同 `/v3/admin/contract/list`、退款政策 `/v3/admin/refund-policy/list`、评价 `/list`、支付 `/list`、保险方案 `/list`……保险订单列表同理就是 `/v3/admin/insurance/list`。
|
||||
- 请把保险订单页的列表请求 URL 从 `…/insurance/orders/list` 改成 `…/insurance/list`,**入参/分页参数(page/pageSize/筛选项)完全不变**。
|
||||
|
||||
3. 详情/按订单查仍走 `/orders/...`,不要动:列表是 `/list`、详情是 `/orders/{id}`,这两者本身不冲突,冲突只来自前端给列表错误地拼了 `/orders/` 前缀。
|
||||
|
||||
## 路径对照表
|
||||
|
||||
| 用途 | 现状(错误) | 应改为(正确) |
|
||||
|---|---|---|
|
||||
| 保险订单**列表** | `GET /v3/admin/insurance/orders/list?page=1&pageSize=20` ❌ | `GET /v3/admin/insurance/list?page=1&pageSize=20` ✅ |
|
||||
| 保险订单**详情** | — | `GET /v3/admin/insurance/orders/{id}`(id 为 Long) |
|
||||
| 按业务订单查保险 | — | `GET /v3/admin/insurance/orders/by-order/{orderId}` |
|
||||
|
||||
## 实测(测试服网关 https://api.test.1814.love:9443)
|
||||
|
||||
错误路径(复现用户截图的报错):
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/v3/admin/insurance/orders/list?page=1&pageSize=20" \
|
||||
-H "Authorization: Bearer <adminToken>"
|
||||
# → {"code":400,"message":"参数类型错误: id='list'(需要 Long 类型)","data":null,"success":false}
|
||||
```
|
||||
|
||||
正确路径(返回真实订单数据):
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/v3/admin/insurance/list?page=1&pageSize=20" \
|
||||
-H "Authorization: Bearer <adminToken>"
|
||||
# → {"code":200,"message":"成功","data":{"records":[
|
||||
# {"insuranceOrderId":"2065360048397193217","policyNo":"PICC2026GUARD",
|
||||
# "premium":3400.00,"insuredCount":1,"coverageStartDate":"2026-01-01",
|
||||
# "coverageEndDate":"2026-12-31","status":"CANCELLED","statusLabel":"已退保", ...}
|
||||
# ], "total":..., "page":1, "pageSize":20}}
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
后端列表端点正常、契约就是 `/v3/admin/insurance/list`,**无需后端改动**。请前端把保险订单列表页的请求 URL 改为 `/v3/admin/insurance/list` 即可消除红条报错。
|
||||
|
||||
> 附:本页此前的「保险产品下拉为空」是另一个前端数据加载问题(后端 `/products/all` 实测返 10 个 ACTIVE 产品),详见同目录 `13_前端核实_保险下单页产品下拉空与司机险混淆-后端正常无需改-管理后台.md`。这两个都是该新页面接线/取数的前端问题。
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户