比较提交
400
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
e1b0a667cd | ||
|
|
c3acb6e822 | ||
|
|
8bdd0d9057 | ||
|
|
1eafac1a59 | ||
|
|
ded9289d98 | ||
|
|
119c459808 | ||
|
|
06fa788882 | ||
|
|
a74320185f | ||
|
|
e2b36e1b60 | ||
|
|
3ae7d06158 | ||
|
|
0c3b9056e6 | ||
|
|
6b3d84ba7b | ||
|
|
491adfc28e | ||
|
|
2cbdcfb13a | ||
|
|
2481ff8f5a | ||
|
|
c591d15518 | ||
|
|
c52cc720e8 | ||
|
|
944c79f51a | ||
|
|
f4b6d94611 | ||
|
|
6d59634818 | ||
|
|
2333614961 | ||
|
|
97a52f3d71 | ||
|
|
97ca1fd45d | ||
|
|
22db05c349 | ||
|
|
89a08e4e50 | ||
|
|
6657de1404 | ||
|
|
4484d924e6 | ||
|
|
4759d2302a | ||
|
|
8f45e333f4 | ||
|
|
0cedc09d76 | ||
|
|
e6e352e92f | ||
|
|
ab46f0d0d5 | ||
|
|
91fe677e90 | ||
|
|
1c5badc7b4 | ||
|
|
3f10b2478e | ||
|
|
d58a2baca5 | ||
|
|
cda1516aee | ||
|
|
da7f0d888a | ||
|
|
df55131c3f | ||
|
|
13a48155d0 | ||
|
|
937d06002f | ||
|
|
acf586b8f3 | ||
|
|
3c0a8ce1a7 | ||
|
|
ca44f7dbc2 | ||
|
|
d64fb9b8f0 | ||
|
|
d0b68d526e | ||
|
|
d16f03e052 | ||
|
|
e2bf0b7dde | ||
|
|
163ba33516 | ||
|
|
32bc988a8a | ||
|
|
d2eb4cb817 | ||
|
|
a3eee13f65 | ||
|
|
b7357dbfb7 | ||
|
|
e56d1c6fd8 | ||
|
|
46079e505c | ||
|
|
188426d9d0 | ||
|
|
9b09336538 | ||
|
|
afef353fc2 | ||
|
|
42d0d0eec0 | ||
|
|
0c62e86ced | ||
|
|
71aa64896e | ||
|
|
8f5ecc3b6f | ||
|
|
f6a6aa67ca | ||
|
|
5056ed423a | ||
|
|
996fcf0105 | ||
|
|
6e3ed604fb | ||
|
|
cdffec0f00 | ||
|
|
020076ec47 | ||
|
|
5dd236bf43 | ||
|
|
5ba2fc6082 | ||
|
|
54f234fd0f | ||
|
|
790fb18ea1 | ||
|
|
c0019c8eba | ||
|
|
ebbd8ad6a3 | ||
|
|
7e1e6657ec | ||
|
|
012e2e2a9a | ||
|
|
9d7a1663e2 | ||
|
|
2ee06034ef | ||
|
|
4130f26911 | ||
|
|
1cf12fc336 | ||
|
|
1028ae4da5 | ||
|
|
6b26d283c9 | ||
|
|
7075d4362d | ||
|
|
ce2e35ea07 | ||
|
|
d22d6ea2a7 | ||
|
|
e1b4f9ebbe | ||
|
|
82af878a37 | ||
|
|
5c84441822 | ||
|
|
9b90e2dab2 | ||
|
|
d7c80b13ed | ||
|
|
c44af6b3f8 | ||
|
|
7a28cc5a98 | ||
|
|
fda17dc2be | ||
|
|
776045488b | ||
|
|
8dd344fec5 | ||
|
|
fee4dcac7b | ||
|
|
046f8bed2c | ||
|
|
68ae0e1c63 | ||
|
|
0d84396a56 | ||
|
|
ba993200c5 | ||
|
|
ffa788058b | ||
|
|
a890c12997 | ||
|
|
4c683d0358 | ||
|
|
fa26d0a1ec | ||
|
|
7dd6635195 | ||
|
|
a98151dc0a | ||
|
|
5f6d86ea19 | ||
|
|
dd56a2b0e5 | ||
|
|
8e1485fc32 | ||
|
|
34e50005f9 | ||
|
|
84c22ea77a | ||
|
|
983c274860 | ||
|
|
094146d793 | ||
|
|
0a689d93c5 | ||
|
|
17bc59e6d8 | ||
|
|
0a1b497397 | ||
|
|
5b2de4a2a0 | ||
|
|
f5cf9b2f53 | ||
|
|
03d38eca14 | ||
|
|
747510fe08 | ||
|
|
a2626f6590 | ||
|
|
ae331af398 | ||
|
|
621ec3c9f1 | ||
|
|
70fefbcf45 | ||
|
|
257b72ff59 | ||
|
|
36d2f83ead | ||
|
|
b4c44254d3 | ||
|
|
c2092c8932 | ||
|
|
dfbd18506c | ||
|
|
e1e155577e | ||
|
|
d461dbe895 | ||
|
|
50b22e6b6b | ||
|
|
57bf865c3b | ||
|
|
1083e3ba5e | ||
|
|
d98db3e76e | ||
|
|
0d2122d4b6 | ||
|
|
0329ca28ac | ||
|
|
5c285a39c0 | ||
|
|
950381cfcc | ||
|
|
320e48ac61 | ||
|
|
0dfcfd1fc9 | ||
|
|
56e89ee029 | ||
|
|
7c5eafada3 | ||
|
|
de40c74d73 | ||
|
|
1aeb52b8cd | ||
|
|
42184e66cb | ||
|
|
b3c2f52ca7 | ||
|
|
03f4599f91 | ||
|
|
d7d2083276 | ||
|
|
c8ec9e440b | ||
|
|
e7410b4d95 | ||
|
|
d1e98fa028 | ||
|
|
360d32fbea | ||
|
|
c1e421867a | ||
|
|
d50b369c8f | ||
|
|
0266bd0c37 | ||
|
|
5644bbf4e0 | ||
|
|
de43d84f12 | ||
|
|
3da4d00882 | ||
|
|
a4fb47b2e3 | ||
|
|
45ea603be0 | ||
|
|
738e239a07 | ||
|
|
2490021161 | ||
|
|
84286cc925 | ||
|
|
627c4c8721 | ||
|
|
c2ec420f36 | ||
|
|
d362c3acb1 | ||
|
|
57c7dcd2aa | ||
|
|
5236c14599 | ||
|
|
5fd8827cfe | ||
|
|
738560e3af | ||
|
|
05f0f548d2 | ||
|
|
231f2e0411 | ||
|
|
f3712c9162 | ||
|
|
1eaff7273f | ||
|
|
0ab42ab0d4 | ||
|
|
f407b0765b | ||
|
|
690046d0b2 | ||
|
|
23dfa84f64 | ||
|
|
4e381e13d0 | ||
|
|
c5996d43ce | ||
|
|
5cedd5fb44 | ||
|
|
63fd34d7fd | ||
|
|
514a2e167a | ||
|
|
76c446312b | ||
|
|
1c022e7943 | ||
|
|
02e8d75fd3 | ||
|
|
698a64d1a1 | ||
|
|
e4fe3ccbf1 | ||
|
|
baaf9a0e2e | ||
|
|
3094d055b6 | ||
|
|
036f1558a1 | ||
|
|
b0cabd9436 | ||
|
|
de72a4f9e6 | ||
|
|
28e3bc46cb | ||
|
|
44e1599741 | ||
|
|
5755a954a8 | ||
|
|
4ab1d52c8c | ||
|
|
71a0544914 | ||
|
|
7921162dfa | ||
|
|
31ed8a0e31 | ||
|
|
5f50581ba1 | ||
|
|
fabb787f81 | ||
|
|
fd6638a1b0 | ||
|
|
c550f00901 | ||
|
|
95368c0303 | ||
|
|
cb60e2cf67 | ||
|
|
a86e136b47 | ||
|
|
4c782a118b | ||
|
|
af1756768f | ||
|
|
ec43d39803 | ||
|
|
926336c5aa | ||
|
|
ad04e0b450 | ||
|
|
b8596041d7 | ||
|
|
0e911a98f3 | ||
|
|
65ddaae00f | ||
|
|
469ae00f98 | ||
|
|
0959c16268 | ||
|
|
872ba16ea3 | ||
|
|
595b1a6d2f | ||
|
|
5a071d8fe2 | ||
|
|
f108466c7a | ||
|
|
672942369e | ||
|
|
ff7e119073 | ||
|
|
a133aa7a50 | ||
|
|
c7bd95e01f | ||
|
|
c76ef049cc | ||
|
|
fc9ca84692 | ||
|
|
793e81fab7 | ||
|
|
8e1da77703 | ||
|
|
6cfed6863a | ||
|
|
719031462f | ||
|
|
a4e5ed7dc5 | ||
|
|
9f29dae489 | ||
|
|
d38c24b1d2 | ||
|
|
28340e8013 | ||
|
|
5363980fe5 | ||
|
|
a664ab410a | ||
|
|
b3a2cf07e7 | ||
|
|
579a64e543 | ||
|
|
8c7ebe19c1 | ||
|
|
289fa89a70 | ||
|
|
93428611ea | ||
|
|
10b9ea32f0 | ||
|
|
3b1d6f3767 | ||
|
|
8225112e9f | ||
|
|
44f016b99d | ||
|
|
6aed45f380 | ||
|
|
86927d4039 | ||
|
|
360981f42f | ||
|
|
214915b1ea | ||
|
|
c70ca66473 | ||
|
|
3953ec74c2 | ||
|
|
a24f7cc724 | ||
|
|
5158b4ef7e | ||
|
|
0fbd9f321b | ||
|
|
f438c9bd63 | ||
|
|
443ffdfab5 | ||
|
|
7939795d8f | ||
|
|
8b53273713 | ||
|
|
5e53e06f35 | ||
|
|
82fe6c6059 | ||
|
|
96da306c1d | ||
|
|
737e3fcb4c | ||
|
|
1a2a1bdb65 | ||
|
|
2f613a6ea9 | ||
|
|
fe3fe0e248 | ||
|
|
4110192fd4 | ||
|
|
186231976e | ||
|
|
b0fee56955 | ||
|
|
b2ac18ff52 | ||
|
|
1554534753 | ||
|
|
2a612a98cc | ||
|
|
1f5b1eea47 | ||
|
|
1d88fab99d | ||
|
|
d50c0d1804 | ||
|
|
1ecc2b91d5 | ||
|
|
a09466557f | ||
|
|
5293bb84cf | ||
|
|
5349d91d27 | ||
|
|
8af58c6c26 | ||
|
|
46730e7a73 | ||
|
|
96db84a917 | ||
|
|
3b86274217 | ||
|
|
154338d76d | ||
|
|
2b069a2d37 | ||
|
|
f802d92994 | ||
|
|
d023480172 | ||
|
|
8e1ec2ff65 | ||
|
|
4efa7e3a17 | ||
|
|
a5e9bcda50 | ||
|
|
3928b35212 | ||
|
|
a0b43444db | ||
|
|
09a155a1e7 | ||
|
|
b955b244df | ||
|
|
f6ca749fe3 | ||
|
|
40e47b1b6a | ||
|
|
2792bf31fa | ||
|
|
da23030b62 | ||
|
|
957622305b | ||
|
|
988058474c | ||
|
|
ed376947fc | ||
|
|
16c743d37e | ||
|
|
ffdfd6057a | ||
|
|
080fe9528d | ||
|
|
0a64dd8ccf | ||
|
|
4ac2465003 | ||
|
|
fee1e9a5aa | ||
|
|
e69a5b9550 | ||
|
|
7ca2565338 | ||
|
|
6daaf40faa | ||
|
|
76301f2c02 | ||
|
|
aa3a32080c | ||
|
|
07f4db337d | ||
|
|
4fb4821b14 | ||
|
|
b14f70292d | ||
|
|
fda2f2ea49 | ||
|
|
2e26fd4a8b | ||
|
|
f1254e37b5 | ||
|
|
dc8a3ef559 | ||
|
|
b3c427bf23 | ||
|
|
869d75c988 | ||
|
|
df0463e589 | ||
|
|
61bf4a46f4 | ||
|
|
6399b0aba9 | ||
|
|
15a82f9d45 | ||
|
|
f60ce32c10 | ||
|
|
f3a25fb629 | ||
|
|
85dcb1a129 | ||
|
|
2531b4a711 | ||
|
|
afeb26cf31 | ||
|
|
8af962ba55 | ||
|
|
827f7301e9 | ||
|
|
506f9dd715 | ||
|
|
c6924d4c3d | ||
|
|
14b83d5801 | ||
|
|
b58a9afe59 | ||
|
|
5fd2ef9bb8 | ||
|
|
3c2e093ef6 | ||
|
|
20255492d0 | ||
|
|
3fa393d0d7 | ||
|
|
ad9b2a5c52 | ||
|
|
54e1cb6f28 | ||
|
|
7eb2308d20 | ||
|
|
ce9da1a372 | ||
|
|
3041884f3c | ||
|
|
0a7781ae00 | ||
|
|
cf2861ee02 | ||
|
|
07271c7ede | ||
|
|
0fa10a8846 | ||
|
|
81e6d3aa44 | ||
|
|
bbd6d7b864 | ||
|
|
d178008c4f | ||
|
|
b9f4c54df5 | ||
|
|
ced26c2548 | ||
|
|
f909409c16 | ||
|
|
a6475f4efa | ||
|
|
d65e40a936 | ||
|
|
04f5a4c5e9 | ||
|
|
5de8b3497f | ||
|
|
70c648a70f | ||
|
|
7dc8033c95 | ||
|
|
88d1e4843e | ||
|
|
987d468cac | ||
|
|
c810f5be2d | ||
|
|
ef246da67c | ||
|
|
46eb07b972 | ||
|
|
a031db038b | ||
|
|
daa80e084d | ||
|
|
306c31943d | ||
|
|
465547976e | ||
|
|
b382c6c40b | ||
|
|
f215878bab | ||
|
|
a38e197c1c | ||
|
|
6791f74cc3 | ||
|
|
c3d7f4b48f | ||
|
|
37355a753e | ||
|
|
1042d7ad62 | ||
|
|
8fc0df51b5 | ||
|
|
64637bb344 | ||
|
|
d5f8f64bcb | ||
|
|
faaefbe8de | ||
|
|
22e0b40614 | ||
|
|
f32db894e4 | ||
|
|
372db232c9 | ||
|
|
2e7ea21ef1 | ||
|
|
da21377d9d | ||
|
|
6c8ccae1d2 | ||
|
|
9c9d2ddbae | ||
|
|
62253a2646 | ||
|
|
4dddd378c7 | ||
|
|
1662671ff6 | ||
|
|
be67501c86 | ||
|
|
b6181f2c0a | ||
|
|
20e55c2d2a | ||
|
|
557bbf4ad1 | ||
|
|
c6c031e6f3 | ||
|
|
79906d0320 | ||
|
|
f6fb5e16d8 | ||
|
|
82191f0931 |
@@ -0,0 +1,2 @@
|
||||
.githooks/* text eol=lf
|
||||
scripts/*.mjs text eol=lf
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# changelog 发布门禁(推送前强制校验)
|
||||
# 启用(每台机一次): git config core.hooksPath .githooks
|
||||
# 拦截目标: 接口类 changelog 未部署测试服(backend_status != deployed)就推送给前端,
|
||||
# 以及文件名/frontmatter 结构违规。规则实现见 scripts/validate-changelog-*.mjs。
|
||||
zero=0000000000000000000000000000000000000000
|
||||
status=0
|
||||
while read local_ref local_sha remote_ref remote_sha; do
|
||||
# 删除远端分支的推送没有本地内容可校验
|
||||
[ "$local_sha" = "$zero" ] && continue
|
||||
if [ "$remote_sha" = "$zero" ]; then
|
||||
base=$(git rev-parse --verify origin/main 2>/dev/null) || continue
|
||||
else
|
||||
base=$remote_sha
|
||||
fi
|
||||
[ "$base" = "$local_sha" ] && continue
|
||||
node scripts/validate-changelog-filenames.mjs --base "$base" --head "$local_sha" || status=1
|
||||
node scripts/validate-changelog-frontmatter.mjs --base "$base" --head "$local_sha" || status=1
|
||||
done
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "推送被 changelog 发布门禁拦截:接口类条目必须测试服已部署+实测(backend_status=deployed)后才能推送给前端。" >&2
|
||||
echo "修正文件后重试;规则详见 BACKEND_CHANGELOG_DELIVERY_GUIDE.md §2.1。" >&2
|
||||
fi
|
||||
exit $status
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5888"
|
||||
title: "修复调整订单加费被核单金融守卫误拦——定制师本人无法提交(584086)"
|
||||
consumer: "admin"
|
||||
change_type: "修复缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已修复并部署测试服。定制师本人调整订单产生加费差额不再 584086;同时补上 submit 全链路订单归属校验(跨单操作 581008),行为对前端透明。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 修复:调整订单加费被核单金融守卫误拦(#5888 / #5891)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #5889 + #5892(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 定制师本人操作「调整订单」,产生加费差额(priceDelta>0)即报 `584086 无权修改核单资金数据,仅主管、管理员或财务可操作`,无法提交。减费方向不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 修复内容(对前端透明,无接口契约变化)
|
||||
|
||||
1. **移除误加的金融角色守卫**:`DiscountService.insertActiveSurcharge` 是「调整订单算价差额写入专用」方法,7-21 核单其他收入 PR 给它误加了 `SettlementWriteGuard`(只放行主管/管理员/财务),导致定制师本人被拦。已移除,与减费侧(`insertActiveDiscount`)口径对齐。
|
||||
|
||||
2. **补全 submit 全链路订单归属校验**(#5891):`AdjustmentService.submit` 锁定订单后新增 `OrderViewGuard.assertOrderAccessible` —— ADMIN/SUPER_ADMIN 放行;房务角色 581045;其余角色须为本单定制师(`consultantId == adminId`),否则 **581008 无权查看此订单**。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 定制师本人调整订单加费 | ❌ 584086 | ✅ 正常提交 |
|
||||
| 非本单定制师/车务等调整他人订单 | ⚠️ 可提交(越权) | ❌ 581008 拦截 |
|
||||
| 房务角色提交调整 | ⚠️ 可提交(越权) | ❌ 581045 拦截 |
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "确认执行页「车·师傅」多段/已派槽位显示空白:单段分支读 UI 选中态 props 而非执行段数据"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端零改动。已用测试服真实 API 核实(订单 26-3698,看板 GET /admin/fleet/board/orders/2086270138171994114):activeAssignments 返回 1 条完整执行段(vehiclePlate=蒙A-H7777 / vehicleModel=丰田汉兰达 / vehicleSeats=7 / driverName=阿拉坦 / driverPhone=135****5019 / startDate=08-17 / endDate=08-19),父层 confirmationAssignments 即取自该数据。根因在前端 Step4Confirm.vue:confirmationSegments.length<=1 时走单段分支,读的是父层 vehicle/driver props(= AssignModal 的 selVehicleObj/selDriverObj,useVehicleDriverPicker 的 UI 当前选中态),用户未做改派选择时为空 → 显示空白。应对单段分支也 fallback 到 confirmationSegments[0](或直接统一走 segments 分支)。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 确认执行页「车·师傅」多段/已派槽位显示空白(前端)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step4「确认执行」→「车 · 师傅」区
|
||||
> **后端**:本条**零改动**。下面结论来自测试服真实 API(订单 26-3698)。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
确认执行页(Step4)「车 · 师傅」区显示空白/不全:车辆「· · 座」、师傅「·」,明明该槽位已派 1 车 3 天。
|
||||
|
||||
## 二、后端已排除(数据完整)
|
||||
|
||||
`GET /admin/fleet/board/orders/2086270138171994114` → `activeAssignments` 返回 **1 条聚合执行段**:
|
||||
|
||||
```json
|
||||
{
|
||||
"vehiclePlate": "蒙A-H7777",
|
||||
"vehicleModel": "丰田汉兰达",
|
||||
"vehicleSeats": 7,
|
||||
"driverName": "阿拉坦",
|
||||
"driverPhone": "135****5019",
|
||||
"serviceDate": null,
|
||||
"startDate": "2026-08-17",
|
||||
"endDate": "2026-08-19"
|
||||
}
|
||||
```
|
||||
|
||||
(单槽位聚合整段,startDate~endDate=3 天。字段完整,非空。)
|
||||
|
||||
## 三、根因(前端)
|
||||
|
||||
`AssignModal.vue` 把 `confirmationAssignments`(= `activeAssignments`)作为 `:assignments` 传给 `Step4Confirm.vue`,同时传 `:vehicle="selVehicleObj"` `:driver="selDriverObj"`。
|
||||
|
||||
`Step4Confirm.vue` 渲染逻辑(`confirmationSegments` computed):
|
||||
|
||||
- **多段分支**(`confirmationSegments.length > 1`):遍历 `confirmationSegments` 正确展示。
|
||||
- **单段分支**(`length <= 1`,当前场景命中):读父层 props `vehicle?.plate / driver?.name` —— 即 `selVehicleObj/selDriverObj`(`useVehicleDriverPicker` 的 **UI 当前选中态**)。用户直接从已派状态进入 Step4 未做改派选择时,这两个对象为 **null** → 显示空白。
|
||||
|
||||
即:单段执行时错误依赖了「本次 UI 是否选中过车辆/司机」,而不是回退到服务端已有的执行段数据。
|
||||
|
||||
## 四、修复建议
|
||||
|
||||
- 单段分支也改为读 `confirmationSegments[0]`(vehiclePlate/vehicleModel/vehicleSeats + driverName/driverPhone),与多段分支同一数据源;
|
||||
- 或直接去掉单段分支,统一走 `confirmationSegments` 遍历(数据已由父层聚合好)。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- [ ] 已派 1 车 3 天的槽位进 Step4,「车·师傅」显示 蒙A-H7777 · 丰田汉兰达 · 7座 / 阿拉坦 · 135****5019
|
||||
- [ ] 未做任何改派选择时也能正确回显(不再依赖 UI 选中态)
|
||||
- [ ] 多段接续(同一槽位不同天不同车/司机)仍按段展示不受影响
|
||||
@@ -10,9 +10,11 @@
|
||||
文件名:
|
||||
|
||||
```text
|
||||
DD_issue_业务标题-{新增接口|修改接口|删除接口}-{管理后台|小程序端}.md
|
||||
DD_issue_业务标题-{新增接口|修改接口|删除接口|修复|前端缺陷|前端优化|前端修复}-{管理后台|小程序端}.md
|
||||
```
|
||||
|
||||
纯前端条目(无后端工单)issue 段写字面量 `frontend`,如 `10_frontend_标题-前端缺陷-管理后台.md`。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
@@ -44,10 +46,27 @@ target_release: ""
|
||||
verified_at: ""
|
||||
```
|
||||
|
||||
- 需要前端修改:`frontend_status: "pending"`
|
||||
- 需要前端修改:`frontend_status: "pending"`(此时前端侧可回写 `frontend_owner` 认领 + `verified_at` 实测日期)
|
||||
- 不需要前端修改:`frontend_status: "not_required"`
|
||||
- 后端不要代替前端填写 `implemented`、`released` 或 `verified`
|
||||
|
||||
**`not_required` 条目任何人(含前端)不得回写 `frontend_owner` / `frontend_ref` / `target_release` / `verified_at`**——这 4 个字段是「前端需要动作」的认领/验收标记,`not_required` 语义是「前端零改动、无需认领」,填了就会被发布门禁(E_FRONTEND 残留校验)拦下。前端要表达「已知悉」用评论/口头即可,不动 frontmatter。若前端评估后认为其实需要适配,应把 `frontend_status` 改成 `pending` 再回写 owner,而不是在 `not_required` 上叠字段。(2026-08-19 #6077 实例:mmg 回写 `not_required` 条目的 `frontend_owner=mmg`+`verified_at`,合并后被门禁拦,清空后放行。)
|
||||
|
||||
## 2.1 发布门禁(硬规则,2026-08-10 wx 定)
|
||||
|
||||
**给前端推送的 changelog,内容必须是测试环境已经存在、可实测到的。**
|
||||
|
||||
- 接口类条目(新增接口/修改接口/删除接口):推送前必须走完「PR 合并 → 部署测试服 → 测试服真实 API 验证」,frontmatter 必须 `backend_status: "deployed"`,并在正文「验证证据」章节贴实测结果。
|
||||
- `backend_status` 为 `merged` / `pending` / `implemented` 等未部署状态的条目**禁止 push**(校验规则 E_BACKEND_PENDING 会拦)。「先给前端契约、部署随后」的预告式推送一律禁止——前端拿到 changelog 会立刻联调,接口不在等于空耗与误判。
|
||||
- 纯前端条目(前端缺陷/前端优化/前端修复):`backend_status: "not_required"`,change_type 用对应前端类型;`frontend_status: "not_required"` 时不得残留 frontend_owner / frontend_ref / target_release / verified_at。
|
||||
- 背景:2026-08-06~08-07 三条未部署即推送的条目(#5599/#5567/#5633)导致前端在测试环境验不到字段(2026-08-10 投诉属实);当时仓库 CI 因校验规则假阳性长期常红被忽略,规则已于 2026-08-10 修正(前端条目类型合法化、`{orderId}` 路径参数不再误判为占位符),此后 **CI 红 = 真违规,必须当场修复回填**。
|
||||
|
||||
**推送校验(强制)**:
|
||||
|
||||
- 推荐一次性启用本地钩子,之后 push 自动拦截:`git config core.hooksPath .githooks`
|
||||
- 未启用钩子则每次 push 前手动跑 §3 的两条校验命令,红了不许推。
|
||||
- 仓库 CI(changelog-filename-gate)对每次 push 复检;push 后请回看 Gitea Actions 状态,红 X 必须当场处理。
|
||||
|
||||
## 2.5 写作方法论(对齐 yst 团队 changelog-conventions SKILL,2026-08-04 起执行)
|
||||
|
||||
**受众优先**:触达 `/admin/*` `/mp/*` `/v3/admin/*` `/v3/mp/*` 等对外前缀的改动**一律**写前端 changelog,哪怕"前端代码零改动"(前端 AI 可能有 workaround 需清理信号)。`/v3/internal/*` Feign 接口**必须拆出去**单独走后端 changelog,不许和 admin/mp 接口塞同一份(反例:# traveler 11 接口事故)。
|
||||
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,392 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>供应商详情资源信息分页 API 接口规范 · v1.0</title>
|
||||
<style>
|
||||
:root {
|
||||
--ink: #172033;
|
||||
--muted: #667085;
|
||||
--line: #dfe5ef;
|
||||
--soft: #f6f8fb;
|
||||
--blue: #1677ff;
|
||||
--blue-soft: #eaf3ff;
|
||||
--green: #15803d;
|
||||
--green-soft: #eaf8ef;
|
||||
--amber: #a15c00;
|
||||
--amber-soft: #fff7e6;
|
||||
--red: #b42318;
|
||||
--code: #101828;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background: #eef2f7;
|
||||
font: 14px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.page {
|
||||
width: min(1180px, calc(100% - 40px));
|
||||
margin: 28px auto 64px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 18px 48px rgba(16, 24, 40, .08);
|
||||
overflow: hidden;
|
||||
}
|
||||
header {
|
||||
padding: 42px 48px 34px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #123d78, #1677ff 68%, #39a0ff);
|
||||
}
|
||||
header h1 { margin: 10px 0 8px; font-size: 30px; line-height: 1.3; }
|
||||
header p { margin: 0; opacity: .9; }
|
||||
.eyebrow { font-size: 12px; letter-spacing: .12em; text-transform: uppercase; opacity: .78; }
|
||||
.badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 22px; }
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid rgba(255,255,255,.35);
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,.14);
|
||||
font-size: 12px;
|
||||
}
|
||||
main { padding: 12px 48px 54px; }
|
||||
nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 0 -48px 24px;
|
||||
padding: 13px 48px;
|
||||
overflow-x: auto;
|
||||
background: rgba(255,255,255,.96);
|
||||
border-bottom: 1px solid var(--line);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
nav a { color: #344054; text-decoration: none; white-space: nowrap; font-size: 13px; }
|
||||
nav a:hover { color: var(--blue); }
|
||||
section { scroll-margin-top: 62px; padding-top: 22px; }
|
||||
h2 { margin: 0 0 14px; font-size: 22px; }
|
||||
h3 { margin: 24px 0 10px; font-size: 16px; }
|
||||
p { margin: 8px 0; }
|
||||
code {
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
background: var(--soft);
|
||||
color: #175cd3;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: .93em;
|
||||
}
|
||||
pre {
|
||||
margin: 12px 0;
|
||||
padding: 18px 20px;
|
||||
overflow: auto;
|
||||
border-radius: 10px;
|
||||
background: var(--code);
|
||||
color: #d1e9ff;
|
||||
font: 12px/1.65 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
pre code { padding: 0; color: inherit; background: transparent; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 12px 0 18px; }
|
||||
th, td { padding: 10px 12px; border: 1px solid var(--line); text-align: left; vertical-align: top; }
|
||||
th { background: var(--soft); color: #344054; font-weight: 600; }
|
||||
td:first-child code { white-space: nowrap; }
|
||||
.endpoint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 12px 0 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #b9d8ff;
|
||||
border-radius: 10px;
|
||||
background: var(--blue-soft);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.method { padding: 4px 9px; border-radius: 6px; color: #fff; background: var(--blue); font-weight: 700; }
|
||||
.path { font: 600 14px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: nowrap; }
|
||||
.callout {
|
||||
margin: 14px 0;
|
||||
padding: 13px 15px;
|
||||
border-left: 4px solid var(--blue);
|
||||
border-radius: 6px;
|
||||
background: var(--blue-soft);
|
||||
}
|
||||
.callout.ok { border-color: var(--green); background: var(--green-soft); }
|
||||
.callout.warn { border-color: #f59e0b; background: var(--amber-soft); }
|
||||
.callout strong { display: block; margin-bottom: 2px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 16px 0; }
|
||||
.card { padding: 15px; border: 1px solid var(--line); border-radius: 10px; background: #fff; }
|
||||
.card b { display: block; margin-bottom: 4px; color: #344054; }
|
||||
.card span { color: var(--muted); }
|
||||
.status-ok { color: var(--green); font-weight: 700; }
|
||||
.status-pending { color: var(--amber); font-weight: 700; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
li + li { margin-top: 5px; }
|
||||
footer { padding: 24px 48px; border-top: 1px solid var(--line); background: var(--soft); color: var(--muted); }
|
||||
@media (max-width: 760px) {
|
||||
.page { width: 100%; margin: 0; border: 0; border-radius: 0; }
|
||||
header, main, footer { padding-left: 20px; padding-right: 20px; }
|
||||
nav { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
table { display: block; overflow-x: auto; white-space: nowrap; }
|
||||
}
|
||||
@media print {
|
||||
@page { size: A4; margin: 14mm; }
|
||||
body { background: #fff; }
|
||||
.page { width: 100%; margin: 0; border: 0; box-shadow: none; }
|
||||
header { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
||||
nav { display: none; }
|
||||
main { padding: 10px 0 20px; }
|
||||
footer { padding: 16px 0 0; }
|
||||
section { break-inside: avoid-page; }
|
||||
pre { white-space: pre-wrap; word-break: break-word; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article class="page">
|
||||
<header>
|
||||
<div class="eyebrow">HL Supplier API · Admin</div>
|
||||
<h1>供应商详情资源信息分页 API 接口规范</h1>
|
||||
<p>v1.0 · Issue #6316 · 2026-08-25</p>
|
||||
<div class="badges">
|
||||
<span class="badge">已实现 · 可联调</span>
|
||||
<span class="badge">后端 deployed</span>
|
||||
<span class="badge">Gateway verified</span>
|
||||
<span class="badge">前端 pending</span>
|
||||
<span class="badge">只读接口</span>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<nav aria-label="文档目录">
|
||||
<a href="#overview">概览</a>
|
||||
<a href="#request">请求</a>
|
||||
<a href="#response">响应</a>
|
||||
<a href="#fields">字段</a>
|
||||
<a href="#semantics">业务口径</a>
|
||||
<a href="#errors">错误码</a>
|
||||
<a href="#frontend">前端接入</a>
|
||||
<a href="#verification">验证</a>
|
||||
<a href="#rollback">撤回</a>
|
||||
</nav>
|
||||
|
||||
<section id="overview">
|
||||
<h2>1. 接口概览</h2>
|
||||
<div class="endpoint"><span class="method">GET</span><span class="path">/admin/supplier/items/{supplierId}/resource-info/page</span></div>
|
||||
<p>用于供应商管理详情页“资源信息”页签,分页展示该供应商当前有效关系对应的资源权威信息。关系来源为本系统 <code>supplier_resource_rel</code>;Resource 提供九类本地资源,Fleet 提供车辆资源。</p>
|
||||
<div class="grid">
|
||||
<div class="card"><b>实现状态</b><span class="status-ok">已实现 · 可联调</span></div>
|
||||
<div class="card"><b>调用方</b><span>管理后台;前端接入待完成</span></div>
|
||||
<div class="card"><b>数据副作用</b><span>无写库、Redis、MQ、配置或审计副作用</span></div>
|
||||
</div>
|
||||
<div class="callout ok"><strong>契约真相</strong>本文以合并提交 <code>4f032cd6f</code> 的 Controller、请求/响应 VO、Service 权限门禁及 TEST Gateway 验收为准。</div>
|
||||
</section>
|
||||
|
||||
<section id="request">
|
||||
<h2>2. 请求契约</h2>
|
||||
<h3>2.1 路径与查询参数</h3>
|
||||
<table>
|
||||
<thead><tr><th>参数</th><th>位置</th><th>类型</th><th>必填</th><th>约束与默认值</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>supplierId</code></td><td>path</td><td>string</td><td>是</td><td>正整数;Snowflake ID 必须按字符串传递</td></tr>
|
||||
<tr><td><code>page</code></td><td>query</td><td>integer</td><td>否</td><td>默认 1,最小 1;公共分页兼容 <code>pageNo</code></td></tr>
|
||||
<tr><td><code>pageSize</code></td><td>query</td><td>integer</td><td>否</td><td>默认 20,范围 1..100</td></tr>
|
||||
<tr><td><code>resourceModule</code></td><td>query</td><td>string</td><td>否</td><td>为空查询全部;非空按关系冻结模块精确筛选</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>2.2 支持的资源模块</h3>
|
||||
<table>
|
||||
<thead><tr><th>编码</th><th>moduleName</th><th>数据归属</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>SCENIC</code></td><td>景区管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>RESTAURANT</code></td><td>餐厅管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>SUPPLIES</code></td><td>备品管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>SUPPLIES_COMBO</code></td><td>组合配品</td><td>Resource</td></tr>
|
||||
<tr><td><code>ACTIVITY</code></td><td>游玩项目管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>HOTEL</code></td><td>酒店管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>SERVICE</code></td><td>服务管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>COST_ITEM</code></td><td>额外成本</td><td>Resource</td></tr>
|
||||
<tr><td><code>STAFF</code></td><td>服务人员管理</td><td>Resource</td></tr>
|
||||
<tr><td><code>VEHICLE</code></td><td>车队管理-车队管理</td><td>Fleet(内部批量聚合)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>2.3 调用示例</h3>
|
||||
<pre><code>GET /admin/supplier/items/2091715622923657217/resource-info/page?page=1&pageSize=20&resourceModule=SCENIC
|
||||
Authorization: Bearer <有效管理端访问令牌></code></pre>
|
||||
<div class="callout warn"><strong>权限是双门禁</strong>服务端仅允许 ADMIN、FINANCE、SUPER_ADMIN,并同时要求 <code>supplier:view</code> 与 <code>supplier:resource:view</code>。不能只通过隐藏页签代替服务端授权。</div>
|
||||
</section>
|
||||
|
||||
<section id="response">
|
||||
<h2>3. 响应结构</h2>
|
||||
<p>统一返回 <code>Result<PageResult<SupplierResourceInfoRespVO>></code>。业务失败通常仍是 HTTP 200,调用方必须检查 <code>code</code>、<code>success</code> 与 <code>message</code>。</p>
|
||||
<pre><code>{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"relationId": "2091715622923657218",
|
||||
"resourceModule": "SCENIC",
|
||||
"moduleName": "景区管理",
|
||||
"resourceId": "2091715622923657001",
|
||||
"resourceName": "示例景区",
|
||||
"coverUrl": null,
|
||||
"city": "海拉尔",
|
||||
"isCharged": true,
|
||||
"isChargedName": "是",
|
||||
"settleTypeCode": "CASH",
|
||||
"settleTypeName": "现付",
|
||||
"tags": [
|
||||
{ "tagId": "2084636804090089473", "tagName": "自然风光", "tagColor": "#52C41A" }
|
||||
],
|
||||
"seasons": [
|
||||
{ "seasonCode": "spring", "seasonName": "春" }
|
||||
],
|
||||
"statusCode": "ENABLED",
|
||||
"statusName": "启用",
|
||||
"enabled": true,
|
||||
"resourceAvailable": true,
|
||||
"updateTime": "2026-08-25 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="fields">
|
||||
<h2>4. records[] 字段</h2>
|
||||
<table>
|
||||
<thead><tr><th>字段</th><th>类型</th><th>可空</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>relationId</code></td><td>string</td><td>否</td><td>供应商资源关系 ID</td></tr>
|
||||
<tr><td><code>resourceModule</code></td><td>string</td><td>否</td><td>关系冻结的资源模块编码</td></tr>
|
||||
<tr><td><code>moduleName</code></td><td>string</td><td>否</td><td>模块中文名</td></tr>
|
||||
<tr><td><code>resourceId</code></td><td>string</td><td>否</td><td>资源 ID;资源缺失仍保留关系原值</td></tr>
|
||||
<tr><td><code>resourceName</code></td><td>string</td><td>是</td><td>当前资源名称</td></tr>
|
||||
<tr><td><code>coverUrl</code></td><td>string</td><td>是</td><td>当前有效封面 URL</td></tr>
|
||||
<tr><td><code>city</code></td><td>string</td><td>是</td><td>城市展示值</td></tr>
|
||||
<tr><td><code>isCharged</code></td><td>boolean</td><td>是</td><td>是否收费;不适用/未知为 null</td></tr>
|
||||
<tr><td><code>isChargedName</code></td><td>string</td><td>是</td><td>是否收费中文名</td></tr>
|
||||
<tr><td><code>settleTypeCode</code></td><td>string</td><td>是</td><td>结算方式编码</td></tr>
|
||||
<tr><td><code>settleTypeName</code></td><td>string</td><td>是</td><td>结算方式中文名</td></tr>
|
||||
<tr><td><code>tags</code></td><td>array</td><td>否</td><td>标签列表;无数据为 []</td></tr>
|
||||
<tr><td><code>seasons</code></td><td>array</td><td>否</td><td>标准季节列表;无数据为 []</td></tr>
|
||||
<tr><td><code>statusCode</code></td><td>string</td><td>是</td><td>资源当前权威状态编码</td></tr>
|
||||
<tr><td><code>statusName</code></td><td>string</td><td>是</td><td>归一化状态中文名</td></tr>
|
||||
<tr><td><code>enabled</code></td><td>boolean</td><td>是</td><td>归一化启用标记</td></tr>
|
||||
<tr><td><code>resourceAvailable</code></td><td>boolean</td><td>否</td><td>false 表示关系保留但资源已删除/缺失</td></tr>
|
||||
<tr><td><code>updateTime</code></td><td>string</td><td>是</td><td>资源本身更新时间;yyyy-MM-dd HH:mm:ss</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>4.1 子结构</h3>
|
||||
<table>
|
||||
<thead><tr><th>数组</th><th>字段</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>tags[]</code></td><td><code>tagId</code>、<code>tagName</code>、<code>tagColor</code></td><td>标签 ID 沿用各资源模块的字符串表达</td></tr>
|
||||
<tr><td><code>seasons[]</code></td><td><code>seasonCode</code>、<code>seasonName</code></td><td>标准季节编码与中文名</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="semantics">
|
||||
<h2>5. 业务口径</h2>
|
||||
<ul>
|
||||
<li>只读取当前有效的供应商资源关系,按关系 <code>update_time DESC, rel_id DESC</code> 稳定排序。</li>
|
||||
<li>响应中的 <code>updateTime</code> 是资源主数据更新时间,不是关系更新时间。</li>
|
||||
<li>标量字段不适用、未知或资源缺失时返回 <code>null</code>;<code>tags</code> 与 <code>seasons</code> 永远返回数组。</li>
|
||||
<li>资源被软删除或不存在时不丢弃关系行:<code>resourceAvailable=false</code>,名称、状态、更新时间等当前资源字段为 null。</li>
|
||||
<li>十类资源以本系统权威数据为准;展示形式可参考现有资源列表,但不要从参考图硬编码字段值或状态。</li>
|
||||
<li>供应商处于草稿、审批中、合作中、暂停、黑名单、归档等任意生命周期状态时均可只读查询。</li>
|
||||
<li>Fleet/字典依赖出现空响应、非成功、重复、缺失、额外或非法数据时返回 395039,不降级为部分成功。</li>
|
||||
</ul>
|
||||
<div class="callout"><strong>内部依赖说明</strong>Resource 通过内部 Token 调用 <code>POST /internal/fleet/vehicles/supplier-resource-info/batch</code> 聚合车辆信息。该路径不面向管理端,前端不得调用、转发或持有内部 Token。</div>
|
||||
</section>
|
||||
|
||||
<section id="errors">
|
||||
<h2>6. 错误码</h2>
|
||||
<table>
|
||||
<thead><tr><th>业务码</th><th>场景</th><th>管理端建议</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>401</code></td><td>未认证或登录态失效</td><td>按统一登录续期/退出逻辑处理</td></tr>
|
||||
<tr><td><code>400</code></td><td>supplierId、page 或 pageSize 等参数不合法</td><td>修正请求,不自动重试</td></tr>
|
||||
<tr><td><code>395001</code></td><td>供应商不存在</td><td>关闭失效详情或刷新列表</td></tr>
|
||||
<tr><td><code>395034</code></td><td>resourceModule 不受支持</td><td>仅使用本文十个稳定编码</td></tr>
|
||||
<tr><td><code>395039</code></td><td>Fleet、字典或资源必要依赖不可用/响应不完整</td><td>提示稍后重试,不展示旧数据冒充成功</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<pre><code>{
|
||||
"code": 395034,
|
||||
"message": "不支持的资源模块",
|
||||
"data": null,
|
||||
"success": false
|
||||
}</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="frontend">
|
||||
<h2>7. 管理端接入清单</h2>
|
||||
<ol>
|
||||
<li>在供应商详情“账号信息”页签后新增“资源信息”页签;仅在页签打开时加载数据。</li>
|
||||
<li>建议列:资源名称(封面 + 名称)、资源模块、城市、是否收费、结算方式、标签、季节、状态、更新时间。</li>
|
||||
<li>所有 ID 按字符串保存、传参和比较,禁止转换为 JavaScript number。</li>
|
||||
<li>优先展示服务端中文字段;未知字段显示“—”,不得前端猜测状态或字典名称。</li>
|
||||
<li><code>resourceAvailable=false</code> 时保留行并明确显示“资源已删除/不可用”。</li>
|
||||
<li>模块筛选或页容量改变时回到第 1 页;pageSize 最大 100。</li>
|
||||
<li>按 <code>code/success</code> 判断业务结果;不要仅依据 HTTP 200。</li>
|
||||
<li>页签为只读展示,不增加绑定、改绑、解绑、启停或删除按钮。</li>
|
||||
</ol>
|
||||
<h3>7.1 建议验收场景</h3>
|
||||
<table>
|
||||
<thead><tr><th>场景</th><th>预期</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>有资源关系</td><td>按分页显示,中文字段、数组、字符串 ID 正常</td></tr>
|
||||
<tr><td>无资源关系</td><td>records=[]、total=0,不显示错误空态</td></tr>
|
||||
<tr><td>按模块筛选</td><td>返回行的 resourceModule 全部等于筛选值</td></tr>
|
||||
<tr><td>关系存在但资源缺失</td><td>保留关系行,resourceAvailable=false</td></tr>
|
||||
<tr><td>无双权限/未登录</td><td>服务端拒绝,页面不泄露数据</td></tr>
|
||||
<tr><td>依赖暂不可用</td><td>展示 395039 对应提示,不展示不完整成功页</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="verification">
|
||||
<h2>8. 实现与验收状态</h2>
|
||||
<ul>
|
||||
<li><strong>代码:</strong>PR #6330 已合并 <code>dev-v3</code>,合并提交 <code>4f032cd6f6698607a2f1524437533d595975fc9a</code>。</li>
|
||||
<li><strong>自动化:</strong>Resource 全量 2043 项零失败;Fleet 可运行全量 3853 项零失败;GatewayRouteAuditTest 3 项零失败;独立审计套件再次通过。</li>
|
||||
<li><strong>TEST:</strong>任务 <code>cc1fe248</code>、<code>2697d374</code> 精确部署同一合并提交,Resource/Fleet 双实例与 Nacos 各 2 实例健康。</li>
|
||||
<li><strong>真实 Gateway:</strong>有效管理端会话只读查询 3 个供应商,实际取得 1 条 SCENIC 关系;18 字段、字符串 ID、数组、时间和模块筛选均通过。</li>
|
||||
<li><strong>负向:</strong>395001、395034、page/pageSize 参数 400 与未认证 401 均实测通过。</li>
|
||||
<li><strong>车辆样本:</strong>TEST 当时无 VEHICLE 供应商关系;未伪造数据,跨服务路径由双服务真实部署健康与内部契约/批量/失败关闭测试覆盖。</li>
|
||||
<li><strong>环境恢复:</strong>任务 <code>45e843e5</code>、<code>c05eda6e</code> 已回切包含本次合并的 <code>dev-v3</code>;Resource 8082/8182、Fleet 8087/8187 及 Nacos 各 2 个实例健康,临时部署分支已删除。</li>
|
||||
</ul>
|
||||
<div class="callout ok"><strong>数据清理</strong>TEST 验收全部为 GET 只读请求,没有创建或修改供应商、资源、数据库、Redis、MQ 或配置数据。</div>
|
||||
</section>
|
||||
|
||||
<section id="rollback">
|
||||
<h2>9. 撤回方案</h2>
|
||||
<ol>
|
||||
<li>从最新 <code>dev-v3</code> 创建回退分支,执行 <code>git revert -m 1 --no-edit 4f032cd6f6698607a2f1524437533d595975fc9a</code>,经独立 PR 合入。</li>
|
||||
<li>依次重新构建并滚动部署 Resource、Fleet,分别保持双实例可用。</li>
|
||||
<li>本次无数据库、Redis、MQ、Nacos 或其他配置变更,无需 DDL、DML、缓存清理、消息补偿或配置恢复,也无不可逆影响。</li>
|
||||
<li>管理端停止调用新增 GET 和读取本次字段;既有供应商详情、账号和资源管理接口不受影响。</li>
|
||||
<li>经 Gateway 复测新增路径撤回、既有供应商详情正常,并确认两服务双实例和 Nacos 健康。</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
关联:Issue #6316 · PR #6330 · 后端负责人 @lc · 本文件为可离线、可打印单页接口规范。
|
||||
</footer>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6207"
|
||||
title: "AI 推荐产品多维筛选开放接口(免登录,排除私人定制)"
|
||||
consumer: "mp"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-23"
|
||||
status_note: "GET /mp/product/ai-recommend 免登录开放接口,AI 推荐用。默认返回 PUBLISHED + CORE/GROUP(排除 CUSTOM 私人定制),支持多维筛选。后端/网关已测试服验证,前端待消费。"
|
||||
updated_at: "2026-08-23"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【新增接口·小程序端】AI 推荐产品多维筛选开放接口(#6207)
|
||||
|
||||
> **PR**: #6220 | **服务**: hl-mp-service / hl-product-service-v2 / hl-gateway | **作者**: wx | **更新时间**: 2026-08-23
|
||||
|
||||
> 免登录开放接口,供 AI 做产品推荐调用。**除私人定制(CUSTOM)以外的全部已上架(PUBLISHED)产品**,多维筛选,全部条件可选可组合。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
AI 做产品推荐需要一个查询接口:能筛选除私人定制以外的全部已上架产品,条件越多越好(目的地、天数、报价范围、团期、包含资源、人数、季节等),**不需要登录**。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | AI推荐产品查询 | GET | `/mp/product/ai-recommend` | 🆕 新增 | 免登录,多维筛选,分页 |
|
||||
|
||||
> 网关白名单已放行(`JwtAuthFilter` SKIP_URLS),无 token 可直接调用。
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. AI推荐产品查询 `GET /mp/product/ai-recommend`
|
||||
|
||||
BFF 透传至 product-service `GET /internal/mp/product/ai-query`。pageSize 最大 50。
|
||||
|
||||
**数据范围(硬性)**:
|
||||
- `product.status = PUBLISHED`(已上架)
|
||||
- `product_type IN (CORE, GROUP)`(**排除 CUSTOM 私人定制**)
|
||||
|
||||
#### 入参(全部可选,可任意组合)
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| keyword | Query | String | 否 | ≤100 字符 | 关键词/目的地(匹配产品名/副标题) |
|
||||
| productType | Query | String | 否 | CORE/GROUP | 产品类型(默认 CORE+GROUP,传单一值可收窄) |
|
||||
| category | Query | String | 否 | family/honeymoon/photography/experience/driving | 产品分类 |
|
||||
| lineId | Query | Long | 否 | - | 产品线ID |
|
||||
| minDays | Query | Integer | 否 | ≥1 | 最短天数 |
|
||||
| maxDays | Query | Integer | 否 | ≥1 | 最长天数 |
|
||||
| seasons | Query | String | 否 | spring/summer/autumn/winter,逗号分隔多值 | 季节 |
|
||||
| tags | Query | String | 否 | 逗号分隔多值 | 产品标签 |
|
||||
| minPrice | Query | BigDecimal | 否 | - | 最低起步价 |
|
||||
| maxPrice | Query | BigDecimal | 否 | - | 最高起步价 |
|
||||
| startDate | Query | String | 否 | yyyy-MM-dd | 出发日期起始 |
|
||||
| endDate | Query | String | 否 | yyyy-MM-dd | 出发日期结束 |
|
||||
| partySize | Query | Integer | 否 | ≥1 | 人数(仅与 startDate+endDate 组合时生效,过滤「订不下」的产品) |
|
||||
| resourceType | Query | String | 否 | SCENIC/ACTIVITY/RESTAURANT/SERVICE/CUSTOM | 包含资源类型 |
|
||||
| resourceKeyword | Query | String | 否 | - | 包含资源关键词 |
|
||||
| orderBy | Query | String | 否 | sort/price_asc/price_desc/newest | 排序 |
|
||||
| page | Query | Integer | 否 | 默认1,≥1 | 页码 |
|
||||
| pageSize | Query | Integer | 否 | 默认10,1~50 | 每页条数(**最大 50**,>50 返 400) |
|
||||
|
||||
> **seasons/tags 传法**:逗号分隔多值,如 `seasons=summer,winter`、`tags=亲子,摄影`。
|
||||
|
||||
#### 出参 `Result<PageResult<MpProductAiQueryItemVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records[].productId | String | 产品ID(雪花,字符串防精度丢失) |
|
||||
| records[].productNo | String | 产品编号 |
|
||||
| records[].productType | String | CORE/GROUP |
|
||||
| records[].name | String | 产品名称 |
|
||||
| records[].subtitle | String | 副标题 |
|
||||
| records[].introduction | String | 产品简介 |
|
||||
| records[].category | String | 产品分类 |
|
||||
| records[].tripDays / tripNights | Integer | 天数 / 晚数 |
|
||||
| records[].coverImageUrl | String | 封面图 |
|
||||
| records[].carouselImages | String[] | 轮播图 |
|
||||
| records[].tags / seasons | String[] | 标签 / 季节 |
|
||||
| records[].lineId | String | 产品线ID |
|
||||
| records[].lineName | String | 产品线名称 |
|
||||
| records[].startPrice | String | 起步价(字符串) |
|
||||
| records[].startPriceLabel | String | `¥3980起/人` |
|
||||
| records[].paymentType | String | FULL / DEPOSIT |
|
||||
| records[].status | String | PUBLISHED |
|
||||
| records[].sortOrder | Integer | 排序号 |
|
||||
| records[].earliestBookingDate | String | 最早可订日期 yyyy-MM-dd |
|
||||
| records[].availableDateSummary | String | 可订日期摘要(如 `2026-09-11 起`) |
|
||||
| records[].availabilitySummary | String | 余位摘要(GROUP: `8个班期可报名,余位充足(2026-09-11 ~ 2026-10-30)`;CORE: `9天可订(2026-08-23 ~ 2026-08-31)`) |
|
||||
| records[].matchedDestinations | String[] | 匹配到的目的地/途经点摘要 |
|
||||
| total | Integer | 总条数 |
|
||||
| page / pageSize | Integer | 页码 / 每页条数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```
|
||||
GET /mp/product/ai-recommend?productType=GROUP&seasons=summer&minPrice=0&maxPrice=20000&page=1&pageSize=5
|
||||
```
|
||||
|
||||
#### 响应示例(测试服实测)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"productId": "2056947670512971778",
|
||||
"productNo": "G260423004",
|
||||
"productType": "GROUP",
|
||||
"name": "测试小蒙马-多档-固定金额",
|
||||
"subtitle": "",
|
||||
"introduction": "测试产品简介",
|
||||
"category": "photography",
|
||||
"tripDays": 5,
|
||||
"tripNights": 4,
|
||||
"coverImageUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/...",
|
||||
"tags": ["小蒙马"],
|
||||
"seasons": ["summer"],
|
||||
"lineId": "2001",
|
||||
"lineName": "测试产品线",
|
||||
"startPrice": "8800.00",
|
||||
"startPriceLabel": "¥8800起/人",
|
||||
"paymentType": "FULL",
|
||||
"status": "PUBLISHED",
|
||||
"sortOrder": 0,
|
||||
"earliestBookingDate": "2026-09-11",
|
||||
"availableDateSummary": "2026-09-11 起",
|
||||
"availabilitySummary": "8个班期可报名,余位充足(2026-09-11 ~ 2026-10-30)"
|
||||
}
|
||||
],
|
||||
"total": 5,
|
||||
"page": 1,
|
||||
"pageSize": 5
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据响应(无匹配条件)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": { "records": [], "total": 0, "page": 1, "pageSize": 5 },
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应(参数防御)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "每页条数不能大于50",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 筛选语义(后端为准)
|
||||
|
||||
- **所有条件 AND 组合**:同时传多个条件时,返回同时满足全部条件的产品。
|
||||
- **seasons / tags 多值取 OR**:`seasons=summer,winter` 表示「夏季 **或** 冬季」。
|
||||
- **报价范围按起步价过滤**:起步价 = GROUP 未来班期最低成人价 或 CORE 价格日历未来最低价,`minPrice/maxPrice` 作用于该值。
|
||||
- **人数(partySize)过滤语义**:**仅当同时传了出发日期范围(startDate/endDate 至少一个)+ partySize 才生效**——过滤「订不下」的产品:
|
||||
- GROUP:看日期范围内是否有余位 ≥ partySize 的可报名班期(ENROLLING/NEARLY_FULL)
|
||||
- CORE:看日期范围内是否有库存(dailyStock - sold)≥ partySize 的日期
|
||||
- **只传 partySize 不传日期 → 不触发人数过滤**。
|
||||
- **目的地关键词**:匹配产品名/副标题 **或** 途经点名称 **或** 行程节点名称(任一处命中即算)。
|
||||
- **日期倒置自动修正**:startDate 晚于 endDate 时后端自动交换,不报错。
|
||||
|
||||
### ✅ 正确 / ⚠️ 注意
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| ✅ 无条件调用 | 返回全部在售(CORE+GROUP 且 PUBLISHED)分页 |
|
||||
| ✅ `seasons=summer,winter` | 夏季或冬季的产品 |
|
||||
| ✅ `minPrice=1000&maxPrice=5000` | 起步价在区间内的产品 |
|
||||
| ✅ `startDate=2026-09-01&endDate=2026-10-01&partySize=2` | 该日期范围内能订下 2 人的产品 |
|
||||
| ⚠️ `pageSize=999` | 400 拒绝(上限 50) |
|
||||
| ⚠️ 只传 `partySize` 不传日期 | 人数条件不生效 |
|
||||
|
||||
---
|
||||
|
||||
## 五、测试环境已验证(2026-08-23)
|
||||
|
||||
```
|
||||
GET /mp/product/ai-recommend?page=1&pageSize=5 → 200 total=22 ✓
|
||||
GET /mp/product/ai-recommend?keyword=草原&page=1&pageSize=5 → 200 total=13 ✓
|
||||
GET /mp/product/ai-recommend?productType=GROUP&page=1&pageSize=3 → 200 total=6 ✓
|
||||
GET /mp/product/ai-recommend?category=family&page=1&pageSize=5 → 200 total=12 ✓
|
||||
GET /mp/product/ai-recommend?seasons=summer,winter&page=1&pageSize=5 → 200 total=19 ✓
|
||||
GET /mp/product/ai-recommend?minPrice=1000&maxPrice=5000&page=1&pageSize=3 → 200 ✓
|
||||
GET /mp/product/ai-recommend?orderBy=price_asc&page=1&pageSize=5 → 200 ✓(第一条约 2980 便宜档)
|
||||
GET /mp/product/ai-recommend?productType=GROUP&seasons=summer&minPrice=0&maxPrice=20000 → 200 total=5 ✓
|
||||
GET /mp/product/ai-recommend?startDate=2026-08-24&endDate=2026-10-22 → 200 total=14 ✓
|
||||
GET /mp/product/ai-recommend?pageSize=999 → 400 每页条数不能大于50 ✓
|
||||
GET /mp/product/ai-recommend?category=driving&seasons=winter&keyword=不存在xyz → 200 empty ✓
|
||||
```
|
||||
|
||||
免登录(无 token)经网关调用全部返回 200。
|
||||
|
||||
---
|
||||
|
||||
## 六、相关历史
|
||||
|
||||
- 关联 Issue: [wx/HL#6207](https://git.1814.love:8443/wx/HL/issues/6207)
|
||||
- 关联 PR: [wx/HL#6220](https://git.1814.love:8443/wx/HL/pulls/6220)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -5,7 +5,7 @@ title: "Step2 canonical full snapshot 与稳定槽位"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "released"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "Pi"
|
||||
|
||||
@@ -5,15 +5,15 @@ title: "历史终止订单车辆核单空态"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst(GIT)"
|
||||
backend_status: "pending"
|
||||
gateway_status: "pending"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "pi-main-session"
|
||||
frontend_ref: "hl-admin@4a2dcc8e20fe23ffc2b3d35ae8f02e62661b642d"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-06"
|
||||
status_note: "PR #5570 已合并 dev-v3;运行时行为尚未验证,管理后台待适配新增阻断枚举。"
|
||||
updated_at: "2026-08-06"
|
||||
status_note: "PR #5570 已合并 dev-v3;2026-08-10 复核确认测试服已部署、网关链路实测连通(见文末验证证据章节)。管理后台待适配新增阻断枚举。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -295,3 +295,11 @@ Authorization: Bearer <管理后台访问令牌>
|
||||
|
||||
- **后端负责人**:@yst / yaosutu
|
||||
- **前端消费方**:管理后台车辆核单 Tab
|
||||
|
||||
## 验证证据(2026-08-10 复核回填,wx)
|
||||
|
||||
> 本条 changelog 2026-08-06 推送时 backend_status=pending(未部署),违反「测试服部署+实测后才通知前端」流程。2026-08-10 复核补齐部署与验证证据如下:
|
||||
|
||||
- 测试服 hl-order-service-v3 运行版本为 2026-08-10 15:10 构建(dev-v3),晚于 PR #5570 合并点(2026-08-06 08:17),本变更代码已在运行实例中。
|
||||
- `GET /v3/admin/order/{orderId}/settlement/step3/vehicles` 经网关 9443 + 真 admin token 实测链路连通(普通订单返回既有业务码,行为正常)。
|
||||
- 测试库当前无 `flow_status=TERMINATED` 的历史终止订单,`LEGACY_VEHICLE_FEE_SOURCE_MISSING` 安全空态场景暂无法端到端复现,该场景行为以合并代码 + 部署点位确认;如前端联调需要真实数据请联系后端造数。
|
||||
|
||||
@@ -7,13 +7,13 @@ author: "yst"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@479c44ce399995782504887e0f3aa68274a24e19"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-06"
|
||||
status_note: "hl-resource-service;PR #5583 已合并 dev-v3 并部署测试服,Gateway 实调验证通过(3 接口 + 必填/关键词/鉴权边界全绿);等待管理后台接入。"
|
||||
updated_at: "2026-08-06"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@790e76ab1cadf44d81e610152432fb806144d898"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5597 已合并 dev-v3(99baa37a8)并部署 TEST;网关验证 3/3(推荐车/司机+备选、只读确认无写操作、605012)。wx 口径:系统只做推荐填充,所有写操作车务手动点击(确认走既有派车接口)。前端需在派车弹窗接入一键重派入口(新接口仅返回推荐数据)。"
|
||||
updated_at: "2026-08-06"
|
||||
status_note: "2026-08-07 协调台实测:26-6457 改需求(SUV→商务车)后从 assigned 回「待派车」(旧派单清空待重派),页面含「一键清空」入口,改需求→重派流程通,标 verified。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T14:48:09+08:00"
|
||||
---
|
||||
@@ -22,6 +22,18 @@ generated: "2026-08-06T14:48:09+08:00"
|
||||
|
||||
> 后端完成:PR #5597 已合并 dev-v3 并部署 TEST,网关验证 3/3 通过。
|
||||
|
||||
## ⚠️ 口径最终确认(wx,2026-08-07):要的是【一键清空】
|
||||
|
||||
**不是"一键重派",是"一键清空"**——定制师改出发日期/人数/用车需求后,车务点【一键清空】把之前配置完的车辆清空。
|
||||
|
||||
**前端实现**:
|
||||
- 【一键清空】按钮 = 调**既有接口** `DELETE /admin/fleet/assignments/slots/{slotId}`(Body: `{"reason":"需求变更,一键清空"}`),**不需要新接口**
|
||||
- 删除联动取消已派车、日切片/快照/看板自动一致;返回 removedRowCount/cancelledAssignmentCount/retainedSlotIds
|
||||
- 清空后:车务按新需求**手动**重新派(`auto-recommend` 仅推荐参考,**不自动配**)
|
||||
- 错误码:605012 槽位不存在 / 605007 已最终确认不可删 / 605027 已出发不可删
|
||||
|
||||
下文的 auto-recommend 仅作推荐数据源(可选),清空动作走 DELETE /slots/{slotId}。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5593"
|
||||
title: "取消派单退保幂等:退保检查任务独立类型+重试不调保司+任务锁短重试"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@2b2dedfe65aea63746b62de5188f82630461cc80"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-06"
|
||||
status_note: "后端完成:PR #5605 合并 dev-v3(ef5199207)并部署 TEST;网关实测取消派单退保幂等——每保单仅 1 条 REFUND 明细(同保单不重复退保)、REFUND_CHECK 独立类型(退保检查)逐服务日收敛、taskType=REFUND 筛选不再混入检查任务。前端需在任务列表/筛选/详情展示 REFUND_CHECK(退保检查)类型标签。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T15:40:15+08:00"
|
||||
---
|
||||
|
||||
# 取消派单退保幂等:退保检查任务独立类型+重试不调保司+任务锁短重试
|
||||
|
||||
> 服务端已部署 TEST 并网关验证;前端展示适配项见"前端/调用方动作"。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5593](https://git.1814.love:8443/wx/HL/issues/5593)
|
||||
- **PR**: [#5605](https://git.1814.love:8443/wx/HL/pulls/5605)
|
||||
- **Merge commit**: [ef5199207](https://git.1814.love:8443/wx/HL/commit/ef5199207)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 | 变更 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/admin/fleet/insurance/tasks` | `FleetInsuranceTaskController`(fleet) | 查询参数 `taskType` 新增取值 `REFUND_CHECK`(退保检查);`taskType=REFUND` 只返回真实调保司退保的明细任务,不再混入 REFUND_CHECK 汇总任务 |
|
||||
| `POST` | `/admin/fleet/insurance/tasks/<taskId>/retry` | `FleetInsuranceTaskController`(fleet) | 语义变更:`REFUND_CHECK` 类型任务重试只收敛服务日退保检查结论,**绝不调用保司退保**(修复前 REFUND_CHECK 任务复用 REFUND 类型,重试会走退保逻辑) |
|
||||
|
||||
## 契约影响文件
|
||||
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/insurance/enums/FleetInsuranceTaskTypeEnum.java`(新增 `REFUND_CHECK("REFUND_CHECK","退保检查")`)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/insurance/vo/FleetInsuranceTaskPageReqVO.java`(taskType 枚举校验 + REFUND_CHECK)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/insurance/FleetInsuranceTaskService.java`(REFUND_CHECK 构造按 bizKey 前缀自动解析类型;retryRefundCheck 不调保司;tryLockTask 瞬时锁竞争短重试 500ms×1)
|
||||
|
||||
## 根因与修复说明
|
||||
|
||||
- 取消派单同保单出现"2 条 REFUND 都 SUCCESS":其中 1 条是 **REFUND_CHECK 逐服务日退保检查汇总**(纯本地收敛、不调保司),因任务类型枚举只有 PURCHASE/REFUND,检查任务 taskType 复用 REFUND,管理端按 taskType=REFUND 查询同保单出现 2 条 → 重复退保观感;**保游侧实际只退 1 次**(cancel 仅在 REFUND 明细执行)
|
||||
- 潜在真重复源:REFUND_CHECK 任务被人工重试时旧代码走 `retryRefund` 会调保司退保——已修复(检查任务重试只收敛不调保司)
|
||||
- 附带:司机险任务锁瞬时竞争导致的"任务锁暂不可用"PENDING 积压——`tryLockTask` 获取失败后短等待 500ms 重试一次
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
1. **任务列表类型展示**:`taskTypeLabel` 新增"退保检查"(REFUND_CHECK),任务列表/详情可区分"退保"(REFUND 明细)与"退保检查"(REFUND_CHECK 汇总),避免同保单重复退保观感
|
||||
2. **类型筛选**:任务筛选器 taskType 可选值增加 `REFUND_CHECK`;`REFUND` 筛选现在只返回真实退保明细(历史 REFUND_CHECK 任务仍为 REFUND 老值,属存量数据不迁移)
|
||||
3. **重试按钮**:REFUND_CHECK 任务可重试(只收敛检查结论,不会触发退保);提示文案建议区分"重试退保"与"重试退保检查"
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:FleetInsuranceTaskServiceTest 139/139(+3:REFUND_CHECK 重试收敛不调保司 / 明细缺失保持待处理 / 服务日有效派单拒绝重试)、FleetInsuranceTaskControllerTest 13/13、AssignmentInsuranceOutboxProcessorTest 44/44;fleet 全量 verify BUILD SUCCESS(含 spotless,10:54 min)
|
||||
- 网关验证(TEST,海日汗 perTrip 2085268464900960257 取消):
|
||||
- 取消后每保单仅 **1 条 REFUND 明细**(08-20 保单 2085268465328758786、08-23 保单 2085268539798626306 各 1 条,均 SUCCESS 且保单 CANCELLED)——同保单不重复退保
|
||||
- **REFUND_CHECK(退保检查)独立类型**逐服务日 4 条(08-20~23)全部 SUCCESS(有保单日"全部退保明细已成功"、无保单日"无需退保")
|
||||
- `taskType=REFUND` 筛选只返回 2 条明细(不再混入检查任务);`taskType=REFUND_CHECK` 筛选返回 4 条检查任务
|
||||
- 已取消派单重复取消被状态机拒绝(605020)
|
||||
- 兼容性结论:历史 REFUND_CHECK 任务(taskType 存量 REFUND)不迁移,仅在列表中显示原"退保"标签;新产生任务使用 REFUND_CHECK 类型;接口字段结构不变
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5594"
|
||||
title: "退保前校验保单已出单,未出单撤销投保任务而非退保成功"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5606 已合并 dev-v3 并部署 TEST,网关验证通过。退保收敛前校验保单已出单(INSURED),未出单时撤销投保任务并收敛退保为撤销终态,不再产生'未投保却退保成功'假象。"
|
||||
updated_at: "2026-08-06"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T16:10:00+08:00"
|
||||
---
|
||||
|
||||
# 退保前校验保单已出单,未出单撤销投保任务而非退保成功
|
||||
|
||||
> 后端完成:PR #5606 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5594](https://git.1814.love:8443/wx/HL/issues/5594)
|
||||
- **PR**: [#5606](https://git.1814.love:8443/wx/HL/pulls/5606)
|
||||
- **Merge commit**: [a95d949b](https://git.1814.love:8443/wx/HL/commit/a95d949b)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
TEST 实证(道尔吉 08-30):派单取消退保时,投保任务(PURCHASE)仍处于 PENDING(错误 100503「司机险任务锁暂不可用」,insurance_order_id 为空),但同日退保检查(REFUND_CHECK)已收敛 SUCCESS——形成「未投保成功却退保成功」假象。
|
||||
|
||||
根因:`refundServiceDateIfNoActiveAssignment` 在退保候选保单(INSURED/INSURING)为空时直接收敛 REFUND_CHECK 为 SUCCESS;`retryRefund`/`refundPolicyUnderLifecycleLock` 在无保单或非退保候选时按幂等「退保成功」收敛——未出单投保任务被悬挂、退保却显示成功,投保/退保状态机不一致。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/fleet/driver-insurance/task/list` | `DriverInsuranceController`(fleet) | 状态枚举新增 `CANCELLED`(已撤销)终态:未出单投保任务撤销后任务列表展示「已撤销」,退保任务同样收敛为「已撤销」而非「成功」 |
|
||||
|
||||
无请求/响应字段增减;仅 `FleetInsuranceTaskStatusEnum` 新增 `CANCELLED` 状态值,管理后台任务状态展示与筛选可见新增状态。**无前端 API 变化**。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- **退保前校验保单已出单**:`refundServiceDateIfNoActiveAssignment` 发现该司机该服务日无已出单保单(INSURED)且存在未出单投保任务(PURCHASE PENDING/PROCESSING 且 insurance_order_id 为空)时,先撤销投保任务为 `CANCELLED`(终态),再收敛退保任务为撤销终态,**不再产生「退保成功」假象**
|
||||
- **幂等边界**:已有保险订单的投保中任务(如退保后重投)不撤销,避免误伤合法在途投保
|
||||
- **投保/退保状态机一致性**:PURCHASE 未出单 → CANCELLED(撤销,无保单);REFUND/REFUND_CHECK 在无投保任务时保持既有幂等 SUCCESS(权威保单本就无可退);撤销场景统一收敛 CANCELLED
|
||||
- **汇总收敛**:`summarizeRefundChecks` 将 CANCELLED 计为撤销收敛,避免误判 RETRYABLE_FAILURE 无限重试
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
无。任务列表状态展示自动透出新枚举值「已撤销」,无需前端配合。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:FleetInsuranceTaskServiceTest 142/142(含 3 个新增用例:取消退保撤销投保、无任务幂等成功、重试退保撤销);保险域 187 项全过
|
||||
- fleet verify **3199 项**(含 MySQL 集成)0 失败;spotless 通过
|
||||
- 网关验证(TEST,16:22 部署后):① 任务列表 200,道尔吉 08-30 存量任务可见(PURCHASE PENDING 未出单 + REFUND SUCCESS 旧假象),部署未破坏存量;② 保单接口实证 08-30 起保保单 0 张("未投保却退保成功"场景实锤);③ 对已 SUCCESS REFUND 任务 retry 幂等安全(返回 SUCCESS、不调保司、不重复退保)
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5595"
|
||||
title: "全程行换司机不再被605909误拦(历史跨日派单迁移判定放宽 + 存量迁移工具)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-06"
|
||||
status_note: "后端完成:PR #5602/#5607/#5609 已合并 dev-v3 并部署 TEST(15:52 滚动 DONE);迁移工具已执行(5/5 存量行 group_id 回清);网关验证三轮换司机全 PASS:change 200(修复前 605909)、旧行 canceled、新行 assigned 全程形态、退旧投新占用正确。前端无需配合。"
|
||||
updated_at: "2026-08-06"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T16:10:00+08:00"
|
||||
---
|
||||
|
||||
# 全程行换司机不再被 605909 误拦(历史跨日派单迁移判定放宽 + 存量迁移工具)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5602](https://git.1814.love:8443/wx/HL/pulls/5602)、[#5607](https://git.1814.love:8443/wx/HL/pulls/5607)、[#5609](https://git.1814.love:8443/wx/HL/pulls/5609)
|
||||
> **Issue**: [#5595](https://git.1814.love:8443/wx/HL/issues/5595)
|
||||
> **日期**: 2026-08-06
|
||||
> **影响**: 🟢 **缺陷修复**,无契约变更(`POST /admin/fleet/assignments/{assignmentId}/change` 行为修复)。已派/待确认**全程行**(#5562 全程槽模型,`service_date` 为 NULL、`start_date~end_date` 覆盖整个服务期)此前 change 被 605909「发现未完成迁移的历史跨日派单」拦截,本次修复后换车/换司机/退旧投新全链路可用。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5595(P1)E2E 实测:档期冲突A 已派全程行(`2085213644705927170`,assigned,8/20~8/23)换司机 → 605909 拦截,换人走不通。
|
||||
|
||||
**根因**(两条链叠加):
|
||||
1. #5589 在 `consumePlaceholderToActive` 把全程行升级回填 `assignment_group_id=自身行 id`,使 #5562 全程槽模型的全程行(`group_id` 应为 NULL)被 `ensureDailySlices`/`normalizeActiveSlotRows` 误判为「历史跨日聚合行」→ change/confirm 触发 605909;
|
||||
2. 即使迁移清回 `group_id`,change 整槽替换产物(`group_id=新组`、`service_date` NULL)在**再次 change** 时仍因 `isFullTripRow`(要求 `group_id IS NULL`)与 `cancelActiveSlotRange`(全程行分支要求 `group_id IS NULL`)被误判 → 605909/605020。
|
||||
|
||||
## 修复内容(内部,无接口/字段变化)
|
||||
|
||||
1. **占位消费不再回填组 id**:`consumePlaceholderToActive` 移除 `assignment_group_id=自身行 id` 回填,全程行升级保持 `group_id IS NULL`(#5562 模型一致);HOLD 通知单行组命中由 `selectByAssignmentGroupId`/`ForUpdate`/`markHoldNotificationSent` 的 `assignment_id + group_id IS NULL` 兜底保证(#5589 能力保留);
|
||||
2. **全程形态行判定放宽**:`isFullTripRow` 从「`group_id IS NULL` 且多日」放宽为「`service_date IS NULL` 且 `start/end` 完整」——change 整槽替换产物(带新组 id)同样按全程行处理;`ensureDailySlices`/`normalizeActiveSlotRows` 对全程形态行豁免拆天,不再触发 605909;
|
||||
3. **改派保持全程形态**:`buildReplacementSlice` 对全程形态行保持 `service_date` NULL、`start/end` 覆盖整个服务期(修复前被压成 effectiveDate 单日切片,丢服务期剩余日期);change 冲突检查/日志范围覆盖整个服务期;
|
||||
4. **整槽取消放宽**:`cancelActiveSlotRange` 全程行分支由「`service_date IS NULL AND group_id IS NULL`」放宽为「`service_date IS NULL`」(外层已限定槽位匹配);
|
||||
5. **存量迁移工具**:`hl-workflow/tools/migrate_5595_fulltrip_single_row_group.py`——把 #5589 回填产生的「单行组全程行」(`group_id=自身`、`service_date` NULL、多日、in-flight)幂等回清 `group_id=NULL`,带 manifest 回滚;TEST 已执行 5/5。
|
||||
|
||||
## 接口行为
|
||||
|
||||
- `POST /admin/fleet/assignments/{assignmentId}/change`(换车/换司机):全程形态行(assigned/holding)**200**,旧行 canceled、新行 assigned 且保持全程形态(`service_date` NULL、`start/end` 覆盖全程),退旧投新占用正确(旧司机释放、新司机建立)。
|
||||
- 历史跨日聚合行(`service_date` 非 NULL 的旧版多日行)仍按 605909 保护(#5318 语义保留)。
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5596"
|
||||
title: "派单预校验增加驾照/年检/车辆保险到期强提醒(不拦截,车务人工判断)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "hl-ui"
|
||||
frontend_ref: "hl-admin@572d25e64b5d5ed2cb325dcbb377978c425d4f1e"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "2026-08-07 协调台实测:派车选车页对司机显示「无保险」「全年保险不覆盖本行程」「常驻司机已拉黑」等预校验警告(驾照/年检/保险提醒生效),标 verified。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T18:30:00+08:00"
|
||||
---
|
||||
|
||||
# 派单预校验增加驾照/年检/车辆保险到期强提醒(不拦截)
|
||||
|
||||
> 后端完成:PR #5617 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5596](https://git.1814.love:8443/wx/HL/issues/5596)
|
||||
- **PR**: [#5617](https://git.1814.love:8443/wx/HL/pulls/5617)
|
||||
- **Merge commit**: [f846361f](https://git.1814.love:8443/wx/HL/commit/f846361f)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
协调台实测:司机王信驾照 **2022-02-05 已过期 4 年**,但派单 precheck 无驾照到期校验——仅提示档期冲突/跨常驻,过期司机仍可正常派单(安全合规隐患)。
|
||||
|
||||
**口径(wx 定)**:驾照/证件到期**不拦截,强提醒即可**——车务人工判断是否派单。
|
||||
|
||||
## 变更接口
|
||||
|
||||
### `POST /admin/fleet/assignments/precheck`(修改响应)
|
||||
|
||||
`warnings` 数组新增 3 个非阻断强提醒类型(只进 warnings、不影响 `conflict` 阻断判定):
|
||||
|
||||
| type | 触发条件 | 文案示例 |
|
||||
|---|---|---|
|
||||
| `license_expired` | 司机驾照到期日早于本单服务结束日 | 司机驾照已于 2022-02-05 过期(本单服务期间证件失效),请人工确认是否派单 |
|
||||
| `veh_inspect_expired` | 车辆年检(行驶证)到期日早于本单服务结束日 | 车辆年检已于 2025-01-01 过期(本单服务期间证件失效),请人工确认是否派单 |
|
||||
| `veh_insure_expired` | 车辆保险到期日早于本单服务结束日 | 车辆保险已于 2025-06-01 过期(本单服务期间无保险保障),请人工确认是否派单 |
|
||||
|
||||
判定基准取**服务结束日**(行程期间任一天证件失效都提醒),无结束日回退开始日;证件到期日为空不提示。
|
||||
|
||||
### `POST /admin/fleet/assignments/candidates`(修改响应)
|
||||
|
||||
`drivers.records[]`(司机候选)新增字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `licenseExpire` | string(date) | 驾照到期日;未填为空 |
|
||||
| `licenseExpired` | boolean | 驾照是否已过期(相对请求服务日期;候选页醒目提示用) |
|
||||
| `licenseExpiredMessage` | string | 醒目提示文案,如「司机驾照已于 2022-02-05 过期,请人工确认」;未过期为空 |
|
||||
|
||||
## 行为变化
|
||||
|
||||
- **过期司机派单不再无感放行**:precheck 对驾照/年检/保险过期的车、司机给出强提醒 warning,弹窗醒目展示「驾照已过期」,但**不拦截**——车务人工判断是否派单
|
||||
- **候选页醒目提示**:司机候选列表直接透出 `licenseExpired` 标记与文案,前端可标红「驾照已过期」
|
||||
- **不改变既有阻断逻辑**:证件到期不影响 `conflict`/`conflicts`,档期冲突、资源不可用、跨常驻等既有判定不变
|
||||
- 写接口(create/change)**不新增拦截**,与口径一致(人说的算)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- **派单弹窗(precheck)**:warnings 出现 `license_expired` / `veh_inspect_expired` / `veh_insure_expired` 时醒目展示(红色警示样式),确认后仍可正常提交派单
|
||||
- **候选页**:司机候选 `licenseExpired=true` 时展示「驾照已过期」醒目标签(可用 `licenseExpiredMessage` 直接展示),不限制选择
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试 572 项全绿:AssignmentServiceTest 378(新增 2 例:过期证件强提醒不阻断 conflict=false、证件未过期无提示)、AssignmentCandidateServiceTest 47(新增 2 例:候选驾照过期标记与文案、有效驾照不标记)、DriverServiceTest 147
|
||||
- fleet 全量 verify 3203 项,仅 ReleaseEMixedBinaryHarnessTest 2 个 Windows 进程时序用例 flaky 失败(单独重跑 23/23 全绿、干净基线同样全绿、失败用例每次不同=并行负载竞争,与本次改动无关);MySQL 8.0.33 集成 12/12 单独复跑全绿;spotless 通过
|
||||
- 网关验证(TEST,18:10 部署后):① precheck 过期司机王信+有效车 → HTTP 200、conflict=false(不阻断)、warnings 含 `license_expired`「司机驾照已于 2022-02-05 过期(本单服务期间证件失效),请人工确认是否派单」;② 对照有效驾照司机 → 无任何证件到期提醒;③ 候选接口王信 `licenseExpired=true`、`licenseExpiredMessage`「司机驾照已于 2022-02-05 过期,请人工确认」;共 10/10 通过
|
||||
@@ -0,0 +1,312 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5598"
|
||||
title: "终止行程重复提交改一律 581049:COMPLETED 订单再次提交不再重放首次结果"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "hl-ui"
|
||||
frontend_ref: "hl-admin@f1874ef91e12e49cf9014f4ad17b008fe08996aa"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5604 已合并 dev-v3 并部署 TEST,网关实测:COMPLETED 订单再次提交 terminate 一律返回 581049「订单已终止,禁止重复提交」(同正文也不再 200 重放)。前端如依赖 #5460 的同正文重放取回结果逻辑需适配。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 🔧【修改接口·管理后台】终止行程重复提交改一律 581049 (#5598)
|
||||
|
||||
> **PR**:[#5604](https://git.1814.love:8443/wx/HL/pulls/5604) | **服务**:`hl-order-service-v3` | **更新时间**:2026-08-06 | **消费端**:管理后台
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
终止行程(出行中提前结束订单)是**有副作用的写操作**:首次提交会把订单从 TRAVELLING 推进到 COMPLETED、生成终止退款单、写核单退款关联。此前(#5460)对"首次成功但响应丢失"的场景提供了幂等重放:同正文重放返回 200 和既有退款结果。实测中发现该重放语义让"重复提交"与"首次提交"边界模糊,且重放判定依赖请求指纹快照,对升级前的历史订单还要做语义等价兜底,复杂度高、收益低。
|
||||
|
||||
本次收紧为**状态机幂等**:订单一旦 COMPLETED(已终止/已完成),再次提交终止**一律拒绝**,返回 `581049「订单已终止,禁止重复提交」`,零副作用(不重写退款、不重发事件、不做指纹比对)。
|
||||
|
||||
**首次正常终止(TRAVELLING → COMPLETED)行为完全不变**,仍返回 200 和退款结果。
|
||||
|
||||
## 变更接口清单
|
||||
|
||||
| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 终止行程(出行中) | `POST` | `/v3/admin/order/:id/terminate` | 修改 | COMPLETED 订单重复提交:同正文重放由 `200` 返回既有结果改为一律 `581049`;581049 文案由「终止行程重试与首次终止边界或请求不一致」改为「订单已终止,禁止重复提交」 |
|
||||
|
||||
入参字段结构、出参字段结构**均无变化**。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 终止行程(出行中)
|
||||
|
||||
- **接口说明**:出行中因特殊原因提前终止订单,订单进入 COMPLETED 状态,由核单/结算流程继续处理;后端按权威数据重算退款金额。
|
||||
- **使用场景**:管理后台订单详情页,出行中订单执行"终止行程"操作。
|
||||
- **认证**:需要管理后台登录态和订单操作权限;房务角色不可操作。
|
||||
- **幂等性**:**不再提供重放幂等**。首次提交成功即生效;订单 COMPLETED 后任何再次提交(无论正文是否与首次一致)一律返回 `581049`,零副作用。
|
||||
- **限流**:未声明接口级独立限流规则。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 说明与校验 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | Path | Long | 是 | 订单 ID(路径写作 `:id`) |
|
||||
|
||||
无 Query 参数。
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
请求体类型:`OrderTerminateTripReqVO`(**本次无变化**)。
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明与校验 |
|
||||
|---|---|---|---|
|
||||
| `cancelReason` | String | 是 | 终止原因 + 调整说明(合并,不单列 adjustReason) |
|
||||
| `endDayNumber` | Integer | 是 | 停在第几天(截断点,1-based),最小 1 |
|
||||
| `lineUsages[]` | Array | 否 | 统一资源行已用判定列表;优先级高于旧分组数组,`lineKey` 来自 refund-preview 的 `refundLines[].lineKey` |
|
||||
| `lineUsages[].lineKey` | String | 是(数组内) | 退款资源行 key,示例 `ITINERARY_NODE:97010:2` |
|
||||
| `lineUsages[].used` | Boolean | 是(数组内) | 是否已使用 |
|
||||
| `rooms[]` | Array | 否 | 住宿已用判定列表;兼容旧入参,新逻辑优先使用 `lineUsages` |
|
||||
| `tickets[]` | Array | 否 | 门票/活动已用判定列表;兼容旧入参,`refId` = 行程节点 nodeId |
|
||||
| `services[]` | Array | 否 | 服务已用判定列表;兼容旧入参,`refId` = 行程节点 nodeId |
|
||||
| `supplies[]` | Array | 否 | 备品已用判定列表;兼容旧入参,`refId` 与预览返回一致 |
|
||||
| `rooms[]/tickets[]/services[]/supplies[]` 元素 | Object | — | 结构为 refId Long 必填 + used Boolean 必填 |
|
||||
| `vehicles[]` | Array | 否 | 用车每天已用判定列表;仅兼容接收,`used` 不参与计算,后端按车辆 serviceDate 与终止日权威重算 |
|
||||
| `vehicles[].refId` | Long | 是(数组内) | 配车记录 ID(同段车跨天 refId 相同,靠 dayNumber 区分) |
|
||||
| `vehicles[].dayNumber` | Integer | 是(数组内) | 第几天(1-based) |
|
||||
| `vehicles[].used` | Boolean | 是(数组内) | 兼容字段:必填,但车辆退款金额不采用该值 |
|
||||
| `adjustAmount` | Decimal | 否 | 人工调整额(允许负,默认 0;说明并入 cancelReason) |
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
响应类型:`Result<OrderTerminateTripRespVO>`(**本次无变化**)。
|
||||
|
||||
### 5.1 统一响应外层
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `code` | Integer | 否 | 成功为 `200`;失败见第 7 节 |
|
||||
| `message` | String | 否 | 结果说明 |
|
||||
| `data` | Object/null | 失败时为空 | 成功时为终止结果 |
|
||||
| `traceId` | String | 是 | 链路追踪 ID |
|
||||
| `success` | Boolean | 否 | `code=200` 时为 `true` |
|
||||
|
||||
### 5.2 成功响应 `data`
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `terminateRefundId` | String(Long) | 否 | 终止退款主表 ID,按字符串返回 |
|
||||
| `baselineRefund` | String(Decimal) | 否 | 系统建议基线 = max(0, 已付金额 - 已用金额),按字符串返回 |
|
||||
| `adjustAmount` | String(Decimal) | 否 | 人工调整额(入参回显,默认 0),按字符串返回 |
|
||||
| `finalRefund` | String(Decimal) | 否 | 最终返还额 = max(0, baselineRefund + adjustAmount),按字符串返回 |
|
||||
| `settlementRefundId` | String(Long) | 是 | 关联的核单退款结算 ID(核单模块写入后回填),按字符串返回 |
|
||||
| `newStatus` | String | 否 | 终止后订单粗状态,固定 `COMPLETED` |
|
||||
| `newFlowStatus` | String | 否 | 终止后流程细状态,固定 `PENDING_REVIEW` |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `data.newStatus`(订单粗状态,本接口相关取值)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `TRAVELLING` | 出行中 | 终止前状态;只有该状态可首次提交终止 |
|
||||
| `COMPLETED` | 已完成 | 终止后状态;该状态下再次提交一律 581049 |
|
||||
|
||||
### 6.2 `data.newFlowStatus`(流程细状态)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `PENDING_REVIEW` | 待核单 | 终止成功后进入核单流程 |
|
||||
|
||||
本次无数值新增/删除/改义。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| `200` | 终止成功 | 订单为 TRAVELLING 且校验通过;订单推进 COMPLETED 并生成终止退款 |
|
||||
| `400` | 请求参数错误 | 缺 `cancelReason`/`endDayNumber`、数组内必填字段缺失等 |
|
||||
| `581007` | 订单不存在 | `id` 对应订单不存在 |
|
||||
| `581018` | 出行中取消仅适用于出行中订单 | 订单非 TRAVELLING 且非 COMPLETED(待出行/已取消等)时提交终止 |
|
||||
| `581044` | 终止行程:结束日之前的资源必须标记为已使用 | 结束日前资源 used=false |
|
||||
| `581047` | 终止行程:结束日不在订单行程范围内 | `endDayNumber` 越界 |
|
||||
| `581048` | 终止行程:本地车辆费用服务日与订单行程不一致 | 车辆费用服务日与订单行程对不上 |
|
||||
| **`581049`** | **订单已终止,禁止重复提交**(原「终止行程重试与首次终止边界或请求不一致」) | **订单已 COMPLETED 时再次提交终止,无论正文是否与首次一致** |
|
||||
| `584100` | 车务车辆总车费暂时不可用,请稍后重试 | Fleet 车辆费用事实不可用,fail closed |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:出行中订单首次终止(行为不变)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2084280231786430466/terminate
|
||||
Authorization: Bearer <管理后台访问令牌>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户高反送医终止",
|
||||
"endDayNumber": 2,
|
||||
"adjustAmount": 0,
|
||||
"lineUsages": [
|
||||
{
|
||||
"lineKey": "HOTEL_ASSIGNMENT:96011:1",
|
||||
"used": true
|
||||
},
|
||||
{
|
||||
"lineKey": "ITINERARY_NODE:97010:1",
|
||||
"used": true
|
||||
}
|
||||
],
|
||||
"vehicles": [
|
||||
{
|
||||
"refId": 9000004500000001,
|
||||
"dayNumber": 1,
|
||||
"used": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"terminateRefundId": "2084497785524011009",
|
||||
"baselineRefund": "6500.00",
|
||||
"adjustAmount": "0.00",
|
||||
"finalRefund": "6500.00",
|
||||
"settlementRefundId": "2084497785561759746",
|
||||
"newStatus": "COMPLETED",
|
||||
"newFlowStatus": "PENDING_REVIEW"
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况:COMPLETED 订单重复提交(本次变化点)
|
||||
|
||||
**场景说明**:首次终止已成功(无论响应是否丢失),对同一订单再次提交终止——与首次正文完全相同或不同,结果一样。改前同正文重放会返回 200 和首次结果;改后一律 581049,零副作用。
|
||||
|
||||
**请求**:同 8.1(正文可与首次一致,也可不一致)。
|
||||
|
||||
**响应**(TEST 网关实测):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581049,
|
||||
"message": "订单已终止,禁止重复提交",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败:结束日越界
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2084280231786430466/terminate
|
||||
Authorization: Bearer <管理后台访问令牌>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户临时改行程",
|
||||
"endDayNumber": 99
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581047,
|
||||
"message": "终止行程:结束日不在订单行程范围内",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用场景**:订单为 TRAVELLING(出行中)时首次提交终止,走正常终止流程,返回 200 和退款结果。
|
||||
- **不适用场景**:
|
||||
- 订单已 COMPLETED(已终止/已完成):任何再次提交一律 `581049`,不产生任何副作用。
|
||||
- 订单为其他状态(待出行/已取消等):`581018`。
|
||||
- **特殊边界**:终止是有副作用操作,`581049` 不等于"重试成功";前端拿到 581049 后如需终止结果(terminateRefundId / finalRefund),应改走订单详情 / 退款相关查询接口取数,不要再重放 terminate。
|
||||
- **金额口径不变**:车辆已用仍以车辆 serviceDate 与终止日权威判定,`vehicles[].used` 仅为兼容字段。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 项 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 入参字段结构 | `OrderTerminateTripReqVO` 全字段 | 不变 |
|
||||
| 出参字段结构 | `OrderTerminateTripRespVO` 全字段 | 不变 |
|
||||
| 581049 错误文案 | 终止行程重试与首次终止边界或请求不一致 | **订单已终止,禁止重复提交** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 原来(#5460) | 现在 |
|
||||
|---|---|---|
|
||||
| TRAVELLING 订单首次终止 | `200` 返回退款结果,订单推进 COMPLETED | **不变** |
|
||||
| COMPLETED 订单**同正文**重放 | `200`,返回既有 terminateRefundId / baselineRefund / adjustAmount / finalRefund / settlementRefundId | **`581049` 订单已终止,禁止重复提交**,零副作用 |
|
||||
| COMPLETED 订单**不同正文**重试 | `581049`(旧文案) | `581049`(新文案),行为不变 |
|
||||
| 升级前已终止的历史订单重放 | 按快照语义等价校验,等价返 200 / 不等价 581049 | 一律 `581049` |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**是,行为级破坏**。依赖"同正文重放返回 200 取回首次结果"的前端重试/补偿逻辑会失效——现在重放拿到的是 581049。
|
||||
- **前端是否必须同步上线**:**建议同步**。适配方式:
|
||||
1. 终止提交后若因超时/网络原因未收到响应,重试时收到 `581049` 应视为"订单已终止"(首次实际已成功),按成功路径收尾(刷新订单详情),不要当普通错误提示"提交失败"。
|
||||
2. 如需展示首次终止的退款结果,收到 581049 后拉订单详情/退款查询接口取数,不再从重放响应获取。
|
||||
3. 清理 #5460 时期按"同正文重放取回结果"编写的 workaround。
|
||||
- 首次终止路径零变化,不重试的正常操作流程无感。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚即恢复 #5460 重放语义(同正文 200 / 不同正文 581049 旧文案)。前端兼容策略"581049 = 已终止,拉详情取数"在回滚后仍然成立(只是同正文重放会重新拿到 200),无需反向适配。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `581049` 新文案是"订单已终止,禁止重复提交",**不要再按旧文案"边界或请求不一致"做文案匹配**;按 code 判断。
|
||||
- 收到 `581049` 时订单已是 COMPLETED,不要引导用户"修改正文后重试"——任何正文都会被拒。
|
||||
- 终止结果字段(terminateRefundId / finalRefund 等)只有首次终止的 200 响应返回;之后只能从查询类接口获取。
|
||||
- `581018` 与 `581049` 分工:非 TRAVELLING 且未终止过 → 581018;已 COMPLETED → 581049。
|
||||
|
||||
|
||||
## 验证证据
|
||||
|
||||
- `mvn -pl hl-order-service-v3 -am verify` 全绿(含重复终止一律 581049 的回归测试)。
|
||||
- TEST 网关实测(2026-08-06):COMPLETED 订单再次提交 terminate 返回 `code=581049`、`message=订单已终止,禁止重复提交`、`success=false`,零副作用;TRAVELLING 订单首次终止仍 200 返回退款结果。
|
||||
- 已部署 TEST(PR #5604 合并 dev-v3 后滚动发布)。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**:[#5598](https://git.1814.love:8443/wx/HL/issues/5598)
|
||||
- **PR**:[#5604](https://git.1814.love:8443/wx/HL/pulls/5604)
|
||||
- **Merge commit**:[047bb72951](https://git.1814.love:8443/wx/HL/commit/047bb72951)
|
||||
- **被取代的契约**:[04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md](./04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md)(同正文重放语义本次起作废)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**:@yst / yaosutu(腰苏图)
|
||||
- **前端消费方**:管理后台订单详情 - 终止行程操作
|
||||
@@ -0,0 +1,328 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5599"
|
||||
title: "核单餐食餐厅下拉值独立存储"
|
||||
consumer: "admin"
|
||||
author: "yst"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@01bd3875046cd32e9497e2f00a9eb0f782a197dd"
|
||||
verified_at: ""
|
||||
target_release: ""
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
status_note: "PR #5600 已合并 dev-v3;2026-08-10 复核确认测试服已部署并验证(见文末验证证据章节)。restaurantId 关联资源服务餐厅下拉接口 PR #5583"
|
||||
---
|
||||
|
||||
# 核单餐食餐厅下拉值独立存储(不覆盖餐食名称)
|
||||
|
||||
- **变更类型**:修改接口
|
||||
- **端类型**:管理后台
|
||||
- **服务**:hl-order-service-v3
|
||||
- **日期**:2026-08-06
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单「餐食」Tab 原来只有手动录入的 `mealName`(餐食名称)一个文本字段承载餐食/餐厅信息。业务上需要把「从餐厅资源库选择的餐厅」作为独立结构化数据存下来,便于后续按餐厅维度统计与对账,且**不能覆盖**运营手动填写的餐食名称。
|
||||
|
||||
本次变更:餐食费用的「新增 / 修改 / 回显」三个接口同时新增 `restaurantId` + `restaurantName` 两个字段,与 `mealName` 完全独立、互不联动。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | POST /v3/admin/order/{orderId}/settlement/meals | 入参 +2 字段 | 新增 restaurantId / restaurantName(成对、非必填) |
|
||||
| 2 | PUT /v3/admin/order/{orderId}/settlement/meals/{settlementId} | 入参 +2 字段 | 新增 restaurantId / restaurantName(成对、非必填) |
|
||||
| 3 | GET /v3/admin/order/{orderId}/settlement/meals | 出参 +2 字段 | 回显新增 restaurantId / restaurantName |
|
||||
|
||||
无删除字段、无改名字段、无枚举值变化。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 新增餐食费用
|
||||
|
||||
- 方法/路径:`POST /v3/admin/order/{orderId}/settlement/meals`
|
||||
- 接口名:新增餐食费用
|
||||
- 认证:管理后台 JWT(网关统一鉴权)
|
||||
- 幂等性:非幂等(重复提交会产生多条餐食记录)
|
||||
- 限流:走网关默认限流,无接口级特殊限流
|
||||
|
||||
### 3.2 修改餐食费用
|
||||
|
||||
- 方法/路径:`PUT /v3/admin/order/{orderId}/settlement/meals/{settlementId}`
|
||||
- 接口名:修改餐食费用
|
||||
- 认证:管理后台 JWT(网关统一鉴权)
|
||||
- 幂等性:幂等(相同 body 重复 PUT 结果一致)
|
||||
- 限流:走网关默认限流
|
||||
|
||||
### 3.3 查询餐食费用(回显)
|
||||
|
||||
- 方法/路径:`GET /v3/admin/order/{orderId}/settlement/meals`
|
||||
- 接口名:查询餐食费用
|
||||
- 认证:管理后台 JWT(网关统一鉴权)
|
||||
- 幂等性:只读接口
|
||||
- 限流:走网关默认限流
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| orderId | path | Long | 是 | 订单ID(三个接口均有) |
|
||||
| settlementId | path | Long | 是 | 餐食费用记录ID(仅 PUT 修改接口) |
|
||||
|
||||
无 Query 参数。
|
||||
|
||||
### 4.2 请求体字段(POST / PUT 共用同一请求结构)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| mealType | String | 是 | 餐型:BREAKFAST / LUNCH / DINNER |
|
||||
| mealDate | Date(yyyy-MM-dd) | 否 | 发生日期 |
|
||||
| mealName | String | 是 | 餐食名称,手动录入,最长 200 字符;**不受餐厅选择影响、不被覆盖** |
|
||||
| quantity | Integer | 是 | 数量,1-10000 |
|
||||
| unitPrice | Number | 是 | 单价,≥ 0,最多 8 位整数 + 2 位小数;unitPrice × quantity ≤ 99999999.99 |
|
||||
| paymentMethod | String | 是 | 付款类型:CASH_PAID / COMPANY_PAID / SIGNED |
|
||||
| sourceType | String | 是 | 来源类型:MEAL_ASSIGNMENT / MANUAL / SYSTEM |
|
||||
| settlementConfirmStatus | String | 是 | 确认状态:UNCONFIRMED / CONFIRMED |
|
||||
| voucherUrls | String[] | 否 | 凭证 URL 列表,最多 9 个,仅支持 http/https,单个最长 1024 字符 |
|
||||
| restaurantId | Long | 否 | **本次新增**。餐厅资源ID,与 restaurantName 成对出现;不选餐厅则两者都不传 |
|
||||
| restaurantName | String | 否 | **本次新增**。餐厅名称快照,与 restaurantId 成对出现,最长 200 字符 |
|
||||
| remark | String | 否 | 备注,最长 512 字符 |
|
||||
|
||||
**成对校验(关键业务规则)**:`restaurantId` 与 `restaurantName` 必须**同时传或同时不传**(restaurantName 为空白字符串视为未传)。只传其一 → 400,message 为「restaurantId 与 restaurantName 必须成对出现」或「餐食费用字段超出允许范围」(错误码 584094,MEAL_EXPENSE_REQUEST_INVALID)。两字段与手动录入的 `mealName` **完全独立、不联动**——选了餐厅也不会改动/覆盖 mealName。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
- POST / PUT 响应:`Result<SettlementMealRespVO>`(单条记录)
|
||||
- GET 响应:`Result<List<SettlementMealRespVO>>`(列表,元素结构相同)
|
||||
|
||||
SettlementMealRespVO 字段表:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String | 餐食费用ID,雪花ID序列化为字符串 |
|
||||
| mealType | String | 餐型 |
|
||||
| mealDate | Date(yyyy-MM-dd) | 发生日期 |
|
||||
| mealName | String | 餐食名称 |
|
||||
| restaurantId | String \| null | **本次新增**。餐厅资源ID,雪花ID序列化为字符串(防 JS 精度丢失);未选餐厅为 null |
|
||||
| restaurantName | String \| null | **本次新增**。餐厅名称快照(选中时记录,餐厅改名/删除不影响已存核单记录);未选餐厅为 null |
|
||||
| quantity | Integer | 数量 |
|
||||
| unitPrice | String | 单价(BigDecimal 序列化为字符串) |
|
||||
| actualAmount | String | 实际金额 = unitPrice × quantity(BigDecimal 序列化为字符串) |
|
||||
| paymentMethod | String | 付款类型 |
|
||||
| sourceType | String | 来源类型 |
|
||||
| sourceTypeName | String | 来源类型名称 |
|
||||
| voucherUrls | String[] | 凭证 URL 列表 |
|
||||
| settlementConfirmStatus | String | 确认状态 |
|
||||
| settlementConfirmStatusName | String | 确认状态名称 |
|
||||
| remark | String | 备注 |
|
||||
|
||||
外层为统一响应结构:`{ "code": 200, "message": "...", "data": ..., "traceId": ..., "success": ... }`;业务失败时 code 为错误码、data 为 null。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次**无新增、无变更**枚举。以下现有枚举取值保持不变:
|
||||
|
||||
| 字段 | 取值 | 说明 |
|
||||
|---|---|---|
|
||||
| mealType | BREAKFAST / LUNCH / DINNER | 早餐 / 午餐 / 晚餐 |
|
||||
| paymentMethod | CASH_PAID / COMPANY_PAID / SIGNED | 现金垫付 / 公司支付 / 签单 |
|
||||
| sourceType | MEAL_ASSIGNMENT / MANUAL / SYSTEM | 餐食安排 / 手动录入 / 系统生成 |
|
||||
| settlementConfirmStatus | UNCONFIRMED / CONFIRMED | 未确认 / 已确认 |
|
||||
|
||||
`restaurantId` 的取值来源:资源服务「餐厅资源下拉选项」接口 `GET /admin/resource-options/restaurants`(PR #5583 已上线)返回的 `resourceId`。餐厅选项是业务数据,不是枚举/字典。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| HTTP | 错误码 | message | 触发条件 |
|
||||
|---|---|---|---|
|
||||
| 400 | 584094 | 餐食费用字段超出允许范围 | restaurantId 与 restaurantName 只传其一(不成对),service 层抛 MEAL_EXPENSE_REQUEST_INVALID |
|
||||
| 400 | (参数校验失败) | restaurantId 与 restaurantName 必须成对出现 | 同上场景,bean validation 层先行拦截时的提示文案 |
|
||||
| 400 | (参数校验失败) | mealType 必须是 BREAKFAST / LUNCH / DINNER 之一 等 | 既有字段校验失败(本次不变) |
|
||||
| 401 | - | 未登录 / token 失效 | 未携带或携带无效的管理后台 JWT |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:选择餐厅保存 + 回显
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/1951234567890123456/settlement/meals
|
||||
Authorization: Bearer {adminToken}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mealType": "LUNCH",
|
||||
"mealDate": "2026-08-10",
|
||||
"mealName": "团队桌餐(10人标)",
|
||||
"quantity": 10,
|
||||
"unitPrice": 68.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"sourceType": "MANUAL",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"voucherUrls": ["https://oss.example.com/voucher/a1.jpg"],
|
||||
"restaurantId": 1889900112233445566,
|
||||
"restaurantName": "海拉尔XX手把肉餐厅",
|
||||
"remark": "导游现场确认"
|
||||
}
|
||||
```
|
||||
|
||||
响应(200):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "1952345678901234567",
|
||||
"mealType": "LUNCH",
|
||||
"mealDate": "2026-08-10",
|
||||
"mealName": "团队桌餐(10人标)",
|
||||
"restaurantId": "1889900112233445566",
|
||||
"restaurantName": "海拉尔XX手把肉餐厅",
|
||||
"quantity": 10,
|
||||
"unitPrice": "68.00",
|
||||
"actualAmount": "680.00",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceTypeName": "手动录入",
|
||||
"voucherUrls": ["https://oss.example.com/voucher/a1.jpg"],
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"settlementConfirmStatusName": "未确认",
|
||||
"remark": "导游现场确认"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
回显:`GET /v3/admin/order/1951234567890123456/settlement/meals` 返回 `data` 为列表,元素结构同上(含 restaurantId / restaurantName)。
|
||||
|
||||
### 8.2 边界:不选餐厅,纯手动录入(两字段都不传)
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/1951234567890123456/settlement/meals
|
||||
Authorization: Bearer {adminToken}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mealType": "BREAKFAST",
|
||||
"mealDate": "2026-08-11",
|
||||
"mealName": "酒店自助早餐",
|
||||
"quantity": 10,
|
||||
"unitPrice": 0,
|
||||
"paymentMethod": "SIGNED",
|
||||
"sourceType": "MANUAL",
|
||||
"settlementConfirmStatus": "CONFIRMED"
|
||||
}
|
||||
```
|
||||
|
||||
响应(200):`data` 中 `restaurantId: null`、`restaurantName: null`,其余字段正常填充。存量历史餐食记录回显同样两字段为 null。
|
||||
|
||||
### 8.3 业务失败:只传 restaurantId、缺 restaurantName(不成对)
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"mealType": "DINNER",
|
||||
"mealName": "涮羊肉",
|
||||
"quantity": 10,
|
||||
"unitPrice": 88.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"sourceType": "MANUAL",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"restaurantId": 1889900112233445566
|
||||
}
|
||||
```
|
||||
|
||||
响应(400):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584094,
|
||||
"message": "餐食费用字段超出允许范围",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
(若 bean validation 层先行拦截,message 为「restaurantId 与 restaurantName 必须成对出现」。反向场景——只传 restaurantName 不传 restaurantId——同样 400。)
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- 核单「餐食」Tab 新增 / 编辑费用时,从餐厅资源下拉中选择餐厅,结构化保存 restaurantId + restaurantName
|
||||
- 运营纯手动录入餐食(不关联餐厅):两字段都不传即可,行为与本次变更前完全一致
|
||||
|
||||
**不适用**:
|
||||
|
||||
- restaurantId 只接受「餐厅资源下拉选项」接口返回的 resourceId,不接受其他类型资源ID
|
||||
- 已提交核单(终态)订单不可再改餐食费用(核单提交守卫为既有逻辑,本次不变)
|
||||
|
||||
**特殊边界**:
|
||||
|
||||
- **快照语义**:restaurantName 是选中那一刻的名称快照。之后餐厅在资源库改名 / 删除,**不影响**已存核单记录的 restaurantName
|
||||
- **与 mealName 完全独立**:选了餐厅也不会改动 / 覆盖 mealName;mealName 仍是必填手动录入字段
|
||||
- 存量数据:历史餐食费用记录两字段均为 null,回显正常、无需迁移
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 接口 | 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|---|
|
||||
| POST / PUT 保存 | 入参 | 无餐厅字段 | +restaurantId(Long,可选,成对)+restaurantName(String,最长 200,可选,成对) |
|
||||
| GET 回显 | 出参 | 无餐厅字段 | +restaurantId(String,可 null)+restaurantName(String,可 null) |
|
||||
| mealName | 语义 | 餐食/餐厅信息只能挤在这一个文本字段里 | 仍为必填手动录入;餐厅信息改由独立字段承载,不再占用 mealName |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 保存餐食费用 | 想记餐厅只能写进 mealName 文本 | 可结构化传 restaurantId + restaurantName,mealName 不被覆盖 |
|
||||
| 回显 | 无法区分「餐食名称」和「餐厅」 | 两类信息独立字段回显,前端可分开展示 |
|
||||
| 入参校验 | 无成对概念 | restaurantId / restaurantName 不成对 → 400 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **破坏性兼容**:无。两个字段均为可选新增;存量请求不传两字段时行为完全不变;存量数据两列为 NULL
|
||||
- **前端同步上线**:不强制同步。前端未接新字段前,保存 / 回显行为与之前一致(回显多两个 null 字段,不读即可);前端接新字段需等后端部署后联调
|
||||
- **后端部署状态**:PR #5600 已合并 dev-v3,**测试服已部署**(2026-08-10 复核确认,见文末验证证据章节)
|
||||
- **回滚方案**:后端回滚 = 下线两字段即可。回滚时前端需同步停止传这两字段;存量数据两列本就为 NULL 或历史快照值,无数据迁移、无回滚 SQL
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. `restaurantId` 出参是 **String**(雪花 ID 序列化),前端不要用 Number 解析,防 JS 精度丢失
|
||||
2. `unitPrice` / `actualAmount` 出参同样是 String(BigDecimal 序列化),金额展示直接渲染字符串即可
|
||||
3. 编辑时「清掉已选餐厅」= 两字段都不传(或都传 null),保持成对空态
|
||||
4. restaurantName 是快照不是实时关联,餐厅后续改名 / 删除不影响历史核单记录显示,属预期
|
||||
5. 餐厅下拉数据源接口 `GET /admin/resource-options/restaurants` 属资源服务(PR #5583),不在本变更范围内
|
||||
6. 本变更只涉及接口契约;后端代码已合并 dev-v3,测试服部署时间以后端通知为准
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/5599
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5600
|
||||
- Commit:https://git.1814.love:8443/wx/HL/commit/9884585a292924c1a0a7a5fec6b7dc8ab87aa59d
|
||||
- 关联依赖:餐厅资源下拉接口 PR #5583(资源服务,已上线)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
|
||||
## 验证证据(2026-08-10 复核回填,wx)
|
||||
|
||||
> 本条 changelog 2026-08-06 推送时后端尚未部署测试服(原 status_note 自述),违反「测试服部署+实测后才通知前端」流程。2026-08-10 复核补齐部署与验证证据如下:
|
||||
|
||||
- 测试服 hl-order-service-v3 运行版本为 2026-08-10 15:10 构建(dev-v3),晚于 PR #5600 合并点(2026-08-06 14:56),本变更代码已在运行实例中。
|
||||
- 测试服 DB `hl_order_service_v3.order_settlement_meal` 已存在 `restaurant_id` / `restaurant_name` 两列(Flyway 已执行)。
|
||||
- `SettlementMealRespVO` 含 `restaurantId` / `restaurantName` 字段(dev-v3 源码核对)。
|
||||
- `GET /v3/admin/order/{orderId}/settlement/meals` 经网关 9443 + 真 admin token 实测返回 `code=200`(空列表订单);测试库现有餐食记录的订单因核单数据权限(581008 无权查看此订单)未能以 SUPER_ADMIN 直接观测到带值行,字段存在性以 DB 列 + VO 源码 + 部署点位三重证据确认。
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5601"
|
||||
title: "接口参数校验3缺陷:pageNo兼容别名拦截+看板不存在订单报错+保险任务status兼容校验"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5615 合并 dev-v3(e6ecb3555a2ad1d56a4a6d93aebb90bdcd2ec9c5)并部署 TEST;网关实测 pageNo=0/-1 返 400 页码最小为1、看板不存在订单返 605904 订单不存在、保险任务 status=XXX 返 400 枚举错误。前端契约参数名不变(page/pageSize、taskStatus),pageNo/status 为兼容别名(等价生效并校验)。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T17:15:00+08:00"
|
||||
---
|
||||
|
||||
# 接口参数校验3缺陷:pageNo兼容别名拦截+看板不存在订单报错+保险任务status兼容校验
|
||||
|
||||
> 服务端已部署 TEST 并网关验证;前端契约参数名不变,无强制改动。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5601](https://git.1814.love:8443/wx/HL/issues/5601)
|
||||
- **PR**: [#5615](https://git.1814.love:8443/wx/HL/pulls/5615)
|
||||
- **Merge commit**: [e6ecb3555](https://git.1814.love:8443/wx/HL/commit/e6ecb3555a2ad1d56a4a6d93aebb90bdcd2ec9c5)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 | 变更 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/admin/fleet/drivers/page` | `DriverController`(fleet) | 查询参数新增兼容别名 `pageNo`(等价 `page`):`pageNo=0/-1` 返 400「页码最小为1」(修复前被静默忽略返回默认第一页);`driverStatus`/`season`/`insuranceType` 非法枚举值返 400(修复前静默空结果) |
|
||||
| `GET` | `/admin/fleet/board/orders` | `BoardController`(fleet) | 查询参数新增兼容别名 `pageNo`(等价 `page`):非法页码返 400 |
|
||||
| `GET` | `/admin/fleet/board/orders/<orderId>` | `BoardController`(fleet) | 语义修复:订单不存在(跨库确认查无)返 **605904 订单不存在**(修复前返回 200 空壳 VO);order-v3 整体不可达时仍降级回显(relatedDetailReady=false) |
|
||||
| `GET` | `/admin/fleet/insurance/tasks` | `FleetInsuranceTaskController`(fleet) | 查询参数新增兼容别名 `status`(等价 `taskStatus`):`status=非法值` 返 400「任务状态必须是 PENDING/PROCESSING/SUCCESS/RESOLVED/IGNORED 之一」(修复前被静默忽略返回全部任务) |
|
||||
| `GET` | `/admin/fleet/vehicles/page` | `VehicleController`(fleet) | `ownerType`/`vehicleStatus` 非法枚举值返 400(同类扫) |
|
||||
| `GET` | `/admin/fleet/reconciliation/pending-compensations` | `ReconController`(fleet) | `status`/`opType` 非法枚举值返 400(同类扫) |
|
||||
|
||||
> 注:`pageNo`、`status` 为兼容别名,**契约主参数名不变**(`page`/`pageSize`、`taskStatus`);所有继承 `PageParam` 的分页接口(司机/车辆/车队/车型/保险任务/保单/H5 Token/对账补偿等 10 个)自动获得 `pageNo` 兼容与校验。
|
||||
|
||||
## 契约影响文件
|
||||
|
||||
- `hl-common/hl-common-core/src/main/java/com/hulalv/common/result/PageParam.java`(新增 `getPageNo/setPageNo` 兼容别名,@Min 校验复用)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/board/vo/BoardOrderPageReqVO.java`(同上;page/pageSize 校验 #5516 已有)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/board/service/BoardOrderService.java`(queryOrderDetail 无本地派单且订单确认不存在 → 605904;不可达不误报;原 605908 孤儿语义保留)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/insurance/vo/FleetInsuranceTaskPageReqVO.java`(新增 `status` 兼容字段 + @Pattern 校验)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/insurance/FleetInsuranceTaskService.java`(pageTasks status→taskStatus 归一合并)
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/driver/vo/DriverPageReqVO.java`、`vehicle/vo/VehiclePageReqVO.java`、`reconciliation/vo/ReconPendingCompPageReqVO.java`(枚举 @Pattern 校验)
|
||||
|
||||
## 根因与修复说明
|
||||
|
||||
- **① pageNo<=0 不拦**:契约参数名是 `page`(`PageParam` 已有 `@Min(1)` 拦截 `page=0`),历史调用方传 `pageNo` 时被 Spring 当作未知参数静默忽略,返回默认第一页且不报错 → 新增 `pageNo` 兼容别名(getter+setter 映射到 `page`),非法值随 `@Min` 统一返 400
|
||||
- **② 看板不存在订单返回空壳**:`queryOrderDetail` 只在本地有派单快照时校验订单存在(孤儿 605908),无派单且订单不存在时走完组装返回 (id=假id、orderNo=null、...) 空壳 → 无本地派单且跨库确认不存在时直接抛 605904;order-v3 不可达(查询结果未知)仍保留降级回显,不误报
|
||||
- **③ 保险任务非法 status 返回全部**:契约参数名是 `taskStatus`(#5515 已有 @Pattern 校验),调用方传 `status` 时被忽略返回全部任务 → 新增 `status` 兼容字段(等价 `taskStatus`),非法值 400,合法值等价筛选
|
||||
- **同类扫**:按 #5515「非法枚举值显式 400 而非静默」口径补齐 drivers/vehicles/recon 枚举字段;board/expiry、matrix、保单 status 等已有校验确认到位
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
1. **无强制改动**:前端契约参数名(`page`/`pageSize`、`taskStatus`)不变,现有调用不受影响
|
||||
2. **兼容增强**:若历史调用方曾传 `pageNo`/`status`,现在与 `page`/`taskStatus` 等价并校验非法值;同一请求同时传 `page` 与 `pageNo`(或 `taskStatus` 与 `status`)时,后者覆盖前者(Spring 绑定顺序),建议只传契约主参数
|
||||
3. **看板详情错误处理**:不存在订单现返回 605904「订单不存在」(修复前 200 空壳),前端可据此提示
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试 285/285 全绿:BoardOrderServiceTest 87(+2:空壳拦截 605904 / 不可达降级不误报)、BoardControllerTest 11(+3:pageNo 非法 400 / 合法绑定 / 605904 透传)、DriverControllerTest 30(+3:pageNo 非法 400 / 合法绑定 / 枚举 400)、FleetInsuranceTaskControllerTest 15(+2:status 非法 400 / 合法透传)、FleetInsuranceTaskServiceTest 142
|
||||
- fleet verify:255 个测试类全绿(含 spotless);唯一失败 ReleaseEMixedBinaryHarnessTest 2 项经基线对比(dev-v3 干净 HEAD 同样失败)确认为基线环境 flaky(进程 runner 管道/超时),与本次改动无关
|
||||
- 网关验证(TEST,经 api.test.1814.love:9443):
|
||||
- `pageNo=0` / `pageNo=-1` → 400「页码最小为1」(drivers/page 与 board/orders 同口径);`pageNo=2` → 200 正常分页(total=28)
|
||||
- `/admin/fleet/board/orders/999999999999999999` → **605904 订单不存在** data=null(修复前 200 空壳)
|
||||
- `status=XXX` → 400 任务状态枚举错误;`status=SUCCESS` → 200 正常筛选(total=49,records[0].taskStatus=SUCCESS)
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5603"
|
||||
title: "需求换版/取消后孤儿派单对账清理(assigned孤儿自动取消释放资源)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5619/#5620 已合并 dev-v3 并部署 TEST(order-v3/fleet/user 三服务),网关验证通过(sys_job trigger code=200,对账 job 取消 115 条孤儿切片,孤儿残留 0)。新增 internal 对账端点 + 批量需求生效校验 Feign 契约,无前端 API 变化。"
|
||||
updated_at: "2026-08-06"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T18:30:00+08:00"
|
||||
---
|
||||
|
||||
# 需求换版/取消后孤儿派单对账清理(assigned孤儿自动取消释放资源)
|
||||
|
||||
> 后端完成:PR #5619/#5620 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5603](https://git.1814.love:8443/wx/HL/issues/5603)
|
||||
- **PR**: [#5619](https://git.1814.love:8443/wx/HL/pulls/5619)、[#5620](https://git.1814.love:8443/wx/HL/pulls/5620)
|
||||
- **Merge commit**: [fd48756f1](https://git.1814.love:8443/wx/HL/commit/fd48756f1)、[c2e5b81e6](https://git.1814.love:8443/wx/HL/commit/c2e5b81e6)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
需求换版/订单取消时,正常链路经 order-v3 耐久 Outbox 通知 fleet 取消旧派单;但当需求/订单被外部物理删除、Outbox 重放失败或链路中断时,fleet_assignment 残留挂接在失效需求上的 active 切片(unassigned/holding/assigned),其中 **assigned 孤儿持续占用车辆/司机资源**。TEST 环境实测 107 条孤儿(assigned 11 + holding 6 + unassigned 90),全部对应「需求/订单已物理删除」。
|
||||
|
||||
## 方案
|
||||
|
||||
以 order-v3 为需求权威新增**需求存在性对账**(调度通路同 #5588:user Quartz sys_job → fleet internal 端点,每小时 30 分执行):
|
||||
|
||||
1. **order-v3** 新增 internal 端点 `POST /v3/internal/order/vehicle-requirements/existence-batch`——批量校验需求是否仍生效(仅返回 is_active=1 且未逻辑删除的需求 ID)
|
||||
2. **fleet** 新增 `RequirementExistenceReconcileJob`——扫描在途派单 distinct requirement_id → 分批 Feign 校验 → 失效需求逐条走 `cancelActiveByRequirement` **正式取消链路**(需求锁 + 槽位/资源锁 + 保险生命周期锁 + 退保意图 + 占用释放事件);Feign 降级 fail-closed 跳过本轮不误伤;全局锁多实例互斥
|
||||
3. **fleet** 新增端点 `POST /internal/fleet/jobs/requirement-existence-reconcile/run`
|
||||
4. **user** 新增 `RequirementExistenceReconcileJobBridge`(sys_job invoke_target:`requirementExistenceReconcileJob.execute()`,已入 Quartz 白名单)
|
||||
|
||||
换版重绑窗口安全:order-v3 已换版、fleet 未消费 Outbox 时取消旧版行只会让重放退化为「取消+新建」,最终无重复无残留。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 服务 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/v3/internal/order/vehicle-requirements/existence-batch` | order-v3 | internal Feign:批量校验需求生效(Req:`RequirementExistenceBatchReqDTO.requirementIds`≤1000;Resp:`activeRequirementIds`) |
|
||||
| `POST` | `/internal/fleet/jobs/requirement-existence-reconcile/run` | fleet | internal:Quartz 触发需求存在性对账(无参,返取消需求数) |
|
||||
|
||||
均为 `/internal/**` 内网 Feign/Quartz 通路(`InternalAuthInterceptor` 校验 X-Internal-Token,网关不暴露),**无前端 API 变化**。shared 新增 DTO:`RequirementExistenceBatchReqDTO/RespDTO`(hl-common-core)。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 需求失效/删除后的孤儿派单(unassigned/holding/assigned)由对账 job 自动软取消(canceled + 退保意图 + 占用释放),历史保留不物理删除
|
||||
- 对账幂等:需求仍生效不动作;已无在途切片计 0;Feign 不可用跳过本轮
|
||||
- **无自动改派/重派**(#5592 口径延续:系统只取消孤儿,不替车务决策)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
无(前端零改动)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **测试**:fleet RequirementExistenceReconcileJobTest 7/7、FleetJobInternalControllerTest 7/7、verify 3158 用例 0 失败(排除 ReleaseE 已知环境项);order-v3 InternalRequirementControllerTest 17/17、RequirementServiceTest 198/198;user QuartzJobExecutorTest 13/13、bridge 4/4;spotless:check 通过
|
||||
- **部署**:hl-order-service-v3 / hl-fleet-service / hl-user-service 双实例 UP(18:00-18:19)
|
||||
- **网关验证**:sys_job trigger(admin API)code=200;对账 job 执行 `在途需求总数=73 孤儿需求=31 取消切片=115`;DB 断言孤儿残留 0;原 11 条 assigned 孤儿全部 canceled(reason=订单取消或用车需求基线已换版);释放资源回 idle(仍 busy 的资源均有其它在途派单占用,非泄漏)
|
||||
- **存量清理**:evidence/5603/orphan-cleanup-manifest.json(hl-data-cleanup/v1,115 条软取消)+ orphan-cleanup-backup.json(115 行快照)
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5610"
|
||||
title: "核单完毕车辆费用写入fleet对账(实际结算取消手动录入)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@d4ac54f5c0b756fc49ce7efa907d81912a7cfd58"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-06"
|
||||
status_note: ""
|
||||
updated_at: "2026-08-07"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-06T19:42:24+08:00"
|
||||
---
|
||||
|
||||
# 🚗 核单完毕车辆费用写入 fleet 对账(实际结算取消手动录入)
|
||||
|
||||
- **日期**: 2026-08-06
|
||||
- **服务**: `hl-order-service-v3`(核单)→ `hl-fleet-service`(车队对账)
|
||||
- **PR**: [#5622](https://git.1814.love:8443/wx/HL/pulls/5622) + 补丁 [#5624](https://git.1814.love:8443/wx/HL/pulls/5624)
|
||||
- **Issue**: [#5610](https://git.1814.love:8443/wx/HL/issues/5610)
|
||||
- **Merge commit**: [fd67c16a7](https://git.1814.love:8443/wx/HL/commit/fd67c16a74aedaac9f4869c089a03d30ab3d5466) + [973953481](https://git.1814.love:8443/wx/HL/commit/97395348127b766cfa9957664dff853546a38e28)
|
||||
- **作者**: wx
|
||||
- **设计**: hl-backend-changelog [06_5597 写模式数据流设计](./06_5597_hl-order-service-v3_hl-fleet-service_核单完毕车辆费用车队对账数据流设计.md)
|
||||
- **影响范围**: 车队对账页(admin)「实际结算」列数据供给与录入方式变更;`PUT /admin/fleet/reconciliation/actual` 删除
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 |
|
||||
|---|---|---|
|
||||
| POST | /internal/fleet/reconciliation/vehicle-fee | 新增(fleet internal,Feign 直连) |
|
||||
| PUT | /admin/fleet/reconciliation/actual | 删除(实际结算改读核单写入) |
|
||||
| GET | /admin/fleet/reconciliation/cars | 修改(actualTotal 数据源变更 + 新增退款列) |
|
||||
|
||||
### 新增 internal 接口 `POST /internal/fleet/reconciliation/vehicle-fee` 的前置说明
|
||||
|
||||
### 1. 变更内容
|
||||
|
||||
写模式:核单完毕 → order 调 fleet 写入接口 → fleet 对账本地读。
|
||||
|
||||
| 接口 | 位置 | 改前 | 改后 |
|
||||
|---|---|---|---|
|
||||
| `POST /internal/fleet/reconciliation/vehicle-fee`(新增) | fleet internal,Feign 直连 | 不存在 | 核单完毕写入该订单车辆实际费用(逐车逐日) |
|
||||
| `PUT /admin/fleet/reconciliation/actual`(删除) | fleet admin | 车管手工录入实际结算 | **已删除**(404),实际结算改读核单写入数据 |
|
||||
| `GET /admin/fleet/reconciliation/cars` | fleet admin | `fleets[].actualTotal` = 手工录入值 | `fleets[].actualTotal` = SUM(vehicle_fee.actual_amount),新增 `fleets[].refundTotal`、`vehicles[].actualAmount/refundAmount`、`grandTotal.refundTotal` |
|
||||
|
||||
### 新增 internal 接口 `POST /internal/fleet/reconciliation/vehicle-fee`
|
||||
|
||||
- 调用方:hl-order-service-v3(核单完毕 finalize 事务内登记 Fleet 命令 Outbox `SETTLEMENT_VEHICLE_FEE_WRITE`,提交后重放调用;不在核单事务内同步 Feign,可重放补偿)。
|
||||
- 幂等:`orderId + assignmentId + serviceDate` 唯一键,重核覆盖写(金额/退款/核单时间全量覆盖)。
|
||||
- 服务日所在对账期已关账(period CLOSED)拒绝(605600),与 prep 冻结口径一致;须超管 reopen 后重核。
|
||||
- `items[]` 为空(无车订单)直接成功不落行。
|
||||
|
||||
**请求体 `SettlementVehicleFeeWriteDTO`(Feign 契约,hl-common)**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderId` / `orderNo` | Long / String | 订单标识(必填) |
|
||||
| `settledAt` | LocalDateTime | 核单完成时间(必填) |
|
||||
| `items[]` | Array | 逐车逐日明细(必填,可空数组) |
|
||||
|
||||
item:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `assignmentId` | Long | 派单 ID(fleet_assignment.assignment_id,必填;order 侧取核单冻结行 sourceDetailId) |
|
||||
| `vehicleId` / `vehiclePlate` / `vehicleModelName` / `seats` / `driverName` | — | 车辆/司机展示快照(order 核单冻结快照) |
|
||||
| `serviceDate` | LocalDate | 服务日期(必填) |
|
||||
| `actualAmount` | BigDecimal | 核单实际车费(必填,≥0,605611) |
|
||||
| `refundAmount` / `refundedAt` | BigDecimal / LocalDateTime | 终止退款金额/完成时间(可空;仅订单首行携带防对账重复计数) |
|
||||
|
||||
**响应**:`Result<Void>`(200 成功;605600 已关账 / 605610 参数非法 / 605611 金额为负)。
|
||||
|
||||
### 删除 `PUT /admin/fleet/reconciliation/actual`
|
||||
|
||||
- 车队对账「实际结算」不再手工录入:`fleet_reconciliation_actual` 表停止写入(历史数据保留,遗留待定另行处理)。
|
||||
- 关账前置 605603「对账期实际金额未录入」改为校验对账车辆费用表该期至少一行。
|
||||
|
||||
## 2. Feign 接口变更
|
||||
|
||||
| 调用方向 | Controller | 方法与路径 | 请求类型 | 响应类型 | 变更类型 |
|
||||
|---|---|---|---|---|---|
|
||||
| order-v3 → fleet | `ReconciliationVehicleFeeInternalController` | `POST /internal/fleet/reconciliation/vehicle-fee` | `SettlementVehicleFeeWriteDTO` | `Result<Void>` | 新增 |
|
||||
| 前端 → fleet | `ReconciliationController` | `PUT /admin/fleet/reconciliation/actual` | `ReconActualSaveReqVO` | `ReconActualSaveRespVO` | **删除** |
|
||||
|
||||
internal 接口为内网 Feign LB 直连,不经 Gateway;调用方由公共 Feign 拦截器注入 `X-Internal-Token`。order-v3 侧 Feign 方法 `FleetAssignmentExpandFeignClient#writeSettlementVehicleFee`(contextId `fleetAssignmentExpand`)。
|
||||
|
||||
## 3. 统一响应包络与错误
|
||||
|
||||
正常和参数错误响应使用 `Result<T>`(code/message/data/traceId/success)。新增/复用错误码:
|
||||
|
||||
| code | 说明 |
|
||||
|---|---|
|
||||
| 605600 | 服务日所在对账期已关账,重核须先重开账 |
|
||||
| 605610 | 核单车辆费用写入参数非法(幂等键缺失) |
|
||||
| 605611 | 核单实际车费不能为负 |
|
||||
|
||||
## 4. 前端/调用方动作(车队对账页)
|
||||
|
||||
- ❌ **删除**「实际结算」手动录入入口(PUT /actual 已 404);实际结算列改展示核单写入值(只读)。
|
||||
- 保留展示:`actualTotal`(车队级)、`diff`(= actualTotal − payableTotal);新增可空展示:`fleets[].refundTotal`、`vehicles[].actualAmount`、`vehicles[].refundAmount`、`grandTotal.refundTotal`。
|
||||
- 金额为字符串(BigDecimal @JsonSerialize ToStringSerializer),前端按既有金额处理。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:`ReconciliationVehicleFeeServiceTest` 9 例(幂等/重核覆盖/已关账拒绝/负数/参数非法/teamNo 取车辆车队归属)、`ReconciliationQueryServiceTest` 11 例(actual 读对账车辆费用表)、`ReconciliationPeriodServiceTest` 17 例(close 前置改造)、`SettlementVehicleFeeReconciliationPublisherTest` 4 例、`SettlementFinalizeTxServiceTest` 5 例(核单完毕登记 outbox)、`OrderFleetCommandOutboxProcessorTest` 26 例(SETTLEMENT_VEHICLE_FEE_WRITE 分发)、`MapperBoundaryArchTest` 25 例(settlement 域经 RequirementService 契约)。
|
||||
- fleet verify:3196 tests 0 失败 + spotless:check 通过(含 Release E 门禁参数)。
|
||||
- 网关验证(TEST):internal 写入 200 ×3(首写/幂等/重核覆盖),DB 复查 `fleet_reconciliation_vehicle_fee` 3 行金额随覆盖更新;`PUT /actual` 404;对账页 `GET /admin/fleet/reconciliation/cars` 200 显示核单写入实际结算。
|
||||
- order 全量 verify 失败集合(8F+95E)与干净 dev-v3 基线完全一致(#5599 H2 schema 未同步 / #5603 架构违规 / MySQL 集成环境),非本变更引入。
|
||||
- 兼容性结论:`fleet_reconciliation_actual` 历史数据保留未迁移(遗留待定);`GET /admin/fleet/reconciliation/cars` 响应新增字段向后兼容;删除 PUT /actual 为破坏性变更,前端须同步移除录入入口。
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5612"
|
||||
title: "change 逐日调整只有起始日通过范围校验——中途日期误报605028(全程行服务期内任意日期可改派)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5616 合并 dev-v3(e26cb0283cdc5773a220f975b440fc0d8cd80164)并部署 TEST(17:24 滚动 DONE);网关实测中途生效日 08-29 change 成功(修复前 605028),原行 canceled、替换行 assigned 全程形态,随后恢复原车成功。前端无需配合。"
|
||||
updated_at: "2026-08-06"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T17:35:00+08:00"
|
||||
---
|
||||
|
||||
# change 逐日调整中途日期误报 605028 修复(全程行服务期内任意日期可改派)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5616](https://git.1814.love:8443/wx/HL/pulls/5616)
|
||||
> **Issue**: [#5612](https://git.1814.love:8443/wx/HL/issues/5612)
|
||||
> **日期**: 2026-08-06
|
||||
> **影响**: 🟢 **缺陷修复**,无契约变更(`POST /admin/fleet/assignments/assignmentId/change` 行为修复)。全程行(#5562 全程槽模型,`service_date` 为 NULL、`start_date~end_date` 覆盖整个服务期)此前仅起始日 `effectiveDate` 能通过范围校验,服务期内中途日期(如服务期 08-28~31 的 08-29/30/31)全部误报 **605028「生效日期不在派单服务日期范围内」**,逐日换车/司机走不通;本次修复后 `effectiveDate` 在 `[start_date, end_date]` 内都算在范围内。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5612(P1)协调台 E2E 实测:assignment `2085033560443408385`(服务期 2026-08-28~08-31,全程行):
|
||||
|
||||
| effectiveDate | 结果(修复前) | 结果(修复后) |
|
||||
|---|---|---|
|
||||
| 2026-08-28(起始日) | ✅ 通过范围校验(走到冲突校验) | ✅ 不变 |
|
||||
| 2026-08-29 | ❌ 605028 | ✅ 成功 |
|
||||
| 2026-08-30 | ❌ 605028 | ✅ 成功(同逻辑) |
|
||||
| 2026-08-31 | ❌ 605028 | ✅ 成功(同逻辑) |
|
||||
|
||||
**根因**:`AssignmentService#changeTargetRows` 用 `serviceDateOf(row)`(全程行回退 `startDate`)做 `>= effectiveDate` 过滤——中途日期大于 `startDate` 时全程行被过滤掉,目标行集合为空 → 抛 605028。逐日切片行(`service_date = start_date = end_date`)不受影响。
|
||||
|
||||
## 变更说明
|
||||
|
||||
`hl-fleet-service` 的 `AssignmentService`(3 处,无 API/DTO/枚举/错误码变化):
|
||||
|
||||
1. **`changeTargetRows`**:全程行改用 `coversServiceDate(row, effectiveDate)` 过滤——`effectiveDate` 在 `[start_date, end_date]` 内都算在范围内;逐日切片行保持原「生效日及之后」过滤
|
||||
2. **`assertContinuousChangeRange`**:单全程行豁免「生效日必须等于目标首行日期」校验(`serviceDateOf` 回退 `startDate` 无法表达中途生效日),仍按 #5595 整槽替换语义保持全程形态
|
||||
3. **`retainedRows`**:排除全程行——中途生效日时前缀日仍属于被整槽取消替换的同一行,不得再作为保留行参与车辆费用快照与占用刷新
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 | 变更 |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/admin/fleet/assignments/<assignmentId>/change` | `AssignmentController`(fleet) | 行为修复:全程行(service_date NULL、start/end 覆盖整个服务期)的 `effectiveDate` 范围校验从「仅起始日」放宽为「服务期内任意日期”——中途日期不再误报 605028,逐日换车/司机可用;参数、返回结构、错误码均不变 |
|
||||
|
||||
> 注:逐日切片行行为不变(生效日及之后切片替换、日期连续性校验)。全程行仍按 #5595 整槽替换语义(旧行取消保留历史、替换行保持全程形态)。
|
||||
|
||||
`POST /admin/fleet/assignments/assignmentId/change`(参数不变):
|
||||
|
||||
- `effectiveDate` 合法范围:全程行 `[start_date, end_date]` 内任意日期;逐日切片行仍为「生效日 ≤ 末切片日」且目标日期连续
|
||||
- 全程行 change 仍按 #5595「整槽替换」语义:旧行取消保留历史、替换行保持全程形态(`service_date` NULL、`start/end` 覆盖整个服务期)、冲突检查/日志范围覆盖整个服务期
|
||||
- 错误码不变:服务期外日期仍 605028;车辆/司机档期冲突仍 605001
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试 6/6(新增 2):`change_fullTripRow_midwayEffectiveDate_succeeds`(中途生效日整槽替换成功)、`change_fullTripRow_midwayEffectiveDate_reachesConflictCheck`(中途日期走到 605001 冲突校验而非 605028);`AssignmentServiceTest` 全量 378/378 通过;`spotless:check` 通过
|
||||
- fleet `-pl hl-fleet-service -am verify`:除 3 个 MySQL Testcontainers 集成测试因 3306 端口被并行会话占用失败(环境并发,错峰后重跑通过)外全绿
|
||||
- 网关验证(TEST,经 api.test.1814.love:9443,VEHICLE_MANAGER):
|
||||
- `change`(effectiveDate=2026-08-29,换车 蒙A-E2E01 + 司机 斯琴)→ **200 成功**(修复前 605028),affectedDays=1、status=assigned
|
||||
- DB 对账:原行 2085033560443408385 → canceled(保留历史);替换行 assigned、`service_date` NULL、start=08-28、end=08-31 全程形态、新车辆/司机
|
||||
- 验证后 change 恢复原车成功,测试数据复原
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
**无**。契约参数、错误码不变,仅中途日期从误报 605028 变为正常可用。
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5613"
|
||||
title: "派单恢复取消500修复:DAILY_V3快照拓扑按最终方案实际覆盖槽位"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5623 合并 dev-v3(c10aea0f0)并部署 TEST(hl-fleet-service + hl-order-service-v3);网关实测派单 2085286010924527618 取消后 restore-cancel 返回 200 assignmentStatus=assigned,DAILY_V3 快照 revision 1 同步成功,order 侧 requirement DONE、planFleetItemIndexes=[1]、planTopologySize=4。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T19:45:00+08:00"
|
||||
---
|
||||
|
||||
# 派单恢复取消500修复:DAILY_V3快照拓扑按最终方案实际覆盖槽位
|
||||
|
||||
> 服务端已部署 TEST 并网关验证;管理后台契约不变,无强制前端改动。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5613](https://git.1814.love:8443/wx/HL/issues/5613)
|
||||
- **PR**: [#5623](https://git.1814.love:8443/wx/HL/pulls/5623)
|
||||
- **Merge commit**: [c10aea0f0](https://git.1814.love:8443/wx/HL/commit/c10aea0f0)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 | 变更 |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/admin/fleet/assignments/<assignmentId>/restore-cancel` | `AssignmentController`(fleet) | **语义修复**:多槽需求只保留部分槽位时恢复取消不再 500(修复前 `IllegalStateException: DAILY_V3 snapshot invalid: daily rows do not cover declared topology`,事务回滚、状态保持 canceled);恢复成功返回 `assignmentStatus=assigned`、车辆/司机占用回 busy。接口出入参不变 |
|
||||
|
||||
> 内部契约(同步放宽,随本单一起部署):`POST /v3/internal/order/vehicle-assignment/callback`(DAILY_V3 配车快照回写)的 `planFleetItemIndexes` 校验由「必须等于需求声明全槽」放宽为「需求全槽的非空子集」。即车务最终方案只保留部分槽位(未保留槽位按「车务最终实派方案未保留该车辆槽位」取消)时,快照拓扑只含实际保留槽位,订单侧正常接受并回写 requirement 快照摘要。
|
||||
|
||||
## 契约影响文件
|
||||
|
||||
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/snapshot/service/DailyVehicleAssignmentSnapshotFactory.java`(快照拓扑按最终方案实际覆盖槽位收窄;每保留槽×每服务日一行、至少一个用车 cell 等 fail-closed 校验不变)
|
||||
- `hl-order-service-v3/src/main/java/com/hulalv/order/requirement/service/DailyVehicleAssignmentSnapshotService.java`(`validateAgainstRequirement`:ASSIGNED 快照 `planFleetItemIndexes` 允许为需求全槽的非空子集;拓扑完整性仍由 `isDailySnapshotValid` 强制)
|
||||
|
||||
## 根因与修复说明
|
||||
|
||||
- **根因**:需求声明多槽(如 suv+mpv × 4 天 = 拓扑 8),但车务最终方案允许只保留部分槽位——未保留槽位的 unassigned 占位按「车务最终实派方案未保留该车辆槽位」取消且 `dispatch_plan_finalized=0`,不参与当前最终代。恢复取消(restore-cancel)在事务 `BEFORE_COMMIT` 触发 DAILY_V3 快照生成时,快照工厂仍按 `requirement.fleet` 全槽拓扑校验当前 final 行(4 行 ≠ 8)→ `IllegalStateException` → 500,恢复事务回滚,取消后无法恢复。
|
||||
- **修复**:① fleet 快照工厂的拓扑以当前最终方案**实际覆盖的槽位**为准(`planFleetItemIndexes`/`planTopologySize`/`dailyAssignments` 一致),未保留槽位不再要求行覆盖;② order 侧回调校验同步接受需求全槽的非空子集。
|
||||
- **影响范围**:仅影响「多槽需求 + 部分槽位最终方案」场景(修复前该场景任何快照生成都会失败,不止恢复取消);全槽最终方案与 NO_VEHICLE_REQUIRED 行为不变。
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
1. **无强制改动**:restore-cancel 接口与出入参不变;修复前该场景直接 500,现在正常返回。
|
||||
2. **展示留意**:多槽需求只派部分槽位时,订单侧快照摘要 `assignmentPlanFleetItemIndexes` 现在会正常回写为保留槽子集(修复前该场景快照从不成功、需求卡 PROCESSING)。若管理后台/订单详情按需求全槽渲染车辆槽位,可结合快照拓扑判断哪些槽位实际派车、哪些未保留。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:fleet `DailyVehicleAssignmentSnapshotFactoryTest` 11/11(+2:部分槽位快照成功 / 保留槽缺日仍 fail-closed);order `DailyVehicleAssignmentSnapshotServiceTest` 14/14(+2:子集 APPLIED / 超集拒绝)
|
||||
- 回归:`AssignmentServiceTest` 376、`DailyVehicleSnapshotGenerationServiceTest` 15、`RequirementServiceTest` 198、`InternalRequirementControllerTest` 14、`VehicleAssignmentSnapshotContractRedTest` 7、`E2eVehicleAssignmentFinalizeServiceTest` 46 全绿
|
||||
- fleet 全量 `mvn -pl hl-fleet-service -am verify`(含 spotless)BUILD SUCCESS;order-v3 verify 的 11 个失败类经与 #5610 worktree 基线报告逐类对比完全一致(Testcontainers/MyBatis 环境问题),非本单引入
|
||||
- 网关验证(TEST,经 api.test.1814.love:9443):派单 2085286010924527618(需求 2 槽 × 08-20~23、最终方案只保留 mpv 槽)取消后 `POST /admin/fleet/assignments/2085286010924527618/restore-cancel` → **200** `assignmentStatus=assigned`,4 行恢复 assigned 且 `canceled_at` 清空;DAILY_V3 快照 outbox revision 1 投递 SUCCESS;order 侧 requirement 回 DONE、`planFleetItemIndexes=[1]`、`planTopologySize=4`、`usedVehicleDayCount=4`,`order_vehicle_assignment` 4 行(mpv 槽 08-20~23,车辆/司机/费用齐全)
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5618"
|
||||
title: "precheck 补黑名单司机校验,与 batchCreate 校验项对齐"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5625 合并 dev-v3(a1f64877ebcc38869a9cbe18d454aa84dc008f25)并部署 TEST;网关实测黑名单司机 precheck conflict=true + driver_blacklisted(605006) 提前提示,有效司机对照无黑名单提示。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T22:45:00+08:00"
|
||||
---
|
||||
|
||||
# precheck 补黑名单司机校验(#5618)
|
||||
|
||||
## 背景
|
||||
|
||||
E2E 实测:黑名单司机(`blacklisted_at` 已写)`POST /admin/fleet/assignments/precheck` 返回 `conflict=false` 不拦;`POST /admin/fleet/assignments/batch` 抛 605006 拦截。precheck 缺司机赛季校验,车务在候选/预览阶段看不到黑名单拦截提示,提交时才被拦,前后体验不一致。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`POST /admin/fleet/assignments/precheck` 响应 `warnings` 新增两种类型(均置 `conflict=true` 阻断,与 batchCreate 锁内 `assertDriverSeasonAssignable` 对齐):
|
||||
|
||||
| warning.type | 触发条件 | 文案 | 对齐错误码 |
|
||||
|---|---|---|---|
|
||||
| `driver_blacklisted` | 司机 `season=blacklist` | 司机已黑名单(605006),提交派单将被拦截 | 605006 |
|
||||
| `driver_season_not_registered` | 司机 `season=archived/pending` 等非 active | 司机非在册赛季(605013),提交派单将被拦截 | 605013 |
|
||||
|
||||
补充说明:
|
||||
|
||||
- precheck 仍为只读接口,不抛异常、不写库、不取锁;黑名单以 warning + `conflict=true` 形式提前阻断提示。
|
||||
- 非黑名单正常路径行为不变(证件到期等仍为不阻断强提醒)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 工单:https://git.1814.love:8443/wx/HL/issues/5618
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5625
|
||||
- 后端:wx
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
- 接口路径/方法/请求体:不变。
|
||||
- 响应 `data.warnings[].type` 新增枚举值:`driver_blacklisted`、`driver_season_not_registered`(前端提示文案随 msg 透传)。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向测试:`AssignmentServiceTest` 382/382(新增 2 例:黑名单阻断提示 605006、非在册阻断提示 605013)。
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3231 项 0 失败 4 skipped(含 MySQL 集成);spotless 通过。
|
||||
- 部署 TEST 成功(hl-fleet-service, dev-v3, a1f64877)。
|
||||
- 网关验证 7/7 PASS:巴特尔(season=blacklist)+ 蒙A-E2E01 → `conflict=true` + `driver_blacklisted`(605006 文案);有效司机对照 → 无黑名单/非在册提示、`conflict=false`。
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- 管理后台派单预览页可展示 `driver_blacklisted` / `driver_season_not_registered` warning 文案(提前告知车务黑名单拦截原因);不处理也不影响既有逻辑(提交时 batchCreate 仍按 605006/605013 拦截兜底)。
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5626"
|
||||
title: "fleet管理端接口角色权限收紧(非车务角色403拦截)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5628 已合并 dev-v3 并部署 TEST(hl-gateway 双实例),网关验证通过(CUSTOMIZER/ROOM_MANAGER 调 fleet 写接口 code=403,VEHICLE_MANAGER 正常放行,只读车型列表排除路径不受影响)。无请求/响应契约变化,仅权限行为收紧。"
|
||||
updated_at: "2026-08-06"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-06T23:30:00+08:00"
|
||||
---
|
||||
|
||||
# fleet 管理端接口角色权限收紧(非车务角色 403 拦截)
|
||||
|
||||
> 后端完成:PR #5628 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5626](https://git.1814.love:8443/wx/HL/issues/5626)
|
||||
- **PR**: [#5628](https://git.1814.love:8443/wx/HL/pulls/5628)
|
||||
- **Merge commit**: [b08f1b953](https://git.1814.love:8443/wx/HL/commit/b08f1b953)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
P0 安全缺陷:定制师(CUSTOMIZER)等非车务角色可调用车务管理员写接口(实测成功删除司机、调用派单接口透到业务校验)。服务层已有 `FleetAdminRoleGuardInterceptor`(/admin/fleet/** 仅 VEHICLE_MANAGER/SUPER_ADMIN),但网关层缺少角色路由校验,两层防线缺一层。
|
||||
|
||||
## 方案
|
||||
|
||||
**网关层补角色门禁,与服务层同口径双保险**:
|
||||
|
||||
1. `JwtAuthFilter` ADMIN 分支对 `/admin/fleet/**` 做角色门禁——仅 **VEHICLE_MANAGER / SUPER_ADMIN** 放行,其余角色(CUSTOMIZER/ROOM_MANAGER/OPERATOR/MATERIAL_ADMIN/CUSTOMER_SERVICE/ADMIN 等)一律返回 `code=403 无权限访问车务管理,请切换到车务角色`,不透到业务校验
|
||||
2. 排除路径与服务层拦截器对齐:`GET /admin/fleet/vehicle-types/list`(订单域只读车型大类列表)任意 admin 角色可读,避免误伤订单用车需求消费
|
||||
3. 防绕过:服务发现前缀(`/hl-fleet-service/...`)与 matrix 参数路径均不能绕过门禁
|
||||
4. 多角色账号必须先切换到车务角色(`/admin/auth/switch-role`)才能调用车务接口
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 服务 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 全部 | `/admin/fleet/**`(排除 `GET /admin/fleet/vehicle-types/list`) | hl-gateway + hl-fleet-service | 角色门禁:仅 VEHICLE_MANAGER/SUPER_ADMIN 可访问,非车务角色 `code=403` |
|
||||
|
||||
**无请求/响应契约变化**——仅权限行为收紧:非车务角色从「可调用并执行」变为「403 无权限」,车务角色行为不变。涉及写接口示例:`DELETE /admin/fleet/drivers/{driverId}`、`POST /admin/fleet/assignments/batch`、`POST /admin/fleet/vehicles`、`POST /admin/fleet/vehicles/{id}` 等全部 `/admin/fleet/**` 管理接口。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 前端管理后台中,**CUSTOMIZER 等非车务角色**访问 fleet 管理功能将收到 `code=403`「无权限访问车务管理,请切换到车务角色」(Result 协议统一错误体,HTTP 200 + code=403)
|
||||
- **VEHICLE_MANAGER / SUPER_ADMIN** 行为完全不变
|
||||
- 多角色账号(如 wx 同时持有 CUSTOMIZER+VEHICLE_MANAGER)需先切换角色为车务管理员再操作车务功能
|
||||
- 服务层 `FleetAdminRoleGuardInterceptor` 维持既有拦截(网关拦截后请求通常到不了服务层,两层均返回同一 403 语义)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
无需改动(Result 协议统一处理 403;车务功能仅车务角色可见)。若前端存在「定制师视角展示车务入口」的情况,建议按 403 隐藏/禁用入口(可选优化,不阻塞后端验收)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **测试**:网关 JwtAuthFilterTest 26/26(新增多角色越权矩阵 14 例:CUSTOMIZER/ROOM_MANAGER/OPERATOR/MATERIAL_ADMIN/CUSTOMER_SERVICE/ADMIN/SALES/DESIGNER/unknown-role → 403;VEHICLE_MANAGER/SUPER_ADMIN → 放行;排除路径放行;服务发现前缀/matrix 参数防绕过)、JwtAuthFilterGrasslandGuideTest 38/38、TraceIdFilterTest 3/3;fleet FleetAdminRoleGuardInterceptorTest 16/16(新增非车务角色参数化矩阵);fleet 全量 verify 3240 例仅 ReleaseE 8033 环境基线失败(干净基线同表现);gateway 全量仅 CrossSchemaMigrationAuditTest 基线失败(#5435 引入,与本次无关);fleet spotless:check 通过
|
||||
- **部署**:hl-gateway 双实例 UP(23:13-23:14 滚动部署)
|
||||
- **网关验证**:CUSTOMIZER 删司机/派单/建车辆 → `code=403 无权限访问车务管理`;ROOM_MANAGER 删司机 → 403;VEHICLE_MANAGER 删司机 → 透到业务层(600205 司机不存在,不误伤);CUSTOMIZER `GET /admin/fleet/vehicle-types/list` → 200 放行
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5629"
|
||||
title: "precheck 对不存在车辆/司机返回明确 conflict 原因"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5637 合并 dev-v3(d621d25471a1c75652e19bd4c471bb61fb0dea95)并部署 TEST;网关实测不存在车/司机 precheck conflict=true 且 conflicts 含 vehicle_not_found(车辆不存在)/driver_not_found(司机不存在),有效资源对照 conflicts 为空不误报。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T12:10:00+08:00"
|
||||
---
|
||||
|
||||
# precheck 对不存在车辆/司机返回明确 conflict 原因(#5629)
|
||||
|
||||
## 背景
|
||||
|
||||
E2E 实测:`POST /admin/fleet/assignments/precheck` 对**不存在的车辆/司机**(如 vehicleId/driverId=999999999999999999)返回 `code=200, conflict=true`,但 **`conflicts=[]` 为空**——原因只埋在 `warnings`(`vehicle_unavailable`/`driver_unavailable`,msg=「车辆不存在或已删除」/「司机不存在或已删除」),车务预览在冲突明细区看不到"车辆不存在/司机不存在"的明确提示。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`POST /admin/fleet/assignments/precheck` 响应 `conflicts` 新增两种类型(资源不存在时置 `conflict=true` 阻断,与既有 `resourceUnavailable` 行为一致,仅把原因从仅 warnings 补进 conflicts):
|
||||
|
||||
| conflict.type | 触发条件 | msg 文案 |
|
||||
|---|---|---|
|
||||
| `vehicle_not_found` | 车辆不存在(`vehicle==null`) | 车辆不存在 |
|
||||
| `driver_not_found` | 司机不存在(`driver==null`) | 司机不存在 |
|
||||
|
||||
补充说明:
|
||||
|
||||
- `conflicts[].msg` 字段此前对重叠冲突(`vehicle`/`driver`)留空由前端结构化自渲染,本次对不存在类冲突填入明确文案;`PrecheckResult.ConflictItem` BO 新增 `msg` 字段并由 converter 透传。
|
||||
- 车辆/司机**存在但不可用**(维保/停用、休假/待激活)仍只进 `warnings`(`vehicle_unavailable`/`driver_unavailable`),不新增 conflicts 项,行为不变。
|
||||
- `warnings` 中 `vehicle_unavailable`/`driver_unavailable`(含"不存在或已删除"文案)保留,向后兼容。
|
||||
- precheck 仍为只读接口,不抛异常、不写库、不取锁。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 工单:https://git.1814.love:8443/wx/HL/issues/5629
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5637
|
||||
- 后端:wx
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
- 接口路径/方法/请求体:不变。
|
||||
- 响应 `data.conflicts[].type` 新增枚举值:`vehicle_not_found`、`driver_not_found`;此两种类型 `conflictAssignmentId/conflictOrderNo/conflictDateRange` 为空,`msg` 为明确原因文案。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向测试:`AssignmentServiceTest#precheck_notFoundResources_returnsExplicitConflictReason`(不存在车/司机 → conflicts 含两 not_found 类型+msg)、`AssignmentConverterTest#toPrecheckRespVO_notFoundConflictMsgPassthrough`(msg 透传)。
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3242 项 0 失败 4 skipped(含 MySQL 集成);spotless:check 通过。
|
||||
- 部署 TEST 成功(hl-fleet-service, dev-v3, d621d254)。
|
||||
- 网关验证 10/10 PASS:不存在车+不存在司机 → conflict=true + vehicle_not_found(车辆不存在)+driver_not_found(司机不存在);有效车+不存在司机 → 仅 driver_not_found;不存在车+存在司机 → 仅 vehicle_not_found;有效车+存在司机对照 → conflicts=[] 不误报。
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- 管理后台派单预览页冲突明细区可直接展示 `vehicle_not_found`/`driver_not_found` 的 `msg` 文案(车辆不存在/司机不存在);不处理也不影响既有逻辑(`conflict=true` 仍会阻断,warnings 仍含 unavailable 提示兜底)。
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5630"
|
||||
title: "房型分类入口字典白名单校验 + 存量 BIG_BED 归一,行程/配房房型恢复中文显示"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5634 合并 dev-v3(4f881af977be50850a78c3b05ac9912758b8b87c)并部署 TEST;网关实测创建/更新房型 roomCategory=BIG_BED 被 310210 拒绝,存量 BIG_BED 三处数据源归一 KING 后订单详情 roomCategoryLabel 显示中文「豪华大床」。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T11:45:00+08:00"
|
||||
---
|
||||
|
||||
# 房型分类字典白名单校验 + 存量 BIG_BED 归一(#5630)
|
||||
|
||||
## 背景
|
||||
|
||||
行程安排/配房的房型显示英文 `BIG_BED` 无中文。根因链(TEST 库实证):
|
||||
|
||||
1. 房型管理入口 `RoomTypeCreate/UpdateRequest.roomCategory` 只校验非空+长度,**无字典 `room_category` 白名单校验** → `BIG_BED` 经房型管理入口写入 `room_type`(2026-02-19);
|
||||
2. 配房时 `room_type.room_category` 快照进 `house_hotel_assignment` / `house_requirement_assignment_snapshot`,需求 `order_hotel_requirement.days` JSON 亦含该 code;
|
||||
3. 房型 2026-05-28 已被人工改回 `KING`,但快照停留 `BIG_BED`;字典 `room_category` 无 `BIG_BED`(有 `KING`=豪华大床)→ `roomCategoryLabel` 字典映射失败 → 原样显示英文。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`POST /admin/hotel/{hotelId}/room-type` 与 `PUT /admin/hotel/room-type/{roomTypeId}` 新增房型分类字典白名单校验:
|
||||
|
||||
- `roomCategory` 必须在字典 `room_category` 已维护 value 集合内(STANDARD/SINGLE/TWIN/QUEEN/KING/DELUXE/SUITE/FAMILY/YURT/SPECIAL/PARENT_CHILD),否则返回业务错误 **310210**(`房型分类无效:{code},仅允许字典 room_category 已维护的值`),不落库。
|
||||
- 字典服务异常/字典数据缺失时降级放行(warn 日志),不阻塞房型管理主流程。
|
||||
- `updateRoomType` 未变更 `roomCategory` 字段时不触发该校验。
|
||||
|
||||
补充说明:
|
||||
|
||||
- 无 DTO 字段结构变化;非法 category 此前会静默写库,现在返回 400 业务错误码 310210,属行为收紧。
|
||||
- 前端无需适配:房型分类表单一向从字典 `room_category` 下拉选择,字典内值全部放行。
|
||||
|
||||
## 存量数据处理(TEST 已执行,生产需按相同 SQL 归一)
|
||||
|
||||
`BIG_BED→KING`(跟随源房型 3002000000000000013 当前口径,字典映射 `KING→豪华大床`):
|
||||
|
||||
```sql
|
||||
-- TEST 已执行并复核清零(4+2+2 需求 4 处)
|
||||
UPDATE house_hotel_assignment SET room_category='KING' WHERE room_category='BIG_BED'; -- 4 条
|
||||
UPDATE house_requirement_assignment_snapshot SET room_category='KING' WHERE room_category='BIG_BED'; -- 2 条
|
||||
-- order_hotel_requirement.days JSON:读出→递归 roomCategory BIG_BED→KING→写回(2 需求 4 处)
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
- 定向测试 `RoomTypeCategoryValidationTest` 8/8:BIG_BED 拒绝(不写库)/KING 放行/字典异常降级/空字典降级/update 不改 category 不触发/null 拒绝。
|
||||
- 合并后 dev-v3(4f881af9)`mvn -pl hl-resource-service -am verify`:2596 项 0 失败(3 个 option Mapper Testcontainers 测试因 Docker Desktop 29 API≥1.40 与老 ryuk 0.7.0 不兼容排除,基线同失败已实证,与本改动无关)。
|
||||
- 网关实测 9/9:创建/更新 BIG_BED → 310210 拒绝;归一前订单详情 label=BIG_BED(英文实证);三处归一复核清零;归一后两订单 label 全显示「豪华大床」。
|
||||
@@ -0,0 +1,284 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5633"
|
||||
title: "订单详情补核单状态字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@18f8e23b9b2c77a81fed3de87fe9a7b94ff3b201"
|
||||
target_release: "v2.1"
|
||||
verified_at: ""
|
||||
status_note: "PR #5635 已合并 dev-v3;2026-08-10 复核确认测试服已部署、reviewStatus/reviewStatusName 经网关实测在出参中(见文末验证证据章节)。管理后台待接入。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 🔧【修改接口·管理后台】订单详情补核单状态字段 (#5633)
|
||||
|
||||
> **PR**:[#5635](https://git.1814.love:8443/wx/HL/pulls/5635) | **服务**:`hl-order-service-v3` | **更新时间**:2026-08-07 | **消费端**:管理后台
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单详情页原有的「核单中」徽标取自 `flowStatus=REVIEWING`(订单流程状态),而核单列表页按 `review_status`(核单任务状态)分 tab,两者是**两个不同字段**:详情文案「核单中」与列表 tab 文案「核算中」字序不一致,导致用户在详情看到「核单中」后到核单列表找不到对应 tab,产生「单丢了」的误判。
|
||||
|
||||
本次在订单详情出参 `main` 段补齐 `reviewStatus` / `reviewStatusName` 两个字段,与核单列表 tab 使用**同一枚举、同一文案**,消除详情与列表的口径歧义。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 订单详情 | `GET` | `/v3/admin/order/{id}` | 修改 | 出参 `data.main` 段新增 `reviewStatus`、`reviewStatusName` 两个字段;其余字段与行为不变 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 订单详情
|
||||
|
||||
- **接口说明**:查询单个订单的详情聚合数据(主单 + 各 Tab 数据段),本次变更只涉及 `data.main` 段。
|
||||
- **使用场景**:管理后台打开订单详情页。
|
||||
- **认证**:需要管理后台登录态和订单查看权限。
|
||||
- **幂等性**:是,只读查询。
|
||||
- **限流**:未声明接口级独立限流规则。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 说明与校验 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | Path | String(Long) | 是 | 订单 ID,必须为正整数;按字符串传递,避免大整数精度损失 |
|
||||
|
||||
无 Query 参数。
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
GET 请求无请求体。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
响应类型:`Result<OrderDetailRespVO>`。本次变更集中在 `data.main`(`OrderMainVO`)段,下表只列出与本次变更相关的主单状态字段簇;`main` 段其余字段及 `tags` / `overview` 等其他数据段均不变。
|
||||
|
||||
### 5.1 统一响应外层
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `code` | Integer | 否 | 成功为 `200`;失败见 §7 |
|
||||
| `message` | String | 否 | 结果说明 |
|
||||
| `data` | Object/null | 失败时为空 | 成功时为订单详情聚合数据 |
|
||||
| `traceId` | String | 是 | 链路追踪 ID |
|
||||
| `success` | Boolean | 否 | `code=200` 时为 `true` |
|
||||
|
||||
### 5.2 `data.main` 状态字段簇
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `id` | String(Long) | 否 | 订单 ID,按字符串返回 |
|
||||
| `orderNo` | String | 否 | 订单号 |
|
||||
| `orderStatus` | String | 否 | 订单主状态枚举值(6 主状态) |
|
||||
| `orderStatusName` | String | 否 | 订单主状态中文名 |
|
||||
| `flowStatus` | String | 是 | 订单流程细状态枚举值;`REVIEWING` 表示流程处于核单环节,**不等于**核单任务状态 |
|
||||
| `flowStatusName` | String | 是 | 订单流程细状态中文名(如「核单中」) |
|
||||
| `reviewStatus` | String/null | 是 | **本次新增**。核单任务状态枚举值,对应 `order_main.review_status`,与核单列表 tab 同枚举;取值见 §6 |
|
||||
| `reviewStatusName` | String/null | 是 | **本次新增**。核单任务状态中文名,与核单列表 tab 文案一字不差;取值见 §6 |
|
||||
| `flowStep` | Integer/null | 是 | 线性 6 步当前步序号(1-6=进行中各步;null=已取消终态) |
|
||||
| `flowStepTotal` | Integer | 否 | 线性步骤总数,固定 `6` |
|
||||
| `flowStepName` | String/null | 是 | 当前步中文名 |
|
||||
| `flowStepCode` | String/null | 是 | 当前步编码 |
|
||||
| `flowStepStatus` | String/null | 是 | 当前步状态 |
|
||||
|
||||
> `main` 段其余字段(金额、日期、来源、合同/保险/退款状态徽标等)本次无变化,沿用既有契约。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `main.reviewStatus` / `main.reviewStatusName`
|
||||
|
||||
**所属字段**:`data.main.reviewStatus`、`data.main.reviewStatusName` | **类型**:String/null | **可空**:是
|
||||
|
||||
| `reviewStatus` 值 | `reviewStatusName` 值 | 说明 |
|
||||
|---|---|---|
|
||||
| `NONE` | `待核算` | 尚未生成核单任务;对齐核单列表归一逻辑,与 `PENDING` **同显「待核算」**,不单独显示「未核单」 |
|
||||
| `PENDING` | `待核算` | 核单任务待处理 |
|
||||
| `IN_PROGRESS` | `核算中` | 核单任务进行中 |
|
||||
| `COMPLETED` | `已完成` | 核单任务已完成 |
|
||||
| `null` | `null` | 订单尚无 `review_status` 值(如未进入核单流程的订单);此时两字段均为 JSON 空值,不是字符串 `"null"` |
|
||||
|
||||
**容错规则**:
|
||||
|
||||
- `reviewStatus` 为 `null` 时,`reviewStatusName` 也为 `null`,不报错。
|
||||
- `reviewStatus` 为无法识别的值时,`reviewStatusName` **原样返回该值**,接口不抛错。
|
||||
|
||||
**与 `flowStatus` 的关系**:`flowStatus=REVIEWING` 表示订单流程走到核单环节(中文名「核单中」),是流程维度;`reviewStatus` 是核单任务自身的状态机维度,与核单列表 tab 同源。两者并存、语义不同,前端展示核单任务状态时以 `reviewStatus` / `reviewStatusName` 为准。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| `200` | 查询成功 | 正常返回订单详情 |
|
||||
| `400` | 请求参数错误 | `id` 不是正整数 |
|
||||
| `401` | 未登录或登录态失效 | 缺少或携带无效的管理后台访问令牌 |
|
||||
| `403` | 无访问权限 | 登录态、角色或权限不允许访问 |
|
||||
| `581007` | 订单不存在 | `id` 对应订单不存在(含已软删除) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:核单任务进行中
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9223372036854775000
|
||||
Authorization: Bearer <管理后台访问令牌>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**(只截取 `data.main` 状态字段簇,其余字段省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"main": {
|
||||
"id": "9223372036854775000",
|
||||
"orderNo": "HL20260801000001",
|
||||
"orderStatus": "CONFIRMED",
|
||||
"orderStatusName": "已确认",
|
||||
"flowStatus": "REVIEWING",
|
||||
"flowStatusName": "核单中",
|
||||
"reviewStatus": "IN_PROGRESS",
|
||||
"reviewStatusName": "核算中",
|
||||
"flowStep": 5,
|
||||
"flowStepTotal": 6,
|
||||
"flowStepName": "核单结算",
|
||||
"flowStepCode": "SETTLEMENT",
|
||||
"flowStepStatus": "IN_PROGRESS"
|
||||
}
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况:尚无核单任务状态(reviewStatus 为 null)
|
||||
|
||||
**场景说明**:订单尚未进入核单流程,`order_main.review_status` 为 NULL,两字段均返回 JSON 空值;前端按「无核单任务状态」处理,不要按字符串 `"null"` 判断。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/900000000002
|
||||
Authorization: Bearer <管理后台访问令牌>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**(只截取 `data.main` 状态字段簇):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"main": {
|
||||
"id": "900000000002",
|
||||
"orderNo": "HL20260802000002",
|
||||
"orderStatus": "PENDING",
|
||||
"orderStatusName": "待确认",
|
||||
"flowStatus": "AWAITING_CONFIRM",
|
||||
"flowStatusName": "待确认",
|
||||
"reviewStatus": null,
|
||||
"reviewStatusName": null,
|
||||
"flowStep": 2,
|
||||
"flowStepTotal": 6,
|
||||
"flowStepName": "待确认",
|
||||
"flowStepCode": "CONFIRM",
|
||||
"flowStepStatus": "IN_PROGRESS"
|
||||
}
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
另:`reviewStatus=NONE` 时 `reviewStatusName` 返回「待核算」(与 `PENDING` 同文案,对齐核单列表归一逻辑),见 §6。
|
||||
|
||||
### 8.3 业务失败:订单不存在
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/999999999999
|
||||
Authorization: Bearer <管理后台访问令牌>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"data": null,
|
||||
"traceId": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用**:所有 v3 订单的详情查询;需要展示「核单任务状态」(与核单列表 tab 口径一致)的场景。
|
||||
- **不适用**:判断订单流程进度仍用 `flowStatus` / `flowStep` 等流程字段,`reviewStatus` 只表达核单任务自身状态,不能替代流程状态。
|
||||
- **特殊边界**:
|
||||
- 未进入核单流程的订单 `reviewStatus` 为 `null`,属正常数据,不是异常。
|
||||
- `NONE` 与 `PENDING` 展示文案同为「待核算」,与核单列表 tab 完全一致;需要区分「未生成任务」与「任务待处理」时用 `reviewStatus` 枚举值判断,不要用中文名判断。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比(`data.main` 段)
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `reviewStatus` | 不存在 | 新增,String/null,核单任务状态枚举值 |
|
||||
| `reviewStatusName` | 不存在 | 新增,String/null,核单任务状态中文名(与核单列表 tab 同文案) |
|
||||
| 其余字段 | 不变 | 不变 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 详情页核单状态来源 | 只有 `flowStatus=REVIEWING`(文案「核单中」),与核单列表 tab 口径不一致 | 新增 `reviewStatus` / `reviewStatusName`,与核单列表 tab 同枚举、同文案 |
|
||||
| 接口签名 | `Result<OrderDetailRespVO>` | 不变,仅出参多 2 个字段 |
|
||||
| 入参 / 错误码 | 不变 | 不变 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **兼容性**:纯新增出参字段,向后兼容;未接入新字段的前端版本不受影响,可继续按原字段渲染。
|
||||
- **破坏兼容**:无。
|
||||
- **前端同步上线**:不要求同步上线;详情页核单徽标切换到 `reviewStatusName` 可在后端发布后任意时间进行。
|
||||
- **回滚方案**:还原 PR #5635 对应提交即可,`main` 段不再返回这两个字段;前端读取不到时按字段缺失处理(`undefined`),不会解析报错。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `reviewStatusName` 文案与核单列表 tab **一字不差**:「待核算 / 核算中 / 已完成」;不要在前端自行映射文案,直接使用该字段,避免再次出现字序不一致。
|
||||
- `NONE` 与 `PENDING` 同显「待核算」是**有意对齐**核单列表的归一逻辑,不是 bug。
|
||||
- `reviewStatus` 为 `null` 时 `reviewStatusName` 必为 `null`;`reviewStatus` 为非法值时 `reviewStatusName` 原样返回该值,接口不抛错。
|
||||
- `main.id` 等 Long 型 ID 按字符串序列化,前端按字符串处理,避免大整数精度丢失。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[#5633](https://git.1814.love:8443/wx/HL/issues/5633)
|
||||
- **PR**:[#5635](https://git.1814.love:8443/wx/HL/pulls/5635)
|
||||
- **Commit**:[c378061b08](https://git.1814.love:8443/wx/HL/commit/c378061b08e85cc7794b23c2a49691ad4f8eb01e)
|
||||
- **后端负责人**:腰苏图
|
||||
|
||||
## 验证证据(2026-08-10 复核回填,wx)
|
||||
|
||||
> 本条 changelog 2026-08-07 推送时 backend_status=implemented(非法枚举且未部署),违反「测试服部署+实测后才通知前端」流程。2026-08-10 复核补齐部署与验证证据如下:
|
||||
|
||||
- 测试服 hl-order-service-v3 运行版本为 2026-08-10 15:10 构建(dev-v3),晚于 PR #5635 合并点(2026-08-07 14:25),本变更代码已在运行实例中。
|
||||
- `GET /v3/admin/order/{orderId}` 经网关 9443 + 真 admin token 实测:`data.main` 已包含 `reviewStatus` / `reviewStatusName` 两个键(未进入核单流程的订单两键返回 JSON null,符合本文 8.2 边界示例)。
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5636"
|
||||
title: "对账vehicle-fee对齐核单重设计(补driverId必填+移除退款字段)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@d897c3d4fc31932dd351e99f1911144314e2cc2f"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5641 已合并 dev-v3 并部署 TEST(hl-fleet-service + hl-order-service-v3 双实例)。网关验证通过(对账车辆费用查询接口正常返回,响应已不含 refund 字段)。internal Feign 契约变化:写入接口 Item 补 driverId 必填、移除 refundAmount/refundedAt;对账查询 VO 移除 refund 聚合。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T14:00:00+08:00"
|
||||
---
|
||||
|
||||
# 对账 vehicle-fee 对齐核单重设计(补 driverId 必填 + 移除退款字段)
|
||||
|
||||
> 后端完成:PR #5641 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5636](https://git.1814.love:8443/wx/HL/issues/5636)
|
||||
- **PR**: [#5641](https://git.1814.love:8443/wx/HL/pulls/5641)
|
||||
- **Merge commit**: [d0e976f9e](https://git.1814.love:8443/wx/HL/commit/d0e976f9e)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
#5610 已实现 `POST /internal/fleet/reconciliation/vehicle-fee`(核单完毕写入对账车辆实际费用),但参数与核单重设计(hl-backend-changelog 06_5597)不一致:
|
||||
|
||||
| 项 | 设计要求 | 修复前实现 |
|
||||
|----|---------|-----------|
|
||||
| driverId | **必填**(对账按司机关联) | **缺失**(只有 driverName) |
|
||||
| refundAmount/refundedAt | **不含**(核单无退款) | **多了**(核单无退款数据,字段恒空) |
|
||||
| vehicleId | fleet 侧按派单(司机+日期)关联 | order 透传 |
|
||||
|
||||
## 口径(wx 定)
|
||||
|
||||
- **driverId 由核单传**(核单按司机算钱天然携带)
|
||||
- **vehicleId 由 fleet 侧按派单(司机+日期)从 `fleet_assignment` 关联**(核单可不传)
|
||||
- **无 refundAmount/refundedAt**(核单无退款数据;退款归支付/退款模块)
|
||||
|
||||
## 方案
|
||||
|
||||
1. **写入契约** `SettlementVehicleFeeWriteDTO.Item`:补 `driverId`(`@NotNull` 必填);移除 `refundAmount`/`refundedAt`
|
||||
2. **表 `fleet_reconciliation_vehicle_fee`**:migration `V20260807_001` 加 `driver_id` 列 + `idx_driver_service_date` 索引,删 `refund_amount`/`refunded_at` 列(H2 兼容:单动作 ALTER / CREATE INDEX,不带 AFTER)
|
||||
3. **写入 Service**:`driverId` 必填校验(缺失 → 605610);`vehicleId` item 未传时按派单反查 `fleet_assignment.vehicle_id` 关联填入;`driverName` 快照缺失时按派单反查补全
|
||||
4. **对账查询**:移除 refund 聚合(`ReconCarsVehicleVO.refundAmount` / `ReconCarsFleetVO.refundTotal` / `ReconCarsGrandTotalVO.refundTotal` + `ReconciliationQueryService` 三处聚合)
|
||||
5. **order 侧 publisher**:`buildItems` 透传核单冻结行 `line.driverId()`;移除「终止退款挂首行」逻辑
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 服务 | 说明 |
|
||||
|---|---|---|---|
|
||||
| POST | `/internal/fleet/reconciliation/vehicle-fee` | hl-fleet-service | **internal Feign 契约**:Item 补 `driverId`(必填),移除 `refundAmount`/`refundedAt` |
|
||||
| GET | `/admin/fleet/reconciliation/cars` | hl-fleet-service | 响应 VO 移除 `refundTotal`(车队/总计)与 `refundAmount`(车辆明细)字段 |
|
||||
|
||||
**契约变化**:
|
||||
- internal 写入接口(order-v3 → fleet,Feign 直连不经网关):请求 Item **新增必填 `driverId`**、**删除 `refundAmount`/`refundedAt`**。order 侧 publisher 同 PR 同步改造,双侧一并部署,无跨版本兼容窗口。
|
||||
- 对账查询接口(管理后台):响应**删除 refund 相关字段**(`grandTotal.refundTotal`、`fleets[].refundTotal`、`fleets[].vehicles[].refundAmount`)。核单无退款数据,这些字段恒为 0/null,删除不影响实际口径。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 核单完毕写入对账车辆费用时,每条明细必须携带 `driverId`(核单按司机算钱天然有);缺失返回 `code=605610`
|
||||
- 对账「实际结算」列数据源不变(仍读 `SUM(actual_amount)`);退款相关展示字段从响应移除
|
||||
- 既有 `#5610` 期间写入的历史行若无 `driver_id`,随重核覆盖写自然补全
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- **管理后台对账页**:若引用了对账车辆费用响应的 `refundTotal`/`refundAmount` 字段,需移除相关展示(核单无退款,这些字段恒空)。「实际结算」列数据源不变。
|
||||
- internal 写入接口由 order-v3 内部 Feign 调用,双侧同 PR 同步改造并一并部署,前端无感知。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **测试**:fleet reconciliation 38/38(VehicleFeeService 10 含新增 driverId 必填/vehicleId 派单关联用例 + Query 11 + Period 17);order publisher 4/4(含 driverId 透传用例)+ outbox 26/26 + InternalRequirement 17/17;fleet spotless:check 通过
|
||||
- **fleet verify**:3243 例,仅 ReleaseE 8033(需 MySQL 8.0.33 环境)+ MixedBinaryHarness 偶发——基线已知失败,与本案无关
|
||||
- **order-v3 verify**:LayerEnforcement/RedLineArch 转绿(顺带修复 #5603 引入的 Controller→DO ArchUnit 违规);剩余 7 失败 + 95 errors 全为 Testcontainers/MySQL schema 基线失败(`order_settlement_summary` 缺表等),与 vehicle-fee driverId 无关
|
||||
- **部署**:hl-fleet-service + hl-order-service-v3 双实例滚动部署 UP(13:53-13:55)
|
||||
- **网关验证**:VEHICLE_MANAGER 调 `GET /admin/fleet/reconciliation/cars` → 200,`grandTotal` 仅含 `actual/diff/estimated/payable/totalDays/totalOrderCount`,已无 `refundTotal`;CUSTOMIZER 调 → 403(#5626 门禁生效)
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5640"
|
||||
title: "司机详情页直接投保全年保险 POST /admin/fleet/drivers/{id}/insure"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "845d8827"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5645/#5651/#5652 合并 dev-v3(6f7a9c19b70444395558f137e4cde6fb9524ecff)并部署 TEST;网关实证 600211 守卫+直接投保出单(INSURED)+保单可查+档案年险回填+重复投保 540032 拦截。TEST 用 4 天档降档验证(TEST 无 365 天档计划,环境限制);生产上线需配 annual-direct-plan-id 指向生产年险产品(1-无限天)。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T16:20:00+08:00"
|
||||
---
|
||||
|
||||
# 司机详情页直接投保全年保险(#5640)
|
||||
|
||||
## 背景
|
||||
|
||||
司机详情页此前只能查看保单、不能直接投保;现有投保只有按行程(PER_TRIP 派单自动投)与手动选计划/起止的 purchase 接口。本单新增「一键直接投保全年保险」端点(wx 口径:直接投保=给司机投全年保险 annual policy,保额/期限按计划默认不可选,被保人=司机档案证件)。
|
||||
|
||||
## 新增接口
|
||||
|
||||
### `POST /admin/fleet/drivers/{driverId}/insure` —— 司机直接投保全年保险
|
||||
|
||||
**入参**:无(仅路径 driverId)。
|
||||
|
||||
**行为**:
|
||||
- 默认计划解析:nacos `fleet.insurance.annual-direct-plan-id`(缺省回落 `annual-gap-plan-id`)→ DRIVER/BOTH 可用计划唯一候选自动选;**多候选/无候选抛 600211**(禁止按列表顺序猜测保额)。
|
||||
- 保障期:**T+1 起保**(保游硬性约束:即时生效保险起保日必须大于当前时间,540030)× `fleet.insurance.annual-direct-coverage-days` 天(**默认 365=全年**;TEST 环境无 365 天档计划,配 4 天降档验证)。
|
||||
- 被保人:司机档案证件(姓名/身份证/手机/性别自动组装)。
|
||||
- 受理即绑档案年险:insurance_type=annual + 保单号/保费/起止回填 + annualSource=baoyou;保游回调 INSURED 补真实保单号,FAILED/CANCELLED 自动解绑回退(沿用 #3760/#5558 Saga)。
|
||||
- 幂等:同日同人幂等键 + 上游 540032(保障期重叠)双重拦截重复投保。
|
||||
|
||||
**响应**:`DriverInsurancePolicyDTO`(与 purchase/policies 同结构,证件号脱敏)。
|
||||
|
||||
**错误码**:600205 司机不存在 / **600211 未配置默认全年保险计划且候选不唯一(新增)** / 100001 司机身份证非18位 / 540031 计划未标注司机可用 / 540032 保障期已有生效保单 / 540005 计划不存在 / 540007 无匹配费率 / 540034 产品已下架不可售 / 600206 出单成功但年险绑定失败 / 605601 保险服务不可用。
|
||||
|
||||
## 前端交接(司机详情页【投保】按钮)
|
||||
|
||||
- 按钮调 `POST /admin/fleet/drivers/{driverId}/insure`(无 body)。
|
||||
- 成功:返回保单 DTO,提示投保成功并刷新保单列表(`GET /{driverId}/insurance/policies`)与司机详情(insurance_type 变 annual、年险四件套已回填)。
|
||||
- 600211:提示「未配置默认全年保险计划,请联系运营」;540032:提示「该保障期已有生效保单」;600206:提示「出单成功但年险绑定失败,勿重复投保,联系管理员」。
|
||||
- 建议按钮在司机已有生效年险时禁用或二次确认(前端可据详情接口 insurance_type/年险起止判断)。
|
||||
|
||||
## 配置项(nacos `hl-fleet-service-${env}.yml` → `fleet.insurance`)
|
||||
|
||||
| 键 | 含义 | TEST | 生产 |
|
||||
|---|---|---|---|
|
||||
| `annual-direct-plan-id` | 直接投保默认计划 | 2067501078319951874(畅心游20万) | **待配:指向生产年险产品计划(1-无限天)** |
|
||||
| `annual-direct-coverage-days` | 保障天数,默认 365 | 4(TEST 无 365 天档,降档验证) | 不配(默认 365) |
|
||||
|
||||
**遗留(不阻塞本单)**:生产 `annual-direct-plan-id` 需运营在保游维护/确认年险产品计划后配置(wx 确认生产有 1-无限天司机年险产品)。
|
||||
|
||||
## 验证
|
||||
|
||||
- DriverInsuranceServiceTest **30/30**:配置计划一年期+受理即绑 / 单候选自动选 / 多候选 600211 / 无候选 600211 / 降档天数投保。
|
||||
- 6f7a9c19 全量 verify **3253 项 0F/0E**/4 skipped。
|
||||
- 网关实证:未配置→600211;配置后投保出单 INSURED(8.8~8.11 4 天档);保单列表可查;档案年险回填;insurance_order 落账 DRIVER+司机;重复投保 540032 拦截。
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5642"
|
||||
title: "出行人所属地后端解析返回 nativePlace(身份证前6位→地区名)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "7e7085b6"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "2026-08-07 协调台浏览器实测:补身份证后派单页所属地正常显示「内蒙古呼伦贝尔市」(前端 implemented ref=7e7085b6 + 后端 #5656 链路 + 数据有身份证,端到端通),标 verified。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T16:15:00+08:00"
|
||||
---
|
||||
|
||||
# 出行人所属地后端解析返回 nativePlace(#5642)
|
||||
|
||||
## 背景
|
||||
|
||||
出行人"所属地"此前由前端从身份证号前 6 位自行解析,显示乱码。改为后端解析返回。口径经协调台确认:按**权威 GB/T 2260**(现行版,省+地级粒度)解析——150784=内蒙古呼伦贝尔市(工单示例"赤峰"系笔误,1504xx 才是赤峰);不新建 district 表,沿用 `IdCardParser` 静态码表模式(与 `idProvinceName` 同源)。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`GET /v3/admin/order/{id}/traveler/list` 等 admin 出行人出参(`TravelerVO`,含 traveler list / 协同单 / 结算退回详情等所有 `toAdminVO` 路径)**新增 `nativePlace` 字段**:
|
||||
|
||||
| 场景 | nativePlace |
|
||||
|---|---|
|
||||
| 大陆身份证,命中地级码 | 省短名+地市名,如 `150784…`→`内蒙古呼伦贝尔市`、`320101…`→`江苏南京市` |
|
||||
| 直辖市(1101/1201/3101/5001/5002) | 市名,如 `110101…`→`北京市` |
|
||||
| 省直辖县级(4190/4290/4690/6590) | 按 6 位精确映射,如 `469001…`→`海南五指山市`、`659001…`→`新疆石河子市` |
|
||||
| 非身份证(PASSPORT 等) | `null` |
|
||||
| 未命中(含已撤销历史码如 3712 莱芜) | `null`(不报错) |
|
||||
|
||||
补充说明:
|
||||
|
||||
- 解析基于解密后的明文 idNo(converter 层既有行为),仅 18 位结构 + 合法生日段才解析;数据源为现行 GB/T 2260 地级行政区码表,不含已撤销历史代码(历史码身份证所属地返回 null)。
|
||||
- 既有字段不受影响:`idCardMasked` 仍脱敏、`idProvinceCode/idProvinceName` 逻辑不变。
|
||||
- internal VO(明文 Feign 契约)未加该字段,admin 出参仅新增只读字段,无破坏性。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 工单:https://git.1814.love:8443/wx/HL/issues/5642
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5650
|
||||
- 后端:wx
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
- 接口路径/方法/请求体:不变。
|
||||
- 响应 `data[].nativePlace` 新增字段(string | null):所属地(省+地级行政区名);非身份证或未命中为 `null`。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向测试 274 全过:`IdCardParserTest#extractNativePlace_*`(地级码/省直辖县级/非法与未命中 3 例)、`TravelerConverterTest#toAdminVO_*nativePlace*`(2 例),覆盖 TravelerService/TravelerAdminController/OrderDetailService。
|
||||
- 全量 `mvn -pl hl-order-service-v3 -am verify`:7610 测试,7F/95E 全部位于 30 个基线已坏的 Mapper/集成 IT 类(Feign loadbalancer Bean 缺失、Flyway IT schema 校验等环境问题),git stash 基线复跑同类同败,无一涉及 traveler/idcard 影响面。
|
||||
- 部署 TEST 成功(hl-order-service-v3 双实例 8086/8186 UP)。
|
||||
- 网关验证 10/10 PASS(自建 HLTEST 订单 2085637067265449985):150784→内蒙古呼伦贝尔市、469001→海南五指山市、110101→北京市、PASSPORT→null、371201→null 不报错;脱敏与省级字段回归通过。
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- 出行人"所属地"列**直接展示后端返回的 `nativePlace`**,**删除前端自行解析身份证前 6 位的逻辑**(当前乱码来源)。
|
||||
- `nativePlace` 为 `null` 时展示空(如 "-"),不要 fallback 到前端解析。
|
||||
- 适用接口:所有返回 `TravelerVO` 的 admin 接口(出行人列表等)。
|
||||
|
||||
> 2026-08-07 协调台更正:派单详情接口 travelers 缺 nativePlace 系**后端覆盖不全**(fleet 派单详情 BoardOrderDetailVO 链路未加),非前端问题。另立后端工单补 fleet-detail-context→BoardOrderDetailVO 链路。前端 traveler/list 接入无误。
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5643"
|
||||
title: "派车价按车型+日期回显价格日历价(修复空收费日期误免)+ 调价/行程链接文案优化"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5647 已合并 dev-v3 并部署 TEST(15:46 滚动 DONE)。网关验证 6 项全 PASS:候选传空收费日期数组回显日历价 800×4=3200(修复前 4 天 FREE ¥0.00);按日历价派车成功且快照 source=AUTO;调价与行程链接文案按新口径展示。前端无需配合。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T15:50:00+08:00"
|
||||
---
|
||||
|
||||
# 派车价按车型+日期回显价格日历价(修复空收费日期误免)+ 调价/行程链接文案优化
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5647](https://git.1814.love:8443/wx/HL/pulls/5647)
|
||||
> **Issue**: [#5643](https://git.1814.love:8443/wx/HL/issues/5643)
|
||||
> **日期**: 2026-08-07
|
||||
> **影响**: 🟢 **缺陷修复 + 文案优化**,无契约结构变更(字段不变,仅返回值/报错文案修正)。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5643(P1)wx 实测:价格日历有数据(SUV普拉多¥1000/天、GL8¥900、帕萨特¥600、考斯特¥1500,2026-08 各日期),但派车订单 26-6457(蒙A-H7777 汉兰达 SUV 8/9-8/12)派车价 4 天都 ¥0.00。
|
||||
|
||||
**根因**:`POST /admin/fleet/assignments/candidates` 把请求里的 `chargeableServiceDates=[]`(空数组)按字面空集处理 → 全程误判"免费" → 逐日 `assignmentPrice=¥0.00 source=FREE`、自动总价 ¥0。需求未配置收费日期时前端回传空数组(而非 null),触发该路径。
|
||||
|
||||
**网关复现**:同一需求传 `[]` → 4 天 FREE ¥0.00;不传 → 正常 800×4=3200(汉兰达日历价 ¥800/天)。
|
||||
|
||||
## 修复内容(内部,无接口/字段结构变化)
|
||||
|
||||
1. **候选取价空数组归一化**:`AssignmentCandidateService` 对 `chargeableServiceDates` 做空集合→未指定(=全部服务日收费)归一化,候选车辆逐日回显日历价(`source=CALENDAR`)、自动总价正确求和。创建/改派边界的"全免需二次确认+原因"守卫语义不变(`resolveChargeableServiceDates` 未动);已落库全免组(收费日为空的合法态)的改派/保留行取价不受影响。
|
||||
2. **调价报错文案可行动化**(创建与逐日方案两处):价=日历价的日期填了调价原因时,报错由「日历价日期不能填写车辆调价原因」改为「派车价与日历价一致的日期无需填写调价原因,请清空未调价日期的调价原因后重试」。校验语义与审计不变量不变(调价日必须有原因、未调价日不留原因)。
|
||||
3. **行程链接文案分级**:派单通知模板渲染 `itinerary.url 预览阶段(无可定位派车组)显示「行程链接在派单成功后自动生成」;派车组可定位但无效/定位歧义仍显示「行程链接暂不可用,请联系车务确认」(失败关闭保留);HOLD 未 finalize 组签链仍按 #5461 设计失败关闭。模板渲染的收费/免费日期摘要同步空数组归一化。
|
||||
|
||||
## 变更接口
|
||||
|
||||
- `POST /admin/fleet/assignments/candidates`:`chargeableServiceDates` 传 `[]` 与不传行为一致(全部服务日按价格日历取价);候选车辆 `dailyVehicleFees[].calendarPrice/assignmentPrice`、`autoVehicleFeeTotal` 正常回显(非 ¥0)。
|
||||
- `POST /admin/fleet/assignments`、`POST /admin/fleet/assignments/batch`:未调价日期携带调价原因时的报错文案变更(错误码不变,100001/参数非法族)。
|
||||
- `POST /admin/fleet/message-templates/:templateId/render`:`itinerary.url` 在预览阶段的占位文案变更(见上)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3252 Tests,Failures=0,Errors=0(dev-v3 @ 76bc874d);新增候选空收费日期回归用例;spotless 通过。
|
||||
- 部署 TEST:15:46 滚动 DONE。
|
||||
- 网关(订单 26-6457,蒙A-H7777 汉兰达 8/9-8/12,日历价 ¥800/天)6 项全 PASS:①候选传 `[]` 回显 800×4=3200 ②默认入参同样 3200 ③预览文案=行程链接在派单成功后自动生成 ④价=日历价+原因报新文案 ⑤HOLD 派车成功且 DB 快照 calendar=800 全收费、total=3200.00、source=AUTO ⑥HOLD 未 finalize 组渲染按设计失败关闭。
|
||||
|
||||
## 前端配合
|
||||
|
||||
无需配合。派车弹窗/候选列表原本读取的字段不变,现在能拿到正确的日历价。
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5644"
|
||||
title: "司机手机号支持车务管理员直接修改(放开 phone 可改)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "hl-admin@0e8340a1ca012de0c7e965906596ccc3c37d31cd"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-07"
|
||||
status_note: "后端完成:PR #5648 已合并 dev-v3(86e319ac1)并部署 TEST(hl-fleet-service 双实例)。手机号编辑放开:明文入参正常更新(格式 600212 / 重复 600203),脱敏回传保持原值;idCard 仍不可改。网关验证 12/12 通过:改 phone 生效、重复 600203、格式 600212、脱敏保持、idCard 不变、临时司机已清理。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T14:50:00+08:00"
|
||||
---
|
||||
|
||||
# 司机手机号支持车务管理员直接修改(放开 phone 可改)
|
||||
|
||||
> 后端完成:PR #5648 已合并 dev-v3 并部署 TEST。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5644](https://git.1814.love:8443/wx/HL/issues/5644)
|
||||
- **PR**: [#5648](https://git.1814.love:8443/wx/HL/pulls/5648)
|
||||
- **Merge commit**: [86e319ac1](https://git.1814.love:8443/wx/HL/commit/86e319ac1)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
司机手机号此前被锁死:后端编辑接口注释「idCard/phone 不可改:入参 phone 忽略不更新」,前端编辑页手机号禁用(「不可编辑,如需更换请联系管理员」)。wx 要求车务管理员可直接修改司机手机号(联系方式口径,不涉及司机登录账号体系)。
|
||||
|
||||
## 方案
|
||||
|
||||
**编辑司机档案接口放开 phone**(仅车务管理员权限,接口本身已由 `/admin/fleet/**` 角色门禁保护):
|
||||
|
||||
1. `PUT /admin/fleet/drivers/{id}`:入参 `phone` 不再忽略——**非空明文(不含 `*`)时正常更新**,落库前校验:
|
||||
- **格式**:11 位数字,非法返 `code=600212 手机号格式错误(需 11 位数字)`(新增错误码;600211 已被 #5640 占用让位)
|
||||
- **唯一性**:`existsByPhone(phone, excludeSelf)` 排除自身,与其他司机重复返 `code=600203 手机号已存在`
|
||||
2. **脱敏回写防护**:详情页回显的手机号是脱敏值(如 `135****5020`),前端原样回传(含 `*`)时**跳过更新保持原值**(与 license.no 同机制,防掩码串覆盖 AES 加密真值);`phone` 不传(null)同样保持原值
|
||||
3. **idCard 仍不可改**:入参忽略不更新(保持不变)
|
||||
4. 手机号是唯一键(uk_phone),编辑更新走既有加密列等值比对(EncryptTypeHandler),服务层预校验 + DB 唯一键兜底
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 服务 | 说明 |
|
||||
|---|---|---|---|
|
||||
| PUT | `/admin/fleet/drivers/{id}` | hl-fleet-service | **修改接口**:`phone` 入参放开可改(明文 11 位数字更新;含 `*` 脱敏回传 / null 保持原值;格式非法 600212;与其他司机重复 600203);`idCard` 仍忽略不更新 |
|
||||
|
||||
**请求体变化**:`phone` 字段从「编辑忽略」变为「可编辑」(新增时本就必填,字段定义不变,仅编辑语义变化)。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- **车务管理员**编辑司机档案时,手机号可直接修改(不再被忽略);改后立即生效(档案联系方式口径,不联动登录账号)
|
||||
- 修改为**其他司机已占用**的手机号 → `code=600203 手机号已存在`,整单拒绝
|
||||
- 提交非 11 位数字 → `code=600212 手机号格式错误(需 11 位数字)`
|
||||
- 不传 phone / 回传脱敏值 → 保持原手机号不变
|
||||
- 身份证号(idCard)任何情况下不可修改(入参忽略)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
**必须**:司机编辑页手机号**去禁用**(去掉「不可编辑,如需更换请联系管理员」文案),改为可编辑输入框:
|
||||
- 提交前可做 11 位数字格式校验(后端 600212 兜底)
|
||||
- 若用户未修改手机号,编辑表单回显的是脱敏值(含 `*`),**原样回传即可**(后端保持原值)
|
||||
- 修改时提交明文 11 位号码
|
||||
- 后端返回 `600203`(重复)/ `600212`(格式)时按 Result 协议展示错误提示
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **测试**:DriverServiceTest 151/151(新增 4 例:明文更新生效 / 脱敏回传保持原值 / 格式非法 600212 / 重复 600203 排除自身 / null 保持原值)、DriverControllerTest 30/30;fleet 全量 verify **3251 tests 0 失败 4 skipped** + spotless:check 通过(Release E 门禁参数)——见工单评论
|
||||
- **部署**:hl-fleet-service 滚动部署 TEST 双实例 UP(dev-v3=86e319ac1)
|
||||
- **网关验证**:待执行(改 phone 生效 / 脱敏保持 / 600203 重复拒绝)
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5649"
|
||||
title: "holdMode=0直派兼容全程占位行拓扑校验(修复既有缺口)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5653 已合并 dev-v3 并部署 TEST(hl-fleet-service 双实例)。修复 holdMode=0 直派+全程占位行需求拓扑校验必挂(既有缺口,#5643 发现非回归)。无请求/响应契约变化,仅派单 finalize 内部拓扑校验逻辑兼容全程形态。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T17:10:00+08:00"
|
||||
---
|
||||
|
||||
# holdMode=0 直派兼容全程占位行拓扑校验(修复既有缺口)
|
||||
|
||||
> 后端完成:PR #5653 已合并 dev-v3 并部署 TEST,网关验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5649](https://git.1814.love:8443/wx/HL/issues/5649)
|
||||
- **PR**: [#5653](https://git.1814.love:8443/wx/HL/pulls/5653)
|
||||
- **Merge commit**: [86a8a8aa9](https://git.1814.love:8443/wx/HL/commit/86a8a8aa9)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
#5643 发现的既有缺口(非回归):holdMode=0 直派的 batch dailyPlan 在**全程占位行需求**上必挂——`finalizeDailyBatchDispatchPlan` 期望逐日切片(`isSingleDaySlice`),但全程占位行(#5562 全程槽模型:`service_date` NULL、`startDate~endDate` 覆盖整个服务期)被整段消费后保持全程形态,只产生 `slot:startDate` 一个拓扑 key,与逐日 items(`slot:start..slot:end`)不匹配,单行 `isSingleDaySlice` 校验也必挂,拓扑校验误报「最终实派方案写入不完整」。考古 274b03561288(#5595 前基线)同样逻辑。holdMode=1 HOLD 路径跳过 finalize 不受影响。
|
||||
|
||||
## 修复
|
||||
|
||||
`finalizeDailyBatchDispatchPlan` 拓扑校验兼容全程占位行:
|
||||
|
||||
1. **key 展开**:将 `isFullTripRow` 的全程行按服务期逐日展开为多个逻辑 key(`slot:start..slot:end` 全部映射到同一物理行),与逐日 items 的 key 集合对齐
|
||||
2. **单行校验**:合法形态从「仅 `isSingleDaySlice`」放宽为「`isSingleDaySlice || isFullTripRow`」——整段消费的全程行不再误判为写入不完整
|
||||
3. **ID 去重**:全程行跨多天展开为多个逻辑 key 但对应同一物理行,`finalizedAssignmentIds` 按物理行去重(只收一次 ID),避免 `markDispatchPlanGeneration` 的 distinct 校验误报
|
||||
|
||||
## 变更接口
|
||||
|
||||
**无请求/响应契约变化**——仅派单 finalize 内部拓扑校验逻辑修复(`POST /admin/fleet/assignments/batch` 的 dailyPlan 直派路径行为修正:全程占位行需求从「必挂报错」变为「正常冻结」)。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- **holdMode=0 直派 + 全程占位行需求**:从「拓扑校验必挂、报『最终实派方案写入不完整』」修复为「正常冻结最终实派方案」
|
||||
- **holdMode=1 HOLD 路径**:跳过 finalize,完全不受影响
|
||||
- **逐日切片路径**:`isFullTripRow` 为 false 走原逻辑,行为不变
|
||||
- **拓扑真缺失**:仍正确报「写入不完整」(key 集合不匹配时不放松校验)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
无需改动(仅修复了直派路径在全程占位行需求上的误报,前端调用方式不变)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **新增 3 用例**:直派全程占位行不误报 / 直派逐日切片不受影响 / 直派拓扑缺失仍报不完整
|
||||
- AssignmentServiceTest **386/386 全绿**;fleet spotless:check 通过
|
||||
- fleet verify(mvn-throttle 错峰):3243 测试仅 `ReleaseEOccupancyMysql8033RecoveryTest`(需 MySQL 8.0.33 环境)基线失败,与本案无关
|
||||
- **部署**:hl-fleet-service 双实例 UP(17:05)
|
||||
- **网关验证**:VEHICLE_MANAGER 调 `/admin/fleet/board/summary`、`/admin/fleet/reconciliation/cars` 均 200,服务健康
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5654"
|
||||
title: "修复应用到槽位丢价:批量逐日方案未传价日期按价格日历兜底"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端修复完成:PR #5659 合并 dev-v3(d39ef846e166861a836cc86308134d2da0bbca99)并部署 TEST;网关实证修复前 500 NPE、修复后应用到槽位成功且未传价日期按日历价兜底落库+排车表正常读取。前端无需改动(纯后端容错修复)。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T18:10:00+08:00"
|
||||
---
|
||||
|
||||
# 修复应用到槽位丢价(#5654)
|
||||
|
||||
## Bug
|
||||
|
||||
排车表选车弹窗候选带出本次价 ¥900(#5643 已修候选带出),但点「应用到槽位」后排车表该槽位「本次派车价」全 ¥0.00、「日历价」列"—"——价格没写入槽位。
|
||||
|
||||
## 根因
|
||||
|
||||
「应用到槽位」(统一选择车辆/司机→应用)走 `POST /admin/fleet/assignments/batch`(dailyPlan 逐日方案):
|
||||
|
||||
1. `DailyPlanItem.assignmentPrice` 注释「used=true 时必填」但**无校验注解**,前端统一选择应用时不带价;
|
||||
2. `flushDailyPlanSegment` 把 null 价原样包进 `dailyVehicleFees`;
|
||||
3. `assertDailyFeeAdjustmentReasons` 对 null price 调 `compareTo` → **500 NullPointerException**(TEST 堆栈实证 `AssignmentService.java:2919`);
|
||||
4. 应用失败槽位无派车行 → 排车表读取兜底 ¥0/—。
|
||||
|
||||
## 修复(行为变化)
|
||||
|
||||
`POST /admin/fleet/assignments/batch`(dailyPlan 模式):
|
||||
|
||||
- **未传价(assignmentPrice=null)的用车日期不再报错**,按「所选车辆车型 + 服务日」从价格日历兜底(与单体派单 `POST /assignments` 未传 protocolPrice 的口径一致)——修复前该场景 500 NPE。
|
||||
- 未传价日期附带的单日调价原因被忽略(价格来自日历无调价);显式传价且与日历价不一致的日期仍需调价原因(不变)。
|
||||
- VO 注释对齐口径:`assignmentPrice` 可空=日历兜底;`used=false` 时仍必须为空。
|
||||
- 防御:`assertDailyFeeAdjustmentReasons` 对 null 日价跳过(不再 NPE)。
|
||||
|
||||
## 验证
|
||||
|
||||
- AssignmentServiceTest **389/389**(新增 3:无价日不进覆盖集/全无价空覆盖集/null 价不 NPE 防御);dev-v3 全量 verify 3259 项(唯一 Failure 为 releasee 进程时序 flaky,单跑 PASS,与本改动无关)。
|
||||
- 网关实证(复现单 8/13-8/16 双槽位):修复前不带价应用 → 500 NPE;修复后 → 200 成功,槽位 1(汉兰达不带价)4 天 holding `protocol_price=vehicle_fee_calendar_price=800`(日历兜底)AUTO,排车表 `dailyVehiclePlan` 返回 `calendarPrice=assignmentPrice=800、priceSource=CALENDAR`;槽位 0(GL8 带价 900)3600 AUTO 正常。
|
||||
@@ -0,0 +1,432 @@
|
||||
---
|
||||
author: "yst(GIT)"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5655"
|
||||
title: "核单导游/摄影族B嵌套接口下线,统一走族A扁平接口"
|
||||
consumer: "admin"
|
||||
change_type: "删除接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ee7a5d58"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-07"
|
||||
status_note: "后端已删除族B guides/photographers 嵌套 GET/PUT 共4个路由并部署测试服;已行为级验证族B 4端点返回404、族A guide-fees/photographer-fees 正常返回。等待前端将导游/摄影页签从族B路径切换到族A扁平接口,frontend_status=pending 表示等待前端真实领取。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
|
||||
# ⚠️【删除接口·管理后台】核单导游/摄影族B嵌套接口下线,统一走族A扁平接口 (#5655)
|
||||
|
||||
> **PR**: #5658 | **服务**: order-v3 | **更新时间**: 2026-08-07
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单页面的导游、摄影师费用历史上存在两套接口:
|
||||
|
||||
- **族 A(保留)**:`/settlement/guide-fees`、`/settlement/photographer-fees`,按天扁平行结构,与住宿、餐食等页签形态一致。
|
||||
- **族 B(本次删除)**:`/settlement/staff-fees/guides`、`/settlement/staff-fees/photographers`,按人嵌套 `persons[]` 结构。
|
||||
|
||||
一笔导游/摄影费用只对应一个人,族 B 的 `persons[]` 嵌套属于过度设计,且与其他核单页签的平铺形态不一致。本次将族 B 共 4 个路由整体删除,导游/摄影核单统一由族 A 扁平接口承载。
|
||||
|
||||
**旧路径已删,调用返回 HTTP 404「接口不存在」。** 前端必须把导游、摄影师页签的查询/保存调用从族 B 路径切换到族 A 路径。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|--------|------|------|----------|------|
|
||||
| 1 | 查询导游人员费用(族B) | GET | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 删除 | 路由删除,调用返回 HTTP 404 |
|
||||
| 2 | 全量替换导游人员费用(族B) | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 删除 | 路由删除,调用返回 HTTP 404 |
|
||||
| 3 | 查询摄影师人员费用(族B) | GET | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 删除 | 路由删除,调用返回 HTTP 404 |
|
||||
| 4 | 全量替换摄影师人员费用(族B) | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 删除 | 路由删除,调用返回 HTTP 404 |
|
||||
|
||||
替代接口(族 A,**本次未改动,已在线**,前端切换目标):
|
||||
|
||||
| Tab | 查询 | 全量保存 | 确认 |
|
||||
|-----|------|----------|------|
|
||||
| 导游 | `GET /v3/admin/order/:orderId/settlement/guide-fees` | `PUT /v3/admin/order/:orderId/settlement/guide-fees` | `POST /v3/admin/order/:orderId/settlement/guide-fees/confirm` |
|
||||
| 摄影师 | `GET /v3/admin/order/:orderId/settlement/photographer-fees` | `PUT /v3/admin/order/:orderId/settlement/photographer-fees` | `POST /v3/admin/order/:orderId/settlement/photographer-fees/confirm` |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 已删除:族B 导游人员费用查询与保存
|
||||
|
||||
- **原方法与路径**:
|
||||
- `GET /v3/admin/order/:orderId/settlement/staff-fees/guides`
|
||||
- `PUT /v3/admin/order/:orderId/settlement/staff-fees/guides`
|
||||
- **使用场景**:已删除。导游核单查询/保存改用 `/settlement/guide-fees`(见 §3.3)。
|
||||
- **认证**:原接口要求管理后台登录态;接口删除后,即使登录态有效也返回 HTTP 404。
|
||||
- **幂等性**:不适用。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.2 已删除:族B 摄影师人员费用查询与保存
|
||||
|
||||
- **原方法与路径**:
|
||||
- `GET /v3/admin/order/:orderId/settlement/staff-fees/photographers`
|
||||
- `PUT /v3/admin/order/:orderId/settlement/staff-fees/photographers`
|
||||
- **使用场景**:已删除。摄影师核单查询/保存改用 `/settlement/photographer-fees`(见 §3.4)。
|
||||
- **认证**:原接口要求管理后台登录态;接口删除后,即使登录态有效也返回 HTTP 404。
|
||||
- **幂等性**:不适用。
|
||||
- **限流**:无。
|
||||
|
||||
### 3.3 替代:导游费用(族A)
|
||||
|
||||
- **接口名**:查询导游费用 / 全量保存导游费用 / 确认导游费用
|
||||
- **方法与路径**:
|
||||
- `GET /v3/admin/order/:orderId/settlement/guide-fees`
|
||||
- `PUT /v3/admin/order/:orderId/settlement/guide-fees`
|
||||
- `POST /v3/admin/order/:orderId/settlement/guide-fees/confirm`
|
||||
- **使用场景**:核单页面导游页签的查询、全量保存、确认。
|
||||
- **认证**:管理后台登录态 + 订单访问权限。
|
||||
- **幂等性**:GET 只读;PUT 为全量替换语义,需携带 `expectedSourceFingerprint` 乐观锁指纹(取值为最近一次 GET 返回的 `sourceFingerprint`),指纹不匹配拒绝写入;POST confirm 重复确认不产生副作用。
|
||||
- **限流**:无接口级特殊限流。
|
||||
|
||||
### 3.4 替代:摄影师费用(族A)
|
||||
|
||||
- **接口名**:查询摄影师费用 / 全量保存摄影师费用 / 确认摄影师费用
|
||||
- **方法与路径**:
|
||||
- `GET /v3/admin/order/:orderId/settlement/photographer-fees`
|
||||
- `PUT /v3/admin/order/:orderId/settlement/photographer-fees`
|
||||
- `POST /v3/admin/order/:orderId/settlement/photographer-fees/confirm`
|
||||
- **使用场景**:核单页面摄影师页签的查询、全量保存、确认。
|
||||
- **认证 / 幂等性 / 限流**:与 §3.3 导游一致。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数(族A GET / PUT / POST 通用)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||
|------|------|------|------|------|
|
||||
| `orderId` | String(Long) | 是 | 订单 ID | 正整数 |
|
||||
|
||||
GET 无 Query 参数、无请求体。POST confirm 无请求体。
|
||||
|
||||
### 4.2 PUT 请求体(全量保存)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `expectedSourceFingerprint` | String | 是 | 乐观锁指纹,取最近一次 GET 返回的 `sourceFingerprint`;不匹配则拒绝写入 |
|
||||
| `items` | Array | 是 | 全量费用行(扁平按天,无嵌套);传 `[]` 表示清空 |
|
||||
| `excludedCandidateKeys` | String[] | 否 | 被排除的候选 `candidateKey` 列表;无排除传 `[]` |
|
||||
|
||||
`items[]` 行字段与 GET 出参行字段一致(见 §5.2),其中 `id` 已有行需回传、新增行不传。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 顶层字段(GET `data`)
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `category` | String | 否 | 类别;导游固定 `GUIDE`,摄影师固定 `PHOTOGRAPHER` |
|
||||
| `sourceFingerprint` | String | 否 | 乐观锁指纹;PUT 必须通过 `expectedSourceFingerprint` 回传 |
|
||||
| `totalAmount` | String(Decimal) | 否 | 费用合计金额,字符串格式如 `"590.00"` |
|
||||
| `cashPaidAmount` | String(Decimal) | 否 | 现付金额合计 |
|
||||
| `unconfirmedCount` | Integer | 否 | 未确认行数 |
|
||||
| `pendingCandidateCount` | Integer | 否 | 待处理候选数 |
|
||||
| `settlementReady` | Boolean | 否 | 是否已具备核单条件 |
|
||||
| `blockReasonCode` | String | 是 | 不具备核单条件时的机器可读原因;可核单时为 `null`,如 `ITEMS_UNCONFIRMED` |
|
||||
| `editable` | Boolean | 否 | 当前是否可编辑 |
|
||||
| `readOnlyReasonCode` | String | 是 | 只读原因;可编辑时为 `null` |
|
||||
| `items` | Array | 否 | 费用行,扁平按天,无嵌套;无明细为 `[]` |
|
||||
|
||||
### 5.2 `items[]` 行字段
|
||||
|
||||
导游(guide-fees):
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | String(Long) | 是 | 费用行 ID(雪花,字符串);候选未落库行为 `null` |
|
||||
| `candidateKey` | String | 是 | 候选键;手工补录行为 `null` |
|
||||
| `staffAssignmentId` | String(Long) | 是 | 人员派单 ID;无关联为 `null` |
|
||||
| `serviceDate` | String(date) | 否 | 服务日期(单天),格式 `YYYY-MM-DD` |
|
||||
| `name` | String | 否 | 导游姓名 |
|
||||
| `serviceType` | String | 否 | 服务类型编码,见 §6.1 |
|
||||
| `serviceTypeName` | String | 否 | 服务类型名称,如 `全陪导游` |
|
||||
| `paymentMethod` | String | 否 | 付款方式编码,见 §6.3 |
|
||||
| `paymentMethodName` | String | 否 | 付款方式名称 |
|
||||
| `amount` | String(Decimal) | 否 | 金额,字符串格式如 `"295.00"` |
|
||||
| `settlementConfirmStatus` | String | 否 | 核单确认状态编码,见 §6.4 |
|
||||
| `settlementConfirmStatusName` | String | 否 | 核单确认状态名称 |
|
||||
| `remark` | String | 是 | 备注 |
|
||||
| `sourceType` | String | 否 | 来源编码,见 §6.5 |
|
||||
| `sourceTypeName` | String | 否 | 来源名称,如 `手工补录` |
|
||||
| `sourceActive` | Boolean | 否 | 来源是否有效 |
|
||||
| `voucherUrls` | String[] | 否 | 凭证 URL;无凭证为 `[]` |
|
||||
| `completionState` | String | 否 | 行完备状态,如 `COMPLETE` |
|
||||
| `candidateResolution` | String | 否 | 候选处理结果,如 `INCLUDED` |
|
||||
|
||||
摄影师(photographer-fees)与导游**同构**,仅两个字段名不同:
|
||||
|
||||
| 语义 | 导游字段名 | 摄影师字段名 |
|
||||
|------|-----------|--------------|
|
||||
| 姓名 | `name` | `photographerName` |
|
||||
| 类型编码/名称 | `serviceType` / `serviceTypeName` | `feeType` / `feeTypeName` |
|
||||
|
||||
摄影师类型枚举见 §6.2。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 导游 `serviceType`
|
||||
|
||||
**所属字段**:guide-fees `items[].serviceType` | **类型**:String
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `FULL_COURSE_GUIDE` | 全陪导游 |
|
||||
| `LOCAL_GUIDE` | 地接导游 |
|
||||
| `COMMENTARY_SERVICE` | 讲解服务 |
|
||||
| `TEMPORARY_SUPPLEMENT` | 临时补录 |
|
||||
|
||||
### 6.2 摄影师 `feeType`
|
||||
|
||||
**所属字段**:photographer-fees `items[].feeType` | **类型**:String
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `FOLLOW_SHOOT` | 跟拍 |
|
||||
| `PORTRAIT` | 写真 |
|
||||
| `AERIAL_SHOOT` | 航拍 |
|
||||
| `EDITING_DELIVERY` | 剪辑出片 |
|
||||
| `CAMERA_DRONE` | 相机/无人机 |
|
||||
| `OTHER` | 其他 |
|
||||
|
||||
### 6.3 `paymentMethod`
|
||||
|
||||
**所属字段**:`items[].paymentMethod` | **类型**:String
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `COMPANY_PAID` | 公司付款 |
|
||||
| `CASH_PAID` | 现付 |
|
||||
|
||||
### 6.4 `settlementConfirmStatus`
|
||||
|
||||
**所属字段**:`items[].settlementConfirmStatus` | **类型**:String
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `UNCONFIRMED` | 未确认 |
|
||||
| `CONFIRMED` | 已确认 |
|
||||
|
||||
### 6.5 `sourceType`
|
||||
|
||||
**所属字段**:`items[].sourceType` | **类型**:String
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `MANUAL` | 手工补录 | 核单页手工新增的费用行 |
|
||||
| 其他来源编码 | — | 由派单/候选自动带入,以实际返回为准 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
### 7.1 已删除的族B专属错误码(不再返回)
|
||||
|
||||
| code | 原含义 | 变更 |
|
||||
|------|--------|------|
|
||||
| `584023` | DRIVER detail 缺 days[] 数组 / 元素缺 service_date 或 daily_fee 字段 | 删除,不再返回 |
|
||||
| `584024` | GUIDE / PHOTOGRAPHER detail 缺 persons[] / 元素缺 name / days / per_day | 删除,不再返回 |
|
||||
| `584025` | LEADER detail 缺 days 或 per_day 字段 | 删除,不再返回 |
|
||||
| `584028` | OTHER detail 缺 items[] / 元素缺 name / amount | 删除,不再返回 |
|
||||
|
||||
前端若存在针对这 4 个错误码的分支处理,可一并清理。
|
||||
|
||||
### 7.2 本次相关错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `404` | 请求地址不存在 | 调用任一已删除的族B路由(staff-fees/guides、staff-fees/photographers) |
|
||||
| `400` | 请求参数错误 | `orderId` 不是正整数;PUT 请求体字段缺失或非法 |
|
||||
| `403` | 无访问权限 | 登录态或角色无权访问 |
|
||||
| `581007` | 订单不存在 | `orderId` 对应订单不存在 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:GET 导游费用(族A)
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2085641684778958848/settlement/guide-fees
|
||||
Authorization: Bearer JWT_TOKEN
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
响应(测试单真实打样):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"category": "GUIDE",
|
||||
"sourceFingerprint": "a02c66c6...",
|
||||
"totalAmount": "590.00",
|
||||
"cashPaidAmount": "0.00",
|
||||
"unconfirmedCount": 2,
|
||||
"pendingCandidateCount": 0,
|
||||
"settlementReady": false,
|
||||
"blockReasonCode": "ITEMS_UNCONFIRMED",
|
||||
"editable": true,
|
||||
"readOnlyReasonCode": null,
|
||||
"items": [
|
||||
{
|
||||
"id": "2085641684778958849",
|
||||
"candidateKey": null,
|
||||
"staffAssignmentId": null,
|
||||
"serviceDate": "2026-08-10",
|
||||
"name": "王强",
|
||||
"serviceType": "FULL_COURSE_GUIDE",
|
||||
"serviceTypeName": "全陪导游",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司付款",
|
||||
"amount": "295.00",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"settlementConfirmStatusName": "未确认",
|
||||
"remark": "打样导游",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceTypeName": "手工补录",
|
||||
"sourceActive": true,
|
||||
"voucherUrls": [],
|
||||
"completionState": "COMPLETE",
|
||||
"candidateResolution": "INCLUDED"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界:PUT 全量保存空 items(清空导游费用)
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2085641684778958848/settlement/guide-fees
|
||||
Authorization: Bearer JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedSourceFingerprint": "a02c66c6...",
|
||||
"items": [],
|
||||
"excludedCandidateKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
保存后重新 GET 拉取最新 `sourceFingerprint` 再渲染。
|
||||
|
||||
### 8.3 业务失败:调用已删除的族B旧路径返回 404
|
||||
|
||||
请求:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2085641684778958848/settlement/staff-fees/guides
|
||||
Authorization: Bearer JWT_TOKEN
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "请求地址不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
PUT `/settlement/staff-fees/guides`、GET/PUT `/settlement/staff-fees/photographers` 行为一致,均为 HTTP 404。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
- 核单页面导游、摄影师页签的查询、保存、确认全部走族 A 扁平接口。
|
||||
- 一笔费用一行(按人×天扁平铺开),不存在一人多行嵌套。
|
||||
|
||||
**不适用**:
|
||||
- 族 B 的 `persons[]` 嵌套请求体不再有任何承载路径,不得把嵌套 body 改发到族 A(族 A 只接受扁平 `items[]`)。
|
||||
- 领队、司机、其他人员费用早在 #5380 已删除,本次不涉及。
|
||||
|
||||
**特殊边界**:
|
||||
- PUT 必须携带最新 `expectedSourceFingerprint`;并发编辑或保存后未刷新指纹再保存会被拒绝,需重新 GET。
|
||||
- `items=[]` 是合法输入,表示清空该类别费用。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 接口级对比
|
||||
|
||||
| 能力 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 导游费用查询 | `GET /settlement/staff-fees/guides`(族B,persons[] 嵌套) | `GET /settlement/guide-fees`(族A,扁平 items[]) |
|
||||
| 导游费用保存 | `PUT /settlement/staff-fees/guides`(族B) | `PUT /settlement/guide-fees`(族A,带 expectedSourceFingerprint) |
|
||||
| 摄影师费用查询 | `GET /settlement/staff-fees/photographers`(族B) | `GET /settlement/photographer-fees`(族A) |
|
||||
| 摄影师费用保存 | `PUT /settlement/staff-fees/photographers`(族B) | `PUT /settlement/photographer-fees`(族A) |
|
||||
| 族B 4 个路由 | 可用 | **已删除,调用返回 HTTP 404** |
|
||||
|
||||
### 10.2 错误码对比
|
||||
|
||||
| 错误码 | 改前 | 改后 |
|
||||
|--------|------|------|
|
||||
| `584023` / `584024` / `584025` / `584028` | 族B 请求体校验失败时返回 | 不再返回(族B 路由整体删除) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:是。族B 共 4 个路由已删除,未切换的前端版本调用固定 404。
|
||||
- **前端是否必须同步上线**:是。管理后台必须把导游、摄影师页签的查询/保存切换到族 A 路径,并改为扁平 `items[]` 请求体(携带 `expectedSourceFingerprint`)。
|
||||
- **后端数据**:导游/摄影费用底层存储不变,仅接口承载形态收口;历史数据在族 A 下正常可见。
|
||||
|
||||
### 11.2 回滚边界
|
||||
|
||||
- 前端版本不得回滚到仍调用 `staff-fees/guides`、`staff-fees/photographers` 的版本,否则对应页签固定 404。
|
||||
- 后端如需回滚需恢复 4 个路由与 4 个错误码,涉及 PR #5658 整体 revert,由后端评估。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 切换目标路径是 `guide-fees` / `photographer-fees`(短横线、无 staff 前缀),不要拼成 `staff-fees/guide-fees` 等混合路径。
|
||||
- 族A PUT 是全量替换语义:保存时提交整页 `items[]`;只传改动行会丢失未传行。
|
||||
- 保存成功后必须重新 GET 获取最新 `sourceFingerprint`,否则下次 PUT 指纹不匹配被拒。
|
||||
- 摄影师行字段是 `photographerName` / `feeType` / `feeTypeName`,与导游的 `name` / `serviceType` / `serviceTypeName` 不同,不要复用同一套字段映射常量。
|
||||
- `orderId`、行 `id`、`staffAssignmentId` 均按字符串处理;金额字段(`amount` / `totalAmount` / `cashPaidAmount`)为字符串格式 Decimal。
|
||||
- 清理前端对 `584023` / `584024` / `584025` / `584028` 的错误码分支。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5655](https://git.1814.love:8443/wx/HL/issues/5655)
|
||||
- **PR**: [#5658](https://git.1814.love:8443/wx/HL/pulls/5658)
|
||||
- **Merge commit**: [7d29484da77e20a706ec611893545c419f6821b2](https://git.1814.love:8443/wx/HL/commit/7d29484da77e20a706ec611893545c419f6821b2)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst(腰苏图)
|
||||
|
||||
## 验证证据
|
||||
|
||||
- PR #5658 已合并至 `dev-v3`,合并提交 `7d29484da77e20a706ec611893545c419f6821b2`。
|
||||
- 测试服已部署并行为级验证:族B 4 端点(GET/PUT staff-fees/guides、GET/PUT staff-fees/photographers)均返回 HTTP 404;族A guide-fees / photographer-fees 正常返回业务数据。
|
||||
|
||||
## 前端交付(2026-08-08 mmg,hl-admin@ee7a5d58)
|
||||
|
||||
族B→族A 切换已上线 v2.1。导游/摄影师页签查询/保存直连族A `guide-fees`/`photographer-fees`,废弃按人嵌套 StaffFeeTable、并入通用 CategoryTable(与住宿/餐食/车辆页签同形态);PUT 携带 `expectedSourceFingerprint` 类别级指纹乐观锁(照车辆 version 模式 GET attach / 保存后 re-GET 写回 / 冲突刷新)。导游 `name`/`serviceType` 与摄影 `photographerName`/`feeType` 分别映射未复用;人员下拉保留仅预填姓名(族A 无 staffId 落点);未接 confirm 端点(确认语义行级随 PUT);候选最小实现 excludedCandidateKeys 恒 []。
|
||||
|
||||
**待后端确认**:人员费用指纹冲突的错误码 changelog 未给出,前端按车辆域既有 `584108` 复用做冲突刷新分支;若人员域实际返回别的码,请告之以对齐(不阻塞,缺省时错误照常透传提示,仅无自动刷新)。
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5656"
|
||||
title: "派单详情 travelers 补 nativePlace 跨服务透传(#5642 链路补全)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "7e7085b6"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5661 合并 dev-v3(3ee5770af)并部署 TEST(order-v3+fleet 双服务);网关实测派单详情与 travelers 端点 nativePlace 解析正确(150784→内蒙古呼伦贝尔市等 5 场景)。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T18:40:00+08:00"
|
||||
---
|
||||
|
||||
# 派单详情 travelers 补 nativePlace 跨服务透传(#5656)
|
||||
|
||||
## 背景
|
||||
|
||||
#5642 仅为 order 管理端 `TravelerVO`(traveler/list 等)加了 `nativePlace`;派单详情走跨服务链路(order `GET /v3/internal/order/orders/{orderId}/fleet-detail-context` → fleet `BoardOrderDetailVO.travelers`),该链路 traveler DTO 未带 `nativePlace`,派单页/看板详情展示出行人时缺所属地。本单补全该链路。
|
||||
|
||||
## 变更内容
|
||||
|
||||
| 层 | 变更 |
|
||||
|---|---|
|
||||
| hl-common | `OrderTravelerForFleetDTO` 新增可空 `nativePlace`(身份证所属地,省+地级行政区名;非身份证/未命中为 null) |
|
||||
| order 生产者 | `OrderFleetProviderService#toMaskedTraveler` 填充(复用 #5642 `IdCardParser` 解析结果,同源 `idProvinceName`) |
|
||||
| fleet 消费者 | `BoardOrderDetailVO.travelers` / `GET /admin/fleet/board/orders/{id}/travelers` / `FleetTravelerAccessService#listMasked` 均直接复用共享 DTO,无逐字段映射,自动获得字段 |
|
||||
|
||||
受影响接口(均为新增只读字段,无破坏性):
|
||||
|
||||
- `GET /admin/fleet/board/orders/{orderId}`(派单详情聚合)→ `data.travelers[].nativePlace`
|
||||
- `GET /admin/fleet/board/orders/{orderId}/travelers` → `data[].nativePlace`
|
||||
- internal:`GET /v3/internal/order/orders/{orderId}/fleet-detail-context`、`GET /v3/internal/order/orders/{orderId}/fleet-travelers` 的 traveler 投影同步带 `nativePlace`
|
||||
|
||||
取值口径与 #5642 一致:大陆身份证命中地级码→省短名+地市名(如 `150784…`→内蒙古呼伦贝尔市);直辖市→市名;省直辖县级(4190/4290/4690/6590)→6 位精确映射;非身份证或未命中(含已撤销历史码)→`null` 不报错。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 工单:https://git.1814.love:8443/wx/HL/issues/5656
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5661
|
||||
- 后端:wx
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
- 接口路径/方法/请求体:不变。
|
||||
- 响应 travelers 项新增 `nativePlace`(string | null)。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向测试:order `OrderFleetProviderServiceTest` 53 全过(含 fleet-detail-context nativePlace 断言);fleet `BoardOrderServiceTest` 87 + `BoardControllerTest` 11 全过(含派单详情 travelers.nativePlace 透传断言)。
|
||||
- 全量:fleet `mvn -pl hl-fleet-service -am verify` 3256 测试,失败仅 ReleaseEMixedBinaryHarnessTest(1F)+ReleaseEOccupancyMysql8033RecoveryTest(10E) 两个 harness 类,git stash 基线复跑同类同败;order `-pl hl-order-service-v3 -am verify` 7610 测试 7F/95E 失败类集与 #5642 基线逐一相同;fleet spotless:check 通过。
|
||||
- 部署 TEST 成功(hl-order-service-v3 + hl-fleet-service)。
|
||||
- 网关验证(自建 HLTEST 订单 2085637067265449985,复用 #5642 五场景出行人):派单详情与 travelers 端点均断言 150784→内蒙古呼伦贝尔市、469001→海南五指山市、110101→北京市、PASSPORT→null、371201(已撤销码)→null;脱敏与省级字段回归通过。
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- 派单页/看板详情展示出行人所属地时直接使用 `travelers[].nativePlace`;`null` 展示空(如 "-")。
|
||||
- 与 #5642 traveler/list 口径一致,前端两处共用同一展示逻辑即可。
|
||||
|
||||
## 前端核查(2026-08-08 mmg 实查,前端无需新改动)
|
||||
|
||||
本单即 #5642 协调台更正中所述「另立后端工单补 fleet-detail-context→BoardOrderDetailVO 链路」的后端补全。前端派单页所属地列早在 #5642(hl-admin@7e7085b6)已接入并透传:
|
||||
|
||||
- `src/views/fleet/board/components/Step1OrderDetail.vue` 所属地列渲染 `traveler.nativePlace || traveler.idProvinceName || '—'`。
|
||||
- 数据链 `loadBoardOrderDetail → normalizeBoardOrder(...row) → buildActiveAssignmentOrder(...order) → travelers map(...traveler)` 全程透传、无白名单丢字段。
|
||||
- 后端在 `BoardOrderDetailVO.travelers` 补 nativePlace 后,前端列自动取值生效,**前端零改动**。frontend_ref 沿用 #5642 的 7e7085b6(无新提交)。
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5657"
|
||||
title: "确认执行不强制派满建议车辆数(按实际槽位校验,建议数仅参考)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5660 已合并 dev-v3 并部署 TEST(18:27 滚动 DONE)。网关验证真实复现工单场景 6 项全 PASS:订单 26-4949(7人/建议2辆)只派 1 辆 7 座 GL8,确认执行 200 confirmed=true(修复前 605041「缺少第2个车辆槽位」×4)。前端无需配合。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T18:45:00+08:00"
|
||||
---
|
||||
|
||||
# 确认执行不强制派满建议车辆数(按实际槽位校验,建议数仅参考)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5660](https://git.1814.love:8443/wx/HL/pulls/5660)
|
||||
> **Issue**: [#5657](https://git.1814.love:8443/wx/HL/issues/5657)
|
||||
> **日期**: 2026-08-07
|
||||
> **影响**: 🟢 **缺陷修复**,无契约结构变更(`POST /admin/fleet/assignments/:assignmentId/confirm` 行为修复)。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5657(P1)wx 实测:订单 26-4949(7 人),系统建议 2 辆车(suggestedVehicleCount=2),车务只派 1 辆(7 座商务车够坐)。确认执行被拦:「该服务日缺少第 2 个车辆槽位派单」(8/13-8/16 四天都报)+「订单与派单基线已变化,请按逐日差异处理后重试」(605041)。
|
||||
|
||||
**口径(wx)**:车务说的算——车务想派几辆就派几辆,建议车辆数仅作参考,不强制派满。
|
||||
|
||||
**根因**:最终确认基线校验(assertFinalConfirmationBaseline)按需求展开的**期望槽位数**逐槽逐日要求派单覆盖,未派定的槽位也强制要求补齐。
|
||||
|
||||
## 修复内容(内部,无接口/字段结构变化)
|
||||
|
||||
- 确认执行基线校验改为按**实际槽位**校验:未派定的槽位跳过「缺少第 N 个槽位」强制拦截(建议车辆数仅参考);
|
||||
- **已派定槽位**的服务日完整性校验保留(防止半派状态确认);
|
||||
- 座位/车型相关提示与校验逻辑不变(座位数校验保留)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 来源 |
|
||||
|---|---|---|
|
||||
| POST | /admin/fleet/assignments/:assignmentId/confirm | 确认执行:未派满建议数不再 605041,已派槽位缺服务日仍拦截 |
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3258 Tests,Failures=0,Errors=0;新增 2 例回归(建议两辆仅派一辆全程行可确认——已回退验证修复前确实被拦;已派槽位缺服务日仍拦截);spotless 通过。
|
||||
- 部署 TEST:18:27 滚动 DONE。
|
||||
- 网关真实复现(订单 26-4949,蒙A-G8888 GL8 7 座对 7 人,8/13-8/16)6 项全 PASS:删除多余槽位恢复只派 1 辆现场 → 登记司机确认 → 确认执行 200 confirmed=true → DB 终态 4 行 assigned + confirmed_at 非空、无其他活跃槽位。
|
||||
|
||||
## 前端配合
|
||||
|
||||
无需配合。确认执行按车务实际派车数通过,建议车辆数仍作为参考展示。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5662"
|
||||
title: "改派页面缺「选择司机」按钮(无法改派司机)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "c63ce28f"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-07"
|
||||
---
|
||||
|
||||
# 改派页面缺「选择司机」按钮(前端修复)
|
||||
|
||||
## 问题(wx 实测)
|
||||
派单【改派】页面:显示"当前司机 xxx / 当前车辆 xxx"+"车辆和司机可任选一侧先选",但**只有选车入口,没有「选择司机」按钮**——无法改派司机。
|
||||
|
||||
## 后端已就绪(前端直接用)
|
||||
改派接口 `POST /admin/fleet/assignments/{assignmentId}/change` 的 ReqVO **已支持 `newDriverId`**(改司机)——后端能力在,前端缺入口。
|
||||
|
||||
## 前端要做
|
||||
**口径(wx):改派与初次派车交互一致——车辆和司机「可任选一侧先选」**。改派弹窗/页面**加「选择司机」按钮**(与选车并列,两侧都能先选,同初次派车):
|
||||
- 改派页「选择车辆」「选择司机」两个入口并列,**先选哪侧都行**(复用初次派车的统一选择车辆/司机组件,按当前派单日期/需求过滤空闲车辆+司机)
|
||||
- 选定司机 → 调 change 接口传 `newDriverId` 完成改派司机
|
||||
- 改派司机的校验(档期冲突/黑名单/跨常驻确认/保险)与改派车一致(后端 change 已处理)
|
||||
|
||||
## 验收
|
||||
- [ ] 改派页有【选择司机】按钮,可选新司机
|
||||
- [ ] 选定后调 change 传 newDriverId,改派司机生效
|
||||
- [ ] 改派司机校验(冲突/跨常驻/保险)正常提示
|
||||
|
||||
## 关联
|
||||
- 后端 change 接口:`POST /admin/fleet/assignments/{assignmentId}/change`(ReqVO 含 newDriverId/newVehicleId)
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5663"
|
||||
title: "fleet 英文/技术黑话提示统一改中文友好(确认弹窗差异中文名称与文案)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "9784e7aa"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-07"
|
||||
status_note: "后端完成:PR #5672 已合并 dev-v3(2f6b043dd)并部署 TEST(hl-fleet-service 双实例 UP)。确认/创建/改派弹窗逐日差异新增 differenceLabel 中文名称,message 全部改中文友好(动作+原因+怎么办);600211 去配置 key 名、600112 去 busy 英文;内部异常消息(含保险 long 溢出)转中文。网关验证 8/8:605041 差异返回 label=服务日期缺少派车、message=该日期缺少有效的派车安排,请补派后重试,无英文码名透出,differenceType 枚举保留。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T21:38:00+08:00"
|
||||
---
|
||||
|
||||
# fleet 英文/技术黑话提示统一改中文友好(#5663)
|
||||
|
||||
## 背景
|
||||
|
||||
派车页确认执行弹窗透出英文错误码(如 `DAILY_VEHICLE_PLAN_BASELINE_MISMATCH 逐日逐车方案、价格或只读状态已发生变化`),用户看不懂。全面排查 fleet 车务域英文/技术黑话提示,统一中文友好(动作 + 原因 + 怎么办)。
|
||||
|
||||
## 方案
|
||||
|
||||
1. **确认/创建/改派弹窗逐日差异**:`ConfirmRespVO.DailyDifferenceVO`(及 AssignmentWriteRespVO/ChangeAssignmentRespVO 的 dailyDifferences)新增 **`differenceLabel` 中文名称字段**;差异 `message` 全部改为「动作 + 原因 + 怎么办」中文,去除「切片 / 冻结 / 快照 / 槽位」等黑话。
|
||||
2. **错误码文案**:600211 去掉 Nacos 配置 key 名透出;600112 去掉 `(busy)` 英文。
|
||||
3. **内部异常消息**:快照工厂 / 加密 / occupancy 等英文异常消息转中文(日志与兜底透出均友好)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| POST /admin/fleet/assignments(holdMode=0) | 605041 响应 `data.dailyDifferences[].differenceLabel` 新增(中文名称);message 文案更新 |
|
||||
| POST /admin/fleet/assignments/batch | 同上 |
|
||||
| POST /admin/fleet/assignments/确认执行 | 同上 |
|
||||
| POST /admin/fleet/assignments/改派(holdMode=0) | 同上 |
|
||||
| 全部 fleet 管理接口 | 600211/600112 message 文案更新(无字段变更) |
|
||||
|
||||
## 行为变化
|
||||
|
||||
- `differenceType` 英文枚举 **保留不变**(供前端逻辑判断),新增 `differenceLabel` 中文名称与 `message` 用于用户展示。
|
||||
- `differenceLabel` 枚举与中文名称映射:
|
||||
- REQUIREMENT_VERSION_MISMATCH → 用车需求已更新
|
||||
- ORDER_DATE_MISMATCH → 订单日期已调整
|
||||
- ITINERARY_DATE_MISMATCH → 行程日期不一致
|
||||
- ASSIGNMENT_DATE_MISSING → 服务日期缺少派车
|
||||
- ASSIGNMENT_DATE_EXTRA → 服务日期多出派车
|
||||
- HEADCOUNT_BASELINE_MISMATCH → 乘车人数不一致
|
||||
- CAPACITY_INSUFFICIENT → 座位数不足
|
||||
|
||||
## 前端动作
|
||||
|
||||
- **确认/创建/改派失败弹窗**:差异列表展示优先使用 `differenceLabel`(中文名称)+ `message`(动作+原因+怎么办),不要再拼接展示英文 `differenceType` 码名;现有按 `differenceType` 做逻辑判断的分支可保持不变。
|
||||
- 如前端有硬编码的英文码名映射(如 DAILY_VEHICLE_PLAN_BASELINE_MISMATCH),请同步删除或改为按 `differenceLabel` 展示。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- fleet 全量 verify:3261 tests,0 failures,4 skipped;spotless:check 通过(PR #5672)。
|
||||
- 网关实测(TEST):create(holdMode=0) 基线不一致 → code=605041,`dailyDifferences[].differenceLabel="服务日期缺少派车"`、`message="该日期缺少有效的派车安排,请补派后重试"`,无英文码名透出,`differenceType=ASSIGNMENT_DATE_MISSING` 保留。8/8 断言通过。
|
||||
- 部署:hl-fleet-service dev-v3=2f6b043dd 双实例 UP。
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5664"
|
||||
title: "派单多司机时微信预览按司机分 tab(新增批量渲染端点)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "91530f73"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5668 合并 dev-v3(d4ebd435be62cbe377ba712e8f0aed2b41e75a56)并部署 TEST,网关验证双槽位分司机渲染+单项容错+单渲染回归全通过。待前端预览区接入 render-batch 按司机分 tab。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T18:45:00+08:00"
|
||||
---
|
||||
|
||||
# 派单多司机时微信预览按司机分 tab(#5664)
|
||||
|
||||
## 需求
|
||||
|
||||
派单选了多个司机(多槽位)时,微信消息预览按司机分 tab 切换——每个司机一个预览(车辆/行程/链接),分别查看+发送。实证:26-6318 派 2 司机(孙晓三、斯琴)预览只有 1 个。
|
||||
|
||||
## 根因
|
||||
|
||||
预览端点 `POST /admin/fleet/message-templates/{templateId}/render` 请求 VO 只接受**单个** driverId/vehicleId,多槽位只返回 1 个预览。
|
||||
|
||||
## 新增接口
|
||||
|
||||
### `POST /admin/fleet/message-templates/{templateId}/render-batch`
|
||||
|
||||
批量渲染同一模板,多槽位按司机返回预览列表。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{"orderId": 123, "vehicleId": 456, "driverId": 789, "assignmentGroupId": null,
|
||||
"serviceDates": null, "chargeableServiceDates": null, "vehicleFeeWaiverReason": null},
|
||||
{"orderId": 123, "vehicleId": 457, "driverId": 790}
|
||||
]
|
||||
}
|
||||
```
|
||||
- `items`:1~20 项,每项 = 单槽位预览入参(字段同单渲染 render 端点)。
|
||||
- 1 项时等价单渲染(单司机场景不变,前端可继续用 render 或 render-batch 单项)。
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{"index": 0, "driverId": 789, "vehicleId": 456, "assignmentGroupId": null,
|
||||
"success": true,
|
||||
"renderedBody": "师傅您好,HL20260807... 车辆 蒙A-G8888 别克GL8 ...",
|
||||
"variablesUsed": ["driver.name", "vehicle.plate", "..."],
|
||||
"errorCode": null, "errorMessage": null},
|
||||
{"index": 1, "driverId": 790, "vehicleId": 457, "success": true, "renderedBody": "...车辆 蒙A-H7777 丰田汉兰达..."}
|
||||
]
|
||||
}
|
||||
```
|
||||
- `items` 与请求同序;每项独立 `success`。
|
||||
- 单项渲染抛业务异常 → 该项 `success=false` + `errorCode`/`errorMessage`,**不阻断其他槽位**;单项司机/车辆取数异常沿用单渲染容错兜空口径(字段空串、仍 success=true)。
|
||||
- 模板不存在 → 整批 **600801**(与单渲染一致);白名单外占位符 **600800**。
|
||||
|
||||
## 前端交接
|
||||
|
||||
1. **多槽位/多司机预览**:预览区对每槽位构造一个 item(该槽位 driverId/vehicleId/assignmentGroupId/serviceDates 等),调 `render-batch` 一次取回全部预览,按司机/槽位分 **tab** 展示各项 `renderedBody`。
|
||||
2. **发送**:按选中 tab(司机)取对应 `renderedBody` 发送;单项 `success=false` 的 tab 展示 `errorMessage` 并禁用发送。
|
||||
3. **单司机**:1 项即可(无 tab 或单 tab),行为与现状不变;现有 `render` 端点保留兼容(本单未改)。
|
||||
4. tab 标签建议用司机姓名/车牌(renderedBody 内含,或前端用槽位上下文)。
|
||||
|
||||
## 验证
|
||||
|
||||
- MessageTemplateRenderServiceTest **20/20**(新增 4:多司机各自渲染/模板不存在 600801/单项取数异常兜空不阻断/单项业务异常 success=false 不阻断);dev-v3 全量 verify 3265/0F/0E。
|
||||
- 网关实证(TEST,change_notify 模板):双槽位 render-batch → 2 预览各自车辆正确渲染(蒙A-G8888 别克GL8 / 蒙A-H7777 丰田汉兰达);单项不存在车辆槽位兜空不阻断;单渲染 render 回归正常。
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5665"
|
||||
title: "改派时支持增加/删除槽位(调整车辆数量)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "f1c00ba8"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5669 已合并 dev-v3 并部署 TEST(hl-fleet-service 双实例)。新增 POST /admin/fleet/assignments/slots 增槽位;删除槽位复用既有 DELETE /slots/{slotId}。前端需在改派页加【增加槽位】【删除槽位】操作并编排现有接口(见前端交接)。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T20:26:00+08:00"
|
||||
---
|
||||
|
||||
# 改派时支持增加/删除槽位(调整车辆数量)
|
||||
|
||||
> 后端完成:PR #5669 已合并 dev-v3 并部署 TEST,网关验证通过。**前端需在改派页增加【增加槽位】【删除槽位】操作**。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5665](https://git.1814.love:8443/wx/HL/issues/5665)
|
||||
- **PR**: [#5669](https://git.1814.love:8443/wx/HL/pulls/5669)
|
||||
- **Merge commit**: [c3f0f8fac](https://git.1814.love:8443/wx/HL/commit/c3f0f8fac)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
改派(change)原本只能换车/换司机(同槽位内换),不能增加/删除槽位(调整车辆数量)。wx 要改派时可增删槽位。对齐 #5562 槽位模型(派车时可增删)。按协调台口径走 A 方案:复用 expand/slots 机制,**只增删差异、保留已派**,不新建独立重派流程、不侵入 change 主流程。
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 新增:`POST /admin/fleet/assignments/slots`(增加槽位)
|
||||
|
||||
在 active 用车需求上新增 1 个全程槽位(unassigned 全程占位行覆盖整个服务期),新槽位待确认按初次派车。**保留已有槽位(含已派)完全不动**。
|
||||
|
||||
**请求**(`SlotAddReqVO`):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| requirementId | Long | 是 | 归属需求 ID(active 用车需求) |
|
||||
| vehicleType | String | 是 | 新槽位车型(如 suv/mpv) |
|
||||
| seats | Integer | 是 | 新槽位座位需求(1-60) |
|
||||
| reason | String | 否 | 新增原因(≤200 字,操作日志留痕) |
|
||||
|
||||
**响应**(`SlotAddRespVO`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| slotId | Long(字符串) | 新槽位 ID(assignmentSlotId) |
|
||||
| assignmentId | Long(字符串) | 新槽位全程占位行派单 ID |
|
||||
| requirementId | Long(字符串) | 归属需求 ID |
|
||||
| orderId | Long(字符串) | 归属订单 ID |
|
||||
| fleetItemIndex | Integer | 新槽位需求内项次序 |
|
||||
| retainedSlotIds | Long[](字符串) | 新增后 Step2 快照保留槽位集合 |
|
||||
| snapshotVersion | Long(字符串) | 新增后快照版本(无快照时 null) |
|
||||
|
||||
**错误码**:400 参数校验失败(需求 ID/车型/座位数缺失)/ **605012** 需求无派单行 / **605007** 需求不可派(非 active/已取消/不可派)/ 605008 抢锁超时 / 401 未登录 / 403 非车务角色。
|
||||
|
||||
**行为**:
|
||||
- 新槽位生成 1 条 unassigned 全程占位行(`service_date`/`assignment_group_id` 为 NULL,`start_date~end_date` 覆盖整个服务期),车务在该槽派 1 辆管全程的车(候选/价格日历/校验同初次派车 create)
|
||||
- 保留已有槽位(含 holding/assigned 已派)完全不动——只增差异槽位,不重置/重派
|
||||
- **确认状态**:清最终确认标记(改派需重新冻结方案);保留槽位确认状态不变
|
||||
- Step2 快照 retained 追加 + 版本递增 + digest 重算;FleetBoardChangedEvent + 操作日志 SLOT_ADDED
|
||||
|
||||
### 复用:`DELETE /admin/fleet/assignments/slots/{slotId}`(删除槽位,零改动)
|
||||
|
||||
既有端点(#5550/#5572),删除已派(holding/assigned)槽位时联动取消派单(释放占用+退保意图+取消事件),行程已出发拒绝(605027)。
|
||||
|
||||
## 前端交接(改派页)
|
||||
|
||||
在改派页增加两个操作,**编排现有接口**(后端已就绪):
|
||||
|
||||
1. **【增加槽位】**(多派一辆):
|
||||
- 调 `POST /admin/fleet/assignments/slots`(传 requirementId + vehicleType + seats + reason)
|
||||
- 成功后新槽位出现在看板/改派页,车务对新槽位按初次派车(候选/价格/校验)派 1 辆车
|
||||
2. **【删除槽位】**(少派一辆):
|
||||
- 调既有 `DELETE /admin/fleet/assignments/slots/{slotId}`(有派单时后端联动取消)
|
||||
- 行程已出发的槽位不可删(605027);已完成的槽位不可删(605007)
|
||||
3. **换车/换司机**:仍调既有 `POST /{assignmentId}/change`(不变)
|
||||
|
||||
**展示/交互提示**:增删槽位后改派方案需重新冻结(后端已清最终确认标记);保留槽位的确认状态/车辆/司机不变。座位数校验在派车 create 时进行(座位不足进 warnings)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 新增 3 用例:增槽位成功生成全程行+保留已派+Step2 追加 / 需求无派单行 605012 / 需求不可派抛错
|
||||
- AssignmentServiceTest **394/394 全绿**;fleet spotless:check 通过
|
||||
- fleet verify(mvn-throttle 错峰):3251 测试仅 `ReleaseEOccupancyMysql8033RecoveryTest`(需 MySQL 8.0.33 环境)基线失败,与本案无关
|
||||
- **部署**:hl-fleet-service 双实例 UP(20:24)
|
||||
- **网关验证**:VEHICLE_MANAGER 调 `POST /admin/fleet/assignments/slots`:缺 requirementId → 400(参数校验生效);不存在需求 → 605012(业务校验生效);端点部署成功
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5666"
|
||||
title: "candidates 收费日注释对齐行为(空数组=全部收费预览)+ 子集收费回显口径锁定"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "文档/口径对齐,无行为变更:#5643 起空数组已按全部收费预览,本次仅修正 ApiModelProperty 注释并补子集收费回显测试。矩阵派车司机回填与 ¥0 两症状经排查为前端问题,由协调台另行前端 changelog 分流。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T21:00:00+08:00"
|
||||
---
|
||||
|
||||
# candidates 收费日口径对齐(#5666 后端小加固)
|
||||
|
||||
## 背景
|
||||
|
||||
排查 #5666(矩阵派车司机回填/¥0)时发现 `POST /admin/fleet/assignments/candidates` 的 `chargeableServiceDates` 注释("空数组表示全部免费")与实际行为不一致:#5643 起空数组刻意按"全部服务日收费"预览(防止全免 ¥0 陷阱),且已有测试锁定。注释为 #5643 前残留。
|
||||
|
||||
## 变更内容
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| `AssignmentCandidateReqVO.chargeableServiceDates` 注释 | 对齐行为:不传或空数组均按全部服务日收费预览;明确与创建派单接口语义差异(创建接口空数组=全部免费且需 confirmAllServiceDatesFree+免费原因);子集时仅子集收费、其余免费回显 |
|
||||
| 测试 | 新增 `query_subsetChargeableServiceDates_marksOtherDaysFree`:锁定"子集收费日→仅子集 CALENDAR、其余 FREE ¥0、总价仅含子集"回显口径 |
|
||||
|
||||
**无任何运行时行为变更**(纯文档注解 + 测试)。
|
||||
|
||||
## 前端/调用方注意(既有行为复述,非新变更)
|
||||
|
||||
- candidates:`chargeableServiceDates` 不传/空数组 = 全部收费预览;传子集 = 仅子集收费,其余日 `assignmentPrice=0.00`/`source=FREE`(免费日 `calendarPrice=null`,`calendarPriceMissing` 仅收费日缺价时为 true)。
|
||||
- 创建/改派写接口语义不变:空数组 = 全部免费,须 `confirmAllServiceDatesFree=true` + 免费原因。
|
||||
- 矩阵派车司机回填(suggestedDriverId 消费)与部分收费日传入两个症状的修复见协调台前端 changelog。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- 工单:https://git.1814.love:8443/wx/HL/issues/5666
|
||||
- 后端:wx
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
- `POST /admin/fleet/assignments/candidates` 请求字段 `chargeableServiceDates`:仅 Swagger 文档注释对齐既有行为,类型/约束/行为均无变更。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向:`AssignmentCandidateServiceTest` 49 全过(含新增子集收费用例与 #5643 空数组用例);fleet spotless:check 通过。
|
||||
- 全量:`mvn -pl hl-fleet-service -am verify`(日志见证据目录)。
|
||||
- 网关:口径探针复测 candidates 空数组=全收费、子集=[首日]=首日 CALENDAR 其余 FREE(与锁定口径一致)。
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5666"
|
||||
title: "矩阵派车:司机未回填 + 部分日期派车价¥0(前端两问题)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-07"
|
||||
---
|
||||
|
||||
# 矩阵派车:司机未回填 + 部分日期派车价¥0(前端修复)
|
||||
|
||||
后端契约已实测**全部正常**(#5666 后端侧分流确认),两个症状均为前端问题。
|
||||
|
||||
## 问题1:矩阵派车选车辆后常驻司机没回填
|
||||
- **现象**:矩阵派单从车辆日格(如蒙A-T7777)发起,车辆回填了,但司机栏"待选择"(常驻司机没带出)。
|
||||
- **后端正常**:`candidates` 对该车正确返回 `suggestedDriverId`(常驻司机)+ `RESIDENT_AVAILABLE`,一二轮一致。
|
||||
- **前端根因**:前端自动代入常驻司机**仅在 `vehicleSuggestionIntent` 武装时消费**;矩阵 `openAssign(order,{vehicle})` 非 batchMode + activeDailyPlanCell 分支时不武装该 intent(AssignModal.vue else 分支直接 `selVehicle=preselectVehicle`),导致 `suggestedDriverId` **无人消费** → 司机栏空。
|
||||
- **前端修**:矩阵派车场景也要消费 `suggestedDriverId` 回填常驻司机(常驻司机 RESIDENT_AVAILABLE 时自动带出;被拉黑/不可用时提示并留空)。
|
||||
|
||||
## 问题2:部分日期派车价 ¥0
|
||||
- **现象**:矩阵派车 8/21=¥1000 但 8/22-23=¥0。
|
||||
- **后端正常**:该车型日历价 8/15-31 全量 ¥1000(已配)。
|
||||
- **前端根因**:前端矩阵场景 `chargeableServiceDates` 只传了 [8/21](部分收费日)→ 8/21 收费 1000、8/22-23 按 FREE(0)。
|
||||
- **前端修**:矩阵派车收费日应传全服务日(或按需求),不要只传部分导致其余日期 FREE=¥0。
|
||||
|
||||
## 验收
|
||||
- [ ] 矩阵派车选车辆后常驻司机自动回填(suggestedDriverId 被消费)
|
||||
- [ ] 派车价各服务日正确(不部分¥0)
|
||||
- [ ] 后端契约不变(仅前端修)
|
||||
|
||||
## 关联
|
||||
- 后端工单 #5666(后端契约正常,另做 candidates 空数组口径小加固)
|
||||
- 接口 `POST /admin/fleet/assignments/candidates`(返回 suggestedDriverId/RESIDENT_AVAILABLE)
|
||||
|
||||
## 前端核查(2026-08-08 mmg 实查,HEAD 已无活代码路径,前端零改动)
|
||||
|
||||
逐行核实当前 HEAD(v2.1),两个症状的根因对应的是旧版单派流程,现已由更早提交消除,**前端无需改动**:
|
||||
|
||||
**问题1(矩阵司机未回填)**:矩阵 `openAssign(order,{vehicle})` 活路径走 `AssignModal.vue` `batchMode && activeDailyPlanCell` 分支(3109-3125),`isInitialVehiclePreselection` 命中时经 `restoreSelection({autoSelectResidentDriver:true})` 武装 intent;`useVehicleDriverPicker.js:726` `if (autoSelectResidentDriver===true && selVehicle && !selDriver)` 消费 `suggestedDriverId`(RESIDENT_AVAILABLE 自动带出、不可用/黑名单留空并显 suggestedDriverMessage)。changelog 所述「else 分支直接 selVehicle=preselectVehicle」对矩阵场景不可达。既有修复提交 c6b107b8(8/5)/d97cc730(8/6) 早于本 changelog。已有测试锁定(useVehicleDriverPicker.spec 消费/NONE/占用/拉黑用例)。另:#5662 修复后矩阵发起的改派在抽屉内选车同样经 onPickVehicle 武装 intent 回填常驻司机。
|
||||
|
||||
**问题2(部分日期派车价¥0)**:batch 矩阵流程**不向 candidates 传 chargeableServiceDates**(`useVehicleDriverPicker.js:432-438` batchMode 守卫 + init watcher 在 batch 置空该 ref),后端按全部服务日收费预览,不会产生「子集收费、其余 FREE=¥0」。唯一仍携带部分收费日的是旧改派流程 `restoreLegacyVehicleFeeState`(从原派车快照恢复,保留原派车收费决策的既有语义),不在本单「矩阵派车」口径内,擅自改会改变账单。
|
||||
|
||||
**结论**:前端在 HEAD 已无这两个症状的活代码路径,判 not_required。若仍复现,请提供 HEAD 上的操作序列再排查。
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5667"
|
||||
title: "矩阵派车未派订单清单补 requirementId 关联(修复批量派单缺需求 ID 拦截)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5671 已合并 dev-v3 并部署 TEST(21:03 滚动 DONE)。网关验证 5 项全 PASS:矩阵 unassigned-orders 响应补返 requirementId,订单 26-6179 池/看板详情/order 库三方一致,用池内 requirementId 端到端提交批量派单 200 成功。前端无需配合(选择器本就读取该字段,此前响应缺失导致拿不到)。"
|
||||
updated_at: "2026-08-07"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-07T21:10:00+08:00"
|
||||
---
|
||||
|
||||
# 矩阵派车未派订单清单补 requirementId 关联(修复批量派单缺需求 ID 拦截)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5671](https://git.1814.love:8443/wx/HL/pulls/5671)
|
||||
> **Issue**: [#5667](https://git.1814.love:8443/wx/HL/issues/5667)
|
||||
> **日期**: 2026-08-07
|
||||
> **影响**: 🟢 **缺陷修复**,响应**新增字段**(additive,无破坏性变更)。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5667(P1)wx 实测:矩阵派单(订单 26-6179 林晓芸 8/21-8/23),从矩阵车辆蒙A-T7777日格发起→选好司机(乌云毕力格)→提交批量派单时报「缺少当前用车需求 ID,无法提交批量派单」,卡住无法派单。
|
||||
|
||||
**根因**:矩阵未派订单清单接口(`GET /admin/fleet/matrix/unassigned-orders`)响应未携带 requirementId;前端日格选择器把缺失字段归一成空串,与订单详情合并时空值覆盖了详情返回的有效 requirementId,批量派单提交前的前端校验拿不到需求 ID 被拦。
|
||||
|
||||
## 修复内容
|
||||
|
||||
- `GET /admin/fleet/matrix/unassigned-orders` 响应每行**新增 `requirementId` 字段**(字符串序列化):
|
||||
- 口径与看板一致——优先订单侧当前生效用车需求 ID,订单上下文缺失时兜底派单行快照 requirement_id;
|
||||
- 矩阵日格/未派池发起的批量派单可正确携带当前需求 ID 提交(`POST /admin/fleet/assignments/batch` 本就把 requirementId 作必填,无需改动)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| GET | /admin/fleet/matrix/unassigned-orders | 响应每行新增 requirementId(当前生效用车需求 ID) |
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3262 Tests 业务断言全绿;MatrixServiceTest 65/65(+2 回归:无上下文时兜底派单行快照 requirementId、订单上下文当前需求优先);MatrixControllerTest 4/4;FleetRedLineArchTest 12/12;spotless 通过。ReleaseEMixedBinaryHarnessTest(进程 harness 时序)与 ReconciliationInsuranceCostFenceMysqlTest(Testcontainers 3306 端口被并行会话占用)全量内环境性失败,单独重跑 23/23、2/2 全绿,与本改动无关。
|
||||
- 部署 TEST:21:03 滚动 DONE。
|
||||
- 网关真实复现(订单 26-6179,蒙A-T7777 + 乌云毕力格,8/21-8/23)5 项全 PASS:未派池行携带 requirementId=2085662146464530434(修复前无此字段);全池无缺失;池/看板详情/order 库三方一致;用池内 requirementId 提交批量派单(dailyPlan 逐日、跨常驻确认)200 成功;DB 终态全程单行 holding 覆盖 8/21-23 且 requirement_id 关联正确。
|
||||
|
||||
## 前端配合
|
||||
|
||||
无需配合。矩阵日格发起与未派池拖拽发起的选择器本就读取行 requirementId 并随批量派单提交;后端补齐字段后原链路自然恢复,无新增消费逻辑。
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5592"
|
||||
title: "「发送派单通知」按钮改名「下一步」(避免"系统真发送"歧义)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "dcb5f128"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-08"
|
||||
---
|
||||
|
||||
# 「发送派单通知」按钮改名「下一步」(前端修复)
|
||||
|
||||
## 口径(wx)
|
||||
派单通知是**车务复制话术后微信手动发给司机**(系统不真发送)——保留现设计。
|
||||
但前端按钮「发送派单通知」**有歧义**(让人以为系统会真发给司机),改名避免误解。
|
||||
|
||||
## 前端改动
|
||||
派单待确认页的「发送派单通知」按钮 → 改名 **「下一步」**(它实际是"生成/复制话术→进入下一步登记司机回复",不是系统真发送)。
|
||||
- 文案对齐:该页面是"复制话术→微信发给司机→登记司机回复"流程,按钮不应叫"发送"
|
||||
- 相关提示文案同步(如"确认通知正文后发送给司机"→"确认通知正文后进入下一步")
|
||||
|
||||
## 验收
|
||||
- [ ] 按钮显示「下一步」(不再叫"发送派单通知")
|
||||
- [ ] 流程提示清晰(复制话术手动发,不产生"系统真发送"误解)
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5666"
|
||||
title: "矩阵派车可派订单列表信息补全(展示太简→补全)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端优化"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "45cf94a1"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-08"
|
||||
---
|
||||
|
||||
# 矩阵派车可派订单列表信息补全(前端优化)
|
||||
|
||||
## 问题(wx 实测)
|
||||
矩阵派单「选择当前矩阵可派订单」的**可派订单 list 每条显示信息太少**——基本只有团号+日期+人数,车务在矩阵里挑单派车时参考信息不足,来回点详情才看清。
|
||||
|
||||
## 期望(前端补全展示)
|
||||
矩阵派车的可派订单列表,每条订单卡片**补全显示**(一眼看全,不用点详情):
|
||||
- 团号 + 客户名
|
||||
- 出行日期区间 + 天数
|
||||
- 人数(成人/儿童细分)
|
||||
- **建议车型 + 座位数**(如 商务车×2·7座)
|
||||
- **定制师**
|
||||
- **接送机**(接机/送机机场,如 海拉尔东山机场)
|
||||
- **特殊要求标签**(有 WiFi/儿童座椅等)
|
||||
- 急/加急标记、待派/排车中状态
|
||||
|
||||
## 说明
|
||||
- 数据这些字段后端看板/详情接口已有,前端在矩阵可派订单列表透出即可
|
||||
- 目标是车务在矩阵里挑单派车时不点详情也能判断派哪辆
|
||||
|
||||
## 验收
|
||||
- [ ] 矩阵可派订单列表每条显示:团号/客户/日期/人数/建议车型座位/定制师/接送机/特殊要求/状态
|
||||
- [ ] 不点详情即可判断派车
|
||||
|
||||
## 关联
|
||||
- #5666(矩阵派车司机回填+收费日)
|
||||
|
||||
## 前端交付说明(hl-admin@45cf94a1)
|
||||
可派订单卡片已补全:天数、建议车型(看板 `requiredVehicles` 分组带辆数座位,矩阵回退车型大类)、定制师、接送机(海拉尔布尔/机场原文)、特殊要求标签、人数成人/儿童细分。顺带修复急单判定死代码(旧逻辑只认 `urgent`,真实值 `unassigned_urgent` 永不命中,改按 `includes('urgent')`,加急文案与 VehicleGantt 同契约)。
|
||||
|
||||
**待后端补字段(矩阵未派清单 VO `GET /admin/fleet/matrix/unassigned-orders`)**:该接口当前缺 `days`、`adultCount/childCount` 细分、`requiredVehicles[].seats`、`consultantName`、`pickupAt/dropoffAt` 机场文案、`specialTags`。前端按「有哪个透哪个、缺失行整体不渲染」处理,未硬编;这些字段在**看板自拉路径**(`/fleet/board/orders`,drivers/vehicles 页入口)已完整展示。若矩阵场景也需全量,建议后端参照 60_4936 grid assignments 口径给 `MatrixUnassignedOrderVO` 补上述字段。
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5674"
|
||||
title: "核单导游/摄影费用 4 接口请求解析失败错误码 584125 细化为 584128 并透出具体字段原因"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5677 已合并 dev-v3;请求体解析失败/Bean 校验失败错误码由 584125 细化为 584128,message 透出具体字段原因;请求体为空仍返回 584125 不变。成功路径入参/出参字段不变。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】核单导游/摄影费用解析失败错误码细化为 584128 并透出字段原因(#5674)
|
||||
|
||||
> **PR**: [#5677](https://git.1814.love:8443/wx/HL/pulls/5677) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单「导游费用」「摄影费用」两个页签共 4 个写接口,此前在请求体 JSON 解析失败或 Bean 校验失败时统一返回 `584125 导游或摄影费用请求字段不合法`,message 不带具体字段原因,前端和核单人员无法自助定位是哪个字段不合法。本次将解析/校验失败细化为新错误码 `584128`,message 透出具体字段路径与原因;成功路径的入参/出参字段、枚举值、行为全部不变。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 保存导游费用 | PUT | `/v3/admin/order/{orderId}/settlement/guide-fees` | 错误码语义修改 | 若对 `584125` 做过特判需知悉;建议直接展示后端 message |
|
||||
| 2 | 确认导游费用 | PUT | `/v3/admin/order/{orderId}/settlement/guide-fees/confirm` | 错误码语义修改 | 同上 |
|
||||
| 3 | 保存摄影费用 | PUT | `/v3/admin/order/{orderId}/settlement/photographer-fees` | 错误码语义修改 | 同上 |
|
||||
| 4 | 确认摄影费用 | PUT | `/v3/admin/order/{orderId}/settlement/photographer-fees/confirm` | 错误码语义修改 | 同上 |
|
||||
|
||||
> 成功路径的请求体字段、响应结构、状态码 200 行为均未变化,不属于本次契约变更范围。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
4 个接口的协议层约定一致,统一说明:
|
||||
|
||||
- **使用场景**:核单人员在订单核单页维护/确认导游费用、摄影费用明细。
|
||||
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||
- **幂等性**:保存类接口按订单维度覆盖式保存,重复提交相同载荷结果一致;确认接口带 `expectedSourceFingerprint` 乐观校验,重放安全。
|
||||
- **限流**:未声明接口专属限流。
|
||||
- **方法/路径**:
|
||||
- `PUT /v3/admin/order/{orderId}/settlement/guide-fees`
|
||||
- `PUT /v3/admin/order/{orderId}/settlement/guide-fees/confirm`
|
||||
- `PUT /v3/admin/order/{orderId}/settlement/photographer-fees`
|
||||
- `PUT /v3/admin/order/{orderId}/settlement/photographer-fees/confirm`
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
成功路径入参字段本次**无变化**(保存接口为费用明细列表请求体;确认接口在保存基础上多 `expectedSourceFingerprint` 字段,须为 64 位十六进制字符串)。与本次变更相关的入参行为:
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 请求体含未知字段(如把出参字段 `settlementConfirmStatus` 回传进请求体) | `584125`,无具体原因 | `584128`,message 指出不支持的字段名 |
|
||||
| 请求体 JSON 反序列化失败(类型不匹配/格式错误) | `584125`,无具体原因 | `584128`,message 透出解析原因 |
|
||||
| 字段 Bean 校验失败(如 `expectedSourceFingerprint` 不合 Pattern) | `584125`,无具体字段 | `584128`,message 透出 `字段路径: 校验提示` |
|
||||
| 请求体为空 / null | `584125` | `584125`(不变) |
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
成功响应出参字段本次**无变化**。失败响应统一为:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | Integer | 业务错误码,本次新增透出 `584128` |
|
||||
| `message` | String | 错误描述,`584128` 时含具体字段原因 |
|
||||
| `data` | Object | 恒为 `null` |
|
||||
| `success` | Boolean | 恒为 `false` |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次不涉及枚举或字典的新增、删除、改值、改语义。`settlementConfirmStatus` 等既有枚举取值不变。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 | 本次变化 |
|
||||
|---:|---|---|---|
|
||||
| `584125` | 导游或摄影费用请求字段不合法 | 请求体为空 / null | 不变(仅剩该场景触发) |
|
||||
| `584128` | 导游或摄影费用请求字段不合法:{具体原因} | 请求体含未知字段 / 反序列化失败 / Bean 校验失败 | **新增**,替代原 `584125` 的解析/校验场景 |
|
||||
|
||||
`584128` message 构成为固定前缀 + 具体原因:
|
||||
|
||||
- 未知字段:`导游或摄影费用请求字段不合法:导游费用明细不支持字段: settlementConfirmStatus`(摄影接口为「摄影费用明细不支持字段: ...」)
|
||||
- 校验失败:`导游或摄影费用请求字段不合法:expectedSourceFingerprint: 须为 64 位十六进制`
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(保存导游费用,入参出参与改前一致)
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"fees": [
|
||||
{
|
||||
"feeDate": "2026-08-08",
|
||||
"guideName": "张三",
|
||||
"quantity": 1,
|
||||
"unitPrice": 300.00,
|
||||
"settlementAmount": 300.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
|
||||
```
|
||||
|
||||
### 8.2 边界(请求体为空,错误码不变仍为 584125)
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
null
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 584125, "message": "导游或摄影费用请求字段不合法", "data": null, "success": false}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(新增 584128,透出具体字段原因)
|
||||
|
||||
场景 A:把出参字段 `settlementConfirmStatus` 误回传进请求体(未知字段)。
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"fees": [
|
||||
{
|
||||
"feeDate": "2026-08-08",
|
||||
"guideName": "张三",
|
||||
"quantity": 1,
|
||||
"unitPrice": 300.00,
|
||||
"settlementAmount": 300.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"settlementConfirmStatus": "UNCONFIRMED"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 584128, "message": "导游或摄影费用请求字段不合法:导游费用明细不支持字段: settlementConfirmStatus", "data": null, "success": false}
|
||||
```
|
||||
|
||||
场景 B:确认接口指纹字段不合 Pattern(Bean 校验失败)。
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees/confirm
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedSourceFingerprint": "abc123",
|
||||
"fees": []
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 584128, "message": "导游或摄影费用请求字段不合法:expectedSourceFingerprint: 须为 64 位十六进制", "data": null, "success": false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 4 个接口的请求体均接受完整费用明细载荷,成功路径行为与改前完全一致。
|
||||
- ✅ 请求体为空 / null 仍返回 `584125`,该场景未变。
|
||||
- ⚠️ 请求体含未知字段会被明确拒绝并返回 `584128`;不要把查询/详情响应里的出参字段(如 `settlementConfirmStatus`)原样 echo 回保存/确认请求体。
|
||||
- ❌ `expectedSourceFingerprint` 必须是 64 位十六进制字符串,短串或非法字符会被 `584128` 拦截。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
成功路径入参/出参字段**无变化**,略。
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 请求体含未知字段 / 反序列化失败 | `584125 导游或摄影费用请求字段不合法` | `584128 导游或摄影费用请求字段不合法:{具体原因}` |
|
||||
| Bean 校验失败(字段格式/取值不合法) | `584125`,无具体字段 | `584128`,message 透出 `{字段路径}: {校验提示}` |
|
||||
| 请求体为空 / null | `584125` | `584125`(不变) |
|
||||
| 成功保存 / 确认 | `code=200` | `code=200`(不变) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:弱破坏。仅错误码语义变化——原来命中 `584125` 的解析/校验失败场景现在返回 `584128`;若前端对 `584125` 做了特判(专属 toast 文案 / 分支逻辑),这些场景将不再命中。
|
||||
- **前端是否必须同步上线**:不强制。建议直接展示后端 `message`(已含具体字段原因,用户可自助定位),无需为 `584128` 再写翻译表。
|
||||
- **上线顺序边界**:无顺序要求,新旧前端均可工作。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚后解析/校验失败恢复返回 `584125`,message 不再含具体原因;前端若已改为直显 message,不受影响。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 若前端此前对 `584125` 写过特判文案(如「请检查导游费用字段」),请知悉解析/校验失败场景的错误码已变为 `584128`;保留 `584125` 的特判仍覆盖「请求体为空」场景。
|
||||
- 推荐做法:失败 toast 直接展示后端 `message`,不要按错误码硬编码文案。
|
||||
- 排查用户报错时,`584128` 的 message 即包含具体字段路径与原因,可直接据此定位,无需找后端查日志。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5674](https://git.1814.love:8443/wx/HL/issues/5674)
|
||||
- **PR**: [#5677](https://git.1814.love:8443/wx/HL/pulls/5677)
|
||||
- **Commit**: [146cc2ae48](https://git.1814.love:8443/wx/HL/commit/146cc2ae48)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst)
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5675"
|
||||
title: "行程单在派单消息预览时即生成可见(不等派车成功后)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5685/#5687/#5688 已合并 dev-v3 并部署 TEST(hl-fleet-service 双实例)。预览/HOLD 通知即生成可点行程短链,短码跨确认稳定,确认前行程单已可打开。前端交接见下(预览定位参数与降级文案口径)。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T11:20:00+08:00"
|
||||
---
|
||||
|
||||
# 行程单在派单消息预览时即生成可见(不等派车成功后)
|
||||
|
||||
> 后端完成:PR [#5685](https://git.1814.love:8443/wx/HL/pulls/5685) / [#5687](https://git.1814.love:8443/wx/HL/pulls/5687) / [#5688](https://git.1814.love:8443/wx/HL/pulls/5688) 已合并 dev-v3 并部署 TEST,网关端到端验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5675](https://git.1814.love:8443/wx/HL/issues/5675)
|
||||
- **PR**: #5685(主)/ #5687(单行组定位配套)/ #5688(中间态容错配套)
|
||||
- **Merge commit**: [cb44851c9](https://git.1814.love:8443/wx/HL/commit/cb44851c9)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
派单消息预览里行程详情显示「行程链接在派单成功后自动生成」——预览时看不到行程单/链接,司机收到的 HOLD 待确认通知也是「行程确认后发送」占位文案。wx 要预览/通知时就有可点行程单链接。
|
||||
|
||||
## 变更说明(无接口契约变化,行为升级)
|
||||
|
||||
### 行为变化
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 派单消息预览(组可定位) | 「行程链接在派单成功后自动生成」占位 | **真实可点短链** `https://…/s/{code}` |
|
||||
| HOLD 待确认通知(司机收到) | 「行程确认后发送」占位 | **真实可点短链**,确认前即可打开行程单 |
|
||||
| 司机确认后点同一短链 | —(此前无链接) | 短码不变,仍可打开(代际升级兼容,按当前身份重签) |
|
||||
| 派单创建前的纯模板预览(无派单上下文) | 占位文案 | 不变(此时车辆/司机未定,无行程可链) |
|
||||
| 增删槽位/重新确认窗口内的预览 | 500(缺陷) | 「行程链接暂不可用,请联系车务确认」降级文案 |
|
||||
|
||||
### 后端实现(hl-fleet-service)
|
||||
|
||||
1. **HOLD 待确认组可签发行程 token**(`ItineraryTokenService`):全组 HOLDING 且未冻结 dispatchPlan 时豁免 generation 非空/finalized=1 校验,代际用稳定哨兵 `PENDING(0L)`;混合状态未冻结组仍拒绝(失败关闭)。
|
||||
2. **短码跨确认稳定**(`ItineraryShortLinkService.resolveShortLink`):同组同业务(车/司机/槽位/服务日/旅客快照全同)仅代际升级(PENDING→真实 generation)时放行,按当前身份重签 H5 token;换车/换司机/改期/取消仍失败关闭。
|
||||
3. **HOLD 态 H5 可渲染**(`ItineraryH5Service`):全组 HOLDING + PENDING 代际 token 放行。
|
||||
4. **HOLD 通知冻结真链接**(`AssignmentHoldNotificationSnapshotFactory`):不再降级占位(#5461 口径演进)。
|
||||
5. **单行组/全程行定位适配**(`MessageTemplateRenderService`,#5675 配套):effective 组 id(group_id NULL→assignment_id,#5589 口径)+ 全程行 tripEndDate 取 endDate + 身份未就绪中间态容错降级。
|
||||
6. **全程行 serviceDates 展开**(`effectiveServiceDates`):service_date NULL 的全程行按服务期逐日展开,签发与 H5 校验同口径。
|
||||
|
||||
## 前端交接
|
||||
|
||||
1. **预览要拿真链接,请求需能定位派车组**(三选一):传 `assignmentGroupId`;或传 `orderId + vehicleId + driverId`(能唯一命中 active 组时自动定位)。都不传的纯模板预览仍显示占位文案(正常——无派单上下文)。
|
||||
2. **降级文案口径**:`itinerary.url` 可能返回两种文案——「行程链接在派单成功后自动生成」(无派单上下文)/「行程链接暂不可用,请联系车务确认」(组在重新确认窗口等中间态)。前端按文本展示即可,无需特殊处理。
|
||||
3. **通知文案**:HOLD 待确认通知中行程详情现在直接是可点短链,司机确认前后点同一链接均可见行程单。
|
||||
4. 短链有效期=行程结束 +7 天(既有口径不变)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **单测**:token 层 +3(HOLD 未冻结可签发/混合状态仍拒绝/全程行展开)、短链层 +2(代际升级放行/换司机失败关闭)、H5 层 +2(HOLD 渲染可见/finalize 后旧 token 失效)、预览链 +2(单行组定位/中间态容错)、快照工厂更新为 HOLD 签发真链接;messagetemplate+h5 **396/396**、assignment 相关 **444/444** 全绿;spotless 通过
|
||||
- **fleet verify**(mvn-throttle 错峰):仅基线/环境项失败(ReleaseEOccupancyMysql8033 等 3 个需 mysql:8.0.33 Testcontainers;ReleaseEMixedBinaryHarness flaky 复跑全绿),与本案无关
|
||||
- **部署**:hl-fleet-service 双实例 UP(11:16)
|
||||
- **网关端到端**(TEST,VEHICLE_MANAGER):
|
||||
- HOLD 待确认派单(全程槽单行组)render → 返回真短链 `https://web.test.1814.love:9443/s/txU62dA`(非占位)✓
|
||||
- 同组幂等重放复用同码 ✓
|
||||
- 短链 302 → H5 长链(token payload:PENDING 哨兵代际 + 全程行展开 3 个服务日)✓
|
||||
- GET H5 长链 → 200 行程单 JSON(主题/日期/服务日/客户全渲染)——**确认前行程单即可见** ✓
|
||||
- 中间态组(增删槽位重新确认窗口)render → 降级文案,不再 500 ✓
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5676"
|
||||
title: "改派支持按天改派(排车表逐行改派按钮)+ 统一选择修复交接"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "3df533a5"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5686 合并 dev-v3(4e993920440cad0b6105a5f7682d6b33d1444bad)并部署 TEST,网关验证按天改派只改指定天+统一选择单次全改均通过。统一选择「只改第一天」bug 实证为前端逐日循环互覆盖(后端无 bug),修复=前端改单次调 change。待前端接入按天改派+统一选择单次调用。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T11:10:00+08:00"
|
||||
---
|
||||
|
||||
# 改派支持按天改派 + 统一选择修复交接(#5676)
|
||||
|
||||
## 需求
|
||||
|
||||
1. 排车表**每行(每个服务日)**有【改派】按钮——单独改某天的车/司机(按天改派,每天可不同)
|
||||
2. 统一选择按钮=整体改派(所有日期),保留
|
||||
3. wx 实测补充:统一选择**现在不好使,只能改第一天**(选的车/司机只应用到第一天)
|
||||
|
||||
## 统一选择「只改第一天」bug 根因(后端无 bug,前端修复)
|
||||
|
||||
**根因 = 前端「统一选择」逐日循环调 change 互相覆盖**,非后端问题:
|
||||
|
||||
- 改派操作日志实证:order `2080600006514872322` 在 2026-08-06 17:25 对同一槽位**连续两次调 change**(`effectiveDate=2026-08-29` 和 `2026-08-28` 分开、每天一行)——前端对「统一选择」**逐日循环**(每天调一次)。
|
||||
- change 语义 = **「effectiveDate 起连续后缀」**:eff=8/29 改 8/29 起后缀,eff=8/28 又改 8/28 起后缀(覆盖 8/29)…… 逐日循环互相覆盖 → 只有部分天生效(表现为「只改第一天」)。
|
||||
- **后端单次 change 正确全改**:单测证明逐日槽位 3 天,单次 `change(effectiveDate=第一天, 不传 serviceDates)` → 取消整段后缀 + 重建 3 行全新车/司机。
|
||||
|
||||
## 修改接口:`POST /admin/fleet/assignments/{assignmentId}/change`
|
||||
|
||||
新增**可选**字段 `serviceDates`(`List<LocalDate>`,其余字段不变):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `serviceDates` | **按天改派限定的服务日期集**。非空=按天改派模式:只改这些日期的行(其余天保留);**空/不传=现状**(effectiveDate 起连续后缀整体改派)。 |
|
||||
|
||||
### 前端交接(两种改派的正确调用方式)
|
||||
|
||||
**1. 统一选择(整体改派)——修复点**:
|
||||
```json
|
||||
{
|
||||
"effectiveDate": "<第一天>",
|
||||
"newVehicleId": ..., "newDriverId": ...,
|
||||
"holdMode": 0, "reason": "...", "requestId": "<唯一>"
|
||||
}
|
||||
```
|
||||
- **单次调用**,`effectiveDate`=第一天,**不传 `serviceDates`** → 一次整体改派所有天。
|
||||
- **不要再逐日循环调 change**(会互相覆盖)。
|
||||
- 每次操作生成唯一 `requestId`(幂等)。
|
||||
|
||||
**2. 按天改派(新增,每行改派按钮)**:
|
||||
```json
|
||||
{
|
||||
"effectiveDate": "<该天>",
|
||||
"serviceDates": ["<该天>"],
|
||||
"newVehicleId": ..., "newDriverId": ...,
|
||||
"holdMode": 0, "reason": "...", "requestId": "<唯一>"
|
||||
}
|
||||
```
|
||||
- 某天【改派】→ `effectiveDate`=该天 + `serviceDates=[该天]` → **只改该天,其他天不变**。
|
||||
- 每天可不同车/司机(逐行各调一次,每天独立 `requestId`)。
|
||||
- `assignmentId` 路径参数=该槽位任一派单行 ID。
|
||||
|
||||
### 行为说明
|
||||
|
||||
- **校验按天**:按天改派的档期/占用/常驻/保险校验只针对 `serviceDates` 指定的天(某天换车/司机只需该天资源空闲,不影响其他天)。
|
||||
- **逐日价格**:按天改派后逐日车费按各天价格日历/车型分别算(`resolveDaily` 按子集日期逐日取价)。
|
||||
- **兼容**:`serviceDates` 不传时行为与现状完全一致(统一选择整体改派)。
|
||||
|
||||
## 验证
|
||||
|
||||
- AssignmentServiceTest **397/397**(新增 3:按天单天只改该天/按天非连续子集其余保留/统一选择单次全改对照);dev-v3 全量 verify 3273/0F/0E。
|
||||
- 网关实证(TEST,槽位 `344052387587166208`,order `2085662570479304706`,8/21-8/23 三天):
|
||||
- **按天改派**:`change(serviceDates=[8/22])` 换车 → 只 8/22 变,8/21/8/23 保留原车;
|
||||
- **统一选择单次**:`change(effectiveDate=8/21, 不传 serviceDates)` 换车 → 3 天全改。
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5676"
|
||||
title: "统一选择只改第一天(前端改单次调 change=整体改派)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "f4223996"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-08"
|
||||
---
|
||||
|
||||
# 统一选择只改第一天(前端修复)
|
||||
|
||||
## 问题(wx 实测)
|
||||
派车"统一选择车辆/司机"选的车/司机**只应用到第一天**,没应用到所有出行日(该整体改派所有日期)。
|
||||
|
||||
## 根因(#5676 后端实证,责任端=前端)
|
||||
前端"统一选择"**逐日循环调 change**(每天一行,effectiveDate 分别 8/28、8/29...)——change 语义是"effectiveDate 起连续后缀",逐日循环互相覆盖→只部分生效。
|
||||
**后端单次 change(effectiveDate=第一天,不传 serviceDates)正确全改所有天**(取消整段后缀+重建),后端无 bug。
|
||||
|
||||
## 前端修复
|
||||
"统一选择"(整体改派)改为**单次调 change:`effectiveDate=第一天`**(不传 serviceDates)——即整段改派所有出行日。不要逐日循环调。
|
||||
- 按天改派(#5676 新增 serviceDates)是另一功能(按天单独改),保留
|
||||
- 统一选择(整体)= 单次 change effectiveDate=第一天
|
||||
|
||||
## 补充(wx 明确矩阵场景)
|
||||
**矩阵派单**:从矩阵某一行(车辆行)派车/司机时,**所有行程日对应槽位都该是这行的车+司机**(统一应用所有行程日,不只第一天)。与"统一选择"同一口径——矩阵一行派 = 单次 change effectiveDate=第一天(整段改派所有日)。
|
||||
|
||||
## 验收
|
||||
- [ ] 统一选择选的车/司机应用到所有出行日(不只第一天)
|
||||
- [ ] **矩阵派单从一行派车→所有行程日对应槽位=这行的车+司机**
|
||||
- [ ] 按天改派不受影响
|
||||
|
||||
## 关联
|
||||
- #5676(按天改派已实现;统一选择修复=本前端任务)
|
||||
|
||||
## 前端交付与矩阵场景实证(hl-admin@f4223996 + 3df533a5)
|
||||
|
||||
统一选择「只改第一天」已修:根因为逐日/多段槽位下统一选择只锚定被选执行段(effectiveDate=段首日、费用只本段),新增 `mergeSlotChangeSegments`(display.js:263)合并同槽全部有效执行段为整段改派目标,单次 change effectiveDate=整段首日、不发按天 serviceDates。
|
||||
|
||||
**矩阵场景补充实证**:矩阵派单从一行派车走 `matrix/index.vue:442 onDropAssign` → `openAssign(order, { vehicle, mode })`,复用 AssignModal 统一 assign/change 主流程,与统一选择同一单次 change 逻辑——一行派车即整段(所有行程日对应槽位=这行车+司机),不只第一天。按天改派(3df533a5)为独立入口,不受影响。
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5679"
|
||||
title: "确认执行弹一堆重复「已变化」术语提示(合并+去抖+友好化)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "701233df"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-08"
|
||||
---
|
||||
|
||||
# 确认执行弹一堆重复「已变化」术语提示(前端修复)
|
||||
|
||||
## 问题(wx 实测)
|
||||
车务确认司机接单/确认执行时,弹出一堆重复术语提示:「订单与派单基线已变化」「当前派单已变化」「可动作已变化」……信息过载、困惑。
|
||||
|
||||
## 根因(#5679 后端排查,责任端=前端)
|
||||
三条提示全是 hl-ui 前端组装:`AssignModal.vue:3065-3079` 的 watch 对 SSE 刷新做基线 diff(打开时缓存快照 vs 刷新数据),每类差异在 `showFleetBaselineDifferenceDialog`(baselineDifference.js:88-127)堆一张术语卡片,且每次带差异的刷新重复弹;司机确认接单本身导致 assignmentStatus/actionCodes 合法流转,确认执行场景必命中 ACTIVE_ASSIGNMENT+CAPABILITY 两段 diff(即"误弹")。后端基线字段无噪声、契约正常,无可改项。
|
||||
|
||||
## 前端修复(全在前端)
|
||||
1. **合并为单条友好提示**(如"派车信息已更新,请核对后确认"),不堆多张术语卡片
|
||||
2. **去抖**——单次刷新只弹一次(不重复弹)
|
||||
3. **预期变化豁免**——司机确认接单等合法流转不弹"已变化"
|
||||
4. **去术语文案**——去掉"基线/快照/动作"等技术术语,改车务能懂的话
|
||||
|
||||
## 验收
|
||||
- [ ] 确认执行只弹一个简洁友好提示(不堆多个术语卡片)
|
||||
- [ ] 单次刷新只弹一次;合法流转不误弹
|
||||
- [ ] 文案友好(发生啥+怎么办)
|
||||
|
||||
## 关联
|
||||
- #5679(后端排查,契约正常);模式同 #5666(前端修复)
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5681"
|
||||
title: "多槽位确认执行页逐日车费覆盖全程+HOLD 整组确认快照代际修复"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5690 已合并 dev-v3 并部署 TEST(12:52 滚动 DONE)。网关双探针 ALL PASS:自建 2 车需求订单 HOLD 派车→行级冻结代际→详情整组快照代际非空→两组逐日车费各覆盖全部出行日→司机确认→需求级整组确认 200 confirmed=true。前端无需配合(字段与守卫均沿用既有契约,此前是后端数据缺失)。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T13:00:00+08:00"
|
||||
---
|
||||
|
||||
# 多槽位确认执行页逐日车费覆盖全程+HOLD 整组确认快照代际修复
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: [#5690](https://git.1814.love:8443/wx/HL/pulls/5690)
|
||||
> **Issue**: [#5681](https://git.1814.love:8443/wx/HL/issues/5681)
|
||||
> **日期**: 2026-08-08
|
||||
> **影响**: 🟢 **缺陷修复**,无契约结构变更(既有字段数据口径修复)。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5681(P1)wx 实测:订单 26-0703(4 人 2 车 2 司机,8/11-8/14)确认执行页:
|
||||
1. 逐日车费只显示 1 车 1 天(8/11)——应列出每个车每个出行日车费;
|
||||
2. 报「详情缺少整组确认快照,请刷新后重试」——多槽位无法确认执行。
|
||||
|
||||
**根因(双缺陷)**:
|
||||
- 全程单行(service_date NULL)派车组聚合视图的 `dailyVehicleFees`/`chargeableServiceDates`/`freeServiceDates` 由物理行 1:1 生成,`serviceDateOf` 对全程行只取 startDate,逐日车费只产出 1 天;
|
||||
- #5400 重构时 dailyPlan 模式 HOLD 创建只退休旧代际标记、不再冻结新方案代际(items 模式两种模式都会冻结),导致 HOLD 单 `dispatchPlanGeneration` 恒为 NULL,需求级整组确认的代际栅栏与前端整组快照守卫永远拿不到代际。
|
||||
|
||||
## 修复内容(内部,无接口/字段结构变化)
|
||||
|
||||
- `completeDailyPlanCreation`:HOLD 逐日方案创建同样冻结方案代际(finalized=1 + generation,与 items 模式一致);DAILY_V3 最终快照事件仍只在 DIRECT 创建时发布,HOLD 待最终确认后发布(#5400 意图不变)。
|
||||
- `aggregateGroupRows`:全程行聚合视图逐日费用/收费日/免费日按 startDate..endDate 逐日展开。
|
||||
- 兼容:#5675 行程短链哨兵路径只服务存量无代际 HOLD 行,有代际行走真实代际校验,双向兼容;单车/逐日切片行为不变(回归测试钉住)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| GET | /admin/fleet/board/orders/:orderId | activeAssignments[].dailyVehicleFees 全程行覆盖全部出行日;driverConfirmationSummary.dispatchPlanGeneration 对 HOLD 单正常返回 |
|
||||
| POST | /admin/fleet/assignments/requirements/:requirementId/confirm | 多槽位 HOLD 整组确认可正常携带代际提交(修复前永远拿不到代际) |
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 全量 `mvn -pl hl-fleet-service -am verify`:3283 Tests 0 失败;新增 4 回归(HOLD 冻结代际不发快照事件/DIRECT 对照发事件/全程行逐日车费展开 4 天/逐日切片不变);spotless 通过。ReleaseEMixedBinaryHarnessTest(进程时序)与两个 Testcontainers 类(3306 端口并行争用)全量内环境性失败,单独重跑 23/23、2/2、10/10 全绿,与本改动无关。
|
||||
- 部署 TEST:12:52 滚动 DONE。
|
||||
- 网关端到端(自建 2 车需求订单 2085938395749515266,蒙C01E01+孟和/蒙A-S6666+苏和巴特,8/22-8/24):HOLD 派车 → 行级 finalized=1+代际一致 → 详情整组快照代际非空 → 两组逐日车费各 3 天 → 两组司机确认 → 需求级整组确认 **200 confirmed=true** → DB 双行 assigned。单车对照(26-6179)逐日车费 3 天正常。
|
||||
|
||||
## 前端配合
|
||||
|
||||
无需配合。确认执行页的槽位切换、整组快照守卫(requirementId/version/sha/代际/groups)均为既有契约,此前是后端数据缺失导致守卫不通过;修复后原链路自然恢复。
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin)
|
||||
|
||||
逐行核实确认 not_required 属实,零代码改动:整组快照守卫在 `src/views/fleet/board/composables/useAssignFlow.js:166-169` 与 `:518-531`,前端直读 `driverConfirmationSummary.dispatchPlanGeneration`、空则抛「详情缺少整组确认快照,请刷新后重试」——正是本单后端修复的守卫,前端是正确消费方,此前因 HOLD 单代际恒 NULL 不通过,后端补冻结代际后原链路自然恢复;逐日车费前端仅逐日渲染 `activeAssignments[].dailyVehicleFees`,后端全程行按 startDate..endDate 展开后自动正确。前端无任何针对该缺陷的特判或 workaround 需拆除。
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5689"
|
||||
title: "派单通知模板加载失败修复(短链业务不匹配降级 + 全程行结束日口径统一 + 拆箱 NPE)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5692+#5694 已合并 dev-v3(089d1a7e0)并部署 TEST(hl-fleet-service 双实例 UP)。根因两层:①冻结链/预览链对全程行(service_date NULL)结束日口径不一致(startDate vs endDate)→ 短链 tripEndDate 漂移 → matchesBusiness 不匹配抛 605308 冒泡;②确认执行中间态(ASSIGNED+dispatchPlan 未冻结)ItineraryTokenService 三元 long?Long 拆箱 NPE 500。修复:预览链 605308/身份未就绪统一降级「行程链接暂不可用,请联系车务确认」不再 500 阻塞整条模板渲染;SnapshotFactory endDateOf 口径统一;三元装箱 null 安全。网关验证 26-8778(实证单)6/6:render 200 正文完整、render-batch item success=true。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T12:40:00+08:00"
|
||||
---
|
||||
|
||||
# 派单通知模板加载失败修复(#5689)
|
||||
|
||||
## 背景
|
||||
|
||||
派单页消息预览区「通知模板加载失败,请重试」+「派车前先选择并成功渲染通知模板」(实证 26-8778)。
|
||||
|
||||
## 根因(网关 + 库表 + 日志定位)
|
||||
|
||||
1. **短链业务不匹配 605308**:`MessageTemplateRenderService`(预览链)对全程行(service_date NULL)结束日取 `endDate`(#5562 口径),而 `AssignmentHoldNotificationSnapshotFactory`(冻结链)取 `startDate`——同一派车组两条链算出不同 tripEndDate → `createOrGet` 命中已存在短链但 `matchesBusiness(tripEndDate)` 不匹配 → `requireSameBusiness` 抛 605308 冒泡 → 渲染接口失败。
|
||||
2. **拆箱 NPE 500**:确认执行后派车行处于 ASSIGNED + dispatchPlan 未冻结(finalized=0、generation=null)中间态,`ItineraryTokenService` 三元 `holdPendingIdentity ? long常量 : Long字段` 被强制按 long 求值 → false 分支对 null 拆箱 → NPE 500(在既有 fail-closed 检查之前)。
|
||||
3. render-batch 单项失败被容错吞掉(success=false)→ 前端只看到「加载失败」更隐蔽。
|
||||
|
||||
## 修复(PR #5692 + #5694)
|
||||
|
||||
1. **预览容错**:`resolveItineraryShortLink` 捕获 BusinessException 605308 + IllegalStateException 身份未就绪(#5675 白名单)→ 统一降级为「行程链接暂不可用,请联系车务确认」,不再 500 阻塞整条模板渲染;短链配置/落库等非业务异常仍 fail-fast(#5461 不变)。通知发送链(SnapshotFactory/EffectService)不走本方法,保持 fail-closed。
|
||||
2. **口径统一**:`AssignmentHoldNotificationSnapshotFactory` 新增 `endDateOf`(serviceDate NULL → endDate),冻结链与预览链 tripEndDate 一致,根除短链日期漂移。
|
||||
3. **NPE 修复**:`ItineraryTokenService` 三元改 `Long.valueOf(PENDING_DISPATCH_PLAN_GENERATION)`,null 不拆箱 → 正常走「派车组身份不完整或已失效」fail-closed → 预览降级。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 模板渲染接口不再因短链中间态/业务不匹配而 500;预览区模板正常加载渲染,行程链接不可用时显示人工介入文案(而非加载失败)。
|
||||
- 派车组确认执行后(dispatchPlan 未冻结窗口)预览行程链接显示「行程链接暂不可用,请联系车务确认」,dispatchPlan 冻结后自动恢复可点链接。
|
||||
- 错误码 605308/600801/600800 口径不变;非业务异常(如短链配置缺失)仍按原样上抛。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 预览区「通知模板加载失败,请重试」为后端 500 的兜底文案;本次修复后正常场景不再出现。若仍出现,请按响应 code/message 反馈后端(600800 模板变量缺失 / 600801 模板不存在 / 605308 短链业务不匹配)。
|
||||
- 无需字段适配;渲染正文中的行程链接可能为「行程链接暂不可用,请联系车务确认」占位文案(属正常业务态)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向 64/64(render 605308 降级、非业务异常上抛、全程行冻结 endDate 口径、ASSIGNED 中间态不 NPE、预览降级);spotless:check 通过。
|
||||
- 全量 verify 3287+(另一会话并行占用 3306 测试容器导致 MySQL8033 固定端口类竞争失败——基线脆弱项,主工作区干净基线同失败已证;错峰重跑补录)。
|
||||
- 网关实测(TEST,26-8778 实证单)6/6:单渲染 200 + 正文完整(呼伦旅行—订车单/请确认以下订车信息)+ itinerary.url 降级文案 + 有效期正常计算;render-batch 200 + item success=true(修复前 NPE 500 / success=false)。
|
||||
- 部署:hl-fleet-service dev-v3=089d1a7e0 双实例 UP(PR #5692+#5694)。
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5691"
|
||||
title: "改派页确认改单日期对所有出行日回显(修复只展示1天)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5696 合并 dev-v3(007dc20ad86088c22604ffed7d79e20df1318a51)并部署 TEST,网关验证改派页确认改单日期对所有出行日回显。待前端确认改派页用 activeAssignments[].serviceDates 渲染逐日。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T13:25:00+08:00"
|
||||
---
|
||||
|
||||
# 改派页确认改单日期对所有出行日回显(#5691)
|
||||
|
||||
## Bug(wx 实测)
|
||||
|
||||
26-8778 周梦洁(8/17-8/20 4 天行程),改派页「确认改单日期与逐日费用」**只展示 8月17日 一天**——4 天行程只显示 1 天,无法逐日看/改全部。
|
||||
|
||||
## 根因
|
||||
|
||||
改派页「确认改单日期」用 `GET /admin/fleet/board/orders/{orderId}` 返回的 **`activeAssignments[].serviceDates`** 渲染。该单是**已派全程行**(`service_date=NULL`、`start=8/17 end=8/20`、`dispatch_plan_finalized=0` 未按逐日敲定、但已派有车+司机):
|
||||
|
||||
- `currentServiceDatesByGroup` 过滤 `dispatch_plan_finalized==1`,全程行展开(expandFullTripDailyRows)的 slice 因 finalized=0 被滤 → **`serviceDates` 返回空** → 改派页只展示 1 天(fallback 到 startDate)。
|
||||
- 而 `dailyVehiclePlan` / `dailyVehicleFees` 都正常 4 天(不过滤 finalized)——日期与逐日费用天数不一致。
|
||||
|
||||
## 修改接口:`GET /admin/fleet/board/orders/{orderId}`
|
||||
|
||||
`activeAssignments[].serviceDates` 行为修正(无字段结构变化):
|
||||
|
||||
| 场景 | 修复前 | 修复后 |
|
||||
|---|---|---|
|
||||
| 已派全程行(finalized=0,有车+司机) | `serviceDates=[]`(空) | `serviceDates=[start...end]` 全部出行日 |
|
||||
| 逐日行(finalized=1) | 正常 | 不变 |
|
||||
| 未派资源(无车/司机)未敲定切片 | 不纳入 | 不变(仍按 finalized==1 限定) |
|
||||
|
||||
### 前端交接
|
||||
|
||||
- 改派页「确认改单日期」继续用 `activeAssignments[].serviceDates` 渲染——现在对**已派全程行**也返回所有出行日(不再空)。
|
||||
- 「逐日费用」用 `activeAssignments[].dailyVehicleFees` / `dailyVehiclePlan`——天数与 `serviceDates` 一致(配套展示)。
|
||||
- 结合 #5676 按天改派:改派页每天一行展示,点某天【改派】调 `POST /admin/fleet/assignments/{assignmentId}/change` 传 `serviceDates=[该天]` 只改该天。
|
||||
|
||||
## 验证
|
||||
|
||||
- BoardOrderServiceTest **88/88**(新增 1:已派全程行 finalized=0 回显全部服务日;红→绿验证修复前失败);dev-v3 全量 verify 3286/0F/0E。
|
||||
- 网关实证(TEST,周梦洁 order 2085922371843125250):修复后 `activeAssignments[0].serviceDates=[8/17,8/18,8/19,8/20]` 4 天(修复前空),与 dailyVehiclePlan 天数一致。
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5693"
|
||||
title: "矩阵拖拽改派一步到位(拖到目标车/司机直接改成,不再弹窗手选)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "前端修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "23d6e7b1"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "2026-08-08"
|
||||
base: "origin/dev-v3"
|
||||
generated: "2026-08-08"
|
||||
---
|
||||
|
||||
# 矩阵拖拽改派一步到位(前端修复)
|
||||
|
||||
## 口径(wx)
|
||||
矩阵拖拽改派要**一步到位**——拖到目标车辆/司机就**直接改成它**(该车常驻司机或拖到的司机),**不再弹改派页手选槽位+重选车/司机**(拖完还手选=失去拖拽意义)。
|
||||
|
||||
## 现状(前端问题)
|
||||
`matrix/index.vue:442 onDropAssign` 拖后仍 `openAssign` 弹窗手选——拖拽后还要手选槽位+车/司机。
|
||||
|
||||
## 后端能力已齐备(零改动)
|
||||
- change API `POST /admin/fleet/assignments/{id}/change`:`newVehicleId`/`newDriverId` 均可空,必填仅 `effectiveDate`/`holdMode`/`reason`/`requestId`
|
||||
- 拖拽落点行已带 `assignmentId`/`slotId`(只切片该槽位)
|
||||
- 矩阵车辆行已带 `primaryDriverId`(常驻司机)
|
||||
- 价格/收费日默认沿用
|
||||
|
||||
## 前端修复
|
||||
拖拽 drop 后**直接调 change**:
|
||||
- 拖到车辆行 → change 传该 vehicleId + 其 primaryDriverId(常驻司机),effectiveDate=落点日期,切片该槽位
|
||||
- 拖到司机 → change 传该 driverId
|
||||
- 多槽位:拖拽落点确定改哪个槽位(slotId)
|
||||
- 仅跨常驻确认 / 605041 基线差异 / 多车 warning 等分支弹**轻量确认**,不弹完整改派页手选
|
||||
|
||||
## 验收
|
||||
- [ ] 拖拽到目标车/司机直接改派生效(一步到位,不弹改派页手选)
|
||||
- [ ] 多槽位拖拽落点确定改哪个槽位
|
||||
- [ ] 跨常驻/基线差异等弹轻量确认
|
||||
- [ ] 拖拽后价格/逐日正确
|
||||
|
||||
## 关联
|
||||
- #5693(后端排查,契约齐备零改动;证据 evidence/5693/issue-comment-onestep.md)
|
||||
|
||||
## 联系人
|
||||
- 后端/协调台:wx
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin)
|
||||
|
||||
按协调台拍板的「向导预选直达确认步」口径实现(非字面「直接调 change 不弹页」),hl-admin@23d6e7b1:
|
||||
|
||||
- **矩阵拖拽补传常驻司机**:`matrix/index.vue onDropAssign` 从落点车辆行取 `primaryDriverId`(字符串透传,空归一 null),两个模式分支统一 `openAssign(order, { vehicle, driver: residentDriverId, mode })`。
|
||||
- **AssignModal reassign 应用预选**:拖拽条目 `order.assignmentId` 命中 `activeAssignments` 且带预选车辆时,自动锁定被拖槽位为改派目标 + 进入「已清除重选」态(`changeDraftCleared=true`,等价手选槽位后点清除),费用草稿按 `mergeSlotChangeSegments` 整段合并目标 + `preferSavedTotal` 恢复;`restoreSelection({ vehicleId, driverId, autoSelectResidentDriver })` 复用 batch 分支同一机制——预选司机仅作初始草稿,仍随 `loadCandidates` 携带两侧 ID 复核可用性与常驻关系,不硬信车辆档案;落点车无常驻司机时留空走既有空态。
|
||||
- **保持不变**:提交链 `buildAssignmentSubmission/changeAssignment`、跨常驻确认(605036)、基线差异(605041)、ORDER_HAS_OTHER_VEHICLES warning、逐日车费拦截全部未触碰。
|
||||
- **与原口径差异**:拖拽后仍需用户在改派向导点一次「下一步/确认」提交(未做到完全不弹页直接 change)。取舍原因:落点缺目标司机、落点日期、逐日车费三个向导输入,直接重建精简 change 链需自行补齐这三值,风险高于复用既有向导校验;多槽位由被拖条目 assignmentId 确定槽位(满足「落点确定改哪个槽位」)。
|
||||
- 验证:fleet/board 定向 vitest 413/413 通过;checkpoint(含生产构建)全绿。若需进一步做到「完全不弹页直改」,属另一次精简 change 链重建,建议单列工单。
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5693"
|
||||
title: "矩阵拖拽改派多槽位支持评估 + 候选排除语义回归锁定(改指定槽位不影响其他)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "评估结论:多槽位拖拽改派端到端已实现且槽位语义正确,零行为变更;本次仅补回归测试锁定「候选排除只作用当前槽位,同订单其他槽位仍计目标车/司机冲突」。补充口径(wx,工单评论 id=37195):拖拽改派交互要一步到位(拖到目标车/司机直接改派,不再弹窗重选)——后端一步改派能力已齐备(change 接口 newVehicleId/newDriverId 均可空,详见工单评论 id=37196),属前端交互改造,由协调台另行前端 changelog 分流。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T13:40:00+08:00"
|
||||
---
|
||||
|
||||
# 矩阵拖拽改派多槽位评估与候选排除语义锁定(#5693)
|
||||
|
||||
## 背景
|
||||
|
||||
#5693 要求评估矩阵拖拽改派对"一个订单多车(多槽位)"的支持:目标槽位确定、指定槽位、按槽位校验、按槽位价格/逐日方案。评估结论:**四维均已实现**(#5573 槽位级派车系列建成),唯一缺口是"同订单其他槽位仍计冲突"无回归测试,本单补齐。
|
||||
|
||||
## 变更内容
|
||||
|
||||
| 项 | 变更 |
|
||||
|---|---|
|
||||
| `AssignmentCandidateServiceTest` | 新增 `query_reassignKeepsOtherSlotConflictOnTargetVehicle`:多槽位订单排除 slot0 查询候选时,slot1 占用目标车仍返回冲突(assignmentId/groupId 指向 slot1),available=false |
|
||||
|
||||
零行为/契约变更:无 mapping、字段、类型、必填性、枚举、错误码改动。
|
||||
|
||||
## 评估结论(四维)
|
||||
|
||||
1. **目标槽位确定**:矩阵每槽位独立数据行(assignmentSlotId/fleetItemIndex/assignmentId),拖拽 payload=被拖槽位行;改派提交 "`POST /admin/fleet/assignments/:assignmentId/change`" 以被拖槽位派单为 anchor,后端只切片该槽位。
|
||||
2. **指定槽位**:弹窗槽位表可改选其他槽位("选择改派");候选归属校验 fail-closed(excludeAssignmentId 必须同属当前 orderId+requirementId+fleetItemIndex,跨槽位直接拒绝),不可能误改别槽。
|
||||
3. **校验按槽位**:候选 `withoutExcludedSlot` 只排除当前槽位全部切片;precheck 按组排除自身;座位按 fleetItemIndex 槽位需求(#5573);常驻为车辆↔司机配对(槽位无关,设计如此);保险按派单行事件,只触达被改槽位。
|
||||
4. **价格/逐日方案按槽位**:change 按 effectiveDate 逐日切片原子替换该槽位,保留前缀沿用旧快照;同订单其他车辆返回 `warningCode=ORDER_HAS_OTHER_VEHICLES` 强提示,其他槽位数据不动。
|
||||
|
||||
## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
无契约变更。涉及接口 `POST /admin/fleet/assignments/candidates`(入参 orderId/requirementId/fleetItemIndex/excludeAssignmentId 既有语义)与 `POST /admin/fleet/assignments/:assignmentId/change`(既有槽位原子改派)行为不变。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- 定向测试:`AssignmentCandidateServiceTest` 50/50 通过(含新增用例);fleet 全量 verify 失败项全部归账(ReleaseEMixedBinaryHarnessTest 1F + ReleaseEOccupancyMysql8033RecoveryTest 10E 为既有基线失败;2 类 ContainerLaunch 系并行会话抢占固定 3306,空闲窗口复跑 7/7 通过);spotless:check 通过。
|
||||
- 部署:hl-fleet-service TEST(dev-v3)部署成功。
|
||||
- 网关探针(实单 2085938395749515266 双槽位 8/22-24,只读)8/8 PASS:
|
||||
- A 排除 slot0 后自身车蒙C01E01 available=true 且无冲突(自身槽位被排除);
|
||||
- B slot1 车蒙A-S6666 available=false,冲突指向 slot1 派单 2085938404851130370(其他槽位仍计冲突);
|
||||
- C 跨槽位排除(excludeAssignmentId=slot0 + fleetItemIndex=1)被 fail-closed 拒绝("参数非法: 排除派单不属于当前订单、当前用车需求或当前车型项")。
|
||||
|
||||
## 前端交接
|
||||
|
||||
本单后端变更(回归测试)无需前端配合。另:wx 后续口径(工单评论 id=37195)要求拖拽改派交互**一步到位**——拖到目标车/司机直接改派(该车常驻司机或拖到的司机),不再打开改派弹窗手选槽位+重选车/司机;该需求为纯前端交互改造(后端契约依据见工单评论 id=37196),由协调台另行前端 changelog 跟踪,与本后端 changelog 的 frontend_status=not_required 不冲突。
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin)
|
||||
|
||||
确认 not_required 属实,零代码改动:前端候选查询 `useVehicleDriverPicker.js:190-191` 仅传单数 `excludeAssignmentId`(当前槽位派单行 ID),不跨槽位排除,与本单锁定的「候选排除只作用当前槽位、同订单其他槽位仍计冲突、跨槽位 fail-closed」语义一致;改派提交以被拖/被选槽位派单为 anchor,前端未做跨槽位切片。前端在 #5676 记录的「整段改派时同槽其他执行段占用可能被误报」关切,本单后端明确为有意语义(其他槽位/段仍计冲突、失败关闭),非缺陷,前端维持现状。
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5697"
|
||||
title: "行程链接预览恢复真链接(确认执行组级冻结派车方案代际)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5700 已合并 dev-v3(e95cfc89a)并部署 TEST(hl-fleet-service 双实例 UP 14:52)。根因两层:①26-8778 确认执行(08-08 11:27)早于 #5681 finalize 逻辑部署(11:40)→ 存量遗留中间态(assigned+generation NULL+finalized=0)→ 预览/短链身份缺失(#5689 已把 500 降级为暂不可,本单恢复真链接);②confirm 的 finalize 依赖需求级 publishDailySnapshotIfComplete 条件(拓扑全派+无既有代),需求内存在 legacy 遗留行/非最终拓扑时新确认组不冻结。修复:确认执行组级冻结(markDispatchPlanFinalizedForRows:仅确认组、finalized=0 幂等、不退休同需求其他组;legacy 分支锁内重解析当前代,无代签发新代;confirmRequirement 多组沿用 expected 代或一次新代)+ TEST 存量数据修复(26-8778 原行补 generation/finalized)。网关验证:26-8778 渲染 200 返回真链接(改派后新组 t6wpsPy,8/17-8/20,有效期至 08-27)+ render-batch success=true + H5 解析 200(新旧链均正常)。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T15:00:00+08:00"
|
||||
---
|
||||
|
||||
# 行程链接预览恢复真链接(#5697)
|
||||
|
||||
## 背景
|
||||
|
||||
#5675/#5689 修复部署后,26-8778 派单预览行程详情仍显示「行程链接暂不可,联系车务确认」。
|
||||
|
||||
## 根因(库表+代码+时间线定位)
|
||||
|
||||
1. **存量遗留中间态**:26-8778 确认执行(08-08 11:27:31)早于 #5681 finalize 逻辑部署(11:40)——行保持 assigned + dispatch_plan_generation NULL + finalized=0 → 预览/短链解析身份缺失(#5689 已把 500 降级为「暂不可」,本单恢复真链接)。
|
||||
2. **代码缺口**:confirm 的 finalize 依赖需求级 `publishDailySnapshotIfComplete` 条件(拓扑全派 + 无既有代)——需求内存在 legacy 遗留行或非最终拓扑时,新确认组不会冻结 → 确认组预览/短链身份仍缺失。
|
||||
|
||||
## 修复(PR #5700)
|
||||
|
||||
1. `FleetAssignmentMapper.markDispatchPlanFinalizedForRows`:组级冻结——仅确认组行、finalized=0 幂等、不退休同需求其他组(改派/换版仍由 retire/clear 全需求退休)。
|
||||
2. `confirm` legacy 分支:锁内重解析需求当前代(并发组先冻结则沿用同代),无代签发新 snowflake 代,确认组组级冻结。
|
||||
3. `confirmRequirement` 原子引擎:多组确认沿用 `expectedPlanGeneration` 或循环外签发一次新代(多组共用),逐个确认组组级冻结。
|
||||
4. **存量数据修复(TEST)**:26-8778 原行补写 generation/finalized(精确单行 UPDATE + 前后 SELECT 对比);该行后续(14:33-14:37)被测试改派流程取消,改派新组已带代际(finalized=1)。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 确认执行后的派车组(含与 legacy 遗留行共存的场景)预览返回可点行程链接,不再「暂不可」。
|
||||
- 新 HOLD/DIRECT 派单创建即冻结代际(#5681 既有行为不变);本修复补充确认执行组级冻结,需求级最终快照事件语义不变。
|
||||
- 短链解析侧代际升级兼容(哨兵→真实代)不变;取消派车后旧短链随身份失效(既有语义)。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 预览区「行程链接暂不可用,请联系车务确认」为业务降级文案:确认执行后(dispatchPlan 冻结前窗口)或派车组身份失效时展示;正常确认后展示可点链接(h5 行程契约页)。
|
||||
- 无需字段适配;模板渲染接口行为与错误码口径不变。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:+2(render 确认后已冻结全程行返回真实链接;legacy 遗留行共存时新组确认组级冻结且不误发需求级快照);confirm 相关 4 用例回归;保险并发确认测试补 stub 回归。
|
||||
- 全量 verify 3294 tests 0 失败 4 skipped + spotless:check 通过。
|
||||
- 网关实测(TEST,26-8778):渲染 200 返回真链接(https://web.test.1814.love:9443/s/t6wpsPy,8/17-8/20,有效期至 08-27);render-batch 200 + item success=true;H5 短链解析 200(行程契约 V2 全 4 天)。
|
||||
- 部署:hl-fleet-service dev-v3=e95cfc89a 双实例 UP(14:52)。
|
||||
|
||||
## 前端实证确认(2026-08-09 mmg,hl-admin v2.1)
|
||||
|
||||
纯后端修复(确认执行组级冻结代际),前端无需改动:
|
||||
|
||||
- changelog §前端动作明确「无需字段适配;模板渲染接口行为与错误码口径不变」。
|
||||
- 已 grep 实证:全 `src/` 无任何行程链接预览 / 「暂不可用」文案 / 短链解析 / `render-batch` / `dispatchPlan` 相关代码——行程链接预览为后端渲染(H5 / 网关)能力,hl-admin 不持有该预览组件或降级文案。
|
||||
- 预览区「行程链接暂不可用」为后端业务降级文案,正常确认后由后端返回可点 h5 链接,前端无字段适配需求。
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5698"
|
||||
title: "改派修复:已派全程行改派生效(计费日期整段校验)+ 清除司机/车辆后槽位空态待重选"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "106aa658"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端接口行为修复:POST /admin/fleet/assignments/ID/change 对已派全程行(service_date 为空、start/end 覆盖整个服务期)改派时,计费日期/费用快照/组派冲突检查按整段服务期展开——此前 serviceDateOf 只回退生效日第一天,前端按全部服务日提交计费日期即报「计费日期必须属于本派车组服务日期」,改派完全不生效(TEST 操作日志 13:59 连续 7 次 change_failed 实证)。修复后网关实证改派成功(双向往返均生效)。前端配合项见正文「前端交接」。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T15:00:00+08:00"
|
||||
---
|
||||
|
||||
# 改派修复:已派全程行改派生效 + 清除司机/车辆后槽位空态
|
||||
|
||||
> 后端完成:PR #5699 已合并 dev-v3(fb8945e97)并部署 TEST,网关验证通过(改派双向往返生效、现场已还原)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5698](https://git.1814.love:8443/wx/HL/issues/5698)
|
||||
- **PR**: [#5699](https://git.1814.love:8443/wx/HL/pulls/5699)
|
||||
- **关联**: [#5676](https://git.1814.love:8443/wx/HL/issues/5676)(按天改派)、[#5691](https://git.1814.love:8443/wx/HL/issues/5691)(回显全部服务日)
|
||||
|
||||
### 联系人
|
||||
|
||||
- 后端:wx(GIT)
|
||||
- 前端配合:mmg / 协调台
|
||||
|
||||
---
|
||||
|
||||
## 背景(wx 实测)
|
||||
|
||||
改派页两个 bug:
|
||||
|
||||
1. **清除司机没清空**:点「清除车辆/司机」后槽位仍显示原车蒙A-E2E99+王信(已生效派车),用户以为没清空。
|
||||
2. **统一改派确认后司机没变**:选新司机→确认→还是原司机,改派完全不生效("完全不懂")。
|
||||
|
||||
## 后端修复(#5698,已部署 TEST)
|
||||
|
||||
### Bug 2 根因
|
||||
|
||||
TEST 操作日志实证:订单 HL20260808105342627(蒙A-E2E99+王信,8/17-8/20 **全程行** service_date=NULL)13:59 连续 7 次 `change_failed`,报错均为:
|
||||
|
||||
> `参数非法: 计费日期必须属于本派车组服务日期`
|
||||
|
||||
已派全程行改派按整槽替换(#5595 语义),但 `resolveChargeableServiceDates` 用 `serviceDateOf()` 对全程行只回退到 **startDate 一天**;前端按全部服务日提交计费日期(#5691 回显全部服务日后的真实载荷)→ 校验失败 → change 抛异常回滚 → 改派不生效(前端静默失败)。
|
||||
|
||||
### 修复
|
||||
|
||||
`AssignmentService#changeTargetServiceDates`:全程行按 [startDate..endDate] **整段展开**,逐日切片行取自身 serviceDate;应用于改派 4 处——计费日期校验、对账期检查、组派冲突检查、替换行费用快照报价(quote 从只报第一天改为整段)。
|
||||
|
||||
**接口契约不变**(无新增/变更字段),仅修复既有行为。守卫不放宽:计费日期超出服务期仍拒绝;跨常驻组合仍要求确认(605036);无变化载荷仍拒绝(605029)。
|
||||
|
||||
### 验证
|
||||
|
||||
- 定向测试 `AssignmentServiceTest` 400 项全绿(新增 2 项:整段计费日期改派成功 + 越界日期仍拒绝);回归 210 项全绿;fleet verify 3293 项全绿;spotless 通过
|
||||
- 网关实证(TEST,wx 实测现场):统一改派(effectiveDate=第一天 + 整段计费日期)到新司机/新车辆 **业务成功**(修复前 7 连败),替换行 assigned 全程形态 8/17-8/20;换回原派同样成功;现场已还原为 蒙A-E2E99+王信
|
||||
|
||||
## 前端交接(pending,需前端配合)
|
||||
|
||||
### 1. 清除司机/车辆后槽位显示空态(协调台定口径 B)
|
||||
|
||||
现状:改派页「清除车辆/司机」(#5662)只清**本次改派草稿**(不触发后端),槽位行继续显示已生效派车(蒙A-E2E99+王信)——用户误以为"没清空"。
|
||||
|
||||
期望(口径 B):清除后该槽位行显示**空态(待重选)**,用户明确"已清空",随后重新选择车/司机,提交改派生效。后端派单必须车+司机的终态不变(**改派提交前仍是原值**),清除只是前端进入"待重选"展示态。
|
||||
|
||||
- 相关代码:`src/views/fleet/board/components/AssignModal.vue`(`clearChangeAssignmentDraft` / `changeDraftCleared` / 槽位表「当前车辆/当前司机」列)
|
||||
- 交互参考:清除后已有「已清除草稿,可重新选择」提示与「选择车辆/司机」入口,仅需将槽位表两列在 `changeDraftCleared=true` 时改为空态展示(如显示「待重新选择」)
|
||||
- 验收:清除后槽位行不再显示原车/原司机;重选新司机提交后列表展示新司机
|
||||
|
||||
### 2. 统一改派链路
|
||||
|
||||
后端修复后,前端现有提交链路(`useAssignFlow.js` `buildAssignmentSubmission`:单次调 change、effectiveDate=第一天、不传 serviceDates、携带整段 chargeableServiceDates)可直接生效,无需改动。按天改派(serviceDates)不受影响。
|
||||
|
||||
## 变更接口
|
||||
|
||||
### `POST /admin/fleet/assignments/ID/change`(行为修复,接口契约不变)
|
||||
|
||||
- 已派全程行(`service_date` 为空、`start_date`/`end_date` 覆盖整个服务期)改派时,入参 `chargeableServiceDates` 允许携带该行覆盖的**全部服务日**(此前只认生效日第一天,其余日期报「计费日期必须属于本派车组服务日期」导致改派失败回滚)
|
||||
- `chargeableServiceDates` 超出目标行服务期仍拒绝(守卫不放宽);其余入参语义不变(`effectiveDate` 必填、`serviceDates` 空=连续后缀整体改派、`newVehicleId`/`newDriverId` 可空=保留原值)
|
||||
- 响应结构不变;替换行费用快照/报价按整段服务期计算(不再只算第一天)
|
||||
|
||||
## 前端/调用方动作
|
||||
|
||||
- 后端无动作:现有提交链路(单次调 change + effectiveDate=第一天 + 整段计费日期)修复后直接生效
|
||||
- **前端配合项(pending)**:改派页「清除车辆/司机」后槽位行显示空态(待重选),不再显示原车/原司机——见上文「前端交接」
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 定向测试:`AssignmentServiceTest` 400 项全绿(新增 2 项全程行整段计费日期改派成功/越界拒绝);回归 FleetAssignmentMapper 61 + 候选 50 + 快照 11 + 看板 88 全绿;fleet verify 3293 项全绿;spotless 通过
|
||||
- 部署:TEST 滚动部署 hl-fleet-service 双实例 UP(8087/8187,dev-v3 @ fb8945e97)
|
||||
- 网关实证(wx 实测现场 HL20260808105342627):统一改派到新司机/新车辆业务成功(修复前 7 连败),替换行 assigned 全程形态 8/17-8/20;换回原派同样成功;现场已还原
|
||||
|
||||
## 验收清单(本单)
|
||||
|
||||
- [x] 清除司机正常清空(司机+车辆都清)——口径 B 已定:前端清除后槽位空态待重选(**前端交接 pending**);后端终态语义不变
|
||||
- [x] 统一改派确认后司机改成新选的(生效)——后端修复完成,TEST 网关实证双向往返生效
|
||||
- [x] 定向测试 + PR 合并 + 部署 TEST + 网关验证——PR #5699 合并 dev-v3(fb8945e97),TEST 滚动部署双实例 UP,网关验证通过
|
||||
- [x] changelog(yst 完整格式)——本文件
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5701"
|
||||
title: "登记司机确认不被「通知结果正在确认」拦截(人工确认直接成功)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5705/#5716/#5719 已合并 dev-v3 并部署 TEST(hl-fleet-service 双实例)。登记司机确认(MANUAL_CONTACT)与 confirm 确认执行均不再被 605042 拦截;已冻结 HOLD 组 confirm 不再误判 605020。全链路「登记确认→confirm assigned」网关实证走通(AMBIGUOUS+已冻结组 confirm 200 assigned)。前端交接见下(605042 场景收敛与错误提示口径)。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T18:15:00+08:00"
|
||||
---
|
||||
|
||||
# 登记司机确认不被「通知结果正在确认」拦截(人工确认直接成功)
|
||||
|
||||
> 后端完成:PR [#5705](https://git.1814.love:8443/wx/HL/pulls/5705) 已合并 dev-v3 并部署 TEST,网关全链路验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5701](https://git.1814.love:8443/wx/HL/issues/5701)
|
||||
- **PR**: [#5705](https://git.1814.love:8443/wx/HL/pulls/5705) / [#5716](https://git.1814.love:8443/wx/HL/pulls/5716) / [#5719](https://git.1814.love:8443/wx/HL/pulls/5719)
|
||||
- **Merge commit**: [d28d2cd11](https://git.1814.love:8443/wx/HL/commit/d28d2cd11)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
派单页点「登记司机已确认」报 **605042「司机通知结果正在确认,请稍后重试」**——HOLD 通知异步发送结果仍 AMBIGUOUS(短信网关未回执)时,人工登记被拦;连锁导致 confirm 确认执行报 605020(司机确认没登记上,状态未到可确认执行)。口径(wx):车务点确认就是确认——系统不知道司机线下结果,人工登记与异步通知结果应解耦。
|
||||
|
||||
## 变更说明(无接口契约变化,行为修正)
|
||||
|
||||
### `POST /admin/fleet/assignments/{assignmentId}/driver-confirmation` 行为变化
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 通知结果确认中(AMBIGUOUS)+ **MANUAL_CONTACT** 人工联系登记 | 605042 拦截 | **直接登记成功** |
|
||||
| 通知结果确认中(AMBIGUOUS)+ **NOTIFICATION** 通知回复来源 | 605042 拦截 | 400「无系统通知发送成功事实时必须显式选择人工联系来源」(更准确的引导) |
|
||||
| 通知结果确认中 + 未显式指定来源 | 605042 拦截 | 400「无系统通知发送成功事实时必须显式选择人工联系来源」 |
|
||||
| 通知已发送(SENT)/未发送(NOT_SENT/失败/取消) | 不变 | 不变 |
|
||||
|
||||
### `POST /admin/fleet/assignments/{assignmentId}/confirm` 行为变化(补修)
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 通知结果确认中(AMBIGUOUS)+ 已登记司机确认 | 605042 拦截 | **直接推进 assigned**(同登记确认口径,与异步通知结果解耦) |
|
||||
| 已冻结 HOLD 组(dispatch_plan_finalized=1,#5697 冻结方案常态) | **605020 误判**(组级冻结 CAS 对已冻结行幂等跳过,但断言要求全量更新,自相矛盾) | 正常推进 assigned(断言只覆盖未冻结行) |
|
||||
|
||||
### 不变的保护
|
||||
|
||||
- 其余 605042 保护点(expand 换版 / cancel / change / undo 等**通知代际推进与身份失效**场景)保留原拦截。
|
||||
- NOTIFICATION 来源仍需通知 SENT 事实(原有校验不变)。
|
||||
|
||||
## 前端交接
|
||||
|
||||
1. **人工登记路径**:司机线下确认后,登记请求传 `driverReplySource=MANUAL_CONTACT`(+操作人)即可直接成功,不再需要等通知结果回执或重试。
|
||||
2. **605042 在该端点不再出现**:前端可移除 driver-confirmation 的 605042 重试引导;通知未回执时若用户选「通知回复」来源,会收到 400 提示「请选择人工联系来源」,按文案展示即可。
|
||||
3. **全链路**:登记确认成功后,`POST /{assignmentId}/confirm`(确认执行)即可走通 assigned(605020 连锁消除)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **网关全链路(补修实证,TEST,holding 派单 2085978741208481793)**:通知 AMBIGUOUS(delivery_status=dispatching)+ 已冻结组(dispatch_plan_finalized=1)+ 已登记 MANUAL_CONTACT 司机确认 → `confirm` 确认执行 → **200 成功,confirmed=true、status=assigned**、返回行程 H5 链接(一次实证同时覆盖 AMBIGUOUS 放行与已冻结组 605020 修复)
|
||||
|
||||
- **单测**:新增 2 用例(AMBIGUOUS+MANUAL_CONTACT 登记成功并写凭证;AMBIGUOUS+未显式来源拒绝并提示人工联系),替换原 605042 拦截用例;AssignmentServiceTest **402/402 全绿**;spotless 通过
|
||||
- **fleet verify**(mvn-throttle 错峰,Docker 原生 Testcontainers):仅 `ReleaseEOccupancyMysql8033RecoveryTest` 10 个基线失败(frozen Release E V002 SHA-256 发布产物缺失,与本案无关),其余 3286 全绿
|
||||
- **部署**:hl-fleet-service 双实例 UP(15:29)
|
||||
- **网关全链路**(TEST,VEHICLE_MANAGER,holding 派单 2085662146632224770):
|
||||
- 构造通知 AMBIGUOUS(delivery_status=dispatching)→ `driver-confirmation` MANUAL_CONTACT 登记 → **200 直接成功**(修复前 605042)✓
|
||||
- 通知恢复终态后 `confirm` 确认执行 → **200,confirmed=true、status=assigned**、返回行程 H5 链接、车辆/司机状态置 busy ✓(605020 连锁消除)
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin)
|
||||
|
||||
确认 not_required 属实,零代码改动:
|
||||
|
||||
1. **前端唯一登记路径即 MANUAL_CONTACT**:`useAssignFlow.js:121 buildDriverConfirmationSubmission` 硬编码 `driverReplySource: 'MANUAL_CONTACT'`,注释明确「此入口只供后台人工电话/微信登记;必须与通知回执事实显式隔离」。后端变更后该路径在 AMBIGUOUS 时直接成功(修复前 605042 拦截),正是本修复目标,前端天然受益。
|
||||
2. **无 605042 特判可移除**:grep 全 `src/` 无任何 605042 处理或「正在确认请稍后重试」重试引导;业务错误统一走 `request.js` 弹 `body.message`,后端返回文案原样展示(交接项 2 的「可移除重试引导」前端本就不存在)。
|
||||
3. **不会触发新 400**:新 400「请选择人工联系来源」仅在 NOTIFICATION 来源或未显式指定来源时出现;前端永远显式 MANUAL_CONTACT,不会命中。
|
||||
4. **confirm 确认执行**(2026-08-08 补修后更新):后端补修(PR #5716/#5719)已让 confirm 在 AMBIGUOUS + 已登记司机确认时**也不再被 605042 拦截**(与登记确认同口径解耦),并修复已冻结 HOLD 组(dispatch_plan_finalized=1)confirm 误判 605020。前端实证:全 `src/` 无任何 605042 业务处理;唯一 605020 引用是 `baselineDifference.spec.js:47` 断言它**不是**基线差异错误(即不特殊处理);confirm 提交链 `useAssignFlow.js:1095 confirmAssignment` 无错误码特判,错误统一走 `request.js` 弹后端 message。补修属后端行为放宽,前端天然受益,零改动——not_required 维持不变。
|
||||
@@ -0,0 +1,334 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5704"
|
||||
title: "核单域下线数据指纹乐观锁:6 个端点删除指纹/版本号入参与出参字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "d32e14b9"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5709 已合并 dev-v3;核单域 6 个端点删除 expectedSourceFingerprint/version 入参与 sourceFingerprint/version 出参,错误码 584108/584110/584325 同步下线。字段删除属硬破坏契约,前端必须先停传这些字段再与后端同批发布。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】核单域下线数据指纹乐观锁,6 个端点删除指纹/版本号字段(#5704)
|
||||
|
||||
> **PR**: [#5709](https://git.1814.love:8443/wx/HL/pulls/5709) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单为单人负责场景,不存在多人并发同时修改同一订单核单数据的情况。此前引入的数据指纹(sha256)/版本号乐观锁机制(前端 GET 拿到指纹,保存/确认/提交时回传,数据被他人改动则拒存)对单人操作无实际保护价值,且强制前端每次保存前 GET 并回传 64 位十六进制指纹,增加对接负担。本次将核单域内该机制整体下线:**删除 6 个端点的指纹/版本号入参字段与对应出参字段,3 个相关错误码不再触发**。
|
||||
|
||||
> ⚠️ 本次为**字段删除类硬破坏契约**:其中 5 个端点的请求体带未知字段白名单校验,前端若继续传已删除的字段会被拒绝(详见 §11)。**前端必须先删除这些字段的传参,再与后端同批发布。**
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 保存导游费用 | PUT | `/v3/admin/order/{orderId}/settlement/guide-fees` | 删除入参 `expectedSourceFingerprint`、删除出参 `sourceFingerprint` | 停止传参/读字段 |
|
||||
| 2 | 确认导游费用 | POST | `/v3/admin/order/{orderId}/settlement/guide-fees/confirm` | 删除入参 `expectedSourceFingerprint` | 停止传参 |
|
||||
| 3 | 保存摄影费用 | PUT | `/v3/admin/order/{orderId}/settlement/photographer-fees` | 删除入参 `expectedSourceFingerprint`、删除出参 `sourceFingerprint` | 停止传参/读字段 |
|
||||
| 4 | 确认摄影费用 | POST | `/v3/admin/order/{orderId}/settlement/photographer-fees/confirm` | 删除入参 `expectedSourceFingerprint` | 停止传参 |
|
||||
| 5 | 保存车辆核单草稿 | PUT | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 删除入参 `version`、删除出参 `version` | 停止传参/读字段 |
|
||||
| 6 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 删除入参 `reimbursementExpectedSourceFingerprint`、`groupExpectedSourceFingerprint` | 停止传参 |
|
||||
|
||||
同时下线的错误码:`584108`、`584110`、`584325`(详见 §7)。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:核单人员在订单核单页维护导游费用、摄影费用、车辆核单草稿,并在全部分类就绪后完成核单提交。
|
||||
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||
- **幂等性**:保存类接口按订单维度覆盖式保存,重复提交相同载荷结果一致;确认/提交接口重放安全(不再有指纹前置校验)。
|
||||
- **限流**:未声明接口专属限流。
|
||||
- **方法/路径**:见 §2 变更清单(共 6 个端点;对应的 GET 查询端点出参同步删除指纹/版本号字段,见 §5)。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `orderId` | Long | 是 | 订单 ID,路径参数,6 个端点一致 |
|
||||
|
||||
### 4.2 请求体字段(变更后现状)
|
||||
|
||||
**PUT /settlement/guide-fees(保存导游费用)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `items` | Array | 是 | 导游费用明细全量集合,最多 200 条;每项含 `id`/`candidateKey`/`staffAssignmentId`/`serviceDate`/`name`/`serviceType`/`paymentMethod`/`amount`/`remark`/`voucherUrls`/`sourceResolution`/`candidateStatus` |
|
||||
| `excludedCandidateKeys` | Array of String | 否 | 明确排除的候选 key;空数组表示本次不新增排除项 |
|
||||
| ~~`expectedSourceFingerprint`~~ | - | - | **已删除,禁止再传**(传了会被 584128 白名单拒绝) |
|
||||
|
||||
**POST /settlement/guide-fees/confirm(确认导游费用)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `itemIds` | Array of Long | 是 | 待确认 INCLUDED 费用明细 ID 列表(JSON 中每项为字符串),1~200 条 |
|
||||
| ~~`expectedSourceFingerprint`~~ | - | - | **已删除,禁止再传** |
|
||||
|
||||
**PUT /settlement/photographer-fees、POST /settlement/photographer-fees/confirm**:字段结构同导游两个端点,仅业务对象为摄影费用,删除字段同为 `expectedSourceFingerprint`。
|
||||
|
||||
**PUT /settlement/step3/vehicles(保存车辆核单草稿)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `items` | Array | 是 | 车辆核单全量明细 |
|
||||
| ~~`version`~~ | - | - | **已删除,禁止再传**(传了会被白名单拒绝,返回 400) |
|
||||
|
||||
**POST /settlement/finalize(完成核单)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `remark` | String | 否 | 提交备注 |
|
||||
| `reimbursementConfirmation` | Object | 条件必填 | 主报账确认凭据;含 `transferDate`(转账日期)、`transferRef`(转账流水号,主报账净额非 0 时必填,trim 后最长 128 字符)、`advanceSettledFlag`(预支是否已处理,必填布尔)、`signedVoucher`(签字凭证,至少 1 个 URL 非空文件:`files[{name,url}]` + `note`) |
|
||||
| ~~`reimbursementExpectedSourceFingerprint`~~ | - | - | **已删除,禁止再传** |
|
||||
| ~~`groupExpectedSourceFingerprint`~~ | - | - | **已删除,禁止再传** |
|
||||
|
||||
> 注:finalize 请求体未启用未知字段白名单,误传旧指纹字段会被**静默忽略**(不报 400),但前端仍应停止传参,避免依赖「传了也没事」的行为。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
成功路径出参结构不变,仅删除指纹/版本号字段:
|
||||
|
||||
| 端点 | 删除的出参字段 | 原作用 |
|
||||
|---|---|---|
|
||||
| GET/PUT `/settlement/guide-fees`、POST `/settlement/guide-fees/confirm` 响应 | ~~`sourceFingerprint`~~ | 导游费用来源数据 sha256 指纹 |
|
||||
| GET/PUT `/settlement/photographer-fees`、POST `/settlement/photographer-fees/confirm` 响应 | ~~`sourceFingerprint`~~ | 摄影费用来源数据指纹 |
|
||||
| GET/PUT `/settlement/step3/vehicles` 响应 | ~~`version`~~ | 车辆核单草稿版本号 |
|
||||
|
||||
其余出参字段(如导游/摄影的 `category`/`totalAmount`/`cashPaidAmount`/`unconfirmedCount`/`pendingCandidateCount`/`settlementReady`/`blockReasonCode`/`items`/`editable`/`readOnlyReasonCode`,车辆的 `orderId`/`totalAmount`/`allConfirmed`/`settlementReady`/`blockReasonCode`/`items`/`frozen` 等)均无变化。前端不要再读取 `sourceFingerprint` / `version`,读取结果恒为 undefined。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次不涉及枚举或字典的新增、删除、改值、改语义。`serviceType`(FULL_COURSE_GUIDE/LOCAL_GUIDE/COMMENTARY_SERVICE/TEMPORARY_SUPPLEMENT)、`paymentMethod`(COMPANY_PAID/CASH_PAID/SIGNED)、`blockReasonCode` 等既有取值不变。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 本次变化 |
|
||||
|---:|---|---|
|
||||
| `584108` | 车辆核单明细已变化,请刷新 | **已删除,不再触发** |
|
||||
| `584110` | 导游或摄影费用数据已变化,请刷新 | **已删除,不再触发** |
|
||||
| `584325` | 核单提交指纹缺失(FINALIZE_FINGERPRINT_REQUIRED) | **已删除,不再触发** |
|
||||
| `584315` | 核单来源数据已变化,请刷新后重新确认 | **仍在用**:车辆保存的来源数据漂移门禁改抛此码(承接原 584108 场景) |
|
||||
| `584128` | 导游或摄影费用请求字段不合法:{具体原因} | 不变;前端误传已删除字段时由该码拒绝(见 §8.3) |
|
||||
|
||||
> 前端如曾对 `584108`/`584110`/`584325` 写过特判(专属提示/自动刷新分支),这些分支不会再命中,应移除;车辆来源漂移场景改判 `584315`。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(保存导游费用,不再回传指纹)
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "9001",
|
||||
"candidateKey": null,
|
||||
"staffAssignmentId": "11",
|
||||
"serviceDate": "2026-08-08",
|
||||
"name": "导游甲",
|
||||
"serviceType": "FULL_COURSE_GUIDE",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"amount": "500.00",
|
||||
"remark": null,
|
||||
"voucherUrls": [],
|
||||
"sourceResolution": null,
|
||||
"candidateStatus": "COMPLETE"
|
||||
}
|
||||
],
|
||||
"excludedCandidateKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"category": "GUIDE",
|
||||
"totalAmount": "500.00",
|
||||
"cashPaidAmount": "0.00",
|
||||
"unconfirmedCount": 1,
|
||||
"pendingCandidateCount": 0,
|
||||
"settlementReady": false,
|
||||
"blockReasonCode": "UNCONFIRMED_ITEMS",
|
||||
"items": [],
|
||||
"editable": true,
|
||||
"readOnlyReasonCode": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:响应中已无 `sourceFingerprint` 字段。
|
||||
|
||||
### 8.2 边界(完成核单,不再传双指纹)
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2084000000000002978/settlement/finalize
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"remark": "核单完成",
|
||||
"reimbursementConfirmation": {
|
||||
"transferDate": "2026-08-08",
|
||||
"transferRef": "TX20260808001",
|
||||
"advanceSettledFlag": true,
|
||||
"signedVoucher": {
|
||||
"files": [{"name": "voucher.jpg", "url": "https://oss.example.com/voucher/1.jpg"}],
|
||||
"note": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 200, "message": "success", "data": {"submitted": true}, "success": true}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(旧前端仍传已删除字段,被白名单拒绝)
|
||||
|
||||
场景 A:保存导游费用仍传 `expectedSourceFingerprint`。
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/guide-fees
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"items": []
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 584128, "message": "导游或摄影费用请求字段不合法:导游费用请求不支持字段: expectedSourceFingerprint", "data": null, "success": false}
|
||||
```
|
||||
|
||||
场景 B:保存车辆核单草稿仍传 `version`。
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 3,
|
||||
"items": []
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 400, "message": "请求数据格式错误:车辆核单请求不支持字段: version", "data": null, "success": false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 保存/确认/提交不再要求前端先 GET 取指纹,可直接操作;单人负责场景下后写覆盖先写,与既有使用方式一致。
|
||||
- ✅ 车辆保存仍保留「来源数据漂移」业务门禁:草稿加载后若派单/用车来源数据已变化,保存时返回 `584315`,提示刷新后重新确认——这不是乐观锁,是业务一致性校验。
|
||||
- ❌ 导游/摄影 4 个端点与车辆保存端点对请求体做字段白名单校验,**任何未知字段都会被拒**(含本次删除的指纹/版本号字段),不要把查询响应整个 echo 回请求体。
|
||||
- ❌ 已终态(冻结)的核单数据仍不可编辑,该约束与本次变更无关,保持不变。
|
||||
|
||||
### 9.1 保存时必须剥掉的只读派生字段(导游/摄影)
|
||||
|
||||
导游/摄影保存接口(PUT guide-fees / photographer-fees)的 GET 响应 items[] 里含有后端计算的只读派生字段,**保存回传时必须剥掉**,否则触发白名单 400(错误码 584128「不支持字段: xxx」)。
|
||||
|
||||
必须剥掉的字段:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `sourceType` / `sourceTypeName` | 来源类型及中文名 |
|
||||
| `sourceActive` | 来源是否仍有效 |
|
||||
| `serviceTypeName` | 导游服务类型中文名 |
|
||||
| `feeTypeName` | 摄影费用类型中文名 |
|
||||
| `paymentMethodName` | 付款方式中文名 |
|
||||
| `settlementConfirmStatus` / `settlementConfirmStatusName` | 核算确认状态及中文名 |
|
||||
| `candidateResolution` | 候选处理结果 |
|
||||
|
||||
通则:**所有 `*Name` 中文字段 + `sourceType`/`sourceActive` + 确认状态 + 候选处理结果,都是后端算的,保存一律不回传。** 推荐前端保存前按允许字段重建 payload(维护 toSaveItem 映射),不要把 GET 响应对象整个 echo 回去。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 端点 | 字段 | 原来 | 现在 |
|
||||
|---|---|---|---|
|
||||
| PUT guide-fees / photographer-fees | `expectedSourceFingerprint` | 入参,回传 GET 拿到的指纹 | **已删除** |
|
||||
| POST guide-fees/confirm、photographer-fees/confirm | `expectedSourceFingerprint` | 入参 | **已删除** |
|
||||
| GET/PUT guide-fees、photographer-fees 响应 | `sourceFingerprint` | 出参,64 位十六进制 | **已删除** |
|
||||
| PUT step3/vehicles | `version` | 入参,草稿版本号 | **已删除** |
|
||||
| GET/PUT step3/vehicles 响应 | `version` | 出参,整数版本号 | **已删除** |
|
||||
| POST finalize | `reimbursementExpectedSourceFingerprint`、`groupExpectedSourceFingerprint` | 入参,双指纹 | **已删除** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 保存导游/摄影费用 | 必须先 GET 取 `sourceFingerprint` 回传,指纹不匹配返回 584110 | 直接保存,无指纹校验 |
|
||||
| 保存车辆核单草稿 | 必须回传 `version`,不匹配返回 584108 | 直接保存;来源数据漂移改返回 584315 |
|
||||
| 完成核单提交 | 必须传主报账+单团核算双指纹,缺失返回 584325 | 直接提交,无指纹校验 |
|
||||
| 请求体含已删除字段 | 正常受理 | 导游/摄影返回 584128、车辆返回 400;finalize 静默忽略 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**是,硬破坏**。导游/摄影 4 个端点 + 车辆保存端点带请求体字段白名单,旧前端继续传 `expectedSourceFingerprint` / `version` 会被拒绝(584128 / 400),核单保存、确认链路直接不可用。
|
||||
- **前端是否必须同步上线**:**必须同批**。前端需先删除上述字段的传参与读取,再与后端同批发布;旧前端 + 新后端 = 核单保存/确认全部报错。
|
||||
- **上线顺序边界**:前后端同批发布;若必须分先后,**先上前端(停传字段),再上后端**。
|
||||
- **前端特判清理**:移除对 `584108`/`584110`/`584325` 的特判;车辆来源漂移提示改挂 `584315`。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚即恢复原指纹契约;但已改造的新前端(不传指纹)在旧后端上会触发指纹校验失败——**回滚必须前后端同批回滚**。
|
||||
- 数据侧无迁移:指纹/版本号不持久化在业务表,回滚无数据修复成本。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 本次只下线核单域内上述 6 个端点的指纹机制;应收总览/逐条优惠确认的指纹错误码 `584300`/`584302`(OVERVIEW_FINGERPRINT_EXPIRED / DISCOUNT_FINGERPRINT_EXPIRED)**不在本次范围,仍在用**,相关确认接口的指纹传参保持不变。
|
||||
- 保存类接口幂等语义不变:按订单维度覆盖式全量保存,重复提交相同载荷结果一致。
|
||||
- 排查用户报错时,`584128` / 400 的 message 已含具体不支持的字段名,可直接据此定位前端是否还在传旧字段。
|
||||
- 小程序端(/v3/mp/*)不涉及本次变更,无需任何改动。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5704](https://git.1814.love:8443/wx/HL/issues/5704)
|
||||
- **代码 PR**: [#5709](https://git.1814.love:8443/wx/HL/pulls/5709)
|
||||
- **文档 PR**: [#5711](https://git.1814.love:8443/wx/HL/pulls/5711)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst)
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin@d32e14b9)
|
||||
|
||||
已按契约停传/停读全部指纹/版本号字段,清理下线错误码特判:
|
||||
|
||||
- **入参删除**:保存导游/摄影 `buildStaffFeeTabSaveRequest` 不再传 `expectedSourceFingerprint`(含回退 #5674 给导游强制传指纹的逻辑——该 changelog 已撤回并入本单);保存车辆 `buildVehicleSaveRequest` 删 `version`;finalize 删 `reimbursementExpectedSourceFingerprint`/`groupExpectedSourceFingerprint`。人员/车辆保存 items 白名单重建(不回传 sourceType/settlementConfirmStatus 等只读字段)保留不动。
|
||||
- **出参停读**:报告适配不再投影 `sourceFingerprint`;GET 回读不再持久化车辆 version / 人员指纹。
|
||||
- **错误码特判**:车辆来源漂移恢复分支 584108 改挂 **584315**(恢复语义不变:丢弃过期草稿+GET 回读权威明细+提示重新核对);人员 584110/584108 指纹冲突分支删除(下线后不再命中);584325 下线清理。584300/584302(应收总览/优惠确认指纹)不在范围,未触碰;`attachRefreshedDetailForReportError` 报告侧 584312/584314/584315 刷新逻辑保留。
|
||||
- **UI 门禁**:`detail.vue canComplete` 由「持有双报告指纹」放宽为「双报告已生成」(finalize 前 service 层双报告 GET 的 584311/584313 保护仍在,指纹门禁本是 UI 冗余);`ReportModal` 凭据表单重置改用报告快照引用触发(语义等价)。
|
||||
- **前端未接 confirm 端点**:grep 确认无 `guide-fees/confirm`、`photographer-fees/confirm` 调用,6 端点中实际涉及 4 个。
|
||||
- 验证:settlement + orderV2 定向 vitest 105/105 通过;checkpoint(含 Vitest 全量 + 生产构建)全绿。
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5708"
|
||||
title: "核单车辆下拉新增车型当日牌价 dayPrice(参考价,非最终核算价)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a9ddbd48"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5713 已合并 dev-v3;GET /v3/admin/order/{orderId}/settlement/vehicle-options 出参 data[] 新增 dayPrice 字段(车型当日牌价,元/车天)。dayPrice 是参考价非最终核算价,最终核算价以派单后车辆快照 dailyPrice / 核单行 amount 为准。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】核单车辆下拉新增车型当日牌价 dayPrice(#5708)
|
||||
|
||||
> **PR**: [#5713](https://git.1814.love:8443/wx/HL/pulls/5713) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单 Step3 车辆 Tab 的车辆下拉选项接口,此前只返回车牌 / 车型 / 司机等基础信息,核单人员看不到该车型当日的参考牌价,选车时无法预估金额。本次在出参 `data[]` 新增 `dayPrice` 字段,按订单出发日期取该车型当日定价,**仅供前端展示参考,不是最终核算价**。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 查询核单车辆异步下拉 | GET | `/v3/admin/order/{orderId}/settlement/vehicle-options` | 出参新增字段 | 读取 `data[].dayPrice`,建议标注"参考价" |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:核单人员在订单核单 Step3 车辆 Tab 选择车辆时,通过关键字异步搜索候选车辆。
|
||||
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||
- **幂等性**:是,只读查询。
|
||||
- **限流**:未声明接口专属限流。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `orderId` | Path | Long | 是 | 订单 ID |
|
||||
| `keyword` | Query | String | 否 | 可匹配车牌、品牌型号、车型大类和常驻司机姓名 |
|
||||
| `limit` | Query | Integer | 否 | 由 Fleet 按默认 10、最大 20 处理 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
GET 请求无请求体。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
响应类型:`Result<List<SettlementVehicleOptionRespVO>>`。
|
||||
|
||||
### 5.1 统一响应外层
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `code` | Integer | 否 | 成功为 `200`;失败见 §7 |
|
||||
| `message` | String | 否 | 结果说明 |
|
||||
| `data` | Array | 失败时为空 | 成功时为车辆下拉选项数组 |
|
||||
| `success` | Boolean | 否 | `code=200` 时为 `true` |
|
||||
|
||||
### 5.2 `data[]` 字段(共 8 个)
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `vehicleId` | String(Long) | 否 | 车辆 ID |
|
||||
| `plate` | String | 否 | 车牌号 |
|
||||
| `modelName` | String | 否 | 车型名称 |
|
||||
| `typeName` | String | 否 | 车型大类 |
|
||||
| `primaryDriverId` | String(Long) | 是 | 常驻司机 ID |
|
||||
| `primaryDriverName` | String | 是 | 常驻司机姓名 |
|
||||
| `label` | String | 否 | 下拉展示文案(车牌/车型/大类/司机拼接) |
|
||||
| **`dayPrice`** | **Decimal** | **是** | **车型当日牌价(元/车天),本次新增**;语义见下 |
|
||||
|
||||
### 5.3 `dayPrice` 语义(**重点,前端必读**)
|
||||
|
||||
- **是参考价,不是最终核算价**:`dayPrice` 来自 fleet 车型定价日历(按订单出发日 `departDate` 取当日牌价,未定价日回退车型 `basePrice`)。
|
||||
- **最终核算价以派单后车辆快照 `dailyPrice` / 核单行 `amount` 为准**。前端展示 `dayPrice` 时应标注"参考价",避免误导核单人员把它当成结算价。
|
||||
- **fleet 未上线该字段前 `dayPrice` 为 `null`**(向前兼容);fleet 侧现已上线(配套 Issue #5706),正常出值。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次不涉及枚举新增或改值。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---:|---|---|
|
||||
| `400` | 请求参数错误 | `orderId` 非法(如 0、负数、非数字) |
|
||||
| `581007` | 订单不存在 | `orderId` 对应订单不存在 |
|
||||
| `584071` | 无权访问该订单 | 当前账号不在订单可访问范围内 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功 —— 返回含 dayPrice
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/vehicle-options?keyword=汉兰达&limit=10
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"vehicleId": "301",
|
||||
"plate": "蒙A88888",
|
||||
"modelName": "丰田汉兰达",
|
||||
"typeName": "SUV",
|
||||
"primaryDriverId": "45",
|
||||
"primaryDriverName": "张师傅",
|
||||
"label": "蒙A88888 丰田汉兰达 SUV 张师傅",
|
||||
"dayPrice": "1000.00"
|
||||
},
|
||||
{
|
||||
"vehicleId": "302",
|
||||
"plate": "蒙A66666",
|
||||
"modelName": "丰田汉兰达",
|
||||
"typeName": "SUV",
|
||||
"primaryDriverId": "46",
|
||||
"primaryDriverName": "李师傅",
|
||||
"label": "蒙A66666 丰田汉兰达 SUV 李师傅",
|
||||
"dayPrice": "1000.00"
|
||||
}
|
||||
],
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况 —— 车型当日未定价时 dayPrice 回退 basePrice 或为 null
|
||||
|
||||
**场景说明**:某车型在订单出发日未配置定价日历时,后端回退取车型 `basePrice`;若 fleet 侧未上线该字段则返回 `null`。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/vehicle-options?keyword=别克&limit=10
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"vehicleId": "305",
|
||||
"plate": "蒙A11111",
|
||||
"modelName": "别克GL8",
|
||||
"typeName": "MPV",
|
||||
"primaryDriverId": "47",
|
||||
"primaryDriverName": "王师傅",
|
||||
"label": "蒙A11111 别克GL8 MPV 王师傅",
|
||||
"dayPrice": null
|
||||
}
|
||||
],
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
前端拿到 `dayPrice: null` 时应展示为 "—" 或不显示该行参考价,**不要展示为 `0`**。
|
||||
|
||||
### 8.3 业务失败 —— orderId 非法返 400
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/0/settlement/vehicle-options?keyword=汉兰达
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
无请求体。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{"code": 400, "message": "请求参数错误", "data": null, "success": false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用场景**:核单车辆下拉展示参考牌价,辅助核单人员选车预估金额。
|
||||
- **不适用场景**:**不要把 `dayPrice` 当作最终核算价**展示在结算明细里;最终价以派单后快照 `dailyPrice` / 核单行 `amount` 为准。
|
||||
- **特殊边界**:`dayPrice` 为 `null` 时表示 fleet 侧暂无该车型当日定价数据,不等于"免费";展示时应与 `0.00` 区分。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| `data[].dayPrice` | 不存在 | **新增**;Decimal,可空 |
|
||||
|
||||
其余 7 个字段(`vehicleId`/`plate`/`modelName`/`typeName`/`primaryDriverId`/`primaryDriverName`/`label`)无变化。
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 车辆下拉展示 | 只有车牌/车型/司机 | 新增当日参考牌价 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。仅出参新增字段,旧前端不读 `dayPrice` 即可继续工作。
|
||||
- **前端是否必须同步上线**:否。字段为增强信息,前端可择机上线展示。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 回滚即出参不再返回 `dayPrice`;已上线的新前端应把 `dayPrice` 作为可选字段处理(读到就展示、读不到就不展示),天然兼容回滚。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **`dayPrice` 是参考价**,前端展示建议加"参考价"标注,避免与最终核算价混淆。
|
||||
- fleet 侧配套改动见 Issue #5706;fleet 未上线前 `dayPrice` 恒为 `null`,前端需做 null 兜底展示。
|
||||
- `dayPrice` 单位为元/车天(整段行程 1 天的价格),不是整段总价。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5708](https://git.1814.love:8443/wx/HL/issues/5708)
|
||||
- **PR**: [#5713](https://git.1814.love:8443/wx/HL/pulls/5713)
|
||||
- **Merge commit**: [10ba85d65](https://git.1814.love:8443/wx/HL/commit/10ba85d65eca08fa5b1f9da60a4e8c9cfd318938)
|
||||
- **配套 fleet 侧 Issue**: [#5706](https://git.1814.love:8443/wx/HL/issues/5706)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst)
|
||||
- **fleet 侧对接**: @wx
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin@a9ddbd48)
|
||||
|
||||
- 前端已接该下拉接口:`use-settlement-vehicle-options.js` 此前 `OPTION_FIELDS` 只投影 7 个契约字段(不含 dayPrice)。
|
||||
- 修复:OPTION_FIELDS 与候选 option 增加 `dayPrice`(null 归一为 null);CategoryTable `renderVehicleSelect` 新增 `renderLabel(option, selected)`——**下拉项**追加「参考价 ¥x/车天」角标(teleport 弹层用内联样式,scoped 选不中),**选中态**只显示车牌文案不带参考价;`dayPrice` 为 null 时显「—」,不当作 0 也不当最终核算价。
|
||||
- 保存契约安全:`select()` 写回 source 的字段显式列出(vehicleId/vehicleModelId/vehicleModelName/driverId/driverName),dayPrice 不进 source、不进 step3/vehicles 保存 payload。
|
||||
- 验证:settlement 全量定向 vitest 93/93 通过(含候选 option 携带 dayPrice 断言);checkpoint 全绿。
|
||||
@@ -0,0 +1,273 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5712"
|
||||
title: "车辆核单 FLEET 行核算金额放开可编辑(amount 不再撞 584109)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a9ddbd48"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5715 已合并 dev-v3;PUT /v3/admin/order/{orderId}/settlement/step3/vehicles 对 FLEET 来源行的 amount 字段从只读放开为可编辑,结构字段仍受 584109 保护。前端若把 amount 渲染为只读输入框,应放开为可编辑。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】车辆核单 FLEET 行核算金额放开可编辑(#5712)
|
||||
|
||||
> **PR**: [#5715](https://git.1814.love:8443/wx/HL/pulls/5715) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-08
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单 Step3 车辆 Tab 的全量保存接口此前对 **FLEET(车务)来源行** 的 `amount`(核算金额)做只读保护:前端把 GET 回显的金额改大/改小后回传,会被错误码 `584109`「车务来源字段不可直接修改或删除」拦截,接口虽然返回 200 但 message 提示、金额实际不变。
|
||||
|
||||
业务诉求:核单人员需要能对车务来源的核算金额做手工修正(实际结算价与车务快照价不一致的场景,与住宿/门票核单的人工调价口径一致)。本次放开 FLEET 行 `amount` 可编辑,**改完即生效、不留审计痕**。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 全量保存车辆核单草稿 | PUT | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 行为放开(字段零变化) | FLEET 行 `amount` 输入框由只读改可编辑 |
|
||||
| 2 | 查询车辆核单草稿 | GET | `/v3/admin/order/{orderId}/settlement/step3/vehicles` | 出参语义变化 | `items[].amount` 现返回覆盖层修正后金额 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:核单人员在订单核单 Step3 车辆 Tab 维护车辆核单明细(全量覆盖式保存)。
|
||||
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||
- **幂等性**:是,按订单维度全量覆盖式保存,重复提交相同载荷结果一致。
|
||||
- **限流**:未声明接口专属限流。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `orderId` | Path | Long | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 | 本次是否放开 |
|
||||
|---|---|---|---|---|
|
||||
| `items` | Array | 是 | 全量明细 | — |
|
||||
| `items[].id` | Long | 条件必填 | 既有行必传;新增手工行为 null | 否(仍受 584109 保护) |
|
||||
| `items[].sourceType` | String | 是 | `FLEET` / `MANUAL` | 否 |
|
||||
| `items[].serviceDate` | String(date) | 是 | `yyyy-MM-dd` | 否(仍受 584109 保护) |
|
||||
| `items[].vehicleId` | Long | 条件必填 | FLEET 行必传 | 否(仍受 584109 保护) |
|
||||
| `items[].vehiclePlate` | String | 否 | 最长 64 | 否(仍受 584109 保护) |
|
||||
| `items[].vehicleModelId` | Long | 否 | — | 否(仍受 584109 保护) |
|
||||
| `items[].vehicleModelName` | String | 否 | 最长 128 | 否(仍受 584109 保护) |
|
||||
| `items[].driverId` | Long | 否 | — | 否(仍受 584109 保护) |
|
||||
| `items[].driverName` | String | 否 | 最长 64 | 否(仍受 584109 保护) |
|
||||
| `items[].amount` | Decimal | 是 | 非负、最多 2 位小数 | **本次放开为可编辑** |
|
||||
| `items[].paymentMethod` | String | 是 | `CASH_PAID` / `SIGNED` / `COMPANY_PAID` | 否(仍受 584109 保护) |
|
||||
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED` / `CONFIRMED` | 否(本就可改) |
|
||||
| `items[].remark` | String | 否 | 最长 500 | 否(本就可改) |
|
||||
| `items[].voucherUrls` | String[] | 否 | 最多 9 个 URL | 否(本就可改) |
|
||||
|
||||
> **FLEET 行编辑约定**:`vehicleFeeLineId`(即 GET 回显的 `items[].id`)、`serviceDate`、`vehicleId`、`vehiclePlate`、`vehicleModelId`、`vehicleModelName`、`driverId`、`driverName`、`paymentMethod` 这 9 个**车务来源结构字段不允许改**,前端应把这些字段**原样回传**(来自 GET step3/vehicles 的回显值),只允许改 `amount`(以及 `remark` / `voucherUrls` / `settlementConfirmStatus`)。改了结构字段仍撞 `584109`。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
成功响应为统一 `Result` 结构,`code=200`。`data` 出参字段(`orderId` / `totalAmount` / `allConfirmed` / `settlementReady` / `blockReasonCode` / `items` / `frozen`)结构无变化。
|
||||
|
||||
**唯一语义变化**:GET `items[].amount` 现在返回**覆盖层修正后的金额**(改完再查就是新值),不再被 fleet 快照价覆盖。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次不涉及枚举新增或改值。`paymentMethod`(CASH_PAID/SIGNED/COMPANY_PAID)、`settlementConfirmStatus`(UNCONFIRMED/CONFIRMED)、`sourceType`(FLEET/MANUAL)取值不变。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---:|---|---|
|
||||
| `584109` | 车务来源字段不可直接修改或删除 | 改了 FLEET 行的结构字段(serviceDate/vehicleId/vehiclePlate/vehicleModelId/vehicleModelName/driverId/driverName/paymentMethod/vehicleFeeLineId)或试图删除 FLEET 行 |
|
||||
| `400` | 请求数据格式错误 | `amount` 为负数 / 小数位超过 2 位 / 数值超出范围 / 其他 Bean 校验失败 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功 —— FLEET 行 amount 由 800 改为 950
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "9001",
|
||||
"sourceType": "FLEET",
|
||||
"serviceDate": "2026-08-08",
|
||||
"vehicleId": "301",
|
||||
"vehiclePlate": "蒙A88888",
|
||||
"vehicleModelId": "21",
|
||||
"vehicleModelName": "丰田汉兰达",
|
||||
"driverId": "45",
|
||||
"driverName": "张师傅",
|
||||
"amount": "950.00",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"remark": "实际结算价高于快照",
|
||||
"voucherUrls": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
|
||||
```
|
||||
|
||||
随后 GET 同一订单 step3/vehicles,`items[].amount` 返回 `"950.00"`。
|
||||
|
||||
### 8.2 边界情况 —— amount 改为 0.00 仍合法
|
||||
|
||||
**场景说明**:金额下限为 `0.00`,0 元合法(不是"未填")。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "9001",
|
||||
"sourceType": "FLEET",
|
||||
"serviceDate": "2026-08-08",
|
||||
"vehicleId": "301",
|
||||
"vehiclePlate": "蒙A88888",
|
||||
"vehicleModelId": "21",
|
||||
"vehicleModelName": "丰田汉兰达",
|
||||
"driverId": "45",
|
||||
"driverName": "张师傅",
|
||||
"amount": "0.00",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"remark": null,
|
||||
"voucherUrls": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{"code": 200, "message": "success", "data": {"saved": true}, "success": true}
|
||||
```
|
||||
|
||||
### 8.3 业务失败 —— 改了 FLEET 行结构字段 vehiclePlate 撞 584109
|
||||
|
||||
**场景说明**:仅放开 `amount`;结构字段(如车牌)仍受保护。
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/step3/vehicles
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "9001",
|
||||
"sourceType": "FLEET",
|
||||
"serviceDate": "2026-08-08",
|
||||
"vehicleId": "301",
|
||||
"vehiclePlate": "蒙A99999",
|
||||
"vehicleModelId": "21",
|
||||
"vehicleModelName": "丰田汉兰达",
|
||||
"driverId": "45",
|
||||
"driverName": "张师傅",
|
||||
"amount": "950.00",
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"settlementConfirmStatus": "UNCONFIRMED",
|
||||
"remark": null,
|
||||
"voucherUrls": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{"code": 584109, "message": "车务来源字段不可直接修改或删除", "data": null, "success": false}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用场景**:FLEET 行 `amount` 可任意改(非负、最多 2 位小数);改完即生效,**不留审计痕**,与住宿/门票核单人工调价口径一致。
|
||||
- **不适用场景**:FLEET 行的结构字段(车牌 / 司机 / 服务日期 / 车型 / 付款方式 / 行 id)仍不允许改;如需换车换司机请走车务派单链路,不要在核单页改。
|
||||
- **特殊边界**:`amount` 改为 `0.00` 是合法值,不是"清空";MANUAL 行的所有字段本就可改,本次无变化。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| FLEET 行 `amount` | 只读;改值撞 `584109`(PUT 返回 200 但 message 提示、金额不变) | **可编辑**;值合法即落库 |
|
||||
| FLEET 行结构字段(车牌/司机/日期/车型/付款方式) | 只读,撞 `584109` | 仍只读,撞 `584109`(不变) |
|
||||
| GET `items[].amount` | 返回 fleet 快照价 | 返回**覆盖层修正后**金额 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 前端把 FLEET 行 amount 改大/改小回传 | 被 `584109` 拦截,金额不变 | 正常保存,GET 回读新值 |
|
||||
| 前端改 FLEET 行 vehiclePlate | `584109` | `584109`(不变) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。接口签名、字段、错误码零变化;仅放开一个字段的编辑限制。
|
||||
- **前端是否必须同步上线**:否。旧前端继续把 amount 渲染为只读输入框不影响功能;但建议尽快放开为可编辑以匹配新口径。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 回滚即恢复 amount 只读保护,旧前端(amount 只读)天然兼容;已改为可编辑的新前端在旧后端上保存会重新撞 `584109`,需前后端同批回滚。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 本次仅放开 `amount`;**不要把 FLEET 行其他结构字段也放开为可编辑**。
|
||||
- 前端在 FLEET 行编辑表单里,`amount` 用 number input 即可,提交前按"非负、最多 2 位小数"做一次本地校验可减少 400。
|
||||
- 若前端历史上有"FLEET 行 amount 改了被静默回滚"的 workaround(如改完强刷 GET 重新渲染),本次后可保留也可简化,无破坏性。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5712](https://git.1814.love:8443/wx/HL/issues/5712)
|
||||
- **PR**: [#5715](https://git.1814.love:8443/wx/HL/pulls/5715)
|
||||
- **Merge commit**: [d4fce1230](https://git.1814.love:8443/wx/HL/commit/d4fce12309dd3c2ad040ee5d71e9734cb1b8ad5b)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst)
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin@a9ddbd48)
|
||||
|
||||
- 前端 FLEET 行 amount 此前确为只读:`returnDetailAdapter.js adaptVehicleRows` 对 FLEET 行 `editableFields=['confirmStatus','note']`(缺 amount),CategoryTable `rowFieldEditable` 据此把 amount 渲染为只读。
|
||||
- 修复:FLEET 行 editableFields 改为 `['amount','confirmStatus','note']`,amount 输入框放开可编辑;结构字段(车牌/司机/日期/车型/付款方式/id)仍不在 editableFields,保持只读(与后端 584109 保护一致)。凭证列由 `canEditRowVoucher` 独立判定(不看 editableFields),本就开放,无需改。
|
||||
- 保存链路 `buildVehicleSaveRequest` 本就把 `row.amount` 透传进 `items[].amount`、结构字段从 `source.*` 原样回传,无需改——金额改完随全量保存生效。
|
||||
- 验证:settlement 全量定向 vitest 93/93 通过(含更新 `editableFields` 断言);checkpoint 全绿。
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5725"
|
||||
title: "confirm 确认执行两路径一致解除「通知结果确认中」拦截(含原子引擎整组路径)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5726 已合并 dev-v3 并部署 TEST(hl-fleet-service 双实例)。confirm 单段与整组(需求级原子引擎)两路径一致解除 AMBIGUOUS 605042 拦截;实证 26-5484(修复前 605042)与回归 26-8917 均 confirm 200 assigned。接口响应结构不变,无前端消费变化。"
|
||||
updated_at: "2026-08-08"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T21:45:00+08:00"
|
||||
---
|
||||
|
||||
# confirm 确认执行两路径一致解除「通知结果确认中」拦截(含原子引擎整组路径)
|
||||
|
||||
> 后端完成:PR [#5726](https://git.1814.love:8443/wx/HL/pulls/5726) 已合并 dev-v3 并部署 TEST,网关全链路验证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5725](https://git.1814.love:8443/wx/HL/issues/5725)
|
||||
- **PR**: [#5726](https://git.1814.love:8443/wx/HL/pulls/5726)
|
||||
- **Merge commit**: [483cfd1ba](https://git.1814.love:8443/wx/HL/commit/483cfd1ba)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
#5716 修复时只移除了单段 confirm **legacy 路径**(无派车方案代际)的「通知结果确认中」校验;**有代际的确认走需求级原子引擎路径**(`confirmSingletonThroughRequirementEngine` → `confirmRequirement`),其中 `isRequiredHoldingGroup` 分支残留同类校验,AMBIGUOUS 仍 605042。
|
||||
|
||||
实证对照(TEST):26-5484(单代 finalized=1 → 原子路径)confirm 被 605042 拦;26-8917(成功时刻为旧行 finalized=0/orphaned 代际 → legacy 路径)放行——同一 AMBIGUOUS 状态结果不一致的根因。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`POST /admin/fleet/assignments/{assignmentId}/confirm`(单段确认执行)与需求级整组确认(内部共用原子引擎 `confirmRequirement`)在 HOLD 通知结果仍确认中(AMBIGUOUS,短信已发未回执)时,**一律不再返回 605042**,确认执行正常推进 assigned——与 #5701/#5716 已解除的单段 legacy 路径口径一致。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| HOLD 通知结果确认中(AMBIGUOUS)+ 司机已登记确认 + 单段 legacy 路径 | 200(#5716 已解除) | 200 |
|
||||
| HOLD 通知结果确认中(AMBIGUOUS)+ 司机已登记确认 + **有代际(原子引擎/整组)路径** | **605042** | **200 推进 assigned** |
|
||||
|
||||
### 保留不变的保护
|
||||
|
||||
- 司机确认登记缺失(未确认/无有效来源):仍拦截(`DRIVER_CONFIRMATION_REQUIRED`)。
|
||||
- NOTIFICATION 来源(司机回复系统通知)仍需通知 SENT 事实;AMBIGUOUS 下 NOTIFICATION 来源返回 400 提示人工联系来源(#5701 口径,`assertAtomicReplySourceIdentity`)。
|
||||
- 其余 605042 保护点(expand 换版 / cancel / change / undo / 拒接 / 恢复通知等**通知代际推进与身份失效**场景)保留原拦截。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **网关实证(TEST,VEHICLE_MANAGER)**:
|
||||
- 26-5484(订单 2086077200523558914,AID 2086077201060397057):holding + AMBIGUOUS(通知日志 delivery_status=dispatching)+ 司机已确认(21:20:15)+ 有代际 finalized=1 → `confirm`(sendItinerarySms=false)→ **200 confirmed=true status=assigned**(修复前 605042)
|
||||
- 回归 26-8917(AID 2086050105051250690):同 AMBIGUOUS + 有代际 → confirm **200 confirmed=true status=assigned**
|
||||
- 实证后通知日志恢复原状(canceled),两单保持 assigned 正常业务流转
|
||||
- **测试**:新增 `confirmRequirement_holdNotificationAmbiguous_proceedsToAssigned`;AssignmentServiceTest 404/404 全绿;fleet verify 仅基线 ReleaseEOccupancyMysql8033RecoveryTest 失败(与本案无关)
|
||||
|
||||
## 前端交接
|
||||
|
||||
无接口契约变化(响应结构、字段、错误码集合均不变,仅 605042 出现场景收敛到既有口径)。
|
||||
|
||||
## 前端实证确认(2026-08-08 mmg,hl-admin)
|
||||
|
||||
确认 not_required 属实,零代码改动:
|
||||
|
||||
1. **全 `src/` 零 605042 引用**:grep 无任何 605042 特判/重试引导;confirm 业务错误统一走 `request.js` 弹后端 message,后端不再返回即前端不再展示。
|
||||
2. **confirm 两路径前端入口均无 605042 处理**:单段 `useAssignFlow.js:1095 confirmAssignment` 与整组/原子引擎路径 `useAssignFlow.js:1088 confirmRequirementAssignments` 都无错误码特判,后端解除拦截后天然受益。
|
||||
3. **AMBIGUOUS 前端展示与 confirm 拦截无关**:仅 `HoldNotificationStatus.vue:178/231`(发送结果标签 + canRetry fail-closed 守卫)与 `OrderDrawer.vue:708`(状态标签)消费 AMBIGUOUS,属通知发送结果展示,本变更(confirm 执行拦截)不触碰,行为不变。
|
||||
4. 与 #5701(登记确认解除 605042)/#5716(confirm 单段 legacy 路径解除)同系列收尾:本条把原子引擎整组路径对齐到同一口径,前端无对应差异逻辑。
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5744"
|
||||
title: "改派后订单 vehicle_control_status 卡 PROCESSING 不推进 DONE(五层根因修复)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T16:20:00+08:00"
|
||||
---
|
||||
|
||||
# 08_5744_改派后订单vehicle_control_status卡PROCESSING修复-修改接口-管理后台
|
||||
|
||||
- **date**: 2026-08-09
|
||||
- **ticket**: 5744
|
||||
- **change_type**: 修改接口
|
||||
- **service**: hl-fleet-service, hl-order-service-v3
|
||||
- **frontend_status**: not_required
|
||||
- **status_note**: 修复改派(含接续/按天改派)后订单 vehicle_control_status 卡 PROCESSING 不回写 DONE 的问题(实证 26-6436)。五层修复全部为 fleet→order 快照回写链路的行为修正,前端无改动项;订单流程条由后端回写自然推进。## 变更接口或验证证据
|
||||
|
||||
### 接口契约
|
||||
|
||||
**无契约变更**:POST /admin/fleet/assignments/:assignmentId/change、/:assignmentId/confirm、/:assignmentId/driver-confirmation、/requirements/:requirementId/confirm 的请求/响应字段、错误码均不变;internal 新增补偿端点 POST /internal/fleet/jobs/vehicle-assignment-snapshot/refinalize(运维补偿用,不影响对外契约)。
|
||||
|
||||
### 验证证据
|
||||
|
||||
- **根因(五层,代码级实证 26-6436 全程行+改派)**:
|
||||
1. `isFinalizedDispatchPlanTopologyRows` 全程槽(serviceDate=null 单行覆盖全程)逐日覆盖检查误判 → HOLD 确认/改派确认的 finalPlan 事件不发布(直派无条件发布故正常);
|
||||
2. 改派生成的全程行携带非空 assignment_group_id,快照工厂 isFullTripRow 要求 groupId 为空 → 快照按逐日拓扑校验失败,confirm 回滚;
|
||||
3. order 侧最终确认 fence 占用(582092,暂时性)被快照投递按业务错误隔离而非重试;
|
||||
4. 隔离事件计入前驱阻塞 → 后续 revision 永久 PENDING;
|
||||
5. order 侧未初始化(contract_version 为空)强绑 revision=1,fleet 重发更高 revision 永远冲突。
|
||||
- **修复**:#5745/#5747/#5748/#5749/#5753 五 PR + 追加验收 #5761/#5763(混合拓扑按槽位分桶校验+nullsFirst 排序;当前代身份排除同槽被替换的 canceled 旧行),fleet 4 处 + order 1 处 + 补偿端点;
|
||||
- **测试**:AssignmentServiceTest 408/408(含 canceled 旧全程行回归)、FactoryTest 14/14(含混合拓扑/逐日槽缺天)、GenerationServiceTest 15/15、OutboxSenderTest 26/26、OutboxMapperTest 13/13、order DailyVehicleAssignmentSnapshotServiceTest 17/17;fleet 全量 verify 3313 tests 失败项全部归账(ReleaseEMixedBinaryHarnessTest 2F + ReleaseEOccupancyMysql8033RecoveryTest 10E 为既有环境类基线);
|
||||
- **部署自验(TEST 实单 26-6436 全链)**:change(HOLD)→driver-confirmation→confirm 全部成功;快照 rev=5 生成;fence 占用期间投递重试(retry_count=6, HTTP_582092,非隔离);fence 过期后自动投递 SUCCESS;**order_main.vehicle_control_status PROCESSING→DONE、order_vehicle_requirement.status=DONE、contract=DAILY_V3 rev=6**;探针 4/4 PASS(evidence/5744/gateway-probe-output.txt)。追加验收:带 groupId 全程行/逐日行混合拓扑 confirm 正常(单测+TEST 实链原子确认 rev=6 投递 DONE);直派全程行(groupId=null)与纯逐日回归保持。
|
||||
|
||||
## 前端交接
|
||||
|
||||
无。流程条「配车·处理中→下一步」由后端回写驱动,前端无需改动。
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-batch3slot"
|
||||
title: "多槽位(3 槽)派单提交 batchCreate 请求体缺顶层必填字段且丢槽,派单未创建"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端缺陷:3 槽位派单排车页点「下一步·发送给司机」,POST /admin/fleet/assignments/batch 请求体仅 {items:[2 个]}——缺 orderId/requirementId/startDate/endDate/holdMode/requestId 顶层必填字段,且 3 槽只发出 2 个 items(丢 1 槽)。后端返回 200 但未创建派单(看板 3 槽仍待派车)。2 槽位(26-6040)请求体完整、派单成功。后端无问题,待前端修复 3 槽位 batchCreate 请求体构造。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T22:10:00+08:00"
|
||||
---
|
||||
|
||||
# 多槽位(3 槽)派单提交 batchCreate 请求体缺顶层必填字段且丢槽,派单未创建
|
||||
|
||||
> 前端缺陷,待前端修复。后端接口与逻辑无问题。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **前端负责人**: @mmg
|
||||
- **后端负责人**: @wx(已确认后端无问题)
|
||||
|
||||
## 现象(TEST 实测,2026-08-08)
|
||||
|
||||
订单 26-4254(id=2086056645539885057,跨月 8/30-9/1,18 人,用车需求 **SUV×1 + 商务车×1 + 大巴×1 共 3 槽位**):
|
||||
|
||||
1. 排车页 3 个槽位逐日全部应用完成(页面显示「已完成 3/3 个最终方案槽位」,槽1 蒙A-E2E01 道尔吉 / 槽2 蒙A-E5555 阿木古愣 / 槽3 蒙C08E08 宝音德力格尔,各 3 天已确认)。
|
||||
2. 勾上 8/30 接机「参与」(消除「至少一辆用车必须参与接机」警告)后,点底部「**下一步 · 发送给司机(3 辆)**」。
|
||||
3. 抓包:`POST /admin/fleet/assignments/batch` 请求体为——
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{"orderId":"2086056645539885057","vehicleId":"2085284111341023234","driverId":"2065272150012444674","assignmentGroupId":"2086057368872779778","serviceDates":["2026-08-30","2026-08-31","2026-09-01"]},
|
||||
{"orderId":"2086056645539885057","vehicleId":"2064998142394183681","driverId":"2065272145633591298","assignmentGroupId":"2086057368876974082","serviceDates":["2026-08-30","2026-08-31","2026-09-01"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**两个错误**:
|
||||
- **顶层缺必填字段**:无 `orderId` / `requirementId` / `startDate` / `endDate` / `holdMode` / `requestId`(这些字段只在每个 item 里出现了 orderId/assignmentGroupId,顶层全缺)。
|
||||
- **丢槽**:3 个槽位只发出 **2 个 items**(丢了 1 个槽)。
|
||||
|
||||
4. 结果:后端返回 200,但**看板 3 槽位仍全部「待派车」**(activeAssignments=0),派单未创建。
|
||||
|
||||
## 对比(正常)
|
||||
|
||||
订单 26-6040(**2 槽位**)同样操作:batchCreate 请求体含完整顶层字段(orderId/requirementId/startDate/endDate/holdMode/requestId)+ 2 个 items,返回 200 且派单正常创建(holding)。
|
||||
|
||||
## 期望
|
||||
|
||||
3 槽位(多槽位)派单提交时,batchCreate 请求体应:
|
||||
- 顶层携带完整必填字段:`orderId` / `requirementId` / `startDate` / `endDate` / `holdMode` / `requestId`;
|
||||
- `items` 包含**全部槽位**(3 槽发 3 个 item),不丢槽;
|
||||
- 每个 item 的 `fleetItemIndex` / `vehicleId` / `driverId` / `serviceDates` 与各槽位选择一致。
|
||||
|
||||
## 复现路径
|
||||
|
||||
派单看板 → 26-4254 → 派车派人 → 下一步 → 3 个槽位分别「统一选择车辆/司机」选好车+司机应用到槽位(3/3)→ 勾 8/30 接机「参与」→ 点「下一步 · 发送给司机(3 辆)」→ 抓 batchCreate 请求体。
|
||||
|
||||
## 备注
|
||||
|
||||
- 单槽位派单(AssignModal 单体 createAssignment,走 `useAssignFlow.handleSubmit`,payload 含完整字段)正常;问题在**多槽位排车页的批量提交**路径。
|
||||
- 后端 batchCreate 接口对缺顶层字段的请求返回 200 而非 400 参数校验,建议后端顺带评估是否应 fail-fast(另案,不阻塞本前端修复)。
|
||||
|
||||
## 前端实证确认(2026-08-09 mmg,hl-admin v2.1)
|
||||
|
||||
该缺陷对应**已被替换的旧批量实现**,当前 v2.1 不满足复现条件,无需改动:
|
||||
|
||||
- **形态不符**:抓包的 `{items:[...]}`(缺顶层必填字段 + 3 槽发 2 个)是 `69847975 多车槽位原子批量派车` 的旧 items 形态;`7b99fe7d 支持逐日逐车派车方案`起已改为**逐日逐车 dailyPlan 模型**。
|
||||
- **顶层字段齐全**:当前 `buildBatchAssignmentSubmission`(`useAssignFlow.js:375`)产出的 `data` 含完整顶层必填字段 `orderId/orderNo/requirementId/startDate/endDate/headcount/holdMode/fromEntry/requestId` + `dailyPlan`,**无 `items` 键**;测试 `useAssignFlow.spec.js:271` 明确断言 `data 不含 items`。
|
||||
- **丢槽结构性不可能**:`validateDailyVehiclePlan`(`daily-vehicle-plan.js:518-527`)对每个服务日 × 每个槽位序号做笛卡尔积完整性校验,3 槽 × 3 天必须满 9 格,缺任一格在提交前即 `throw` 拦截(`服务日 X 缺少车辆槽位 N`),不会发出丢槽请求。
|
||||
- **结论**:TEST 抓包来自旧前端版本。当前 v2.1 批量派单顶层字段齐全且丢槽被结构校验拦截,本单关闭为 not_required。
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-reassign-display"
|
||||
title: "改派选完车辆/司机后,槽位「当前车辆/当前司机」列与按天改派表格不回显新选择"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "82f5b771"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端缺陷:改派选完车辆/司机(完成选择)后,槽位顶部列与按天改派表格均不回显新选择。选择已在前端草稿(底部可见)且可正常提交生效,纯前端回显未刷新。前端已修复(cb763a74):选完后两处列立即回显新选择。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-08T22:50:00+08:00"
|
||||
---
|
||||
|
||||
# 改派选完车辆/司机后,槽位列与按天改派表格不回显新选择
|
||||
|
||||
> 前端缺陷,待前端修复。后端无问题(改派可正常提交生效)。
|
||||
|
||||
## 现象(TEST 实测,订单 26-6436 周梦洁)
|
||||
|
||||
改派流程操作:改派 → 下一步 → 选择改派 → 清除车辆/司机 → 选择车辆/司机 → 弹窗选**蒙A-T7777 + 宝音德力格尔** → 点「完成选择」。
|
||||
|
||||
回到排车页后(用户实测标注):
|
||||
- **槽位表格顶部**:「当前车辆」「当前司机」列——有时回显新选(蒙A-E2E99 王信),有时仍显示「待重新选择」,**不稳定**。
|
||||
- **按天改派表格**(3 个服务日 8/23~8/25):「当前车辆」「当前司机」列**始终仍显示旧派单**「**蒙A-E2E01 道尔吉**」(未回显新选)——这是主要问题。
|
||||
- **底部改派草稿**正确显示「蒙A-T7777(丰田普拉多)· 宝音德力格尔 135\*\*\*\*5009」——选择已被前端记录,只是上面两处列没刷新。
|
||||
|
||||
用户以为没选上,易困惑/重复操作。
|
||||
|
||||
## 涉及接口
|
||||
|
||||
### 1. 派单详情(回显数据源)
|
||||
|
||||
```
|
||||
GET https://api.test.1814.love:9443/admin/fleet/board/orders/{orderId}
|
||||
```
|
||||
|
||||
路径参数:`orderId`(如 2086270156475936769,团号 26-6436)
|
||||
|
||||
响应(节选,data 节点)——**这是改派前的当前派单事实**:
|
||||
```json
|
||||
{
|
||||
"currentAssignment": {
|
||||
"id": "2086276434870853634",
|
||||
"vehiclePlate": "蒙A-E2E01",
|
||||
"driverName": "道尔吉",
|
||||
"assignmentStatus": "assigned"
|
||||
},
|
||||
"vehicleSlots": [{
|
||||
"fleetItemIndex": 0,
|
||||
"vehiclePlate": "蒙A-E2E01",
|
||||
"driverName": "道尔吉",
|
||||
"slotStatus": "assigned",
|
||||
"vehicleId": "2085284111341023234",
|
||||
"driverId": "2065272150012444674"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:改派「完成选择」后,新选择只在前端**草稿态**(尚未提交 change),此时槽位列/按天表格若仍读 `currentAssignment`/`vehicleSlots`(即旧 assigned 派单),就永远显示旧值。前端应在选完后用**草稿的新 vehicleId/driverId(车辆车牌/司机姓名)**覆盖回显这两处列,而不是继续展示 assigned 事实。
|
||||
|
||||
### 1b. 按天改派表格数据源(重点问题的具体接口)
|
||||
|
||||
按天改派表格(3 个服务日的「当前车辆/当前司机」列)读的是**同一个详情接口的 `dailyVehiclePlan` 字段**:
|
||||
|
||||
```
|
||||
GET https://api.test.1814.love:9443/admin/fleet/board/orders/{orderId}
|
||||
```
|
||||
|
||||
响应 `data.dailyVehiclePlan`(数组,每个服务日一项)实际值(改派前的旧派单):
|
||||
```json
|
||||
[
|
||||
{"serviceDate":"2026-08-23","vehiclePlate":"蒙A-E2E01","vehicleModel":"丰田普拉多","driverName":"道尔吉","driverPhone":"135****5014","assignmentStatus":"assigned","planState":"USED","vehicleId":"2085284111341023234","driverId":"2065272150012444674"},
|
||||
{"serviceDate":"2026-08-24", "...": "同上(蒙A-E2E01 道尔吉)"},
|
||||
{"serviceDate":"2026-08-25", "...": "同上"}
|
||||
]
|
||||
```
|
||||
|
||||
**问题点**:改派「完成选择」后,新选择在前端草稿态(`newVehicleId/newDriverId`),但按天表格继续读 `dailyVehiclePlan[].vehiclePlate/driverName`(旧 assigned 派单)渲染——所以始终显示旧车旧司机。前端应在选完后用草稿的新车/新司机覆盖 `dailyVehiclePlan` 各天的回显(或重新拉取草稿态的逐日方案接口,而非 assigned 事实)。
|
||||
|
||||
### 2. 改派提交(选择真正生效)
|
||||
|
||||
```
|
||||
POST https://api.test.1814.love:9443/admin/fleet/assignments/{assignmentId}/change
|
||||
```
|
||||
|
||||
路径参数:`assignmentId`(当前派单 id,如 2086276434870853634)
|
||||
|
||||
请求体(示例):
|
||||
```json
|
||||
{
|
||||
"newVehicleId": "蒙A-T7777对应vehicleId",
|
||||
"newDriverId": "宝音德力格尔对应driverId",
|
||||
"effectiveDate": "2026-08-23",
|
||||
"holdMode": 1,
|
||||
"reason": "改派",
|
||||
"requestId": "front-change-<ts>"
|
||||
}
|
||||
```
|
||||
|
||||
响应:`code=200` 成功,旧行取消、新行生效。**此接口实测正常**(改派提交后车辆/司机正确变更)——所以是纯前端回显问题。
|
||||
|
||||
## 期望
|
||||
|
||||
改派「完成选择」后(草稿态、未提交前),以下两处立即回显新选的车辆+司机(与底部草稿一致):
|
||||
1. 槽位表格顶部「当前车辆」「当前司机」列;
|
||||
2. 按天改派表格每个服务日的「当前车辆」「当前司机」列。
|
||||
|
||||
即:选完后前端用草稿的 `newVehicleId/newDriverId`(解析出车牌/司机名)覆盖这两处的展示,让用户明确看到改派结果。
|
||||
|
||||
## 复现路径
|
||||
|
||||
派单看板 → 已派车订单(如 26-6436)→ 改派 → 下一步 → 选择改派 → 清除车辆/司机 → 选择车辆/司机 → 弹窗选车+选司机 → 完成选择 → 观察槽位顶部列与按天改派表格的「当前车辆/当前司机」列。
|
||||
|
||||
## 备注
|
||||
|
||||
- 初次派单(AssignModal)选完应用后槽位列正常回显 ✅;问题仅在**改派(重新派单)流程**的「完成选择」回显环节。
|
||||
- 后端改派提交(POST /{id}/change)正常,选择已正确传入并生效——纯前端回显问题。
|
||||
|
||||
## 前端实证确认(2026-08-09 mmg,hl-admin@cb763a74 → 82f5b771)
|
||||
|
||||
已修复,整段与按天改派两处列选完后立即回显新选择:
|
||||
|
||||
- **根因**:改派清除车/司机后点「完成选择」,`selVehicle/selDriver` 草稿已写入但 `changeDraftCleared` 不复位(`applyResourceSelectionAndClose` 走 `day` scope 仅关抽屉即返回);槽位列空态判定 `isChangeRowClearedPending = isChangeRowSelected && changeDraftCleared`(`AssignModal.vue`)仍命中 → 停在「待重新选择」,与底部改派草稿不一致。
|
||||
- **修复(cb763a74)**:新增 `resolveChangeRowDraftDisplay`,改派选中行已选出新选择(`selVehicleObj/selDriverObj`,与底部草稿同源)时解析出行内展示对象;整段与按天改派的「当前车辆/当前司机」列在已选新选择时立即回显新选择,未选时仍显示「待重新选择」空态,未清除时读原快照。
|
||||
- **残留补修(82f5b771,对应本单 §1b 按天表格主问题)**:cb763a74 的按天分支只在「按天模式命中被点选那一天」时回显,**整段改派**(`selectedChangeDayDate` 为空)下按天表格所有服务日行仍走 else 读旧 assigned 快照 → 始终显示旧派单(即后端实测「按天表格始终显示蒙A-E2E01」)。放宽 `isChangeDayRowClearedPending`:按天改派仍只命中该天,整段改派选中槽位即命中其全部服务日行,由 `resolveChangeRowDraftDisplay` 回显新选择。
|
||||
- 验证:assign-modal 相关 3 spec 10/10 通过;checkpoint(含生产构建)全绿。
|
||||
@@ -0,0 +1,574 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5729"
|
||||
title: "核单报表(单团核算+报账表)出参明细数组 List<Map> 改为强类型 VO,并为所有枚举/字典 code 字段补充中文 xxxName 标签字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "4f5f4e47"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5743 已合并 dev-v3;单团核算 incomeLines/costCategories 与报账表 incomeLines/expenseLines/advanceLines/vehicleLines 共 6 个列表字段从 List<Map<String,Object>> 改为强类型 VO;新增 13 个 xxxName/statusText 中文标签字段;新增 5 个数据字典。出参有新增字段、无删除字段,旧字段名保持不变——前端不传这些新字段不影响,但应尽快适配以展示中文名。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T18:00:00+08:00"
|
||||
---
|
||||
|
||||
# 核单报表(单团核算+报账表)出参强类型化并补充中文标签字段(#5729)
|
||||
|
||||
> **PR**: [#5743](https://git.1814.love:8443/wx/HL/pulls/5743) | **Commit**: [db8974cc9](https://git.1814.love:8443/wx/HL/commit/db8974cc9) | **Merge**: [7b4c69aa8](https://git.1814.love:8443/wx/HL/commit/7b4c69aa8) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-09
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单模块的「单团核算报表」和「报账表」两个查询接口,此前明细数组(收入行、成本分类、支出行、预支行、车辆逐日行)返回类型为 `List<Map<String,Object>>`,前端无法生成 TypeScript 类型定义,且枚举/字典 code 字段(如 `type`、`category`、`channel`、`payType`、`collectorRole`、`staffRole`、`expenseType`、`subsidyType`、`advanceType`、`sourceType` 等)只有 code 没有对应中文名,前端需自行硬编码 code→label 映射。本次将 6 个列表字段改为强类型 VO,并为所有枚举/字典 code 新增对应的中文 `xxxName` / `statusText` 字段(后端走数据字典/枚举 label 回填),前端可直接展示中文名,逐步移除硬编码。
|
||||
|
||||
> 本次为出参扩展类变更:仅新增字段,无删除字段,旧字段名、类型、语义完全不变。前端不传/不读新字段不影响现有功能,但建议尽快适配以展示中文标签。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 查询单团核算表 | GET | `/v3/admin/order/{orderId}/settlement/reports/group` | 出参 `incomeLines[]` 从 Map 改 VO,新增 `typeName` | 类型适配,展示中文名 |
|
||||
| 2 | 查询单团核算表 | GET | `/v3/admin/order/{orderId}/settlement/reports/group` | 出参 `costCategories[]` 从 Map 改 VO,新增 `categoryName` | 类型适配,展示中文名 |
|
||||
| 3 | 查询报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 出参 `incomeLines[]` 从 Map 改 VO,新增 `typeName`/`channelName`/`payTypeName`/`collectorRoleName` | 类型适配,展示中文名 |
|
||||
| 4 | 查询报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 出参 `expenseLines[]` 从 Map 改 VO,新增 `categoryName`/`paymentMethodName`/`mealTypeName`/`expenseTypeName`/`subsidyTypeName`/`staffRoleName`/`sourceTypeName` | 类型适配,展示中文名 |
|
||||
| 5 | 查询报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 出参 `advanceLines[]` 从 Map 改 VO,新增 `typeName`/`payeeRoleName`/`advanceTypeName`/`statusText` | 类型适配,展示中文名 |
|
||||
| 6 | 查询报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 出参 `vehicleLines[]` 新增 `sourceTypeName` | 展示中文名 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:核单人员在订单核单页查看单团核算报表(收入与成本汇总)和报账表(收支明细与预支),用于财务复核与结算。
|
||||
- **认证**:需要管理后台登录态(Bearer Token)。
|
||||
- **幂等性**:GET 接口,天然幂等。
|
||||
- **限流**:未声明接口专属限流。
|
||||
- **方法/路径**:见 §2 变更清单。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
两个接口均为纯 GET 查询,仅路径参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `orderId` | Long | 是 | 订单 ID,路径参数,两个接口一致 |
|
||||
|
||||
无 Query 参数、无请求体。本次入参无变化。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
> 以下仅列出本次有变更的行级 VO 字段;RespVO 顶层字段(如 `baseOrderAmount`/`totalCost`/`grossProfit` 等汇总字段)**不变**,不重复列出。
|
||||
|
||||
### 5.1 单团核算收入行(SettlementGroupIncomeLineVO)
|
||||
|
||||
`incomeLines[]` 从 `List<Map>` 改为 `List<SettlementGroupIncomeLineVO>`,固定 4 行:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `type` | String | 是 | 行类型枚举 code:`BASE_ORDER`(订单应收)/ `OTHER_INCOME`(其他收入)/ `DISCOUNT`(优惠,负数)/ `ACTUAL_REFUND`(实际退款,负数) |
|
||||
| `typeName` ⭐新增 | String | 否 | 行类型中文名,走 `settlement_report_line_type` 字典;`BASE_ORDER`→订单应收、`OTHER_INCOME`→其他收入、`DISCOUNT`→优惠、`ACTUAL_REFUND`→实际退款 |
|
||||
| `amount` | BigDecimal | 是 | 金额,保留两位小数;DISCOUNT/ACTUAL_REFUND 为负数 |
|
||||
|
||||
### 5.2 单团核算成本分类行(SettlementGroupCostCategoryVO)
|
||||
|
||||
`costCategories[]` 从 `List<Map>` 改为 `List<SettlementGroupCostCategoryVO>`,固定 8 行:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `category` | String | 是 | 费用类别 code:`HOTEL` / `TICKET` / `MEAL` / `VEHICLE` / `GUIDE` / `PHOTOGRAPHER` / `OTHER_EXPENSE` / `INSURANCE` |
|
||||
| `categoryName` ⭐新增 | String | 否 | 费用类别中文名,走 `settlement_category` 字典;`HOTEL`→住宿、`TICKET`→门票/游玩项目、`MEAL`→餐食、`VEHICLE`→车辆、`GUIDE`→导游、`PHOTOGRAPHER`→摄影、`OTHER_EXPENSE`→其他支出、`INSURANCE`→保险 |
|
||||
| `amount` | BigDecimal | 是 | 金额,保留两位小数 |
|
||||
|
||||
### 5.3 报账表收入行(SettlementReimbursementIncomeLineVO)
|
||||
|
||||
`incomeLines[]` 从 `List<Map>` 改为 `List<SettlementReimbursementIncomeLineVO>`,目前仅 `DRIVER_CASH_RECEIPT`(司机现金收款)一类:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `type` | String | 是 | 行类型 code:当前固定 `DRIVER_CASH_RECEIPT` |
|
||||
| `typeName` ⭐新增 | String | 否 | 行类型中文名,走 `settlement_report_line_type` 字典;`DRIVER_CASH_RECEIPT`→司机现金收款 |
|
||||
| `receiptId` | Long | 否 | 线下收款记录 ID(Long,JSON 序列化为字符串) |
|
||||
| `amount` | BigDecimal | 是 | 收款金额,保留两位小数 |
|
||||
| `channel` | String | 否 | 收款渠道 code:`DRIVER_CASH` / `BANK_TRANSFER` / `CONSULTANT_COLLECTION` |
|
||||
| `channelName` ⭐新增 | String | 否 | 收款渠道中文名,走 `PaymentChannelEnum` 枚举 label;`DRIVER_CASH`→报账人收款、`BANK_TRANSFER`→银行转账、`CONSULTANT_COLLECTION`→顾问代收 |
|
||||
| `payType` | String | 否 | 收款款项类型 code:`DEPOSIT` / `FULL` / `BALANCE` |
|
||||
| `payTypeName` ⭐新增 | String | 否 | 收款款项类型中文名,走 `PayType` 枚举 label;`DEPOSIT`→定金、`FULL`→全款、`BALANCE`→尾款 |
|
||||
| `collectorStaffId` | Long | 否 | 收款人人员安排 ID(Long,JSON 序列化为字符串) |
|
||||
| `collectorName` | String | 否 | 收款人姓名 |
|
||||
| `collectorRole` | String | 否 | 收款人角色 code:如 `DRIVER` |
|
||||
| `collectorRoleName` ⭐新增 | String | 否 | 收款人角色中文名,走 `staff_role` 字典;`DRIVER`→司机等 |
|
||||
| `receivedAt` | String | 否 | 收款时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `remark` | String | 否 | 备注;无备注时为空 |
|
||||
|
||||
### 5.4 报账表支出行(SettlementReimbursementExpenseLineVO)
|
||||
|
||||
`expenseLines[]` 从 `List<Map>` 改为 `List<SettlementReimbursementExpenseLineVO>`,7 族稀疏联合(每行仅本族字段非 null):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `kind` | String | 是 | 行种类:`HOTEL` / `TICKET` / `MEAL` / `VEHICLE_FEE` / `STAFF:GUIDE` / `EXPENSE:FUEL` / `SUBSIDY:MEAL` 等 |
|
||||
| `category` | String | 是 | 费用类别 code:`HOTEL` / `TICKET` / `MEAL` / `VEHICLE` / `GUIDE` / `PHOTOGRAPHER` / `OTHER_EXPENSE` / `INSURANCE` |
|
||||
| `categoryName` ⭐新增 | String | 否 | 费用类别中文名,走 `settlement_category` 字典(同 §5.2) |
|
||||
| `amount` | BigDecimal | 是 | 实际金额,保留两位小数 |
|
||||
| `paymentMethod` | String | 是 | 付款方式:`CASH_PAID` / `COMPANY_PAID` / `SIGNED` |
|
||||
| `paymentMethodName` ⭐新增 | String | 否 | 付款方式中文名,走 `settlement_payment_method` 字典;`CASH_PAID`→现金已付、`COMPANY_PAID`→公司支付、`SIGNED`→签单 |
|
||||
| `voucherUrls` | Array of String | 否 | 凭证 URL 数组;无凭证时为空数组 |
|
||||
| `remark` | String | 否 | 备注 |
|
||||
| -- | -- | -- | **以下按族分组,仅本族字段非 null** |
|
||||
| `hotelAssignmentId` | Long | 否 | [HOTEL] 配房安排 ID |
|
||||
| `hotelId` | Long | 否 | [HOTEL] 酒店资源 ID |
|
||||
| `roomTypeId` | Long | 否 | [HOTEL] 房型 ID |
|
||||
| `dayNumber` | Integer | 否 | [HOTEL/TICKET] 行程第几天 |
|
||||
| `stayDate` | String | 否 | [HOTEL] 入住日期 yyyy-MM-dd |
|
||||
| `hotelName` | String | 否 | [HOTEL] 酒店名称 |
|
||||
| `roomType` | String | 否 | [HOTEL] 房型编码 |
|
||||
| `roomTypeName` | String | 否 | [HOTEL] 房型名称 |
|
||||
| `roomCount` | Integer | 否 | [HOTEL] 房间数 |
|
||||
| `unitPrice` | BigDecimal | 否 | [HOTEL/MEAL] 单价 |
|
||||
| `plannedCost` | BigDecimal | 否 | [HOTEL/TICKET] 计划成本 |
|
||||
| `sourceType` | String | 否 | [HOTEL/TICKET] 明细来源类型 code |
|
||||
| `sourceTypeName` ⭐新增 | String | 否 | [HOTEL/TICKET] 明细来源类型中文名,走 `SettlementDetailSourceType` 枚举 label |
|
||||
| `sourceId` | Long | 否 | [HOTEL] 来源记录 ID |
|
||||
| `scenicAssignmentId` | Long | 否 | [TICKET] 景区安排 ID |
|
||||
| `dayDate` | String | 否 | [TICKET] 游玩日期 yyyy-MM-dd |
|
||||
| `scenicName` | String | 否 | [TICKET] 景区/项目名称 |
|
||||
| `specName` | String | 否 | [TICKET] 规格名称 |
|
||||
| `ticketCount` | Integer | 否 | [TICKET] 票数 |
|
||||
| `ticketUnitPrice` | BigDecimal | 否 | [TICKET] 门票单价 |
|
||||
| `sellPrice` | BigDecimal | 否 | [TICKET] 销售价 |
|
||||
| `totalAmount` | BigDecimal | 否 | [TICKET] 票面总额 |
|
||||
| `mealType` | String | 否 | [MEAL] 餐食类型:`BREAKFAST` / `LUNCH` / `DINNER` / `SELF` |
|
||||
| `mealTypeName` ⭐新增 | String | 否 | [MEAL] 餐食类型中文名,走 `meal_type` 字典;`BREAKFAST`→早餐、`LUNCH`→午餐、`DINNER`→晚餐、`SELF`→自理 |
|
||||
| `mealDate` | String | 否 | [MEAL] 用餐日期 yyyy-MM-dd |
|
||||
| `mealName` | String | 否 | [MEAL] 餐食名称 |
|
||||
| `quantity` | Integer | 否 | [MEAL] 份数 |
|
||||
| `staffRole` | String | 否 | [STAFF] 人员角色 code:`GUIDE` / `GUIDE_ASSISTANT` / `LEADER` / `PHOTOGRAPHER` / `DRIVER` / `OTHER` |
|
||||
| `staffRoleName` ⭐新增 | String | 否 | [STAFF] 人员角色中文名,走 `staff_role` 字典 |
|
||||
| `staffId` | Long | 否 | [STAFF] 人员安排 ID |
|
||||
| `staffName` | String | 否 | [STAFF] 人员姓名 |
|
||||
| `totalPlannedCost` | BigDecimal | 否 | [STAFF] 计划费用合计 |
|
||||
| `reimburse` | BigDecimal | 否 | [STAFF] 应报销金额 |
|
||||
| `detail` | Object | 否 | [STAFF] 人员费用嵌套明细(JSON 对象) |
|
||||
| `settleStatus` | String | 否 | [STAFF] 结算状态 |
|
||||
| `settledDate` | String | 否 | [STAFF] 结算日期 yyyy-MM-dd |
|
||||
| `transferRef` | String | 否 | [STAFF] 转账流水号 |
|
||||
| `sourceRecordType` | String | 否 | [VEHICLE_FEE] 来源记录类型 |
|
||||
| `sourceDetailId` | Long | 否 | [VEHICLE_FEE] 车辆费用明细 ID |
|
||||
| `serviceDate` | String | 否 | [VEHICLE_FEE] 服务日期 yyyy-MM-dd |
|
||||
| `vehicleId` | Long | 否 | [VEHICLE_FEE] 车辆 ID |
|
||||
| `vehiclePlate` | String | 否 | [VEHICLE_FEE] 车牌号 |
|
||||
| `vehicleModelId` | Long | 否 | [VEHICLE_FEE] 车型 ID |
|
||||
| `vehicleModelName` | String | 否 | [VEHICLE_FEE] 车型名称 |
|
||||
| `driverId` | Long | 否 | [VEHICLE_FEE] 司机 ID |
|
||||
| `driverName` | String | 否 | [VEHICLE_FEE] 司机姓名 |
|
||||
| `startDate` | String | 否 | [VEHICLE_FEE] 服务开始日期 yyyy-MM-dd |
|
||||
| `endDate` | String | 否 | [VEHICLE_FEE] 服务结束日期 yyyy-MM-dd |
|
||||
| `dailyPrice` | BigDecimal | 否 | [VEHICLE_FEE] 日单价 |
|
||||
| `paymentTypeCode` | String | 否 | [VEHICLE_FEE] 车务付款类型编码 |
|
||||
| `paymentTypeName` | String | 否 | [VEHICLE_FEE] 车务付款类型名称(原已有字段,不变) |
|
||||
| `vehicleFeeWaiverReason` | String | 否 | [VEHICLE_FEE] 车辆费用减免原因 |
|
||||
| `vehicleFeeSource` | String | 否 | [VEHICLE_FEE] 车辆费用来源说明 |
|
||||
| `vehicleFeeAdjustmentReason` | String | 否 | [VEHICLE_FEE] 车辆费用调整原因 |
|
||||
| `vehicleFeeAdjustedBy` | Long | 否 | [VEHICLE_FEE] 车辆费用调整操作人 ID |
|
||||
| `vehicleFeeAdjustedAt` | String | 否 | [VEHICLE_FEE] 车辆费用调整时间 |
|
||||
| `expenseType` | String | 否 | [EXPENSE] 其他费用类型:`FUEL` / `TOLL` / `PARKING` / `RENTAL` / `MAINTENANCE` / `OTHER` |
|
||||
| `expenseTypeName` ⭐新增 | String | 否 | [EXPENSE] 其他费用类型中文名,走 `expense_type` 字典;`FUEL`→油费、`TOLL`→过路费、`PARKING`→停车费、`RENTAL`→租车费、`MAINTENANCE`→维修保养、`OTHER`→其他 |
|
||||
| `projectName` | String | 否 | [EXPENSE/SUBSIDY] 项目名称 |
|
||||
| `expenseDate` | String | 否 | [EXPENSE/SUBSIDY] 费用发生日期 yyyy-MM-dd |
|
||||
| `subsidyType` | String | 否 | [SUBSIDY] 补贴类型:`MEAL` / `PHONE` / `OVERTIME` / `OTHER` |
|
||||
| `subsidyTypeName` ⭐新增 | String | 否 | [SUBSIDY] 补贴类型中文名,走 `subsidy_type` 字典;`MEAL`→餐补、`PHONE`→话补、`OVERTIME`→加班补贴、`OTHER`→其他 |
|
||||
|
||||
### 5.5 报账表预支行(SettlementReimbursementAdvanceLineVO)
|
||||
|
||||
`advanceLines[]` 从 `List<Map>` 改为 `List<SettlementReimbursementAdvanceLineVO>`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `type` | String | 是 | 行类型 code:当前固定 `APPROVED_ADVANCE` |
|
||||
| `typeName` ⭐新增 | String | 否 | 行类型中文名,走 `settlement_report_line_type` 字典;`APPROVED_ADVANCE`→已审批预支 |
|
||||
| `advanceId` | Long | 否 | 预支单 ID(Long,JSON 序列化为字符串) |
|
||||
| `payeeStaffId` | Long | 否 | 借款对象人员安排 ID |
|
||||
| `payeeName` | String | 否 | 借款对象姓名 |
|
||||
| `payeeRole` | String | 否 | 借款对象角色 code |
|
||||
| `payeeRoleName` ⭐新增 | String | 否 | 借款对象角色中文名,走 `staff_role` 字典 |
|
||||
| `advanceType` | String | 否 | 预支类型 code |
|
||||
| `advanceTypeName` ⭐新增 | String | 否 | 预支类型中文名,走 `advance_type` 字典 |
|
||||
| `amount` | BigDecimal | 是 | 预支金额,保留两位小数 |
|
||||
| `purpose` | String | 否 | 预支用途 |
|
||||
| `voucherUrl` | String | 否 | 凭证 URL |
|
||||
| `status` | String | 否 | 预支状态:`SUBMITTED` / `APPROVED` / `REJECTED` |
|
||||
| `statusText` ⭐新增 | String | 否 | 预支状态中文名,走 `AdvanceStatus` 枚举 label;`SUBMITTED`→已提交、`APPROVED`→已通过、`REJECTED`→已拒绝 |
|
||||
| `submittedAt` | String | 否 | 提交时间 yyyy-MM-dd HH:mm:ss |
|
||||
| `approvedAt` | String | 否 | 审批时间 yyyy-MM-dd HH:mm:ss |
|
||||
| `approvedBy` | String | 否 | 审批人姓名 |
|
||||
|
||||
### 5.6 报账表车辆逐日行(SettlementReimbursementVehicleLineVO)
|
||||
|
||||
`vehicleLines[]` 原已是强类型 VO,本次仅新增 1 个字段:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `sourceTypeName` ⭐新增 | String | 否 | 车辆费用来源中文名,走 `SettlementDetailSourceType` 枚举 label;`FLEET`→车务、`MANUAL`→外部 |
|
||||
|
||||
其余字段(`sourceDetailId`、`serviceDate`、`vehicleId`、`vehiclePlate`、`vehicleModelId`、`vehicleModelName`、`driverId`、`driverName`、`dailyPrice`、`amount`、`paymentMethod`、`paymentMethodName`、`sourceType`、`draftLineId`、`settlementConfirmStatus`、`remark`、`voucherUrls`、`vehicleFeeWaiverReason`、`vehicleFeeSource`、`vehicleFeeAdjustmentReason`、`vehicleFeeAdjustedById`、`vehicleFeeAdjustedAt`)**不变**。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 新增数据字典
|
||||
|
||||
本次通过 Flyway 迁移(`hl-user-service`)新建 5 个字典,并为 `staff_role` 补回 `DRIVER` 值:
|
||||
|
||||
| 字典类型 | dict_type_id | 值数 | 用途 | 取值 |
|
||||
|---|---|---|---|---|
|
||||
| `settlement_category` | 10143 | 8 | 核单费用类别中文名 | `HOTEL`→住宿、`TICKET`→门票/游玩项目、`MEAL`→餐食、`VEHICLE`→车辆、`GUIDE`→导游、`PHOTOGRAPHER`→摄影、`OTHER_EXPENSE`→其他支出、`INSURANCE`→保险 |
|
||||
| `settlement_payment_method` | 10144 | 3 | 核单付款方式中文名 | `CASH_PAID`→现金已付、`COMPANY_PAID`→公司支付、`SIGNED`→签单 |
|
||||
| `settlement_report_line_type` | 10145 | 6 | 核单报表行类型中文名 | `BASE_ORDER`→订单应收、`OTHER_INCOME`→其他收入、`DISCOUNT`→优惠、`ACTUAL_REFUND`→实际退款、`DRIVER_CASH_RECEIPT`→司机现金收款、`APPROVED_ADVANCE`→已审批预支 |
|
||||
| `expense_type` | 10147 | 6 | 其他费用类型中文名 | `FUEL`→油费、`TOLL`→过路费、`PARKING`→停车费、`RENTAL`→租车费、`MAINTENANCE`→维修保养、`OTHER`→其他 |
|
||||
| `subsidy_type` | 10148 | 4 | 补贴类型中文名 | `MEAL`→餐补、`PHONE`→话补、`OVERTIME`→加班补贴、`OTHER`→其他 |
|
||||
| `staff_role` | 8012 | +1 | 人员角色补回 `DRIVER`;现有 5 值不变 | 新增 `DRIVER`→司机;原 `GUIDE`→导游、`GUIDE_ASSISTANT`→助理导游、`LEADER`→领队、`PHOTOGRAPHER`→摄影师、`OTHER`→其他 不变 |
|
||||
|
||||
> 注:`dict_type_id=10146` 跳过(`meal_type` 字典为历史手工所建,已存在,直接复用,不新建)。
|
||||
|
||||
### 6.2 涉及的既有枚举/字典(不变,仅补充中文名映射来源)
|
||||
|
||||
| 来源 | 作用字段 | 说明 |
|
||||
|---|---|---|
|
||||
| `PaymentChannelEnum` | `channelName` | `DRIVER_CASH`→报账人收款、`BANK_TRANSFER`→银行转账、`CONSULTANT_COLLECTION`→顾问代收 |
|
||||
| `PayType` 枚举 | `payTypeName` | `DEPOSIT`→定金、`FULL`→全款、`BALANCE`→尾款 |
|
||||
| `SettlementDetailSourceType` 枚举 | `sourceTypeName`(支出行 + 车辆行) | `MANUAL`→手工、`HOUSE_ASSIGNMENT`→配房结果、`SCENIC_ASSIGNMENT`→配景区结果 等 |
|
||||
| `AdvanceStatus` 枚举 | `statusText` | `SUBMITTED`→已提交、`APPROVED`→已通过、`REJECTED`→已拒绝 |
|
||||
| `advance_type` 字典 | `advanceTypeName` | 预支类型中文(如 `ACCOMMODATION_DEPOSIT`→住宿押金) |
|
||||
| `meal_type` 字典 | `mealTypeName` | `BREAKFAST`→早餐、`LUNCH`→午餐、`DINNER`→晚餐、`SELF`→自理 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次不涉及错误码新增、删除或语义变化。`584118`(报表数据未就绪)、`584066`(暂无主报账人)等既有错误码不变。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(单团核算报表)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/group
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "1001",
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"baseOrderAmount": "12800.00",
|
||||
"otherIncomeAmount": "500.00",
|
||||
"discountAmount": "-200.00",
|
||||
"adjustedReceivableAmount": "12600.00",
|
||||
"paidAmount": "10000.00",
|
||||
"actualRefundedAmount": "-300.00",
|
||||
"netRevenueAmount": "12300.00",
|
||||
"netReceivedAmount": "9700.00",
|
||||
"outstandingAmount": "2600.00",
|
||||
"hotelCost": "3600.00",
|
||||
"ticketCost": "2400.00",
|
||||
"mealCost": "1200.00",
|
||||
"vehicleCost": "880.00",
|
||||
"guideCost": "500.00",
|
||||
"photographerCost": "0.00",
|
||||
"otherExpenseCost": "200.00",
|
||||
"insurancePremium": "150.00",
|
||||
"totalCost": "8930.00",
|
||||
"paidCost": "7500.00",
|
||||
"unpaidCost": "1430.00",
|
||||
"grossProfit": "3370.00",
|
||||
"grossProfitRate": "0.2740",
|
||||
"travelerCount": 8,
|
||||
"perCapitaRevenue": "1537.50",
|
||||
"perCapitaCost": "1116.25",
|
||||
"perCapitaProfit": "421.25",
|
||||
"incomeLines": [
|
||||
{"type": "BASE_ORDER", "typeName": "订单应收", "amount": "12800.00"},
|
||||
{"type": "OTHER_INCOME", "typeName": "其他收入", "amount": "500.00"},
|
||||
{"type": "DISCOUNT", "typeName": "优惠", "amount": "-200.00"},
|
||||
{"type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": "-300.00"}
|
||||
],
|
||||
"costCategories": [
|
||||
{"category": "HOTEL", "categoryName": "住宿", "amount": "3600.00"},
|
||||
{"category": "TICKET", "categoryName": "门票/游玩项目", "amount": "2400.00"},
|
||||
{"category": "MEAL", "categoryName": "餐食", "amount": "1200.00"},
|
||||
{"category": "VEHICLE", "categoryName": "车辆", "amount": "880.00"},
|
||||
{"category": "GUIDE", "categoryName": "导游", "amount": "500.00"},
|
||||
{"category": "PHOTOGRAPHER", "categoryName": "摄影", "amount": "0.00"},
|
||||
{"category": "OTHER_EXPENSE", "categoryName": "其他支出", "amount": "200.00"},
|
||||
{"category": "INSURANCE", "categoryName": "保险", "amount": "150.00"}
|
||||
],
|
||||
"generatedBy": "2037",
|
||||
"generatedByName": "腰苏图",
|
||||
"generatedAt": "2026-08-08T15:30:00",
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 典型成功(报账表,含多族支出行、预支行、车辆逐日行)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/reimbursement
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "2001",
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"driverCollectedTailAmount": "5000.00",
|
||||
"approvedAdvanceAmount": "3000.00",
|
||||
"reportablePaidCostAmount": "7500.00",
|
||||
"reporterNetAmount": "-2500.00",
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "DRIVER_CASH_RECEIPT",
|
||||
"typeName": "司机现金收款",
|
||||
"receiptId": "8001",
|
||||
"amount": "2000.00",
|
||||
"channel": "DRIVER_CASH",
|
||||
"channelName": "报账人收款",
|
||||
"payType": "BALANCE",
|
||||
"payTypeName": "尾款",
|
||||
"collectorStaffId": "7001",
|
||||
"collectorName": "司机甲",
|
||||
"collectorRole": "DRIVER",
|
||||
"collectorRoleName": "司机",
|
||||
"receivedAt": "2026-08-06 18:20:30",
|
||||
"remark": "尾款现金"
|
||||
}
|
||||
],
|
||||
"expenseLines": [
|
||||
{
|
||||
"kind": "HOTEL",
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"amount": "1200.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"voucherUrls": ["https://oss.example.com/v1.jpg"],
|
||||
"remark": "含早",
|
||||
"hotelAssignmentId": "6001",
|
||||
"hotelName": "草原明珠大酒店",
|
||||
"roomType": "STANDARD",
|
||||
"roomTypeName": "标间",
|
||||
"roomCount": 3,
|
||||
"unitPrice": "400.00",
|
||||
"plannedCost": "1200.00",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果",
|
||||
"dayNumber": 2,
|
||||
"stayDate": "2026-08-06"
|
||||
},
|
||||
{
|
||||
"kind": "TICKET",
|
||||
"category": "TICKET",
|
||||
"categoryName": "门票/游玩项目",
|
||||
"amount": "600.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"voucherUrls": [],
|
||||
"scenicName": "希拉穆仁草原",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 5,
|
||||
"ticketUnitPrice": "120.00",
|
||||
"sellPrice": "150.00",
|
||||
"totalAmount": "600.00",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "配景区结果",
|
||||
"dayNumber": 3,
|
||||
"dayDate": "2026-08-07"
|
||||
},
|
||||
{
|
||||
"kind": "STAFF:GUIDE",
|
||||
"category": "GUIDE",
|
||||
"categoryName": "导游",
|
||||
"amount": "300.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"voucherUrls": [],
|
||||
"staffRole": "GUIDE",
|
||||
"staffRoleName": "导游",
|
||||
"staffId": "7011",
|
||||
"staffName": "导游乙",
|
||||
"totalPlannedCost": "1500.00",
|
||||
"reimburse": "300.00",
|
||||
"detail": {},
|
||||
"settleStatus": "COMPLETED",
|
||||
"settledDate": "2026-08-08",
|
||||
"transferRef": "TX20260808001"
|
||||
},
|
||||
{
|
||||
"kind": "EXPENSE:FUEL",
|
||||
"category": "OTHER_EXPENSE",
|
||||
"categoryName": "其他支出",
|
||||
"amount": "200.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"voucherUrls": [],
|
||||
"expenseType": "FUEL",
|
||||
"expenseTypeName": "油费",
|
||||
"projectName": "全程油费",
|
||||
"expenseDate": "2026-08-06"
|
||||
},
|
||||
{
|
||||
"kind": "SUBSIDY:MEAL",
|
||||
"category": "OTHER_EXPENSE",
|
||||
"categoryName": "其他支出",
|
||||
"amount": "150.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"voucherUrls": [],
|
||||
"subsidyType": "MEAL",
|
||||
"subsidyTypeName": "餐补",
|
||||
"projectName": "每日餐补",
|
||||
"expenseDate": "2026-08-06"
|
||||
}
|
||||
],
|
||||
"advanceLines": [
|
||||
{
|
||||
"type": "APPROVED_ADVANCE",
|
||||
"typeName": "已审批预支",
|
||||
"advanceId": "5001",
|
||||
"payeeStaffId": "7001",
|
||||
"payeeName": "司机甲",
|
||||
"payeeRole": "DRIVER",
|
||||
"payeeRoleName": "司机",
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"advanceTypeName": "住宿押金",
|
||||
"amount": "3000.00",
|
||||
"purpose": "酒店押金",
|
||||
"voucherUrl": "https://oss.example.com/advance-v1.jpg",
|
||||
"status": "APPROVED",
|
||||
"statusText": "已通过",
|
||||
"submittedAt": "2026-08-05 10:00:00",
|
||||
"approvedAt": "2026-08-05 12:00:00",
|
||||
"approvedBy": "财务丙"
|
||||
}
|
||||
],
|
||||
"vehicleLines": [
|
||||
{
|
||||
"sourceDetailId": "9001",
|
||||
"serviceDate": "2026-08-06",
|
||||
"vehicleId": "9101",
|
||||
"vehiclePlate": "蒙A-5376",
|
||||
"vehicleModelId": "9201",
|
||||
"vehicleModelName": "坦克500",
|
||||
"driverId": "9301",
|
||||
"driverName": "司机甲",
|
||||
"dailyPrice": "880.00",
|
||||
"amount": "880.00",
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务",
|
||||
"draftLineId": "9401",
|
||||
"settlementConfirmStatus": "CONFIRMED",
|
||||
"remark": null,
|
||||
"voucherUrls": []
|
||||
}
|
||||
],
|
||||
"generatedBy": "2037",
|
||||
"generatedByName": "腰苏图",
|
||||
"generatedAt": "2026-08-08T15:30:00"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 边界情况(空数组:订单还未生成核单报表)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002999/settlement/reports/group
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584118,
|
||||
"message": "核单报表数据未就绪,请先完成核单",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
当订单尚未完成核单(无终态快照)时,两个报表接口均返回 `584118`。完成核单后,两个接口正常返回数据,`incomeLines` / `costCategories` / `expenseLines` / `advanceLines` 等明细数组无数据时为空数组 `[]`(不会是 null)。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用状态**:订单已完成核单(存在终态快照),`settlement_status` 为 `COMPLETED` 或已反确认重开前曾确认过。
|
||||
- **不适用状态**:核单未完成(`settlement_status=PENDING` 或其他中间态)时返回 `584118`(报表数据未就绪)。
|
||||
- **特殊边界**:
|
||||
- 单团核算 `incomeLines[]` 固定 4 行(BASE_ORDER / OTHER_INCOME / DISCOUNT / ACTUAL_REFUND),无数据行也不缺行(金额为 0)。
|
||||
- 单团核算 `costCategories[]` 固定 8 行(HOTEL~INSURANCE),同样不缺行。
|
||||
- 报账表 `incomeLines[]` 目前仅司机现金收款行,无司机收款时为空数组。
|
||||
- 报账表 `expenseLines[]` 每行仅本族字段非 null(`@JsonInclude(NON_NULL)`),前端按 `kind` 前缀(如 `HOTEL` / `TICKET` / `MEAL` / `STAFF:` / `VEHICLE_FEE` / `EXPENSE:` / `SUBSIDY:`)区分族渲染。
|
||||
- `xxxName` 字段在字典不可用或 code 未命中时可能为 null(不阻断主流程),前端展示时应降级回 code 或以空字符串处理。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 数组字段 | 修改前类型 | 修改后类型 | 影响 |
|
||||
|---|---|---|---|
|
||||
| `SettlementGroupReportRespVO.incomeLines` | `List<Map<String,Object>>` | `List<SettlementGroupIncomeLineVO>` | TS 类型可生成;新增 `typeName` 字段 |
|
||||
| `SettlementGroupReportRespVO.costCategories` | `List<Map<String,Object>>` | `List<SettlementGroupCostCategoryVO>` | TS 类型可生成;新增 `categoryName` 字段 |
|
||||
| `SettlementReimbursementReportRespVO.incomeLines` | `List<Map<String,Object>>` | `List<SettlementReimbursementIncomeLineVO>` | TS 类型可生成;新增 `typeName`/`channelName`/`payTypeName`/`collectorRoleName` |
|
||||
| `SettlementReimbursementReportRespVO.expenseLines` | `List<Map<String,Object>>` | `List<SettlementReimbursementExpenseLineVO>` | TS 类型可生成;新增 `categoryName`/`paymentMethodName`/`mealTypeName`/`expenseTypeName`/`subsidyTypeName`/`staffRoleName`/`sourceTypeName` |
|
||||
| `SettlementReimbursementReportRespVO.advanceLines` | `List<Map<String,Object>>` | `List<SettlementReimbursementAdvanceLineVO>` | TS 类型可生成;新增 `typeName`/`payeeRoleName`/`advanceTypeName`/`statusText` |
|
||||
| `SettlementReimbursementVehicleLineVO.sourceTypeName` | 不存在 | `String` | 新增字段 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 方面 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 前端获取 code 中文名 | 需前端自维护 code→label 硬编码映射表 | 后端直接在出参里提供 `xxxName` 字段,前端直接渲染 |
|
||||
| 类型安全 | `Map<String,Object>`,IDE 无补全,字段拼写错误运行时才发现 | 强类型 VO,Knife4j 和前端代码生成均有完整类型定义 |
|
||||
| JSON 输出 | 所有 Map key 全量输出(含 null 值 key) | `@JsonInclude(NON_NULL)`,仅非 null 字段输出,JSON 体积减小 |
|
||||
| 字典降级 | 前端硬编码,字典增减需发版 | 后端走数据字典动态加载(5 分钟本地缓存),字典变化实时生效 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **破坏兼容**:否。本次为纯新增字段,旧字段名、类型、语义完全不变。前端不读新字段不影响现有功能。
|
||||
- **前端同步上线**:不强制同步。前端可先上线新接口对接(读 `xxxName` 替换硬编码),再逐步移除旧硬编码。但建议尽快适配以统一展示效果——后端字典更新后前端硬编码可能不同步。
|
||||
- **回滚方案**:若需回滚后端,前端需回退到读旧 Map 结构(字段名不变,只是少了 `xxxName`)。回滚到旧版后 `xxxName` 字段不再出现,前端如已移除硬编码则中文名会丢失。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **出参 JSON 的 key 集合可能有变化**:旧版 `List<Map>` 输出所有 key(含 null 值 key),新版 `@JsonInclude(NON_NULL)` 仅输出非 null key。前端按 key 遍历/检查存在性时注意——null 值的 key 不再出现,应使用 `!= null` 而非 `hasOwnProperty` 检查。
|
||||
2. **`xxxName` 可能为 null**:字典加载失败或 code 未命中时 `xxxName` 为 null(不阻断主流程),前端展示时应 fallback 到 code 原值或空字符串。不要假设 `xxxName` 一定非空。
|
||||
3. **`kind` 字段区分支出行族**:报账表支出行的 7 个族(HOTEL / TICKET / MEAL / STAFF:* / VEHICLE_FEE / EXPENSE:* / SUBSIDY:*)共享同一个 VO,`kind` 字段标识当前行属于哪个族,前端按 `kind` 前缀路由渲染组件。`kind` 字段本次不变。
|
||||
4. **Long 类型 ID 字段**:`receiptId`、`collectorStaffId`、`advanceId`、`payeeStaffId`、`staffId`、`hotelAssignmentId`、`hotelId`、`roomTypeId`、`sourceId`、`scenicAssignmentId`、`sourceDetailId`、`vehicleId`、`vehicleModelId`、`driverId`、`vehicleFeeAdjustedBy`、`draftLineId` 等均为 Long 类型,JSON 序列化为**字符串**(`@JsonSerialize(using = ToStringSerializer.class)`),前端注意不要用 `typeof === 'number'` 判断。
|
||||
5. **车辆逐日行 `vehicleFeeAdjustedAt` 时间格式**:该字段格式为 `yyyy-MM-dd'T'HH:mm:ss`(ISO-8601),不同于其他时间字段的 `yyyy-MM-dd HH:mm:ss`,前端解析时注意。
|
||||
6. **`staff_role` 字典新增 `DRIVER`**:团期人员角色下拉会新增「司机」选项(用户已确认接受此副作用)。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5729](https://git.1814.love:8443/wx/HL/issues/5729)
|
||||
- **PR**: [#5743](https://git.1814.love:8443/wx/HL/pulls/5743)
|
||||
- **Commit**: [db8974cc9](https://git.1814.love:8443/wx/HL/commit/db8974cc9)
|
||||
- **Merge commit**: [7b4c69aa8](https://git.1814.love:8443/wx/HL/commit/7b4c69aa8)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @yst(yaosutu / 腰苏图)
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5730-5732"
|
||||
title: "后端已修复:房型 BIG_BED 全链路中文(#5730)+ 派车通知预览日期按接续段分段(#5732)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "两个后端修复已合并 dev-v3 并部署 TEST 验证通过:①#5730 房型 BIG_BED 全链路翻中文「大床房」;②#5732 派车通知预览(render-batch)日期按 serviceDates 分段。前端无需改代码的可直接验证展示;接续分 tab 接入时日期即正确分段。请前端知晓并验证。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T11:40:00+08:00"
|
||||
---
|
||||
|
||||
# 后端已修复:房型中文(#5730)+ 预览日期分段(#5732)
|
||||
|
||||
> 两个后端修复已合并 dev-v3、部署 TEST 并验证通过。告知前端知晓/验证/配合接入。
|
||||
|
||||
## 变更内容
|
||||
|
||||
### ① #5730 房型 BIG_BED 全链路中文展示
|
||||
|
||||
**修复**:`room_category` 字典补充 BIG_BED→「大床房」中文 label,行程/住宿安排/配房等展示处 `roomCategoryLabel` 现在映射到中文,不再显示英文 BIG_BED。
|
||||
|
||||
**前端影响**:**无需改代码**。原先住宿安排/行程里房型显示「BIG BED1」的位置,现在应显示「大床房」。请前端**验证**房型中文展示是否已正常(如订单 26-8083 娜仁托娅的住宿安排)。
|
||||
|
||||
### ② #5732 派车通知预览(render-batch)日期按接续段分段
|
||||
|
||||
**修复**:`render`/`render-batch` 渲染的「日期」(order.startDate/endDate/days 占位符)在传入 `serviceDates` 非空时按 **min~max 分段**,未传时回退订单全程;与实际 HOLD 通知快照口径一致。
|
||||
|
||||
**接口**:`POST /admin/fleet/message-templates/{templateId}/render-batch`
|
||||
|
||||
**修复后实证**(订单 26-6436,模板 2073978002412105729):
|
||||
- item0 道尔吉 serviceDates=["2026-08-23"] → 渲染「日期:**2026-08-23 至 2026-08-23**」
|
||||
- item1 王信 serviceDates=["2026-08-24","2026-08-25"] → 渲染「日期:**2026-08-24 至 2026-08-25**」
|
||||
|
||||
**前端影响**:配合「车辆接续派车通知多司机分 tab」(见 09_frontend_车辆接续... changelog)接入 render-batch 时,传入各司机的 serviceDates,渲染的「日期」即正确分段(各司机只看到自己负责的服务日期范围),无需前端额外处理日期。
|
||||
|
||||
## 验证口径
|
||||
|
||||
- 房型中文:前端页面看房型是否显示「大床房」(原 BIG_BED)。
|
||||
- 日期分段:前端接 render-batch 分 tab 后,各 tab 预览的「日期」应为该司机负责的服务日期段(非订单全程)。
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5730"
|
||||
title: "房型 BIG_BED 字典补中文(住宿安排/行程/配房全链路显示「大床房」)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "3ec1a933"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5731 已合并 dev-v3 并部署 TEST(hl-user-service)。room_category 字典补充 BIG_BED→大床房(migration V20260809_001),行程/住宿安排/配房等所有展示处(roomCategoryLabel 字典动态映射)自动命中中文;实证 BIG_BED 订单详情 20 处 roomCategoryLabel 全部为「大床房」。无 API 契约变化,前端无需改动。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T11:00:00+08:00"
|
||||
---
|
||||
|
||||
# 房型 BIG_BED 字典补中文(住宿安排/行程/配房全链路显示「大床房」)
|
||||
|
||||
> 后端完成:PR [#5731](https://git.1814.love:8443/wx/HL/pulls/5731) 已合并 dev-v3 并部署 TEST,网关实证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5730](https://git.1814.love:8443/wx/HL/issues/5730)
|
||||
- **PR**: [#5731](https://git.1814.love:8443/wx/HL/pulls/5731)
|
||||
- **Merge commit**: [0d6654e35](https://git.1814.love:8443/wx/HL/commit/0d6654e35)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
运营实测:住宿安排房型显示英文「BIG BED1」,应显示「大床房」。根因:`order_settlement_hotel.room_type` 存量存英文枚举 `BIG_BED`,但房型分类字典 `room_category` 缺 BIG_BED 条目(TEST 原仅 11 项),行程/住宿安排/配房等展示处的 `roomCategoryLabel` 字典翻译(code→label,未命中降级回 code)映射不到中文。
|
||||
|
||||
## 变更内容
|
||||
|
||||
`hl-user-service` 新增字典迁移 `V20260809_001`:`room_category` 补充 `BIG_BED → 大床房`(dict_data_id=5001000000000003210,sort_order=11 追加,不扰动既有排序)。配房表存的是 code 非 label、展示处均为字典动态映射,补字典后全链路自动生效,无需回填快照。与 #5630(写入侧校验房型分类必须在字典内)方向一致——BIG_BED 从此为合法字典 code。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 住宿安排/行程/配房展示 BIG_BED 房型 | 显示英文 code「BIG BED」 | 显示中文「大床房」 |
|
||||
| 其余房型(STANDARD/SINGLE/TWIN/QUEEN/DELUXE/KING/SUITE/FAMILY/YURT/SPECIAL/PARENT_CHILD) | 中文 label | 不变 |
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **网关实证(TEST,ROOM_MANAGER)**:`GET /admin/house/orders/2086260844957487105`(BIG_BED 订单,order_settlement_hotel 20 行 BIG_BED)→ **200,20 处 roomCategoryLabel 全部=「大床房」**。
|
||||
- **字典完整性**:room_category ACTIVE 12 项(既有 11 项保留 + BIG_BED)。
|
||||
- **测试**:新增 `RoomCategoryBigBedDictionaryMigrationTest` 3/3;hl-user-service verify **3530 tests 全绿**(含既有字典迁移测试);hl-resource-service `RoomTypeCategoryValidationTest` 8/8(#5630 校验回归)。
|
||||
|
||||
## 前端交接
|
||||
|
||||
无接口契约变化(字段、结构、错误码均不变,仅字典 label 数据补齐),前端无需改动。
|
||||
|
||||
## 前端实证确认(2026-08-09 mmg,hl-admin@3ec1a933)
|
||||
|
||||
主路径无需改动(后端 `roomCategoryLabel` 字典动态映射正常返回「大床房」,后端实证 20 处全中)。但前端实证发现一处可加固点,已补:
|
||||
|
||||
- **实证**:grep 全 `src/` 零 BIG_BED 硬编码;展示走 `roomCategoryLabel || roomCategoryLabel(code)`——后端 label 优先,本地兜底表降级。本地兜底表 `ROOM_CATEGORY_LABEL`(`orderDetailAdapter.js`)缺 BIG_BED,仅当后端 label 缺失的降级场景会回落英文 code。
|
||||
- **补固**:本地兜底表补 `BIG_BED: '大床房'`(与 QUEEN 同显),覆盖后端 label 缺失的降级路径。单行、低风险。
|
||||
- 验证:orderDetailAdapter + v3Adapter 83/83 通过;checkpoint 全绿。
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5732"
|
||||
title: "派车通知预览(render/render-batch)日期按 serviceDates 分段(接续场景各司机显示自己负责的服务日)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5740 合并 dev-v3(38f4f99a2)并部署 TEST,网关验证接续场景两司机预览日期各自分段(道尔吉 8/23~8/23、王信 8/24~8/25,修复前均为订单全程),与司机实际收到的 HOLD 通知日期口径一致。接口字段结构无变化,渲染行为修复,前端无需改动(预览页展示即正确分段)。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T12:15:00+08:00"
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
`POST /admin/fleet/message-templates/{templateId}/render` 与 `POST /admin/fleet/message-templates/{templateId}/render-batch`
|
||||
|
||||
- **行为变化**:请求项携带 `serviceDates` 时,模板变量 `order.startDate` / `order.endDate` / `order.days` 按该司机的 `serviceDates` min~max 分段渲染(与司机实际收到的 HOLD 通知口径一致:`AssignmentHoldNotificationSnapshotFactory` 行级 serviceDate min/max);不传 `serviceDates`(单司机非接续/派单前预览)仍按订单全程渲染。
|
||||
- **请求/响应字段结构无变化**:`serviceDates` 原有字段(此前仅用于车费安排渲染)现同时驱动「日期」占位符。
|
||||
- 模板占位符示例:`日期:{{order.startDate}} 至 {{order.endDate}}`。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- hl-fleet-service verify 3307/0F/0E + spotless 通过;MessageTemplateRenderServiceTest 30/30(新增 4 用例)。
|
||||
- TEST 网关实证(订单 26-6436,接续场景,探针 `tools/probe_5732_render_dates.py` 5/5 PASS):
|
||||
- 道尔吉段 `serviceDates=[2026-08-23]` → 日期 **2026-08-23 至 2026-08-23**
|
||||
- 王信段 `serviceDates=[2026-08-24,2026-08-25]` → 日期 **2026-08-24 至 2026-08-25**
|
||||
- 不传 serviceDates → 订单全程 2026-08-23 至 2026-08-25(回归)
|
||||
- 全程段预览与 HOLD 快照工厂在 fleet_assignment 实际行集合上的 min/max 口径一致
|
||||
@@ -0,0 +1,362 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5739"
|
||||
title: "核单报表快照指纹彻底下线:报表出参删除 sourceFingerprint,reportStatus 不再返回 STALE"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "19d001a4"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端已实现:清理 stale 死代码——returnDetailAdapter 不再投影 stale(注释更新 #5704/#5739 双批次口径)、ReportModal 删徽章「已过期」/error 态+「来源数据已变化请刷新」error 提示+报账表单 stale 守卫、detail.vue openReport 仅未生成时重拉去 stale 重载。reportStatus 只留 GENERATED/CONFIRMED 落库原值。grep 复核无残留;核单域定向 39/39 过 + checkpoint 精确文件集全过。前后端同批发布(前端先上),承接 #5704。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】核单报表快照指纹彻底下线,出参删 sourceFingerprint、reportStatus 不再返回 STALE(#5739)
|
||||
|
||||
> **PR**: [#5764](https://git.1814.love:8443/wx/HL/pulls/5764) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-09
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
本次是核单域指纹机制下线的**收尾批次**,承接 [#5704](https://git.1814.love:8443/wx/HL/issues/5704)(PR #5709,写入路径 6 端点指纹入参/出参下线)。
|
||||
|
||||
#5704 之后,核单报表(单团核算表 / 主报账人报账表)的读取路径仍残留一套「快照指纹」逻辑:报表落库时记录来源数据 sha256 指纹,前端每次 GET 报表时后端实时重算当前来源指纹,两者不一致就把 reportStatus 实时改报为 STALE(提示「数据已变化,需刷新」),并在出参中携带 sourceFingerprint。
|
||||
|
||||
指纹机制整体废弃后,该派生逻辑同步删除:
|
||||
|
||||
- 两个报表查询接口出参**删除 sourceFingerprint 字段**;
|
||||
- reportStatus 枚举**删除 STALE 值**,只保留落库状态 GENERATED / CONFIRMED,接口不再做实时指纹比对。
|
||||
|
||||
> ⚠️ 本次为**出参删字段 + 枚举删值的硬破坏契约**:前端若还在读取 sourceFingerprint、或对 reportStatus === 'STALE' 写过特判(刷新提示 / 重新生成按钮等),必须清理后再与后端同批发布。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group | 删除出参 sourceFingerprint;reportStatus 枚举删除 STALE | 停读字段 / 清理 STALE 特判 |
|
||||
| 2 | 查询主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 删除出参 sourceFingerprint;reportStatus 枚举删除 STALE | 停读字段 / 清理 STALE 特判 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:核单页面展示单团核算表(全团收入/成本/毛利核算)与主报账人报账表(主报账人收付对账与转账结论)。
|
||||
- **认证**:需要管理后台登录态(Bearer Token);房务角色(HOUSE)被 OrderViewGuard.assertNotHouseRole 拦截。
|
||||
- **幂等性**:只读查询,天然幂等。
|
||||
- **限流**:未声明接口专属限流。
|
||||
- **方法/路径**:见 §2 变更清单。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long | 是 | 订单 ID,路径参数,必须大于 0 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无(GET 请求,无请求体,无 Query 参数)。**入参零变化**。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 GET /v3/admin/order/{orderId}/settlement/reports/group(单团核算表)
|
||||
|
||||
统一响应 Result 包装,data 字段如下(按响应 JSON 字段序):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String(Long) | 报表记录 ID;报表未落库时可为 null |
|
||||
| orderId | String(Long) | 订单 ID |
|
||||
| reportStatus | String | 报表状态,枚举见 §6;**不再返回 STALE** |
|
||||
| ~~sourceFingerprint~~ | - | **已删除**,前端不再收到此字段 |
|
||||
| baseOrderAmount | String(BigDecimal) | 订单应收(基础订单金额) |
|
||||
| otherIncomeAmount | String(BigDecimal) | 其他收入合计 |
|
||||
| discountAmount | String(BigDecimal) | 优惠合计(负数) |
|
||||
| adjustedReceivableAmount | String(BigDecimal) | 调整后应收 |
|
||||
| paidAmount | String(BigDecimal) | 已收金额 |
|
||||
| actualRefundedAmount | String(BigDecimal) | 实际退款金额 |
|
||||
| netRevenueAmount | String(BigDecimal) | 净收入 |
|
||||
| netReceivedAmount | String(BigDecimal) | 净收款 |
|
||||
| outstandingAmount | String(BigDecimal) | 待收尾款 |
|
||||
| hotelCost | String(BigDecimal) | 住宿成本 |
|
||||
| ticketCost | String(BigDecimal) | 门票成本 |
|
||||
| mealCost | String(BigDecimal) | 餐食成本 |
|
||||
| vehicleCost | String(BigDecimal) | 车辆成本 |
|
||||
| guideCost | String(BigDecimal) | 导游成本 |
|
||||
| photographerCost | String(BigDecimal) | 摄影成本 |
|
||||
| otherExpenseCost | String(BigDecimal) | 其他支出成本 |
|
||||
| insurancePremium | String(BigDecimal) | 保费 |
|
||||
| totalCost | String(BigDecimal) | 成本合计 |
|
||||
| paidCost | String(BigDecimal) | 已付成本 |
|
||||
| unpaidCost | String(BigDecimal) | 未付成本 |
|
||||
| grossProfit | String(BigDecimal) | 毛利 |
|
||||
| grossProfitRate | String(BigDecimal) | 毛利率 |
|
||||
| travelerCount | Integer | 出行人数 |
|
||||
| perCapitaRevenue | String(BigDecimal) | 人均收入 |
|
||||
| perCapitaCost | String(BigDecimal) | 人均成本 |
|
||||
| perCapitaProfit | String(BigDecimal) | 人均毛利 |
|
||||
| incomeLines | Array | 收入行,固定 4 行;每项含 type(BASE_ORDER/OTHER_INCOME/DISCOUNT/ACTUAL_REFUND) / typeName / amount |
|
||||
| costCategories | Array | 成本分类行,固定 8 行;每项含 category(HOTEL/TICKET/MEAL/VEHICLE/GUIDE/PHOTOGRAPHER/OTHER_EXPENSE/INSURANCE) / categoryName / amount |
|
||||
| generatedBy | String(Long) | 生成人 ID |
|
||||
| generatedByName | String | 生成人姓名 |
|
||||
| generatedAt | String | 生成时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| confirmedBy | String(Long) | 确认人 ID;未确认为 null |
|
||||
| confirmedByName | String | 确认人姓名 |
|
||||
| confirmedAt | String | 确认时间 |
|
||||
|
||||
### 5.2 GET /v3/admin/order/{orderId}/settlement/reports/reimbursement(主报账人报账表)
|
||||
|
||||
统一响应 Result 包装,data 字段如下:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String(Long) | 报表记录 ID;未落库时可为 null |
|
||||
| orderId | String(Long) | 订单 ID |
|
||||
| reportStatus | String | 报表状态,枚举见 §6;**不再返回 STALE** |
|
||||
| ~~sourceFingerprint~~ | - | **已删除**,前端不再收到此字段 |
|
||||
| primaryReporterId | String(Long) | 主报账人人员安排 ID |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| reportVersion | Integer | 报表版本号 |
|
||||
| driverCollectedTailAmount | String(BigDecimal) | 司机代收尾款 |
|
||||
| approvedAdvanceAmount | String(BigDecimal) | 已审批预支金额 |
|
||||
| reportablePaidCostAmount | String(BigDecimal) | 可报账已付成本 |
|
||||
| reporterNetAmount | String(BigDecimal) | 报账人净额 |
|
||||
| primaryReporterCollectedAmount | String(BigDecimal) | 主报账人已收款 |
|
||||
| publicPrepaidAmount | String(BigDecimal) | 对公预付金额 |
|
||||
| primaryReporterDueAmount | String(BigDecimal) | 主报账人应结金额 |
|
||||
| advanceOutstandingAmount | String(BigDecimal) | 预支未销余额 |
|
||||
| reconNetAmount | String(BigDecimal) | 对账净额 |
|
||||
| transferDirection | String | 转账方向 |
|
||||
| transferAmount | String(BigDecimal) | 转账金额 |
|
||||
| incomeLines | Array | 收款行;每项含 type / typeName / receiptId / amount / channel / channelName / payType / payTypeName / collectorStaffId / collectorName / collectorRole / collectorRoleName / receivedAt / remark |
|
||||
| expenseLines | Array | 支出行;每项含 kind / category / categoryName / amount / paymentMethod / paymentMethodName / voucherUrls / remark + 类别扩展字段(住宿 hotelName/roomTypeName 等) |
|
||||
| advanceLines | Array | 预支行 |
|
||||
| vehicleLines | Array | 已确认车辆逐日费用明细;**无数据固定返回空数组** |
|
||||
| transferStatus | String | 转账状态 |
|
||||
| transferDate | String | 转账日期,格式 yyyy-MM-dd |
|
||||
| transferRef | String | 转账流水号 |
|
||||
| advanceSettledFlag | Boolean | 预支是否已处理 |
|
||||
| signedVoucher | Object | 签字凭证;含 files[{name,url}] + note |
|
||||
| generatedBy | String(Long) | 生成人 ID |
|
||||
| generatedByName | String | 生成人姓名 |
|
||||
| generatedAt | String | 生成时间 |
|
||||
| confirmedBy | String(Long) | 确认人 ID |
|
||||
| confirmedByName | String | 确认人姓名 |
|
||||
| confirmedAt | String | 确认时间 |
|
||||
|
||||
> 上述两表除删除 sourceFingerprint 外,其余字段名称、类型、语义均无变化。前端不要再读取 sourceFingerprint,读取结果恒为 undefined。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### reportStatus(两接口共用)
|
||||
|
||||
| 值 | 含义 | 本次变化 |
|
||||
|---|---|---|
|
||||
| GENERATED | 报表已生成(或实时计算态),未确认 | 不变 |
|
||||
| CONFIRMED | 报表已随完成核单确认 | 不变 |
|
||||
| ~~STALE~~ | 原语义:落库后来源数据发生变化(指纹不一致),实时派生 | **已删除,不再返回** |
|
||||
|
||||
**STALE 原触发场景(现已消亡)**:报表落库(生成/确认)后,订单的收退款、费用明细、车辆费用等来源数据又被修改(含 reopen 反确认后改数),原来 GET 报表时后端会比对落库指纹与实时指纹,不一致则把 reportStatus 改报 STALE。现在该实时比对整体移除,reportStatus 就是落库原值。
|
||||
|
||||
其余枚举/字典(incomeLines[].type、costCategories[].category、channel、payType、paymentMethod、transferDirection、transferStatus 等)取值与语义均无变化。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次**无新增、无删除错误码**。两接口既有的访问拦截(如房务角色 403、订单不存在)行为不变;finalize 前置的双报告已生成保护(584311 / 584313)不在本次范围、保持不变。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(单团核算表)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/group
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "8801",
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "GENERATED",
|
||||
"baseOrderAmount": "12800.00",
|
||||
"otherIncomeAmount": "500.00",
|
||||
"discountAmount": "-300.00",
|
||||
"adjustedReceivableAmount": "13000.00",
|
||||
"paidAmount": "13000.00",
|
||||
"actualRefundedAmount": "0.00",
|
||||
"netRevenueAmount": "13000.00",
|
||||
"netReceivedAmount": "13000.00",
|
||||
"outstandingAmount": "0.00",
|
||||
"hotelCost": "3600.00",
|
||||
"ticketCost": "1200.00",
|
||||
"mealCost": "800.00",
|
||||
"vehicleCost": "2000.00",
|
||||
"guideCost": "500.00",
|
||||
"photographerCost": "0.00",
|
||||
"otherExpenseCost": "100.00",
|
||||
"insurancePremium": "200.00",
|
||||
"totalCost": "8400.00",
|
||||
"paidCost": "7000.00",
|
||||
"unpaidCost": "1400.00",
|
||||
"grossProfit": "4600.00",
|
||||
"grossProfitRate": "0.3538",
|
||||
"travelerCount": 4,
|
||||
"perCapitaRevenue": "3250.00",
|
||||
"perCapitaCost": "2100.00",
|
||||
"perCapitaProfit": "1150.00",
|
||||
"incomeLines": [
|
||||
{"type": "BASE_ORDER", "typeName": "订单应收", "amount": "12800.00"},
|
||||
{"type": "OTHER_INCOME", "typeName": "其他收入", "amount": "500.00"},
|
||||
{"type": "DISCOUNT", "typeName": "优惠", "amount": "-300.00"},
|
||||
{"type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": "0.00"}
|
||||
],
|
||||
"costCategories": [
|
||||
{"category": "HOTEL", "categoryName": "住宿", "amount": "3600.00"},
|
||||
{"category": "TICKET", "categoryName": "门票", "amount": "1200.00"}
|
||||
],
|
||||
"generatedBy": "1001",
|
||||
"generatedByName": "张三",
|
||||
"generatedAt": "2026-08-09 10:20:30",
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:响应中已无 sourceFingerprint 字段;reportStatus 只会是 GENERATED 或 CONFIRMED。
|
||||
|
||||
### 8.2 边界(报账表无车辆明细 + 未落库实时态)
|
||||
|
||||
报表未落库(订单尚未生成过报账表)时接口仍正常返回实时计算结果,id 可为 null、reportStatus 固定 GENERATED、vehicleLines 固定空数组:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/reimbursement
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": null,
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "GENERATED",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"reportVersion": null,
|
||||
"driverCollectedTailAmount": "0.00",
|
||||
"approvedAdvanceAmount": "0.00",
|
||||
"reportablePaidCostAmount": "0.00",
|
||||
"reporterNetAmount": "0.00",
|
||||
"primaryReporterCollectedAmount": "0.00",
|
||||
"publicPrepaidAmount": "0.00",
|
||||
"primaryReporterDueAmount": "0.00",
|
||||
"advanceOutstandingAmount": "0.00",
|
||||
"reconNetAmount": "0.00",
|
||||
"transferDirection": null,
|
||||
"transferAmount": "0.00",
|
||||
"incomeLines": [],
|
||||
"expenseLines": [],
|
||||
"advanceLines": [],
|
||||
"vehicleLines": [],
|
||||
"transferStatus": null,
|
||||
"transferDate": null,
|
||||
"transferRef": null,
|
||||
"advanceSettledFlag": null,
|
||||
"signedVoucher": null,
|
||||
"generatedBy": null,
|
||||
"generatedByName": null,
|
||||
"generatedAt": null,
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/999999999/settlement/reports/group
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 584xxx, "message": "订单不存在", "data": null, "success": false}
|
||||
```
|
||||
|
||||
> 该失败路径与本次变更无关,仅作最小复现样例;具体错误码以订单域统一「订单不存在」码为准。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 报表落库后再改来源数据(收款、费用、车辆费用等),reportStatus 保持落库原值(GENERATED / CONFIRMED),不再实时翻成 STALE;前端展示的报表金额仍是实时计算的当前值。
|
||||
- ✅ reopen(反确认)后重新查报表,reportStatus 按落库记录返回,不再出现 STALE 中间态。
|
||||
- ❌ 不要再依赖 reportStatus === 'STALE' 做「数据已变化」提示;该信号已彻底不存在,且没有替代字段(指纹机制整体废弃,核单为单人负责场景,见 #5704 背景)。
|
||||
- ❌ 不要把历史缓存/本地存储里的 sourceFingerprint 回传到任何接口——报表两个 GET 接口本就无入参;写入侧接口的指纹入参已在 #5704 删除。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 接口 | 字段 | 原来 | 现在 |
|
||||
|---|---|---|---|
|
||||
| GET reports/group | sourceFingerprint | 出参,64 位十六进制 sha256 | **已删除** |
|
||||
| GET reports/reimbursement | sourceFingerprint | 出参,64 位十六进制 sha256 | **已删除** |
|
||||
| 两接口 | reportStatus | GENERATED / CONFIRMED / STALE(实时派生) | 仅 GENERATED / CONFIRMED(落库原值) |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 报表落库后来源数据被修改,再 GET 报表 | reportStatus 实时改报 STALE | 返回落库原值(GENERATED 或 CONFIRMED) |
|
||||
| reopen 反确认后 GET 报表 | 指纹不一致,reportStatus = STALE | 按落库状态返回,无 STALE |
|
||||
| 出参字段 | 含 sourceFingerprint | 不再含该字段 |
|
||||
| 报表金额数值 | 实时计算 | 实时计算(**不变**) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**是,硬破坏**。出参删除 sourceFingerprint 字段 + 枚举删除 STALE 值。旧前端若读取 sourceFingerprint 得到 undefined、若对 STALE 写了特判分支则永远不会再命中(静默失效,不报错)。
|
||||
- **前端是否必须同步上线**:**必须同批**。前端需先停读 sourceFingerprint、清理 STALE 特判(刷新提示/禁用确认按钮/重新生成入口等),再与后端同批发布。
|
||||
- **上线顺序边界**:前后端同批发布;若必须分先后,**先上前端(停读字段 + 清理 STALE 分支),再上后端**。旧前端 + 新后端不会报 500,但 STALE 相关 UI 逻辑成死代码。
|
||||
- **数据库侧**:本批次后端同步 DROP 了 settlement 域 15 个指纹列(Flyway 迁移随服务部署执行),与前端无直接关系,但意味着**回滚后指纹无法恢复原值**(见 §11.2)。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 代码回滚即恢复 sourceFingerprint 出参与 STALE 派生逻辑;但**数据库指纹列已 DROP**,回滚后重新计算的落库指纹为空,历史报表的 STALE 比对行为不可完全复原。
|
||||
- 因此**回滚必须前后端同批回滚**,且接受「历史报表不再派生 STALE」的行为落差。数据侧无业务数据修复成本(指纹非业务数据)。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 本次只动两个报表查询接口的出参与 reportStatus 枚举;接口路径、入参、金额数值计算逻辑**均未变化**。
|
||||
- 与 #5704 的关系:#5704 下线**写入路径**(保存/确认/完成核单 6 个端点)的指纹入参与出参;本次 #5739 下线**报表读取路径**的指纹出参与 STALE 派生。两批共同完成指纹机制的整体下线,前端的指纹相关代码应已全部清除。
|
||||
- 原分类确认状态 VO(SettlementCategoryChecksRespVO)中的 sourceFingerprint 字段本次也顺手删除、confirmStatus 的 STALE 派生同步移除;但该 VO 对应的 category-checks 接口**早在 PR #5324 已下线**,当前无任何端点返回它,前端无感,无需处理。
|
||||
- finalize(完成核单)接口的 Swagger notes 中「提交双指纹」属文档残留,实际入参指纹字段已在 #5704 删除,以 #5704 changelog 为准。
|
||||
- 小程序端(/v3/mp/*)不涉及本次变更,无需任何改动。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5739](https://git.1814.love:8443/wx/HL/issues/5739)
|
||||
- **PR**: [#5764](https://git.1814.love:8443/wx/HL/pulls/5764)
|
||||
- **Merge Commit**: [60e4409a](https://git.1814.love:8443/wx/HL/commit/60e4409a77ca81e203e456f063e3d8d91d8f48a1)
|
||||
- **前置批次**: Issue [#5704](https://git.1814.love:8443/wx/HL/issues/5704) / PR [#5709](https://git.1814.love:8443/wx/HL/pulls/5709)(写入路径指纹下线,changelog 见 2026-08/08_5704)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst)
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5746"
|
||||
title: "全项目报错文案中文治理——兜底不泄漏英文原文,校验/错误码/e2e 文案全中文"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5760 已合并 dev-v3 并部署 TEST(order-v3/fleet/resource/user/order-v2/product-v2/mp 七服务)。接口字段/结构/错误码 code 均不变,仅 message 文案治理:500 兜底不再拼接[异常类名]:英文原文(统一中文+带 traceId)、校验/JSON 解析/类型不匹配等框架错误全中文、39 条英文错误码与 318 条夹杂技术词文案中文化、e2e 引擎文案中文化、防回潮守门测试上线。前端无需改动。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T18:00:00+08:00"
|
||||
---
|
||||
|
||||
# 全项目报错文案中文治理——兜底不泄漏英文原文,校验/错误码/e2e 文案全中文
|
||||
|
||||
> 后端完成:PR [#5760](https://git.1814.love:8443/wx/HL/pulls/5760) 已合并 dev-v3 并部署 TEST,网关实证通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5746](https://git.1814.love:8443/wx/HL/issues/5746)
|
||||
- **PR**: [#5760](https://git.1814.love:8443/wx/HL/pulls/5760)
|
||||
- **Merge commit**: [fa7dc738f](https://git.1814.love:8443/wx/HL/commit/fa7dc738f)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
|
||||
## 背景
|
||||
|
||||
前端弹窗直接展示英文技术异常原文(真实截图:`服务器内部错误[UnknownClassException]: Unable to find an implementation for interface io.jsonwebtoken.io.Serializer...`)。治理目标:到达前端响应的 message 必须全中文、无技术黑话、说人话(请检查/请重试/联系客服)、可定位(traceId)。
|
||||
|
||||
## 变更内容
|
||||
|
||||
1. **兜底 handler 改造(hl-common-log GlobalExceptionHandler,全项目生效)**:`handleGeneral` 不再拼接 `[异常类名]: 英文原文`,统一「系统繁忙,请稍后重试或联系客服」+ traceId(原文只进日志/监控);`handleConstraint`/`handleValidation`/`handleBind` 非中文文案一律降级中文;`handleHttpMessageNotReadable` 不再回显 Jackson 英文原文;`handleTypeMismatch` 去掉 Java 类型名;`handleBusiness` 的 null 文案兜底。
|
||||
2. **全局中文校验兜底**:新增 `ValidationMessages.properties`(hl-common-log,27 键覆盖 javax.validation 内置约束),287 处无 message 校验注解自动中文(决策:全局兜底方案替代逐一补 message,记录于工单)。
|
||||
3. **错误码文案治理**:39 条无中文(含 E2E 581090-581099、合同 510302/510503/510504、结算 584014/584015、微信 240102 等)+ 318 条中文夹杂技术词(字段名→中文、枚举值→中文、字典 key/配置 key→中文;保留日期格式 yyyy-MM-dd、车型 SUV/MPV、文件格式 PDF/SVG 等用户可理解项)逐条过一遍;23 条纯 `{0}` 占位符约定调用方必须填中文(javadoc + 守门测试)。
|
||||
4. **e2e 引擎文案中文化**:E2eRunService/E2eScopedLifecycleService/E2eScopedOrderCreateService/E2eVehicleAssignmentFinalizeService 等 70+ 处英文 → 中文,同步测试断言。
|
||||
5. **防回潮守门**:新增 `ErrorCodeChineseAuditTest`(错误码 message 必须含中文;BusinessException 单实参字面量必须中文)+ `ValidationMessagesPresenceTest`。
|
||||
6. **测试基础设施**:resource-service Testcontainers 1.19.8→1.21.4(Docker Desktop 29 API 兼容)。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 任意未捕获异常(500 兜底) | `服务器内部错误[UnknownClassException]: Unable to...` | `系统繁忙,请稍后重试或联系客服` + traceId |
|
||||
| 校验失败(无 message 注解) | `must not be null` / `list.arg0: must not be empty` | `该字段不能为空` 等中文 |
|
||||
| JSON 解析失败 | 回显 `Unrecognized field "xxx"...` 英文片段 | `请求数据格式错误,请检查参数是否正确` |
|
||||
| 参数类型不匹配 | `参数类型错误: xx='yy'(需要 Long 类型)` | `参数 xx 格式错误,请检查后重试` |
|
||||
| 错误码文案(510302/E2E 等 39+318 条) | 英文/技术词/纯占位符 | 全中文 |
|
||||
| e2e 引擎错误(581090-581099) | `E2E RUN_STATE_CONFLICT: version mismatch` | `测试执行状态冲突:版本不一致` |
|
||||
|
||||
**不变**:接口字段/结构、HTTP 状态语义(400/401/403/404/405/500)、错误码 code 全部保持,前端仅展示文案变化。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **网关实证(TEST,ROOM_MANAGER)**:非法 JSON → `请求数据格式错误,请检查参数是否正确`;缺参 → `服务日期不能为空`;类型不匹配 → `参数 orderId 格式错误,请检查后重试`(无 Java 类型名);业务错误 → `该订单非房务可见`;网关 401 → `缺少有效的 Authorization 头` + traceId。
|
||||
- **500 兜底**:GlobalExceptionHandlerTest 单测断言 handleGeneral 返回 `系统繁忙,请稍后重试或联系客服` 且不含异常类名/英文(真实截图 JJWT 场景在旧实例复现后,重启新代码登录恢复正常)。
|
||||
- **测试**:守门测试 4/4;order-v3 7550 / fleet 3307 / user 3530 / resource 1755 / order-v2 3496 / product-v2 1510 / mp 986 全量 verify(文案相关全绿,基线失败集除外:fleet ReleaseEOccupancy 10、order-v3 IT 上下文 51 + 业务断言漂移 5 等,dev-v3 原样复现)。
|
||||
|
||||
## 前端交接
|
||||
|
||||
无接口契约变化(字段、结构、错误码 code 均不变,仅 message 文案),前端无需改动;弹窗直接展示 message 即可。
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5750"
|
||||
title: "派单操作时间线接口:订单卡片「查看日志」数据源"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a9a48643"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "已实现。board.js 新增 getFleetOrderOperationLog;resolveBoardRowActions 全状态分支加「查看日志」(卡片图标按钮+悬浮提示、表格入「更多」);新建 OperationLogModal 时间线弹窗(分页/keyword/时间区间/升降序,opTypeLabel/summary 直渲)。frontend_ref=a9a48643(BoardCardView 卡片入口由并发提交 7ec70cc0 承载)。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T18:10:00+08:00"
|
||||
---
|
||||
|
||||
## 新增接口
|
||||
|
||||
`GET /admin/fleet/orders/{orderId}/operation-log`
|
||||
|
||||
查询该订单全部派单操作时间线(数据源 `fleet_assignment_operation_log`),按 create_time 倒序分页(默认 pageSize=50,上限 200)。
|
||||
|
||||
### 请求参数(query)
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| page | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 50,上限 200 |
|
||||
| sortBy | string | 否 | `time,desc`(默认)/ `time,asc` 升序看完整时间线 |
|
||||
| keyword | string | 否 | 模糊搜索(summary/操作人),≤32 字 |
|
||||
| startDate / endDate | string | 否 | 时间区间过滤(ISO 8601,如 `2026-08-01T00:00:00`) |
|
||||
|
||||
### 返回体
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2086386125831561218",
|
||||
"time": "2026-08-09 17:36:30",
|
||||
"opType": "change_completed",
|
||||
"opTypeLabel": "修改派单完成",
|
||||
"summary": "王骁 修改派单完成:蒙A-E2E99宝音德力格尔 → 蒙A-K1999其木格,生效日 2026-08-23",
|
||||
"operatorName": "王骁",
|
||||
"effectiveDate": "2026-08-23",
|
||||
"detailJson": "{\"previousVehicleId\":\"...\",\"newVehicleId\":\"...\"}"
|
||||
}
|
||||
],
|
||||
"total": 25,
|
||||
"page": 1,
|
||||
"pageSize": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
- `id` / `time`:日志 ID / 操作时间
|
||||
- `opType`:操作类型英文枚举(cancel_requested / cancel_completed / cancel_restored / cancel_failed / cancel_evidence_recorded / driver_notification_recorded / insurance_refund_pending / insurance_refund_succeeded / insurance_refund_failed / change_requested / change_completed / change_failed / slot_added / slot_removed / hold_notification / assignment_created / confirmed / driver_confirmed)
|
||||
- `opTypeLabel`:中文标签(直接渲染)
|
||||
- `summary`:**后端拼好的可读摘要**(操作人 + 动作 + 前后资源对比,如改派「旧车牌旧司机 → 新车牌新司机,生效日 x」、失败带原因、保险退保带明细统计),前端直接展示
|
||||
- `operatorName`:操作人——**企业微信名优先,无企微名显示用户名**;系统/自动动作(如保险退保定时任务)为「系统」
|
||||
- `effectiveDate`:生效日(按天改派/取消等),无则 null
|
||||
- `detailJson`:明细 JSON 字符串(前后值/原因等),前端按需展示或忽略
|
||||
|
||||
### 权限
|
||||
|
||||
网关 `/admin/fleet/**` 统一鉴权,车务/管理员可见。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- hl-fleet-service verify 3319/0F/0E + FleetRedLineArchTest 12/12 + spotless 通过;定向测试 7/7
|
||||
- TEST 网关实证(订单 26-6436,探针 `tools/probe_5750_operation_log.py` 11/11 PASS):字段齐全、opTypeLabel 中文、summary 含前后资源对比、系统动作显示「系统」、分页/升序/翻页正常、total 与 DB 一致
|
||||
|
||||
---
|
||||
|
||||
## 后端修复补充 (#5770,2026-08-10,PR #5775)
|
||||
|
||||
首版只给 `assignment_created / hold_notification / confirmed / driver_confirmed` 四类补了枚举中文标签,但**没有写入点**——时间线永远缺「新建派单/HOLD 通知/司机确认/确认执行」这四类里程碑行。本次读侧合成补齐,前端**无需改动**,同一接口现返回更完整的时间线。
|
||||
|
||||
### 行为变化(同接口,返回项更全)
|
||||
1. **新增 4 类里程碑合成行**:读接口时从 `fleet_assignment` 的 `create_time / hold_sent_at / driver_confirmed_at / confirmed_at` 合成 `assignment_created`(新建派单)/`hold_notification`(HOLD 通知)/`driver_confirmed`(司机确认)/`confirmed`(确认执行) 行,与落表行按时间合并、去重后返回。
|
||||
- 合成行 `id=null`(无 `fleet_assignment_operation_log` 主键,前端可据此区分合成 vs 真实行,与房务 #4246 同约定)。
|
||||
- 司机确认行 `operatorName` = 司机姓名(非管理员);其余里程碑走操作人解析。
|
||||
2. **keyword 现按前端所见字段过滤**:此前只搜库里恒为「车务/系统」的原始 actor_name 与写侧动作词,按操作人真名/车牌/司机名搜必空;现改为对**渲染后**的 operatorName/summary/opTypeLabel/detailJson 过滤,可搜到展示的操作人真名、车牌、司机名。
|
||||
3. **`effectiveDate`「生效日」修复**:改派 summary 的生效日此前因写侧 Hutool 把 LocalDate 序列化成 epoch 毫秒、读侧解析失败而**永不渲染**;现写侧改 ISO 明文、读侧兼容 epoch 毫秒/ISO 两形态,存量数据也能正确显示。
|
||||
|
||||
### TEST 实证(2026-08-10)
|
||||
`GET /admin/fleet/orders/2086341233369616386/operation-log` 返回 4 条合成行:新建派单(系统) / 司机确认(巴雅尔) / HOLD 通知(米明光) / 新建派单(系统),中文标签、操作人三路解析、时间倒序正确。
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5751"
|
||||
title: "车务管理员查看订单详情/打印行程单误拦修复(OrderViewGuard 读接口放行 VEHICLE_MANAGER)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5765 已合并 dev-v3 并部署 TEST(18:31 滚动 DONE)。网关角色切换链路探针 ALL PASS:admin 切车务管理员后详情/行程/打印行程单/发票/支付流水/预支候选/状态日志全部 200(修复前 581008);房务仍 581045、定制师非本单仍 581008。前端无需配合。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T18:50:00+08:00"
|
||||
---
|
||||
|
||||
# 车务管理员查看订单详情/打印行程单误拦修复
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: [#5765](https://git.1814.love:8443/wx/HL/pulls/5765)
|
||||
> **Issue**: [#5751](https://git.1814.love:8443/wx/HL/issues/5751)
|
||||
> **日期**: 2026-08-09
|
||||
> **影响**: 🟢 **缺陷修复**,无契约结构变更(角色守卫口径修正,接口/字段/错误码均不变)。前端无需改代码。
|
||||
|
||||
## 背景
|
||||
|
||||
#5751(P1)实证:admin 账号切「车务管理员」(VEHICLE_MANAGER)角色在派单看板点「打印行程单」报 **581008 无权查看此订单**。#5504 读接口越权收口(#5512/#5513)把订单详情/行程/打印/签单/发票/预支/支付/调整单/推送记录等读端点从「放行非房务角色」收紧为「必须本单定制师」,**漏放行 VEHICLE_MANAGER**——车务管理员是派车作业角色(跨订单),本就不是定制师,被误拦。
|
||||
|
||||
## 修复内容(守卫口径修正,无接口变化)
|
||||
|
||||
`OrderViewGuard` 新增 **`assertOrderReadable(order)`**:放行 `ADMIN`/`SUPER_ADMIN`/`VEHICLE_MANAGER`,其余后台角色仍须本单定制师(否则 581008);房务(ROOM_MANAGER/house_keeper_lead)仍 581045。
|
||||
|
||||
#5504 收口的**读接口**全部切换至 `assertOrderReadable`:
|
||||
- 订单详情 / 行程 / 状态日志 / 发票(OrderDetailService)
|
||||
- 取消预览(OrderService.getCancelPreview)
|
||||
- 打印行程单(PrintItineraryService)/ 签单预览(SignVoucherService)
|
||||
- 预支查询与候选(OrderAdvanceService)
|
||||
- 发票管理(InvoiceAdminService)/ 调整单(AdjustmentAdminController)/ 推送记录(OrderPushRecordService)
|
||||
- 支付流水 / 线下收款列表与选项(AdminOrderPaymentController / ManualReceiptService)
|
||||
|
||||
**财务写域保持收紧**(仍 `assertOrderAccessible`,车务管理员不得执行):终止退款(prepareTermination)、核单(settlement 全部)、收款登记写操作。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| GET | /v3/admin/order/:id(详情)/itinerary/print-itinerary/invoices/status-log | VEHICLE_MANAGER 可访问(修复前 581008) |
|
||||
| GET | /v3/admin/order/:id/advance/payee-candidates、/adjustment-record、/payment/list、推送记录等 #5504 收口读端点 | VEHICLE_MANAGER 可访问 |
|
||||
| — | 终止退款/核单/收款登记等财务写接口 | 不变(车务管理员仍无权) |
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 受影响面定向测试全绿:OrderViewGuardTest 16、OrderDetailServiceTest 84、OrderServiceTest 178、两控制器测试 5+6、Advance 5、Invoice 3、PushRecord 5。
|
||||
- order-v3 全量 verify 7561 tests:2 Failures(#5599 H2 schema 漂移,基线既有)+ 24 Errors(Testcontainers 3306 端口争用/loadbalancer 环境,基线实证同失败),与本改动无关。
|
||||
- 部署 TEST 18:31 滚动 DONE。
|
||||
- 网关探针 ALL PASS(订单 2086341233369616386):车务管理员 7 个读端点全部 200;ROOM_MANAGER 仍 581045;CUSTOMIZER 非本单仍 581008(越权收口不回退)。
|
||||
|
||||
## 前端配合
|
||||
|
||||
无需配合。前端调用链不变,角色权限由后端守卫恢复;车务管理员账号(当前角色 VEHICLE_MANAGER)的订单详情/行程单功能自然恢复。
|
||||
|
||||
---
|
||||
|
||||
## 后续补充 (#5777 审查扫尾,2026-08-10)
|
||||
|
||||
审查发现同族读端点漏切一个:**优惠/附加费清单** `GET /v3/admin/order/{orderId}/discount-surcharge/list`(`DiscountService.listDiscountSurcharge`)此前仍走 `assertOrderAccessible`,车务管理员打开订单详情「优惠」Tab 会复现 581008。本次守卫切到读侧 `assertOrderReadable`,与支付流水/发票等读端点同口径:VEHICLE_MANAGER 可读,房务/组长 581045、非本单定制师 581008 不变。定向用例 DiscountServiceAccessGuardTest 16 绿,已部署 TEST。前端无需配合。
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-house-detail-tabs-empty"
|
||||
title: "房务订单详情「定制师需求/配房行程/操作日志」tab 全显示 0/暂无数据,但后端数据正常返回"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "fa59235d"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "已修复。真根因:父组件 v-if+show 同拍驱动 OrderDetailModal,挂载瞬间 show 已是 true,驱动 load() 的 watch 无 immediate 漏触发 → 详情接口从未调用 → 三 tab 全 0(非 adapter 断点,探针证伪 changelog 原自述)。修复:OrderDetailModal setup 末尾 onMounted 兜底,挂载时 show+orderId 就位即补 load();新增 OrderDetailModal.spec 回归。frontend_ref=fa59235d。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T16:10:00+08:00"
|
||||
---
|
||||
|
||||
# 房务订单详情 tab 全显示 0/暂无数据,但后端数据正常
|
||||
|
||||
> 前端缺陷,待前端排查。后端数据正常。
|
||||
|
||||
## 现象(TEST 实测,订单 26-3559 吴国强,order_id=2086270159575535618)
|
||||
|
||||
定制师已提交用房需求,但房务打开订单详情:
|
||||
- **配房行程**:计数 0,内容「暂无配房行程数据」
|
||||
- **定制师需求**:计数 0
|
||||
- **操作日志**:计数 0
|
||||
|
||||
## 后端数据实测正常(不是后端问题)
|
||||
|
||||
```
|
||||
GET https://api.test.1814.love:9443/admin/house/orders/2086270159575535618
|
||||
```
|
||||
|
||||
返回 code=200,data 包含完整数据:
|
||||
- `requirement.current.days`:完整需求明细(day1 8/24 满洲里市 标间 STANDARD 4间 预算 1120.00 等,consultantName=王骁)
|
||||
- `requirement.current`:requirementId=2086358876319375361,status=PROCESSING,isCurrent=true
|
||||
- `itinerary`:配房行程 1 条(dayNumber=1, stayDate=2026-08-24, 海拉尔区, arrange=pending 待配房, expectedRoom 标间4间)
|
||||
- `tabCounts`:`{"itineraryCount":0,"messageCount":1,"unreadMessageCount":0,"operationLogCount":3}`
|
||||
|
||||
**后端需求/行程/日志数据都正常返回了**,但前端三个 tab 全显示 0/空。
|
||||
|
||||
## 前端问题定位线索
|
||||
|
||||
`OrderDetailModal.vue` 的 tab 计数绑定:
|
||||
- 配房行程:`merged.days.length`
|
||||
- 定制师需求:`messageCount`(computed 读 `merged.messageCount`)
|
||||
- 操作日志:`operationLogCount`(computed 读 `merged.operationLogCount`)
|
||||
|
||||
`merged` 来自 `orderDetailAdapter.js`。后端 `tabCounts.messageCount=1`、`operationLogCount=3`、`itinerary`/`requirement.current.days` 都有数据,但前端全显示 0——**疑似 adapter 没正确把后端的 itinerary/requirement.current.days/tabCounts 映射进 merged.days/messageCount/operationLogCount**,或组件读取字段与 adapter 输出字段对不上。
|
||||
|
||||
## 期望
|
||||
|
||||
房务订单详情正确显示:
|
||||
- 配房行程 tab:显示 itinerary 的每日配房行程(待配房/已配房)
|
||||
- 定制师需求 tab:显示 requirement.current 的需求明细(每天房型/间数/预算/备注)
|
||||
- 操作日志 tab:显示操作日志(operationLogCount=3 条)
|
||||
|
||||
## 复现路径
|
||||
|
||||
房务(housekeeper)→ 订单详情(26-3559 吴国强)→ 看「配房行程/定制师需求/操作日志」三个 tab 计数与内容。
|
||||
|
||||
## 备注
|
||||
|
||||
- 后端接口数据完整(上方实测),**纯前端渲染/适配问题**。
|
||||
- 建议前端在浏览器 DevTools 看 `/admin/house/orders/{orderId}` 实际响应 vs 组件渲染,定位 adapter 映射断点。
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-assign-modal-close-on-next"
|
||||
title: "派单待确认页点「下一步」后整个派单窗口关闭,应保持开启进入确认执行"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "4c81471c"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端已实现:handleHoldSubmit 待确认链路 batch/change 提交后改发 submitted-keep-open 不关窗(完成态 direct 保留 submitted+关窗);AssignModal defineEmits 加 submitted-keep-open;index.vue 新增 onSubmittedKeepOpen 复用 onAssignmentBaselineConflict 同款恢复路径(refreshBoardAfterMutation 刷新详情+仍 holding 则 assignMode=confirmHold),由 AssignModal 重开 watch 驱动进待确认/确认执行。useAssignFlow.spec 36/36(含 batch 不关窗+完成态关窗锁定)、AssignModal 关联 60/60、checkpoint 全过。纯前端交互修复。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T16:40:00+08:00"
|
||||
---
|
||||
|
||||
# 派单待确认页点「下一步」后整个派单窗口关闭,应保持开启进入确认执行
|
||||
|
||||
> 前端交互缺陷,待前端修复。
|
||||
|
||||
## 现象(TEST 实测,订单 26-6436 周梦洁)
|
||||
|
||||
派单流程走到第 3 步「待确认」页(司机待确认通知 + 微信消息预览 + 等待司机回复确认),点底部「**下一步**」按钮后,**整个派单窗口(弹窗)直接关闭了**——没有进入第 4 步「确认执行」。
|
||||
|
||||
车务还得重新打开订单才能继续确认执行,体验断档。
|
||||
|
||||
## 期望
|
||||
|
||||
待确认页点「下一步」:
|
||||
- 提交待确认(登记司机确认/进入下一步)成功后,**保持配车/派单窗口开启**,进入第 4 步「确认执行」,让车务继续完成确认执行操作。
|
||||
- 而不是直接关闭整个派单窗口。
|
||||
|
||||
## 复现路径
|
||||
|
||||
派单看板 → 已派车订单(如 26-6436)→ 派单流程走到第 3 步「待确认」页 → 点底部「下一步」→ 观察:整个派单窗口关闭(应进入第 4 步确认执行)。
|
||||
|
||||
## 备注
|
||||
|
||||
- 纯前端交互/路由问题(步骤推进时窗口状态管理),后端无问题。
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-continuity-notify-tabs"
|
||||
title: "车辆接续场景:派车通知需按司机分多 tab(接入 render-batch)+ 按日改派槽位汇总显示接续"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "84ed152e"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "车辆接续(一槽多天不同车/司机)派车通知需按司机分 tab。mmg 已在 d7f78a17 接入 render-batch 分 tab,但实测只显示【未确认】段(改派 8/23 道尔吉时只有道尔吉 tab,已确认的王信 8/24-25 被 slotChangeRows 的『终态段不产生行』过滤掉了)。车务口径 B:接续单派车通知要展示【全部接续司机】tab(含已确认段),不只未确认段——故 reopen 待补充。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-09T11:20:00+08:00"
|
||||
---
|
||||
|
||||
# 车辆接续场景:派车通知按司机分多 tab + 按日改派槽位汇总显示接续
|
||||
|
||||
> 前端改造,**后端能力已具备、无需改后端**。待前端接入。
|
||||
|
||||
## 场景(车辆接续)
|
||||
|
||||
一个车辆槽位在多天内由**不同车辆/司机接续**服务。例:订单 26-6436(8/23~8/25 三天),按天改派后:
|
||||
- 8/23:蒙A-E2E01 道尔吉
|
||||
- 8/24~8/25:蒙A-E2E99 王信
|
||||
|
||||
即同一槽位 8/23 是道尔吉、8/24-25 是王信——**一个槽位对应多个司机/多辆车(接续)**。
|
||||
|
||||
## 问题 1:派车通知只显示单 tab,接续的其他司机没通知预览
|
||||
|
||||
**现象**:待确认页「司机待确认通知」微信消息预览**只有一个 tab(道尔吉)**,接续的王信(8/24-25)没有 tab、看不到也给不了他的派车通知。
|
||||
|
||||
**期望**:派车通知预览**按司机分多个 tab,一个 tab 一个司机**,每个 tab 显示该司机所负责天数/车辆的派车通知;发送时按选中司机取对应的 renderedBody。
|
||||
|
||||
**后端已支持——直接用 `render-batch` 批量渲染接口**:
|
||||
|
||||
```
|
||||
POST https://api.test.1814.love:9443/admin/fleet/message-templates/{templateId}/render-batch
|
||||
```
|
||||
|
||||
接口注释原文:**"多槽位/多司机预览按槽位逐项渲染同一模板,返回与请求 items 同序的结果列表;前端预览区按司机分 tab,发送时按选中司机取对应 renderedBody。"**
|
||||
|
||||
请求体(**真实调用**,订单 26-6436 orderId=2086270156475936769,模板 id=2073978002412105729「排车待确认-标准」):
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{"orderId":"2086270156475936769","vehicleId":"2085284111341023234","driverId":"2065272150012444674","serviceDates":["2026-08-23"]},
|
||||
{"orderId":"2086270156475936769","vehicleId":"2085539421276286978","driverId":"2067084362829979650","serviceDates":["2026-08-24","2026-08-25"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
- `items[]` 每项 = 一个司机/接续段的预览入参(orderId/vehicleId/driverId/assignmentGroupId/serviceDates 等,同单渲染),1~20 项;1 项时等价单渲染(无 tab)。
|
||||
- vehicleId:蒙A-E2E01=2085284111341023234 / 蒙A-E2E99=2085539421276286978;driverId:道尔吉=2065272150012444674 / 王信=2067084362829979650。
|
||||
|
||||
响应(**真实返回**,code=200):
|
||||
```json
|
||||
{"code":200,"message":"成功","data":{"items":[
|
||||
{"success":true,"renderedBody":"呼伦旅行—订车单\n...\n道尔吉,您好,请确认以下订车信息:\n团号:26-6436\n日期:2026-08-23 至 2026-08-25\n人数:2人(成人2)\n车辆:蒙A-E2E01 丰田普拉多(7座)\n..."},
|
||||
{"success":true,"renderedBody":"呼伦旅行—订车单\n...\n王信,您好,请确认以下订车信息:\n团号:26-6436\n日期:2026-08-23 至 2026-08-25\n人数:2人(成人2)\n车辆:蒙A-E2E99 丰田普拉多(7座)\n...行程详情:https://web.test.1814.love:9443/s/tlMa2xQ..."}
|
||||
]}}
|
||||
```
|
||||
- `items[]` 与请求同序,每项独立 success + renderedBody;单项失败不阻断其他项。
|
||||
|
||||
**⚠️ 实测发现的后端问题(另见后端工单)**:两个司机渲染的「日期」都是「2026-08-23 至 2026-08-25」(订单全程),**没按各自 serviceDates 分段**——道尔吉只负责 8/23(serviceDates=[8/23])却显示全程,王信负责 8/24-25 也显示全程。司机会搞不清自己负责哪几天。需后端 render-batch 渲染的日期按 serviceDates 分段。
|
||||
|
||||
**前端现状**:`src/api/fleet/message-template.js` **只封装了单渲染 `POST /render`,没有 render-batch**;待确认页预览仍调单渲染,只渲染第一个司机。
|
||||
|
||||
**前端要改**:
|
||||
1. `message-template.js` 增加 `renderBatch(templateId, items)` 封装(POST `/fleet/message-templates/{templateId}/render-batch`)。
|
||||
2. 待确认页派车通知预览:从 `board/orders/{orderId}` 的 `dailyVehiclePlan` 按 `assignmentId`/司机分组得到各接续段(每段的 vehicleId/driverId/serviceDates),调 render-batch 传入多 item;预览区按司机分 tab 展示各 item 的 renderedBody;发送时按选中司机取对应 renderedBody。
|
||||
|
||||
## 问题 2:按日改派顶部槽位汇总只显示第一天,未反映接续
|
||||
|
||||
**现象**:按日改派表格里每天已正确显示各车各司机(8/23 道尔吉、8/24-25 王信),但**顶部槽位表格「当前车辆/当前司机」只显示第一天的**(蒙A-E2E01 道尔吉),看不出这是接续单。
|
||||
|
||||
**期望**:接续槽位的顶部「当前车辆/当前司机」汇总应体现接续(如显示「多车接续」/分段列出各段车与司机,或标注「接续:道尔吉(8/23)+王信(8/24-25)」),让车务一眼看出该槽位是多车多司机接续。
|
||||
|
||||
**数据源**:`board/orders/{orderId}` 的 `dailyVehiclePlan`(按 serviceDate 各天 vehiclePlate/driverName/assignmentId)——前端按 assignmentId 分组即可识别接续并分段展示。
|
||||
|
||||
## 复现路径
|
||||
|
||||
派单看板 → 已派车订单(如 26-6436)→ 改派 → 下一步 → 按天改派 → 选中 8/23 改派给另一车/司机(形成接续)→ 待确认页看派车通知预览(只有单 tab);排车页看顶部槽位汇总(只显示第一天)。
|
||||
|
||||
## 补充 2(2026-08-09,新建派车 batchMode 槽位内接续也丢司机 tab)
|
||||
|
||||
**现象**:新建派车(多槽位,非改派)场景,一个槽位内有接续(多天不同司机)时,派车通知只按「槽位」分 tab,**槽位内接续的司机没有单独 tab**。实测 26-3559 吴国强:槽位1 蒙A-E5555(阿木古愣 8/24-25 + 道尔吉 8/26 接续)、槽位2 蒙A-G8888(满都拉全程),共 **3 个司机**,但派车通知只 **2 个 tab(阿木古愣、满都拉)**——道尔吉(槽位1 的 8/26 接续)没 tab,且阿木古愣 tab 的日期含不属于他的 8/26。
|
||||
|
||||
**根因**:`AssignModal.messagePreviewSlots` 的 batchMode 分支(3131-3153)按 `selectedSlots.map`(每槽位 1 项),`driverName = slot.driver?.name || cells.find(driverName)`(取槽位第一个司机)、`serviceDates = 全部 used 天`——**没拆槽位内接续司机**。
|
||||
|
||||
**期望**:batchMode 也按「槽位×司机段」分 tab——同一槽位内有接续(多天不同司机)时,拆成多个司机 tab(如槽位1 拆成 阿木古愣 8/24-25 + 道尔吉 8/26 两个 tab),每个 tab 的 serviceDates 只含该司机负责的天。与改派分支的口径一致(按司机分段)。
|
||||
|
||||
## 备注
|
||||
|
||||
- 后端 `change` 接口的 `serviceDates`(按天改派限定日期集,其余天保留)已支持拆接续;`render-batch` 已支持多司机预览。**后端两处均无需改**。
|
||||
- 关联:改派回显缺陷(08_frontend_改派选完车司机槽位列不回显)——都是接续/改派场景的前端展示问题,可一并处理。
|
||||
|
||||
## 补充(2026-08-09 车务口径 B,reopen 原因)
|
||||
|
||||
mmg 已在 d7f78a17 接入 render-batch 分 tab,但实测(订单 26-6436,改派 8/23 道尔吉、8/24-25 王信)**只显示道尔吉 1 个 tab**——因为 `slotChangeRows`(AssignModal.vue:1306)的过滤规则是「**终态段不产生行**」,已确认的王信 8/24-25 段被过滤,只有未确认的道尔吉段生成 tab。
|
||||
|
||||
**车务口径 B:接续单派车通知要展示【全部接续司机】tab(含已确认段),不只未确认段。**
|
||||
|
||||
- **期望**:改派/接续场景待确认页,派车通知按该槽位【全部接续司机】分 tab(如道尔吉 8/23 + 王信 8/24-25 两个 tab),含已确认段;车务可复制任意 tab 话术分别发给对应司机(已确认段是否重发由车务决定,但预览要能看到全部)。
|
||||
- **需调整**:`slotChangeRows`(或派车通知预览专用的段数据源)不要过滤已确认终态段,把该槽位全部接续执行段都喂给 render-batch 分 tab。
|
||||
- **接口**:仍用 `POST /admin/fleet/message-templates/{templateId}/render-batch`,items 传全部接续段(每段 vehicleId/driverId/serviceDates),后端渲染无问题(已实证能渲染任意段)。
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "N/A"
|
||||
title: "核单「完成核单」按钮校验逻辑简化"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ce4d98b3"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "已实现。canComplete 由 5 条减为 3 条 UI 态防误触(hasLoadingStep/hasSavingStep/dirty),askComplete 同步删 vehicleSettlementBlocked 兜底拦截,并删除车辆分类顶部常驻提示条;LEGACY 金额未知空态的车辆编辑锁保留(防空草稿存成 0 元)。frontend_ref=ce4d98b3。"
|
||||
updated_at: "2026-08-09"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 核单「完成核单」按钮校验逻辑简化
|
||||
|
||||
## 变更类型
|
||||
修改接口(前端逻辑调整,后端零改动)
|
||||
|
||||
## 端类型
|
||||
管理后台
|
||||
|
||||
## 变更说明
|
||||
|
||||
「完成核单」按钮的前端校验逻辑简化,去掉业务约束类校验,只保留 UI 态防误触校验。
|
||||
|
||||
### 改动前(5 条校验)
|
||||
|
||||
```javascript
|
||||
const canSubmit = computed(() =>
|
||||
!hasLoadingStep && // 1. 没有任何分类在加载中
|
||||
!hasSavingStep && // 2. 没有任何分类在保存中
|
||||
!dirty && // 3. 没有任何未保存的修改
|
||||
!vehicleSettlementBlocked && // 4. 车辆核单状态不被拦(删)
|
||||
reportsReady // 5. 司机报账表 + 单团核算表都已生成(删)
|
||||
)
|
||||
```
|
||||
|
||||
### 改动后(3 条校验)
|
||||
|
||||
```javascript
|
||||
const canSubmit = computed(() =>
|
||||
!hasLoadingStep && // 1. 没有任何分类在加载中
|
||||
!hasSavingStep && // 2. 没有任何分类在保存中
|
||||
!dirty // 3. 没有任何未保存的修改
|
||||
)
|
||||
```
|
||||
|
||||
### 删除的 2 条校验及原因
|
||||
|
||||
| 删除项 | 原因 |
|
||||
|---|---|
|
||||
| `vehicleSettlementBlocked` | 后端 `finalize` 已有统一硬校验(`performSubmitBlockingChecks`),前端无需重复拦截 |
|
||||
| `reportsReady` | 报表是 finalize 的**产出**而非**前置条件**,逻辑反了,直接删除 |
|
||||
|
||||
### 后端统一拦截行为
|
||||
|
||||
用户点击「完成核单」后,后端 `finalize` 接口统一校验:
|
||||
- 对账一致性(支付流水 = 手工收款)
|
||||
- 车辆费用 freeze 校验
|
||||
- 导游/摄影费用确认状态
|
||||
- 司机结算完成 + 转账凭证
|
||||
- 其他收入确认状态
|
||||
|
||||
校验失败返回对应错误码 + 提示信息,前端直接展示即可。
|
||||
|
||||
## 影响范围
|
||||
- 管理后台核单页面「完成核单」按钮
|
||||
- 后端零改动
|
||||
|
||||
## 关联
|
||||
- 无 Issue(口头需求)
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5778"
|
||||
title: "房型分类字典 room_category:BIG_BED 下线合并到 QUEEN(消除同名「大床房」碰撞)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ca770153"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "前端已实现:实证前端唯一 BIG_BED 引用为 orderDetailAdapter.js ROOM_CATEGORY_LABEL 兜底表 BIG_BED:'大床房'(#5730 补,3ec1a933),后端字典 BIG_BED 全链路不出现+存量订正 QUEEN 后成死代码,已删除;「大床房」统一由 QUEEN 承载。grep 复核 BIG_BED 全清零(仅注释);orderDetailAdapter.spec 32/32 过 + checkpoint 全过。注:原 frontend_status=none 为笔误(非法值),实为有改动,经 pending→claimed→implemented 交付。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T11:00:00+08:00"
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
#5730 给 `room_category` 字典补了 `BIG_BED → 大床房`,但字典早已有 `QUEEN → 大床房`,导致同一分类下出现两条不可区分的 ACTIVE「大床房」(测试服实测 12 条)。方案A(wx 定夺):保留 QUEEN、下线 BIG_BED,跨服务把存量 `BIG_BED` 数据订正为 `QUEEN`。
|
||||
|
||||
## 变更(数据/字典,前端无接口改动)
|
||||
|
||||
- `room_category` 字典:`BIG_BED` 条目置 **INACTIVE**(退役),下拉/翻译/白名单全链路不再出现;`大床房` 现只由 `QUEEN` 承载。
|
||||
- 存量数据订正 `BIG_BED → QUEEN`:resource `room_type.room_category`、order-v3 `house_hotel_assignment` / `house_requirement_assignment_snapshot` / `house_inquiry_message` 的 `room_category`、`order_settlement_hotel.room_type`。
|
||||
|
||||
## 对前端的影响
|
||||
|
||||
- **无需改动**。房型下拉少一条重复的「大床房」(原 BIG_BED),其余项不变;已存的 BIG_BED 记录现统一显示为 QUEEN 的「大床房」,`roomCategoryLabel` 口径不变。
|
||||
- 若前端本地硬编码过 `BIG_BED` 房型码(不应有),需改用 `QUEEN`。
|
||||
|
||||
## 验证证据(2026-08-10)
|
||||
|
||||
- `GET /admin/dict/all` room_category 11 条,BIG_BED 不在 ACTIVE,无同名碰撞,大床房→QUEEN。
|
||||
- 直连测试库零残留:sys_dict_data BIG_BED=INACTIVE;resource room_type / order-v3 四表 BIG_BED 计数全 0。
|
||||
- 部署后已清 Redis 字典缓存(db0:反向索引成员 + `cache:dict:data:room_category` + 索引)。
|
||||
@@ -0,0 +1,680 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5781"
|
||||
title: "单团核算表扩充逐项明细出参(incomeLines.details / costCategories.lines)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "3993f15a"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(3993f15a):单团核算表收入行 incomeLines[].details、成本分类 costCategories[].lines 逐项明细行内展开——detailColumns 显式剔除 details/lines/voucherUrls 父列(否则嵌套结构被 detailCellValue JSON.stringify 成多余列,后端已 deployed 老前端会自动渲出),新增 expand 列+renderChildTable 渲染逐项子表(仅当行确有非空逐项才显示展开入口,空数组不渲染);REPORT_FIELD_LABELS 补逐项字段中文表头(stayDate/hotelName/dailyPrice/totalPremium 等),REPORT_CODE_TO_NAME_FIELD 补 source/serviceType/bizType code→Name(后端回填 sourceName/serviceTypeName/bizTypeName,缺名回退 code)。负数金额不取绝对值、*Name 中文名直接展示均遵循 changelog 边界。ReportModal.spec 新增展开用例:父表不渲 JSON 列、展开后显示逐项子表。核单域 spec 全过,checkpoint 全绿。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【✨ 修改接口·管理后台】单团核算表扩充逐项明细出参(#5781)
|
||||
|
||||
> **PR**: #5786 | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-10
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
单团核算表(GET /v3/admin/order/{orderId}/settlement/reports/group)此前只返回「4 行收入合计 + 8 行成本合计」,财务/运营在核对某一行合计时看不到它是由哪些逐项明细加总出来的,只能跳回各分类明细页签逐条对账。
|
||||
|
||||
本次变更为**纯出参增量**:在每条收入行下挂 details(收入逐项明细)、在每个成本分类下挂 lines(成本逐项明细,分类专属结构),让前端在核算表内直接展开逐项,无需再跳页签拼装。
|
||||
|
||||
**不变的部分**:顶部汇总字段(baseOrderAmount / totalCost / grossProfit 等全部金额字段)、incomeLines 仍固定 4 行、costCategories 仍固定 8 行、各行 amount 合计口径,全部与变更前一致。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group | 修改(出参纯增量) | incomeLines[i] 新增 details 数组;costCategories[i] 新增 lines 数组(元素结构随 category 不同而不同,按 category 窄化) |
|
||||
|
||||
配套数据字典(前端可调 GET /admin/dict/data/{dictType} 动态渲染中文名):
|
||||
|
||||
| 字典 type | 用途 | 本次状态 |
|
||||
|-----------|------|----------|
|
||||
| settlement_category | 成本分类中文名 | 已存在(不变) |
|
||||
| settlement_payment_method | 付款方式中文名 | 已存在(不变) |
|
||||
| settlement_report_line_type | 收入行类型中文名 | 已存在(不变) |
|
||||
| insurance_biz_type | 保险业务类型中文名 | **本次新增** |
|
||||
| settlement_refund_source | 人工返还来源中文名 | **本次新增** |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询单团核算表
|
||||
|
||||
- **使用场景**:核单工作台「单团核算」页签,财务/运营查看单团收入成本毛利全貌及逐项明细
|
||||
- **认证**:管理后台 JWT;房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)无权调用(返 581045)
|
||||
- **幂等性**:是(GET 只读)
|
||||
- **限流**:无
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | Long | ✅ | 订单 ID,必须 > 0,否则返参数校验错误 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无请求体。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
响应类型:`Result<SettlementGroupReportRespVO>`(`code=200` 表示成功,`data` 为下表结构)。
|
||||
|
||||
### 5.1 顶层字段(SettlementGroupReportRespVO)
|
||||
|
||||
> ⚠️ 本表全部字段与变更前一致,**本次无增删改**,列出仅为自包含。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 报表 ID(Long 序列化为字符串,无落库记录时可能缺省) |
|
||||
| `orderId` | String | 订单 ID(Long 序列化为字符串) |
|
||||
| `reportStatus` | String | 报表状态:`GENERATED`=已生成(实时组装)/ `CONFIRMED`=已确认(终态快照回放) |
|
||||
| `baseOrderAmount` | Number | 订单应收金额,两位小数 |
|
||||
| `otherIncomeAmount` | Number | 其他收入合计 |
|
||||
| `discountAmount` | Number | 优惠合计(负数) |
|
||||
| `adjustedReceivableAmount` | Number | 调整后应收 |
|
||||
| `paidAmount` | Number | 已收金额 |
|
||||
| `actualRefundedAmount` | Number | 实际退款合计(负数) |
|
||||
| `netRevenueAmount` | Number | 净收入 |
|
||||
| `netReceivedAmount` | Number | 实收净额 |
|
||||
| `outstandingAmount` | Number | 未收尾款 |
|
||||
| `hotelCost` / `ticketCost` / `mealCost` / `vehicleCost` / `guideCost` / `photographerCost` / `otherExpenseCost` / `insurancePremium` | Number | 8 个成本分类合计(住宿/门票/餐食/车辆/导游/摄影/其他支出/保险) |
|
||||
| `totalCost` | Number | 成本总计 |
|
||||
| `paidCost` | Number | 已付成本合计 |
|
||||
| `unpaidCost` | Number | 未付成本合计 |
|
||||
| `grossProfit` | Number | 毛利 |
|
||||
| `grossProfitRate` | Number | 毛利率 |
|
||||
| `travelerCount` | Number | 出行人数 |
|
||||
| `perCapitaRevenue` / `perCapitaCost` / `perCapitaProfit` | Number | 人均收入 / 人均成本 / 人均毛利 |
|
||||
| `incomeLines` | Array | 收入行,**固定 4 行**,结构见 5.2 |
|
||||
| `costCategories` | Array | 成本分类行,**固定 8 行**,结构见 5.3 |
|
||||
| `generatedBy` / `generatedByName` / `generatedAt` | String / String / String | 生成人 ID / 姓名 / 生成时间 |
|
||||
| `confirmedBy` / `confirmedByName` / `confirmedAt` | String / String / String | 确认人 ID / 姓名 / 确认时间(未确认时缺省) |
|
||||
|
||||
### 5.2 收入行(SettlementGroupIncomeLineVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | String | 行类型:`BASE_ORDER`=订单应收 / `OTHER_INCOME`=其他收入 / `DISCOUNT`=优惠 / `ACTUAL_REFUND`=实际退款 |
|
||||
| `typeName` | String | 行类型中文名(字典 `settlement_report_line_type` 回填) |
|
||||
| `amount` | Number | 行合计金额,两位小数;`DISCOUNT` / `ACTUAL_REFUND` 为负数 |
|
||||
| `details` | Array | ✨ **本次新增**:收入逐项明细(`SettlementGroupIncomeDetailVO`),无逐项时为空数组 `[]` |
|
||||
|
||||
### 5.3 收入逐项明细(SettlementGroupIncomeDetailVO)✨ 本次新增
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `itemName` | String | 项目名(订单应收 / 增费项目名 / 优惠名称 / 退款来源中文名) |
|
||||
| `content` | String | 内容说明(如规格、退款原因) |
|
||||
| `source` | String | 人工返还来源 code,**仅** `ACTUAL_REFUND` 下的人工返还行透出:`DRIVER_ONSITE` / `COMPANY_COMPENSATION` |
|
||||
| `sourceName` | String | 人工返还来源中文名(字典 `settlement_refund_source`) |
|
||||
| `unitPrice` | Number | 单价,两位小数;无单价概念时缺省 |
|
||||
| `headCount` | Number | 人数;无人数概念时缺省 |
|
||||
| `quantity` | Number | 数量;无数量概念时缺省 |
|
||||
| `amount` | Number | 金额,两位小数;`DISCOUNT` / `ACTUAL_REFUND` 明细为负数 |
|
||||
| `paymentMethod` | String | 付款方式:`CASH_PAID` / `COMPANY_PAID` / `SIGNED`;无付款方式时缺省 |
|
||||
| `paymentMethodName` | String | 付款方式中文名(字典 `settlement_payment_method`) |
|
||||
| `sourceType` | String | 来源类型(9 值,见 §6.6);无来源时缺省 |
|
||||
| `sourceTypeName` | String | 来源中文名 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
**各 type 下 details 的内容口径**:
|
||||
|
||||
| type | details 内容 |
|
||||
|------|-------------|
|
||||
| `BASE_ORDER` | 订单应收一行汇总(订单侧无逐项时单行) |
|
||||
| `OTHER_INCOME` | 其他收入逐项 |
|
||||
| `DISCOUNT` | 优惠逐项(金额取负) |
|
||||
| `ACTUAL_REFUND` | 实际退款逐项:线上退款 + 人工返还(`source` = DRIVER_ONSITE / COMPANY_COMPENSATION),金额取负 |
|
||||
|
||||
### 5.4 成本分类行(SettlementGroupCostCategoryVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `category` | String | 费用类别:`HOTEL` / `TICKET` / `MEAL` / `VEHICLE` / `GUIDE` / `PHOTOGRAPHER` / `OTHER_EXPENSE` / `INSURANCE` |
|
||||
| `categoryName` | String | 费用类别中文名(字典 `settlement_category`) |
|
||||
| `amount` | Number | 分类合计金额,两位小数 |
|
||||
| `lines` | Array | ✨ **本次新增**:成本逐项明细,**元素结构随 `category` 不同而不同**(分类专属行 VO),无逐项时为空数组 `[]`;前端按 `category` 判别窄化到 5.5~5.11 的对应结构 |
|
||||
|
||||
### 5.5 成本逐项行 · HOTEL 住宿(SettlementGroupHotelLineVO)✨
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `stayDate` | String | 入住日期,`yyyy-MM-dd` |
|
||||
| `hotelName` | String | 酒店名称 |
|
||||
| `roomTypeName` | String | 房型名称 |
|
||||
| `roomCount` | Number | 房间数 |
|
||||
| `unitPrice` | Number | 单价,两位小数 |
|
||||
| `plannedCost` | Number | 计划成本,两位小数 |
|
||||
| `amount` | Number | 核算金额(实际成本),两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.6 成本逐项行 · TICKET 门票(SettlementGroupTicketLineVO)✨
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `dayDate` | String | 游玩日期,`yyyy-MM-dd` |
|
||||
| `scenicName` | String | 景区/项目名称 |
|
||||
| `specName` | String | 规格名称(如 成人票) |
|
||||
| `ticketCount` | Number | 票数 |
|
||||
| `ticketUnitPrice` | Number | 门票单价,两位小数 |
|
||||
| `sellPrice` | Number | 销售价,两位小数 |
|
||||
| `plannedCost` | Number | 计划成本,两位小数 |
|
||||
| `amount` | Number | 核算金额(实际成本),两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.7 成本逐项行 · MEAL 餐食(SettlementGroupMealLineVO)✨
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `mealDate` | String | 用餐日期,`yyyy-MM-dd` |
|
||||
| `mealName` | String | 餐食名称 |
|
||||
| `mealType` | String | 餐食类型:`BREAKFAST` / `LUNCH` / `DINNER` / `SELF` |
|
||||
| `mealTypeName` | String | 餐食类型中文名(字典 `meal_type`) |
|
||||
| `quantity` | Number | 份数 |
|
||||
| `unitPrice` | Number | 单价,两位小数 |
|
||||
| `amount` | Number | 核算金额(实际金额),两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名;餐食明细无来源时缺省 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.8 成本逐项行 · VEHICLE 用车(SettlementGroupVehicleLineVO)✨
|
||||
|
||||
> 车务逐日行(VEHICLE_FEE 族)与车辆费用行(EXPENSE 族:油费/过路费等)的**稀疏并集**:每行仅本族字段非空,另一族字段整体缺省。无单价/数量概念,`dailyPrice` 即核算单价列。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `serviceDate` | String | [VEHICLE_FEE] 服务日期,`yyyy-MM-dd` |
|
||||
| `startDate` | String | [VEHICLE_FEE] 服务开始日期,`yyyy-MM-dd` |
|
||||
| `endDate` | String | [VEHICLE_FEE] 服务结束日期,`yyyy-MM-dd` |
|
||||
| `vehiclePlate` | String | [VEHICLE_FEE] 车牌号 |
|
||||
| `vehicleModelName` | String | [VEHICLE_FEE] 车型名称 |
|
||||
| `driverName` | String | [VEHICLE_FEE] 司机姓名 |
|
||||
| `dailyPrice` | Number | [VEHICLE_FEE] 日单价(即核算单价列),两位小数 |
|
||||
| `paymentTypeName` | String | [VEHICLE_FEE] 车务付款类型名称 |
|
||||
| `amount` | Number | 核算金额,两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名 |
|
||||
| `expenseType` | String | [EXPENSE] 车辆费用类型:`FUEL` / `TOLL` / `PARKING` / `RENTAL` / `MAINTENANCE` |
|
||||
| `expenseTypeName` | String | [EXPENSE] 车辆费用类型中文名(字典 `expense_type`) |
|
||||
| `projectName` | String | [EXPENSE] 项目名称 |
|
||||
| `expenseDate` | String | [EXPENSE] 费用发生日期,`yyyy-MM-dd` |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.9 成本逐项行 · GUIDE 导游 / PHOTOGRAPHER 摄影(SettlementGroupStaffLineVO,两分类共用)✨
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `serviceDate` | String | 服务日期,`yyyy-MM-dd`;无服务日时缺省 |
|
||||
| `name` | String | 人员姓名 |
|
||||
| `serviceType` | String | 服务类型:`GUIDE` / `PHOTOGRAPHER` |
|
||||
| `serviceTypeName` | String | 服务类型中文名(字典 `staff_role`) |
|
||||
| `amount` | Number | 核算金额,两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名;无来源时缺省 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.10 成本逐项行 · OTHER_EXPENSE 其他支出(SettlementGroupOtherExpenseLineVO)✨
|
||||
|
||||
> 其他费用行(EXPENSE 族)与补贴行(SUBSIDY 族)的**稀疏并集**:每行仅本族字段非空。无单价/数量概念。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `expenseDate` | String | 费用发生日期,`yyyy-MM-dd` |
|
||||
| `projectName` | String | 项目名称 |
|
||||
| `expenseType` | String | [EXPENSE] 其他费用类型:固定 `OTHER` |
|
||||
| `expenseTypeName` | String | [EXPENSE] 其他费用类型中文名(字典 `expense_type`) |
|
||||
| `subsidyType` | String | [SUBSIDY] 补贴类型:`PHONE` / `OVERTIME` |
|
||||
| `subsidyTypeName` | String | [SUBSIDY] 补贴类型中文名(字典 `subsidy_type`) |
|
||||
| `amount` | Number | 核算金额(实际金额),两位小数 |
|
||||
| `paymentMethod` / `paymentMethodName` | String | 付款方式 code / 中文名 |
|
||||
| `sourceType` / `sourceTypeName` | String | 来源类型 code / 中文名;无来源时缺省 |
|
||||
| `remark` | String | 备注 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||
|
||||
### 5.11 成本逐项行 · INSURANCE 保险(SettlementGroupInsuranceLineVO)✨
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `productName` | String | 保险产品名称 |
|
||||
| `bizType` | String | 业务类型:`ORDER` / `DRIVER` |
|
||||
| `bizTypeName` | String | 业务类型中文名(字典 `insurance_biz_type`,本次新增) |
|
||||
| `totalPremium` | Number | 保费(即核算金额列),两位小数 |
|
||||
| `extPolicyNo` | String | 外部保单号 |
|
||||
| `voucherUrls` | Array<String> | 凭证 URL 数组(电子保单 PDF) |
|
||||
|
||||
> 保险行无 `plannedCost` / `sourceType` 字段,不输出。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `incomeLines[].type`(字典 `settlement_report_line_type`)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `BASE_ORDER` | 订单应收 | 订单应收行 |
|
||||
| `OTHER_INCOME` | 其他收入 | 其他收入行 |
|
||||
| `DISCOUNT` | 优惠 | 优惠行(金额为负) |
|
||||
| `ACTUAL_REFUND` | 实际退款 | 实际退款行(金额为负) |
|
||||
|
||||
### 6.2 `costCategories[].category`(字典 `settlement_category`)
|
||||
|
||||
| 值 | 中文 | 对应 lines 行结构 |
|
||||
|----|------|-------------------|
|
||||
| `HOTEL` | 住宿 | §5.5 |
|
||||
| `TICKET` | 门票/游玩项目 | §5.6 |
|
||||
| `MEAL` | 餐食 | §5.7 |
|
||||
| `VEHICLE` | 车辆 | §5.8 |
|
||||
| `GUIDE` | 导游 | §5.9 |
|
||||
| `PHOTOGRAPHER` | 摄影 | §5.9(与 GUIDE 共用) |
|
||||
| `OTHER_EXPENSE` | 其他支出 | §5.10 |
|
||||
| `INSURANCE` | 保险 | §5.11 |
|
||||
|
||||
### 6.3 `paymentMethod`(字典 `settlement_payment_method`)
|
||||
|
||||
出现于:收入逐项明细、HOTEL / TICKET / MEAL / VEHICLE / 人员 / 其他支出各成本逐项行。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `CASH_PAID` | 现金已付 |
|
||||
| `COMPANY_PAID` | 公司支付 |
|
||||
| `SIGNED` | 签单 |
|
||||
|
||||
### 6.4 `details[].source`(字典 `settlement_refund_source`,✨ 本次新增字典)
|
||||
|
||||
仅 `ACTUAL_REFUND` 下人工返还行透出。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `DRIVER_ONSITE` | 司机现场返还 |
|
||||
| `COMPANY_COMPENSATION` | 公司赔付 |
|
||||
|
||||
### 6.5 `lines[].bizType`(字典 `insurance_biz_type`,✨ 本次新增字典)
|
||||
|
||||
仅 INSURANCE 保险行。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `ORDER` | 订单险 |
|
||||
| `DRIVER` | 司机险 |
|
||||
|
||||
### 6.6 `sourceType`(后端枚举 SettlementDetailSourceType)
|
||||
|
||||
出现于:收入逐项明细、HOTEL / TICKET / MEAL / VEHICLE / 人员 / 其他支出各成本逐项行。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `MANUAL` | 手工 |
|
||||
| `HOUSE_ASSIGNMENT` | 配房结果 |
|
||||
| `SCENIC_ASSIGNMENT` | 景区 |
|
||||
| `ACTIVITY_ASSIGNMENT` | 游玩项目 |
|
||||
| `MEAL_ASSIGNMENT` | 餐饮安排 |
|
||||
| `FLEET` | 车务 |
|
||||
| `STAFF_ASSIGNMENT` | 人员安排 |
|
||||
| `ORDER_SURCHARGE` | 订单增费 |
|
||||
| `SYSTEM` | 系统 |
|
||||
|
||||
### 6.7 `lines[].mealType`(字典 `meal_type`)
|
||||
|
||||
仅 MEAL 餐食行。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `BREAKFAST` | 早餐 |
|
||||
| `LUNCH` | 午餐 |
|
||||
| `DINNER` | 晚餐 |
|
||||
| `SELF` | 自理 |
|
||||
|
||||
### 6.8 `lines[].serviceType`(字典 `staff_role`)
|
||||
|
||||
仅 GUIDE / PHOTOGRAPHER 人员行。本接口只会出现 `GUIDE` / `PHOTOGRAPHER` 两个值(字典另有 助理导游/领队/司机/其他,不在本接口出现)。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `GUIDE` | 导游 |
|
||||
| `PHOTOGRAPHER` | 摄影师 |
|
||||
|
||||
### 6.9 `lines[].expenseType`(字典 `expense_type`)
|
||||
|
||||
VEHICLE 行(EXPENSE 族):`FUEL`=油费 / `TOLL`=过路费 / `PARKING`=停车费 / `RENTAL`=租车费 / `MAINTENANCE`=维修保养。
|
||||
OTHER_EXPENSE 行(EXPENSE 族):固定 `OTHER`=其他。
|
||||
|
||||
### 6.10 `lines[].subsidyType`(字典 `subsidy_type`)
|
||||
|
||||
仅 OTHER_EXPENSE 行(SUBSIDY 族)。
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `PHONE` | 话补 |
|
||||
| `OVERTIME` | 加班补贴 |
|
||||
|
||||
### 6.11 `reportStatus`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `GENERATED` | 已生成 | 未核单订单,实时组装 |
|
||||
| `CONFIRMED` | 已确认 | 已核单(SETTLED)订单,终态快照回放 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 400 | 参数校验失败 | `orderId` 缺失或 < 1 |
|
||||
| 581007 | 订单不存在 | `orderId` 对应订单不存在或已删除 |
|
||||
| 581045 | 房务角色无权查看订单详情,房务仅可配房 | 房务管理员 / 房务组长角色调用 |
|
||||
|
||||
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||
|
||||
### 8.1 典型成功(已核单订单,逐项明细完整填充)
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566778/settlement/reports/group
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**(节选,仅展示本次新增结构所在的 incomeLines / costCategories,顶层汇总字段与变更前一致故省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "1957000000000000001",
|
||||
"orderId": "1956112233445566778",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "BASE_ORDER",
|
||||
"typeName": "订单应收",
|
||||
"amount": 12800.00,
|
||||
"details": [
|
||||
{
|
||||
"itemName": "订单应收",
|
||||
"content": "小红书(孙雷) 王彧琪",
|
||||
"unitPrice": 1280.00,
|
||||
"headCount": 10,
|
||||
"amount": 12800.00
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"amount": 200.00,
|
||||
"details": [
|
||||
{
|
||||
"itemName": "现场加收骑马费",
|
||||
"unitPrice": 100.00,
|
||||
"quantity": 2,
|
||||
"amount": 200.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "ORDER_SURCHARGE",
|
||||
"sourceTypeName": "订单增费",
|
||||
"remark": "现场加收"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "DISCOUNT",
|
||||
"typeName": "优惠",
|
||||
"amount": -500.00,
|
||||
"details": [
|
||||
{
|
||||
"itemName": "早鸟优惠",
|
||||
"amount": -500.00
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ACTUAL_REFUND",
|
||||
"typeName": "实际退款",
|
||||
"amount": -300.00,
|
||||
"details": [
|
||||
{
|
||||
"itemName": "司机现场返还",
|
||||
"content": "少住一晚退房差",
|
||||
"source": "DRIVER_ONSITE",
|
||||
"sourceName": "司机现场返还",
|
||||
"amount": -300.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"remark": "现场返还现金"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"costCategories": [
|
||||
{
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"amount": 3600.00,
|
||||
"lines": [
|
||||
{
|
||||
"stayDate": "2026-07-30",
|
||||
"hotelName": "草原明珠大酒店",
|
||||
"roomTypeName": "标间",
|
||||
"roomCount": 3,
|
||||
"unitPrice": 400.00,
|
||||
"plannedCost": 1200.00,
|
||||
"amount": 1200.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果",
|
||||
"remark": "含早",
|
||||
"voucherUrls": ["https://oss/v1.jpg"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "VEHICLE",
|
||||
"categoryName": "车辆",
|
||||
"amount": 1760.00,
|
||||
"lines": [
|
||||
{
|
||||
"serviceDate": "2026-07-30",
|
||||
"startDate": "2026-07-30",
|
||||
"endDate": "2026-07-31",
|
||||
"vehiclePlate": "蒙A-5376",
|
||||
"vehicleModelName": "坦克500",
|
||||
"driverName": "司机甲",
|
||||
"dailyPrice": 880.00,
|
||||
"amount": 880.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务"
|
||||
},
|
||||
{
|
||||
"expenseType": "FUEL",
|
||||
"expenseTypeName": "油费",
|
||||
"projectName": "全程油费",
|
||||
"expenseDate": "2026-07-31",
|
||||
"amount": 880.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司支付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "GUIDE",
|
||||
"categoryName": "导游",
|
||||
"amount": 1600.00,
|
||||
"lines": [
|
||||
{
|
||||
"serviceDate": "2026-07-30",
|
||||
"name": "导游乙",
|
||||
"serviceType": "GUIDE",
|
||||
"serviceTypeName": "导游",
|
||||
"amount": 1600.00,
|
||||
"paymentMethod": "SIGNED",
|
||||
"paymentMethodName": "签单",
|
||||
"sourceType": "STAFF_ASSIGNMENT",
|
||||
"sourceTypeName": "人员安排"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "INSURANCE",
|
||||
"categoryName": "保险",
|
||||
"amount": 150.00,
|
||||
"lines": [
|
||||
{
|
||||
"productName": "保游畅享境内游保险",
|
||||
"bizType": "ORDER",
|
||||
"bizTypeName": "订单险",
|
||||
"totalPremium": 150.00,
|
||||
"extPolicyNo": "PY20260730001",
|
||||
"voucherUrls": ["https://oss/policy1.pdf"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(无任何逐项明细 / 金额为 0)
|
||||
|
||||
**场景说明**:订单无任何其他收入、优惠、退款,且各成本分类未录入任何逐项 —— `details` / `lines` 返回**空数组** `[]`(不是 null),对应分类 `amount` 可为 `0.00`。所有 null 的可选字段(unitPrice / headCount / paymentMethod / remark / voucherUrls 等)**整体缺省不出现在 JSON 中**。
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566889/settlement/reports/group
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**(节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": "1956112233445566889",
|
||||
"reportStatus": "GENERATED",
|
||||
"incomeLines": [
|
||||
{ "type": "BASE_ORDER", "typeName": "订单应收", "amount": 12800.00, "details": [
|
||||
{ "itemName": "订单应收", "unitPrice": 1280.00, "headCount": 10, "amount": 12800.00 }
|
||||
] },
|
||||
{ "type": "OTHER_INCOME", "typeName": "其他收入", "amount": 0.00, "details": [] },
|
||||
{ "type": "DISCOUNT", "typeName": "优惠", "amount": 0.00, "details": [] },
|
||||
{ "type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": 0.00, "details": [] }
|
||||
],
|
||||
"costCategories": [
|
||||
{ "category": "HOTEL", "categoryName": "住宿", "amount": 0.00, "lines": [] },
|
||||
{ "category": "TICKET", "categoryName": "门票/游玩项目", "amount": 0.00, "lines": [] },
|
||||
{ "category": "MEAL", "categoryName": "餐食", "amount": 0.00, "lines": [] },
|
||||
{ "category": "VEHICLE", "categoryName": "车辆", "amount": 0.00, "lines": [] },
|
||||
{ "category": "GUIDE", "categoryName": "导游", "amount": 0.00, "lines": [] },
|
||||
{ "category": "PHOTOGRAPHER", "categoryName": "摄影", "amount": 0.00, "lines": [] },
|
||||
{ "category": "OTHER_EXPENSE", "categoryName": "其他支出", "amount": 0.00, "lines": [] },
|
||||
{ "category": "INSURANCE", "categoryName": "保险", "amount": 0.00, "lines": [] }
|
||||
]
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(订单不存在 / 房务角色越权)
|
||||
|
||||
**场景说明 A**:`orderId` 不存在 → 581007。
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/999999999/settlement/reports/group
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{ "code": 581007, "msg": "订单不存在", "data": null }
|
||||
```
|
||||
|
||||
**场景说明 B**:房务管理员角色调用 → 581045。
|
||||
|
||||
```json
|
||||
{ "code": 581045, "msg": "房务角色无权查看订单详情,房务仅可配房", "data": null }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用场景**:订单存在即可调;未核单订单(`reportStatus=GENERATED`)走实时组装,已核单订单(`reportStatus=CONFIRMED`)走终态快照回放,**两条路径出参结构完全一致**,前端无需区分
|
||||
- ❌ **不适用场景**:房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)调用 → 581045
|
||||
- ⚠️ **特殊边界**:
|
||||
- 逐项明细**不出已付/未付逐行列**,逐行只有单价/数量/核算金额(`paidCost` / `unpaidCost` 仍是顶层分类维度的合计口径,不在逐项层)
|
||||
- VEHICLE / OTHER_EXPENSE 两分类的行是同数组内两族结构稀疏并集(见 §5.8 / §5.10),前端渲染列时按「本族字段是否出现」判别
|
||||
- `DISCOUNT` / `ACTUAL_REFUND` 行及其明细金额均为**负数**,前端不要自行取绝对值
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `incomeLines[].details` | 无此字段 | ✨ 新增 `Array<SettlementGroupIncomeDetailVO>`,无逐项时为 `[]` |
|
||||
| `costCategories[].lines` | 无此字段 | ✨ 新增 `Array`(分类专属行结构,按 `category` 窄化),无逐项时为 `[]` |
|
||||
| 顶层汇总字段 / 4 行收入合计 / 8 行成本合计 | 现状 | **不变** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 核算表核对逐项明细 | 只能看到行合计,需跳各分类明细页签逐条对账 | 行内直接展开 `details` / `lines` 逐项 |
|
||||
| 数据字典 | 无 `insurance_biz_type` / `settlement_refund_source` | ✨ 新增两个字典(保险业务类型 / 人工返还来源) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。纯出参增量,原有字段名 / 类型 / 结构 / 合计口径零变化;老前端不读 `details` / `lines` 不受影响
|
||||
- **前端是否必须同步上线**:否。前端按自身排期接入逐项展开即可
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #5786
|
||||
- **回滚后清理**:无(无 DDL、无缓存、无脏数据;两个字典 `insurance_biz_type` / `settlement_refund_source` 保留无害)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **null 字段整体缺省**:所有行 VO 标注了 null 不序列化,可选字段(unitPrice / headCount / quantity / paymentMethod / sourceType / remark / voucherUrls 等)为 null 时**该 key 不出现在 JSON 中**,前端按可选字段处理,不要断言 key 必存在
|
||||
- `details` / `lines` 无逐项时是**空数组 `[]`** 而不是字段缺省,可直接 `.length` 判空
|
||||
- **行结构窄化**:`costCategories[].lines` 元素是多态结构,前端必须先按 `category` 判别再取分类专属字段(GUIDE / PHOTOGRAPHER 共用人员行结构)
|
||||
- **金额符号**:`DISCOUNT` / `ACTUAL_REFUND` 的行金额与明细金额均为负数;成本各行为正数
|
||||
- **中文名渲染**:`*Name` 字段后端已回填中文名(字典缺值时回退硬编码),可直接展示;如需动态字典渲染,调 `GET /admin/dict/data/{dictType}`,dictType 见 §6 各子节
|
||||
- 无历史 workaround 需要清理(本能力此前不存在)
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5781](https://git.1814.love:8443/wx/HL/issues/5781)
|
||||
- **PR**: [#5786](https://git.1814.love:8443/wx/HL/pulls/5786)
|
||||
- **Merge commit**: [fdc429bf](https://git.1814.love:8443/wx/HL/commit/fdc429bf8546ce2cbb8c69f4166b153cccb4839c)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5782"
|
||||
title: "派车确认后订单侧「车控处理中」不再卡 10 分钟;配车完成时间不再早 8 小时"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "接口契约零变化,前端无需改动。两点行为变化前端可感知:①车控状态回配不再需要等 10 分钟(端到端实测 36 秒,fence 占用由 10 分钟降至 352 毫秒);②assignmentCompletedAt 等时间字段口径修正(此前早 8 小时)。测试服 12:26 已用真实 API 走完整确认链路验证通过。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T12:40:00+08:00"
|
||||
---
|
||||
|
||||
# 车务/订单: 派车确认后订单侧「车控处理中」不再卡 10 分钟;配车完成时间不再早 8 小时
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187) + hl-order-service-v3 (8086/8186)
|
||||
> **PR**: #5783
|
||||
> **Issue**: #5782
|
||||
> **日期**: 2026-08-10
|
||||
> **影响范围**: 管理后台订单详情「用车安排」状态回配时效;配车完成/提交/调价时间字段展示口径
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
车务派单看板已显示「已派车」,但定制师侧订单详情「用车安排」仍显示 `车控处理中`、流程条停在 `配车·处理中`,**约 10 分钟后**才变「已回配」。每次派车/改派确认必现。
|
||||
|
||||
#5744 曾把 order 侧的拒绝改成「可重试」做兜底,只让状态最终能自愈,没消除 fence 被长期占用的来源,所以现象从「永久卡住」变成「必卡 10 分钟」。
|
||||
|
||||
## 二、根因(后端,接口契约无关)
|
||||
|
||||
fleet 释放 order 侧「用车需求最终确认 fence」的调用误用了 `Result.getCheckedData()` 解包 `Result<Void>`——该方法在 `data == null` 时抛 `100901 远程服务返回数据为空`,而释放端点固定返回 `Result.success()`(data 恒 null)。于是 **order 侧其实已释放成功的调用,被 fleet 100% 判成失败**:释放事件永远重试并阻塞同顺序键后继,fence 只能等 10 分钟租约过期;期间配车快照回调被 `582092` 挡回重试,订单侧 `vehicle_control_status` 一直是 PROCESSING。
|
||||
|
||||
## 三、变更
|
||||
|
||||
| 项 | 变化 |
|
||||
|---|---|
|
||||
| fence 释放调用 | 改按 `isSuccess()` 判定(全仓 71 个 `Result<Void>` 端点已扫,无第二处同类误用) |
|
||||
| 释放补偿告警 | 重试预算耗尽时日志升级 ERROR(保留「永不隔离」设计,隔离等于 fence 永不释放) |
|
||||
| 时间字段落库 | fleet/order-v3 共 4 处 UTC `OffsetDateTime` 直接 `toLocalDateTime()` 丢偏移,改为按本地时区换算 |
|
||||
|
||||
**接口路径、请求/响应结构、错误码均无变化。**
|
||||
|
||||
## 四、对前端的影响
|
||||
|
||||
- **无需改动**。
|
||||
- 行为变化 1:派车确认后订单详情「用车安排」由「车控处理中」转「已回配」不再需要等 ~10 分钟(现由分钟级 outbox 调度驱动)。此前若前端做过「10 分钟内不刷新/长轮询」之类的迁就,可以去掉。
|
||||
- 行为变化 2:以下时间字段此前**比北京时间早 8 小时**,现已修正为本地时间口径 —— 订单详情用车安排的配车完成时间 `assignmentCompletedAt`、逐日配车行的 `submittedAt`、车费调整时间 `vehicleFeeAdjustedAt`。
|
||||
- **注意**:本次未做存量数据订正,修复上线前落库的历史行仍是早 8 小时的旧值,新数据起口径正确。如需订正存量另行提。
|
||||
|
||||
## 五、验证证据(2026-08-10 测试服)
|
||||
|
||||
端到端实测(12:26,订单 26-9313,2 车辆槽位 × 3 天,真实 API 走完「登记司机确认 → 需求级原子最终确认」):
|
||||
|
||||
| 指标 | 修复前 | 修复后实测 |
|
||||
|---|---|---|
|
||||
| fence 占用 | 10m01s ~ 11m13s(贴 10 分钟租约) | **352 毫秒** |
|
||||
| 释放事件投递 | retry_count=20 永不成功 | **retry_count=0,一次成功** |
|
||||
| 配车快照回调投递 | retry_count=7 才成功 | **retry_count=0,一次成功** |
|
||||
| 确认 → 订单侧 `vehicle_control_status=DONE` | ~10 分钟 | **36 秒**(12:26:24 → 12:27:00) |
|
||||
| `assignmentCompletedAt` | 记成 04:26(早 8 小时) | **12:26:24**(北京时间正确) |
|
||||
|
||||
- API 复验 `GET /v3/admin/order/{id}/itinerary`:`vehicleGroup.requirement.status=DONE`,`assignments` 6 条齐全。
|
||||
- 部署完成 12:17:28 → **12:18:00 起积压释放事件恢复成功投递**(该事件类型自 08-04 起 48 条全 PENDING、0 条 SUCCESS)。
|
||||
- 单测:新增 8 例(void 成功不判失败 / 业务失败 strict 抛出 / 非 strict 降级 / 传输 null / UTC→本地换算保持时刻不变);fleet 定向 + 架构门禁 96 绿,order-v3 全量 6291 绿。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5782](https://git.1814.love:8443/wx/HL/issues/5782)
|
||||
- **PR**: [#5783](https://git.1814.love:8443/wx/HL/pulls/5783)
|
||||
- **Merge commit**: [6a52a4ca8](https://git.1814.love:8443/wx/HL/commit/6a52a4ca8)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5784"
|
||||
title: "用车需求换版不再自动清除已派车:保留待人工核对 + 看板详情新增徽章字段"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "188e0d64"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "前端已实现:新建 utils/requirementSuperseded.js 归一化保留派单明细(ID 字符串透传,count 以后端字段为权威、缺省回退明细长度)+ RequirementSupersededCard.vue 子组件(count>0 挂「需求已改版,请人工核对」徽章并渲染核对列表 车/司机/日期/状态,日期逐日 serviceDate 优先回退 startDate~endDate,状态复用 resolveAssignmentStatusLabel/ORDER_STATUS_META);OrderDrawer 详情抽屉与 AssignModal 派单弹窗各引入一次。保留行不进 activeAssignments/dailyVehiclePlan、不产生看板卡片,徽章只在详情/派单弹窗级。util 5/5、组件 4/4、看板关联 287/287、checkpoint 全过。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务/订单: 用车需求换版不再自动清除已派车,保留待人工核对(#5784)
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187) + hl-order-service-v3 (8086/8186)
|
||||
> **PR**: #5785(主)+ #5787/#5789(连带)
|
||||
> **Issue**: #5784
|
||||
> **日期**: 2026-08-10
|
||||
> **影响范围**: 派单看板订单详情(新增 2 字段);换版/改期/改天数后的派车与订单配车摘要行为
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(行为纠偏)
|
||||
|
||||
此前:改用车需求(车型/座位)、改出发日期、改行程天数会**自动取消**未沿用的已派车(含已确认 assigned)并软删订单侧配车摘要。
|
||||
现在:**只自动取消未派车的 unassigned 占位;holding/assigned 一律保留**(状态、日期、资源占用都不动),盖「已被新版需求取代」标记,由车管人工核对后改派或取消。订单侧旧配车摘要也不再软删。增加出行人数行为不变(全部沿用)。
|
||||
|
||||
依据:SRS FLEET §9.18 #2 拍板(派单确认后是独立资产)+ 房务同款口径(既有配房不自动清理)。
|
||||
|
||||
## 接口变化(管理后台)
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}`(看板订单详情)**新增 2 个响应字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `requirementChangePendingCount` | int | 需求已改版待人工核对的保留派单数;**>0 时前端应挂「需求已改版,请人工核对」徽章** |
|
||||
| `supersededAssignments` | array | 保留派单明细:`assignmentId`/`assignmentGroupId`/`assignmentSlotId`/`requirementId`(旧版)/`supersededByRequirementId`(新版)/`requirementSupersededAt`/`assignmentStatus`(holding\|assigned)/`vehiclePlate`/`vehicleModel`/`driverName`/`startDate`/`endDate`/`serviceDate`;ID 均字符串序列化 |
|
||||
|
||||
其余接口路径/入参无变化。`PUT /v3/admin/order/{id}/vehicle-requirement` 响应中 `assignmentDeletedCount` 字段保留但**恒为 0**(不再软删)。
|
||||
|
||||
## 对前端的要求(mmg)
|
||||
|
||||
1. 派单看板**详情/派单弹窗**:`requirementChangePendingCount > 0` 时挂「需求已改版,请人工核对」徽章,用 `supersededAssignments` 渲染人工核对列表(含车/司机/日期/状态)。
|
||||
2. 处置动作复用现有能力:改派(一键释放重派 #5592 已有)或取消(普通取消派单);被取消/改派后字段自动归零。
|
||||
3. 注意:保留行**不出现**在 `activeAssignments`/`dailyVehiclePlan`(那是当前需求的方案);也不产生看板列表卡片,徽章只在详情级。
|
||||
4. 订单详情(定制师侧)无字段变化;换版后旧配车在新方案回配前的展示口径不变。
|
||||
|
||||
## 联动语义(供前端理解,无需实现)
|
||||
|
||||
- 保留行继续占用车辆/司机(同日期同资源不可重复派),人工取消后释放;改期后新日期不受旧占用影响。
|
||||
- 换版自动化只作用于 unassigned:减槽取消未派占位、增槽补新 unassigned。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 后端 PR #5785(主)+ #5787/#5789(连带)已合并 dev-v3 并部署测试服(backend_status=deployed 于 2026-08-10 回填,测试服 hl-order-service-v3 运行 2026-08-10 构建版本)。
|
||||
- 详情接口新增 2 字段随该版本在测试服生效;前端实现自测 util 5/5、组件 4/4、看板关联 287/287、checkpoint 全过(见 status_note)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5784](https://git.1814.love:8443/wx/HL/issues/5784)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5788"
|
||||
title: "换版保留派单可改派:候选排除放行 superseded 行 + vehicleSlots 新增标旧字段"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "【已被 #5810 取代,本条前端契约作废,勿再施工;前端历史实现引用 b31ac7c3 已随本条作废】新契约见 `changelogs-v2/2026-08/11_5810_换版槽位人工制-需求语句与派车日期门禁-修改接口-管理后台.md`:换版改为整槽平移,`superseded`/`supersededAssignments`/`requirementChangePendingCount` 三字段语义已死(恒 false/[]/0),本条要求的「标旧渲染」全部删除,改为槽位统一原样渲染 + 需求语句 requirementFleetText + 605062 日期门禁。以下为历史记录:【遵 17:54 暂停令回退,等 #5810 新契约】064d0be2 按 17:31 二次打回的 2.8 规格把旧数据重做为完整槽位卡(SupersededSlotCard+改派/取消链,AssignModal 59/59、picker 47/47、fleet/board 444/444、checkpoint 含生产构建全过),但交付先于 17:54「暂停 2.8 施工、保持现状」说明 push。遵暂停令已 revert(3441e69e),v2.1 回到 b31ac7c3 口径(superseded 撞号过滤+标旧窄条+返工4项:vehicleSlots 为源合并/exclude 同源/放开统一选择/放开删除 superseded 例外)。#5810 换版策略升级(槽位人工制+需求语句 requirementFleetText+出发日期门禁)后端落地、本 changelog 出全新前端契约后,再按新形态实现(预告:槽位头去「建议{车型}」标签、改版横幅显示需求语句、槽位统一原样渲染)。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务: 需求换版保留派单可改派 + 槽位视图标旧(#5788)
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187)
|
||||
> **PR**: #5796
|
||||
> **Issue**: #5788
|
||||
> **日期**: 2026-08-10
|
||||
> **影响范围**: 派单看板订单详情 vehicleSlots 结构(新增 3 字段)、派单候选查询排除校验语义
|
||||
> **背景**: #5784 换版保留行此前点「重新配置车辆」必报「参数非法: 排除派单不属于当前订单、当前用车需求或当前车型项」(26-9313 实测),改派通路被拦死;且槽位视图无法区分新旧行。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 1. 详情接口 vehicleSlots 新增 3 字段
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` 的 `vehicleSlots[]` 每行新增:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `requirementId` | string(Long) | 槽位行所属用车需求ID;与顶层 requirementId 不等且 superseded=true 时为换版保留的旧需求行 |
|
||||
| `superseded` | boolean | **是否换版保留的旧派单行**。true=旧数据,仅供人工核对后改派/取消,不参与当前方案日格进度与车费计数 |
|
||||
| `supersededByRequirementId` | string(Long) | 取代该行的新需求ID;superseded=true 时非空 |
|
||||
|
||||
**排序约定**: 当前需求槽位行在前(顺序不变),换版保留行追加在尾部。保留行固定 `canDelete=false`,`deleteBlockReason="需求已改版保留行,请改派或取消,不支持直接删除槽位"`。
|
||||
|
||||
**前端要做**: 保留行渲染「旧数据/需求已改版」标识(区别于当前槽位),操作只留「改派 / 取消」;顶部黄条(supersededAssignments)保持现状作提醒入口。注意保留行的 `fleetItemIndex` 是旧版展开序,可能与当前需求槽位撞号,**渲染分组请以 superseded 标志区分,勿再单靠 fleetItemIndex**。
|
||||
|
||||
### 2. 候选查询排除校验放宽(保留行改派入口打通)
|
||||
|
||||
`POST /admin/fleet/assignments/candidates`:
|
||||
|
||||
- `excludeAssignmentId` 现在允许两类:①当前需求本身的派单(原口径,仍须 requirementId+fleetItemIndex 精确匹配);②**换版保留派单**(supersededByRequirementId 非空,属当前订单即可)。
|
||||
- **排除保留行时 `fleetItemIndex` 可不传**(旧版序号在新需求下无语义,后端不再校验);座位匹配自动锚保留行自身座位快照(requiredSeats),不再按新需求车型项定位。
|
||||
- 保留行自身占用会被正确排除:实测 exclude 后原车 available=true、conflicts=[](可改回原车或换车)。
|
||||
- 防护不回归:非本订单/不存在的 assignmentId、普通行 fleetItemIndex 不匹配 → 仍报「参数非法」。
|
||||
|
||||
### 2.5 跟进(PR #5798,2026-08-10):排除未派车占位行也不校验序号
|
||||
|
||||
现版前端槽位上下文缺 `fleetItemIndex` 时回退 0(`useAssignFlow.js:294`),排除 SUV 占位行(实际序号 1)时被后端精确匹配拒绝(26-9313 复现)。后端已放宽:**排除当前需求的 unassigned 占位行时不校验 fleetItemIndex**(需求归属仍校验),座位按占位行自身 requiredSeats 快照。现版前端不改也能选车,但仍建议槽位上下文与 `vehicleSlots[].fleetItemIndex`/`assignmentId` 同源取值,消除序号错位。
|
||||
|
||||
### 2.6 展示布局建议(wx 2026-08-10 口径)
|
||||
|
||||
场景:原需求商务车×2(两辆均已派),改为商务×1+SUV×1。期望车务再派车时看到:
|
||||
|
||||
1. **商务槽(当前)**: 沿用原车回显(换版时签名匹配自动沿用,如 26-9313 的蒙A-E5555)——可加「沿用」小徽标(判定见 #5784 changelog);
|
||||
2. **SUV 槽(当前)**: 待选车,点选车走候选接口(本次已修通);
|
||||
3. **旧数据行**: 被取代的原商务车(蒙A-S6666)从 `vehicleSlots` 里 `superseded=true` 的行渲染,标「旧数据/需求已改版」。**操作能力与普通行一致(可改派/取消),不要禁用**——旧数据与新数据无本质区别,只要日期对得上;仅改出发日期场景下旧车/司机日期不符时,车务需先取消旧行再进行下一步(此时给出引导提示)。
|
||||
|
||||
即"原来的两辆商务车都可见(一辆沿用在新槽、一辆标旧),新需求两个槽位齐全"。
|
||||
|
||||
### 2.7 ⚠️ a75efc05 复测未过·前端需返工(wx 2026-08-10 现场实测 26-9313,浏览器抓包实证)
|
||||
|
||||
标旧行没渲染、选车仍报错、槽位误显锁定,根因共 4 项(前 2 项同一根因):
|
||||
|
||||
1. **数据源遮蔽(根因)**: `explicitAssignmentSlots`(batchAssignmentSlots.js)只要 `order.assignmentSlots` 是数组就直接返回,**永远读不到 detail 的 `vehicleSlots`**。实测本单列表行 `assignmentSlots` 有 2 个普通槽(不含 superseded 行、无 superseded/canDelete 字段)→ ①supersededSlots 恒空,「旧数据·需求已改版」行一次都没渲染过;②槽位对象丢失 `canDelete=true`(detail 有、列表无)→ 误显"当前槽位不可删除,请刷新详情后重试"。**修法**: superseded 标旧行与 canDelete/deleteBlockReason 必须以 detail `vehicleSlots` 为源(合并或优先)。
|
||||
2. **candidates 排除同源缺陷阻塞实锤**: 槽位2 选车请求实抓 `fleetItemIndex=1, excludeAssignmentId=2086270154022285313`(=槽位1 E5555 已派行)——picker 上下文的 excludeAssignmentId 取了订单级行而非本槽位行,被后端精确匹配拒绝报"参数非法"。后端已再兜底(PR #5802:同订单上下文不一致的排除改为**忽略**并 WARN,不再报错),**现版前端已可正常选车**,但同源修正仍需落地:excludeAssignmentId/fleetItemIndex 一律取本槽位行(vehicleSlots 同行的 assignmentId+fleetItemIndex),否则被误传行会在候选里如实显示冲突(略影响体验不阻塞)。
|
||||
3. **去除"已锁定"(wx 2026-08-10 口径)**: `isExistingAssignment` 槽位的「统一选择车辆/司机」按钮禁用+显示"已锁定"——去除该锁定;已有槽位同样允许统一选车(走候选接口,后端排除口径已放开)。
|
||||
4. **任何槽位车务都可删除(wx 2026-08-10 口径)**: 删除按钮不再因 canDelete 缺失/false 隐藏或拦截——后端 #5572 本就允许删除一切未完结槽位(已派/待确认删除时联动取消释放),仅已完结/已关账行后端会拒并给原因,前端直接放开按钮、失败时展示后端 message 即可(换版保留旧行例外:仍走改派/取消不走删除)。
|
||||
|
||||
**⚖️ 形态重定(wx 2026-08-10 最终口径,推翻上一段"形态定稿")**: 「旧数据·需求已改版」**不接受窄条单列**。旧数据必须作为**槽位条目**渲染进「排车方案」槽位列表,与「车辆槽位 N」同构。按下方 2.8 规格实现。
|
||||
|
||||
**⏸️ 暂停施工(wx 2026-08-10 晚,最高优先)**: 2.8 的"旧数据槽位卡"形态**不要做了**。wx 最终口径已升级为后端换版策略简化(工单 #5810:换版不自动增减槽位、全部行平移绑定新需求、superseded 形态退役、需求转语句字段 requirementFleetText、出发日期唯一门禁)。后端落地后本 changelog 会出全新前端契约(预告:槽位头去"建议{车型}"标签、改版横幅显示需求语句、槽位统一原样渲染)。在那之前 #5788 前端侧**保持现状即可**,勿再按 2.8 施工。
|
||||
|
||||
### 2.8 🎯 旧数据槽位·实现规格(最终版,给前端按此落地,逐条执行不要自行简化)
|
||||
|
||||
**A. 移除**: AssignModal 中 a75efc05 引入的 `am-superseded-slots` 窄条区块(含"该行属上一版用车需求的保留派单…请在订单详情核对后改派或取消"提示条)整体删除。
|
||||
|
||||
**B. 渲染位置与结构**: `supersededSlots`(来源见 F)的每一行,渲染为一张**槽位卡片**,复用当前槽位(`am-slot-collapse__item`)的同构结构,排在全部当前需求槽位之后。卡片头从左到右:
|
||||
1. 标题位(替代"车辆槽位 N"):**「旧数据 · 需求已改版」**;
|
||||
2. 车型徽标位:该行 `requiredVehicleTypeLabel`(如"商务车");
|
||||
3. 车辆位:`vehiclePlate + vehicleModel`(如 蒙A-S6666 丰田赛那);
|
||||
4. 司机位:`driverName`;
|
||||
5. 状态位:**「已派车 · 待人工核对」**;
|
||||
6. 视觉:整卡灰底/弱化以区分当前槽位(可沿用现有 superseded 样式类)。
|
||||
|
||||
**C. 展开内容**: 后端**刻意不提供**保留行的逐日切片(dailyVehiclePlan 排除保留行防双重计数,#5787),所以展开区渲染**一条区间行**即可:服务日期=`serviceStartDate ~ serviceEndDate`(26-9313 即 08/22~08/24)、车、司机、状态;**不渲染逐日价格编辑**。
|
||||
|
||||
**D. 操作(与普通槽位一致,均直接可用,不引导跳订单详情)**:
|
||||
1. **「更换车辆/司机(改派)」**: 打开统一选择器,candidates 请求参数——`orderId`=当前订单、`requirementId`=**详情顶层当前需求 id**(不是保留行的 requirementId!)、`excludeAssignmentId`=**该保留行自身 assignmentId**、`fleetItemIndex` **不传**(后端口径 2 已支持,传了也不校验)、`startDate/endDate`=保留行自身日期。选定车/司机后走既有改派提交链路。
|
||||
2. **「取消」**: `DELETE /admin/fleet/assignments/{assignmentId}`,body: `cancelReason` 必填、`driverNotified` 必填(true/false)、`requestId` 幂等;无凭证时后端返回 605026「未上传取消凭证,请二次确认」→ 弹确认后携 `confirmWithoutEvidence=true` 重试。
|
||||
3. **不提供「删除槽位」按钮**(该行 `canDelete=false`,`deleteBlockReason` 已给文案;删除≠取消,保留行只走改派/取消)。
|
||||
|
||||
**E. 处置后的刷新预期**: 改派或取消成功后重拉详情——该旧数据槽位卡消失、顶部黄条(supersededAssignments)消失、`requirementChangePendingCount` 归 0。
|
||||
|
||||
**F. 数据源(不许再走列表 assignmentSlots)**: 旧数据槽位只认 detail `GET /admin/fleet/board/orders/{orderId}` 的 `vehicleSlots[]` 中 `superseded===true` 的行(b31ac7c3 已修数据源合并,沿用);**分组/去重一律用 superseded 标志,严禁用 fleetItemIndex**(旧版序号与当前槽位撞号,26-9313 实测旧行 index=1 与 SUV 槽撞)。
|
||||
|
||||
**G. 改期场景提示**: 保留行 `serviceStartDate/serviceEndDate` 与订单当前出行日期不一致时,卡内加一行提示:「旧派车日期与当前需求不符,请先取消后重新派车」(改期时旧车/司机占用的是旧日期,车务须先取消)。
|
||||
|
||||
**H. 验收(测试服 26-9313 实测,全过才算完)**:
|
||||
- [ ] 排车方案列表内可见"旧数据·需求已改版"槽位卡(S6666/巴雅尔/08/22~08/24/已派车·待人工核对),位于当前槽位之后,窄条已删;
|
||||
- [ ] 该卡「更换车辆/司机」→ candidates 业务码 200,原车 S6666 在候选中 available=true(自排除生效);
|
||||
- [ ] 该卡「取消」(走二次确认)→ 徽章计数归 0、卡与黄条消失;
|
||||
- [ ] 当前需求槽位的选车/删除/统一选择功能不回归。
|
||||
|
||||
## 验证证据:实测数据样例(测试服 26-9313)
|
||||
|
||||
```json
|
||||
// vehicleSlots 尾部保留行
|
||||
{"fleetItemIndex":1, "requiredVehicleType":"mpv", "slotStatus":"assigned",
|
||||
"vehiclePlate":"蒙A-S6666", "superseded":true,
|
||||
"requirementId":"2086270153913225218",
|
||||
"supersededByRequirementId":"2086700635972915201",
|
||||
"canDelete":false, "deleteBlockReason":"需求已改版保留行,请改派或取消,不支持直接删除槽位"}
|
||||
```
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **相关工单**: [#5788](https://git.1814.love:8443/wx/HL/issues/5788)、[#5784](https://git.1814.love:8443/wx/HL/issues/5784)(换版保留规则)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5797"
|
||||
title: "车务聊天定制师端气泡永远「已送达」修复——车务读过即显示「已读」,并新增定制师端点对点 READ 信令"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "接口契约零变化,前端无需改动即生效(readByPeer 字段在车务会话首次真正有值)。附带新增:车务团队读后向定制师推点对点 im-chat-read 信令,SSE 角标实时化接线时可顺带消费实现气泡实时翻「已读」。测试服 16:00 后已用真实 API + Redis 信令抓包验证通过。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T16:20:00+08:00"
|
||||
---
|
||||
|
||||
# 车务/聊天: 定制师端气泡永远「已送达」修复——车务读过即显示「已读」
|
||||
|
||||
> **服务**: hl-user-service
|
||||
> **PR**: #5799
|
||||
> **Issue**: #5797
|
||||
> **日期**: 2026-08-10
|
||||
> **影响范围**: 管理后台车务订单聊天(FLEET:{orderId} 会话)定制师端气泡已读态;聊天 READ 信令
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
定制师在订单详情聊天框给车务发消息后,无论车务/超管是否已打开聊天窗读过,定制师端气泡**永远显示「已送达」**、刷新也不变「已读」。服务端团队共享已读水位其实一直在正常推进(#5792 修复后车务侧红点清零正常),坏的只是定制师端「读出来」这半边——#4689 车务聊天上线起即存在。
|
||||
|
||||
## 二、根因(后端,接口契约无关)
|
||||
|
||||
车务团队占位 adminId=0 与房务「抢单前无人接单」占位 0 **撞值**:定制师成员行的对端恒为 0(=车务团队),后端解析对端已读水位时把 0 一律当房务占位短路返 null → `readByPeer` 恒 false。
|
||||
|
||||
## 三、变更(均后端内部,接口路径/请求/响应结构零变化)
|
||||
|
||||
| # | 变化 |
|
||||
|---|---|
|
||||
| 1 | 对端=车务团队时改读 admin_id=0 团队行共享水位 → **任一车务/获准超管读过,定制师端 `readByPeer` 即为 true**(open / open-fleet / 消息分页单一漏斗全覆盖);房务抢单前占位行为不变 |
|
||||
| 2 | **新增**:车务团队读且水位实际推进时,向当前定制师推一条**点对点 `im-chat-read` 信令**(payload:`adminId`=定制师、`readerAdminId`、`lastReadMessageId`、`conversationKey`;此前定制师收不到任何 READ——团队清零信令只按 VEHICLE_MANAGER 角色广播) |
|
||||
| 3 | 读侧对齐发送侧「定制师身份优先」:定制师本人持超管角色打开自己会话时,推进自己行而非团队水位(否则自读会把自己气泡刷成「已读」) |
|
||||
|
||||
## 四、对前端的影响
|
||||
|
||||
- **无需改动即生效**:刷新/重开聊天框后,`readByPeer` 在车务会话首次真正有值,气泡按现有渲染逻辑显示「已读」。
|
||||
- **给 SSE 角标实时化接线的顺带增强**(见关联 changelog):定制师端现在会收到点对点 `im-chat-read` 信令,接入 chatSignalBus 后可把「自己发的、id<=lastReadMessageId」的气泡实时翻成「已读」,不必等刷新。信令结构与既有 #4289 点对点 READ 完全一致,无新字段。
|
||||
|
||||
## 五、验证证据(2026-08-10 测试服)
|
||||
|
||||
| 验证点 | 实测 |
|
||||
|---|---|
|
||||
| 定制师视角发消息、车务未读 | `readByPeer=false`(不误报) |
|
||||
| 车务读后定制师拉线程 | **`readByPeer=true`**(此前恒 false) |
|
||||
| 真实车务团队读(open-fleet) | 团队行水位 673→714、unread 1→0 |
|
||||
| READ 信令 Redis 抓包 | 两条:团队广播(targetRole=VEHICLE_MANAGER)+ **点对点(adminId=定制师)**,水位正确 |
|
||||
| 定制师本人(超管)自读 | 团队行水位不动(不再自己读自己) |
|
||||
|
||||
- 单测:新增 11 例,chat 相关 219+33 全绿。
|
||||
- 现场顺带修复:订单 26-9313 定制师端三条「已送达」消息,部署后刷新即显示「已读」。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5797](https://git.1814.love:8443/wx/HL/issues/5797)
|
||||
- **PR**: [#5799](https://git.1814.love:8443/wx/HL/pulls/5799)
|
||||
- **Merge commit**: [31ffa7459](https://git.1814.love:8443/wx/HL/commit/31ffa7459)
|
||||
- **相关 changelog**: `changelogs-v2/2026-08/10_frontend_车务看板聊天角标实时化与已读联动-前端优化-管理后台.md`(SSE 接线时消费本单新增的点对点 READ 信令)
|
||||
- **前置修复**: #5792(车务读→团队红点清零半边)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5808"
|
||||
title: "消息中心纳管车务团队会话消息:list 混排团队池行 + teamMessage 字段 + read-all 三段清零"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "55311b31"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "前端已实现(范围:团队角标+隐藏删除,55311b31):MyMessages/index.vue 类型列 teamMessage=true 行在 messageTypeLabel 旁加「团队」NTag 角标(区分个人消息,读态全队共享不代表「我读过」);删除入口两处按 teamMessage=true 隐藏——more 菜单「删除」项 + 详情抽屉(handleDeleteFromDetail)删除按钮(后端 281014 团队共享消息不可删)。单条已读/read-all 三段清零/SSE conversationUnreadCount 余量下发均后端语义扩展、接口字段兼容,前端零改动即受益。无 MyMessages 专属 spec(列表 useListPage 重 mock 成本高于局部展示+menu 条件改动价值),由 checkpoint(ESLint/Stylelint/暗色/断点/生产构建)全绿覆盖。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 消息中心: 纳管车务团队会话消息(#5808)
|
||||
|
||||
> **服务**: hl-user-service (8081)
|
||||
> **PR**: #5812
|
||||
> **Issue**: #5808
|
||||
> **日期**: 2026-08-10
|
||||
> **影响范围**: /admin/message 五个端点(list/detail/{id}/read/read-all/{id} DELETE)+ SSE 共享 READ 信令载荷
|
||||
> **背景**: 车务管理员铃铛红点「全部已读」清不掉且列表找不到源头——FLEET 订单会话中定制师广播只落一行 admin_id=0 团队池行,个人收件箱无副本,此前消息中心既看不见也管不了它。本次把团队池行纳入消息中心管理。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 1. GET /admin/message/list — 车务角色下混排团队池聊天行 + 新增 teamMessage 字段
|
||||
|
||||
- 当前角色为 **VEHICLE_MANAGER** 时,列表在个人收件箱行之外混排**车务团队共享池聊天行**(全体车务共享同一份,含读态),按 createTime DESC 混排、分页 total 准确;其它角色行为不变。
|
||||
- **所有行**新增字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `teamMessage` | boolean | **是否车务团队共享消息**。true=团队池行(全队共享读态:任一车务已读,全队该行变已读);false=个人行 |
|
||||
|
||||
- 排序修正:同秒消息补 id 决胜列,翻页不再可能重复/丢行(对全部角色生效)。
|
||||
- messageType=ORDER/NORMAL、categoryCode 过滤与池行组合语义不变(池行属 ORDER=聊天类)。
|
||||
|
||||
**前端要做**:`teamMessage=true` 行渲染「团队」角标以区分个人消息;该类行的已读状态是全队共享的,不代表"我读过"。
|
||||
|
||||
### 2. GET /admin/message/{id} — 详情放行池行
|
||||
|
||||
车务管理员可查团队池行详情(返回体同样含 `teamMessage=true`);其它角色查池行仍返回 200401「站内信不存在或无权访问」。详情仍是纯查询不改已读。
|
||||
|
||||
### 3. PUT /admin/message/{id}/read — 池行单条已读=推进团队共享水位
|
||||
|
||||
对 `teamMessage=true` 行标已读:按会话**单调推进团队共享水位到该行**(该会话中更早的团队消息一并置已读,更晚的保持未读),**全体车务**红点同步回落;SSE 广播共享 READ 信令 + 定制师端点对点已读回执(同 #5797 语义)。重复调用幂等静默。
|
||||
|
||||
### 4. PUT /admin/message/read-all — 语义扩为三段清零
|
||||
|
||||
原来只清个人系统通知,现在依次清:①个人系统通知(现状)→ ②个人聊天逐会话推进个人水位(对端会收到已读回执)→ ③车务角色下全部 FLEET 团队会话推进共享水位到最新(**清掉全体车务的红点**,即 #4689「任一读、全队清零」语义的批量形态)。
|
||||
|
||||
执行后 `GET /admin/message/unread-count` 归 0。极端并发下个别会话正被读写会跳过(日志可查),再点一次即清完。**前端无需改动即受益**:铃铛红点从此「全部已读」必能点掉。
|
||||
|
||||
### 5. DELETE /admin/message/{id} — 池行禁删
|
||||
|
||||
对 `teamMessage=true` 行删除返回业务错误 **281014**「团队共享消息不可删除」(团队共享消息不允许单人删掉全队的记录)。**前端要做**:`teamMessage=true` 行隐藏/禁用删除入口。个人行删除行为不变。
|
||||
|
||||
### 6. SSE 共享 READ 信令载荷微调
|
||||
|
||||
`im-chat-read`(共享 READ)信封中 `conversationUnreadCount` 原恒为 0,现**部分已读时携带真实余量**(单条已读只推进到该行时,会话可能还有更晚的未读)。前端若有按该字段刷会话红点的逻辑,直接用下发值即可(原逻辑兼容)。
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
测试服全链路实测通过(车务 token):unread-count=1 → list 见池行(teamMessage=true) → 详情 200 → DELETE 281014 → 单条已读 → unread-count=0 → read-all 幂等 → 超管反例不可见池行;HOUSE 留言池数据零影响。hl-user-service 全量 3578 单测绿。
|
||||
@@ -0,0 +1,328 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5809"
|
||||
title: "完成核单去转账/签字凭据采集:finalize 改无请求体,主报账对账与报账表删除凭据字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "3993f15a"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(3993f15a):finalize 改无请求体(仅 Path 传 orderId)——orderV2.finalizeSettlement 改单参 body=undefined,settlementService.completeSettlement 删 remark trim/校验与 reimbursementConfirmationPayload 组装改 completeSettlement(id);ReportModal 删转账日期/预支是否已结清/流水号/签字单凭证/备注整块凭据表单与相关 script,formulaText 改注记凭据摘除;detail.vue 删 ReportModal 凭据绑定、reimbursementConfirmation ref、askComplete 改单参、删路由 watch 重置块。测试同步:orderV2.spec finalize 用例改断言 body=undefined;settlementService.spec 重写 finalize payload 断言为 toHaveBeenCalledWith(ORDER_ID)、删「净额非零必须提交日期流水」用例、各 finalize 用例去 confirmation 第二参;ReportModal.spec 删两个凭据编辑用例与 FileUpload mock/helper。前端先上、后端随后(前后端需同批,后端上线前老前端传凭据字段会 400,已按排期先前端)。核单域四 spec 34+16 全过,checkpoint 7 文件全绿(ESLint/Vitest 全量/生产构建)。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】完成核单去转账/签字凭据采集:finalize 改无请求体,recon 入参删 4 字段,报账表出参删 4 字段(#5809)
|
||||
|
||||
> **PR**: [#5813](https://git.1814.love:8443/wx/HL/pulls/5813) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-10
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单流程此前在「完成核单」和「主报账对账保存」两个环节采集转账/签字凭据:转账日期、转账流水号、预支是否已处理、签字凭证文件(图片 + 备注)。产品上决定凭据采集不再挂在核单链路(核单只负责核算确认,凭据留档走线下或其他系统),因此本次把凭据相关字段从 3 个接口整体摘除:
|
||||
|
||||
- **完成核单 finalize**:整个请求体删除,改为仅 Path 传 orderId 的无 body 接口;
|
||||
- **主报账对账保存 recon**:入参只保留 transferStatus,删 4 个凭据字段及其必填校验;
|
||||
- **主报账人报账表 GET**:出参删同样 4 个凭据字段,报账弹窗改纯展示。
|
||||
|
||||
> ⚠️ 本次为**入参删字段 + 出参删字段的硬破坏契约**:相关 ReqVO 配置了 ignoreUnknown=false,前端若继续传已删字段会直接 400,**前后端必须同批上线**(详见 §11、§12)。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 完成核单 | POST | /v3/admin/order/{orderId}/settlement/finalize | 删除整个请求体(原 SettlementSubmitReqVO:remark + reimbursementConfirmation 凭据对象) | 请求不再带 body;清理凭据表单/校验逻辑 |
|
||||
| 2 | 主报账对账保存 | PUT | /v3/admin/order/{orderId}/settlement/recon | 入参删 transferDate / transferRef / advanceSettledFlag / signedVoucher,仅保留 transferStatus | 请求体只传 transferStatus;清理凭据表单 |
|
||||
| 3 | 主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 出参删 transferDate / transferRef / advanceSettledFlag / signedVoucher | 停读 4 字段;报账弹窗删凭据展示区 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:
|
||||
- finalize:管理后台核单页面对账完成后点击「完成核单」,把订单核算推进到终态(落 FINALIZED 快照)。
|
||||
- recon:主报账对账弹窗保存转账状态(待转账 / 已转账)。
|
||||
- reports/reimbursement:主报账人报账表弹窗,展示主报账人收付对账与转账结论。
|
||||
- **认证**:需要管理后台登录态(Bearer Token);房务角色(HOUSE)被 OrderViewGuard 拦截。
|
||||
- **幂等性**:
|
||||
- finalize:改为纯终态校验,已有 FINALIZED 快照且终态一致时直接返回原结果,**天然幂等**,重复点击无副作用。
|
||||
- recon:同一 transferStatus 重复保存为覆盖写,幂等。
|
||||
- reports/reimbursement:只读查询,天然幂等。
|
||||
- **限流**:未声明接口专属限流。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
三个接口一致:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long | 是 | 订单 ID,路径参数,必须大于 0 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
#### POST /v3/admin/order/{orderId}/settlement/finalize(完成核单)
|
||||
|
||||
**无请求体**。原 SettlementSubmitReqVO 整体删除,被删字段如下(前端不得再传):
|
||||
|
||||
| 原字段 | 类型 | 原说明 | 现状 |
|
||||
|---|---|---|---|
|
||||
| remark | String | 核单整体备注 | 已删除;服务端不再写 settlement_summary.remark |
|
||||
| reimbursementConfirmation | Object | 凭据对象 | 已删除 |
|
||||
| reimbursementConfirmation.transferDate | String(yyyy-MM-dd) | 转账日期 | 已删除 |
|
||||
| reimbursementConfirmation.transferRef | String | 转账流水号 | 已删除 |
|
||||
| reimbursementConfirmation.advanceSettledFlag | Boolean | 预支是否已处理 | 已删除 |
|
||||
| reimbursementConfirmation.signedVoucher | Object | 签字凭证,含 files[{name,url}] + note | 已删除 |
|
||||
|
||||
> 原「reporterNetAmount 非 0 时强制 transferDate / transferRef 必填」的服务端校验同步移除;原 finalize 凭据校验失败错误码 584317 在此接口不再触发。
|
||||
|
||||
#### PUT /v3/admin/order/{orderId}/settlement/recon(主报账对账保存)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| transferStatus | String | 是 | 转账状态,枚举:PENDING(待转账)/ COMPLETED(已转账) |
|
||||
|
||||
被删字段(前端不得再传,传了会 400):
|
||||
|
||||
| 原字段 | 类型 | 原说明 | 现状 |
|
||||
|---|---|---|---|
|
||||
| transferDate | String(yyyy-MM-dd) | 转账日期 | 已删除 |
|
||||
| transferRef | String | 转账流水号 | 已删除 |
|
||||
| advanceSettledFlag | Boolean | 预支是否已处理 | 已删除 |
|
||||
| signedVoucher | Object | 签字凭证,含 files[{name,url}] + note | 已删除 |
|
||||
|
||||
> 原「transferStatus=COMPLETED 时 transferRef 必填」校验同步移除。
|
||||
|
||||
#### GET /v3/admin/order/{orderId}/settlement/reports/reimbursement(主报账人报账表)
|
||||
|
||||
无请求体、无 Query 参数,入参零变化。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 POST finalize / PUT recon
|
||||
|
||||
统一响应 Result 包装,data 为操作结果(成功 code=200)。出参结构无变化。
|
||||
|
||||
### 5.2 GET /v3/admin/order/{orderId}/settlement/reports/reimbursement(主报账人报账表)
|
||||
|
||||
统一响应 Result 包装,data 字段如下(仅列关键字段 + 本次删除项):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String(Long) | 报表记录 ID;未落库时可为 null |
|
||||
| orderId | String(Long) | 订单 ID |
|
||||
| reportStatus | String | 报表状态:GENERATED / CONFIRMED |
|
||||
| primaryReporterId | String(Long) | 主报账人人员安排 ID |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| reportVersion | Integer | 报表版本号 |
|
||||
| driverCollectedTailAmount | String(BigDecimal) | 司机代收尾款 |
|
||||
| approvedAdvanceAmount | String(BigDecimal) | 已审批预支金额 |
|
||||
| reportablePaidCostAmount | String(BigDecimal) | 可报账已付成本 |
|
||||
| reporterNetAmount | String(BigDecimal) | 报账人净额 |
|
||||
| primaryReporterCollectedAmount | String(BigDecimal) | 主报账人已收款 |
|
||||
| publicPrepaidAmount | String(BigDecimal) | 对公预付金额 |
|
||||
| primaryReporterDueAmount | String(BigDecimal) | 主报账人应结金额 |
|
||||
| advanceOutstandingAmount | String(BigDecimal) | 预支未销余额 |
|
||||
| reconNetAmount | String(BigDecimal) | 对账净额 |
|
||||
| transferDirection | String | 转账方向 |
|
||||
| transferAmount | String(BigDecimal) | 转账金额 |
|
||||
| incomeLines | Array | 收款行;每项含 type / typeName / receiptId / amount / channel / channelName / payType / payTypeName / collectorStaffId / collectorName / collectorRole / collectorRoleName / receivedAt / remark |
|
||||
| expenseLines | Array | 支出行;每项含 kind / category / categoryName / amount / paymentMethod / paymentMethodName / voucherUrls / remark + 类别扩展字段 |
|
||||
| advanceLines | Array | 预支行 |
|
||||
| vehicleLines | Array | 已确认车辆逐日费用明细;无数据固定返回空数组 |
|
||||
| transferStatus | String | 转账状态(PENDING / COMPLETED),保留 |
|
||||
| ~~transferDate~~ | - | **已删除**,前端不再收到此字段 |
|
||||
| ~~transferRef~~ | - | **已删除** |
|
||||
| ~~advanceSettledFlag~~ | - | **已删除** |
|
||||
| ~~signedVoucher~~ | - | **已删除** |
|
||||
| generatedBy / generatedByName / generatedAt | - | 生成人 ID / 姓名 / 时间 |
|
||||
| confirmedBy / confirmedByName / confirmedAt | - | 确认人 ID / 姓名 / 时间 |
|
||||
|
||||
> 除删除 4 个凭据字段外,其余字段名称、类型、语义均无变化。前端不要再读取这 4 个字段,读取结果恒为 undefined。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### transferStatus(recon 入参 + 报账表出参共用)
|
||||
|
||||
| 值 | 含义 | 本次变化 |
|
||||
|---|---|---|
|
||||
| PENDING | 待转账 | 不变 |
|
||||
| COMPLETED | 已转账 | 不变(原「COMPLETED 时 transferRef 必填」联动校验移除) |
|
||||
|
||||
其余枚举/字典(reportStatus、incomeLines[].type、channel、payType、paymentMethod、transferDirection 等)取值与语义均无变化。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 本次变化 |
|
||||
|---|---|---|
|
||||
| 400(参数校验) | 请求体含未知字段 | **新增触发路径**:ReqVO ignoreUnknown=false,前端继续传已删凭据字段(recon / finalize)会直接返回 400 |
|
||||
| 584317 | 原 finalize 凭据校验失败(transferDate/transferRef 缺失等) | **此接口不再触发**(凭据校验整体移除) |
|
||||
|
||||
其余既有错误码(订单不存在、双报告未生成保护 584311 / 584313、房务角色 403 等)行为不变。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(完成核单,无请求体)
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/2084000000000002978/settlement/finalize
|
||||
Authorization: Bearer <token>
|
||||
Content-Length: 0
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:请求不带任何 body。幂等——已有 FINALIZED 快照且终态一致时重复调用直接返回原结果。
|
||||
|
||||
主报账对账保存(只传 transferStatus):
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/recon
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"transferStatus": "COMPLETED"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 200, "message": "success", "data": null, "success": true}
|
||||
```
|
||||
|
||||
### 8.2 边界(报账表出参已无凭据字段)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/reimbursement
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "8802",
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "GENERATED",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"reportVersion": 1,
|
||||
"driverCollectedTailAmount": "0.00",
|
||||
"approvedAdvanceAmount": "0.00",
|
||||
"reportablePaidCostAmount": "0.00",
|
||||
"reporterNetAmount": "0.00",
|
||||
"primaryReporterCollectedAmount": "0.00",
|
||||
"publicPrepaidAmount": "0.00",
|
||||
"primaryReporterDueAmount": "0.00",
|
||||
"advanceOutstandingAmount": "0.00",
|
||||
"reconNetAmount": "0.00",
|
||||
"transferDirection": null,
|
||||
"transferAmount": "0.00",
|
||||
"incomeLines": [],
|
||||
"expenseLines": [],
|
||||
"advanceLines": [],
|
||||
"vehicleLines": [],
|
||||
"transferStatus": "PENDING",
|
||||
"generatedBy": "1001",
|
||||
"generatedByName": "张三",
|
||||
"generatedAt": "2026-08-10 10:20:30",
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 响应中已无 transferDate / transferRef / advanceSettledFlag / signedVoucher 四个字段(不是返回 null,是字段不存在)。
|
||||
|
||||
### 8.3 业务失败(前端仍传已删字段 → 400)
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/recon
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"transferStatus": "COMPLETED", "transferRef": "202608100001"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 400, "message": "请求参数格式错误(含未识别字段 transferRef)", "data": null, "success": false}
|
||||
```
|
||||
|
||||
> ReqVO ignoreUnknown=false,任何已删字段(transferDate / transferRef / advanceSettledFlag / signedVoucher / remark / reimbursementConfirmation)传入都会 400。这是本次最需要前端规避的失败路径。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 完成核单只需订单处于可核单终态 + 双报告已生成(584311 / 584313 保护不变),不再要求任何凭据。
|
||||
- ✅ recon 只需选择转账状态(PENDING / COMPLETED),COMPLETED 也不再要求转账流水号。
|
||||
- ✅ 报账表弹窗改为纯展示核算数据 + 转账状态,无凭据上传/回填交互。
|
||||
- ❌ 不要在前端保留凭据采集表单(转账日期/流水号/预支处理标记/签字凭证上传),继续提交会 400。
|
||||
- ❌ 不要把本地缓存/草稿里的凭据数据回传到 finalize 或 recon。
|
||||
- ❌ 订单历史凭据数据仍在库中(零 DDL,列未删),但接口不再返回;不要依赖报账表接口读取历史凭据。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 接口 | 字段 | 原来 | 现在 |
|
||||
|---|---|---|---|
|
||||
| POST finalize | 请求体整体 | SettlementSubmitReqVO(remark + reimbursementConfirmation{transferDate, transferRef, advanceSettledFlag, signedVoucher{files[{name,url}], note}}) | **无请求体**,仅 Path 传 orderId |
|
||||
| PUT recon | transferDate / transferRef / advanceSettledFlag / signedVoucher | 入参(transferStatus=COMPLETED 时 transferRef 必填) | **已删除**,入参仅保留 transferStatus |
|
||||
| GET reports/reimbursement | transferDate / transferRef / advanceSettledFlag / signedVoucher | 出参 | **已删除**,其余出参不变 |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 完成核单时 reporterNetAmount 非 0 | 强制要求 transferDate / transferRef,缺失报 584317 | 无任何凭据校验,直接走终态确认 |
|
||||
| 完成核单提交备注 remark | 写入 settlement_summary.remark | 不再接收、不再写入 |
|
||||
| recon 保存 transferStatus=COMPLETED | transferRef 必填 | 仅保存 transferStatus |
|
||||
| 报账表弹窗 | 展示并可回填凭据区 | 纯展示,无凭据字段 |
|
||||
| finalize 终态快照比对 | 含凭据字段比对 | 不再比对凭据 |
|
||||
| finalize 重复调用 | 凭据差异可能导致非幂等 | 纯终态校验,终态一致直接返回原结果,天然幂等 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**是,硬破坏**。finalize 请求体删除 + recon 入参删 4 字段 + 报账表出参删 4 字段;且 ReqVO ignoreUnknown=false,旧前端继续传凭据字段会直接 400(不是静默忽略)。
|
||||
- **前端是否必须同步上线**:**必须同批**。前端需先删掉凭据表单与字段读取,再与后端同批发布。
|
||||
- **上线顺序边界**:前后端同批发布;若必须分先后,**先上前端(停传/停读凭据字段),再上后端**。旧前端 + 新后端 = finalize / recon 直接 400,功能不可用。
|
||||
- **数据库侧**:**零 DDL**,数据库列未动,历史凭据数据保留在库中(接口不再读写),无数据迁移成本。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 代码回滚即恢复原请求体/入参/出参与凭据校验;数据库无变更,历史数据完整,回滚无数据修复成本。
|
||||
- **回滚必须前后端同批回滚**:新前端(不传凭据)+ 旧后端(reporterNetAmount 非 0 强制凭据)会导致 finalize 报 584317 失败。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **契约破坏红线**:相关 ReqVO 配置了 ignoreUnknown=false,前端**继续传任何已删字段都会 400**(不是忽略)。包括:finalize 的 remark / reimbursementConfirmation 整个对象,recon 的 transferDate / transferRef / advanceSettledFlag / signedVoucher。前后端必须同批上线。
|
||||
- 本次只动凭据采集相关字段;订单金额核算逻辑、reportStatus 枚举、双报告生成/确认保护(584311 / 584313)均未变化。
|
||||
- finalize 的 Swagger 描述中若残留凭据相关文案属文档残留,以本 changelog 为准。
|
||||
- 小程序端(/v3/mp/*)不涉及本次变更,无需任何改动。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5809](https://git.1814.love:8443/wx/HL/issues/5809)
|
||||
- **PR**: [#5813](https://git.1814.love:8443/wx/HL/pulls/5813)
|
||||
- **Commit**: [0e2ee34d6d](https://git.1814.love:8443/wx/HL/commit/0e2ee34d6d)
|
||||
- **相关批次**: Issue [#5739](https://git.1814.love:8443/wx/HL/issues/5739) / PR [#5764](https://git.1814.love:8443/wx/HL/pulls/5764)(核单指纹下线,changelog 见 2026-08/09_5739)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst) 腰苏图
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-batch-register-driver-confirm-disabled"
|
||||
title: "多槽位原子提交后停留在「待确认」页,「登记司机已确认」按钮恒灰不可点"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "f02dc8e3"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "前端已实现:useAssignFlow batch 分支 emit('submitted-keep-open') 后 await nextTick,从响应逐槽 assignments 按当前预览 tab(新增 activePreviewKey 入参=key=fleetItemIndex)取该槽位 assignment.id 落 activeAssignmentId(缺省回退首槽)并置 stage/finalized;AssignModal 传 activePreviewKey: activeMessagePreviewKey。useAssignFlow.spec 37/37(含多槽位按 tab 落 id 用例)、AssignModal 关联 65/65、checkpoint 全过。tab 切换逐段登记由 confirmHold 恢复态槽位选择联动,整组确认读后端 driverConfirmationSummary.allDriverConfirmed。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T12:20:00+08:00"
|
||||
---
|
||||
|
||||
# 多槽位原子提交后停留在「待确认」页,「登记司机已确认」按钮恒灰不可点
|
||||
|
||||
> 前端交互缺陷,待前端修复。是 `frontend-fleet-assign-modal-close-on-next`(4c81471c 改为不关窗)之后暴露出来的连带缺口。
|
||||
|
||||
## 现象(TEST 实测,订单 26-9313 孙晓东,2 个车辆槽位)
|
||||
|
||||
派单流程勾选 2 个车辆槽位 → 点「下一步」原子提交 → toast「已原子提交 2 个车辆槽位,等待司机确认」,弹窗按 4c81471c 的新行为保持开启、停在第 3 步「待确认」页 → **右下角「登记司机已确认」按钮灰掉点不动**,流程走不下去。
|
||||
|
||||
## 后端已排查,数据齐备(非后端问题)
|
||||
|
||||
| 检查项 | 实测结果 |
|
||||
|---|---|
|
||||
| `POST /admin/fleet/assignments/batch` 响应 | `BatchAssignmentWriteRespVO.assignments[]` 逐槽返回 `assignment`(复用单派 `AssignmentWriteRespVO`),**含 `id`** |
|
||||
| `GET /admin/fleet/board/orders`(列表行) | `assignmentId = 2086270154022285313`(非空) |
|
||||
| `GET /admin/fleet/board/orders/{orderId}`(详情) | 顶层无 `assignmentId` 字段(本来就不返回),派单身份走 `currentAssignment.id = 2086270154022285313`;`activeAssignments[]` 两段齐全 |
|
||||
| DB `fleet_assignment` | 两行均 `assignment_status=holding`、`hold_mode=1`,身份完整 |
|
||||
|
||||
## 定位(前端)
|
||||
|
||||
`useAssignFlow.js` 待确认链路 batch 分支(4c81471c 后):
|
||||
|
||||
```js
|
||||
if (submission.type === 'batch') {
|
||||
message.success(`已原子提交 ${selectedSlots.value.length} 个车辆槽位,等待司机确认`)
|
||||
emit('submitted-keep-open')
|
||||
return // ← 全程不设 activeAssignmentId
|
||||
}
|
||||
```
|
||||
|
||||
单槽路径走的是 `const assignmentId = result?.id ?? result?.assignmentId` → `activeAssignmentId.value = String(assignmentId)`,所以单槽正常。
|
||||
|
||||
而 `Step3DriverConfirm.vue` 的按钮是 `:disabled="!assignmentId || uploadingCount > 0"`,`assignment-id` 直接绑 `activeAssignmentId`。batch 路径下它为空 → 按钮恒灰。
|
||||
|
||||
原设计指望父层 `onSubmittedKeepOpen` → `refreshBoardAfterMutation` → `assignMode.value = 'confirmHold'` 触发 AssignModal 重开 watch 走 `restoreFlowState()` 恢复 `assignmentId`。实测按钮仍灰,说明这条恢复链在多槽位场景没走通(可能 mode 赋同值 watch 未触发,或恢复时机晚于渲染)——**依赖「刷新详情 + 重开 watch」这条长链恢复本身就脆**。
|
||||
|
||||
## 建议修法(最短路径)
|
||||
|
||||
batch 提交成功后,直接从响应里取当前预览 tab(司机)对应槽位的 id 落到 `activeAssignmentId`,不再依赖重开 watch:
|
||||
|
||||
```js
|
||||
const items = result?.assignments || []
|
||||
const current = items.find((it) => it.fleetItemIndex === activeFleetItemIndex) || items[0]
|
||||
const id = current?.assignment?.id
|
||||
if (id) activeAssignmentId.value = String(id)
|
||||
```
|
||||
|
||||
多槽位下第 3 步本身是按司机分 tab 的(截图里「阿木古愣·蒙A-E5555」「巴雅尔·蒙A-S6666」),切 tab 时同步把 `activeAssignmentId` 换成该 tab 槽位的 id,即可逐段登记司机确认。整组是否都确认了,读后端 `driverConfirmationSummary.allDriverConfirmed`(多执行段只信服务端守恒汇总,别在前端推导)。
|
||||
|
||||
## 临时绕过(给车务)
|
||||
|
||||
关掉派单弹窗,回派单看板重新点开该订单进入确认流程——此时恢复态从看板**列表行**取 `assignmentId`(非空),「登记司机已确认」按钮可正常点击。
|
||||
|
||||
## 复现路径
|
||||
|
||||
派单看板 → 选一个 2 槽位需求的订单(如 26-9313)→ 排车勾 2 个槽位 → 「下一步」原子提交 → 弹窗保持开启停在「待确认」→ 观察右下角「登记司机已确认」按钮灰掉不可点。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **前置前端改动**: [4c81471c](https://git.1814.love:8443/wx/hl-ui/commit/4c81471c)(派单待确认页点「下一步」窗口保持开启,本缺陷由其暴露)
|
||||
- **相关 changelog**: `changelogs-v2/2026-08/09_frontend_派单待确认页点下一步窗口关闭应保持开启-前端缺陷-管理后台.md`
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-step4-confirm-continuity-sms"
|
||||
title: "确认执行页 2 问题:①车辆接续只显示第一段车/师傅 ②「不发送短信」口径澄清(仅控制确认后行程短信)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "6709c157"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端已实现:①Step4Confirm 新增 assignments prop(父层 confirmationAssignments),多段时遍历渲染每段「服务日期段+车辆+师傅」(confirmationSegments+segmentDateLabel,逐日 serviceDates 优先回退 startDate~endDate),单段/无接续回退单数 prop 旧行为不破;AssignModal 传 :assignments。②「不发送短信」提示改「仅不发送确认后的行程短信,派单通知短信仍会发送」。step4-confirm.spec 补 3 回归用例 5/5 过 + checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T10:20:00+08:00"
|
||||
---
|
||||
|
||||
# 确认执行页 2 问题:车辆接续只显示第一段 + 「不发送短信」口径澄清
|
||||
|
||||
> 前端缺陷,后端能力已具备、无需改后端。待前端修复。
|
||||
|
||||
## 场景(车辆接续,订单 26-6436 周梦洁,order_id=2086270156475936769)
|
||||
|
||||
一个车辆槽位在多天内由不同车辆/司机接续服务:
|
||||
- 8/23:蒙A-E2E01 道尔吉(driver_id=2065272150012444674,vehicle_id=2085284111341023234)
|
||||
- 8/24~8/25:蒙A-E2E99 王信(driver_id=2067084362829979650,vehicle_id=2085539421276286978)
|
||||
|
||||
## 问题①:确认执行页「车·师傅」只显示第一段
|
||||
|
||||
**现象**:「车·师傅」只显示 蒙A-E2E01/道尔吉(第一段),没显示王信(第二段);但「行程短信」行写「全部 2 个执行段·必选」(系统知道有 2 段)。
|
||||
|
||||
**定位(前端)**:`Step4Confirm.vue` 的 `vehicle`/`driver` 是**单数 prop**,父组件 `AssignModal.vue` 传入 `selVehicleObj`/`selDriverObj`(`useVehicleDriverPicker` 的当前活动槽位选中快照),模板只用 `vehicle?.plate`/`driver?.name` 渲染 1 组。**逐日车费同源**:`dailyVehicleFees` ref 由 `applyVehicleFeeDraft(assignment.dailyVehicleFees)` 填充,同样只取**活动槽位单段** assignment 的逐日费用——26-6436 只显示 8/23 ¥1000,缺 8/24-25 王信段(¥1000×2),最终总车费只 ¥1000。
|
||||
|
||||
**后端数据完整(已核实,无需后端改动)**:
|
||||
- `GET /admin/fleet/board/orders/2086270156475936769` 的 `activeAssignments`(List\<CurrentAssignmentVO\>)返回**全部有效派车组**,每组含 vehiclePlate/vehicleModel/vehicleSeats/driverName/driverPhone(BoardOrderDetailVO 字段已核实);
|
||||
- `dailyVehiclePlan` 正确返回全部 2 车 2 司机(8/23 蒙A-E2E01/道尔吉 + 8/24-25 蒙A-E2E99/王信)。
|
||||
|
||||
**期望**:确认执行页遍历全部执行段(`confirmationAssignments`/`activeAssignments`)展示多段「车辆+师傅」以及**各段逐日车费与合计**(后端每段均返回 `dailyVehicleFees` 全部服务日快照 + `vehicleFeeTotal`),车务能核对完整执行信息与完整车费。
|
||||
|
||||
## 问题②:选「不发送短信」确认后司机仍收到短信——口径澄清
|
||||
|
||||
**定位(前后端逻辑均正确,非缺陷,建议前端补提示文案)**:
|
||||
|
||||
| 事实 | 证据 |
|
||||
|------|------|
|
||||
| 前端确认时实传 sendItinerarySms | 8/10 09:09 最终确认(王信 8/24-25 转 assigned)落库 `fleet_assignment.itinerary_sms_decision=0`;8/9 15:10 起历次确认均为 0 —— 前端传值正确 |
|
||||
| 后端拦截正确 | decision=0 的确认均未创建 ITINERARY_SMS outbox 事件;全订单仅 1 条 ITINERARY_SMS 事件(event_id=2086286779542855682,8/9 11:01:44 创建、12:43 处理成功)对应 8/9 11:01 首次确认(decision=1,当时选择了发送短信) |
|
||||
| 用户「仍收到短信」的真实来源 | ① 派单通知短信 `FLEET_DISPATCH_CREATED`(模板 SMS_510265061,**与行程短信同模板**):由派单/改派 HOLD_NOTIFICATION 触发,派单时必发,不受 sendItinerarySms 控制——8/10 09:09:09 王信、09:22:39 道尔吉均在「不发送短信」确认(09:09:27)前后收到;② 8/9 11:01 首次确认(选择了发送短信)已发出的行程短信(12:32 发送),后取消改派无法撤回 |
|
||||
|
||||
**结论**:「不发送短信」只控制**确认后创建的行程短信事件**(FLEET_ITINERARY_READY),**不控制派单通知短信**(FLEET_DISPATCH_CREATED,派单时必发)。验收项「选不发送短信确认后不创建/不发送行程短信事件」在现行代码与 DB 事实下已成立。
|
||||
|
||||
**建议前端**:确认执行页选择「不发送短信」时提示「仅不发送确认后的行程短信,派单通知短信仍会发送」,避免车务误解。
|
||||
|
||||
## 变更接口
|
||||
|
||||
- 无后端接口变化。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- DB 实证(TEST hl_fleet_service):`fleet_assignment` 全史 16 行,itinerary_sms_decision 仅首次确认=1,其余全部=0;`fleet_assignment_insurance_outbox` 全史仅 1 条 ITINERARY_SMS(首次确认创建,SUCCESS)。
|
||||
- DB 实证(TEST hl_user_service):`notification_send_log` 仅 1 条 FLEET_ITINERARY_READY(8/9 12:32 发王信 185\*\*\*\*2756,status=0);8/10 09:09/09:22 两条为 FLEET_DISPATCH_CREATED(派单短信)。
|
||||
- 源码核实:`AssignmentService.confirmRequirement`/`confirm` 均 `if (sendItinerarySms)` 才 `writeItinerarySms`;`BoardOrderDetailVO.activeAssignments` 返回全部执行段。
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-step4-confirm-daily-fee-continuity"
|
||||
title: "确认执行页逐日车费未随接续段遍历仍只显示第一段(车·师傅已修好,逐日车费漏改)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ffb2df89"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端已实现(承接 6709c157 车·师傅遍历):vehicleFeeAmount.js 新增 mergeContinuityDailyVehicleFees(按服务日合并全部接续段逐日快照、同日去重取首段)+ sumContinuityVehicleFeeTotal(优先后端逐段 vehicleFeeTotal 求和、任一段缺总价回退聚合逐日价精确合计);AssignModal 新增 confirmationDailyVehicleFees/confirmationVehicleFeeTotal computed——step4(confirmHold)且多段接续时聚合全部段,Step4Confirm 绑定改为此二值,dailyVehicleFees 改派草稿不污染。26-6436 总车费 ¥1000→¥3000。vehicleFeeAmount.spec 补 4 聚合用例 23/23 过 + checkpoint 全过。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-10T10:30:00+08:00"
|
||||
---
|
||||
|
||||
# 确认执行页逐日车费未随接续段遍历(仍只显示第一段)
|
||||
|
||||
## 现象(TEST 实测,订单 26-6436,1 槽位 2 车 2 司机接续)
|
||||
- 「车·师傅」**已修好**(6709c157):遍历全部接续段(08/23 蒙A-E2E01/道尔吉 + 08/24-25 蒙A-E2E99/王信)✅;
|
||||
- 「不发送短信」口径提示也已加 ✅;
|
||||
- **但「逐日车费」list 仍只显示 1 条**(8月23日 ¥1000),缺 8/24、8/25(王信段),最终总车费只 ¥1000——3 日行程应显示 3 条、总车费 ¥3000。
|
||||
|
||||
## 后端数据(完整,无需改)
|
||||
`GET /admin/fleet/board/orders/{orderId}` 的 `activeAssignments`(List<CurrentAssignmentVO>)每段均返回完整逐日车费:
|
||||
- 段1 道尔吉 [8/23~8/23]:dailyVehicleFees 1 条(8/23),vehicleFeeTotal=1000.00;
|
||||
- 段2 王信 [8/24~8/25]:dailyVehicleFees 2 条(8/24、8/25),vehicleFeeTotal=2000.00。
|
||||
|
||||
## 期望(前端)
|
||||
「逐日车费」与「车·师傅」一致遍历全部执行段合并:展示 3 条(8/23 道尔吉 + 8/24、8/25 王信,标注各段车/司机),最终总车费=各段 vehicleFeeTotal 合计(¥3000)。当前前端逐日车费仍只取活动槽位单段(`applyVehicleFeeDraft(assignment.dailyVehicleFees)` 只取当前 assignment),需改为遍历全部执行段合并(可复用 confirmationSegments/confirmationAssignments 的遍历逻辑)。
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-order-vehicle-arrange-display"
|
||||
title: "订单详情「用车安排」展示优化:逐日行按车聚合 + 换版沿用语义标注"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端优化"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "728413e6"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-10"
|
||||
status_note: "前端已实现(范围:按车聚合+部分回配标注):新建 _shared/vehicleArrangeGroup.js 按(车牌+司机)聚合 DAILY_V3 逐日镜像行(单行/无键回退原样,收 serviceDates/区间);v3Adapter 透传 serviceDate/startDate/endDate/serviceDays;VehicleArrangeCard 已回配车辆改聚合车卡+日期 chips+头部 PROCESSING/PENDING 且有回配行细化「部分回配(x/N)」;ConfiguredVehiclesModal 同步聚合(小计仍按原始逐日行求和)。util 6/6、主卡 12/12、checkpoint 全过。【沿用徽标暂缓】换版「沿用」槽缺字段级判据(数据上无法区分沿用槽 vs 新配槽),待后端在 assignments/requirement 补判定字段后另行接入;TEST 无逐日镜像已回配真实样本,按契约描述盲写、缺字段回退现状。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 订单详情: 用车安排展示优化(按车聚合 + 沿用语义)
|
||||
|
||||
> wx 2026-08-10 实测反馈两处(TEST 订单 26-9313)。
|
||||
|
||||
## 问题 1:逐日行平铺重复(截图:同一辆车/司机出现 3 次 × 2 车 = 6 行)
|
||||
|
||||
DAILY_V3 配车快照是「逐服务日 × 车辆槽位」的明细(2 车 × 3 天 = 6 行数据,**数据本身正确**),前端把 `vehicleGroup.assignments[]` 每行直接平铺渲染。
|
||||
|
||||
**建议**:按(车牌/司机 或 assignmentId 前缀相同的槽)聚合成一张车卡,卡内展示服务日期范围或日期 chips。后端行含 `serviceDate`(逐日镜像)或 `startDate/endDate/serviceDays`(feign 聚合段),聚合信息足够,无需后端改动。
|
||||
|
||||
## 问题 2:换版后沿用车辆缺「沿用」语义(易被当成残留数据)
|
||||
|
||||
改用车需求(如 mpv×2 → mpv×1+suv×1)后,签名匹配的槽位**自动沿用**旧派车(SRS §9.18 #2 拍板:派单确认后是独立资产,不误撤已通知司机的派单)。此时详情呈现为「需求摘要=车控处理中」+ 一辆「已回配」的车并列,定制师会误以为是旧数据残留(wx 今日即如此反馈)。
|
||||
|
||||
**建议**:
|
||||
- 当前需求部分槽位已配、部分待配时,头部状态细化为「部分回配(1/2)」而非笼统「车控处理中」;
|
||||
- 沿用槽位的车卡加「沿用」小徽标(新需求版本仍在处理中、但该车延续自上一版方案)。
|
||||
|
||||
判定数据:`vehicleGroup.requirement.status=PROCESSING/PENDING` 且 `assignments` 非空 ⇒ 即「部分回配/沿用」形态(回配完成时 status=DONE)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **相关工单**: [#5784](https://git.1814.love:8443/wx/HL/issues/5784)(换版保留/沿用规则落地)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-board-chat-badge-realtime"
|
||||
title: "车务看板聊天角标不实时、需刷新才更新——后端 SSE 信令已齐(含 #5792 超管补口),前端待接"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端优化"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "no-op(前端已接,无需改动):fleet/board 的 SSE 角标链路 2026-07-23 已接入(commit 13c7c8bf/be326246)——index.vue watch(lastChatSignal) 对 im-chat/im-chat-read 防抖重拉并合并权威 unreadMessageCount,onFleetChatRead 本地读完即时 mergeFleetUnreadCount(orderId,0);SSE 由 BasicLayout 全局激活、身份无关;具名 im-chat/im-chat-read 事件经 useAdminMessageSSE.pushChatSignal 透传 conversationKey,getFleetChatOrderId 按 FLEET: 前缀匹配。报告的「不实时需刷新」根因是后端 #5792 前车务定向信令未推给超管/车务连接,属后端部署范畴,前端无新代码。验证两身份实时依赖 #5792 PR 合并部署后实测。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务看板: 聊天角标实时化与已读联动(前端接线)
|
||||
|
||||
> **背景**: 定制师给车务发消息,派单看板卡片「定制师 王骁」入口无实时角标提醒,要手动刷新才更新;房务侧已有 SSE 实时角标实现。
|
||||
> **Issue**: #5792(后端超管读态联动部分)
|
||||
|
||||
## 后端现状(信令已齐,无需新接口)
|
||||
|
||||
- 定制师发消息 → user-service 经 SSE 按 `targetRoleKey=VEHICLE_MANAGER` 广播 `im-chat` 信令(含 conversationKey=FLEET:{orderId}、团队未读数)。
|
||||
- 任一车务读 → 团队共享清零,广播 `im-chat-read`(conversationUnreadCount=0)。
|
||||
- **#5792 修复(本日)**:①超管在看板读消息后团队红点真正清零 + 定制师端「已送达→已读」回执;②面向车务角色的定向信令(上述两类 + `fleet-board-changed`)同样推给在线**超管**连接——此前超管连接收不到任何车务定向信令,即使前端接了订阅,超管身份打开看板也不会实时。
|
||||
- 看板列表行 `unreadMessageCount` 口径不变(团队共享未读)。
|
||||
|
||||
## 对前端的要求(mmg)
|
||||
|
||||
1. `fleet/board` 页面订阅 SSE `im-chat` / `im-chat-read`(chatSignalBus 已有房务接法可参照):
|
||||
- `im-chat`(conversationKey 匹配 FLEET:{orderId})→ 对应卡片角标 +1 或取信令内未读数;
|
||||
- `im-chat-read`(conversationUnreadCount=0)→ 对应卡片角标清零(他人读了也同步归零)。
|
||||
2. 打开会话读完后本地即时清零(不等信令回环)。
|
||||
3. 验证两种身份:VEHICLE_MANAGER 与 SUPER_ADMIN 登录看板均应实时(后者依赖 #5792 部署)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#5792](https://git.1814.love:8443/wx/HL/issues/5792)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端负责人**: @mmg
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5810"
|
||||
title: "换版槽位人工制:新增需求语句 requirementFleetText + 派车日期门禁 605062 + 槽位统一原样渲染(取代 #5788 全部旧数据形态)"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "b6f9c44a"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(b6f9c44a):删 RequirementSupersededCard 组件+utils/requirementSuperseded.js+两 spec,AssignModal/OrderDrawer 摘除标旧块/supersededSlots computed/删除守卫/样式;batchAssignmentSlots 删 superseded 合并/收集/返回(保留 canDelete/deleteBlockReason 合并),槽位统一以 vehicleSlots 为源原样渲染能力不降级;槽位头两处「建议{车型}」chip 删除(改派卡头+派车卡头,派车卡头保留「全程」标记),删 slotChangeRequirementLabel/slotRequiredVehicleLabel;新增 requirementFleetText 常驻需求语句(后端权威文案,null 不渲染),挂原 SupersededCard 位(弹窗级+详情级)。605062 实证:confirm 走全局拦截器透传 msg、batch 走 silentError+catch message.error(error.message),msg 已含引导语,统一透传即满足,前端零改动。保持 housekeeper requirementSuperseded 独立契约/BoardSlotSummary/司机建议/OrderDrawer「需求:」行。fleet/board 35 spec 427 全过,checkpoint 8 文件全绿。注:配套 11_5810b(格式改车队组成写法)与 11_frontend(Step2 需求展示区布局+统一选车弹窗空白缺陷)为独立待办,本字段原样渲染不解析、对格式变更零改动即受益。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务:换版槽位人工制 —— 需求语句 + 派车日期门禁 + 槽位统一原样渲染(#5810)
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187)
|
||||
> **PR**: #5815
|
||||
> **Issue**: #5810(取代 #5788 的前端契约)
|
||||
> **日期**: 2026-08-10 后端部署完成
|
||||
> **影响范围**: 派单看板订单详情 `GET /admin/fleet/board/orders/{orderId}`(新增 1 字段 + 3 个字段语义退化为恒空)、派车推进类接口新增错误码 605062
|
||||
> **一句话**: 换版后端不再自动增删槽位、不再区分新旧行,前端也不要再区分——**槽位有什么就原样画什么**;需求变成一句提示语;唯一会拦住车务的是「派车日期不在需求日期窗内」。
|
||||
|
||||
---
|
||||
|
||||
## 0. 先读这段:为什么推翻 #5788 的做法
|
||||
|
||||
#5788(后端 #5784/#5796/#5798/#5802)的老机制是:定制师改用车需求 → 后端按「车型+座位」签名匹配,**匹配上的旧派车行沿用、匹配不上的标 `superseded`(旧数据)、缺的自动补未派占位、多的自动取消**。于是页面上出现了「新槽位 + 旧数据行」两类东西,前端为了表达它反复返工三轮(窄条 → 卡片 → 单独区块),wx 每一轮都不满意。
|
||||
|
||||
**2026-08-10 wx 最终口径(本单实现的就是这个)**:
|
||||
|
||||
> 「槽位的数据配置啥就是啥,不自动清除数据,不自动增加/减少槽位。只在改出发日期的时候做日期判断,日期不对没法下一步。」
|
||||
|
||||
所以后端把整套「签名匹配 / 自动补位 / 自动取消 / superseded 标记」全部删掉,换成**整槽平移**:换版时把该订单下所有未删派车行(含已完成 completed,只排除 canceled)原封不动地重新绑定到新需求 ID 上——槽位数、车辆、司机、日期、价格、状态全不动,只是"户口"迁到新需求名下。存量带 `superseded` 标记的历史行也已由 Flyway 一次性迁移重绑完毕。
|
||||
|
||||
**给前端的直接含义**:**再也没有"旧数据行"这个概念了。** 所有槽位都是当前需求下的普通槽位,一视同仁地渲染成普通槽位卡。
|
||||
|
||||
---
|
||||
|
||||
## 1. 新增字段:`requirementFleetText`(需求语句)
|
||||
|
||||
### 接口
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` —— **根级新增**(不在 `vehicleSlots` 里):
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `requirementFleetText` | string | 是(需求不可达时 `null`) | 当前有效用车需求的车型语句 |
|
||||
|
||||
### 取值规则(后端已实现,前端直接展示即可,**不要自己再拼**)
|
||||
|
||||
- 格式:车型项按 `{车型中文标签}×{数量}({座位数}座)` 生成,多项用中文顿号 `、` 连接
|
||||
- 实例(测试服真实返回):
|
||||
- 26-9313 改需求后 → `"商务车×1(7座)、SUV×1(5座)"`
|
||||
- 单车型单辆 → `"商务车×1(7座)"`
|
||||
- 车型标签走字典 normalize,字典查不到时回退车型原始编码;数量 ≤0 按 1 计;座位数缺失时省略 `(N座)` 后缀
|
||||
|
||||
### 前端怎么用
|
||||
|
||||
1. **改版提示横幅**:文案 `需求已改版,请对照新需求人工调整` + 紧跟这句 `requirementFleetText`。
|
||||
⚠️ 注意:横幅的**触发条件不再是** `requirementChangePendingCount > 0`(该字段现在恒 0,见 §2)。是否显示横幅由前端自行决定(例如:本次进入派车页时对比上次看到的 `requirementVersion`,或干脆常驻显示"当前需求:xxx")。**推荐做法**:不做横幅态,直接在派车页/订单概览常驻一行「当前需求:商务车×1(7座)、SUV×1(5座)」,这样任何时候车务都能对照,不依赖任何"变更"状态。
|
||||
2. **订单概览**:同一句展示,位置见 wx 截图指向的区域(需求信息块)。
|
||||
3. `null` 时该行整体不渲染(不要显示"当前需求:null"或空冒号)。
|
||||
|
||||
---
|
||||
|
||||
## 2. ⚠️ 必须删除的旧渲染分支(#5788 遗留)
|
||||
|
||||
以下三个字段**契约保留不删**(兼容期,避免前端 500),但**语义已死**,值恒为空:
|
||||
|
||||
| 字段 | 位置 | #5810 后的值 | 前端处理 |
|
||||
|------|------|-------------|---------|
|
||||
| `requirementChangePendingCount` | 根级 | **恒 `0`** | 删掉依赖它的徽章/横幅触发逻辑 |
|
||||
| `supersededAssignments` | 根级 | **恒 `[]`** | 删掉整个"旧数据区块/列表"渲染 |
|
||||
| `vehicleSlots[].superseded` | 槽位 | **恒 `false`** | 删掉 `superseded ? 旧数据卡 : 普通卡` 的三元分支 |
|
||||
| `vehicleSlots[].supersededByRequirementId` | 槽位 | **恒 `null`** | 无用 |
|
||||
| `vehicleSlots[].requirementId` | 槽位 | 恒 == 根级 `requirementId` | 无需比对 |
|
||||
|
||||
**具体到 mmg 已写的代码**(按 #5788 三轮返工的产物):
|
||||
|
||||
- `SupersededSlotCard`(064d0be2 的旧数据槽位卡组件)→ **整个组件删除**
|
||||
- "标旧窄条"(a75efc05/b31ac7c3 的窄条渲染)→ **删除**
|
||||
- 任何 `slot.superseded === true` 的判断、任何把 `supersededAssignments` 单独成区/成行的代码 → **删除**
|
||||
- 槽位列表数据源:**只用 `vehicleSlots`**,按后端返回顺序原样渲染,每一项都是普通槽位卡(可派车/可换车/可取消/可删除,能力与普通槽位完全一致)
|
||||
|
||||
**验收标准**:改需求前后,页面槽位卡数量与内容**完全不变**(除非车务自己动手加/删/改)。26-9313 实测返回 3 个槽位(原商务车 E5555 行、原商务车 S6666 行、新 SUV 空槽),三个都是普通卡,没有任何"旧"标记。
|
||||
|
||||
---
|
||||
|
||||
## 3. ⚠️ 槽位头去掉「建议 {车型}」标签
|
||||
|
||||
槽位头当前显示的「建议 商务车」「建议 SUV」这类标签 —— **去掉**。
|
||||
|
||||
原因:槽位人工制下车务爱派什么车派什么车,后端不再按车型校验(车型/数量/人数不符**不拦截**,见 §4),"建议"标签只会让车务误以为必须匹配。车型诉求统一由 §1 的 `requirementFleetText` 在横幅/概览表达一次即可。
|
||||
|
||||
字段层面:`vehicleSlots[].requiredVehicleType` / `requiredVehicleTypeLabel` / `requiredSeats` **契约保留**(后端仍返回真实值,其它场景可能用到),但**不要再渲染成槽位头的"建议 xx"标签**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 新增错误码 605062:派车日期与需求不符(唯一硬门禁)
|
||||
|
||||
### 触发场景
|
||||
|
||||
定制师**改了出发日期/行程天数**后,需求的日期窗变了,但车务原来配的车还挂在旧日期上。此时车务想推进(发送给司机 / 确认执行 / 提交最终方案),后端拦截:
|
||||
|
||||
```json
|
||||
{ "code": 605062, "msg": "存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续", "data": null }
|
||||
```
|
||||
|
||||
### 哪些接口会返回
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` | 需求级确认执行(preflight + 锁内双重校验) |
|
||||
| `POST /admin/fleet/assignments/batch` | 批量提交最终实派方案(提交入参日期 + 既有在途行双向校验) |
|
||||
| 逐日方案提交(`batch` 走 `dailyPlan` 入参) | 同上 |
|
||||
|
||||
### 前端处理
|
||||
|
||||
1. `msg` 已是**完整可直接展示的中文**,直接弹给用户即可,不要自己另写文案。
|
||||
2. 建议在提示后附操作引导:`请在槽位列表中删除或改期日期不符的槽位后重试`。
|
||||
3. **不需要**前端预先算日期做本地拦截(后端是权威,且前端算容易与后端口径不一致);如果想给视觉提示,可用 `vehicleSlots[].serviceStartDate/serviceEndDate` 与订单 `departDate/endDate` 比对给个黄色角标,但**不要**据此禁用按钮。
|
||||
|
||||
### 不拦截的情况(明确告知,避免前端"帮倒忙"加校验)
|
||||
|
||||
- **加天数**(需求日期窗**扩大**):旧派车行日期仍落在扩大后的窗内 → **完全放行**,且**不强制车务把新增的天派满**
|
||||
- **车型/数量/人数与需求不符** → **不拦截**(降级为提示语句,即 §1 的 `requirementFleetText`)
|
||||
- 已完成(`completed`)的历史行日期越窗 → **不拦截**(终态行无法调整,不能卡死推进)
|
||||
|
||||
---
|
||||
|
||||
## 5. 行为变化速查(后端换版语义,供前端理解页面为什么这样)
|
||||
|
||||
| 场景 | #5788 老行为 | #5810 新行为 |
|
||||
|------|-------------|-------------|
|
||||
| 定制师改车型(2商务 → 1商务+1SUV) | 匹配上的沿用,匹配不上的标旧,缺的自动补占位 | **全部行原样平移**,槽位数不变;SUV 需求只体现在语句里 |
|
||||
| 定制师改日期 | 同上 + 旧日期行标旧 | 全部行原样平移(日期不动);推进时 **605062** 拦截,车务手动改期/取消后放行 |
|
||||
| 定制师加天数 | 自动补新天占位 | 不自动补;车务可自行补派新增天(不补也能推进) |
|
||||
| 定制师减车辆数 | 自动取消多余未派占位 | **不自动取消**;多出来的槽位由车务自己删(删除能力 #5572 已全放开) |
|
||||
| 旧存量 superseded 行 | 特殊形态 | Flyway 已一次性迁移重绑为普通行 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 联调数据(测试服可直接复现)
|
||||
|
||||
| 场景 | 订单 | 说明 |
|
||||
|------|------|------|
|
||||
| 改车型后的三槽位原样 | 团号 `26-9313`(orderId `2086270153414103041`) | 详情返回 `requirementFleetText="商务车×1(7座)、SUV×1(5座)"`,`vehicleSlots` 3 项、`superseded` 全 false、`supersededAssignments=[]` |
|
||||
| 605062 拦截 / 放行 | 订单 `2086697882311680002` | 已被本次实测改到 08-28~31 且已确认执行完毕;如需重现 605062,请改该单出发日期后再点确认执行 |
|
||||
|
||||
后端实测记录:改车型场景槽位零变动;改日期场景 confirm 返 605062、取消越窗行并窗内重派后 confirm 返 200;加天数场景不拦截,且新增天用他单占用车被 605001 正确拒绝、precheck 如实返回车+司机双 conflicts。
|
||||
|
||||
---
|
||||
|
||||
## 7. 不变的部分(无需改动)
|
||||
|
||||
- `vehicleSlots` 其余字段(`assignmentSlotId`/`fleetItemIndex`/`assignmentGroupId`/`slotStatus`/`canDelete` 等)语义全不变
|
||||
- 候选查询 `POST /admin/fleet/assignments/candidates` 的 `excludeAssignmentId` 语义不变(#5802 宽容口径保留:同订单不命中的排除项后端忽略并记 WARN,不再报"参数非法")
|
||||
- 派车/改派/取消/增删槽位所有既有接口契约不变
|
||||
- 网关路由无新增
|
||||
|
||||
---
|
||||
|
||||
## 8. 前端 checklist(做完请把 frontmatter 的 `frontend_status` 改为 `implemented` 并填 `frontend_ref`)
|
||||
|
||||
- [ ] 详情响应新增消费 `requirementFleetText`,在派车页/订单概览展示为一行需求语句(null 不渲染)
|
||||
- [ ] 删除 `SupersededSlotCard` 组件及所有"旧数据"分支渲染(窄条/区块/角标)
|
||||
- [ ] 删除对 `supersededAssignments`、`requirementChangePendingCount`、`slot.superseded` 的一切依赖
|
||||
- [ ] 槽位列表数据源统一为 `vehicleSlots`,按序原样渲染为普通槽位卡,能力(派车/换车/取消/删除)不因来源不同而降级
|
||||
- [ ] 槽位头去掉「建议 {车型}」标签
|
||||
- [ ] 派车推进类接口(confirm / batch)接住 `605062`,直接展示后端 `msg` + 操作引导
|
||||
- [ ] 不新增任何前端侧日期硬拦截(不 disable 按钮)
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5810"
|
||||
title: "需求车型语句 requirementFleetText 格式调整:座位内联 + 加号连接(商务车7座×1 + SUV5座×1)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端实证 not_required(2026-08-11,mmg):本条仅后端 requirementFleetText 拼接格式变化(座位内联+加号连接),字段名/类型/位置/可空性全不变,接入方式零变化(原样显示)。grep 实证前端两消费点(AssignModal.vue:791、OrderDrawer.vue:115)均 `当前需求:{{ requirementFleetText }}` 原样渲染 + v-if 防空(null 不渲染),computed 纯 `String(...??'').trim()` 透传,零解析/零自拼(无 split/×/、/+ 处理),三条红线(不解析/不自拼/null 不渲染)全满足,后端新格式自动呈现。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 需求车型语句格式调整(#5810 展示口径跟进,PR #5817)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5817(已合并 dev-v3,已部署测试服)
|
||||
> **日期**: 2026-08-11
|
||||
> **关联**: `11_5810_换版槽位人工制-需求语句与派车日期门禁-修改接口-管理后台.md` §1 的格式部分被本条取代;配套布局要求见 `11_frontend_派单Step2需求展示区与统一选车弹窗空白-前端缺陷-管理后台.md` §3
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` 根级字段 `requirementFleetText`,**仅格式变化,字段名/类型/位置/可空性全不变**:
|
||||
|
||||
| | 拼接规则 | 26-9313 实例 |
|
||||
|---|---------|-------------|
|
||||
| 改前 | `{车型}×{数量}({座位}座)`,中文顿号 `、` 连接 | `商务车×1(7座)、SUV×1(5座)` |
|
||||
| **改后** | `{车型}{座位}座×{数量}`,**` + `**(空格加号空格)连接 | **`商务车7座×1 + SUV5座×1`** |
|
||||
|
||||
座位数内联到车型后、多项用加号连接,对齐车务口头表述车队组成的习惯写法,一眼读出"这单要几台什么车"(wx 2026-08-11 定稿)。
|
||||
|
||||
**兜底语义完全不变**:车型标签走归一字典(未知车型回退原始编码)、`count` 为空或非正按 1、`seats` 为空时省略座位段(形如 `SUV×2`)、需求不可达时整字段为 `null`。
|
||||
|
||||
## 前端影响
|
||||
|
||||
**接入方式零变化** —— 本字段的用法一直是「后端给什么就原样显示什么」:
|
||||
|
||||
- ✅ 直接把字符串渲染出来即可
|
||||
- 🔴 **不要解析这个字符串**(不要按 `×`/`、`/`+` 切分取车型或数量)
|
||||
- 🔴 **不要自己按 `vehicleSlots` 或需求明细拼**——拼法口径归后端一处收口,否则日后改口径要改两边
|
||||
- `null` 时整块不渲染(不要出现「当前需求:」空冒号或「当前需求:null」)
|
||||
|
||||
展示位置见配套 changelog:派单弹窗 Step2「排车」页槽位表格下方常驻一行,形如 `当前需求:商务车7座×1 + SUV5座×1`。
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 09:4x,PR #5817 合并 dev-v3 → Deploy Panel 部署 hl-fleet-service(exit_code=0)→ SSH 确认进程 09:40 重启 → 网关 `https://api.test.1814.love:9443` 车务管理员 token 实测:
|
||||
|
||||
| 订单 | `requirementFleetText` 实测值 |
|
||||
|------|------------------------------|
|
||||
| 26-9313(两车型混合) | `商务车7座×1 + SUV5座×1` ✅ |
|
||||
| 26-3698 | `商务车7座×2` |
|
||||
| 26-0821 | `商务车7座×2` |
|
||||
| 26-3420 / 26-9452 / 26-7944 | `SUV5座×1` |
|
||||
| 26-4789 / 26-6575 / 26-3281 | `商务车7座×1` |
|
||||
|
||||
单测:`BoardOrderServiceTest` 4 个 requirementFleetText 用例全绿(常规两项 / 空需求返 null / 缺座位缺数量兜底 / 未知车型回退原始值);fleet 全量 3382 tests 除存量环境依赖用例外全绿;`FleetRedLineArchTest` 12 tests 绿。
|
||||
@@ -0,0 +1,330 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5816"
|
||||
title: "主报账对账 transferStatus 彻底下线:报账表出参删字段,recon 读/写路径正式声明下线(404)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "b22d6c14"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(b22d6c14):实证 transferStatus 在视图层零引用、getSettlementRecon/saveSettlementRecon 无生产调用方(转账状态流转不再走核单链路),删 orderV2.js 这两个 recon 死导出及 jsdoc,改下线注记;reconNetAmount 等派生金额由报账表接口透出不受影响,报账表 jsdoc 补 #5816 出参再删 transferStatus 说明。软破坏(少字段不报错),前端不读即兼容。orderV2.spec 13 全过,checkpoint 单文件全绿(ESLint/Vitest 全量/生产构建)。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】主报账对账 transferStatus 彻底下线:报账表出参删 transferStatus,recon 读/写路径正式声明下线(#5816)
|
||||
|
||||
> **PR**: [#5826](https://git.1814.love:8443/wx/HL/pulls/5826) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-11
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
主报账对账(recon)的「转账状态」(待转账 PENDING / 已转账 COMPLETED)此前挂在核单链路维护:主报账人报账表出参带 transferStatus,配套有 recon 读 / 写两个接口。产品上决定转账状态流转不再由核单链路承载,本次把 transferStatus 从对外契约彻底摘除,并清理 recon 幽灵接口的残留死代码:
|
||||
|
||||
- **主报账人报账表**(GET reports/reimbursement):出参删除 transferStatus 字段(#5809 批次删凭据字段时该字段曾保留,本次一并下线);
|
||||
- **recon 写接口**(PUT /v3/admin/order/{orderId}/settlement/recon):正式声明下线。该路径自 2026-07-22 核单接口收口起已无服务端入口,本次清理其入参类与写路径死代码,**调用返回 404**;
|
||||
- **recon 读接口**(GET /v3/admin/order/{orderId}/settlement/recon):同样早已无服务端入口(404),其派生金额数据(reconNet 等)仍通过报账表接口内嵌透出,不受本次变更影响。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 出参删 transferStatus | 停读该字段;清理转账状态展示 / workaround |
|
||||
| 2 | 主报账对账保存 | PUT | /v3/admin/order/{orderId}/settlement/recon | 接口下线(早已无入口,本次清死代码正式声明) | 停调;调用返回 404 |
|
||||
| 3 | 主报账对账查询 | GET | /v3/admin/order/{orderId}/settlement/recon | 接口下线(同上,早已无入口) | 停调;调用返回 404 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **使用场景**:管理后台核单页「主报账人报账表」弹窗,展示主报账人收付对账与净额结论(reconNetAmount 等派生金额保留不变)。
|
||||
- **认证**:需要管理后台登录态(Bearer Token);房务角色(HOUSE)被 OrderViewGuard 拦截。
|
||||
- **幂等性**:只读查询,天然幂等。
|
||||
- **限流**:未声明接口专属限流。
|
||||
- **网关**:无网关路由变更。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
入参零变化。
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long | 是 | 订单 ID,路径参数,必须大于 0 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
GET /v3/admin/order/{orderId}/settlement/reports/reimbursement,统一响应 Result 包装,data 字段如下(仅列关键字段 + 本次删除项):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String(Long) | 报表记录 ID;未落库时可为 null |
|
||||
| orderId | String(Long) | 订单 ID |
|
||||
| reportStatus | String | 报表状态:GENERATED / CONFIRMED |
|
||||
| primaryReporterId | String(Long) | 主报账人人员安排 ID |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| reportVersion | Integer | 报表版本号 |
|
||||
| driverCollectedTailAmount | String(BigDecimal) | 司机代收尾款 |
|
||||
| approvedAdvanceAmount | String(BigDecimal) | 已审批预支金额 |
|
||||
| reportablePaidCostAmount | String(BigDecimal) | 可报账已付成本 |
|
||||
| reporterNetAmount | String(BigDecimal) | 报账人净额 |
|
||||
| primaryReporterCollectedAmount | String(BigDecimal) | 主报账人已收款 |
|
||||
| publicPrepaidAmount | String(BigDecimal) | 对公预付金额 |
|
||||
| primaryReporterDueAmount | String(BigDecimal) | 主报账人应结金额 |
|
||||
| advanceOutstandingAmount | String(BigDecimal) | 预支未销余额 |
|
||||
| reconNetAmount | String(BigDecimal) | 对账净额(保留不变) |
|
||||
| transferDirection | String | 转账方向 |
|
||||
| transferAmount | String(BigDecimal) | 转账金额 |
|
||||
| incomeLines | Array | 收款行;每项含 type / typeName / receiptId / amount / channel / channelName / payType / payTypeName / collectorStaffId / collectorName / collectorRole / collectorRoleName / receivedAt / remark |
|
||||
| expenseLines | Array | 支出行;每项含 kind / category / categoryName / amount / paymentMethod / paymentMethodName / voucherUrls / remark + 类别扩展字段 |
|
||||
| advanceLines | Array | 预支行 |
|
||||
| vehicleLines | Array | 已确认车辆逐日费用明细;无数据固定返回空数组 |
|
||||
| ~~transferStatus~~ | - | **已删除**,前端不再收到此字段(不是返回 null,是字段不存在) |
|
||||
| generatedBy / generatedByName / generatedAt | - | 生成人 ID / 姓名 / 时间 |
|
||||
| confirmedBy / confirmedByName / confirmedAt | - | 确认人 ID / 姓名 / 时间 |
|
||||
|
||||
> 除删除 transferStatus 外,其余字段名称、类型、语义均无变化。金额 / 收款派生字段(reconNetAmount、driverCollectedTailAmount 等)保留不变。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### transferStatus(本次从对外契约彻底消失)
|
||||
|
||||
| 值 | 原含义 | 本次变化 |
|
||||
|---|---|---|
|
||||
| PENDING | 待转账 | **随字段一起从出参消失**,不再有任何接口返回 |
|
||||
| COMPLETED | 已转账 | **随字段一起从出参消失**,不再有任何接口返回 |
|
||||
|
||||
其余枚举/字典(reportStatus、incomeLines[].type、channel、payType、paymentMethod、transferDirection 等)取值与语义均无变化。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 本次变化 |
|
||||
|---|---|---|
|
||||
| 404 | 路径不存在 | **旧 recon 路径触发**:PUT / GET /v3/admin/order/{orderId}/settlement/recon 早已无服务端入口,调用返回 404 |
|
||||
| - | 原 transferStatus 校验相关错误码 | **不再触发**(码位保留下线、不复用,对外契约不删号) |
|
||||
|
||||
其余既有错误码(订单不存在、双报告未生成保护 584311 / 584313、房务角色 403 等)行为不变。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(报账表出参已无 transferStatus)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002978/settlement/reports/reimbursement
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "8802",
|
||||
"orderId": "2084000000000002978",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"reportVersion": 2,
|
||||
"driverCollectedTailAmount": "500.00",
|
||||
"approvedAdvanceAmount": "200.00",
|
||||
"reportablePaidCostAmount": "300.00",
|
||||
"reporterNetAmount": "-600.00",
|
||||
"primaryReporterCollectedAmount": "500.00",
|
||||
"publicPrepaidAmount": "300.00",
|
||||
"primaryReporterDueAmount": "600.00",
|
||||
"advanceOutstandingAmount": "200.00",
|
||||
"reconNetAmount": "-600.00",
|
||||
"transferDirection": "COMPANY_TO_REPORTER",
|
||||
"transferAmount": "600.00",
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "DRIVER_CASH",
|
||||
"typeName": "司机代收",
|
||||
"receiptId": "9001",
|
||||
"amount": "500.00",
|
||||
"channel": "CASH",
|
||||
"channelName": "现金",
|
||||
"payType": "TAIL",
|
||||
"payTypeName": "尾款",
|
||||
"collectorStaffId": "7001",
|
||||
"collectorName": "司机甲",
|
||||
"collectorRole": "DRIVER",
|
||||
"collectorRoleName": "司机",
|
||||
"receivedAt": "2026-08-10 15:20:30",
|
||||
"remark": null
|
||||
}
|
||||
],
|
||||
"expenseLines": [],
|
||||
"advanceLines": [],
|
||||
"vehicleLines": [],
|
||||
"generatedBy": "1001",
|
||||
"generatedByName": "张三",
|
||||
"generatedAt": "2026-08-10 10:20:30",
|
||||
"confirmedBy": "1001",
|
||||
"confirmedByName": "张三",
|
||||
"confirmedAt": "2026-08-11 09:00:00"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 响应中已无 transferStatus 字段(不是返回 null,是字段不存在)。
|
||||
|
||||
### 8.2 边界(无收款 / 无报表记录,出参仍无 transferStatus)
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2084000000000002999/settlement/reports/reimbursement
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": null,
|
||||
"orderId": "2084000000000002999",
|
||||
"reportStatus": "GENERATED",
|
||||
"primaryReporterId": null,
|
||||
"primaryReporterName": null,
|
||||
"primaryReporterRole": null,
|
||||
"reportVersion": 1,
|
||||
"driverCollectedTailAmount": "0.00",
|
||||
"approvedAdvanceAmount": "0.00",
|
||||
"reportablePaidCostAmount": "0.00",
|
||||
"reporterNetAmount": "0.00",
|
||||
"primaryReporterCollectedAmount": "0.00",
|
||||
"publicPrepaidAmount": "0.00",
|
||||
"primaryReporterDueAmount": "0.00",
|
||||
"advanceOutstandingAmount": "0.00",
|
||||
"reconNetAmount": "0.00",
|
||||
"transferDirection": null,
|
||||
"transferAmount": "0.00",
|
||||
"incomeLines": [],
|
||||
"expenseLines": [],
|
||||
"advanceLines": [],
|
||||
"vehicleLines": [],
|
||||
"generatedBy": null,
|
||||
"generatedByName": null,
|
||||
"generatedAt": null,
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> 全空 / 零金额边界下同样不返回 transferStatus。
|
||||
|
||||
### 8.3 业务失败(调用旧 recon 写路径返回 404)
|
||||
|
||||
```http
|
||||
PUT /v3/admin/order/2084000000000002978/settlement/recon
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"transferStatus": "COMPLETED"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code": 404, "message": "请求路径不存在", "data": null, "success": false}
|
||||
```
|
||||
|
||||
> 旧写路径(PUT /recon)与旧读路径(GET /recon)均已无服务端入口,任何调用一律 404。前端若残留「保存转账状态」逻辑需整体移除。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ 报账表弹窗展示核算数据 + 转账方向 / 金额结论(transferDirection / transferAmount 保留),不再有「转账状态」展示位。
|
||||
- ✅ reconNet 等金额 / 收款派生逻辑保留不变,报账表数据口径与上一版一致。
|
||||
- ❌ 不要在前端保留 transferStatus 的读取、展示(待转账 / 已转账标签、下拉框)或本地缓存。
|
||||
- ❌ 不要调用 PUT / GET /v3/admin/order/{orderId}/settlement/recon,两条路径均已 404。
|
||||
- ❌ 订单历史 transferStatus 数据仍在库中(零 DDL,列未删),但接口不再返回;不要依赖任何接口读取历史转账状态。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 接口 | 字段 | 原来 | 现在 |
|
||||
|---|---|---|---|
|
||||
| GET reports/reimbursement | transferStatus | 出参(PENDING / COMPLETED) | **已删除**,其余出参不变 |
|
||||
| PUT /settlement/recon | 整个接口 | 保存转账状态(入参 transferStatus) | **已下线**,调用返回 404 |
|
||||
| GET /settlement/recon | 整个接口 | 查询对账 + 转账状态 | **已下线**,调用返回 404(派生金额改由报账表接口透出) |
|
||||
|
||||
### 10.2 出参 JSON 对照(transferStatus 删前 / 删后)
|
||||
|
||||
删前(旧版响应尾部片段):
|
||||
|
||||
```json
|
||||
{
|
||||
"reconNetAmount": "-600.00",
|
||||
"transferDirection": "COMPANY_TO_REPORTER",
|
||||
"transferAmount": "600.00",
|
||||
"vehicleLines": [],
|
||||
"transferStatus": "PENDING",
|
||||
"generatedBy": "1001"
|
||||
}
|
||||
```
|
||||
|
||||
删后(新版响应同位置片段):
|
||||
|
||||
```json
|
||||
{
|
||||
"reconNetAmount": "-600.00",
|
||||
"transferDirection": "COMPANY_TO_REPORTER",
|
||||
"transferAmount": "600.00",
|
||||
"vehicleLines": [],
|
||||
"generatedBy": "1001"
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 行为级对比
|
||||
|
||||
| 场景 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 报账表读取转账状态 | 出参带 transferStatus | 字段不存在,读取恒为 undefined |
|
||||
| 保存转账状态 | PUT /settlement/recon | 接口 404,无替代接口(该能力整体下线) |
|
||||
| 查询对账详情 | GET /settlement/recon | 接口 404;派生金额从报账表接口读取 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:**软破坏**。出参少一个字段不会导致请求失败(区别于 #5809 入参删字段传了会 400),但前端若依赖 transferStatus 做展示 / 判断会拿到 undefined。
|
||||
- **前端是否必须同步上线**:**建议同批**。前端需删除 transferStatus 相关展示与逻辑,并对 recon 旧路径的残留调用做清理(调用即 404)。
|
||||
- **上线顺序边界**:先后端上前端无报错风险(仅少字段);若前端先删引用、后端后上,旧后端仍返回 transferStatus,前端不读即可,两个方向均不阻塞。
|
||||
- **数据库侧**:**零 DDL**,transfer_status 列保留在库,无数据迁移成本。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 代码回滚即恢复出参 transferStatus;数据库无变更,历史数据完整,回滚无数据修复成本。
|
||||
- 前端已删引用的前提下回滚后端,出参会多一个字段,前端不读不受影响。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 本次出参删字段与 #5809(入参删字段传了 400)不同:**不会因为字段缺失而报错**,前端唯一的坑是继续读 transferStatus 得到 undefined。
|
||||
- 转账状态维护能力整体下线,**没有替代接口**;产品上该流程不再走核单链路。
|
||||
- transferStatus 相关历史错误码码位保留但不再触发(对外契约不删号),前端错误码映射表可保留无需清理。
|
||||
- 小程序端(/v3/mp/*)不涉及本次变更,无需任何改动。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5816](https://git.1814.love:8443/wx/HL/issues/5816)
|
||||
- **PR**: [#5826](https://git.1814.love:8443/wx/HL/pulls/5826)
|
||||
- **Commit**: [253dc795e](https://git.1814.love:8443/wx/HL/commit/253dc795e)
|
||||
- **相关批次**: Issue [#5809](https://git.1814.love:8443/wx/HL/issues/5809) / PR [#5813](https://git.1814.love:8443/wx/HL/pulls/5813)(完成核单去凭据,changelog 见 2026-08/10_5809)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu (yst) 腰苏图
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5818"
|
||||
title: "车务全量代码审计整改:保险错误码迁段(540033-540037→601200-601204)、新增多个业务码、对账/看板响应结构与数值变化、若干入参新增校验"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "46a57557"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "PR #5837 已合并 dev-v3 并部署测试服,全量 3485 用例 0 失败、ArchTest 12/12、Flyway 迁移实测通过(540033 残留 0 / 601200 恰好 7 行)。本条汇总一轮全量审计整改中**所有前端可见**的变化,按「必须改」「按需改」「只需知晓」三档排列,逐条给了触发条件与处置口径。前端已实现(46a57557):实证后 A1 保险错误码(前端零引用)/A2 dayPrice(仅展示经 formatPrice 内部 Number 安全)/A5 到期看板(前端固定白名单传值)/B3 opType(无下拉)/B4 priceSource(DAILY_FEE_SOURCES 已含 FREE)/B5(纯后端)零改动;实际落地子集——A6 模板编辑 isDefault 恒参与 diff 对比、submit 写接口前按编辑态裁剪(编辑未变省略避免 600804,顺带修复仅切换默认被无变化短路误拦);A3/A4 useAssignFlow 新增 605063(回执损坏禁自动重试)/605064(全程槽逐日部分免费)专用提示;B1 对账行标识改后端权威 fleet 字段(兜底行 fleetTeamId 可为 null,旧数据回退)并抽 reconAdapter 纯函数;B2 保险分来源金额优先读 manualAmount/baoyouAmount+sourceSubtotals(消除同司机跨来源矛盾行,兼容旧响应回退);B6 清 CAPACITY_INSUFFICIENT/passengerCapacity/capacityGap 死契约。测试:reconAdapter.spec 6+EditModal.spec 4+useAssignFlow final-confirm +2+baselineDifference 同步,fleet 全域 66 文件 618 用例全绿,checkpoint 10 文件含生产构建全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务:全量代码审计整改的前端契约变化(#5818 / PR #5837)
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187)
|
||||
> **PR**: #5837
|
||||
> **Issue**: [#5818](https://git.1814.love:8443/wx/HL/issues/5818)
|
||||
> **背景**: 对 hl-fleet-service(740 文件约 10 万行)做了一轮规范/质量/逻辑三维全量审计并整改(确认 151 条缺陷,修复 127 条 + CR 整改)。绝大多数是后端内部修复,但有以下几类**前端能看见**的变化。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口清单
|
||||
|
||||
| 接口 | 变化类型 | 见下文 |
|
||||
|------|---------|--------|
|
||||
| `GET /admin/fleet/insurance/tasks` | `errorCode` 值域迁段(存量数据一并改写) | A1 |
|
||||
| `GET /admin/fleet/vehicles/options` | `dayPrice` number → string | A2 |
|
||||
| `POST /admin/fleet/assignments/{assignmentId}/confirm` | 新增错误码 605063 | A3 |
|
||||
| `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` | 新增 605063、首次给出完整错误码表(605059/605062/605063);`dailyDifferences[]` 删 2 字段 | A3 / B6 |
|
||||
| `POST /admin/fleet/assignments` | 新增错误码 605064 | A4 |
|
||||
| `POST /admin/fleet/assignments/batch` | 新增错误码 605064 | A4 |
|
||||
| `POST /admin/fleet/assignments/{assignmentId}/change` | 新增错误码 605064 | A4 |
|
||||
| `GET /admin/fleet/board/expiry` | `kinds`/`buckets` 非法值由静默忽略改为 100001 | A5 |
|
||||
| `PUT /admin/fleet/message-templates/{id}` | `isDefault` 语义变化 + 新增 600804 | A6 |
|
||||
| `GET /admin/fleet/reconciliation/cars` | `grandTotal.actual`/`diff` 数值变化;`fleets[]` 可能出现兜底行 | B1 |
|
||||
| `GET /admin/fleet/reconciliation/insurance` | 数值变化;`drivers[]` 新增分来源金额字段 | B2 |
|
||||
| `GET /admin/fleet/reconciliation/pending-compensations` | `opType` 白名单补 `INSURANCE` | B3 |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` | `priceSource` 新增 `FREE`;混态派车组展示区间修正 | B4 / B5 |
|
||||
| `POST /admin/fleet/h5/tokens` | `expireDays` 新增 1-90 上限 | C1 |
|
||||
| 操作时间线查询 | `page` 新增 100000 上限 | C1 |
|
||||
| `POST` / `PUT /admin/fleet/vehicles` | `primaryDriverId` 新增存在性校验(600205) | C2 |
|
||||
| `POST /admin/fleet/insurance/tasks/{taskId}/retry` | 补登记 601210-601215(原返 100500) | C3 |
|
||||
| `POST /admin/fleet/drivers/{driverId}/insurance/purchase`、`/insure` | 降级时由 100500 改返 605601 | C3 |
|
||||
| `POST /admin/fleet/driver-pending/{pendingId}/approve` | 删 605022,新增 600205 / 600210 | C3 |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 A. 必须改(不改会踩坑)
|
||||
|
||||
### A1. 保险任务 `errorCode` 值域迁段:540033-540037 → 601200-601204
|
||||
|
||||
**接口**:`GET /admin/fleet/insurance/tasks`(列表)响应 `errorCode` 字段。
|
||||
|
||||
**原因**:车务此前在 order 服务的 5xxxxx 段私铸错误码,其中 **540033 / 540034 与 hl-order-service-v3 已注册的真码撞号且语义完全不同**(order 侧 540033 是「被保人缺少出生日期」、540034 是「产品已下架/不可售」)。现已迁到 fleet 自有的 601200-601299 段。
|
||||
|
||||
| 旧码 | 新码 | 含义 |
|
||||
|------|------|------|
|
||||
| 540033 | **601200** | 司机年保未覆盖服务日 |
|
||||
| 540034 | **601201** | 保险台账与司机年保档案不一致,需人工核对 |
|
||||
| 540035 | **601202** | 退保后保障状态待复核 |
|
||||
| 540036 | **601203** | 司机保险档案在处理期间已变更 |
|
||||
| 540037 | **601204** | 历史取消事件缺少取消前状态 |
|
||||
|
||||
⚠️ **存量数据已随 Flyway 迁移一并改写**(测试服实测:迁移后 540033-540037 残留 0)。所以**发版当天所有历史待处理行的码会同时跳变**。
|
||||
|
||||
**前端处置**:若在任何地方硬编码过 540033-540037 做分支、文案映射或埋点,必须同步改成 601200-601204。
|
||||
|
||||
⚠️ **注意 `errorCode` 这一列是两个来源合用**:601200-601204 是车务自有的失败原因码,其余是保险服务上游业务码原样透传(如 540007 无匹配费率)。**不能按段位反推服务归属,也不能把它当 HTTP 响应码用。**
|
||||
|
||||
### A2. 车辆下拉 `dayPrice` 由 JSON number 改为 string
|
||||
|
||||
**接口**:`GET /admin/fleet/vehicles/options` 的 `dayPrice`。
|
||||
|
||||
`450.00`(number)→ `"450.00"`(string),与 `VehicleModelRespVO.basePrice` 口径统一(金额统一字符串下发,避免 JS 浮点精度问题)。
|
||||
|
||||
**前端处置**:参与计算前必须 `Number(dayPrice)`。否则 `dayPrice * days` 会退化成字符串拼接(`"450.00450.00"`)、`dayPrice.toFixed(2)` 直接 TypeError、`dayPrice > 0` 变成字符串比较。
|
||||
|
||||
### A3. 派车确认新增错误码 605063(不可自愈终态,**禁止自动重试**)
|
||||
|
||||
**接口**:`POST /admin/fleet/assignments/{assignmentId}/confirm` 与 `POST /admin/fleet/assignments/requirements/{requirementId}/confirm`。
|
||||
|
||||
```json
|
||||
{ "code": 605063, "msg": "原子确认回执已损坏,无法幂等重放,请联系管理员" }
|
||||
```
|
||||
|
||||
原来这个场景走全局兜底返 `100500 系统繁忙`,前端会落 default 分支甚至无脑重试。
|
||||
|
||||
**前端处置**:**这是不可自愈终态**——同 requestId 重试永远返回同码。不得自动重试、不得静默轮询,直接提示用户联系管理员人工处理。
|
||||
|
||||
另:需求级确认端点 `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 首次给出完整错误码表,前端若之前只处理 605062,需补 **605059**(同 requestId 用于不同确认内容,需换新 requestId)与 **605063**。
|
||||
|
||||
### A4. 全程槽「逐日部分免费」新增专用错误码 605064
|
||||
|
||||
**接口**:`POST /admin/fleet/assignments`(单派)、`POST /admin/fleet/assignments/batch`、`POST /admin/fleet/assignments/{assignmentId}/change`。
|
||||
|
||||
```json
|
||||
{ "code": 605064, "msg": "全程槽不支持逐日部分免费,请整程统一计费或改用逐日派车方案" }
|
||||
```
|
||||
|
||||
**触发条件**:对一个**全程槽**(一条行覆盖整个服务期)提交的 `chargeableServiceDates` 既不是全部服务日、也不是空集合,而是真子集。
|
||||
|
||||
**原因(重要)**:修复前这种输入会被静默算错——按首日单点判定后整行写入,首日免费就导致**整程车费归零**(资损)。现在明确拒绝。
|
||||
|
||||
**前端处置**:给该码专用提示,引导用户「整程统一计费」或「改用逐日派车方案」;该码**不可自动重试**(同参数必然同码)。若前端有按 `code == 100001` 的通用参数错误提示分支,此场景会不再命中它。
|
||||
|
||||
### A5. 到期看板 `kinds` / `buckets` 非法值由静默忽略改为报错
|
||||
|
||||
**接口**:`GET /admin/fleet/board/expiry`。
|
||||
|
||||
传白名单外的值原来是「静默忽略、返四类全量」,现在直接返 `100001`。
|
||||
|
||||
- `kinds` 合法值:`inspect` / `vehInsure` / `license` / `driverInsure`
|
||||
- `buckets` 合法值:`expired` / `urgent` / `soon` / `watch` / `ok`
|
||||
- **大小写敏感**(传 `LICENSE` / `Expired` 会报错)
|
||||
|
||||
**为什么这么改**:静默忽略会让「筛选不生效却返回全量」被误读成「该类目下真有这么多」。这是对齐 #5455 已定案口径——列表接口的 `statuses` / `vehicleTypeKeys` 本来就是拒绝非法值,到期接口是唯一例外。
|
||||
|
||||
### A6. 消息模板编辑 `isDefault` 语义变化 + 新增 600804
|
||||
|
||||
**接口**:`PUT /admin/fleet/message-templates/{id}`。
|
||||
|
||||
- `isDefault` 语义由「不传 = 非默认」改为「**编辑不传 = 保持原值**」(新增仍是不传 = 非默认)。
|
||||
- 新增错误码 **600804**「默认模板不可取消默认」(与 delete 的 600803 对称)。
|
||||
|
||||
**前端处置**:若编辑表单里始终回传 `isDefault: false`,取消默认会从「静默成功」变为 600804 报错。请改为只在用户真的切换时才传该字段。
|
||||
|
||||
---
|
||||
|
||||
## 🟡 B. 按需改(数值/结构变化,看你怎么渲染)
|
||||
|
||||
### B1. 对账「车队」Tab 数值与结构变化
|
||||
|
||||
**接口**:`GET /admin/fleet/reconciliation/cars`
|
||||
|
||||
1. **`grandTotal.actual` 与 `diff` 的数值会变**:修复前从 `fleets[]` 累加,漏计了 `team_no` 为空的核单车费行与「本期已无 active prep 但有核单车费」的车队;现在对费用行全量求和。财务侧会看到**历史月份合计变大、diff 由偏负回正**——这是修 bug 不是回归。
|
||||
2. **`fleets[]` 可能出现「兜底车队行」**:`fleet="unknown"` / `fleetName="未归属车队"`(team_no 缺失桶),或 `fleet=` 真实 teamCode(本期无 active prep 但有核单车费)。这类行 `vehicles=[]`、`estimatedTotal=payableTotal="0.00"`、`actualTotal`/`diff` 有值;能反查到车队的已补齐 `fleetTeamId`/`fleetType`/`settleType`/`settleMode`,真无主数据的 `unknown` 桶 `fleetTeamId` 仍为 null。
|
||||
**前端处置**:**行 key 请用 `fleet` 字段,不要用 `fleetTeamId`**;按 `settleMode` 分列渲染时要容忍兜底行。
|
||||
注:只在**未传** `fleets`/`fleetTeamIds` 时出现;显式筛车队时行为不变(且此时 `grandTotal.actual` 只统计被选中车队,比修复前更严格)。
|
||||
|
||||
### B2. 对账「保险」Tab 数值与结构变化
|
||||
|
||||
**接口**:`GET /admin/fleet/reconciliation/insurance`
|
||||
|
||||
1. **数值会变**:修复前用「该司机在区间内最早那一行」的 source 代表全月,导致逐日投保的司机整月被渲染成「无保险」且金额不入小计。现在逐行按 `driver_insurance_source` 分组求和。`fleets[].sourceSubtotals.manual/baoyou`、`grandTotal`、`typeCounts`、`drivers[]` 的多个字段**数值与取值都会变**。
|
||||
2. **`drivers[]` 新增分来源金额字段**(`manualAmount` / `baoyouAmount`),`insuranceAmount` 保留为合计。这样「Σ drivers 按 source 分桶 == sourceSubtotals」可对账(修复前同司机跨来源会出现 `source=BAOYOU` 但金额含 MANUAL 的矛盾行)。
|
||||
|
||||
**建议**:部署后拉一个已有数据的月份做前后对比截图给财务确认。
|
||||
|
||||
### B3. 待补偿列表 `opType` 接受 `INSURANCE`
|
||||
|
||||
**接口**:`GET /admin/fleet/reconciliation/pending-compensations`
|
||||
|
||||
`opType` 白名单补齐 `INSURANCE`(此前传 `INSURANCE` 返 400,而该值确实会被写入)。前端下拉可补该项:`GENERATE` / `INVALIDATE` / `REACTIVATE` / `TRUNCATE` / `INSURANCE`。
|
||||
|
||||
### B4. 派车板逐日车费 `priceSource` 新增 `FREE`
|
||||
|
||||
**接口**:`GET /admin/fleet/board/orders/{orderId}` 的 `dailyVehiclePlan[].priceSource`
|
||||
|
||||
免费服务日此前被**错标为 `OVERRIDE`**(因为一个 `@TableField(exist=false)` 的字段恒为 null 导致整段判断是死分支),现在正确返回 `FREE`,且 `priceAdjustmentReason` 在 `FREE` 时返回免费豁免原因而非恒 null。金额不变(免费日仍 0.00)。
|
||||
|
||||
### B5. 派车板混态派车组的展示区间修正
|
||||
|
||||
**接口**:`GET /admin/fleet/board/orders/{orderId}`(组视图 / 槽位卡)
|
||||
|
||||
多日派车组在行程进行中是天然混态(前几天已完结、后几天在途)。修复前展示区间只按「主状态行」收窄,却带着全程的逐日车费明细,自相矛盾(区间 1 天却有 5 条逐日车费)。现在区间与车费明细同源,**逐日车费条数会变多(变正确)**,无字段结构变化。
|
||||
|
||||
### B6. 派车确认差异契约删字段
|
||||
|
||||
**接口**:`POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 的 `data.dailyDifferences[]`
|
||||
|
||||
删除 `passengerCapacity` / `capacityGap` 两字段,`differenceType` 的取值去掉 `CAPACITY_INSUFFICIENT`——三者自 #5810 起后端已永远不产出,属死契约。
|
||||
|
||||
---
|
||||
|
||||
## 🟢 C. 只需知晓(新增入参校验 / 错误码登记)
|
||||
|
||||
### C1. 新增入参上限
|
||||
|
||||
| 接口 | 字段 | 新增校验 | 影响 |
|
||||
|------|------|---------|------|
|
||||
| `POST /admin/fleet/h5/tokens` | `expireDays` | `@Min(1) @Max(90)` | 传 0 或 365 原来能建 token,现在 400。若有「长期有效」按钮传大值会失败 |
|
||||
| 操作时间线 | `page` | `@Max(100000)` | 超限原来返空列表,现在 400。建议前端自行夹紧避免翻页越界 |
|
||||
|
||||
### C2. 车辆绑定常驻司机新增存在性校验
|
||||
|
||||
**接口**:`POST` / `PUT /admin/fleet/vehicles` 与导入 UPDATE 分支。
|
||||
|
||||
`primaryDriverId` 指向不存在或已软删的司机,现在直接返 **600205「司机不存在」**(此前静默落库成悬挂引用)。前端下拉缓存过期会踩到,需要有兜底提示。
|
||||
|
||||
### C3. 错误码清单补登记(行为零变化,只是 Swagger 补全)
|
||||
|
||||
- `POST /admin/fleet/insurance/tasks/{taskId}/retry`:补登记 **601210-601215**(权威保单未通过复核 / 权威保单查询失败 / 权威年保快照查询失败 / 既有成功任务等待权威数据复核 / 冲突后权威查询失败 / 冲突后档案复核失败)。这些场景**原先一律返 100500「系统繁忙」**,现在返精确码。
|
||||
- `POST /admin/fleet/drivers/{driverId}/insurance/purchase` 与 `/insure`:order-v3 保险服务降级时由 100500 改为按 Controller 承诺的 **605601** 返回。
|
||||
- `POST /admin/fleet/driver-pending/{pendingId}/approve`:**删除 605022**(该码全服务已无抛出点,前端若有该分支应删或转为不可达兜底),**新增 600205**(自带车常驻司机不存在)与 **600210**(并发冲突,续签审核 CAS 失败,需刷新重试)。
|
||||
|
||||
### C4. 已删除的请求字段
|
||||
|
||||
`DriverSaveReqVO.blacklistReason` 请求字段已删除(后端早已不消费)。不会 400(Jackson 忽略未知字段),但 Swagger 契约里没有了,前端可清理。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
**门禁**:全量 `mvn -pl hl-fleet-service test`(clean 后)**3485 个用例 0 失败**,仅剩 10 个 `ReleaseEOccupancyMysql8033RecoveryTest` 错误(该类设计上只能由 `test/run-mysql833-provider.ps1` 传冻结 SHA 系统属性运行,基线同款);`FleetRedLineArchTest` 12/12 绿;`spotless:check` clean。相比基线(3382 用例 / 1 失败 + 13 错误)新增约 100 个用例,红项只减不增。
|
||||
|
||||
**测试服实测**(部署 task 55207386,双实例 12:59 重启):
|
||||
|
||||
1. **错误码迁段(A1)**:迁移前 `SELECT error_code, LEFT(error_message,30), COUNT(*) FROM fleet_insurance_task WHERE error_code IN ('540033','540034') GROUP BY 1,2` → 7 行 540033、文案全部以「司机年保」开头(100% 命中迁移条件);迁移后 540033-540037 **残留 0**、601200 **恰好 7 行**。
|
||||
2. **P0 终止截断**(本次整改的最高优先级缺陷,前端不可见但影响车务可用性):订单 2086697883247022081 含两条全程行(08-26~28 与 08-29~31),`POST /internal/fleet/assignment/truncate-from-termination` 传 terminateDate=08-30 返回 **200**(修复前必抛 605907 且永久失败、车/司机占用永不释放)。落库形态:原全程行按天拆片 → 首日 08-29 保留 assigned 且车费按新边界从 4500 重算为 1500,08-30/08-31 两片 canceled;另一条 08-26~28 的行完全未被触碰;对账 prep 精确反标(08-29 active、08-30/08-31 canceled、另一行 08-26~28 全 active)——既不漏截也不多截。
|
||||
3. Flyway 全量迁移在 H2 与测试服 MySQL 均执行成功。
|
||||
|
||||
---
|
||||
|
||||
## 前端 checklist
|
||||
|
||||
- [ ] A1 保险 `errorCode` 码表由 540033-540037 改为 601200-601204
|
||||
- [ ] A2 `/vehicles/options` 的 `dayPrice` 参与计算前 `Number()` 转换
|
||||
- [ ] A3 confirm 两端点接住 605063(**禁自动重试**),需求级确认补 605059 分支
|
||||
- [ ] A4 派单三端点接住 605064(**禁自动重试**),给专用引导文案
|
||||
- [ ] A5 到期看板 `kinds`/`buckets` 只传白名单值且**大小写严格**
|
||||
- [ ] A6 模板编辑不再无条件回传 `isDefault: false`,接住 600804
|
||||
- [ ] B1 对账车队行 key 改用 `fleet` 字段,容忍兜底行(`vehicles=[]`、`fleetTeamId` 可能为 null)
|
||||
- [ ] B2 对账保险 Tab 消费新增的 `manualAmount`/`baoyouAmount`
|
||||
- [ ] B3 待补偿 `opType` 下拉补 `INSURANCE`
|
||||
- [ ] B4 逐日车费 `priceSource` 支持 `FREE`
|
||||
- [ ] C1 `expireDays` ≤90、`page` ≤100000 前端夹紧
|
||||
- [ ] C2 车辆保存接住 600205
|
||||
|
||||
做完请把 frontmatter 的 `frontend_status` 改为 `implemented` 并填 `frontend_ref`。
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5818"
|
||||
title: "车务第二轮审计整改:保险任务 CANCELLED 可筛、订单进度新增 SKIPPED、错误码声明纠偏、h5 附件来源白名单"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a4174560"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "PR #5864 已合并 dev-v3 并部署测试服实测(全量 3607 用例 0 失败、ArchTest 13/13)。前端已实现(2026-08-11,mmg,a4174560)——实证仅 2 项需改并落地:A1 保险任务状态下拉补 CANCELLED(已撤销,statusType 同 IGNORED 归 default 置灰,修复后 50 条已撤销任务可见,insurance/index.vue);A2 派单进度步骤条补 SKIPPED 渲染(OrderDrawer.vue stepStatus 标 finish 置灰+currentProgressStep 无进行中时计入 DONE+SKIPPED 防定位偏前)。其余实证 not_required/自动受益:A3 attemptId 后端「传了不 400 仅无效」前端兼容;B1 对账车队筛选后端修 bug 前端只展示自动受益;B2 headcountLabel display.js:940/fleetDisplay.js:99 直接展示无 N人 正则解析;B3 605007/605002 仅注释提及(活跃分支已随 #5822 清)605037/605038/605049 无硬编码走全局拦截器透传;B4 车辆删除已接 600109(vehicles/index.vue:1040)司机删除 600204 走全局拦截器透传;B5/C1/C2/C3 后端侧前端零消费。测试:insurance spec +1(下拉含 CANCELLED+状态色),新建 order-drawer-progress.spec 3 用例,fleet/board 431 + insurance 14 全绿,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务:第二轮审计整改的前端契约增量(#5818 / PR #5864)
|
||||
|
||||
> **服务**: hl-fleet-service (8087/8187)
|
||||
> **PR**: #5864(第一轮见 #5837,changelog `11_5818_车务全量审计整改…`)
|
||||
> **背景**: 第二轮审计打第一轮自报的覆盖空洞 + 三个新镜头(安全越权 / 接口契约符合性 / 测试有效性),确认 35 条缺陷并全部修复。大部分是后端内部与门禁修复,以下是**前端能看见**的增量。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口清单
|
||||
|
||||
| 接口 | 变化 | 档位 |
|
||||
|------|------|------|
|
||||
| `GET /admin/fleet/insurance/tasks` | `taskStatus` / 兼容别名 `status` 白名单补 `CANCELLED` | A1 |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` | 四阶段进度 `status` 新增第 5 个取值 `SKIPPED` | A2 |
|
||||
| HOLD 通知状态查询 | 删除死入参 `attemptId` | A3 |
|
||||
| `GET /admin/fleet/reconciliation/cars` | 按车队筛选时实付不再归零、整行不再消失 | B1 |
|
||||
| `GET /admin/fleet/matrix/**` | `headcountLabel` 格式明确为「2大1童」式多段串 | B2 |
|
||||
| `POST /admin/fleet/assignments` 等 4 个写端点 | 补声明必抛的 605037 / 605038 / 605049 | B3 |
|
||||
| `POST /admin/fleet/assignments/slots`(addSlot) | 删除死声明 605007 | B3 |
|
||||
| `POST /admin/fleet/assignments/{id}/change` | 删除死声明 605002 | B3 |
|
||||
| `DELETE /admin/fleet/vehicles/{id}`、`/drivers/{id}` | 软删守卫已生效,会真拒(600109 / 600204) | B4 |
|
||||
| 对账关账 / 重开 | close 删死声明 605602、reopen 补必抛 605605 | B5 |
|
||||
| `GET /admin/fleet/drivers`(列表) | `driverStatus` 校验文案纠正 | C1 |
|
||||
| 4 个消息模板端点 | notes 的成功包络写法纠正 | C2 |
|
||||
| h5 司机入职提交 | 12 类附件 URL 新增来源白名单校验 | C3 |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 A. 必须改
|
||||
|
||||
### A1. 保险任务 `CANCELLED` 状态现在可以筛了
|
||||
|
||||
`GET /admin/fleet/insurance/tasks` 的 `taskStatus`(及兼容别名 `status`)白名单补上 `CANCELLED`。
|
||||
|
||||
**这不是新增状态**——`CANCELLED`(派单取消且保单未出单时自动撤销投保任务)一直在真实落库,但查询侧的 Bean Validation 白名单漏了它,**传该值直接 400**,车务根本无法筛出这批任务。测试服实测修复后 `total=50`,即此前有 50 条已撤销任务对车务完全不可见。
|
||||
|
||||
**前端处置**:状态下拉补 `CANCELLED`(已撤销)。完整取值:`PENDING` / `PROCESSING` / `SUCCESS` / `RESOLVED` / `CANCELLED` / `IGNORED`。
|
||||
|
||||
### A2. 订单详情四阶段进度新增第 5 个取值 `SKIPPED`
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` 的四阶段进度 `status`,契约此前只声明 4 个值,但后端在 `holdMode=DIRECT`(直接派定、跳过司机确认)时**实际会下发 `SKIPPED`**。
|
||||
|
||||
**前端处置**:进度条/步骤组件补 `SKIPPED` 的渲染(建议样式:置灰 + 「已跳过」),否则会落到未知分支。
|
||||
|
||||
### A3. HOLD 通知状态查询删除死入参 `attemptId`
|
||||
|
||||
该字段 Swagger 宣称用于幂等与审计定位,但 Controller 根本不读,全服务无任何读取点。已删除。前端若在传,去掉即可(传了也不会 400,只是无效)。
|
||||
|
||||
---
|
||||
|
||||
## 🟡 B. 按需改
|
||||
|
||||
### B1. 对账「车队」Tab 按车队筛选时实付不再归零
|
||||
|
||||
**接口**:`GET /admin/fleet/reconciliation/cars`(`periodStart` + `periodEnd` 必填)
|
||||
|
||||
显式筛选车队时,此前用「有没有 active prep」当费用行的过滤条件,导致**整支车队本期 prep 全被反标**(派单取消 / 提前完结截断)时,该队**已核单的费用行被整体丢弃** → 返回 `fleets=[]`、实付归零、整行从页面消失。已改为按选中车队本身过滤。
|
||||
|
||||
财务侧会看到这类车队重新出现且实付有值——修 bug 不是回归。
|
||||
|
||||
### B2. `headcountLabel` 格式明确
|
||||
|
||||
契约此前写「暂按 N 人」,实现在订单上下文有分档时输出的是「2大1童」式多段串(大/童/幼/婴 只拼非零档),只有无上下文或全档为 0 才回退「N人」。契约与 3 个 VO 的 example 已对齐实现。
|
||||
|
||||
**前端处置**:若有按「N人」格式做正则解析的地方,改为直接展示该字符串。
|
||||
|
||||
### B3. 错误码声明纠偏(删死声明 / 补漏声明)
|
||||
|
||||
| 端点 | 变化 |
|
||||
|------|------|
|
||||
| `POST /admin/fleet/assignments`(create)等 4 个写端点 | **补**必抛的 `605037` / `605038`(车辆或司机处于维保、停用等不可派状态)、`605049` |
|
||||
| addSlot | **删** `605007`(该码 #5822 已撤销,全服务无定义无抛出点) |
|
||||
| change | **删** `605002`「车型座位不足」(零抛出点;#5810 后座位不符只提示不阻断,与 create 的 notes 本来就矛盾) |
|
||||
|
||||
**前端处置**:删掉对 605007 / 605002 的分支(永远不会命中);给 605037 / 605038 加提示(引导换车或换司机)。
|
||||
|
||||
### B4. 车辆 / 司机软删守卫已真的生效
|
||||
|
||||
`DELETE /admin/fleet/vehicles/{id}` 与 `/drivers/{id}` 的 notes 此前写「守卫随派单模块落地生效、当前不拦、契约先行」,实际早已经实抛 **600109**(车辆今日及以后仍有在途派单)/ **600204**(司机同理)。文档已改为真实口径。
|
||||
|
||||
**前端处置**:删除接口时要接住这两个码并给出「先取消派单再删除」的引导——此前前端可能因为文档写「不拦」而没做这个分支。
|
||||
|
||||
### B5. 对账关账 / 重开错误码纠偏
|
||||
|
||||
`close` 走 upsert 语义(该期从未关过也能直接建 CLOSED 行),故删掉它的死声明 `605602`「对账期不存在」;`reopen` 补上必抛的 `605605`。
|
||||
|
||||
---
|
||||
|
||||
## 🟢 C. 只需知晓
|
||||
|
||||
### C1. 司机列表 `driverStatus` 校验文案纠正
|
||||
|
||||
400 文案此前把 `pending` 解释成「待续签」(赛季维度语义),与 `DriverStatusEnum` 的权威语义「待激活」相反。已以枚举为准改正。仅文案,取值不变。
|
||||
|
||||
### C2. 消息模板 4 个端点的成功包络写法纠正
|
||||
|
||||
notes 此前写 `{ code: 0, msg: "成功" }`,实际统一包络是 **`code = 200`、字段名 `message`**(不是 `msg`)。仅文档纠正,实际响应一直如此。
|
||||
|
||||
### C3. h5 司机入职提交新增附件来源白名单
|
||||
|
||||
免鉴权提交端点对 12 类附件 URL 此前**零校验**,司机可把任意外部地址写进 pending 并在审核通过后带进正式司机档案。现按 host 判定来源白名单(用 `java.net.URL` 解析 host 判定,而非整串 `startsWith`——后者会被 userinfo 混淆串骗过)。
|
||||
|
||||
**影响**:正常走本系统 OSS 上传流程的附件不受影响;若 H5 页面有任何绕过标准上传、直接填外部 URL 的路径,会被拒绝。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
**门禁**:全量 `mvn -pl hl-fleet-service test` **3607 用例 0 失败**;`FleetRedLineArchTest` **13/13**(含本轮把 R2/R3/R4/R12/R8 的选靶口径从包路径扩为「包路径 ∪ 注解」并集后新覆盖的 4 个 Controller / 6 个 Service);`spotless:check` clean。
|
||||
|
||||
> 附带说明一个对「测试可信度」的重要修正:此前 `mvn test` 长期有 10 个 `ReleaseEOccupancyMysql8033RecoveryTest` ERROR(该类缺 `@EnabledIfSystemProperty`,默认构建下是硬报错而非跳过),本轮补上后**全量测试第一次真正全绿(退出码 0)**,此后「红」即真红。同时修正了 6 个集成测试类 18+ 个 `@Test` 在默认构建里从不执行、却在「N 用例 0 失败」汇总里隐形的问题。
|
||||
|
||||
**测试服实测**(部署 task 47191651;期间发现进程比新 jar 旧,已 SSH 重启双实例后复验):
|
||||
|
||||
1. 保险任务 `taskStatus=CANCELLED` → `code=200`、`total=50`(修复前必 400)。
|
||||
2. 到期看板合法值 `kinds=inspect&buckets=expired` → 200;非法值 `kinds=INSPECT` → `100001` 且文案明确列出合法取值。
|
||||
3. 对账 `/cars`(`periodStart`/`periodEnd`)→ 200,4 个车队全部带 `settleMode`(兜底口径生效)。
|
||||
4. 对账 `/insurance` → 200,`drivers[]` 已下发 `manualAmount` / `baoyouAmount`,且 `insuranceAmount 70.56 = manual 70.56 + baoyou 0.00` 自洽。
|
||||
5. Flyway `V20260811_007` 执行成功,`fleet_insurance_task` 的 `task_status` / `task_type` 列注释已补全 `CANCELLED` / `REFUND_CHECK`。
|
||||
|
||||
---
|
||||
|
||||
## 前端 checklist
|
||||
|
||||
- [ ] A1 保险任务状态下拉补 `CANCELLED`(已撤销)
|
||||
- [ ] A2 四阶段进度补 `SKIPPED` 渲染分支
|
||||
- [ ] A3 HOLD 通知状态查询去掉 `attemptId` 入参
|
||||
- [ ] B3 删除 605007 / 605002 分支,补 605037 / 605038 提示
|
||||
- [ ] B4 车辆/司机删除接住 600109 / 600204 并引导「先取消派单」
|
||||
- [ ] B2 `headcountLabel` 直接展示,不做格式解析
|
||||
- [ ] B1 对账车队 Tab 复核筛选后的实付数值(预期比修复前更完整)
|
||||
@@ -0,0 +1,563 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5820"
|
||||
title: "主报账人报账表出参重构:顶层三维化 + 支出行统一扁平字段(破坏性变更)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "fd070dc2"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,fd070dc2),仅 driver/reimbursement 分支,group 单团核算未重构零改动:①adaptSettlementReport 的 reportStatus/confirmedAt 改优先读 baseInfo 回退顶层(不改则 confirmed 恒 false);②driver 汇总卡 6 字段改读 baseInfo,公共预支 publicPrepaidAmount→approvedAdvanceAmount,删 transferDirection 改 reporterNetAmount 正负推导(正=报账人应转回公司/负=公司应补报账人,金额取 transferAmount);③REPORT_DETAIL_CONFIG.driver 删 vehicleLines 项(车辆并入 expenseLines category=VEHICLE 逐天一行);④REPORT_FIELD_LABELS 补 date/reimburseAmount,expenseLines 12 扁平明细列数据驱动自适应。orderHeader 新增 returnDate/travelerCount 未接入(ReportModal 标题栏取 props.order 不读 orderHeader,按本次范围不接)。测试:ReportModal.spec 重构 fixture+1 baseInfo 方向推导用例,returnDetailAdapter.spec +4 baseInfo 兼容用例,settlement 91+4 全绿,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】主报账人报账表出参重构:顶层三维化 + 支出行统一扁平字段(#5820 / #5859)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单结算域的「主报账人报账表」接口,供管理后台在订单核单时查看主报账人(通常是司机)的代收、垫付支出、预支与净额结算情况。
|
||||
|
||||
本次重构解决两个历史问题:
|
||||
|
||||
1. **出参结构混乱**(#5820):原出参把 30+ 个汇总字段平铺在顶层,支出行按费用类别拆成 7 族各自一套字段名(住宿用 hotelName/roomTypeName/stayDate、餐食用 mealName/mealTypeName/mealDate、门票用 scenicName/specName/dayDate、车辆用 vehiclePlate/driverName/serviceDate/dailyPrice……),前端需要为每类支出写一套渲染逻辑,且存在 4 对语义重复的镜像字段。
|
||||
2. **订单抬头字段不全 + 方向字段冗余**(#5859):baseInfo.transferDirection 与 transferAmount 正负 / reporterNetAmount 表达的信息重复;orderHeader 缺返回日期与出行人总数。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更 | 类型 |
|
||||
|---|------|------|
|
||||
| 1 | 顶层 30+ 平铺汇总字段全部收拢进 baseInfo 子对象 | ⚠️ 破坏性 |
|
||||
| 2 | expenseLines 支出行由 7 族稀疏字段统一为同一套 12 个扁平字段 | ⚠️ 破坏性 |
|
||||
| 3 | 顶层 vehicleLines 字段删除(车辆支出行并入 expenseLines,签单/公司直付不再重复列出) | ⚠️ 破坏性 |
|
||||
| 4 | baseInfo 删除 6 个冗余字段:reconNetAmount / driverCollectedTailAmount / publicPrepaidAmount / advanceOutstandingAmount / transferStatus / transferDirection | ⚠️ 破坏性 |
|
||||
| 5 | 报账口径明确为口径 A:expenseLines 只含报账人垫付(CASH_PAID)支出 | 🔧 行为变化 |
|
||||
| 6 | orderHeader 新增 returnDate(返回日期)、travelerCount(出行人总数) | ✨ 新增字段 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
|
||||
| 接口名 | 查询主报账人报账表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表 |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID,必须大于 0 |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体,无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
统一响应 Result<SettlementReimbursementReportRespVO>,data 结构如下。
|
||||
|
||||
### 5.1 顶层结构
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| baseInfo | Object | 是 | 基础信息(汇总值 + 报账人 + 审计字段),恒下发对象 |
|
||||
| orderHeader | Object | 否 | 订单头(订单号/团号/产品/客户/出团日期/定制师/出行人构成) |
|
||||
| incomeLines | Array | 是 | 收入行(司机代收);无数据固定返回空数组 [] |
|
||||
| expenseLines | Array | 是 | 支出行(统一扁平字段,仅报账人垫付支出);无数据固定返回空数组 [] |
|
||||
| advanceLines | Array | 是 | 预支明细行(已审批预支逐条);无数据固定返回空数组 [] |
|
||||
|
||||
> 顶层**不再有任何平铺的汇总金额字段**,也**不再有 vehicleLines**。
|
||||
|
||||
### 5.2 baseInfo 字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(String) | 核单记录 ID(settlement_recon.recon_id);未生成时为空。Long 序列化为字符串防 JS 精度丢失 |
|
||||
| orderId | Long(String) | 订单 ID,序列化为字符串 |
|
||||
| reportStatus | String | 报表状态,枚举:GENERATED(已生成)/ CONFIRMED(已确认) |
|
||||
| reportVersion | Integer | 报表版本号,固定从 1 开始 |
|
||||
| primaryReporterId | Long(String) | 主报账人人员安排 ID |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| primaryReporterCollectedAmount | BigDecimal | 主报账人代收金额(收入行合计),保留两位小数 |
|
||||
| approvedAdvanceAmount | BigDecimal | 已审批预支金额(预支行合计),保留两位小数 |
|
||||
| reportablePaidCostAmount | BigDecimal | 可报账已付成本(支出行合计),保留两位小数 |
|
||||
| reporterNetAmount | BigDecimal | 报账人净额(代收 + 预支 - 支出);正 = 报账人应转回公司,负 = 公司应补报账人 |
|
||||
| primaryReporterDueAmount | BigDecimal | 主报账人应收尾款(核单口径),保留两位小数 |
|
||||
| transferAmount | BigDecimal | 转账金额(净额绝对值),保留两位小数 |
|
||||
| generatedBy | Long(String) | 生成人 ID |
|
||||
| generatedByName | String | 生成人姓名 |
|
||||
| generatedAt | String | 生成时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| confirmedBy | Long(String) | 确认人 ID;未确认时为空 |
|
||||
| confirmedByName | String | 确认人姓名;未确认时为空 |
|
||||
| confirmedAt | String | 确认时间,格式 yyyy-MM-dd HH:mm:ss;未确认时为空 |
|
||||
|
||||
**转账方向判定**(替代被删的 transferDirection):
|
||||
|
||||
- reporterNetAmount > 0 → 报账人应转回公司
|
||||
- reporterNetAmount < 0 → 公司应补报账人
|
||||
- transferAmount = |reporterNetAmount|
|
||||
|
||||
### 5.3 orderHeader 字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| orderNo | String | 订单号 |
|
||||
| teamNo | String | 团号(订金未支付为 null) |
|
||||
| productName | String | 产品名 |
|
||||
| productType | String | 产品类型枚举(如 CORE) |
|
||||
| productTypeName | String | 产品类型中文名(字典 product_type 回填) |
|
||||
| customerName | String | 客户姓名 |
|
||||
| departDate | String | 出团日期,格式 yyyy-MM-dd |
|
||||
| returnDate | String | **新增**:返回日期,格式 yyyy-MM-dd |
|
||||
| consultantName | String | 定制师姓名 |
|
||||
| travelerCount | Integer | **新增**:出行人总数(成人+儿童+幼童+婴儿,空档按 0 计) |
|
||||
| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null),如 "2大 1儿童 1幼童" |
|
||||
|
||||
### 5.4 expenseLines 支出行字段表(统一扁平 12 字段)
|
||||
|
||||
**所有分类共用同一套字段**,前端单 table 渲染即可,不用再按类别分叉。
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| category | String | 是 | 费用类别 code:HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE |
|
||||
| categoryName | String | 是 | 费用类别中文名(字典 settlement_category) |
|
||||
| itemName | String | 是 | 项目名(分类特有信息折叠,见下方折叠规则表) |
|
||||
| unitPrice | BigDecimal | 否 | 单价,保留两位小数;**无单价概念的分类不输出该键** |
|
||||
| quantity | BigDecimal | 否 | 数量(住宿=房间数,餐食=份数,门票=票数,车辆按天每行=1);**无数量概念的分类不输出该键** |
|
||||
| amount | BigDecimal | 是 | 实际金额,保留两位小数 |
|
||||
| reimburseAmount | BigDecimal | 是 | 报账金额,保留两位小数;人员行应报销与金额不同时分别给出,其余分类与 amount 相同 |
|
||||
| paymentMethod | String | 是 | 付款方式;报账支出行固定 CASH_PAID |
|
||||
| paymentMethodName | String | 是 | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码),如 "现金已付" |
|
||||
| date | String | 是 | 业务日期,格式 yyyy-MM-dd(住宿=入住日,门票=游玩日,餐食=用餐日,车辆=服务日,人员=结算日) |
|
||||
| remark | String | 否 | 备注;**无备注时不输出该键** |
|
||||
| voucherUrls | Array<String> | 否 | 凭证 URL 数组;**无凭证时不输出该键** |
|
||||
|
||||
**itemName 折叠规则**(按 category):
|
||||
|
||||
| category | itemName 格式 | 示例 |
|
||||
|----------|--------------|------|
|
||||
| HOTEL | 酒店名-房型 | 呼伦贝尔香格里拉大酒店-大床房 |
|
||||
| TICKET | 景区名-规格 | 套娃景区-成人票 |
|
||||
| MEAL | 餐食名(餐类型) | 手把肉套餐(午餐) |
|
||||
| VEHICLE | 车牌 车型/司机 | 蒙A-E2E01 丰田普拉多/巴雅尔 |
|
||||
| GUIDE / PHOTOGRAPHER | 人员姓名 | 巴特尔 |
|
||||
| OTHER_EXPENSE / INSURANCE | 项目名 | 旅游意外险 |
|
||||
|
||||
### 5.5 incomeLines 收入行字段表(本次未动)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | 行类型 code |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| receiptId | Long(String) | 线下收款记录 ID |
|
||||
| amount | BigDecimal | 收款金额,保留两位小数 |
|
||||
| channel | String | 收款渠道 code |
|
||||
| channelName | String | 收款渠道中文名(PaymentChannelEnum 枚举 label) |
|
||||
| payType | String | 收款款项类型 code |
|
||||
| payTypeName | String | 收款款项类型中文名(PayType 枚举 label) |
|
||||
| collectorStaffId | Long(String) | 收款人人员安排 ID |
|
||||
| collectorName | String | 收款人姓名 |
|
||||
| collectorRole | String | 收款人角色 code |
|
||||
| collectorRoleName | String | 收款人角色中文名(字典 staff_role) |
|
||||
| receivedAt | String | 收款时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| remark | String | 备注;无备注时为空 |
|
||||
|
||||
### 5.6 advanceLines 预支明细行字段表(本次未动)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | 行类型 code |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type),如 "已审批预支" |
|
||||
| advanceId | Long(String) | 预支单 ID |
|
||||
| payeeStaffId | Long(String) | 借款对象人员安排 ID |
|
||||
| payeeName | String | 借款对象姓名 |
|
||||
| payeeRole | String | 借款对象角色 code |
|
||||
| payeeRoleName | String | 借款对象角色中文名(字典 staff_role) |
|
||||
| advanceType | String | 预支类型 code |
|
||||
| advanceTypeName | String | 预支类型中文名(字典 advance_type),如 "住宿押金" |
|
||||
| amount | BigDecimal | 预支金额,保留两位小数 |
|
||||
| purpose | String | 预支用途 |
|
||||
| voucherUrl | String | 凭证 URL |
|
||||
| status | String | 预支状态 code |
|
||||
| statusText | String | 预支状态中文名(AdvanceStatus 枚举 label),如 "已通过" |
|
||||
| submittedAt | String | 提交时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| approvedAt | String | 审批时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| approvedBy | String | 审批人姓名 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
| 字段 | 来源 | 取值 |
|
||||
|------|------|------|
|
||||
| baseInfo.reportStatus | 枚举 | GENERATED(已生成)/ CONFIRMED(已确认) |
|
||||
| expenseLines.category | 字典 settlement_category | HOTEL(住宿)/ TICKET(门票/游玩项目)/ MEAL(餐食)/ VEHICLE(车辆)/ GUIDE(导游)/ PHOTOGRAPHER(摄影师)/ OTHER_EXPENSE(其他费用)/ INSURANCE(保险) |
|
||||
| expenseLines.paymentMethod | 字典 settlement_payment_method | 报账支出行固定 CASH_PAID(现金已付) |
|
||||
| incomeLines.channel | PaymentChannelEnum | 收款渠道枚举 |
|
||||
| incomeLines.payType | PayType 枚举 | 收款款项类型枚举(如尾款) |
|
||||
| advanceLines.status | AdvanceStatus 枚举 | 预支状态(如已通过) |
|
||||
| orderHeader.productType | 字典 product_type | 产品类型 |
|
||||
|
||||
**已删除的枚举字段**:transferStatus(#5816 连带下线)、transferDirection(#5859 删除,方向由 transferAmount 正负 / reporterNetAmount 表达)。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
| 584088 | 核单凭证数据损坏,请联系管理员处理 | 快照读回时凭证数据异常(脏行),不会抛 500 |
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
请求:
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement
|
||||
|
||||
响应(已核单 CONFIRMED 订单,含住宿/门票/餐食/车辆支出 + 一条已审批预支):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"id": "2087089146102255617",
|
||||
"orderId": "2087088947225038849",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"reportVersion": 1,
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "巴雅尔",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 0,
|
||||
"approvedAdvanceAmount": 3000.00,
|
||||
"reportablePaidCostAmount": 1799.00,
|
||||
"reporterNetAmount": -4799.00,
|
||||
"primaryReporterDueAmount": 0,
|
||||
"transferAmount": 4799.00,
|
||||
"generatedBy": "2037350531801993218",
|
||||
"generatedByName": "腰苏图",
|
||||
"generatedAt": "2026-08-11 16:10:03",
|
||||
"confirmedBy": "2037350531801993218",
|
||||
"confirmedByName": "腰苏图",
|
||||
"confirmedAt": "2026-08-11 16:10:03"
|
||||
},
|
||||
"orderHeader": {
|
||||
"orderNo": "HL20260811001",
|
||||
"teamNo": "T20260811001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-08-29",
|
||||
"returnDate": "2026-09-02",
|
||||
"consultantName": "李四",
|
||||
"travelerCount": 5,
|
||||
"travelerComposition": "2大 1儿童 1幼童"
|
||||
},
|
||||
"incomeLines": [],
|
||||
"expenseLines": [
|
||||
{
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"itemName": "呼伦贝尔香格里拉大酒店-大床房",
|
||||
"unitPrice": 320.00,
|
||||
"quantity": 1,
|
||||
"amount": 320.00,
|
||||
"reimburseAmount": 320.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-29",
|
||||
"remark": "含早",
|
||||
"voucherUrls": ["https://oss.example.com/voucher/hotel-1.jpg"]
|
||||
},
|
||||
{
|
||||
"category": "TICKET",
|
||||
"categoryName": "门票/游玩项目",
|
||||
"itemName": "套娃景区-成人票",
|
||||
"unitPrice": 99.00,
|
||||
"quantity": 1,
|
||||
"amount": 99.00,
|
||||
"reimburseAmount": 99.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-30"
|
||||
},
|
||||
{
|
||||
"category": "MEAL",
|
||||
"categoryName": "餐食",
|
||||
"itemName": "手把肉套餐(午餐)",
|
||||
"unitPrice": 68.00,
|
||||
"quantity": 5,
|
||||
"amount": 340.00,
|
||||
"reimburseAmount": 340.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-30"
|
||||
},
|
||||
{
|
||||
"category": "VEHICLE",
|
||||
"categoryName": "车辆",
|
||||
"itemName": "蒙A-E2E01 丰田普拉多/巴雅尔",
|
||||
"unitPrice": 1000.00,
|
||||
"quantity": 1,
|
||||
"amount": 1000.00,
|
||||
"reimburseAmount": 1000.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-29"
|
||||
}
|
||||
],
|
||||
"advanceLines": [
|
||||
{
|
||||
"type": "APPROVED_ADVANCE",
|
||||
"typeName": "已审批预支",
|
||||
"advanceId": "5001",
|
||||
"payeeStaffId": "7001",
|
||||
"payeeName": "巴雅尔",
|
||||
"payeeRole": "DRIVER",
|
||||
"payeeRoleName": "司机",
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"advanceTypeName": "住宿押金",
|
||||
"amount": 3000.00,
|
||||
"purpose": "酒店押金",
|
||||
"voucherUrl": "https://oss.example.com/advance-v1.jpg",
|
||||
"status": "APPROVED",
|
||||
"statusText": "已通过",
|
||||
"submittedAt": "2026-08-28 10:00:00",
|
||||
"approvedAt": "2026-08-28 12:00:00",
|
||||
"approvedBy": "财务丙"
|
||||
}
|
||||
]
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**边界 1:零收入零预支零支出**(刚生成报表、尚未录任何行)——三个数组固定返回空数组,不是 null:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"id": "2087089146102255617",
|
||||
"orderId": "2087088947225038849",
|
||||
"reportStatus": "GENERATED",
|
||||
"reportVersion": 1,
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "巴雅尔",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 0,
|
||||
"approvedAdvanceAmount": 0,
|
||||
"reportablePaidCostAmount": 0,
|
||||
"reporterNetAmount": 0,
|
||||
"primaryReporterDueAmount": 0,
|
||||
"transferAmount": 0,
|
||||
"generatedBy": "2037350531801993218",
|
||||
"generatedByName": "腰苏图",
|
||||
"generatedAt": "2026-08-11 16:10:03",
|
||||
"confirmedBy": null,
|
||||
"confirmedByName": null,
|
||||
"confirmedAt": null
|
||||
},
|
||||
"orderHeader": {
|
||||
"orderNo": "HL20260811001",
|
||||
"teamNo": null,
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-08-29",
|
||||
"returnDate": "2026-09-02",
|
||||
"consultantName": "李四",
|
||||
"travelerCount": 0,
|
||||
"travelerComposition": null
|
||||
},
|
||||
"incomeLines": [],
|
||||
"expenseLines": [],
|
||||
"advanceLines": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**边界 2:人员行(GUIDE)+ 无凭证无备注 + 报销金额与金额不同**——unitPrice/quantity/remark/voucherUrls 键不输出:
|
||||
|
||||
```json
|
||||
{
|
||||
"category": "GUIDE",
|
||||
"categoryName": "导游",
|
||||
"itemName": "巴特尔",
|
||||
"amount": 500.00,
|
||||
"reimburseAmount": 450.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-31"
|
||||
}
|
||||
```
|
||||
|
||||
**边界 3:门票单价为 0**(免费票)——unitPrice: 0.0 正常输出,金额 0:
|
||||
|
||||
```json
|
||||
{
|
||||
"category": "TICKET",
|
||||
"categoryName": "门票/游玩项目",
|
||||
"itemName": "蓝房子(乌苏浪子湖)-成人票",
|
||||
"unitPrice": 0.0,
|
||||
"quantity": 1,
|
||||
"amount": 0.0,
|
||||
"reimburseAmount": 0.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-08-31"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**订单不存在**:
|
||||
|
||||
GET /v3/admin/order/999999999/settlement/reports/reimbursement
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**参数校验失败**(orderId = 0):
|
||||
|
||||
GET /v3/admin/order/0/settlement/reports/reimbursement
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "订单 ID 必须大于 0",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**房务角色访问被拦**(House 角色 JWT 调用):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 403,
|
||||
"message": "无权限访问",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
- 订单已进入核单流程(已生成核单记录),查看主报账人维度的结算报表
|
||||
- 已核单(CONFIRMED)与核单中(GENERATED)订单均可调,报表实时计算
|
||||
|
||||
**不适用**:
|
||||
- 未发起核单的订单:baseInfo.id 等审计字段为空,汇总金额为 0
|
||||
- 需要看整团(含非报账人支付)口径时用单团核算表接口 GET /v3/admin/order/{orderId}/settlement/reports/group,本接口是报账人视角
|
||||
|
||||
**特殊边界**:
|
||||
- **报账口径 A**:expenseLines 只含报账人垫付(CASH_PAID)的支出;签单 / 公司直接付的车务费**不进** expenseLines,前端不要期待在支出行里看到所有成本
|
||||
- incomeLines 只含报账人代收,线上支付(微信等)不在此列
|
||||
- 旧快照兼容:历史已确认报表的旧快照数据读回时,旧扁平键静默忽略、落默认空 baseInfo,不会抛 500
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 顶层结构对比
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 汇总字段 | 30+ 个平铺在顶层(id/orderId/reportStatus/primaryReporterCollectedAmount/...) | 全部收拢进 baseInfo 子对象 |
|
||||
| 车辆支出 | 顶层独立 vehicleLines 数组(车辆专属字段) | 删除;车辆支出行并入 expenseLines(category=VEHICLE) |
|
||||
| 订单头 | orderHeader(9 字段) | orderHeader(11 字段,新增 returnDate/travelerCount) |
|
||||
|
||||
### 10.2 baseInfo 字段级对比(删除清单)
|
||||
|
||||
| 删除字段 | 原位置 | 替代取值 |
|
||||
|----------|--------|----------|
|
||||
| reconNetAmount | 顶层平铺 | baseInfo.reporterNetAmount(同值镜像) |
|
||||
| driverCollectedTailAmount | 顶层平铺 | baseInfo.primaryReporterCollectedAmount(同值镜像) |
|
||||
| publicPrepaidAmount | 顶层平铺 | baseInfo.approvedAdvanceAmount(同口径) |
|
||||
| advanceOutstandingAmount | 顶层平铺 | 无替代(该口径废弃,预支看 approvedAdvanceAmount + advanceLines) |
|
||||
| transferStatus | baseInfo(#5816 连带下线) | 无替代(转账状态跟踪能力下线) |
|
||||
| transferDirection | baseInfo | 由 reporterNetAmount 正负判定:正=报账人应转回公司,负=公司应补报账人;金额取 transferAmount |
|
||||
|
||||
> 其余原顶层平铺字段(id/orderId/reportStatus/reportVersion/primaryReporter*/各金额/generated*/confirmed*)**字段名与语义不变**,仅位置从顶层移入 baseInfo。
|
||||
|
||||
### 10.3 expenseLines 字段级对比
|
||||
|
||||
| 修改前(按类别稀疏字段) | 修改后(统一扁平字段) |
|
||||
|--------------------------|------------------------|
|
||||
| HOTEL: hotelName / roomTypeName / stayDate / roomCount / unitPrice | itemName=「酒店-房型」/ date / quantity / unitPrice |
|
||||
| TICKET: scenicName / specName / dayDate / ticketCount / ticketUnitPrice | itemName=「景区-规格」/ date / quantity / unitPrice |
|
||||
| MEAL: mealName / mealTypeName / mealDate / quantity / unitPrice | itemName=「餐食名(餐类型)」/ date / quantity / unitPrice |
|
||||
| VEHICLE: vehiclePlate / driverName / serviceDate / dailyPrice | itemName=「车牌 车型/司机」/ date / unitPrice(quantity 恒 1) |
|
||||
| GUIDE/PHOTOGRAPHER: staffName / settledDate / amount / reimburseAmount | itemName=姓名 / date / amount / reimburseAmount |
|
||||
| 各类各自的付款方式/备注/凭证字段名 | 统一 paymentMethod/paymentMethodName/remark/voucherUrls |
|
||||
|
||||
### 10.4 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 签单/公司直付车务费 | 出现在 vehicleLines | 不出现在本接口任何行(口径 A:只列报账人垫付) |
|
||||
| 支出行渲染 | 前端按 7 族类别各写一套列 | 单 table 统一 12 字段渲染 |
|
||||
| 调旧字段(如 data.reportStatus、data.vehicleLines、data.baseInfo.transferDirection) | 有值 | **undefined**(字段不存在,不报错静默丢失) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:⚠️ 是。所有读顶层平铺汇总字段、读 vehicleLines、按类别分叉渲染支出行、读 transferStatus/transferDirection 的前端代码**全部失效**(取到 undefined)。
|
||||
- **前端必须同步上线**:是。前端需要:
|
||||
1. 汇总字段读取路径从 data.xxx 改为 data.baseInfo.xxx
|
||||
2. 支出行渲染改为单 table 统一字段(category/categoryName/itemName/unitPrice/quantity/amount/reimburseAmount/paymentMethod/paymentMethodName/date/remark/voucherUrls)
|
||||
3. 删除 vehicleLines 相关渲染
|
||||
4. 转账方向展示改由 reporterNetAmount 正负 + transferAmount 推导
|
||||
5. 4 个镜像字段改读保留字段(见 §10.2 替代表)
|
||||
- **后端兼容**:旧快照数据读回不报错(静默忽略旧键),无需数据迁移。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = revert PR #5839 + PR #5865 两个 merge commit,重启 hl-order-service-v3。出参即恢复旧结构。
|
||||
- 前端回滚 = 切回旧版前端包。前后端必须同版本(新后端 + 旧前端 = 页面全空)。
|
||||
- 无 DDL,无数据迁移,回滚无残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **所有 Long ID 字段(id/orderId/primaryReporterId/receiptId/advanceId/各种 staffId/generatedBy/confirmedBy)序列化为 JSON 字符串**,前端按 string 处理,不要 Number() 转换(防 JS 精度丢失)。
|
||||
2. expenseLines 中 unitPrice / quantity / remark / voucherUrls 是**条件输出键**:无值时整个键不出现(NON_NULL),前端取值前判空。
|
||||
3. reimburseAmount 大多数分类与 amount 相同;只有人员行(GUIDE/PHOTOGRAPHER)应报销与金额可能不同,展示报账口径时以 reimburseAmount 为准。
|
||||
4. 转账方向不要再找 transferDirection 字段:用 reporterNetAmount > 0 判「报账人转回公司」、< 0 判「公司补报账人」,transferAmount 恒为绝对值。
|
||||
5. orderHeader.travelerComposition 全空档时为 null,teamNo 订金未支付时为 null,展示需兜底。
|
||||
6. 本接口与单团核算表 reports/group 是两个口径:本接口 = 主报账人视角(只含报账人垫付),group = 整团视角(含全部成本)。前端不要把两个接口的行混在一起渲染。
|
||||
7. 车辆支出行在 expenseLines 里按服务日**逐天一行**(quantity 恒 1),不是一车一行。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue:
|
||||
- https://git.1814.love:8443/wx/HL/issues/5820 (主报账人报账表 RespVO 重构)
|
||||
- https://git.1814.love:8443/wx/HL/issues/5859 (删 transferDirection + orderHeader 补 returnDate/travelerCount)
|
||||
- PR:
|
||||
- https://git.1814.love:8443/wx/HL/pulls/5839 (#5820 出参重构)
|
||||
- https://git.1814.love:8443/wx/HL/pulls/5865 (#5859 orderHeader 补字段 + 删 transferDirection)
|
||||
- 服务:hl-order-service-v3(端口 8086)
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5822"
|
||||
title: "车辆槽位无条件可删除(删除即释放车/司机)+ 行程结束后端拦截 605047 + 逐日计划恒空两处修复"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "408ffc63"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(408ffc63):AssignModal 删除失败 catch 删 605007/605027 硬编码文案分支改统一透传后端 message(605047 含引导语),一键清空 clearAllSlotsErrorMessage 同步去 605007/605027(保留 605012 引导);删除按钮门禁本就「未完结即可删」(#5572/#5788 已放开),注释口径更新为「任意状态可删,唯一门禁 605047」。逐日计划恒空(dailyVehiclePlan/actualVehicleCount/vehicleFeeSummaries)为后端纯 bug 修复无契约变化,前端零改动。测试:assign-modal-title.spec 三处用例改 605047 透传断言+新增「行程已出发仍可删」正向用例,fleet/board 35 spec 428 全过,checkpoint 2 文件全绿。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务派单:槽位删除放开 + 逐日计划恒空修复(#5822 / #5821 / #5829 / #5831)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5825(#5822+#5821)、#5834(#5829+#5831)—— 均已合并 dev-v3 并部署测试服
|
||||
> **日期**: 2026-08-11
|
||||
> **含 Flyway**: `20260811.001`(删除意图表唯一键改按稳定槽位 ID)
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 1. `DELETE /admin/fleet/assignments/slots/{slotId}` —— 删除门禁全部撤销
|
||||
|
||||
**旧行为**:以下三种情况拒绝删除
|
||||
- `605007` 槽位已完成派车或已最终确认(含 completed 行、已最终确认的取消行)
|
||||
- `605027` 行程已出发
|
||||
|
||||
**新行为**:**任意派车状态均可删除**(已派车 / 待确认 / 已完成 / 已最终确认后取消 / 行程已出发,全部放行)。删除时:
|
||||
- 仍占用车辆/司机的行(holding/assigned)联动取消 → **车辆与司机的档期占用被释放,回到空闲可再次被候选查询选中**
|
||||
- `canceled` / `completed` 历史行保留不物理删(对账、保险退款关联不丢)
|
||||
|
||||
**唯一保留的门禁**:`605047` 行程已结束,派车信息只读,不能修改或改派。
|
||||
|
||||
| 错误码 | 状态 |
|
||||
|---|---|
|
||||
| `605007` | **已下线**,不再由本接口返回(常量已删除) |
|
||||
| `605027` | 本接口不再返回(该码在「整组取消派单」路径仍在用,定义保留) |
|
||||
| `605047` | **新增于本接口**:行程已结束(今天 **严格大于** 服务结束日)时拒绝;**结束日当天仍可删除** |
|
||||
|
||||
**前端要做**:删除按钮的禁用条件只剩「行程已结束」一种;原本针对 605007/605027 的错误提示分支可以删掉,改为透传 605047 的 message。
|
||||
|
||||
### 2. `GET /admin/fleet/board/orders/{orderId}` —— `vehicleSlots` 三项变化
|
||||
|
||||
| 字段 | 变化 |
|
||||
|---|---|
|
||||
| `canDelete` | 不再受派车状态影响(已派车/已确认/已完成一律 `true`);**唯一为 `false` 的情况是行程已结束/已关账**,与接口 605047 同口径 |
|
||||
| `deleteBlockReason` | `canDelete=true` 时恒 `null`;为 `false` 时是只读原因文案(如「行程已结束」) |
|
||||
| 槽位列表本身 | **已被删除的槽位不再返回**。此前因看板由现存派车行聚合、而历史行永不物理删,删除后槽位仍会显示;现已按删除意图过滤 |
|
||||
|
||||
**前端要做**:不要再依赖「`canDelete=false` 就是因为已派车」的旧假设;直接用 `deleteBlockReason` 展示原因即可。
|
||||
|
||||
### 3. `dailyVehiclePlan` / `actualVehicleCount` / `vehicleFeeSummaries` 恒空修复
|
||||
|
||||
**这是无契约变化的纯 bug 修复,但前端表现差异很大**,两个场景此前会让整单逐日计划、实派车数、车费汇总全部为空:
|
||||
|
||||
- **场景 A(#5821)**:方案最终确认过后,那笔派单又被取消 —— 已取消的行仍占住「当前方案」锚点,把活跃行全挡在门外
|
||||
- **场景 B(#5831)**:车务点「添加车辆槽位」后方案重新解冻 —— 此时一条已确认的行都没有,代码却仍按「历史代际」逻辑只保留已完成行程的行
|
||||
|
||||
场景 B 影响面尤其大:**任何点过「添加车辆槽位」的订单都会立刻中招**,车务看不到逐日计划就无法核对、更无法提交最终方案。
|
||||
|
||||
修复后两种场景均正常返回。实测订单 `2086637109266862081`:修复前 `dailyVehiclePlan=[]`、`actualVehicleCount=0`,修复后 **12 条(2 车 × 6 天)、实派 2、车费汇总 2 条**。
|
||||
|
||||
**前端要做**:无需改动,接口数据恢复后原有渲染即正常。若前端曾为规避空数据加过兜底/隐藏逻辑,可以撤掉。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 测试服(网关 `https://api.test.1814.love:9443`,车务管理员 token)实测:
|
||||
|
||||
| 用例 | 结果 |
|
||||
|---|---|
|
||||
| 原报 605007 的槽位删除 | `code=200` ✅ |
|
||||
| 删除后槽位从详情消失 | ✅(非仅接口成功) |
|
||||
| **删除即释放占用** | 蒙A-E2E01 `available=false, 冲突1条` → `available=true, 冲突0条`,联动取消 1 行 ✅ |
|
||||
| 已派车槽位 `canDelete` | 全部 `true` ✅ |
|
||||
| 行程已结束(2026-07-29)订单删槽位 | `605047 行程已结束,派车信息只读,不能修改或改派` ✅ |
|
||||
| 订单 2086637109266862081 逐日计划 | 12 条 / 实派 2 / 车费汇总 2 条 ✅ |
|
||||
| 4 个含「已确认+已取消」行的订单 | `dailyVehiclePlan` 均非空 ✅ |
|
||||
|
||||
Flyway `20260811.001` 已落库(`success=1`,唯一键已改为 `(requirement_id, assignment_slot_id)`)。单测:fleet 全量 3390 / Failures 0(Errors 均为 Testcontainers 等环境依赖类);#5831 与 #5822 核心修复均通过**变异验证**(退回旧逻辑后对应用例失败)。
|
||||
@@ -0,0 +1,209 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5824"
|
||||
title: "换版后看板缺口消失 / 订单车控误标 DONE 修复:assignmentProgress 新增 staleFinalizedPlan,finalizedByFleet 语义收窄为「已对当前用车需求定稿」"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "aa9c087b"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "后端 PR #5843 已合并 dev-v3 并部署测试服,26-5067 双出口实测通过。前端已实现(aa9c087b):BoardSlotSummary 在 assignmentProgress.staleFinalizedPlan===true 时头部挂「需求已换版 · 请核对车型与数量」warning 轻提示(严格 === true,旧契约缺省不误亮,不做按钮禁用)。其余 checklist 实证零改动——finalizedByFleet 前端零消费;「已派几台车」boardSummary 已走 assignmentProgress.assignedSlots/totalSlots 不数 assignmentSlots;CANCELED 槽位渲染层已按 assignmentStatus 区分(display.js canceled 态样式 + OrderDrawer 着色与短信/费用编辑排除);待派车 tab 记录级状态由后端驱动。测试:board-slot-summary 10/10(新增 3 分支用例),fleet 全域 66 文件 619 用例全绿,checkpoint light 全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务看板:换版后缺口重新露出 + 订单车控不再误标完成(#5824)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **日期**: 2026-08-11
|
||||
> **含 Flyway**: `20260811.005`(`fleet_assignment` 新增 `plan_finalized_requirement_id` 定稿归属列)
|
||||
> **无新增端点 · 无字段删除 · 无请求参数变化 · 无需改网关路由**
|
||||
|
||||
---
|
||||
|
||||
## 背景(一句话)
|
||||
|
||||
`#5810` 换版整槽平移只改派单行的 `requirement_id`,「已定稿」标记原样保留,于是**上一版需求的定稿被当成当前版需求的定稿**:
|
||||
看板把缺口整体抹平(多要的车看不见),订单侧还会据此回发最终方案把车控标成已完成(实测 26-5067 少 1 台车仍 DONE)。
|
||||
本次给定稿加上「归属哪一版需求」的证据,读侧据此区分「当代定稿」与「换版平移来的陈旧定稿」,修两个出口:
|
||||
①看板缺口重新露出(下文「变更接口」);②订单车控不再误标完成(下文「订单车控状态(order-v3 侧)」)。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### `GET /admin/fleet/board/orders` —— `assignmentProgress` 结构变化
|
||||
|
||||
#### 1. 新增字段 `staleFinalizedPlan`(Boolean,恒非 null)
|
||||
|
||||
| 取值 | 含义 |
|
||||
|---|---|
|
||||
| `true` | 该需求名下**存在任一非终态派单行携带「上一版需求的定稿」**:换版后旧派单被整槽平移过来,车务尚未对这些行按当前需求重新定稿。判据是**析取**——当代定稿行与陈旧定稿行并存(例如部分槽已按新需求重新确认、部分槽仍是平移过来的旧定稿)时同样为 `true`,不要求「只剩」旧定稿 |
|
||||
| `false` | 全部定稿行都归属当前需求(当代定稿)/ 未定稿 / 历史存量行无归属证据(新列上线前的行,按兼容口径视同当代)/ **携带陈旧归属的行已是终态**(`canceled`、`completed` 一律豁免,不判 `true`)|
|
||||
|
||||
> 终态豁免的原因:改派与按天改派只把行置为 `canceled`、不清定稿标记,完结行同理不会再被刷进新方案;
|
||||
> 不豁免就会让这些永远动不了的历史行把需求**永久**钉成「陈旧」(看板永久缺口提示、订单车控永久卡处理中)。
|
||||
|
||||
**它只是提示,不一定改判**:只有 `staleFinalizedPlan=true` **且确有缺口**(`suggestedSlots` > 现有槽位数)时才撤销定稿权威。
|
||||
换版只改车型或人数、车数没变时(例:V1 商务车×1 → V2 SUV×1),本字段仍为 `true`,但计数与卡片状态**一律不动**——
|
||||
车型/数量不符按 `#5810` 已定口径只做提示、不做门禁。
|
||||
|
||||
**前端建议**:`staleFinalizedPlan=true` 时在卡片上挂一个「需求已换版,请核对车型与数量」的轻提示,配合已有的
|
||||
`requirementFleetText`(当前需求车队组成语句)让车管一眼比对。不要用它做任何按钮禁用。
|
||||
|
||||
#### 2. `finalizedByFleet` 语义收窄
|
||||
|
||||
| | 旧语义 | 新语义 |
|
||||
|---|---|---|
|
||||
| `finalizedByFleet=true` | 车务已提交最终实派方案(不区分是哪一版需求的方案) | 车务**已对当前用车需求**提交最终实派方案 |
|
||||
|
||||
只有当「陈旧定稿 + 确有缺口」同时成立时,`finalizedByFleet` 才由旧口径的 `true` 变成 `false`。
|
||||
其余场景取值不变(含少派:车务对当前需求定稿后少派也仍是 `true`,缺口按实派守恒不再报)。
|
||||
|
||||
#### 3. `staleFinalizedPlan=true` 且有缺口时的具体变化
|
||||
|
||||
以「V1 商务车×1 已定稿并派车 → 换版为 V2 SUV×2」为例(`suggestedSlots=2`,现有 1 个槽位):
|
||||
|
||||
| 字段 | 修复前 | 修复后 |
|
||||
|---|---|---|
|
||||
| `finalizedByFleet` | `true`(按上一版定稿) | `false` |
|
||||
| `staleFinalizedPlan` | 无此字段 | `true` |
|
||||
| `totalSlots` | `1`(按实派守恒) | `2`(按当前需求估算) |
|
||||
| `unassignedSlots` | `0`(缺口被抹平) | `1`(缺口露出) |
|
||||
| `canceledSlots` | `0`(定稿分支恒 0) | 按实际取消槽位数返回(可 > 0) |
|
||||
| 记录级 `assignmentStatus` | `assigned` | `unassigned`(当天/临近出发则 `unassigned_urgent`) |
|
||||
|
||||
**卡片会换 tab**:这类订单从「已派车」跳到「待派车」(临近出发进急单组)。这是本次修复的目的——
|
||||
车管必须在待派车里看到它,否则新需求多要的那台车永远没人补。
|
||||
|
||||
**`statusCounts` 与列表条数同源一致**:`summary.statusCounts`、`statusOptions` 与 `/orders` 列表用的是同一份记录级派生态,
|
||||
「tab 上有 N 条 → 点进去就是 N 条」这一保证在本次改动后仍然成立(已加回归用例锁定)。
|
||||
|
||||
#### 4. ⚠️ 破坏性提醒:`assignmentSlots` 会包含 `CANCELED` 槽位
|
||||
|
||||
`assignmentSlots` 原本在 `finalizedByFleet=true` 时会过滤掉已取消的槽位。
|
||||
由于本次「陈旧定稿 + 有缺口」会把 `finalizedByFleet` 置为 `false`,**这类卡片的 `assignmentSlots` 里会重新出现
|
||||
`baseAssignmentStatus=canceled` / `assignmentStatus=canceled` 的槽位**(该需求下整槽被取消的历史槽位;
|
||||
该需求没有整槽取消历史时列表内容不变)。
|
||||
|
||||
**前端必须按槽位状态区分渲染**:不要假设 `assignmentSlots` 只含活跃槽位;已取消槽位应置灰或折叠,
|
||||
不要计入「已派几台车」的展示口径(改用 `assignmentProgress` 的 `assignedSlots`/`totalSlots`)。
|
||||
这类槽位的 `availableActionCodes` 为空、`canAssign=false`,点不动,但会占版面。
|
||||
|
||||
---
|
||||
|
||||
## `GET /admin/fleet/board/summary` —— 口径变化(字段结构不变)
|
||||
|
||||
| 字段 | 变化 |
|
||||
|---|---|
|
||||
| `todayDepartCount` | 「今日出团」只数「今天出发 **且记录级已派车**」。陈旧定稿且缺口的卡片记录级已被改判为待派车,**不再计入本数** |
|
||||
| `pendingCount` / `pendingUrgentCount` | 上述卡片改由这两个数体现(今天出发必然落在 `unassigned_urgent`) |
|
||||
| `statusCounts.unassigned` / `.assigned` | 随记录级派生态同步变化,与 `/orders` 列表同源 |
|
||||
|
||||
口径理由:车还没配齐,算作「已派车出团」会让当天出团数虚高、车管漏配车。
|
||||
|
||||
---
|
||||
|
||||
## 订单车控状态(order-v3 侧,定制师页面可见)
|
||||
|
||||
本工单修的是两个出口,上面是看板(出口①),这里是订单车控(出口②)。**无接口结构变化**:
|
||||
fleet 不新增/修改端点,变的是 order-v3 既有字段 `vehicle_control_status` 的推进时机。
|
||||
|
||||
**触发条件**(两个条件必须同时成立,与看板同一份「陈旧」行级判据):
|
||||
|
||||
1. 该需求下存在任一非终态派单行携带上一版需求的定稿(终态行同样豁免);
|
||||
2. **当前需求车数 > 现有非取消槽位数**(订单侧刻意排除已取消槽位——取消掉的槽位不是交付出去的车,
|
||||
与看板按槽位视图计数略有差异,这里统一取更保守的一侧,宁可判未完成也不误报完成)。
|
||||
|
||||
**结果对比**:
|
||||
|
||||
| | 修复前 | 修复后 |
|
||||
|---|---|---|
|
||||
| 车务确认后的满派判定 | 按「上一版定稿即权威」判已满派(该分支刻意不比对需求车数) | 退回常规拓扑校验,按当前需求的车数 × 服务日核对,缺车即未满派 |
|
||||
| 是否回发 `DAILY_V3` 最终方案 | 回发 | **不回发** |
|
||||
| 订单 `vehicle_control_status` | 被标 `DONE`(定制师页面显示派车已完成,实际少一台车,实测 26-5067) | 停在处理中(`PROCESSING`),定制师页面继续显示派车进行中 |
|
||||
|
||||
**恢复方式**:车务在看板对**当前需求**重新提交/确认最终方案即可自动恢复——确认时定稿归属会被盖章成当前需求 ID,
|
||||
陈旧判据随即失效,满派后照常回发最终方案、车控回到 `DONE`。无需人工改库,也不需要运维介入。
|
||||
|
||||
**只陈旧不缺车不改判**:车数没变的换版(只改车型/人数)仍按定稿权威判已满派,车控照常推进到 `DONE`,
|
||||
与看板「只提示不改判」保持同一口径。
|
||||
|
||||
---
|
||||
|
||||
## 详情端点说明(无变化)
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` **不返回** `assignmentProgress`,因此也没有 `staleFinalizedPlan`。
|
||||
详情页已有等价且更可操作的信息:`requirementFleetText`(当前需求车队组成)+ `suggestedVehicleCount`(需求车数)
|
||||
+ `actualVehicleCount`(实派槽位数)。由于本次改判只在「确有缺口」时发生,凡列表把卡片挪进待派车的订单,
|
||||
详情页必然满足 `suggestedVehicleCount > actualVehicleCount`,两处不会互相矛盾。
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
测试服(`hl-fleet-service` 部署于 2026-08-11 14:44,含 PR #5843 合并提交 `6789811ea`),实测订单 **26-5067**(`HL20260809095538772`)。
|
||||
|
||||
该单需求 V1 `mpv×1`(已派 1 台并定稿)→ V2 `suv×2`,属于典型「换版后车数变多」场景。
|
||||
|
||||
### 存量回填
|
||||
|
||||
Flyway `20260811.005` 执行后,该单派单行 `plan_finalized_requirement_id` 精确回填为 **V1 的 requirement_id**,与行上当前 `requirement_id` 不等 → 判定为陈旧定稿。真库统计:17 行活跃定稿中 12 行可由确认回执精确反查,其中仅 1 行为真陈旧(即本单),其余存量归属 = 当前需求或留 NULL(兼容档),状态无跳变。
|
||||
|
||||
### 出口①:看板缺口重新露出
|
||||
|
||||
`GET /admin/fleet/board/orders?keyword=26-5067` 实际返回:
|
||||
|
||||
```json
|
||||
"assignmentProgress": {
|
||||
"totalSlots": 2, "suggestedSlots": 2,
|
||||
"finalizedByFleet": false, "staleFinalizedPlan": true,
|
||||
"unassignedSlots": 1, "holdingSlots": 0, "assignedSlots": 1,
|
||||
"completedSlots": 0, "canceledSlots": 0
|
||||
}
|
||||
```
|
||||
|
||||
- 记录级状态 `unassigned` / 「待派车」(修复前为 `assigned`「已派车」)
|
||||
- tab 计数与列表条数一致:`statuses=unassigned` → 1 条;`statuses=assigned` → 0 条
|
||||
- 与订单侧「部分回配 (1/2)」口径对齐
|
||||
|
||||
### 出口②:订单车控不再误标完成
|
||||
|
||||
在测试服对该单再提交一次换版(V3 `suv×2`,车数不变仍缺 1 台)实测:
|
||||
|
||||
| 项 | 修复前(V2 换版时) | 修复后(V3 换版时) |
|
||||
|---|---|---|
|
||||
| fleet 行 `requirement_id` | 平移到新需求 | 平移到新需求(一致) |
|
||||
| `plan_finalized_requirement_id` | 无此列 | 仍为 V1 → 陈旧 |
|
||||
| 是否回发 DAILY_V3 最终方案 | 是 | 否 |
|
||||
| `order_main.vehicle_control_status` | **DONE**(缺 1 台车却判完成) | **PENDING** ✅ |
|
||||
|
||||
### 门禁与测试
|
||||
|
||||
- 受影响 6 个测试类 666/666 全绿;`FleetRedLineArchTest` 12/12;`spotless:check` 通过
|
||||
- 全量 3505 用例,剩余失败均为环境相关且已在干净 `origin/dev-v3` 基线复现(Testcontainers 并发起容器失败、`ReleaseE` 缺 `-Dfleet.mysql.releaseE.finalMigrationSha256` 系统属性),低负载单独重跑 32/32 通过
|
||||
|
||||
## 已知取舍
|
||||
|
||||
带 `statuses=[unassigned]`(含 `unassigned_urgent`)筛选时,后端为了不漏掉「落库态是 assigned/holding、
|
||||
但记录级会被改判成待派车」的卡片,会把 DB 候选集扩到 `unassigned + holding + assigned` 再在内存精筛。
|
||||
副作用:**该筛选条件下扫描量变大,更容易触发 5000 条安全上限**(触发后走游标扫描,或提示「请增加日期或关键词条件」)。
|
||||
不带状态筛选的默认首屏不受影响(本来就查全状态)。
|
||||
|
||||
---
|
||||
|
||||
## 前端 checklist
|
||||
|
||||
- [ ] `assignmentProgress.staleFinalizedPlan=true` 时展示换版核对提示(不做按钮禁用)
|
||||
- [ ] 不再假设 `finalizedByFleet=true` 等于「有过最终方案」,语义已收窄为「对当前需求定稿」
|
||||
- [ ] `assignmentSlots` 按 `assignmentStatus` 区分渲染,容忍 `canceled` 槽位出现
|
||||
- [ ] 「已派几台车」一律取 `assignmentProgress`,不要自己数 `assignmentSlots` 长度
|
||||
- [ ] 待派车 tab 会新增这类换版订单,确认列表/角标/急单分组渲染正常
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户