比较提交
277
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
36a85b8669 | ||
|
|
43b9c16a36 | ||
|
|
fd76eea701 | ||
|
|
49f67134fd | ||
|
|
314c228654 | ||
|
|
3e8ea93caf | ||
|
|
33c0aa2c08 | ||
|
|
6831ec3128 | ||
|
|
42a16b78c6 | ||
|
|
257ecb1233 | ||
|
|
e9b52319c9 | ||
|
|
4060c65261 | ||
|
|
0fcf0801cf | ||
|
|
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 |
@@ -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` 安全空态场景暂无法端到端复现,该场景行为以合并代码 + 部署点位确认;如前端联调需要真实数据请联系后端造数。
|
||||
|
||||
@@ -5,16 +5,16 @@ title: "核单餐食餐厅下拉值独立存储"
|
||||
consumer: "admin"
|
||||
author: "yst"
|
||||
change_type: "修改接口"
|
||||
backend_status: "merged"
|
||||
gateway_status: "pending"
|
||||
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-07"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
status_note: "PR #5600 已合并 dev-v3,尚未部署测试服;restaurantId 关联资源服务餐厅下拉接口 PR #5583"
|
||||
status_note: "PR #5600 已合并 dev-v3;2026-08-10 复核确认测试服已部署并验证(见文末验证证据章节)。restaurantId 关联资源服务餐厅下拉接口 PR #5583"
|
||||
---
|
||||
|
||||
# 核单餐食餐厅下拉值独立存储(不覆盖餐食名称)
|
||||
@@ -298,7 +298,7 @@ Content-Type: application/json
|
||||
|
||||
- **破坏性兼容**:无。两个字段均为可选新增;存量请求不传两字段时行为完全不变;存量数据两列为 NULL
|
||||
- **前端同步上线**:不强制同步。前端未接新字段前,保存 / 回显行为与之前一致(回显多两个 null 字段,不读即可);前端接新字段需等后端部署后联调
|
||||
- **后端部署状态**:PR #5600 已合并 dev-v3,**尚未部署测试服**(见 Front Matter backend_status / status_note)
|
||||
- **后端部署状态**:PR #5600 已合并 dev-v3,**测试服已部署**(2026-08-10 复核确认,见文末验证证据章节)
|
||||
- **回滚方案**:后端回滚 = 下线两字段即可。回滚时前端需同步停止传这两字段;存量数据两列本就为 NULL 或历史快照值,无数据迁移、无回滚 SQL
|
||||
|
||||
## 12. 注意事项
|
||||
@@ -317,3 +317,12 @@ Content-Type: application/json
|
||||
- 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 源码 + 部署点位三重证据确认。
|
||||
|
||||
@@ -5,15 +5,15 @@ title: "订单详情补核单状态字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst(GIT)"
|
||||
backend_status: "implemented"
|
||||
gateway_status: "pending"
|
||||
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(merge commit c378061b08);管理后台待接入 reviewStatus/reviewStatusName。"
|
||||
updated_at: "2026-08-07"
|
||||
status_note: "PR #5635 已合并 dev-v3;2026-08-10 复核确认测试服已部署、reviewStatus/reviewStatusName 经网关实测在出参中(见文末验证证据章节)。管理后台待接入。"
|
||||
updated_at: "2026-08-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -275,3 +275,10 @@ Authorization: Bearer <管理后台访问令牌>
|
||||
- **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 边界示例)。
|
||||
|
||||
@@ -38,9 +38,9 @@ generated: "2026-08-09T16:20:00+08:00"
|
||||
3. order 侧最终确认 fence 占用(582092,暂时性)被快照投递按业务错误隔离而非重试;
|
||||
4. 隔离事件计入前驱阻塞 → 后续 revision 永久 PENDING;
|
||||
5. order 侧未初始化(contract_version 为空)强绑 revision=1,fleet 重发更高 revision 永远冲突。
|
||||
- **修复**:#5745/#5747/#5748/#5749/#5753 五 PR(dev-v3),fleet 3 处 + order 1 处 + 补偿端点;
|
||||
- **测试**:AssignmentServiceTest 407/407、FactoryTest 12/12、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=5**;探针 4/4 PASS(evidence/5744/gateway-probe-output.txt)。
|
||||
- **修复**:#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)与纯逐日回归保持。
|
||||
|
||||
## 前端交接
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ title: "后端已修复:房型 BIG_BED 全链路中文(#5730)+ 派车通
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "released"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
@@ -22,13 +22,15 @@ generated: "2026-08-09T11:40:00+08:00"
|
||||
|
||||
> 两个后端修复已合并 dev-v3、部署 TEST 并验证通过。告知前端知晓/验证/配合接入。
|
||||
|
||||
## ① #5730 房型 BIG_BED 全链路中文展示
|
||||
## 变更内容
|
||||
|
||||
### ① #5730 房型 BIG_BED 全链路中文展示
|
||||
|
||||
**修复**:`room_category` 字典补充 BIG_BED→「大床房」中文 label,行程/住宿安排/配房等展示处 `roomCategoryLabel` 现在映射到中文,不再显示英文 BIG_BED。
|
||||
|
||||
**前端影响**:**无需改代码**。原先住宿安排/行程里房型显示「BIG BED1」的位置,现在应显示「大床房」。请前端**验证**房型中文展示是否已正常(如订单 26-8083 娜仁托娅的住宿安排)。
|
||||
|
||||
## ② #5732 派车通知预览(render-batch)日期按接续段分段
|
||||
### ② #5732 派车通知预览(render-batch)日期按接续段分段
|
||||
|
||||
**修复**:`render`/`render-batch` 渲染的「日期」(order.startDate/endDate/days 占位符)在传入 `serviceDates` 非空时按 **min~max 分段**,未传时回退订单全程;与实际 HOLD 通知快照口径一致。
|
||||
|
||||
|
||||
@@ -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。前端无需配合。
|
||||
@@ -7,12 +7,12 @@ author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
frontend_ref: "fa59235d"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "房务订单详情三个 tab 全空(定制师需求 0/配房行程暂无/操作日志 0),但后端接口数据完整返回。疑似前端 adapter(orderDetailAdapter.js)或组件没正确映射/渲染后端数据。待前端排查修复。"
|
||||
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"
|
||||
|
||||
@@ -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,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 会新增这类换版订单,确认列表/角标/急单分组渲染正常
|
||||
@@ -0,0 +1,284 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5827"
|
||||
title: "⚠️ 派车取消「司机确认」环节:提交即派定、行程单短链直接生成;新增 sendItinerarySms 勾选字段;driver-confirmation 端点下线"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "PR #5873 已合并 dev-v3 并部署测试服(19:22:57 构建,jar 早于进程启动,确认跑新代码)。测试服真实 API 实测通过:driver-confirmation 端点 404 已下线;不传 holdMode 不再 400;派车响应 assignmentStatus=assigned + confirmedAt 有值(提交即派定);未传 sendItinerarySms 时 itinerarySmsStatus=NOT_SENT 且 outbox 无 ITINERARY_SMS/HOLD_NOTIFICATION(不勾不发、Step3 不发通知);行程单链接派车后立即可打开(HTTP 200,带真实定稿代际);看板 progressSteps 三步、holdNotificationStatus=NOT_REQUIRED、状态文案已更新。实测使用无人占用的测试单,验证后已取消还原。前端 2026-08-12 落地:向导 4 步改 3 步(订单详情/排车/确认执行),删 Step3DriverConfirm 与 useDispatchMessage 整套,三写端点顶层加 sendItinerarySms,存量 holding 恢复落第 3 步只读 confirm。fleet/board 35 文件 418 用例全绿。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# ⚠️ 车务派车取消「司机确认」环节 —— 一步到位派定(#5827)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **性质**: **破坏性变更**(流程步骤减少、端点下线、默认行为变更),前端必须跟进
|
||||
|
||||
---
|
||||
|
||||
## 一、业务变化(一句话)
|
||||
|
||||
**派车提交即派定**:不再有「等司机回复确认 → 车务登记司机已确认 → 车务确认执行」这一段,提交后当场落 `assigned`、当场生成行程单短链,车务**不用再点第二次**。
|
||||
|
||||
配套:**Step3 不再自动给司机发「排车待确认」通知**;行程短信改为**只在车务显式勾选时发这一次**。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| `POST` | `/admin/fleet/assignments` | 请求新增 `sendItinerarySms`;响应新增三字段;`holdMode` 摘 `@NotNull` 并废弃;提交即派定 |
|
||||
| `POST` | `/admin/fleet/assignments/batch` | 同上(`sendItinerarySms` 放 body 顶层,批次级统一决策) |
|
||||
| `POST` | `/admin/fleet/assignments/{assignmentId}/change` | 同上;不勾选不再给司机重发行程短信 |
|
||||
| `POST` | `/admin/fleet/assignments/{assignmentId}/driver-confirmation` | **端点下线** |
|
||||
| `POST` | `/admin/fleet/assignments/{assignmentId}/confirm` | 契约文案更新(不再要求「已登记司机确认」);新流程下非必经步骤 |
|
||||
| `POST` | `/admin/fleet/assignments/requirements/{requirementId}/confirm` | 同上 |
|
||||
| `POST` | `/admin/fleet/assignments/{assignmentId}/cancel` | 错误码清单更正:605024 → **605028** |
|
||||
| `GET` | `/admin/fleet/board/orders/{orderId}` | `holdNotificationStatus` / `currentStep` / `stageCode` / 状态说明文案语义变更 |
|
||||
|
||||
## 三、⚠️ 前端必须做的四件事
|
||||
|
||||
### 1. 删掉「待司机确认」开关
|
||||
|
||||
派车表单底部的「待司机确认 · 先锁定司机并发送通知」开关(`holdMode`)**请删除**。
|
||||
|
||||
- 后端已**摘除该字段的 `@NotNull`**,不传不会再 400;
|
||||
- 字段保留兼容但**服务端一律忽略**(传任何值都按一步派定处理)。
|
||||
|
||||
### 2. 删掉「等待司机回复确认」整块 UI
|
||||
|
||||
- 司机回复摘要输入框
|
||||
- 司机确认凭证上传(最多 10 个)
|
||||
- 「登记司机已确认」按钮
|
||||
|
||||
对应端点 **`POST /admin/fleet/assignments/{assignmentId}/driver-confirmation` 已下线**。
|
||||
|
||||
### 3. 新增「发送行程短信」勾选,并在派车/改派时传 `sendItinerarySms`
|
||||
|
||||
**这是本次最关键的行为变更。** 详见第四节。
|
||||
|
||||
### 4. 派车向导由 4 步改为 3 步
|
||||
|
||||
「订单详情 → 排车 → 确认执行」。后端 `currentStep` 已由 4 改 3,`stageCode` 不再产出 `driver_confirmed`。
|
||||
|
||||
---
|
||||
|
||||
## 四、新增字段 `sendItinerarySms`(三个写端点)
|
||||
|
||||
### 请求
|
||||
|
||||
| 端点 | 位置 | 字段 | 类型 | 必填 | 缺省 |
|
||||
|---|---|---|---|---|---|
|
||||
| `POST /admin/fleet/assignments` | body 顶层 | `sendItinerarySms` | Boolean | 否 | **不传 = `false` = 不发** |
|
||||
| `POST /admin/fleet/assignments/batch` | **body 顶层**(不是 items 里) | `sendItinerarySms` | Boolean | 否 | 不传 = `false` |
|
||||
| `POST /admin/fleet/assignments/{assignmentId}/change` | body 顶层 | `sendItinerarySms` | Boolean | 否 | 不传 = `false` |
|
||||
|
||||
> 批量为**批次级统一决策**:整批用同一个值,避免同一需求同一代际内 `itinerary_sms_decision` 不一致。
|
||||
|
||||
### 响应(三个端点新增,与 `ConfirmRespVO` 同口径)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `sendItinerarySms` | Boolean | 本次是否选择发送行程短信 |
|
||||
| `itinerarySmsEventId` | String(雪花,字符串序列化) | 行程短信事件 ID;未发送时为 `null` |
|
||||
| `itinerarySmsStatus` | String | `PENDING`(已入队待发) / `NOT_SENT`(未勾选,不发) |
|
||||
|
||||
### 🔴 行为口径(务必理解)
|
||||
|
||||
- **不勾(含不传)→ 一条司机短信都不发**。
|
||||
- **行程单短链与短信无关**:不发短信也照样生成短链、照样可打开。
|
||||
- 改派同理:**「只改计费日期 / 只改车费」不勾就不会给司机重发短信**(此前会重发)。
|
||||
- 改派的幂等载荷哈希**不含**该字段:同一 `requestId` 改了勾选再提交会返回已冻结的旧回执,**换新 `requestId` 即生效**。
|
||||
|
||||
### 请求示例
|
||||
|
||||
```json
|
||||
POST /admin/fleet/assignments
|
||||
Authorization: Bearer <admin token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 2086270138171994114,
|
||||
"vehicleId": 2064998142394183681,
|
||||
"driverId": 2065272145633591298,
|
||||
"startDate": "2026-08-17",
|
||||
"endDate": "2026-08-19",
|
||||
"headcount": 10,
|
||||
"sendItinerarySms": true,
|
||||
"requestId": "f3c1c8e2-0a1a-4f2b-9a77-1b2c3d4e5f60"
|
||||
}
|
||||
```
|
||||
|
||||
### 成功响应示例(**测试服实测原样**,未传 `sendItinerarySms` / 未传 `holdMode`)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2086824442578542594",
|
||||
"assignmentGroupId": "2086824442578542594",
|
||||
"assignmentSlotId": "345214258881105920",
|
||||
"assignmentStatus": "assigned",
|
||||
"stageCode": "assigned",
|
||||
"stageLabel": "已派车",
|
||||
"currentStep": 3,
|
||||
"skippedStepCodes": [],
|
||||
"protocolPrice": "800.00",
|
||||
"vehicleFeeAutoTotal": "2400.00",
|
||||
"vehicleFeeAutoComplete": true,
|
||||
"vehicleFeeTotal": "2400.00",
|
||||
"vehicleFeeSource": "AUTO",
|
||||
"vehicleFeeAdjustmentReason": null,
|
||||
"dailyVehicleFees": null,
|
||||
"holdSentAt": null,
|
||||
"confirmedAt": "2026-08-11 19:28:02",
|
||||
"sideEffects": {
|
||||
"vehicleStatusUpdated": "busy",
|
||||
"driverStatusUpdated": "busy",
|
||||
"reconPrepRowsCreated": 0,
|
||||
"reconPrepMarkedCanceled": null
|
||||
},
|
||||
"sendItinerarySms": false,
|
||||
"itinerarySmsEventId": null,
|
||||
"itinerarySmsStatus": "NOT_SENT",
|
||||
"dailyDifferences": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
勾选发短信时,仅这三个字段不同:
|
||||
|
||||
```json
|
||||
"sendItinerarySms": true,
|
||||
"itinerarySmsEventId": "345528786529423360",
|
||||
"itinerarySmsStatus": "PENDING"
|
||||
```
|
||||
|
||||
### ⚠️ 行程单链接不在派车响应里
|
||||
|
||||
派车响应**不含** `itineraryUrl`。行程单链接在**看板订单详情**里取:
|
||||
|
||||
```
|
||||
GET /admin/fleet/board/orders/{orderId}
|
||||
→ data.currentAssignment.itineraryUrl
|
||||
```
|
||||
|
||||
**实测**:派车成功后立刻读该字段即有值,且链接**当场可打开**(HTTP 200 返回完整行程数据),token 内 `dispatchPlanGeneration` 为**真实定稿代际**(不是待确认期的 `0` 哨兵)。
|
||||
|
||||
**未勾选发短信时链接同样可用** —— 行程单链接与是否发短信无关。
|
||||
|
||||
> 补充说明:`fleet_itinerary_short_link` 里的**短码**(`/s/xxxx` 形式)只在**发短信时**才铸造(它只服务于短信长度限制)。不发短信时使用的是上面这个完整 token 链接,功能等价、当场可用。
|
||||
|
||||
---
|
||||
|
||||
## 五、废弃字段(保留兼容,服务端忽略)
|
||||
|
||||
| 字段 | 所在端点 | 现在的行为 |
|
||||
|---|---|---|
|
||||
| `holdMode` | create / batch / change | **已摘 `@NotNull`**,传任何值都被忽略,一律按一步派定落库 |
|
||||
| `messageTemplateId` | create / batch.items / change | 已废弃(不再发「待司机确认」通知,无模板可冻结) |
|
||||
| `customBody` | create / batch.items / change | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 六、下线端点
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `POST` | `/admin/fleet/assignments/{assignmentId}/driver-confirmation` | 「登记司机已确认」,随司机确认环节一并下线 |
|
||||
|
||||
---
|
||||
|
||||
## 七、响应语义变更(读侧)
|
||||
|
||||
| 位置 | 原来 | 现在 |
|
||||
|---|---|---|
|
||||
| 派车/批量/改派响应 `assignmentStatus` | 可能是 `holding` | **恒 `assigned`** |
|
||||
| 派车/批量/改派响应 `skippedStepCodes` | 可能非空 | **恒空数组**(字段保留兼容) |
|
||||
| 派车/批量/改派响应 `holdSentAt` | 可能有值 | **恒 `null`** |
|
||||
| 派车/批量/改派响应 `confirmedAt` | 仅确认后才有 | **恒有值**(提交即派定) |
|
||||
| 看板 `currentAssignment.holdNotificationStatus` | `assigned`/`completed` 行恒返回 `PENDING` | **恒返回 `NOT_REQUIRED`**(此前会诱导车务点无效重发) |
|
||||
| 生命周期 `currentStep` | 四阶段 / 4 | **三阶段 / 3** |
|
||||
| 生命周期 `stageCode` | 可能是 `driver_confirmed` | **不再产出该派生态**(时间轴事件类型 `driver_confirmed` 仍保留,用于存量数据回显) |
|
||||
| `holding` 状态中文名 | 「待确认」 | **「待确认执行」**(新流程下只有存量数据会停在该态) |
|
||||
| 看板状态筛选项「已派车」说明 | 「司机已确认,派车已生效」 | **「车务已派定,行程单已生成」** |
|
||||
|
||||
---
|
||||
|
||||
## 八、错误码变化
|
||||
|
||||
### 从契约中移除(不会再抛出,常量保留仅供历史数据字典对照)
|
||||
|
||||
| 码 | 原含义 |
|
||||
|---|---|
|
||||
| `605024` | 排车模式不合法 |
|
||||
| `605025` | 请先登记司机已确认接单 |
|
||||
|
||||
### 更正(此前文档错标)
|
||||
|
||||
`POST /admin/fleet/assignments/{assignmentId}/cancel` 的错误码清单里原写「605024 取消生效日无效」是**错标**(605024 的真实含义是排车模式不合法),已更正为:
|
||||
|
||||
| 码 | 含义 |
|
||||
|---|---|
|
||||
| `605028` | 生效日期不在派单服务日期范围内 |
|
||||
|
||||
---
|
||||
|
||||
## 九、不变的部分(免得误删)
|
||||
|
||||
- **行程单短链**照常生成,且带真实定稿代际;短链身份与「是否发短信」无关。
|
||||
- **时间轴事件类型 `driver_confirmed`** 保留(存量数据回显用)。
|
||||
- **取消 / 撤销取消 / 改派 / 逐日派车**的既有入参与语义不变。
|
||||
- 保险投保时序、槽位锁互斥、占用释放四条链路不变。
|
||||
|
||||
---
|
||||
|
||||
## 十、验证证据
|
||||
|
||||
### 测试服实测(2026-08-11 19:22 部署,19:28 实测)
|
||||
|
||||
部署核实:服务器仓库含合并提交 `1c46e6abf`;fleet jar 构建于 19:22:57,两个实例进程分别启动于 19:23:08 / 19:23:25(**jar 早于进程,确认跑的是新代码**);8087 / 8187 健康检查均 200。
|
||||
|
||||
| 验证项 | 方法 | 结果 |
|
||||
|---|---|---|
|
||||
| `driver-confirmation` 端点已下线 | `POST .../driver-confirmation` | **HTTP 404「接口不存在」** ✅ |
|
||||
| 不传 `holdMode` 不再 400 | 派车请求省略该字段 | **code 200** ✅ |
|
||||
| **提交即派定** | 派车响应 | `assignmentStatus: "assigned"` + `confirmedAt: "2026-08-11 19:28:02"`,**无需第二次调用** ✅ |
|
||||
| 向导 3 步 | 派车响应 + 看板 | `currentStep: 3`;看板 `progressSteps = [ORDER_DETAIL, DISPATCH, CONFIRM_EXECUTE]` ✅ |
|
||||
| `skippedStepCodes` 恒空 / `holdSentAt` 恒 null | 派车响应 | `[]` / `null` ✅ |
|
||||
| **不勾不发** | 未传 `sendItinerarySms` | `sendItinerarySms: false`、`itinerarySmsStatus: "NOT_SENT"`、`itinerarySmsEventId: null` ✅ |
|
||||
| **Step3 不发任何通知** | 查 outbox 全部事件 | 只有 `OCCUPANCY_RECOMPUTE` + `ASSIGNED`(均 SUCCESS),**无 `ITINERARY_SMS`、无 `HOLD_NOTIFICATION`** ✅ |
|
||||
| **行程单链接直接可用** | 看板 `itineraryUrl` → 直接 GET | **HTTP 200 返回完整行程**,token 内 `dispatchPlanGeneration` 为真实定稿代际 ✅ |
|
||||
| `holdNotificationStatus` 修复 | 看板 `currentAssignment` | 已派定行返回 **`NOT_REQUIRED`**(此前恒 `PENDING`)✅ |
|
||||
| 状态说明文案 | 看板 `statusOptions` | `holding` →「车务已排车,待车务确认执行」;`assigned` →「车务已派定,行程单已生成」✅ |
|
||||
| 占用副作用 | 派车响应 `sideEffects` | 车与司机均置 `busy` ✅ |
|
||||
|
||||
> 实测用的是一张**无人使用的测试单**(非现场在用订单),验证完毕已通过业务接口取消还原,占用已释放、无脏数据残留。
|
||||
|
||||
### 后端质量门禁(rebase 到最新 dev-v3 后)
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| `mvn -pl hl-fleet-service clean test-compile` | BUILD SUCCESS |
|
||||
| `mvn -pl hl-fleet-service test -Dtest=*ArchTest` | Tests run **13**,Failures 0 |
|
||||
| `mvn -pl hl-fleet-service spotless:check` | BUILD SUCCESS |
|
||||
| `mvn -pl hl-fleet-service test`(全量) | Tests run **3605**,Failures **0**,Errors **0**,Skipped 5(Docker 门控自跳过的容器类 IT) |
|
||||
|
||||
---
|
||||
|
||||
## 十一、关联
|
||||
|
||||
- 工单 #5827
|
||||
- 换版槽位人工制:#5810
|
||||
- 换版后看板缺口:#5824
|
||||
- 换版即作废定稿:#5851
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5830"
|
||||
title: "房务订单详情新增「订单详情」Tab:景点行程 tripItinerary + 出行人 travelers + 大交通 transport"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "dbec5600"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(dbec5600):OrderDetailModal 新增第 4 个「订单详情」Tab(徽标 tabCounts.tripItineraryCount 回退 tripItinerary.length),新建 OrderDetailPanel 渲染出行人表(脱敏 nameMasked 优先)/大交通表/每日安排(tripItinerary NSteps 节点),复用车务 fleet transport/display 纯函数同口径对齐 Step1;orderDetailReady=false 整块提示「订单信息加载失败,请稍后刷新」不误显「没有出行人」,ready=true 空数据显示「暂无」;orderDetailAdapter 透传 tripItinerary/travelers/transport/orderDetailReady + tripItineraryCount,既有 itinerary(配房住宿视角)一字未动。测试:OrderDetailPanel.spec 4 + orderDetailAdapter.spec 4(透传/降级/count 优先/transport 数组归 null),housekeeper 组件 84 全绿,checkpoint 5 文件含生产构建全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务订单详情新增「订单详情」Tab(#5830)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #5832(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-11
|
||||
> **背景**: 房务订单详情此前只有 3 个 Tab(配房行程 / 定制师需求 / 操作日志),房务看不到出行人与大交通,行程也只有住宿视角。wx 要求对齐车务派单弹窗 Step1「订单详情」的三块内容。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
`GET /admin/house/orders/{orderId}` —— **根级新增 4 个字段**:
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `tripItinerary` | `List<ItineraryDayForFleetDTO>` | 否(降级时空数组) | **景点/餐食每日安排**(与车务 Step1 同源) |
|
||||
| `travelers` | `List<OrderTravelerForFleetDTO>` | 否(降级时空数组) | 出行人,**脱敏版**(`nameMasked` 掩码、无证件/生日明文) |
|
||||
| `transport` | `OrderTransportForFleetDTO` | 是 | 大交通(往返方向 / 交通方式 / 时间 / 路线 / 班次 / 关联出行人) |
|
||||
| `orderDetailReady` | `Boolean` | 否 | 三块是否取数成功,见下方说明 |
|
||||
|
||||
### 🔴 不要和 `itinerary` 搞混
|
||||
|
||||
`itinerary` 字段**依旧是「配房行程」Tab 的住宿视角数据**(`ItineraryDay`:`stayDate` / `cityCode` / `arrange` 配房状态…),本次**一字未动**。新的景点行程叫 **`tripItinerary`**,两者结构完全不同,不要互换或复用。
|
||||
|
||||
### `tabCounts` 新增 key
|
||||
|
||||
```json
|
||||
{
|
||||
"itineraryCount": 2, // 配房行程 Tab(不变)
|
||||
"messageCount": 1, // 留言(不变)
|
||||
"unreadMessageCount": 0, // 留言未读红点(不变)
|
||||
"operationLogCount": 4, // 操作日志(不变)
|
||||
"tripItineraryCount": 3 // 【新增】订单详情 Tab 徽标 = 景点行程天数
|
||||
}
|
||||
```
|
||||
|
||||
### `orderDetailReady` 怎么用
|
||||
|
||||
三块数据是**软依赖**:订单核心侧取数异常时各自降级为空集合/null,房务详情接口**不会 500**(房务主职责是配房,不该被订单核心故障拖垮)。
|
||||
|
||||
但降级后的空数据与「客人确实没录出行人/没填大交通」**长得一模一样**。`orderDetailReady` 用来区分:
|
||||
|
||||
- `true` = 取数成功。此时字段为空就是**真的没有**,正常显示「暂无出行人 / 暂无大交通」
|
||||
- `false` = 取数失败。前端应提示「订单信息加载失败,请稍后刷新」,**不要**显示成「该订单没有出行人」
|
||||
|
||||
实测印证:某订单 `transport` 各字段全 null(客人确实没录大交通),但 `orderDetailReady=true`,属于第一种情况。
|
||||
|
||||
---
|
||||
|
||||
## 前端要做
|
||||
|
||||
新增第 4 个 Tab「订单详情」,渲染三块,样式对齐车务派单弹窗 Step1:
|
||||
|
||||
1. **出行人信息**表:姓名(已脱敏)/ 所属地 / 人员类型 / 性别 / 年龄 / 国籍民族 / 关联班次
|
||||
2. **大交通**表:方向(抵达接团 / 返程送站)/ 交通方式 / 时间 / 路线 / 班次 / 出行人
|
||||
3. **每日安排**(`tripItinerary`):按天展示景点、餐食等行程明细
|
||||
|
||||
Tab 徽标用 `tabCounts.tripItineraryCount`。只读入口下同样只展示、无操作。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 测试服(网关 9443)实测真实房务订单:
|
||||
|
||||
```
|
||||
tripItinerary = 3 天
|
||||
travelers = 1 人(nameMasked = "娜***",脱敏正确)
|
||||
transport = 返回对象(该单客人未录大交通,各字段 null)
|
||||
orderDetailReady = true
|
||||
itinerary(配房行程)= 2 天 ← 无回归
|
||||
tabCounts = {"itineraryCount":2, "messageCount":1, "unreadMessageCount":0,
|
||||
"operationLogCount":4, "tripItineraryCount":3}
|
||||
```
|
||||
|
||||
单测:`hl-order-service-v3` 全量 **6374 / Failures 0**(3 个 Errors 为 surefire 单 JVM 后期 OOM 的环境性问题,单独重跑绿);`HouseDetailAggregatorTest` 80/80;专守循环依赖的 `OrderServiceCircularDependencyContextIT` 绿;ArchTest 绿。
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5838"
|
||||
title: "用房需求每晚支持「客户自订」:days[].customerSelfBooked + 错误码 582097/582098"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "f3b08c5d"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,f3b08c5d),跨定制师/房务两端:①提交侧 FunItemAdjustModal 每晚加「客户自订」开关(打开隐藏候选区改自订说明+备注,关闭恢复候选),serialize 自订晚下发 customerSelfBooked:true+segments 空(有备注保留 1 仅备注段),buildRequirementDays/v3Adapter 双向透传标记与备注;②校验自订晚跳过指定酒店房数校验,非自订晚「至少 1 候选」拦截不变;③错误码 582097/582098 实证零额外编码(submitAdjustment silentError 仍 reject 且 error.message=后端文案,catch 透传);④房务端 orderDetailAdapter 透传标记,ItineraryPanel 空态分支渲染「客户自订」warning 色(机读 arrange 仍 pending 零改动)。进度分母/日历排除自订晚为后端口径,前端只读零改动。hotelRequirement.spec +4、FunItemAdjustModal.spec +2、orderDetailAdapter.spec +1、ItineraryPanel.spec +2,318 全绿,checkpoint 含生产构建全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 用房需求每晚支持「客户自订」(#5838)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #5872(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-11
|
||||
> **背景**: 调整订单弹窗「酒店安排」里,某晚不选酒店只显示「未指定酒店」,语义是「留空由房控安排」。客人自己订了酒店的那晚没法表达,房务分不清「等我配」和「不用我配」。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 1. 提交侧:`days[]` 新增 `customerSelfBooked`
|
||||
|
||||
涉及 **提交用房需求** 与 **调整订单(hotelRequirement)** 两个入口(共用同一 `DayHotelReq` 结构):
|
||||
|
||||
```json
|
||||
"days": [
|
||||
{ "dayNumber": 1, "segments": [ ... ] },
|
||||
{ "dayNumber": 2, "customerSelfBooked": true, "segments": [] }, // 客人自己订,房务不用配
|
||||
{ "dayNumber": 3, "segments": [ ... ] }
|
||||
]
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `customerSelfBooked` | `Boolean` | 整晚级。`true`=该晚客人自己订酒店、房务无需配房;`false`/不传=原有语义(要配) |
|
||||
|
||||
**校验变化**:
|
||||
- `customerSelfBooked=true` 的晚 **允许 `segments` 为空**(也可只填备注,如「客人自订汉庭,勿重复订」)
|
||||
- 非自订晚 **仍必须至少 1 段**,否则返回 **`582098`**「第{N}晚缺少用房需求,请填写房间需求或标记为客户自订」
|
||||
|
||||
> 注:该校验原是 bean 层 `@NotEmpty`(HTTP 422),因 bean 校验读不到同级 `customerSelfBooked` 无法条件化,已下沉 Service 改为业务错误码(项目 HTTP 恒 200 + 错误码口径)。**前端原本处理 422 的分支要改为处理 582098。**
|
||||
|
||||
### 2. 新增错误码 `582097`:已配房的晚不能改成客户自订
|
||||
|
||||
```
|
||||
582097 第{N}晚已配房,不能改为客户自订,请先删除该晚配房
|
||||
```
|
||||
|
||||
**判定含「询房中」**(不只已确认):询房中同样已占用房务人力、可能已向酒店发出询问。
|
||||
|
||||
**为什么 fail closed**:该晚已产生配房行,可能已扣库存、已与酒店确认并产生成本。放行会让配房行变成没有需求对应的孤儿(房务侧看不到、也无从追责),库存也无人释放。要求定制师先显式删配房再改标记,让「谁在什么时候放弃了这间房」留下痕迹。
|
||||
|
||||
### 3. 房务侧:`GET /admin/house/orders/{orderId}` 配房行程透出标记
|
||||
|
||||
`itinerary[]`(配房行程 Tab)每晚新增:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `customerSelfBooked` | `Boolean` | 该晚是否客户自订 |
|
||||
|
||||
同时该晚的 `arrangeLabel` 由「待配房」改为 **「客户自订」**。
|
||||
|
||||
> 🔴 **机读字段 `arrange` 仍然是 `pending`,没有新增枚举值**。前端所有按 `arrange` 取值的既有分支**零改动**;要专门呈现自订晚,请读 `customerSelfBooked` 布尔字段。这是刻意为之,避免新增枚举值打挂既有渲染。
|
||||
|
||||
### 4. 自订晚被排除出三处房务口径
|
||||
|
||||
| 口径 | 变化 |
|
||||
|---|---|
|
||||
| 配房进度分母 `progress.totalCount` | 排除自订晚。3 晚含 1 晚自订 → 显示 **2/2** 而非 2/3(否则房务永远看到「差一晚」) |
|
||||
| `progress.unarrangedDayNumbers`(#5842 新增) | 排除自订晚。否则最终确认的二次确认弹窗会提示「第 2 晚未配房」——而那晚根本不用配 |
|
||||
| 配房日历「待配房」桶 | 排除自订晚。否则日历上会挂一个永远做不完的格子 |
|
||||
|
||||
`unconfirmedDayNumbers` 由同一集合派生,一并受益。
|
||||
|
||||
**抢单池与待办不受影响**(抢单池按需求维度、不迭代 days;带 dayNumber 的待办是「酒店回复超时」,由真实配房行驱动,自订晚没有配房行自然不产生)。
|
||||
|
||||
---
|
||||
|
||||
## 前端要做
|
||||
|
||||
1. **调整订单弹窗「酒店安排」每晚增加「客户自订」开关/按钮**(wx 截图指定位置:该晚「+ 加候选酒店」旁)。打开后该晚的酒店/房型/预算区域可隐藏或置灰,备注仍可填。
|
||||
2. 提交时对自订晚传 `customerSelfBooked: true`,`segments` 可为空数组。
|
||||
3. **错误码处理**:
|
||||
- `582098` → 提示该晚缺用房需求(原 422 分支改这里)
|
||||
- `582097` → 提示"该晚已配房,请先删除配房再改为客户自订"
|
||||
4. **房务侧配房行程 Tab**:`customerSelfBooked=true` 的晚渲染为「客户自订」样式(后端 `arrangeLabel` 已给文案),与「待配房」在视觉上区分开——这是本需求的核心目的。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 测试服(网关 `https://api.test.1814.love:9443`,房务管理员 token)实测订单 26-0543:
|
||||
|
||||
```
|
||||
itinerary[0].customerSelfBooked = false ← 字段已贯通
|
||||
itinerary[0].arrange = pending ← 机读值未变
|
||||
itinerary[0].arrangeLabel = 待配房 ← 非自订晚文案不变
|
||||
progress.unarrangedDayNumbers = [1, 2] ← 与实际未配房晚一致
|
||||
```
|
||||
|
||||
单测:**双向变异验证**通过(①进度分母排除退回 `days.size()` → 对应用例失败;②读回侧 carry-through 置 null → 换版存活用例失败)。全量 6383 tests / 0 failures / 0 errors;BUILD FAILURE 系长跑尾部 fork 起不来(内存耗尽)导致 134 个类未跑、**其中含合并门禁 ArchTest**,已按包补跑 **1223 项全绿**(ArchTest 门禁 51 项)。
|
||||
|
||||
> 补充:自订晚的端到端写操作(提交带标记的需求)未在真实订单上执行——提交会改动定制师的真实数据。相关行为由单测与变异验证覆盖。
|
||||
@@ -0,0 +1,347 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5840"
|
||||
title: "核单两报表统一实时路径 + 出参新增订单头 orderHeader"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yaosutu(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "后端 PR #5854 已 merge 到 dev-v3(merge commit 452ebd63),已部署测试服并网关实调验证 orderHeader 出参生效(主实例 8086);两核单报表出参统一新增 orderHeader 订单头对象,同时去掉已核单订单读终态快照的分叉、统一实时计算。前端实证 not_required(2026-08-11,mmg):出参纯增量、向后兼容、前端非必须同步上线(§11.1)。grep+源码实证——两报表接口封装 getSettlementGroupReport/getSettlementReimbursementReport(orderV2.js:968/951)纯透传,新增 orderHeader 不破坏现有解包;ReportModal 标题栏/打印头取 props.order.teamNo/productName(ReportModal.vue:30/567),该 order 是核单详情页 getSettlementDetail 已加载的主对象(detail.vue:944),非为标题另发请求,§12「可清理重复请求(非强制)」情形本案不成立、无重复请求可清;报表金额行/明细走 reportState.data,前端当前零读取 orderHeader;行为统一(去快照分叉)对前端透明(§2/§10.2),reportStatus 取值不变,#5739 已清 STALE 死代码无残留;orderHeader null 字段不下发/对象恒下发的可空性约束对零读取的现有代码无影响。可选增强(非本次交付):未来若改由报表自身 orderHeader 承载标题栏(含 customerName/consultantName/departDate/travelerComposition),按自身排期接入。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【✨ 修改接口·管理后台】核单两报表统一实时路径 + 出参新增订单头 orderHeader(#5840)
|
||||
|
||||
> **PR**: #5854 | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-11
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单工作台的两个报表接口 —— 单团核算表、主报账人报账表 —— 此前出参只有金额汇总与行列表,**没有订单识别信息**(订单号 / 团号 / 产品 / 客户 / 出团日期 / 定制师 / 出行人构成),报表弹窗的标题栏只能靠前端从订单详情接口另取数据拼装。
|
||||
|
||||
同时,两个接口此前存在**快照读分叉**:未完成核单的订单走实时组装,已完成核单的订单读 finalize 时落库的历史快照 JSON。两条路径的出参结构虽然对齐过,但任何出参字段演进都要同时维护两套组装代码,容易出现「未核单有某字段 / 已核单没有」的不一致。
|
||||
|
||||
本次变更两件事:
|
||||
|
||||
1. **出参纯增量**:两个接口出参统一新增 `orderHeader` 订单头对象(字段全部来自 order_main 单表,无跨服务 JOIN)。
|
||||
2. **行为统一(对前端透明)**:删除快照读分叉,两个接口**统一从 tab 明细实时计算**。已核单订单的明细在核单完成时已冻结,实时计算结果与历史快照一致,前端对数据源切换无感知;`reportStatus` 取值与含义不变。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group | 修改(出参纯增量 + 数据源统一) | 出参 `data` 新增 `orderHeader` 对象;已核单订单不再读终态快照,统一实时计算 |
|
||||
| 2 | 查询主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 修改(出参纯增量 + 数据源统一) | 出参 `data` 新增 `orderHeader` 对象;已核单订单不再读终态快照,统一实时计算 |
|
||||
|
||||
配套数据字典(前端可调 GET /admin/dict/data/{dictType} 动态渲染中文名):
|
||||
|
||||
| 字典 type | 用途 | 本次状态 |
|
||||
|-----------|------|----------|
|
||||
| product_type | 产品类型中文名(orderHeader.productTypeName 回填源) | 已存在(不变) |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查询单团核算表
|
||||
|
||||
- **使用场景**:核单工作台「单团核算」页签 / 报表弹窗,财务/运营查看单团收入成本毛利全貌
|
||||
- **认证**:管理后台 JWT;房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)无权调用(返 581045)
|
||||
- **幂等性**:是(GET 只读)
|
||||
- **限流**:无
|
||||
|
||||
### 3.2 查询主报账人报账表
|
||||
|
||||
- **使用场景**:核单工作台「主报账人报账」页签 / 报账表弹窗,财务查看主报账人代收 / 垫付 / 预支与转账结论
|
||||
- **认证**:管理后台 JWT;房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)无权调用(返 581045)
|
||||
- **幂等性**:是(GET 只读)
|
||||
- **限流**:无
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数(两接口相同)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderId` | Long | ✅ | 订单 ID,必须 > 0,否则返参数校验错误 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无请求体(两接口均为 GET)。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 单团核算表顶层结构(SettlementGroupReportRespVO)
|
||||
|
||||
响应类型:`Result<SettlementGroupReportRespVO>`(`code=200` 表示成功)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 报表 ID(Long 序列化为字符串,无落库记录时缺省) |
|
||||
| `orderId` | String | 订单 ID(Long 序列化为字符串) |
|
||||
| `reportStatus` | String | 报表状态:`GENERATED`=已生成 / `CONFIRMED`=已确认(取值与含义不变,见 §6.2) |
|
||||
| `orderHeader` | Object | ✨ **本次新增**:订单头,结构见 §5.3 |
|
||||
| `baseOrderAmount` / `otherIncomeAmount` / `discountAmount` / `adjustedReceivableAmount` / `paidAmount` / `actualRefundedAmount` / `netRevenueAmount` / `netReceivedAmount` / `outstandingAmount` | Number | 收入侧金额汇总(不变) |
|
||||
| `hotelCost` / `ticketCost` / `mealCost` / `vehicleCost` / `guideCost` / `photographerCost` / `otherExpenseCost` / `insurancePremium` / `totalCost` / `paidCost` / `unpaidCost` / `grossProfit` / `grossProfitRate` | Number | 成本与毛利汇总(不变) |
|
||||
| `travelerCount` / `perCapitaRevenue` / `perCapitaCost` / `perCapitaProfit` | Number | 人均指标(不变) |
|
||||
| `incomeLines` | Array | 收入行,固定 4 行(不变) |
|
||||
| `costCategories` | Array | 成本分类行,固定 8 行(不变) |
|
||||
| `generatedBy` / `generatedByName` / `generatedAt` / `confirmedBy` / `confirmedByName` / `confirmedAt` | String | 生成 / 确认审计字段(不变,未确认时确认字段缺省) |
|
||||
|
||||
### 5.2 主报账人报账表顶层结构(SettlementReimbursementReportRespVO,当前最终形态)
|
||||
|
||||
响应类型:`Result<SettlementReimbursementReportRespVO>`(`code=200` 表示成功)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `baseInfo` | Object | 基础信息:汇总值 + 主报账人 + 生成/确认审计字段(既有结构,本次不变) |
|
||||
| `orderHeader` | Object | ✨ **本次新增**:订单头,结构见 §5.3 |
|
||||
| `incomeLines` | Array | 收入行(司机代收),无数据固定返回空数组 `[]`(既有结构,本次不变) |
|
||||
| `expenseLines` | Array | 支出行(仅报账人垫付 CASH_PAID 支出),无数据固定返回空数组 `[]`(既有结构,本次不变) |
|
||||
| `advanceLines` | Array | 预支明细行(已审批预支逐条),无数据固定返回空数组 `[]`(既有结构,本次不变) |
|
||||
|
||||
### 5.3 订单头(SettlementOrderHeaderVO)✨ 本次新增,两接口共用
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `orderNo` | String | 是 | 订单号,如 `HL20260730001` |
|
||||
| `teamNo` | String | 否 | 团号;**订金未支付时为 null(该 key 不下发)** |
|
||||
| `productName` | String | 是 | 产品名,如 `呼伦贝尔草原 5 日游` |
|
||||
| `productType` | String | 是 | 产品类型枚举:`CORE` / `ROUTE` / `CUSTOM` / `GROUP`(见 §6.1) |
|
||||
| `productTypeName` | String | 否 | 产品类型中文名(字典 `product_type` 回填;字典缺失为 null,该 key 不下发) |
|
||||
| `customerName` | String | 是 | 客户姓名;**核单财务域看真名,不脱敏** |
|
||||
| `departDate` | String | 是 | 出团日期,格式 `yyyy-MM-dd` |
|
||||
| `consultantName` | String | 是 | 定制师姓名 |
|
||||
| `travelerComposition` | String | 否 | 出行人构成文案:成人→`大`、儿童→`儿童`、幼童→`幼童`、婴儿→`婴儿` 四档,数量为 0 的档不显示,空格拼接(如 `2大 1儿童 1幼童`);**四档全空为 null(该 key 不下发)** |
|
||||
|
||||
> ⚠️ `orderHeader` 对象本身**永远下发**(两接口统一实时计算后不再出现「已核单无头」的情况);但其内部 null 字段因 VO 标注 `@JsonInclude(NON_NULL)` **整体缺省,key 不出现在 JSON 中**,前端按可选字段处理。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 `orderHeader.productType`(字典 `product_type`)
|
||||
|
||||
| 值 | 中文 |
|
||||
|----|------|
|
||||
| `CORE` | 核心产品 |
|
||||
| `ROUTE` | 自驾路书 |
|
||||
| `CUSTOM` | 私人定制 |
|
||||
| `GROUP` | 小蒙马 |
|
||||
|
||||
### 6.2 `reportStatus`(取值与含义不变)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `GENERATED` | 已生成 | 订单未完成核单 |
|
||||
| `CONFIRMED` | 已确认 | 订单已完成核单(SETTLED) |
|
||||
|
||||
> 行为说明:改前 `CONFIRMED` 走终态快照回放、`GENERATED` 走实时组装;**改后两种状态统一实时计算**(已核单订单明细已冻结,实时结果与快照一致),前端无需区分数据源。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| 400 | 参数校验失败 | `orderId` 缺失或 < 1 |
|
||||
| 581007 | 订单不存在 | `orderId` 对应订单不存在或已删除 |
|
||||
| 581045 | 房务角色无权查看订单详情,房务仅可配房 | 房务管理员 / 房务组长角色调用 |
|
||||
|
||||
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||
|
||||
### 8.1 典型成功(orderHeader 全字段填充)
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566778/settlement/reports/group
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**(节选,仅展示本次新增的 orderHeader 与相邻字段,其余金额 / 行列表结构与变更前一致故省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "1958000000000000001",
|
||||
"orderId": "1956112233445566778",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"orderHeader": {
|
||||
"orderNo": "HL20260730001",
|
||||
"teamNo": "T20260730001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-08-15",
|
||||
"consultantName": "李四",
|
||||
"travelerComposition": "2大 1儿童 1幼童"
|
||||
},
|
||||
"baseOrderAmount": 12800.00
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
主报账人报账表同一订单的 `orderHeader` 完全一致:
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566778/settlement/reports/reimbursement
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**(节选顶层结构):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"orderId": "1956112233445566778",
|
||||
"reportStatus": "CONFIRMED",
|
||||
"primaryReporterName": "司机甲",
|
||||
"transferDirection": "REPORTER_TO_COMPANY",
|
||||
"transferAmount": 700.00
|
||||
},
|
||||
"orderHeader": {
|
||||
"orderNo": "HL20260730001",
|
||||
"teamNo": "T20260730001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-08-15",
|
||||
"consultantName": "李四",
|
||||
"travelerComposition": "2大 1儿童 1幼童"
|
||||
},
|
||||
"incomeLines": [],
|
||||
"expenseLines": [],
|
||||
"advanceLines": []
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(订金未支付 / 无出行人构成 → null 字段不下发)
|
||||
|
||||
**场景说明**:订单订金未支付(`teamNo` 为 null)、出行人四档数量全为 0(`travelerComposition` 为 null)、产品类型字典缺失(`productTypeName` 为 null)—— 这三个 key **整体不出现在 JSON 中**(不是返回 null 值)。`orderHeader` 对象本身仍下发。
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566889/settlement/reports/group
|
||||
Authorization: Bearer <admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**(节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": "1956112233445566889",
|
||||
"reportStatus": "GENERATED",
|
||||
"orderHeader": {
|
||||
"orderNo": "HL20260810002",
|
||||
"productName": "阿尔山秋色 3 日游",
|
||||
"productType": "CUSTOM",
|
||||
"customerName": "王五",
|
||||
"departDate": "2026-09-01",
|
||||
"consultantName": "赵六"
|
||||
}
|
||||
},
|
||||
"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(两接口同样拦截)。
|
||||
|
||||
**请求**:
|
||||
|
||||
GET /v3/admin/order/1956112233445566778/settlement/reports/reimbursement
|
||||
Authorization: Bearer <房务角色 admin JWT>
|
||||
(无请求体)
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{ "code": 581045, "msg": "房务角色无权查看订单详情,房务仅可配房", "data": null }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **适用场景**:订单存在即可调,未核单(`GENERATED`)与已核单(`CONFIRMED`)订单出参结构完全一致,**都含 `orderHeader`**
|
||||
- ❌ **不适用场景**:房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)调用 → 581045
|
||||
- ⚠️ **特殊边界**:
|
||||
- `orderHeader.teamNo`:订金未支付的订单没有团号,该 key 不下发,前端渲染团号位置需做空态处理(如显示 `-`)
|
||||
- `orderHeader.travelerComposition`:四档(大 / 儿童 / 幼童 / 婴儿)数量全为 0 时该 key 不下发;不要假设其必存在
|
||||
- `orderHeader.customerName` 为**真名不脱敏**(核单财务域需要核对真实客户),前端不要二次脱敏
|
||||
- 已核单订单的明细在核单完成时已冻结,实时计算结果与历史快照一致;若核单后通过「反确认」重新打开,报表会随明细变动实时变化(与改前快照行为不同,但反确认本身就意味着数据要重算)
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.orderHeader`(两接口) | 无此字段 | ✨ 新增对象,结构见 §5.3;内部 null 字段不下发 |
|
||||
| 单团核算表其余顶层字段 / incomeLines / costCategories | 现状 | **不变** |
|
||||
| 主报账人报账表 baseInfo / incomeLines / expenseLines / advanceLines | 现状 | **不变** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 已核单订单(CONFIRMED)报表数据源 | 读 finalize 时落库的终态快照 JSON | 与未核单订单一致,统一从 tab 明细实时计算(明细已冻结,结果与快照一致) |
|
||||
| 两路径出参一致性 | 快照 / 实时两套组装代码,字段演进可能出现不一致 | 单一实时组装路径,出参永远含 `orderHeader`,不再出现「未核单有头 / 已核单无头」分叉 |
|
||||
| `reportStatus` 取值 | GENERATED / CONFIRMED | **不变** |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。出参纯增量,原有字段名 / 类型 / 结构 / 金额口径零变化;老前端不读 `orderHeader` 不受影响。数据源切换对已核单订单结果无差异(明细已冻结)
|
||||
- **前端是否必须同步上线**:否。前端按自身排期接入订单头展示即可
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #5854
|
||||
- **回滚后清理**:无(无 DDL、无缓存、无脏数据;回滚后已核单订单恢复读终态快照)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- **null 字段整体缺省**:`orderHeader` 内部 null 字段(teamNo / productTypeName / travelerComposition)的 **key 不出现在 JSON 中**,前端按可选字段处理,不要断言 key 必存在、也不要按 `key in obj` 之外的 null 值逻辑处理
|
||||
- `orderHeader` **对象本身永远下发**,不需要判空整个对象
|
||||
- **两个接口的 orderHeader 完全一致**,前端可抽公共组件 / 公共 TS 类型复用
|
||||
- **中文名渲染**:`productTypeName` 后端已回填中文名(字典 `product_type`),可直接展示;如需动态字典渲染,调 `GET /admin/dict/data/product_type`
|
||||
- 若前端此前为报表弹窗标题栏从订单详情接口另行拼装订单号 / 产品名等信息,接入 `orderHeader` 后可清理该重复请求(非强制)
|
||||
- 无历史 workaround 需要清理(本能力此前不存在)
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#5840](https://git.1814.love:8443/wx/HL/issues/5840)
|
||||
- **PR**: [#5854](https://git.1814.love:8443/wx/HL/pulls/5854)
|
||||
- **Merge commit**: [452ebd63](https://git.1814.love:8443/wx/HL/commit/452ebd63fc1a34968e90f259e8e87a18007e2c74)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5842"
|
||||
title: "房务最终确认取消「必须每晚配齐」限制:部分配房/未确认行放行 + 新增未配/未确认晚号字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ddb3b823"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,ddb3b823):orderDetailAdapter 透传 progress 新增 unarrangedDayNumbers/unconfirmedDayNumbers(互斥升序,缺省/非数组归一 []),新增纯函数 buildFinalizeConfirmContent 分列拼接二次确认文案;OrderDetailModal 最终确认按钮 @click 改 onFinalizeClick,两列表非空先弹 dialog.warning 二次确认(确认完成才 doFinalize)、均空保持直接确认;日期错位仍由后端 808183 硬拦+本地重拉清理入口不动。测试:adapter spec +3 透传 +4 文案纯函数用例,housekeeper 44 全绿,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务最终确认:取消「必须每晚配齐」限制(#5842)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #5860(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-11
|
||||
> **背景**: 房务「最终确认」原要求每一晚都配房且全部已确认,否则按钮灰掉并提示「配房与当前行程不一致,请调整后再确认」。现场 26-7666(6 天 5 晚)配了 DAY1/2、DAY3/4/5 待配 → 房务无法收口。wx 要求取消该限制,改为二次确认。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### 1. `GET /admin/house/orders/{orderId}` —— `actions.canFinalize` 放开条件
|
||||
|
||||
**旧行为**:只有「每晚都配齐且全部已确认」或「一晚都没配(客人全程自住)」两种情况可最终确认;**部分配房一律禁用**。
|
||||
|
||||
**新行为**:**部分配房、存在未确认配房行,均可最终确认**。
|
||||
|
||||
| 情况 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 每晚配齐且已确认 | ✅ | ✅ 不变 |
|
||||
| 一晚都没配 | ✅ | ✅ 不变 |
|
||||
| **部分晚没配房** | ❌ 禁用 | ✅ **放开** |
|
||||
| **配了但还没确认** | ❌ 禁用 | ✅ **放开** |
|
||||
| 配房日期与当前行程错位 | ❌ 禁用 | ❌ **仍禁用** |
|
||||
|
||||
`disabledReason` 在放开后不再出现因部分配房产生的文案;日期错位时文案为「配房日期与当前行程不一致,请调整后再确认」。
|
||||
|
||||
> ⚠️ **日期错位仍然硬拦**:这是订单改期后残留的旧配房行,放行会把过期日期的房当成有效配房回传给定制师、直接坏数据(与 `pendingRescheduleAssignments` 互为兜底)。前端不要试图绕过。
|
||||
|
||||
### 2. `progress` 新增两个字段(供二次确认弹窗)
|
||||
|
||||
```json
|
||||
"progress": {
|
||||
"arrangedCount": 1,
|
||||
"totalCount": 5,
|
||||
"unarrangedDayNumbers": [1, 2, 3, 5], // 【新增】还没配房的晚号
|
||||
"unconfirmedDayNumbers": [4] // 【新增】配了但还没确认的晚号
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `unarrangedDayNumbers` | `List<Integer>` | 未配房的晚号,升序 |
|
||||
| `unconfirmedDayNumbers` | `List<Integer>` | 已配房但 `confirmStatus != CONFIRMED` 的晚号,升序 |
|
||||
|
||||
两者**互斥**(同一晚不会同时出现在两个列表里),都为空数组时即配齐且全确认。
|
||||
|
||||
**为什么要加这两个字段**:`arrangedCount/totalCount` 只有数量,说不出「缺的是第 3、4、5 晚」,更完全无法表达「配了但酒店还没回复确认」——而这恰恰是本次放开的两类情况,二次确认文案必须分开说,否则房务不知道自己在确认什么。
|
||||
|
||||
---
|
||||
|
||||
## 前端要做
|
||||
|
||||
`canFinalize.enabled` 为 true 且 `unarrangedDayNumbers` 或 `unconfirmedDayNumbers` **非空**时,点「最终确认」先弹二次确认,文案把两类情况分开列,例如:
|
||||
|
||||
```
|
||||
还有第 1、2、3、5 晚未配房,第 4 晚已配房但酒店尚未确认。
|
||||
确认要完成配房吗?
|
||||
```
|
||||
|
||||
两个列表都为空时(配齐且全确认)保持原有直接确认流程,不弹二次确认。
|
||||
|
||||
`disabledReason` 直接透传展示即可,不需要再针对「配房与当前行程不一致」做特殊分支——该文案现在只在真正的日期错位时出现。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 测试服(网关 `https://api.test.1814.love:9443`,房务管理员 token)实测两个真实的部分配房订单:
|
||||
|
||||
| 订单 | 进度 | `unarrangedDayNumbers` | `unconfirmedDayNumbers` | `disabledReason` |
|
||||
|---|---|---|---|---|
|
||||
| 26-3420 | 1/5 | `[1,2,3,5]` | `[4]` | 仅本人持有的订单可最终确认(权限原因,非覆盖度) |
|
||||
| 26-3281 | 1/2 | `[2]` | `[1]` | 同上 |
|
||||
|
||||
两单的 `disabledReason` 均已不再是「配房与当前行程不一致」,证明覆盖度拦截确已解除;字段互斥升序、数值与实际配房状态一致。
|
||||
|
||||
单测:`hl-order-service-v3` 全量 **586 类 / 7512 tests,0 failures 0 errors**,ArchTest 门禁 44 绿。核心逻辑通过**双向变异验证**:退回放开逻辑 → 7 个用例红(覆盖度层 2 / 读口 2 / 写口 3);误把日期错位也放开 → 4 个用例红(含「未确认 + 旧日期」脏行场景)。
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5846"
|
||||
title: "配房候选「定制师推荐」拆分为主选/副选:新增 consultantChoiceRank + consultantChoiceLabel"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "8f33923a"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,8f33923a):PickHotelModal 候选归一化透传 consultantChoiceRank(1主/2副/null)+consultantChoiceLabel,酒店列标签改按 label 渲染(rank=1 warning 醒目/rank=2 default 次级/非点名不显),外层标签容器条件补 consultantChoiceLabel;兼容期旧候选未下发 label 但 isConsultantRecommended=true 时回退显「定制师推荐」。既有 isConsultantRecommended/recommendSource 行为不变。另一消费点 FunItemAdjustModal.vue 不消费推荐字段无需改。测试:新建 pick-hotel-modal.spec 3 用例(主/副选标签 type+文案、非点名不显、兼容期回退),全绿,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 配房候选:「定制师推荐」拆分为主选 / 副选(#5846)
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #5863(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-11
|
||||
> **背景**: 房务「配置酒店」弹窗里,定制师点名的酒店统一显示「定制师推荐」,分不出优先级。wx 转述徐莱口径:**「一个主选,其他都是副选」**——定制师需求里排第一位的候选是主选,其余全部副选。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
`GET /v3/admin/hotel-candidates` —— 候选项**新增 2 个字段**:
|
||||
|
||||
| 字段 | 类型 | 取值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `consultantChoiceRank` | `Integer` | `1` / `2` / `null` | 1=主选,2=副选,null=非定制师点名候选 |
|
||||
| `consultantChoiceLabel` | `String` | `定制师主选` / `定制师副选` / `null` | 可直接渲染的中文标签 |
|
||||
|
||||
**既有字段 `isConsultantRecommended` / `recommended` / `recommendSource` 行为完全不变**(兼容期保留,前端切换完成后再议下线)。
|
||||
|
||||
### 排序保证
|
||||
|
||||
**主选一定排在副选之前**。后端在排序后做了专门的收口重排,确保标识与顺序不会自相矛盾(不会出现「副选排在主选上面」)。普通候选的相对位置不受影响。
|
||||
|
||||
### 判定规则(后端已实现,前端直接用即可)
|
||||
|
||||
- 主选 = 定制师需求 `days[].segments[].candidates[]` 里**每段的第一个候选**
|
||||
- 一晚有多段(同晚住多家)时,**每段各有一个主选**;候选列表是按天扁平返回的,同一酒店若既是 A 段主选又是 B 段副选,按**最高优先级**标为主选
|
||||
- 查询参数 `preferredHotelId` **不影响**主/副判定(它只参与置顶展示),所以房务带不同参数进来看到的主选不会变
|
||||
- 定制师只提房数没选酒店的候选(无 `hotelId`)不参与标记,两个字段为 `null`
|
||||
|
||||
---
|
||||
|
||||
## 前端要做
|
||||
|
||||
把候选行的「定制师推荐」标签改为按 `consultantChoiceLabel` 渲染:
|
||||
|
||||
| `consultantChoiceRank` | 标签 | 建议样式 |
|
||||
|---|---|---|
|
||||
| `1` | 定制师主选 | 强调色(比现「定制师推荐」更醒目) |
|
||||
| `2` | 定制师副选 | 次级色 |
|
||||
| `null` | 不显示标签 | — |
|
||||
|
||||
直接用 `consultantChoiceLabel` 的文案即可,不需要自己根据 rank 拼中文。弹窗顶部「定制师指定酒店已置顶,仍可选择其他酒店」的提示语由前端维护,后端未改。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
2026-08-11 测试服(网关 `https://api.test.1814.love:9443`,房务管理员 token),订单 26-7666:
|
||||
|
||||
| 天 | 需求 days 里的候选顺序 | 候选查询实测 |
|
||||
|---|---|---|
|
||||
| day1 | 阿尔山成悦 → 阿尔善国际维景 | `[0]` 阿尔山成悦 `rank=1 定制师主选`、`[1]` 阿尔善国际维景 `rank=2 定制师副选` |
|
||||
| day2 | 额尔古纳豪星 → 额尔古纳白桦 | `[0]` 额尔古纳豪星 `rank=1 定制师主选`、`[1]` 额尔古纳白桦 `rank=2 定制师副选` |
|
||||
|
||||
主选均在副选之前,其余 19 家候选 `rank=null` 未误标;标识顺序与定制师提交顺序完全一致。
|
||||
|
||||
单测:核心 95 全绿(含新增 `HotelCandidateConsultantChoiceTest` 9 例、`CandidateConsultantChoiceRankTest`),双向变异验证通过。全量 6515 tests / Failures 0;5 个 Errors 全在 `AdjustmentRecordMapperIT`(Testcontainers 集成测试,与本改动零调用关系),**单跑 5/5 绿**,系本机并行构建的容器资源竞争。
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5850"
|
||||
title: "派车候选接口显式返回 selectable:统一选车弹窗空白复发的后端根治(四条组装路径全覆盖)"
|
||||
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: ""
|
||||
status_note: "PR #5861 已合并 dev-v3 并部署测试服,网关 9443 实测:候选车辆 20 行 / 司机 20 行 selectable 全部非 null(19/20 车 true,1 行因阻断性冲突为 false)。前端 not_required——mmg 已于 313609db 把 normalizeCandidateVehicle 的缺省口径改成「undefined 视为可选、显式 false 才禁用」,与本次后端显式下发兼容;本条属于把前端的兜底假设升级成后端契约保证,前端零改动即受益。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务:派车候选接口显式返回 `selectable`(#5850)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **端点**: `POST /admin/fleet/assignments/candidates`
|
||||
> **性质**: 新增响应字段(向后兼容,无破坏性变更)
|
||||
|
||||
---
|
||||
|
||||
## 一、为什么改
|
||||
|
||||
「统一选择车辆/司机」弹窗**反复出现整片空白**(后端返回 200、数据齐全,列表却渲染不出来)。
|
||||
|
||||
根因在前后端口径断层:
|
||||
|
||||
- 前端统一选车入口(`scope=slot` → `availableOnly=true`)用 `isFullyAvailableCandidate()` 过滤候选,该函数**要求 `selectable === true`**;
|
||||
- 但后端从来**没有下发过 `selectable` 字段**(全仓零命中),前端 `normalize` 出来是 `undefined`;
|
||||
- `undefined === true` 为 false → **整列被过滤成空**。单日入口 `availableOnly=false` 不走该过滤,所以只在统一选车弹窗复发。
|
||||
|
||||
前端已在 `313609db` 做了兜底(缺省视为可选)。但「弹窗空白」这个故障此前已复发多次,只靠前端约定"没有字段就当可选"是脆的——本次由后端把这个字段变成**显式契约**,从源头消除断层。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|------|------|------|
|
||||
| `POST` | `/admin/fleet/assignments/candidates` | 响应新增 `selectable` 字段(车辆候选项 + 司机候选项,共 4 条组装路径) |
|
||||
|
||||
## 三、契约变更
|
||||
|
||||
`AssignmentCandidateRespVO` 的车辆候选项与司机候选项各**新增一个字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `selectable` | `Boolean` | 本次选车场景下该候选是否可被选中。**恒非 null** |
|
||||
|
||||
### 与已有 `available` 的区别(务必分清)
|
||||
|
||||
| 字段 | 口径 |
|
||||
|------|------|
|
||||
| `available` | **纯资源维度**:该车/该司机在所选服务日期窗内是否空闲 |
|
||||
| `selectable` | 在 `available` 为真的基础上,**再排除阻断性冲突** |
|
||||
|
||||
后端判定式:
|
||||
|
||||
```
|
||||
selectable = available && conflicts 中不存在阻断项
|
||||
```
|
||||
|
||||
其中「阻断项」= `conflicts[].blocking` **不是显式 `false`** 的项一律按阻断处理(null/缺省从严)。
|
||||
|
||||
> ⚠️ 「已被本弹窗其他槽位选中」属于**前端选车上下文**,后端不感知,**不纳入** `selectable` 口径。前端如需这层排除,仍按自己的选中态叠加过滤。
|
||||
|
||||
### 覆盖范围
|
||||
|
||||
四条候选组装路径**全部**下发该字段,不存在只有部分路径带字段的情况:
|
||||
|
||||
1. `vehicles.records[]` —— 车辆候选分页列表
|
||||
2. `drivers.records[]` —— 司机候选分页列表
|
||||
3. `selectedDriverResidentVehicles[]` —— 选定司机的常驻车辆
|
||||
4. `selectedVehicleResidentDriver` —— 选定车辆的常驻司机
|
||||
|
||||
---
|
||||
|
||||
## 四、前端影响
|
||||
|
||||
**无破坏性变更,前端可零改动。** 但建议按下面的口径收敛:
|
||||
|
||||
- 渲染禁用态时**直接读 `selectable`**,不要再自己用 `available` + `conflicts` 二次推导——推导口径与后端不一致正是这次故障的来源。
|
||||
- 保留「`undefined` 视为可选」的兜底(`313609db` 已做)作为防御,但正常情况下该字段恒有值。
|
||||
- `selectable === false` 的行建议**置灰并展示 `availabilityReason`**,而不是从列表里过滤掉——车管需要知道"这辆车为什么不能选"。
|
||||
|
||||
---
|
||||
|
||||
## 五、验证证据(测试环境实测,2026-08-11,网关 9443,车务管理员 token)
|
||||
|
||||
```
|
||||
POST /admin/fleet/assignments/candidates
|
||||
```
|
||||
|
||||
| 项 | 实测 |
|
||||
|----|------|
|
||||
| `vehicles.records[]` | 20 行,`selectable` **全部非 null**;19 行 `true`,1 行 `false`(存在阻断性冲突) |
|
||||
| `drivers.records[]` | 20 行,`selectable` 全部非 null |
|
||||
| 兼容性 | `available` / `availabilityReasonCode` / `availabilityReason` / `conflicts` 语义与取值**均未变化** |
|
||||
|
||||
---
|
||||
|
||||
## 六、关联
|
||||
|
||||
- 工单 #5850、PR #5861
|
||||
- 前端条目:`11_frontend_派单Step2需求展示区与统一选车弹窗空白-前端缺陷-管理后台.md`(同一故障的前端侧修复 `313609db`)
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5851"
|
||||
title: "换版即作废定稿:定制师改需求/人数/天数/出发日期后车务卡片一律回到待派车;行程 day 增删两个 admin 端点下线"
|
||||
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: "PR #5856 / #5857 已合并 dev-v3 并部署测试服,26-7944(等量换版)与 26-0682(当代定稿回归)双向实测通过。前端实证 not_required(2026-08-11,mmg)——checklist 1 的 staleFinalizedPlan 卡片标记已由 #5824(aa9c087b)实现(BoardSlotSummary 挂「需求已换版 · 请核对车型与数量」warning 提示),本条仅后端放宽改判口径(去掉车数缺口前置,等量换版也改判),前端消费 assignmentProgress/assignmentStatus 的既有逻辑自动受益;checklist 2 canAssign=true 前端无按钮禁用零改动;checklist 3 下线的 /v3/admin/order/{id}/itinerary/days 端点 grep 实证前端零调用(增减天走 adjustment/submit 的 updates.itinerary.days,已存在),前端命中的 add-day/day/{editId}、product/item、hotel-requirements/assignments/days 均为不同端点不受影响。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务:换版即作废定稿 + 行程 day 端点下线(#5851 / #5852 / #5853)
|
||||
|
||||
> **服务**: hl-fleet-service、hl-order-service-v3
|
||||
> **日期**: 2026-08-11
|
||||
> **无 Flyway** · **无新增端点** · **无需改网关路由**
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
#5824 上线后仍有一类场景没修好:需求换版但**车辆数不变**(如 `suv×1` → `商务车×1`、或只改人数/天数/出发日期)时,车务看板仍显示「已派车」,而订单侧已是「配车·待配」。
|
||||
|
||||
产品口径(wx 2026-08-11):**只要定制师改需求 / 人数 / 天数 / 出发日期,车务就要重新配车流程**(行程节点级编辑不算,只有增减天数才算)。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
### `GET /admin/fleet/board/orders` —— 卡片改判口径放宽
|
||||
|
||||
**任何用车需求换版**(不再要求车辆数变多)都会让该订单卡片改判:
|
||||
|
||||
| 字段 | 变化 |
|
||||
|---|---|
|
||||
| `assignmentStatus` / `assignmentStatusLabel` | 陈旧定稿卡片由 `assigned`/`已派车` 改判为 `unassigned`/`待派车`(出团在即为 `unassigned_urgent`) |
|
||||
| `assignmentProgress.finalizedByFleet` | 陈旧定稿时为 `false` |
|
||||
| `assignmentProgress.staleFinalizedPlan` | 存在按上一版需求定稿的在途行时为 `true`(语义不变) |
|
||||
| `canAssign` | 陈旧定稿时保持 `true`,改派动作仍在——**不会出现「显示待派车却点不动」** |
|
||||
|
||||
与 #5824 相比的差异:**去掉了「车辆数缺口」这个前置条件**。等量换版(只改车型/人数/日期)同样改判。
|
||||
|
||||
终态豁免不变:`canceled` / `completed` 行携带陈旧定稿不参与判定。
|
||||
|
||||
`statusCounts`、排序权重、分页 total 同源变化,tab 计数与列表条数保持一致。
|
||||
|
||||
### `GET /admin/fleet/board/summary` —— 计数随之变化
|
||||
|
||||
陈旧定稿卡片计入 `pendingArrangeVehicle` / 待派车分面,不再计入已派车。
|
||||
|
||||
### ⚠️ 端点下线(order-v3)
|
||||
|
||||
| 端点 | 处置 |
|
||||
|---|---|
|
||||
| `POST /v3/admin/order/{orderId}/itinerary/days` | **已删除** |
|
||||
| `DELETE /v3/admin/order/{orderId}/itinerary/days/{dayId}` | **已删除** |
|
||||
| `PUT` editDay | 保留(只改叙事,不动 dayNumber/dayDate) |
|
||||
|
||||
改天数请走 `POST /v3/admin/order/{orderId}/adjustment/submit` 的 `updates.itinerary.days`(数组长度表达增减天)。
|
||||
|
||||
**实测前端零调用**:`itinerary/days` 在 hl-ui 四个远端分支(master/v1/v2/v2.1)grep 全部 0 命中,连 api 包装函数都没写过;UI 上也没有「新增/删除一天」按钮。理论零影响,但仍属 admin 端点移除,故通知。
|
||||
|
||||
### 行为变化(无接口字段变化)
|
||||
|
||||
需求处于 `PENDING` / `PENDING_REVIEW` 时**仅修改出行人数**,此前会被幂等判定吞掉(不产新版本、`headcount` 永久停旧值),导致车务发最终方案被 `HEADCOUNT_BASELINE_MISMATCH` 永久拦死。现已修复:会正常产生 `PENDING_EDIT` 换版(version 号不增、requirementId 换新)并通知车务。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
测试服 2026-08-11 16:33 部署(hl-fleet-service)/ 16:31(hl-order-service-v3)。
|
||||
|
||||
| 单 | 场景 | 卡片状态 | finalizedByFleet | staleFinalizedPlan | canAssign | tab 计数 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 26-7944 | 等量换版 `suv×1` → `商务车×1` | **待派车** | false | true | true | 待派车 1 条 / 已派车 0 条 |
|
||||
| 26-0682 | 当代定稿(回归) | **已派车** | true | false | true | 待派车 0 条 / 已派车 1 条 |
|
||||
|
||||
存量单靠定稿归属证据直接自愈,无需重新换版。
|
||||
|
||||
后端测试:fleet 受影响 5 类 664/664、`FleetRedLineArchTest` 13/13、spotless 通过;order-v3 253/253、`*ArchTest` 44/44。
|
||||
|
||||
---
|
||||
|
||||
## 已知取舍
|
||||
|
||||
纯人数变更且座位仍够时,订单侧 `vehicle_control_status` 仍可能到 DONE(与改动前一致、非回归);**车务看板侧不受影响**,照常改判待派车。
|
||||
|
||||
---
|
||||
|
||||
## 前端 checklist
|
||||
|
||||
- [ ] 陈旧定稿卡片会从「已派车」tab 移到「待派车」tab,`staleFinalizedPlan=true` 建议在卡片上打「需重新核对」标记
|
||||
- [ ] 该类卡片 `canAssign=true`,改派入口需保持可用
|
||||
- [ ] 确认未使用已下线的 `itinerary/days` 两个端点(实测为零调用)
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5855"
|
||||
title: "出行人保存新增「至少一名成人必须填手机号」服务端硬校验(新增错误码 581136)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "24f31813"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,24f31813)。实证保存链路天然兼容——persistTravelerInfo(order-v2/detail/index.vue:1005)走统一拦截器,581136 由后端 message 透传 toast,无需特殊分支。并按 §11「建议」落地表单提示增强:TravelerInfoEditor submit 校验通过后对「最终生效集合」判定,无任何成人(复用 isChildTraveler,travelerType 或 birthday<12 兜底)phone 非空(脱敏含*算已有)时 message.warning 提前提示「至少需要一名成人出行人填写手机号用于合同签署」,不阻断由后端 581136 兜底。测试:useMessage mock 补 warning,+2 用例(无成人手机提示且仍返回 payload/有则不提示),TravelerInfoEditor 11 全绿,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 出行人保存新增「至少一名成人必须填手机号」服务端硬校验(#5855 / PR #5858)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
电子合同签署要求订单中至少有一名成人出行人预留手机号(用于接收签署短信)。此前该约束只在 `traveler/validate` 校验接口里做软提示,保存接口本身不拦截,导致可以保存出"无成人有手机号"的订单,后续签约环节才暴露问题。本次在出行人保存链路上补齐服务端硬校验:保存时若"最终生效的出行人集合"中没有任何一名成人填写手机号,整个保存请求被拒绝(不写库),并返回新增错误码 `581136`。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | `PUT /v3/admin/order/{id}/traveler-info` | 新增服务端校验 | 补全出行信息(全量同步)保存时新增硬校验,触发返回 581136 |
|
||||
| 2 | `POST /v3/admin/order/{id}/traveler/batch-edit` | 新增服务端校验 | 批量编辑出行人保存时新增硬校验,触发返回 581136 |
|
||||
| 3 | `POST /v3/admin/order/{id}/traveler/add` | 新增服务端校验 | 单个新增出行人时新增硬校验,触发返回 581136 |
|
||||
| 4 | `GET /v3/admin/order/{id}/traveler/validate` | 出参文案收紧 | `blockReasons[]` 文案由"至少需要一名出行人填写手机号"收紧为"至少需要一名成人出行人填写手机号",判定口径同步收紧 |
|
||||
| 5 | `POST /v3/admin/order/{id}/traveler/smart-parse` | 新增服务端校验 | 智能批量导入(`dryRun=false` 正式落库)保存时新增硬校验,触发返回 581136(#5867 补入,与 5 入口同一门禁) |
|
||||
|
||||
> 说明:删除出行人接口**不**加此校验;智能导入的 `dryRun=true` 预览模式**不**校验(仅解析不落库)。入参 / 出参结构、字段名、类型均无变化。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | `PUT /v3/admin/order/{id}/traveler-info`;`POST /v3/admin/order/{id}/traveler/batch-edit`;`POST /v3/admin/order/{id}/traveler/add`;`GET /v3/admin/order/{id}/traveler/validate`;`POST /v3/admin/order/{id}/traveler/smart-parse` |
|
||||
| 使用场景 | 管理后台订单详情 - 出行人 Tab:补全出行信息 / 批量编辑 / 单个新增 / 保存前校验 |
|
||||
| 认证 | 管理后台 JWT(admin 端) |
|
||||
| 幂等性 | PUT traveler-info 为全量同步天然幂等;batch-edit / add 为普通写操作 |
|
||||
| 限流 | 无特殊限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
入参结构**无变化**,此处仅强调与本校验相关的字段语义。
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | Long | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体关键字段(出行人行)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| travelerType | String | 是 | 出行人类型,`ADULT`=成人;本校验只统计成人行 |
|
||||
| phone | String | 否 | 手机号。更新时传 `null`=保留原值(不修改),传 `""` 空串=清空;若清空后无任何成人有手机,触发 581136 |
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
三个保存接口出参结构**无变化**,仍为统一 `Result<T>`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | Integer | `200`=成功;`581136`=本次新增的"无成人有手机号"业务错误 |
|
||||
| success | Boolean | 成功 true / 失败 false |
|
||||
| message | String | 失败时为错误文案,前端直接 toast |
|
||||
| data | T | 成功时的业务数据(结构不变) |
|
||||
|
||||
`GET /traveler/validate` 出参 `blockReasons[]`(String 数组)中文案变化:
|
||||
|
||||
| 原来 | 现在 |
|
||||
|------|------|
|
||||
| 至少需要一名出行人填写手机号用于合同签署 | 至少需要一名**成人**出行人填写手机号用于合同签署 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
| 枚举 | 取值 | 说明 |
|
||||
|------|------|------|
|
||||
| travelerType | `ADULT` | 成人,本校验的统计对象 |
|
||||
| travelerType | 其他(儿童等) | 不计入"是否有手机"判定 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | message | 触发条件 |
|
||||
|------|---------|----------|
|
||||
| 581136 | 至少需要一名成人出行人填写手机号用于合同签署 | 保存后"最终生效的出行人集合"中没有任何一名成人(travelerType=ADULT)填写手机号;整个请求被拒,不写库。适用接口:traveler-info 全量同步 / batch-edit 批量编辑 / add 单个新增 / smart-parse 智能批量导入(dryRun=false) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(至少 1 名成人有手机)
|
||||
|
||||
请求 `PUT /v3/admin/order/123/traveler-info`,出行人中含成人且至少一名成人 phone 非空:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"message": "success",
|
||||
"data": { }
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(成人 phone 传 null 保留原值)
|
||||
|
||||
更新出行人时成人行 `phone: null` 表示保留原手机号不修改,只要原有数据里仍有成人有手机即通过校验,响应同 8.1。
|
||||
|
||||
### 8.3 业务失败(无任何成人有手机 → 581136)
|
||||
|
||||
请求 `PUT /v3/admin/order/123/traveler-info`,提交的所有成人 phone 均为空(或清空):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581136,
|
||||
"success": false,
|
||||
"message": "至少需要一名成人出行人填写手机号用于合同签署",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
出行人**未落库**,前端按 Result 约定 `code != 200` toast `message` 即可。
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- 适用:订单出行人的补全 / 批量编辑 / 单个新增 / 智能批量导入(`smart-parse` 且 `dryRun=false`)保存。
|
||||
- 不适用:删除出行人接口(删除不加此校验,即使删完后无成人有手机也不拦截删除动作本身);智能导入 `dryRun=true` 预览模式(仅解析不落库,不校验)。
|
||||
- 特殊边界:校验看的是"保存后最终生效的集合",不是单条提交行——例如 batch-edit 把唯一有手机的成人 phone 清空,即使其他行不变也会触发 581136。智能导入为**追加式**(最终集合 = 库中现有 + 本次解析行),只要合并后存在一名成人有手机即放行。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
| 维度 | 原来 | 现在 |
|
||||
|------|------|------|
|
||||
| 保存接口校验 | 不校验"成人是否有手机",可保存出无成人手机号的订单 | 保存时硬校验,无成人有手机则整单拒存,返回 581136 |
|
||||
| validate 判定口径 | 任意出行人有手机即通过 | 必须是**成人**有手机才通过 |
|
||||
| validate blockReasons 文案 | 至少需要一名出行人填写手机号用于合同签署 | 至少需要一名成人出行人填写手机号用于合同签署 |
|
||||
| 入参 / 出参结构 | — | 无变化 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- 破坏兼容:否。入参 / 出参结构不变,仅新增一个业务错误码与文案收紧。
|
||||
- 前端同步上线:非强制。前端按现有 `code != 200` toast 约定即可天然兼容;建议在补全 / 编辑出行人表单对成人行补充"至少一名成人需填手机号"的提示,提升体验。
|
||||
- 回滚方案:后端回滚 PR #5858 即恢复原行为,前端无需改动。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 581136 通过 HTTP 200 + `code=581136` 返回(Result 约定),**不是** HTTP 4xx/5xx,前端拦截器按业务 code 处理。
|
||||
- `phone` 字段更新语义:`null`=保留原值,`""`=清空。前端表单回显时注意区分"未改动"与"主动清空"。
|
||||
- 删除出行人不受此校验约束。
|
||||
- 智能批量导入:仅 `dryRun=false` 正式落库时校验,`dryRun=true` 预览不校验;导入文本中无法识别手机号属正常,门禁统计的是"最终集合里是否有成人带手机",不是"每行是否都有手机"。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue: https://git.1814.love:8443/wx/HL/issues/5855
|
||||
- PR: https://git.1814.love:8443/wx/HL/pulls/5858
|
||||
- Commit: https://git.1814.love:8443/wx/HL/commit/06f03e40c3
|
||||
- 关联补充(智能批量导入纳入同一门禁):Issue https://git.1814.love:8443/wx/HL/issues/5867 | PR https://git.1814.love:8443/wx/HL/pulls/5868
|
||||
- 后端负责人:腰苏图(yst)
|
||||
@@ -0,0 +1,225 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5862"
|
||||
title: "派单操作日志补全操作人、操作时间与改派资源快照"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #5869 已合入 dev-v3 并部署 TEST;真实登录经 Gateway 验证操作时间、操作人、动作摘要及历史改派降级契约。现有管理端可直接展示,无前端代码变更。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务派单:操作日志补全操作人、操作时间与改派资源快照
|
||||
|
||||
> **服务**: hl-fleet-service(8087)
|
||||
> **PR**: #5869
|
||||
> **Issue**: #5862
|
||||
> **日期**: 2026-08-11
|
||||
> **影响范围**: 管理端车务看板订单详情和“查看日志”中的派单操作记录
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 操作日志现在稳定返回 `time`、`operatorName`、`opTypeLabel` 和 `summary`,操作人按“企业微信姓名 → 系统用户名 → 系统”的顺序解析。
|
||||
- 改派日志新增结构化 `changeDetail`。新日志保存操作当时的新旧车牌、司机姓名快照,不再要求调用方解析 `detailJson` 中的 ID 后查询当前车辆或司机档案。
|
||||
- 历史日志没有快照时保持兼容:展示字段允许按 ID 查询当前档案,`dataCompleteness` 明确返回 `HISTORICAL_PARTIAL`,不得把当前档案值误认为操作时快照。
|
||||
|
||||
## 一、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 派单操作时间线 | GET | `/admin/fleet/orders/{orderId}/operation-log` | 响应增强 | 日志行补全操作信息与结构化改派详情 |
|
||||
| 2 | 车务看板订单详情 | GET | `/admin/fleet/board/orders/{orderId}` | 响应增强 | `operationLog.records[]` 同步返回相同字段 |
|
||||
|
||||
两个接口沿用原有路径、鉴权和分页契约,不新增请求字段。
|
||||
|
||||
## 二、接口详情
|
||||
|
||||
### 1. 派单操作时间线 `GET /admin/fleet/orders/{orderId}/operation-log`
|
||||
|
||||
**响应模型**: `Result<FleetOrderOperationLogRespVO>`
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| orderId | Path | String | 是 | Long 字符串 | 订单 ID |
|
||||
| page | Query | Integer | 否 | 默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | 否 | 默认 50,最大 200 | 每页条数 |
|
||||
| sortBy | Query | String | 否 | `time,desc` / `time,asc` | 默认按操作时间倒序 |
|
||||
| keyword | Query | String | 否 | 最长 32 字 | 按展示后的摘要、操作人等内容搜索 |
|
||||
| startDate | Query | LocalDateTime | 否 | ISO 8601 | 操作时间起点 |
|
||||
| endDate | Query | LocalDateTime | 否 | ISO 8601 | 操作时间终点 |
|
||||
|
||||
#### 日志行出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String / null | 日志 ID;合成里程碑记录可为 null |
|
||||
| time | String | 操作时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| opType | String | 操作类型英文枚举 |
|
||||
| opTypeLabel | String | 可直接展示的中文动作名称 |
|
||||
| summary | String | 后端生成的操作摘要,改派时包含前后资源和生效日 |
|
||||
| operatorName | String | 企业微信姓名优先,无企业微信姓名时使用系统用户名;自动任务为“系统” |
|
||||
| effectiveDate | String / null | 生效日期,格式 `yyyy-MM-dd` |
|
||||
| detailJson | String / null | 兼容保留的原始明细 JSON,不建议前端再自行解析 ID |
|
||||
| changeDetail | Object / null | 改派结构化详情;非改派动作返回 null |
|
||||
|
||||
#### `changeDetail` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| changeDimensions | String[] | 变更维度:`VEHICLE`、`DRIVER`、`VEHICLE_FEE` |
|
||||
| previousVehicleId / newVehicleId | String / null | 原车、新车 ID |
|
||||
| previousVehiclePlateSnapshot / newVehiclePlateSnapshot | String / null | 操作当时的原车牌、新车牌快照;历史无快照时为 null |
|
||||
| previousVehiclePlate / newVehiclePlate | String / null | 展示值;优先使用快照,历史数据才按 ID 降级查询当前档案 |
|
||||
| previousDriverId / newDriverId | String / null | 原司机、新司机 ID |
|
||||
| previousDriverNameSnapshot / newDriverNameSnapshot | String / null | 操作当时的原司机、新司机姓名快照;历史无快照时为 null |
|
||||
| previousDriverName / newDriverName | String / null | 展示值;优先使用快照,历史数据才按 ID 降级查询当前档案 |
|
||||
| effectiveDate / endDate | String / null | 本次变更的生效日和结束日 |
|
||||
| previousUsedDays | Integer / null | 生效日前原资源在同一槽位已使用的有效服务日数;历史未知为 null |
|
||||
| affectedServiceDates | String[] | 本次实际变更的服务日期,支持非连续日期集合 |
|
||||
| affectedDays | Integer / null | 本次实际变更服务日数;历史未知为 null |
|
||||
| reason | String / null | 变更原因 |
|
||||
| dataCompleteness | String | `COMPLETE` 或 `HISTORICAL_PARTIAL` |
|
||||
|
||||
#### 完整快照响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "2086386125831561218",
|
||||
"time": "2026-08-11 15:05:14",
|
||||
"opType": "change_completed",
|
||||
"opTypeLabel": "修改派单完成",
|
||||
"summary": "lc 修改派单完成:蒙A-E2E99王信 → 蒙A-E2E01道尔吉,生效日 2026-08-23",
|
||||
"operatorName": "lc",
|
||||
"effectiveDate": "2026-08-23",
|
||||
"detailJson": "{...}",
|
||||
"changeDetail": {
|
||||
"changeDimensions": ["VEHICLE", "DRIVER"],
|
||||
"previousVehicleId": "2086000000000000001",
|
||||
"previousVehiclePlateSnapshot": "蒙A-E2E99",
|
||||
"previousVehiclePlate": "蒙A-E2E99",
|
||||
"previousDriverId": "2086000000000000002",
|
||||
"previousDriverNameSnapshot": "王信",
|
||||
"previousDriverName": "王信",
|
||||
"newVehicleId": "2086000000000000003",
|
||||
"newVehiclePlateSnapshot": "蒙A-E2E01",
|
||||
"newVehiclePlate": "蒙A-E2E01",
|
||||
"newDriverId": "2086000000000000004",
|
||||
"newDriverNameSnapshot": "道尔吉",
|
||||
"newDriverName": "道尔吉",
|
||||
"effectiveDate": "2026-08-23",
|
||||
"endDate": "2026-08-25",
|
||||
"previousUsedDays": 2,
|
||||
"affectedServiceDates": ["2026-08-23", "2026-08-24", "2026-08-25"],
|
||||
"affectedDays": 3,
|
||||
"reason": "调整车辆与司机",
|
||||
"dataCompleteness": "COMPLETE"
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 50
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 历史日志降级示例
|
||||
|
||||
```json
|
||||
{
|
||||
"previousVehiclePlateSnapshot": null,
|
||||
"previousVehiclePlate": "蒙A-E2E99",
|
||||
"previousDriverNameSnapshot": null,
|
||||
"previousDriverName": "王信",
|
||||
"newVehiclePlateSnapshot": null,
|
||||
"newVehiclePlate": "蒙A-E2E01",
|
||||
"newDriverNameSnapshot": null,
|
||||
"newDriverName": "道尔吉",
|
||||
"previousUsedDays": null,
|
||||
"affectedServiceDates": [],
|
||||
"affectedDays": null,
|
||||
"dataCompleteness": "HISTORICAL_PARTIAL"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 车务看板订单详情 `GET /admin/fleet/board/orders/{orderId}`
|
||||
|
||||
订单详情原有 `operationLog.records[]` 复用上述日志行模型。本次同步增加并补全 `time`、`opTypeLabel`、`summary`、`operatorName` 和 `changeDetail`,字段含义及历史降级规则与独立操作时间线接口一致。
|
||||
|
||||
## 三、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| time | 部分操作记录展示字段不完整 | 每条可展示记录返回标准操作时间 |
|
||||
| operatorName | 可能只有泛化角色名或缺失 | 企业微信姓名优先,系统用户名兜底,自动任务显示“系统” |
|
||||
| summary / opTypeLabel | 动作信息不完整或需要调用方拼装 | 返回中文动作名称和可直接展示的完整摘要 |
|
||||
| detailJson | 调用方需解析车辆、司机 ID,且查询结果会受当前档案变化影响 | 兼容保留,不再作为稳定展示契约 |
|
||||
| changeDetail | 无 | 返回新旧车辆、司机、使用天数、影响日期及完整性标记 |
|
||||
| 车牌与司机姓名 | 可能展示当前档案值,无法证明操作当时名称 | 新日志保存操作时快照;历史日志明确标记降级状态 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 操作人展示 | 可能无法识别具体人员 | 企微姓名优先,无企微姓名时显示系统用户名 |
|
||||
| 换车或换司机 | 仅凭 ID 无法稳定还原前后资源 | 直接返回操作当时的新旧车牌与司机姓名快照 |
|
||||
| 历史无快照 | 容易把当前档案误当作历史事实 | 可降级展示,但通过 `HISTORICAL_PARTIAL` 明确说明不完整 |
|
||||
| 车辆费用单独变化 | 可能被误写成换车或换司机 | `changeDimensions` 单独标记 `VEHICLE_FEE`,动作摘要按实际维度生成 |
|
||||
|
||||
## 四、契约约束与兼容边界
|
||||
|
||||
- 调用方应优先读取结构化 `changeDetail`;`detailJson` 仅为兼容字段,不保证适合作为长期展示数据源。
|
||||
- `*Snapshot` 是操作时事实;不带 `Snapshot` 的车牌、司机展示字段可能是历史数据按 ID 查询当前档案后的降级值。
|
||||
- 只有本次操作应保存的车牌和司机姓名快照均齐全时,`dataCompleteness` 才返回 `COMPLETE`;存量日志不会伪造快照。
|
||||
- 仅变更车辆、仅变更司机、同时变更车辆与司机、仅变更车辆费用,均以 `changeDimensions` 的实际值为准。
|
||||
- ID 按字符串返回,调用方不得转换为 JavaScript Number。
|
||||
- 本次不改变接口路径、请求参数、权限、分页、排序和统一 `Result` 响应结构。
|
||||
- 快照仅包含车牌和司机姓名,不新增手机号、证件号或其他敏感信息。
|
||||
|
||||
## 五、前端影响
|
||||
|
||||
`frontend_status=not_required`。现有管理端已经可以直接展示操作时间、操作人和后端摘要,本次 TEST 页面已看到“原车牌/原司机 → 新车牌/新司机”的改派结果,无需同步修改前端代码。其他调用方如仍解析 `detailJson`,应逐步改为优先消费 `changeDetail`。
|
||||
|
||||
## 六、验证与测试证据
|
||||
|
||||
- PR #5869 已合入 `dev-v3`,合并提交:`2d9c6ba4f8af1ad997b8b6e3ba7d215b9754ae66`;TEST 的 Fleet 服务双实例健康。
|
||||
- 使用真实登录身份经 TEST Gateway 调用 `GET /admin/fleet/orders/{orderId}/operation-log` 返回 200。订单 `26-3420` 的 10 条记录均具有非空操作时间、动作、操作人和摘要。
|
||||
- TEST 管理端订单 `26-6436` 的日志页面直接展示 `蒙A-E2E99/王信 → 蒙A-E2E01/道尔吉`、动作、生效日、操作人和时间。
|
||||
- 同一订单 3 条存量改派记录均返回 `HISTORICAL_PARTIAL`,且 `changeDetail` 快照、展示值、使用天数和受影响日期等契约字段完整存在,历史无快照字段保持 null。
|
||||
- #5862 定向自动化测试 620 项通过;Fleet 扩大测试 3604 项通过、0 失败、0 错误、7 跳过;固定端口 MySQL 集成测试 11/11 通过。
|
||||
- 为避免通知、派单和审计副作用,TEST 验收未提交新的改派操作;新写日志 `COMPLETE` 快照路径由自动化测试覆盖,测试业务数据状态未改变。
|
||||
- 工单验收标准 14/14 已勾选,验收记录已写入,Issue #5862 已关闭。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不新增或修改数据库结构。
|
||||
- 不修改管理端、小程序、H5 或其他前端代码。
|
||||
- 不改变派单状态机、派单确认、司机通知、保险、结算和车辆费用计算规则。
|
||||
- 不回填或改写历史日志;历史记录只做只读降级展示。
|
||||
|
||||
## 关联
|
||||
|
||||
- Issue: #5862
|
||||
- PR: #5869
|
||||
- 后端合并提交: `2d9c6ba4f8af1ad997b8b6e3ba7d215b9754ae66`
|
||||
- 联系人: @lc
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "房务/定制师抢单前「联系对方」发不出消息(后端实测双视角均可发送)"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "9b824f5c"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "前端已实现(9b824f5c)。changelog 怀疑的「peerAdminId=0 禁用发送」不成立:canSend/sendContent 门禁均不含 peerAdminId,open-house 对 '0' 传 '0' 后端返 200,emit 链全程无门禁。Workflow 对抗审查代码级证实真实根因——非抢单专属,是 request.js 通用缺陷:access token 过期(页面闲置首开聊天常见)时 open-house POST 首试在 shouldDedupe 写时间戳,后端业务体 401 → tryRefreshAndRetry 只清 signal/__requestKey 不清 writeRequestTimestamps,refresh 零延迟重放距首试 <300ms 被误判重复写、预 abort 抛 CanceledError,经 openFlow catch 的 isCanceledRequest 静默吞 → conversationKey 永不赋值,但标题因 peerName 空兜底仍显示「待接单房务」看似正常 → sendContent 因 key 空静默 return(=点发送无反应)。curl 持新鲜 token 不经 401 重放必 200,与观测吻合。修复:① request.js 两条重放路径(401 refresh 重放 + 5xx/限流重试)统一 config.dedupe=false(重放是合法重发非重复提交);② ChatDrawer open 失败(非主动取消)时 message.error 提示并关抽屉,不留死抽屉。测试:request.spec +2(修复前精确复现 CanceledError)37/37,ChatDrawer.spec +2 6/6,全量 190 文件 1780 用例全绿,checkpoint 含生产构建全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 抢单前「联系定制师 / 联系房务」发不出消息(2026-08-11 测试人员反馈)
|
||||
|
||||
> **页面**:房务订单会话弹窗(标题显示「待接单房务」)
|
||||
> **现象**:房务尚未抢单时打开会话,输入内容点发送按钮无反应 / 发不出去
|
||||
> **后端**:本条**无后端改动**,抢单前的留言池链路后端完全正常,实测数据见下
|
||||
|
||||
---
|
||||
|
||||
## 后端实测(2026-08-11 网关 9443)
|
||||
|
||||
用**真正未抢单**的订单复现:`26-0543`(orderId `2086425903478325250`,`house_status=PENDING_CLAIM`、`claimer_id` 为 NULL),**定制师 / 房务两个视角都实测**:
|
||||
|
||||
| 视角(角色) | `POST /admin/message/chat/open-house` | `POST /admin/message/chat/{key}/messages` |
|
||||
|---|---|---|
|
||||
| 定制师(CUSTOMIZER) | `code=200`,key=`HOUSE:2086425903478325250`,`peerAdminId=0`(占位) | **`code=200` 成功** |
|
||||
| 房务(ROOM_MANAGER) | `code=200`,同一 key,`peerAdminId=0`(占位) | **`code=200` 成功** |
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
// 开会话
|
||||
POST /admin/message/chat/open-house
|
||||
{"orderId": 2086425903478325250, "peerAdminId": 0}
|
||||
|
||||
// 发送
|
||||
POST /admin/message/chat/HOUSE:2086425903478325250/messages
|
||||
{"content": "房型已确认"}
|
||||
→ 200 {"messageId":"...", "conversationKey":"HOUSE:...", "sentAt":"..."}
|
||||
```
|
||||
|
||||
消息真实落库,未抢单**不影响**开会话与发送。
|
||||
|
||||
## 排查方向(按怀疑度排序)
|
||||
|
||||
1. **🔴 首要怀疑:`peerAdminId=0` 被当成「无对端」而禁用了发送按钮。** 抢单前对端是**占位 0**(真实房务还不存在),`peerName` 也为 `null`。前端若用 `peerAdminId > 0` 或 `peerName != null` 作为发送可用条件,抢单前就会整个禁用——而后端此时是允许发送的(这正是留言池的设计:定制师先留言,房务抢单后自动接收)。
|
||||
2. 会话 key 的取得方式:抢单前 `open-house` 仍返回正常的 `HOUSE:{orderId}` key,不需要 peer 存在。确认前端没有因为 `peerAdminId=0` 而没去调 `open-house`、导致没有 key 可发。
|
||||
3. 发送按钮的 loading / disabled 状态是否卡在某个未 resolve 的前置请求上(例如等一个抢单信息接口返回)。
|
||||
4. 快捷短语点击后是否只填充了输入框、但未同步内部 state,导致发送时取到空内容被前端自己拦掉。
|
||||
|
||||
## 需要补充的信息
|
||||
|
||||
测试人员是从**哪个入口**打开这个弹窗的(房务工作台 / 订单详情 / 抢单池列表)?不同入口可能不是同一个前端组件,确认后可缩小范围。
|
||||
|
||||
## 验证方式
|
||||
|
||||
修复后用未抢单订单(如 `26-0543`)自测:定制师和房务两侧分别打开会话并发送,均应成功;发出的消息在对方抢单后应能在会话里看到。
|
||||
@@ -0,0 +1,287 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "派单 Step2「下一步·发送给司机」置灰 / 日格恒显 1/3:完成度只认前端会话内的候选证据戳,已定稿豁免因绑死 readOnly 而永不生效"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "bce7fbed"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,bce7fbed),按 wx 拍板口径 A+B 分步:①方案A 降级候选证据硬门,isDailyVehiclePlanCellComplete 车/司机/金额结构齐全即 return true,validateDailyVehiclePlan 的 requireCandidateEvidence 默认 false(显式开启才软校验);②方案B 修豁免通道,markPreservedFinalizedCell/isUnchangedFinalizedPreservedCell 去 readOnly||locked 绑定只认 planFinalized===true+签名未变,判定式两处三元改无条件调用,已定稿可编辑格未改动即完成、改动失效回资源判定。第七节同源缺陷(新增槽位误传 excludeAssignmentId)实证主链路已被 #5788 返工#2 修好(createFleetDraftSlotSelection 无 assignmentId→undefined 不传),契约实测截图来自部署包滞后于本地修复,不重复改。daily-vehicle-plan.spec 候选证据拆「默认不硬拦/显式软校验」+新增已定稿可编辑豁免用例,useAssignFlow.spec 候选证据两 case 改「不再硬拦批量提交」,assign-modal-title.spec 证据失效改「清空但不再卡 selectionReady」。fleet/board 442 全绿,checkpoint 精确文件集全过。可选增强(独立灰色软提示 UI)未做,完成度与按钮均不读证据,属增强非修复必需。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 派单 Step2「下一步」置灰 / 日格恒显 1/3(#5849 前端部分)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step2「排车」
|
||||
> **后端**:本条**零改动**。下面所有结论来自测试服真实 API、真实 DB 与**线上部署包**(`/var/www/hl-admin/assets/`,mtime 2026-08-11 18:06,比本地 hl-ui HEAD 新)三向核实。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
订单 **26-3698**(08/17→08/19,成人 10),车辆槽位 1:
|
||||
|
||||
- 三天**全部「已规划用车」、全部勾「用车」**,车 `蒙A-E5555 丰田埃尔法`、司机 `阿木古愣`、日历价与派车价均 ¥2500 全填
|
||||
- 8月18日 挂橙标:「2026-08-18 车辆槽位1 尚未通过当前日期、车辆和司机的候选校验」
|
||||
- 顶部「1 个槽位 · **1/3 个日格已完成**」
|
||||
- 底部「已完成 **0/1** 个最终方案槽位」
|
||||
- 「下一步 · 发送给司机(1 辆)」**置灰**
|
||||
|
||||
用户原话:**「3 日的行程都有数据,为啥完成 1/3」**。
|
||||
|
||||
---
|
||||
|
||||
## 二、后端已彻底排除
|
||||
|
||||
`GET /admin/fleet/board/orders/2086270138171994114` 返回的三个日格**逐字段对称**,只有 `assignmentId` / `serviceDate` / `pickup*` 不同:
|
||||
|
||||
```json
|
||||
{
|
||||
"serviceDate": "2026-08-18",
|
||||
"fleetItemIndex": 0,
|
||||
"assignmentSlotId": "344659955019812864",
|
||||
"assignmentId": "345501044920422400",
|
||||
"assignmentGroupId": "2086270138708844545",
|
||||
"planFinalized": true,
|
||||
"planState": "USED",
|
||||
"used": true,
|
||||
"readOnly": false,
|
||||
"readOnlyReason": null,
|
||||
"pickupParticipant": false,
|
||||
"pickupRequired": false,
|
||||
"vehicleId": "2064998142394183681",
|
||||
"vehiclePlate": "蒙A-E5555",
|
||||
"vehicleModel": "丰田埃尔法",
|
||||
"driverId": "2065272145633591298",
|
||||
"driverName": "阿木古愣",
|
||||
"calendarPrice": "2500.00",
|
||||
"assignmentPrice": "2500.00",
|
||||
"priceSource": "CALENDAR",
|
||||
"priceAdjustmentReason": null,
|
||||
"assignmentStatus": "holding"
|
||||
}
|
||||
```
|
||||
|
||||
`POST /admin/fleet/assignments/candidates` 全组合实测——**车与司机、单日与整段(8/17→8/19)、三种 `excludeAssignmentId` 传法(组锚点 / 该日行真实 id / 不传)一律**:
|
||||
|
||||
```
|
||||
车 available=true selectable=true availabilityReasonCode=AVAILABLE conflicts=[]
|
||||
司机 available=true selectable=true availabilityReasonCode=AVAILABLE conflicts=[]
|
||||
```
|
||||
|
||||
DB 侧:跨订单全库核查该车该司机 08/17~08/19 **零外单占用**;车 `service_status=ACTIVE` 未维保;司机 `season=active` 未休假、且是该车 primary driver(不触发跨常驻确认);价格日历 08-15~08-22 每天 2500 AVAILABLE 不缺价。
|
||||
|
||||
**Step2 → Step3 全程零 HTTP**:`useAssignFlow.js` 约 :685-701 的 `onStep2Next` 只做 `step.value = 3`。
|
||||
|
||||
---
|
||||
|
||||
## 三、根因
|
||||
|
||||
### 3.1 判定式逐行代入真实数据
|
||||
|
||||
`utils/daily-vehicle-plan.js` 约 :443-466(部署包 `daily-vehicle-plan-CQiA9Iru.js` 逐字一致):
|
||||
|
||||
| # | 代码 | 代入真实 cell | 结果 |
|
||||
|---|---|---|---|
|
||||
| 1 | `if (planState === UNPLANNED && !used) return false` | `USED` / `used=true` | 不早退 |
|
||||
| 2 | `const preservedFinalized = (readOnly \| locked) ? isUnchangedFinalizedPreservedCell(cell) : false` | `readOnly=false`,`locked` 后端不下发 | **`false`(三元走 else,`isUnchangedFinalizedPreservedCell` 根本没被调用)** |
|
||||
| 3 | `if ((readOnly \| locked) && !preservedFinalized) return false` | 条件为 false | 跳过 |
|
||||
| 4 | `if (!used) return true` | `used=true` | 继续 |
|
||||
| 5 | `if (!vehicleId \|\| !driverId) return false` | 两者都有 | 继续 |
|
||||
| 6 | `if (!isValidAssignmentPrice(assignmentPrice)) return false` | `"2500.00"` 合法 | 继续 |
|
||||
| 7 | `if (preservedFinalized) return true` | `false` | **不提前通过** |
|
||||
| 8 | `if (needsPriceReason && !reason) return false` | `2500.00 === 2500.00` 不算改价 | 继续 |
|
||||
| 9 | `if (requiresCrossResidentConfirmation && !confirmCrossResident) return false` | 后端不下发该字段 → `undefined` | 继续 |
|
||||
| 10 | `return preservedFinalized \|\| dailyVehiclePlanCandidateEvidenceMatches(cell)` | `false \|\| evidence?` | **← 唯一决定因素** |
|
||||
|
||||
**这三格的完成与否,100% 只取决于 `_candidateEvidence`。**
|
||||
|
||||
### 3.2 `_candidateEvidence` 是纯前端会话内存字段,服务端回显的格子恒 null
|
||||
|
||||
后端 `DailyVehiclePlanVO` 没有这个字段(见上面响应),前端 `createDailyVehiclePlan` 也从不合成、只做透传(约 :184-186)。
|
||||
|
||||
| 来源 | `_candidateEvidence` |
|
||||
|---|---|
|
||||
| 用户当场在选车抽屉里选车/选司机 | 写入(`AssignModal.vue` 约 :2122-2127 批量、:2477 单格) |
|
||||
| **从服务端快照重建日格** | **硬写 `null`**(`utils/canonical-assignment-snapshot.js` 约 :134) |
|
||||
| 新建槽位空白格 | `null`(`AssignModal.vue` 约 :3560) |
|
||||
|
||||
### 3.3 🔴 唯一豁免 `preservedFinalized` 因为绑死 `readOnly` 而永不生效
|
||||
|
||||
```js
|
||||
// :66-70
|
||||
export function markPreservedFinalizedCell(cell) {
|
||||
if (!(cell.readOnly || cell.locked) || cell.planFinalized !== true) return cell // ← 直接原样返回
|
||||
if (cell._preservedPlanSignature) return cell
|
||||
return { ...cell, _preservedPlanSignature: preservedPlanSignature(cell) }
|
||||
}
|
||||
|
||||
// :72-79
|
||||
function isUnchangedFinalizedPreservedCell(cell = {}) {
|
||||
return Boolean(
|
||||
(cell.readOnly || cell.locked) && // ← 又要求一次
|
||||
cell.planFinalized === true &&
|
||||
cell._preservedPlanSignature &&
|
||||
cell._preservedPlanSignature === preservedPlanSignature(cell)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
后端对这三格下发的是 **`planFinalized: true` + `readOnly: false`**,即「**已定稿、但仍可编辑**」。这个组合三道都过不去:
|
||||
|
||||
1. `markPreservedFinalizedCell` 第一行就 `return cell`,**`_preservedPlanSignature` 从来没被写上**;
|
||||
2. 即便写上了,`isUnchangedFinalizedPreservedCell` 仍要求 `readOnly || locked`;
|
||||
3. 而且 3.1 第 2 步的三元决定了这个函数**压根不会被调用**。
|
||||
|
||||
**→ 对「已定稿但可编辑」的日格,豁免通道彻底是死的**,只能回落到候选证据。
|
||||
|
||||
> **更正一条早先的说法**:先前判断「#5851 换版即作废定稿把 `planFinalized` 置成 false 从而关闭豁免」——**不成立**。接口实测 `planFinalized` 为 `true`。豁免关闭的原因是上面的 `readOnly` 绑定,与 #5851 无关。
|
||||
|
||||
### 3.4 为什么偏偏是「1/3」、且只有一天挂橙标
|
||||
|
||||
两条机制叠加,**不是「中间那天特殊」**:
|
||||
|
||||
1. **只有 1 格能拿到证据**:弹窗初始化自动激活「第一个未完成的用车格」(`AssignModal.vue` 约 :3536-3541;`dailyPlan` 按日期升序,`daily-vehicle-plan.js` 约 :201-205)→ 激活 **8/17**,抽屉对该单日拉候选并**只回写这一格**证据(约 :2418-2481)。8/18、8/19 **从未被激活,永远拿不到证据**。
|
||||
2. **两天都不合格,只显示第一条**:`validateDailyVehiclePlan` 返回**第一条**错误;能定位到具体格时 `dailyPlanGlobalValidationError` 返回空串(约 :1820-1836),改由矩阵按 `serviceDate` + 槽位号渲染**单行**橙标(`DailyVehiclePlanMatrix.vue` 约 :296-301)。所以只有 8/18 挂标,**8/19 同样不合格只是没显示**。另一单表现为 8/21 同理。
|
||||
|
||||
**排除项**:「接机/参与」勾选**不是**原因——`isDailyVehiclePlanCellComplete` 通篇不读 `pickupParticipant`。
|
||||
|
||||
### 3.5 按钮与两个读数的完整链路
|
||||
|
||||
```js
|
||||
// AssignModalFooter.vue 约 :107
|
||||
:disabled="!selectionReady || submitting"
|
||||
|
||||
// AssignModal.vue 约 :1837-1846
|
||||
selectionReady = batchMode
|
||||
? selectedSlots.length > 0 && !selectionBlockedReason && !dailyPlanValidationError
|
||||
: ...
|
||||
|
||||
// 「N/M 个日格已完成」 约 :1526
|
||||
dailyPlan.filter(isDailyVehiclePlanCellComplete).length
|
||||
|
||||
// 「已完成 N/M 个最终方案槽位」 约 :2416
|
||||
slot.selectionReady = cells.every(isDailyVehiclePlanCellComplete)
|
||||
|
||||
// dailyPlanValidationError 约 :1808 → validateDailyVehiclePlan(...)
|
||||
// 其中约 :571-577 抛出那句橙标:
|
||||
if (requireCandidateEvidence && !preservedFinalized
|
||||
&& !dailyVehiclePlanCandidateEvidenceMatches(cell)) {
|
||||
return `${label}尚未通过当前日期、车辆和司机的候选校验`
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 ⚠️「统一选择车辆/司机 → 应用到槽位全部日期」**不能**当绕过手段(wx 实测无效)
|
||||
|
||||
该路径确实会给整槽用车格批量写证据(约 :2102-2128),但它自己还有三道前置门:
|
||||
|
||||
1. `applySlotResource` 要求 `selVehicle && selDriver` 都已在抽屉内选中,否则 `message.warning('请先选择车辆和司机')` 直接返回(约 :2045-2050);
|
||||
2. `applySlotResourceSelection` 开头要求 `candidateSelectionReady`,重查一次仍不 ready 就 `return`(约 :2064-2080);
|
||||
3. `targetCells` 只取 `cell.used && !cell.readOnly && !cell.locked`(约 :2087-2092),空集直接返回。
|
||||
|
||||
**不要把它写进用户手册当 workaround**,必须从判定逻辑本身修。
|
||||
|
||||
---
|
||||
|
||||
## 四、建议改法
|
||||
|
||||
**核心认识:`_candidateEvidence` 是对后端既有保证的重复设卡。** 提交时后端会在锁内重新校验资源可用性、档期冲突、车辆维保、司机休假、日期窗,真有问题会返回明确错误码(605001/605003/605037/605038/605062)。前端不该要求车务为「本来就派好、且没改过」的日格重新点一遍选车抽屉。
|
||||
|
||||
### 方案 A(最快,2 行,直接满足 wx 口径)
|
||||
|
||||
| # | 位置 | 现在 | 改为 |
|
||||
|---|---|---|---|
|
||||
| 1 | `daily-vehicle-plan.js` 约 :484 | `requireCandidateEvidence = true` | `false` |
|
||||
| 2 | `daily-vehicle-plan.js` 约 :465 | `return preservedFinalized \|\| dailyVehiclePlanCandidateEvidenceMatches(normalized)` | `return true` |
|
||||
|
||||
**必须同改**:只改 1 → 按钮能点但计数仍显 1/3;只改 2 → 计数对了但按钮仍被 `dailyPlanValidationError` 卡住。
|
||||
|
||||
### 方案 B(更保守,保留「改动过要重验」的语义)
|
||||
|
||||
1. **修好豁免通道**:把 `markPreservedFinalizedCell` 与 `isUnchangedFinalizedPreservedCell` 的 `readOnly || locked` 前置去掉,只要 `planFinalized === true` 且签名未变即视为「已定稿未改动」;同时把 `isDailyVehiclePlanCellComplete` 第 2 步的三元改为**无条件**调用 `isUnchangedFinalizedPreservedCell(normalized)`。
|
||||
→ 后端已定稿、用户没动过的格子天然完成;用户一改车/司机/价格,签名变化自动失效,仍需重验。
|
||||
2. **从服务端快照重建日格时补齐 `_candidateEvidence`**(`serviceDate` / `vehicleId` / `driverId` / `confirmCrossResident` 直接取该行的值)——把「后端已存在这条派车行」本身当作证据。
|
||||
3. 即使证据缺失也**不要硬置灰**,降级为灰色软提示,由后端做最终裁决。
|
||||
|
||||
### 建议
|
||||
|
||||
**先上方案 A 解阻塞**(wx 现在卡着),再按方案 B 把语义修正确。两者不冲突。
|
||||
|
||||
### 可选增强(与 #5810 / #5824「只提示不门禁」同构)
|
||||
|
||||
新增独立 computed 调 `validateDailyVehiclePlan(..., { requireCandidateEvidence: true })`,渲染成**灰色软提示**、**不接入 `selectionReady`**——保留「这天还没重新验过」的信息,但不卡流程。
|
||||
|
||||
### 回归
|
||||
|
||||
同步调整 `__tests__/assign-modal-title.spec.js`(约 1726 / 1744 / 1988 / 2069 / 2208 / 2236 行)与 `utils/__tests__/daily-vehicle-plan.spec.js` 的期望值。
|
||||
|
||||
---
|
||||
|
||||
## 五、目标行为(wx 2026-08-11 拍板)
|
||||
|
||||
> 只要**槽位里有司机、有车辆,且日期是对的**,就可以进行下一步。**定制师的需求变了,但车务可以不改派车**;和需求数对不上也可以。
|
||||
|
||||
与 #5810「换版槽位人工制」、#5824「车型/数量不符只提示不做门禁」一脉相承。
|
||||
|
||||
| 类别 | 项 | 判定方 | 现状 | 应然 |
|
||||
|---|---|---|---|---|
|
||||
| **硬** | 同车/同司机同日期被**别单**占用 | 后端 605001/605003 + 锁内重校验 | 硬 | 硬 ✅ |
|
||||
| **硬** | 派车日期越出当前需求窗 | 后端 605062(只看日期集合,不含车型/数量/座位/人数) | 硬 | 硬 ✅ |
|
||||
| **硬** | 车辆维保/停用 | 后端 605037 | 硬 | 硬 ✅ |
|
||||
| **硬** | 司机休假/待激活 | 后端 605038 | 硬 | 硬 ✅ |
|
||||
| **硬** | 同日重复车/司机 | 前后端都有 | 硬 | 硬 ✅ |
|
||||
| **硬** | 用车格缺车/缺司机 | 前后端都有 | 硬 | 硬 ✅ |
|
||||
| 软 | 车型不匹配 | 后端已降级 | 软 | 软 ✅ |
|
||||
| 软 | 座位不足 / 槽位数 vs 需求数 | 后端不拦 | 软 | 软 ✅ |
|
||||
| **软** | **日格「候选证据」完成度** | **前端独有** | **硬** | ❌ **降级为提示** |
|
||||
|
||||
> 提醒:**车型不匹配不影响 `selectable`**。后端 `selectable = available && conflicts 全部非阻断`,`requirementMatched` / `seatsEnough` 均不参与。请勿把车型不匹配纳入按钮启用条件。
|
||||
|
||||
---
|
||||
|
||||
## 六、后端侧:本条 0 改动,放开后不会再被拒
|
||||
|
||||
| 守卫 | 结论 |
|
||||
|---|---|
|
||||
| Step2 → Step3 | **无任何后端调用** |
|
||||
| `batchCreate` 契约 | 明文「可少派、多派…车型/数量/人数与需求不符不拦截」;`strictSeats` 三处硬编码 `false` |
|
||||
| 605062 日期越窗 | 判定式只看日期集合归属 |
|
||||
| `hasInvalidDispatchPlanGeneration` | 本单 `generation=NULL` / `finalized=0` → false,不拦 |
|
||||
| `isRequirementFullyAssignedRows` | 只 return false 从不抛,用于快照发布与订单车控 DONE 判定,**非提交门禁** |
|
||||
| `assertAtomicGroupReady` | Step4 确认执行门禁,与 Step2 无关 |
|
||||
| 「每个保留槽位必须覆盖全部服务日」(100001) | 要求每槽×每日一条 item,`used=false` 空格合法;前端本就按满矩阵提交,天然满足 |
|
||||
|
||||
---
|
||||
|
||||
## 七、⚠️ 同源的第二个前端缺陷(独立问题,本次修完仍需处理)
|
||||
|
||||
测试服 `hl-fleet-service.log` 17:40:39~17:41:20 抓到 **8 条**同款 WARN:
|
||||
|
||||
```
|
||||
候选查询排除派单与请求槽位上下文不一致,已忽略排除继续查询:
|
||||
reqFleetItemIndex=2 excludedFleetItemIndex=0
|
||||
excludedAssignmentId=2086270138708844545 excludedStatus=holding
|
||||
```
|
||||
|
||||
**新增槽位(index=2)时,前端仍把 slot0 的 `assignmentId` 当 `excludeAssignmentId` 传。** 后端判定槽位上下文不一致后**静默放弃排除**(不报错、如实展示占用),于是本单自己的占用被算成阻断冲突 → 该车/司机在新槽位下 `selectable=false`。实测:`fleetItemIndex=1` 或 `2` 时三天全部 `selectable=false`,冲突体正是本单自己。
|
||||
|
||||
**影响**:新增槽位**永远拿不到候选证据**——与本条是同一个死结的两端。
|
||||
|
||||
**前端改法**:新增槽位时不要沿用其它槽位的 `assignmentId`;该槽没有既有派车行就**不要传** `excludeAssignmentId`。
|
||||
|
||||
> 后端侧会加固(候选入参支持按槽位排除,不再靠反查单行推断槽位身份),届时另发接口类 changelog。
|
||||
|
||||
---
|
||||
|
||||
## 八、关联
|
||||
|
||||
- 后端工单 #5849
|
||||
- `selectable` 字段口径:`11_5850_候选接口显式返回selectable-修改接口-管理后台.md`
|
||||
- 换版后车型陈旧快照(候选列表误标「需求不匹配」):#5871,修复后另发
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "派单 Step2:统一选车弹窗空白(后端有数据)+ 建议标签仍未删 + 需补用车需求展示区"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "313609db"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(313609db):1)统一选车/司机弹窗空白根因修复——候选按日期窗返回时行常不带 available/selectable,车辆 normalize 旧值 selectable=undefined,被统一选车(scope=slot→availableOnly)的 isFullyAvailableCandidate(要求 selectable===true)整列过滤成空白;单日入口 availableOnly=false 故正常。修 normalizeCandidateVehicle 缺省视为可选(显式 false 才禁用),与司机 normalize 同口径,补回归用例。2)槽位头「建议{车型}」标签已随 #5810(b6f9c44a)删除,本条复核无残留。3)需求语句展示区从弹窗头部挪到 Step2 排车页槽位表格下方常驻一行(原样渲染不解析,null 不渲染),与 11_5810b 车队组成格式兼容(格式收口后端)。picker spec 47 全过(含新增回归),fleet/board 35 spec 428 全过,checkpoint 3 文件全绿。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 派单弹窗 Step2「排车」三项前端待办(2026-08-11 wx 实操反馈)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step2 排车
|
||||
> **实测订单**:26-9313(orderId `2086270153414103041`,孙晓东,08/22→08/24,成人10)
|
||||
> **后端**:本条**无后端改动**,三项全部是前端渲染/布局问题。相关字段在测试环境均已可实测。
|
||||
|
||||
---
|
||||
|
||||
## 1. 🔴 缺陷:「统一选择车辆/司机」弹窗一片空白(后端数据正常)
|
||||
|
||||
### 现象
|
||||
|
||||
槽位卡点「统一选择车辆/司机」→ 弹窗打开后,只渲染出顶部提示条「确认后统一写入该槽位全部可编辑用车日期;每天仍可单独调整」和右上「应用到槽位全部日期」按钮,**下方车辆列表 / 司机列表区域整片空白**。Network 面板中 `candidates` 请求返回 200。
|
||||
|
||||
### 后端实测(2026-08-11 网关 9443,车务管理员 token)
|
||||
|
||||
用该槽位的真实上下文调 `POST /admin/fleet/assignments/candidates`:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": 2086270153414103041,
|
||||
"requirementId": 2086700635972915201,
|
||||
"fleetItemIndex": 1,
|
||||
"startDate": "2026-08-22",
|
||||
"endDate": "2026-08-22",
|
||||
"headcount": 10,
|
||||
"excludeAssignmentId": 2086700639370301441
|
||||
}
|
||||
```
|
||||
|
||||
返回 `code=200`,**数据是齐的**:
|
||||
|
||||
| 字段 | 实测值 |
|
||||
|------|--------|
|
||||
| `vehicles.total` / 本页 | **21** / 20 条(蒙A-E2E01 丰田普拉多、蒙A-E2E99、蒙A-E5555 丰田埃尔法 …) |
|
||||
| `drivers.total` / 本页 | **25** / 20 条(宝音德力格尔 15年、道尔吉 11年、巴雅尔 8年 …) |
|
||||
| `vehicleTypeFacets` | 4 类(SUV系列 9 / 商务车 9 / 大巴系列 2 / …) |
|
||||
| `fleetTeamFacets` | 4 组(北疆协作车队、合作车队A 4辆、合作车队B 2辆 …) |
|
||||
| `canonicalSnapshot` | `planGeneration` / `snapshotVersion=6` / `retainedSlotIds` 均非空 |
|
||||
|
||||
### 结论与排查方向
|
||||
|
||||
**接口有数据,是前端拿到响应后没有把列表渲染出来。** 请重点看:
|
||||
|
||||
1. 响应取数路径:车辆/司机是 `data.vehicles.records` / `data.drivers.records`(分页对象,不是裸数组),`total` 在 `data.vehicles.total`。是否误按裸数组解构导致渲染空列表?
|
||||
2. 该弹窗(「统一选择」入口)与单日「选择车辆/司机」入口是否复用同一个列表组件——单日入口此前是能出数据的,对比两条入口的 props/数据源差异。
|
||||
3. 弹窗内是否有基于槽位状态/日期区间的前置过滤(例如按 `startDate===endDate` 或「可编辑用车日期」集合过滤),把 20 条全过滤掉了。
|
||||
4. Network 里 `candidates` 调了两次(截图可见),确认是否第二次请求的响应覆盖了第一次、而第二次带了更严格的筛选参数。
|
||||
|
||||
---
|
||||
|
||||
## 2. ⚠️ 催办:槽位头「建议 SUV」「建议 车型未标注」标签仍未删除
|
||||
|
||||
`changelogs-v2/2026-08/11_5810_换版槽位人工制-需求语句与派车日期门禁-修改接口-管理后台.md` **§3 已明确要求删除**,当前线上(测试环境)仍在渲染,wx 2026-08-11 再次点名。
|
||||
|
||||
- 槽位头的「建议 {车型}」标签 → **全部去掉**(含车型缺失时的「建议 车型未标注」兜底文案)
|
||||
- 字段层面 `vehicleSlots[].requiredVehicleType` / `requiredVehicleTypeLabel` / `requiredSeats` **后端仍返回真实值、契约保留**(其它场景可能用),只是**不要再渲染成槽位头的"建议 xx"标签**
|
||||
- 原因重申:槽位人工制下车务爱派什么车派什么车,后端不按车型校验(车型/数量/人数不符**不拦截**),"建议"标签会让车务误以为必须匹配。车型诉求统一由下面第 3 条的需求语句表达一次
|
||||
|
||||
---
|
||||
|
||||
## 3. 📐 新增布局:Step2 排车页需要一块「用车需求」展示区
|
||||
|
||||
### 位置(wx 截图指定)
|
||||
|
||||
派单弹窗 Step2「排车」页,**槽位表格下方的空白区域**(截图中红框位置:最后一个槽位的日期表格结束之后、弹窗底部之前)。当前这块是纯空白,浪费了整屏空间,而车务在这一步恰恰最需要对照需求。
|
||||
|
||||
### 展示内容
|
||||
|
||||
数据源:`GET /admin/fleet/board/orders/{orderId}` **根级** `requirementFleetText`(字段已存在,测试环境可实测)。
|
||||
|
||||
- 26-9313 实测当前值:`商务车×1(7座)、SUV×1(5座)`
|
||||
- ⚠️ 后端正在把格式统一为 **`商务车7座×1 + SUV5座×1`**(座位数内联到车型后、多项用加号连接,wx 2026-08-11 定稿)。格式变更单独发 changelog,**对前端接入方式无影响**——
|
||||
- 🔴 **前端只管原样展示这个字符串,不要解析、不要自己按 `vehicleSlots` 或需求明细拼**(拼法口径归后端一处收口,否则日后改口径要改两边)
|
||||
- `null` 时(需求不可达)整块不渲染,不要出现「当前需求:」空冒号或「当前需求:null」
|
||||
|
||||
### 建议形态
|
||||
|
||||
一行常驻文本即可,例如:
|
||||
|
||||
```
|
||||
当前需求:商务车7座×1 + SUV5座×1
|
||||
```
|
||||
|
||||
不做"变更态横幅"——常驻展示的好处是任何时候车务都能对照,不依赖任何"需求是否改过"的状态判断(`requirementChangePendingCount` 现已恒 0,不能再作为触发条件,见 #5810 §2)。
|
||||
|
||||
可一并放进这块区域的既有字段(同一接口根级,均已可实测,按需取用):
|
||||
|
||||
| 字段 | 26-9313 实测值 | 说明 |
|
||||
|------|---------------|------|
|
||||
| `requirementFleetText` | `商务车×1(7座)、SUV×1(5座)`(即将改为新格式) | **必展示** |
|
||||
| `headcount` | `10` | 乘车人数 |
|
||||
| `suggestedVehicleCount` | `2` | 需求车辆数 |
|
||||
| `specialTags` | `["儿童安全座椅","大行李空间","中文司机","老司机(5年以上)"]` | 特殊诉求,标签形态 |
|
||||
| `requirementRemark` | `孙晓东10人mpvx1 suv*1` | 定制师需求备注 |
|
||||
|
||||
---
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 第 1 条:2026-08-11 网关 `https://api.test.1814.love:9443` 车务管理员 token 实测 `POST /admin/fleet/assignments/candidates`,返回 `code=200`、`vehicles.total=21`、`drivers.total=25`、facets 非空(详见上表)。
|
||||
- 第 2、3 条:同订单 `GET /admin/fleet/board/orders/2086270153414103041` 实测 `requirementFleetText="商务车×1(7座)、SUV×1(5座)"`、`headcount=10`、`suggestedVehicleCount=2`、`specialTags` 4 项、`requirementRemark` 非空;`vehicleSlots` 返回 3 个槽位且 `superseded` 全 false(#5810 后无旧数据行,符合预期)。
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "派单弹窗需求信息展示补全:Step1 缺结构化车型语句、Step2 缺特殊诉求与文字备注(后端字段已全部就绪)"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a18e9db9"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-11"
|
||||
status_note: "前端已实现(2026-08-11,mmg,a18e9db9):三块同源当前生效需求原样渲染不自拼。Step1OrderDetail 用车备注块补 requirementFleetText 车型语句行(纳入 hasVehicleNotes 空态,null 不渲染该行);AssignModal Step2 底部当前需求块抽 RequirementFleetSummary.vue(车型语句+specialTags 中文标签直渲+requirementRemark 备注,空数组/falsy 各不渲染)。step1-order-detail.spec +2、新建 requirement-fleet-summary.spec 4 用例,checkpoint 精确文件集全过。"
|
||||
updated_at: "2026-08-11"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 派单弹窗需求信息展示补全(原 #5848 → 转前端)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗
|
||||
> **后端**:**零改动**。三块信息早已在同一份详情响应里返回,只是前端两处各渲染了一半。
|
||||
|
||||
---
|
||||
|
||||
## 一、现状:两处各缺一半
|
||||
|
||||
| 位置 | 现在显示 | 缺什么 |
|
||||
|------|---------|--------|
|
||||
| **Step1「订单详情」→ 定制师/用车备注**区块 | 只有文字备注(例:`用车备注 杨子墨6人suv*2`) | **缺结构化车型语句** |
|
||||
| **Step2「排车」→ 底部「当前需求」**区块 | 只有结构化语句(例:`当前需求:SUV7座×1 + 商务车7座×1`) | **缺通用特殊诉求 + 文字备注** |
|
||||
|
||||
wx 的要求:**两处都要同时展示「结构化车型语句 + 通用特殊诉求 + 文字备注」三块完整信息**。车管在 Step2 排车时最需要看到「要儿童座椅」「要中文司机」这类诉求,现在只能看到车型数量。
|
||||
|
||||
---
|
||||
|
||||
## 二、数据来源:一个接口、一份响应、三个字段
|
||||
|
||||
**两处同源**——Step1 和 Step2 用的是**同一个 order 对象**,来自同一次请求:
|
||||
|
||||
```
|
||||
GET /admin/fleet/board/orders/{orderId}
|
||||
Authorization: Bearer <admin token>
|
||||
(无请求体)
|
||||
```
|
||||
|
||||
响应 `data` 里三个字段:
|
||||
|
||||
| 字段 | 类型 | 是否可空 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| `requirementFleetText` | `String` | **可为 null** | 后端拼好的**结构化车型整句**,如 `SUV7座×1 + 商务车7座×1` |
|
||||
| `specialTags` | `List<String>` | **恒为数组**(无诉求时 `[]`) | 通用特殊诉求,**值本身就是中文标签** |
|
||||
| `requirementRemark` | `String` | **可为 null** | 文字备注 / 其他诉求 |
|
||||
|
||||
> `requirements` 是 `requirementRemark` 的**兼容别名**(同值),新代码请用 `requirementRemark`。
|
||||
|
||||
### 测试环境实测(2026-08-11,26-3698)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"requirementFleetText": "SUV7座×1 + 商务车7座×1",
|
||||
"specialTags": ["含高速油费"],
|
||||
"requirementRemark": "和法国飞行速度",
|
||||
"requirements": "和法国飞行速度",
|
||||
"plannerNote": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、三个字段的使用口径(重要)
|
||||
|
||||
### 1. `requirementFleetText` —— **原样渲染,禁止自行拼装**
|
||||
|
||||
整句由后端统一拼好,格式是 #5817 定的「车队组成写法」:**座位内联 + 加号连接**。
|
||||
|
||||
```
|
||||
SUV7座×1 + 商务车7座×1
|
||||
```
|
||||
|
||||
规则(后端已实现,前端不需要复刻):
|
||||
- 缺座位时省略座位:`SUV×2 + 商务车7座×1`
|
||||
- 车型未知时回退原始值
|
||||
- 需求为空或 `fleet` 为空 → 返回 **`null`**(整块不渲染)
|
||||
|
||||
⚠️ **禁止**用 `vehicleSlots[].requiredVehicleTypeLabel` 或 `requiredVehicles` 自己聚合拼这句话——那些是**派单行的历史快照**,需求换版后是**陈旧值**(会显示成换版前的旧车型)。只有 `requirementFleetText` 是当前生效需求的权威值。
|
||||
|
||||
### 2. `specialTags` —— **直接渲染,不要查字典翻译**
|
||||
|
||||
数组里的值**本身就是中文标签**(后端字典 `vehicle_special_demand` 的 `dict_value` 与 `dict_label` 有意保持一致),直接当 tag 渲染即可:
|
||||
|
||||
```
|
||||
["儿童安全座椅", "大行李空间", "中文司机", "老司机(5年以上)", "含高速油费", "静音车型", "有WiFi"]
|
||||
```
|
||||
|
||||
恒为数组,`length === 0` 时不渲染该块。
|
||||
|
||||
### 3. `requirementRemark` —— 自由文本
|
||||
|
||||
可能为 `null`,判 falsy 时不渲染。
|
||||
|
||||
---
|
||||
|
||||
## 四、前端要做的两件事
|
||||
|
||||
### 1. Step1「定制师/用车备注」区块 —— 补车型语句
|
||||
|
||||
在现有的 `plannerNote` / `requirementRemark` / `specialTags` 之外,增渲染 `order.requirementFleetText`(原样输出)。`null` 时不显示该行。
|
||||
|
||||
### 2. Step2 底部「当前需求」区块 —— 补诉求与备注
|
||||
|
||||
在现有的 `requirementFleetText` 之后,补上:
|
||||
- `order.specialTags` → tag 列表(空数组不渲染)
|
||||
- `order.requirementRemark` → 文本(null 不渲染)
|
||||
|
||||
---
|
||||
|
||||
## 五、版本一致性(验收项,后端已保证)
|
||||
|
||||
「两处展示的三块信息来自同一版当前生效需求,不串版本」这一条**后端天然满足**:
|
||||
|
||||
三个字段全部出自**同一个当前生效需求对象**(`is_active=1` 且 version 最大的那一版),一次取数、一起下发。前端只要都从这份响应取值,就不可能串版本。
|
||||
|
||||
反之,如果前端从 `vehicleSlots` 或 `requiredVehicles` 自己聚合,就会踩到派单行的陈旧快照——见上文第三节的警告。
|
||||
|
||||
---
|
||||
|
||||
## 六、空态汇总
|
||||
|
||||
| 场景 | 后端返回 |
|
||||
|------|---------|
|
||||
| 无生效需求 / 需求不可达 | `requirementFleetText: null`、`specialTags: []`、`requirementRemark: null` |
|
||||
| `fleet` 为空数组或解析失败 | `requirementFleetText: null` |
|
||||
| 无特殊诉求 | `specialTags: []`(**不是 null**) |
|
||||
| 无备注 | `requirementRemark: null`(不转空串) |
|
||||
|
||||
整块判空规则:整句 `null` → 不渲染该行;tags `length === 0` → 不渲染;备注 falsy → 不渲染。**不要出现空白区块或字面量 `null`。**
|
||||
|
||||
---
|
||||
|
||||
## 七、关联
|
||||
|
||||
- 原后端工单 #5848(已关单,判定为纯前端渲染缺口)
|
||||
- 车型语句格式定义:#5817 / `11_5810b_需求车型语句改车队组成写法-修改接口-管理后台.md`
|
||||
- 陈旧快照坑的成因:#5871(换版后派单行 `required_vehicle_type` 不刷新)
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5870"
|
||||
title: "看板详情 vehicleSlots 新增展示序号 slotDisplayNo + 候选车辆新增需求不匹配原因字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "b9b17dbf"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已修复并部署测试服,API 实测通过。(1) #5870 项8:详情端点 `/admin/fleet/board/orders/{orderId}` 的 vehicleSlots 每条新增 slotDisplayNo(1 起,当前有效槽位按 fleetItemIndex 升序位次,删除意图槽过滤不占编号;换版保留行 superseded 尾部追加 slotDisplayNo=null)。实测订单 HL20260811204009314:活跃 idx1 + 已删意图 idx2 + canceled idx0 → vehicleSlots 返回 displayNo=1,2,删除意图槽不占位。(2) #5871 项6:候选接口 `/admin/fleet/assignments/candidates` 车辆候选项新增 requirementMismatchReasonCode(VEHICLE_TYPE/SEATS/VEHICLE_TYPE_AND_SEATS,匹配时 null)+ requirementMismatchMessage(后端直出中文指明本槽位需求,如「本槽位需求为商务车7座,该车为SUV」)。实测订单 HL20260811205737244(需求 mpv×2):11 台 SUV/大巴 mismatch 车辆 reasonCode=VEHICLE_TYPE 且文案指明本槽位需求,大巴正确识别「大巴客车」。前端 2026-08-12 已接入并交付:①详情/列表槽位「车辆槽位 N」改读 slotDisplayNo(构建侧 batchAssignmentSlots+daily-vehicle-plan 透传,6 处展示点统一走 display.js resolveSlotDisplayNo,兜底 fleetItemIndex+1→列表位次);②候选「需求不匹配」标签改读 requirementMismatchMessage(VehiclePickerList 两处 NTag,兜底「需求不匹配」,只提示不阻断),reasonCode 仅透传未用。fleet/board 35 文件 421 用例全绿,checkpoint 含生产构建全过。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 看板详情展示序号 + 候选需求不匹配原因(#5870 项8 / #5871 项6,PR #5908 + #5909)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5908(候选 mismatch 字段 + Step2 多车型建槽定位)+ #5909(详情端点 slotDisplayNo 补齐)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: #5893 按等价实现修了 #5870/#5871 核心缺陷,但两个契约字段按工单方案原文应下发而未落地。本批补全兑现,全部为**新增响应字段,向后兼容,无破坏性变更**。
|
||||
|
||||
---
|
||||
|
||||
## 一、变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|------|------|------|
|
||||
| `GET` | `/admin/fleet/board/orders/{orderId}` | `vehicleSlots[]` 新增 `slotDisplayNo` 字段 |
|
||||
| `GET` | `/admin/fleet/board/orders` | `assignmentSlots[]` 新增 `slotDisplayNo` 字段(列表端点同步) |
|
||||
| `POST` | `/admin/fleet/assignments/candidates` | 车辆候选项新增 `requirementMismatchReasonCode` / `requirementMismatchMessage` |
|
||||
|
||||
---
|
||||
|
||||
## 二、#5870 项 8:看板详情展示序号 slotDisplayNo
|
||||
|
||||
### 为什么改
|
||||
|
||||
前端「车辆槽位 N」此前用 `fleetItemIndex + 1` 本地计算。删槽-加槽后 fleetItemIndex 走「最小空缺复用」,身份序号与展示位次会劈叉;前端加/删槽的乐观更新与刷新竞态还会出现跳号/重号。必须由后端统一下发与身份序号解耦的展示序号。
|
||||
|
||||
### 契约字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `slotDisplayNo` | `Integer` | 当前有效槽位按 `fleetItemIndex` 升序的展示位次,**1 起**。删除意图命中的槽位被过滤、不占编号;换版保留行(superseded)追加在尾部,`slotDisplayNo=null` 不参与当前编号。详情 `vehicleSlots` 与列表 `assignmentSlots` 同口径下发。 |
|
||||
|
||||
### 前端接入
|
||||
|
||||
「车辆槽位 N」改读 `slotDisplayNo`(兜底 `fleetItemIndex + 1`)。删除槽位后其余槽位的 `slotDisplayNo` 由后端刷新时重新计算,前端无需本地重排。
|
||||
|
||||
---
|
||||
|
||||
## 三、#5871 项 6:候选需求不匹配原因
|
||||
|
||||
### 为什么改
|
||||
|
||||
车型/座位不匹配此前只有 `requirementMatched: false` 裸布尔,前端只能笼统打「需求不匹配」,无法讲清「本槽位到底要什么」。本批让后端直出可直显的维度与文案。
|
||||
|
||||
### 契约字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `requirementMismatchReasonCode` | `String` | 不匹配维度:`VEHICLE_TYPE`(车型不符)/ `SEATS`(座位不足)/ `VEHICLE_TYPE_AND_SEATS`(双不符)。`requirementMatched=true` 时为 `null`;槽位需求不可达回退前端传参时也为 `null`(保持「仅提示不限制」)。 |
|
||||
| `requirementMismatchMessage` | `String` | 后端直出中文文案,指明本槽位需求,如「本槽位需求为商务车7座,该车为SUV」「本槽位需求座位数为7,该车核定座位不足」。匹配或需求不可达时为 `null`。 |
|
||||
|
||||
### 与 `selectable` 的关系(务必分清)
|
||||
|
||||
车型/座位不匹配**只提示不阻断**:`requirementMatched=false` 不影响 `selectable`,与 #5810 / #5824 / #5849 既定口径一致。前端不应据 mismatch 字段禁用选择。
|
||||
|
||||
### 前端接入
|
||||
|
||||
候选列表「需求不匹配」标签文案改读 `requirementMismatchMessage`(兜底「不符合本槽位需求」)。
|
||||
|
||||
---
|
||||
|
||||
## 验证证据(测试服 API 实测)
|
||||
|
||||
- **详情 slotDisplayNo**:订单 HL20260811204009314(活跃 idx1 + 删除意图 idx2 + canceled idx0)→ `vehicleSlots` 返回 `slotDisplayNo=1,2`,删除意图槽(idx2)被过滤不占编号,序号连续补位。
|
||||
- **候选 mismatch 字段**:订单 HL20260811205737244(需求 mpv×2)→ 11 台 SUV/大巴 mismatch 车辆 `requirementMismatchReasonCode=VEHICLE_TYPE`,文案「本槽位需求为商务车7座,该车为SUV」;大巴车正确识别为「大巴客车」(验证 label↔key 闭合)。
|
||||
@@ -0,0 +1,470 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5876"
|
||||
title: "核单 finalize warnings 结构化带行id + summary 新增 settled 标识(warnings 出参破坏性变更)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "1f716683"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端 PR #5881 已 merge 到 dev-v3 并部署测试服。finalize 出参 warnings 由字符串数组改为对象数组(破坏性,按字符串渲染的前端必须改读 message 字段);summary 出参新增 settled 字段,判是否已核单改读 settled,不要再判 id==null。前端 2026-08-12 落地(1f716683):实证唯一破坏性命中 finance/settlement/detail.vue 软预警 join,改读 item.message 兼容过渡期字符串元素;summary/settled 前端无封装零消费不改;detail.spec +2,settlement 36 全绿。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】核单 finalize warnings 结构化 + summary 新增 settled 标识(#5876)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单结算域的两个管理后台接口:
|
||||
|
||||
1. **完成核单 finalize**:核单页点「完成核单」时提交,原子冻结核单事实并生成核单汇总。出参带 `warnings` 软预警(现付缺凭证等,不阻塞提交),前端用于提示定制师补传凭证。
|
||||
2. **核单汇总快照 summary**:核单页 / 财务报表页读取整单金额、成本、毛利汇总。
|
||||
|
||||
本次整改两个出参可读性问题:
|
||||
|
||||
1. **warnings 无法定位行**(旧结构):warnings 是 `List<String>`,文案形如「住宿 D2 现付缺凭证」。同一天有多条住宿/门票明细时,前端拿到的多条文案完全相同,无法区分是哪一行缺凭证,也无法跳转/锚定到具体明细行。
|
||||
2. **summary 空壳无法可靠判空**(旧行为):订单未核单时接口返回一个空壳对象,此前前端用 `id == null` 判「尚未核单」,但 `advanceSummary` 字段恒有默认空对象(不是 null),单靠字段判空容易误判。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更 | 类型 |
|
||||
|---|------|------|
|
||||
| 1 | finalize 出参 `warnings` 由 `List<String>` 改为 `List<WarningItemVO>`(每条带 settlementId / category / dayNumber / message) | ⚠️ 破坏性 |
|
||||
| 2 | summary 出参新增 `settled` 字段(Boolean):已核单 = true,未核单空壳 = false | ✨ 新增字段 |
|
||||
|
||||
无入参变化、无删除字段、无 DDL。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 完成核单
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | POST /v3/admin/order/{orderId}/settlement/finalize |
|
||||
| 接口名 | 完成核单 |
|
||||
| 使用场景 | 管理后台核单页,明细核对完成后提交,原子冻结核单事实并生成核单汇总快照 |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 非幂等写操作;重复提交会被「缺少核单终态快照 / 快照已变化」类错误码拦截,前端不要自动重试 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
### 3.2 查核单汇总快照
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/summary |
|
||||
| 接口名 | 查核单汇总快照 |
|
||||
| 使用场景 | 管理后台核单页 / 财务报表页读取整单金额、成本、毛利汇总 |
|
||||
| 认证 | 管理后台 JWT |
|
||||
| 角色限制 | 无接口级角色限制(网关鉴权通过即可) |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 接口 | 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| finalize | orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
|
||||
| summary | orderId | Long | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体 / Query
|
||||
|
||||
两个接口均无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 finalize 出参(Result<SettlementSubmitRespVO>,本次仅 warnings 变化)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| summaryId | Long(String) | 新写入的 settlement_summary 主键,序列化为字符串 |
|
||||
| finalSnapshotId | Long(String) | 核单终态快照 ID,序列化为字符串 |
|
||||
| finalSnapshotVersionNo | Integer | 核单终态快照版本号 |
|
||||
| finalSnapshotStatus | String | 核单终态快照状态,固定 FINALIZED |
|
||||
| orderId | Long(String) | 订单 ID,序列化为字符串 |
|
||||
| settledAt | String | 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss |
|
||||
| totalAmount | String | 订单总金额快照(BigDecimal 序列化为字符串,下同) |
|
||||
| paidAmount | String | 已付金额快照 |
|
||||
| balanceAmount | String | 尾款金额快照 |
|
||||
| roomCost | String | 住宿实际成本 |
|
||||
| ticketCost | String | 门票实际成本 |
|
||||
| staffCost | String | 人员费用实际成本 |
|
||||
| subsidyCost | String | 补助实际成本 |
|
||||
| mealCost | String | 餐食实际成本 |
|
||||
| vehicleCost | String | 车辆基础服务总车费 |
|
||||
| otherExpenseCost | String | 其他支出实际成本 |
|
||||
| insurancePremium | String | 保险实际保费(未出单/已撤单 = 0) |
|
||||
| totalActualCost | String | 总实际成本 |
|
||||
| driverTransferAmount | String | 给司机/主报账人转回金额(有付款类型的分类仅计 CASH_PAID,不含保险) |
|
||||
| profitAmount | String | 公司毛利 = totalAmount - totalActualCost |
|
||||
| profitRate | Number | 毛利率(小数,totalAmount=0 时填 0) |
|
||||
| orderStatusAfter | String | 结算后订单状态,如「待财务复核」 |
|
||||
| mqTriggered | Boolean | 当前版本固定 false;完成核单不发布结算 MQ |
|
||||
| warnings | Array<WarningItemVO> | **本次变更**:软预警对象数组(原来为字符串数组);无预警时为空数组 [] |
|
||||
|
||||
**WarningItemVO 字段表**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| settlementId | Long(String) | 触发预警的核单明细行 id(settlement_hotel / settlement_ticket 主键),序列化为字符串;前端据此锚定/跳转具体明细行 |
|
||||
| category | String | 核单分类枚举名,当前仅 HOTEL(住宿)/ TICKET(门票/游玩项目)两类会触发预警 |
|
||||
| dayNumber | Integer | 行程第几天 |
|
||||
| message | String | 预警文案,与旧字符串元素一致,如「住宿 D2 现付缺凭证」 |
|
||||
|
||||
### 5.2 summary 出参(Result<SettlementSummaryRespVO>,本次新增 settled)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| settled | Boolean | **本次新增**:是否已核单。summary 快照存在 = true;未核单空壳 = false |
|
||||
| id | Long | settlement_summary 主键;未核单时为 null |
|
||||
| orderId | Long | 订单 ID;空壳时也有值 |
|
||||
| totalAmount | String | 订单总金额快照(BigDecimal 序列化为字符串,下同) |
|
||||
| paidAmount | String | 已付金额快照 |
|
||||
| balanceAmount | String | 尾款金额快照 |
|
||||
| roomCost | String | 住宿实际成本 |
|
||||
| ticketCost | String | 门票实际成本 |
|
||||
| staffCost | String | 人员费用实际成本 |
|
||||
| subsidyCost | String | 补助实际成本 |
|
||||
| mealCost | String | 餐食实际成本 |
|
||||
| vehicleCost | String | 车辆基础服务总车费(按 Fleet 派车组合计) |
|
||||
| otherExpenseCost | String | 其他支出实际成本 |
|
||||
| insurancePremium | String | 保险实际保费 |
|
||||
| refundTotal | String | 返还合计(只读展示,不进利润公式) |
|
||||
| totalActualCost | String | 总实际成本 |
|
||||
| driverTransferAmount | String | 给司机/主报账人转回金额(仅计 CASH_PAID) |
|
||||
| profitAmount | String | 公司毛利 |
|
||||
| profitRate | Number | 毛利率(小数) |
|
||||
| settledBy | Long | 核单人 id;未核单时为 null |
|
||||
| settledByName | String | 核单人姓名快照 |
|
||||
| settledAt | String | 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss |
|
||||
| remark | String | 核单备注 |
|
||||
| finalSnapshotId | Long(String) | 当前核单终态快照 ID;未完成核单或已重新打开时为 null |
|
||||
| finalSnapshotVersionNo | Integer | 当前核单终态快照版本号;无当前快照时为 null |
|
||||
| finalSnapshotStatus | String | 有当前快照时固定 FINALIZED,否则为 null |
|
||||
| finalizedAt | String | 当前核单终态快照完成时间;无当前快照时为 null |
|
||||
| finalizedByName | String | 当前核单终态快照操作人姓名;无当前快照时为 null |
|
||||
| advanceSummary | Object | 已审批通过的订单预支汇总;**恒下发对象,不是 null**(空壳时也有值) |
|
||||
|
||||
**advanceSummary 子对象字段表**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| approvedAmount | String | 已审批通过预支合计金额,默认 "0" |
|
||||
| records | Array | 已审批通过预支记录列表,默认空数组 [];元素结构同预支接口出参 |
|
||||
|
||||
> 未核单空壳时:`settled=false`,仅 `orderId` 与 `advanceSummary`(默认空对象)有值,**其余字段全部为 null**。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
| 字段 | 来源 | 取值 |
|
||||
|------|------|------|
|
||||
| warnings[].category | SettlementCategory 枚举 | 全量:HOTEL(住宿)/ TICKET(门票/游玩项目)/ MEAL(餐食)/ VEHICLE(车辆)/ GUIDE(导游)/ PHOTOGRAPHER(摄影)/ OTHER_INCOME(其他收入)/ OTHER_EXPENSE(其他支出);**当前预警只会出现 HOTEL / TICKET** |
|
||||
| finalSnapshotStatus | 快照状态枚举 | FINALIZED(已定稿) |
|
||||
| orderStatusAfter | 订单状态中文文案 | 如「待财务复核」 |
|
||||
|
||||
本次无枚举值增删;category 只是从隐含在文案里变成显式字段。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
### 7.1 finalize
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
| 584081 | 对账数据不一致:已付金额与支付流水和线下收款合计不符,禁止带病结算 | paid_amount 与实际入账总额不符(存量脏数据或并发漂移) |
|
||||
| 584085 | 存在辅助人员结算未完成,请全部结算后提交 | 任一非主报账司机费用行未结算完成或缺转账凭证号 |
|
||||
| 584321 | 当前订单缺少核单终态快照,请重新完成核单 | 缺终态快照(含重复提交场景) |
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
|
||||
> finalize 还有人员费用 / 其他收入未就绪等前置门禁错误码(584xxx 段),非本次变更,按响应 message 直接提示即可。
|
||||
|
||||
### 7.2 summary
|
||||
|
||||
只读接口,无业务错误码;orderId 查不到快照时返回 200 + 空壳对象(settled=false),**不会报「订单不存在」**。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**finalize**(核单完成,D2 住宿与 D2 门票各一条现付缺凭证,同日两行靠 settlementId 区分):
|
||||
|
||||
POST /v3/admin/order/2087088947225038849/settlement/finalize
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"summaryId": "9600000000001",
|
||||
"finalSnapshotId": "9600000000002",
|
||||
"finalSnapshotVersionNo": 1,
|
||||
"finalSnapshotStatus": "FINALIZED",
|
||||
"orderId": "2087088947225038849",
|
||||
"settledAt": "2026-08-12T10:30:25",
|
||||
"totalAmount": "24800.00",
|
||||
"paidAmount": "24800.00",
|
||||
"balanceAmount": "0.00",
|
||||
"roomCost": "4280.00",
|
||||
"ticketCost": "3680.00",
|
||||
"staffCost": "14260.00",
|
||||
"subsidyCost": "720.00",
|
||||
"mealCost": "860.00",
|
||||
"vehicleCost": "5200.00",
|
||||
"otherExpenseCost": "1260.00",
|
||||
"insurancePremium": "180.00",
|
||||
"totalActualCost": "23120.00",
|
||||
"driverTransferAmount": "22940.00",
|
||||
"profitAmount": "1680.00",
|
||||
"profitRate": 0.0677,
|
||||
"orderStatusAfter": "待财务复核",
|
||||
"mqTriggered": false,
|
||||
"warnings": [
|
||||
{
|
||||
"settlementId": "9600000000003",
|
||||
"category": "HOTEL",
|
||||
"dayNumber": 2,
|
||||
"message": "住宿 D2 现付缺凭证"
|
||||
},
|
||||
{
|
||||
"settlementId": "9600000000011",
|
||||
"category": "TICKET",
|
||||
"dayNumber": 2,
|
||||
"message": "门票 D2 现付缺凭证"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**summary**(已核单订单):
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/summary
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"settled": true,
|
||||
"id": "9600000000001",
|
||||
"orderId": "2087088947225038849",
|
||||
"totalAmount": "24800.00",
|
||||
"paidAmount": "24800.00",
|
||||
"balanceAmount": "0.00",
|
||||
"roomCost": "4280.00",
|
||||
"ticketCost": "3680.00",
|
||||
"staffCost": "14260.00",
|
||||
"subsidyCost": "720.00",
|
||||
"mealCost": "860.00",
|
||||
"vehicleCost": "5200.00",
|
||||
"otherExpenseCost": "1260.00",
|
||||
"insurancePremium": "180.00",
|
||||
"refundTotal": "2260.00",
|
||||
"totalActualCost": "23120.00",
|
||||
"driverTransferAmount": "22940.00",
|
||||
"profitAmount": "1680.00",
|
||||
"profitRate": 0.0677,
|
||||
"settledBy": "30001",
|
||||
"settledByName": "李定制师",
|
||||
"settledAt": "2026-08-12T10:30:25",
|
||||
"remark": "核单完成 出行 7 天 0 投诉",
|
||||
"finalSnapshotId": "9600000000002",
|
||||
"finalSnapshotVersionNo": 1,
|
||||
"finalSnapshotStatus": "FINALIZED",
|
||||
"finalizedAt": "2026-08-12T10:30:25",
|
||||
"finalizedByName": "财务终审员",
|
||||
"advanceSummary": {
|
||||
"approvedAmount": "2000.00",
|
||||
"records": []
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**边界 1:finalize 无任何软预警**——warnings 为空数组,不是 null:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"summaryId": "9600000000001",
|
||||
"warnings": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**边界 2:summary 查未核单订单**——返回空壳对象(HTTP 200,不报错),仅 orderId / advanceSummary 有值,其余全 null,settled=false:
|
||||
|
||||
GET /v3/admin/order/2087088947225038850/settlement/summary
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"settled": false,
|
||||
"id": null,
|
||||
"orderId": "2087088947225038850",
|
||||
"totalAmount": null,
|
||||
"paidAmount": null,
|
||||
"balanceAmount": null,
|
||||
"roomCost": null,
|
||||
"ticketCost": null,
|
||||
"staffCost": null,
|
||||
"subsidyCost": null,
|
||||
"mealCost": null,
|
||||
"vehicleCost": null,
|
||||
"otherExpenseCost": null,
|
||||
"insurancePremium": null,
|
||||
"refundTotal": null,
|
||||
"totalActualCost": null,
|
||||
"driverTransferAmount": null,
|
||||
"profitAmount": null,
|
||||
"profitRate": null,
|
||||
"settledBy": null,
|
||||
"settledByName": null,
|
||||
"settledAt": null,
|
||||
"remark": null,
|
||||
"finalSnapshotId": null,
|
||||
"finalSnapshotVersionNo": null,
|
||||
"finalSnapshotStatus": null,
|
||||
"finalizedAt": null,
|
||||
"finalizedByName": null,
|
||||
"advanceSummary": {
|
||||
"approvedAmount": "0",
|
||||
"records": []
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**边界 3:同一天同分类多条缺凭证**——旧结构下两条文案完全相同无法区分;新结构每条带独立 settlementId,可逐条定位/跳转:
|
||||
|
||||
```json
|
||||
"warnings": [
|
||||
{ "settlementId": "9600000000003", "category": "HOTEL", "dayNumber": 2, "message": "住宿 D2 现付缺凭证" },
|
||||
{ "settlementId": "9600000000005", "category": "HOTEL", "dayNumber": 2, "message": "住宿 D2 现付缺凭证" }
|
||||
]
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**finalize 辅助人员结算未完成**:
|
||||
|
||||
POST /v3/admin/order/2087088947225038849/settlement/finalize
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 584085,
|
||||
"message": "存在辅助人员结算未完成,请全部结算后提交",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**finalize 订单不存在**:
|
||||
|
||||
POST /v3/admin/order/999999999/settlement/finalize
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
**finalize 房务角色访问被拦**(House 角色 JWT 调用):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 403,
|
||||
"message": "无权限访问",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- finalize:订单核单明细已逐步保存完成、核对无误后点「完成核单」提交
|
||||
- summary:核单页 / 财务报表页随时读取汇总;未核单订单也可调(返回空壳),前端据此展示「尚未核单」占位
|
||||
|
||||
**不适用**:
|
||||
|
||||
- finalize:明细未保存完整 / 辅助人员结算未完成 / 对账不一致的订单(被 584xxx 门禁拦截,见 §7)
|
||||
- summary:想看明细行级数据时不要用它,走各分类明细接口;本接口只有汇总快照
|
||||
|
||||
**特殊边界**:
|
||||
|
||||
- warnings 是**软预警**,不阻塞 finalize 提交;有预警也照常返回 200 完成核单
|
||||
- 预警仅覆盖 CASH_PAID(现付)且 voucher_urls 为空的住宿 / 门票明细行;签单、公司直付等其他支付方式不产生预警
|
||||
- summary 空壳的 advanceSummary 恒为默认对象(approvedAmount="0"、records=[]),**不是 null**,不能拿它当判空依据
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 接口 | 字段 | 修改前 | 修改后 |
|
||||
|------|------|--------|--------|
|
||||
| finalize | warnings | `List<String>`,元素如 `"住宿 D2 现付缺凭证"` | `List<WarningItemVO>`,元素为对象 `{ settlementId, category, dayNumber, message }` |
|
||||
| summary | settled | 无此字段 | 新增 Boolean:已核单 = true,未核单空壳 = false |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 同日多条同类缺凭证 | 多条相同文案,无法区分哪一行 | 每条带独立 settlementId,可定位/跳转具体明细行 |
|
||||
| 前端渲染预警 | 直接渲染字符串 | 必须读 `warnings[].message`;可用 `settlementId` + `category` 做行锚定 |
|
||||
| 前端判「是否已核单」 | 判 `data.id == null`(不可靠,advanceSummary 恒非 null 易误导) | 判 `data.settled === false` |
|
||||
| 调旧结构(如把 warnings[i] 当字符串拼接) | 正常显示文案 | **显示 [object Object] 或报错**(字段结构已变) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:⚠️ 部分破坏。finalize 的 warnings 结构变化是破坏性的:原按字符串数组渲染 warnings 的前端代码会显示异常。summary 仅新增字段,向后兼容。
|
||||
- **前端必须同步上线**:是(针对 finalize warnings 渲染处)。前端需要:
|
||||
1. warnings 渲染从「直接渲染字符串」改为读 `item.message`
|
||||
2. (可选)用 `item.settlementId` + `item.category` 实现点击预警跳转/高亮对应明细行
|
||||
3. 判「是否已核单」改读 `settled` 字段,删除 `id == null` 判空逻辑
|
||||
- **后端兼容**:无 DDL、无数据迁移;未消费 warnings 的前端功能不受影响。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = revert PR #5881 的 merge commit(125bb9c96e),重启 hl-order-service-v3,warnings 恢复字符串数组、settled 字段消失。
|
||||
- 前端回滚 = 切回旧版前端包。注意新旧前后端要配套:新前端 + 旧后端时 warnings[].message 取不到值、settled 恒 undefined。
|
||||
- 零 DDL,回滚无残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **warnings 元素里的 settlementId 序列化为 JSON 字符串**(Long 防 JS 精度丢失),前端按 string 处理,不要 Number() 转换。
|
||||
2. warnings 无预警时返回空数组 [],不是 null,渲染前正常遍历即可。
|
||||
3. category 当前只会出现 HOTEL / TICKET;SettlementCategory 枚举还有其他值(MEAL / VEHICLE / GUIDE 等),前端如做映射表建议按全量枚举写,缺值兜底显示原 code。
|
||||
4. summary 判空**只认 settled**:`settled === false` 即「尚未核单」,此时除 orderId / advanceSummary 外全部字段为 null,展示必须兜底,不要直接渲染金额。
|
||||
5. advanceSummary 恒为对象(未核单也是 `{ approvedAmount: "0", records: [] }`),不要拿它判「是否已核单」。
|
||||
6. finalize 是写操作且有前置门禁(对账一致、人员结算完成等),失败按错误码 message 提示,**不要静默重试**;重复提交会被快照类错误码拦截。
|
||||
7. finalize / summary 两个接口的金额类字段(totalAmount / paidAmount / 各 cost / profitAmount 等)均序列化为字符串,前端展示直接用,计算需自行转数值。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/5876
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5881
|
||||
- Commit:https://git.1814.love:8443/wx/HL/commit/125bb9c96e
|
||||
- 服务:hl-order-service-v3(端口 8086)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5895"
|
||||
title: "排车页槽位卡头车型大类标签修复——type_key 别名 normalize 成中文标签"
|
||||
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-12"
|
||||
status_note: "后端已修复并部署测试服,API 实测通过。排车页「车辆槽位 N」卡片头在 PR #5904 新增的 vehicleCategoryLabel 字段实测返回别名 suv2(应显示 SUV):因为 fleet_vehicle_type.type_key 存的是别名,而 VehicleCategoryNormalizer.categoryLabel() 只认规范 key suv/mpv/bus/sedan,直接传入会原样返回别名。修复=先 normalize() 成规范 key 再取中文标签。实测订单 26-3698(蒙A-H7777 type_key=suv2):vehicleCategoryKey 由 suv2 → suv、vehicleCategoryLabel 由 suv2 → SUV。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 排车页槽位卡头车型大类标签修复——type_key 别名 normalize 成中文标签(#5895)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5904(槽位头车型大类字段新增)+ #5907(别名标签修复,本条)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 用户要求排车页「车辆槽位 N」卡片头显示车型大类名(多个和车牌一样分割显示)。#5904 加了 vehicleCategoryKey/vehicleCategoryLabel 字段,但实测发现已派车辆的大类标签返回了别名 `suv2` 而非中文「SUV」。
|
||||
|
||||
---
|
||||
|
||||
## 修复内容(对前端透明,无接口契约变化)
|
||||
|
||||
**根因**:`fleet_vehicle_type.type_key` 存的是别名(如 `suv2`、`mpv2`),`BoardOrderService.toVehicleSlotVO` 直接把它传给 `VehicleCategoryNormalizer.categoryLabel()`,而该方法只认规范 key `suv/mpv/bus/sedan`,未匹配时**原样返回**别名 → 前端拿到 `suv2`。
|
||||
|
||||
**修复**:先 `VehicleCategoryNormalizer.normalize(info.getCategoryKey())` 归一别名到规范 key,再取 `categoryLabel()`;归一失败(null)时保留原值兜底。
|
||||
|
||||
```java
|
||||
String categoryKey = VehicleCategoryNormalizer.normalize(info.getCategoryKey());
|
||||
vehicleCategoryKey = categoryKey == null ? info.getCategoryKey() : categoryKey;
|
||||
vehicleCategoryLabel = VehicleCategoryNormalizer.categoryLabel(vehicleCategoryKey);
|
||||
```
|
||||
|
||||
**字段口径(#5904 原有,本条不改)**:
|
||||
- 未派:`vehicleCategoryKey/Label` = 需求车型大类(如 `suv/SUV`)
|
||||
- 已派:`vehicleCategoryKey/Label` = 实际车辆车型大类 normalize 后的规范 key + 中文标签
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 26-3698 / 2086270138171994114)
|
||||
|
||||
`GET /admin/fleet/board/orders/2086270138171994114` → `vehicleSlots[0]`:
|
||||
|
||||
| 字段 | 修复前 | 修复后 |
|
||||
|---|---|---|
|
||||
| vehicleCategoryKey | `suv2` | `suv` |
|
||||
| vehicleCategoryLabel | `suv2` | `SUV` |
|
||||
|
||||
DB 佐证:`fleet_vehicle` 蒙A-H7777 的 `vehicle_type_id` 对应 `fleet_vehicle_type.type_key=suv2`(别名,非规范 key),确认是别名 → normalize 路径生效。
|
||||
|
||||
## 测试
|
||||
|
||||
- 新增用例 `BoardOrderServiceTest.queryOrderDetail_vehicleSlots_vehicleCategoryAliasKeyNormalized`:已派槽位 `type_key=suv2` 别名 → normalize 成 `SUV`。
|
||||
- `BoardOrderServiceTest` 125/125 绿。
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5895"
|
||||
title: "看板代际筛选放宽+槽位头车型大类字段——确认执行页逐日车费完整、槽位卡头显示车型大类名"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已修复并部署测试服,API 实测通过。修复两块:(1) BUG1 确认执行页逐日车费 3 天只显示 1 天+总车费「价格不完整」——BoardOrderService.selectCurrentDailyPlanRows 代际筛选放宽为「当前代际 && (finalized=1 或 assigned/holding)」,buildVehicleFeeSummaries 已派切片有 protocolPrice 按日累加、无则读 vehicle_fee_total 兜底;(2) BUG5 排车页「车辆槽位 N」卡片头新增 vehicleCategoryKey/vehicleCategoryLabel 字段(未派=需求车型大类,已派=实际车辆车型大类)。实测订单 26-3698:dailyVehiclePlan 返回 3 天(08-17/18/19 各 calendarPrice=800.00/assignmentPrice=800.00/priceSource=CALENDAR)、vehicleFeeSummaries amount=2400.00;vehicleSlots[0] vehicleCategoryKey=suv/vehicleCategoryLabel=SUV。前端 2026-08-12 实证 not_required:BUG1 取值修复前端透传自动受益;BUG5 vehicleCategoryLabel 全仓零消费,车型大类已走 requiredVehicleTypeLabel 显示,接入新字段属可选卡头增强未做。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 看板代际筛选放宽+槽位头车型大类字段(#5895 / PR #5904)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5904(本条主修复)+ #5907(别名标签补丁,另条 changelog)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 用户报障两个 BUG:(BUG1) 确认执行页逐日车费 3 天只显示 1 天、总车费「价格不完整」;(BUG5) 排车页「车辆槽位 N」卡片头只显示车牌,应显示车型大类名(多个和车牌一样分割显示)。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}`(看板订单详情)
|
||||
|
||||
---
|
||||
|
||||
## 改动一:BUG1 确认执行页逐日车费完整
|
||||
|
||||
**根因**:`selectCurrentDailyPlanRows` 代际筛选过严,只认「当前代际且 finalized=1」的派车方案行;换版/改派后旧行被排除 → `dailyVehiclePlan` 漏天、`vehicleFeeSummaries` 算不出总价。
|
||||
|
||||
**修复**:
|
||||
- 筛选放宽为「当前代际 && (`dispatch_plan_finalized=1` 或 派单 status=assigned/holding)」——已派/已锁定的实际执行行也纳入。
|
||||
- `buildVehicleFeeSummaries` 计费:已派切片有 `protocolPrice` 按日累加;无 `protocolPrice` 时读 `vehicle_fee_total` 兜底(不再恒返「价格不完整」)。
|
||||
|
||||
**响应字段(值修复,无结构变化)**:
|
||||
|
||||
| 字段 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| `dailyVehiclePlan` | 只返回 1 天 | 返回完整服务天数(26-3698 实测 3 天) |
|
||||
| `vehicleFeeSummaries[].amount` | 「价格不完整」 | 正确总价(26-3698 实测 2400.00 = 3×800) |
|
||||
| `dailyVehiclePlan[].calendarPrice/assignmentPrice/priceSource` | 漏天 | 逐日正确(800.00 / 800.00 / CALENDAR) |
|
||||
|
||||
## 改动二:BUG5 槽位头车型大类字段(新增)
|
||||
|
||||
`vehicleSlots[]` 每项新增两个字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `vehicleCategoryKey` | string | 车型大类规范 key(`suv`/`mpv`/`bus`/`sedan` 等)。未派=需求车型大类;已派=实际车辆车型大类(`fleet_vehicle_type.type_key` 别名经 normalize 归一)。 |
|
||||
| `vehicleCategoryLabel` | string | 车型大类中文标签(`SUV`/`商务车`/`大巴`/`轿车` 等),卡片头显示用。 |
|
||||
|
||||
**示例**(26-3698 / 2086270138171994114,已派 suv 槽位):
|
||||
|
||||
```json
|
||||
{
|
||||
"assignmentSlotId": "344659955019812864",
|
||||
"requiredVehicleType": "suv",
|
||||
"requiredVehicleTypeLabel": "SUV",
|
||||
"vehicleCategoryKey": "suv",
|
||||
"vehicleCategoryLabel": "SUV",
|
||||
"requiredSeats": 7,
|
||||
"slotStatus": "assigned",
|
||||
"vehiclePlate": "蒙A-H7777",
|
||||
"vehicleModel": "丰田汉兰达",
|
||||
"driverName": "阿拉坦"
|
||||
}
|
||||
```
|
||||
|
||||
**前端展示建议**:「车辆槽位 N」卡片头 = `vehicleCategoryLabel` + 车牌(已派)/ 需求车型(未派),多槽位按 `fleetItemIndex` 排序分割显示。
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 26-3698 / 2086270138171994114)
|
||||
|
||||
- `dailyVehiclePlan`:3 天(2026-08-17/18/19),逐日 `calendarPrice=800.00 / assignmentPrice=800.00 / priceSource=CALENDAR`。
|
||||
- `vehicleFeeSummaries`:`amount=2400.00`(3×800)。
|
||||
- `vehicleSlots[0]`:`vehicleCategoryKey=suv / vehicleCategoryLabel=SUV`(#5907 补丁后;#5904 原始曾返回别名 `suv2`,见另条 changelog)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5899"
|
||||
title: "修复改订单人数后车务未重走配车——换版后自动重新盖章抵消作废定稿"
|
||||
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-12"
|
||||
status_note: "后端已修复并部署测试服,API 实测通过。修改订单人数/车型等(服务日期窗不变)的换版后,车务看板正确回到「待派车」,订单侧停留在「待配」,不再被自动拉回「配车完成」;车务重新确认后订单正常推进。改行程天数/改出发日期不受影响。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 修复:改订单人数后车务未重走配车——换版后自动重新盖章抵消作废定稿(#5899)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5903(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 订单修改人数后,订单侧短暂显示「待配」又立刻回到「配车已完成」,车务侧看板没有重走配车流程,仍保持「配车完成」,待派车 tab 无此单。
|
||||
|
||||
---
|
||||
|
||||
## 修复内容(对前端透明,无接口契约变化)
|
||||
|
||||
**根因**:`AssignmentService.expandFromRequirementInTransaction` 换版时同事务内先后执行两个互相矛盾的动作:
|
||||
|
||||
1. `invalidateDispatchPlanForMigratedRows`(#5851)换版平移后作废定稿——清 `dispatch_plan_finalized`/`dispatch_plan_generation`,意图让车务重走配车。
|
||||
2. `publishDailySnapshotIfComplete`(#5810)紧接着发现「行都 assigned 且无 finalized」→ 走 `markDispatchPlanGeneration` **自动重新盖章**(finalized=1、归属=新需求)→ 发 `DailyVehicleSnapshotChangedEvent.finalPlan` → 订单被拉回 `VEHICLE_DONE`。
|
||||
|
||||
**修复**:`publishDailySnapshotIfComplete` 检测到归属旧需求的陈旧定稿行时(`hasStaleFinalizedPlanRows` 命中)直接返回,不自动盖章/发 finalPlan。车务 confirm 时归属被重新盖成当前需求,守卫自然放行,闭环保留。
|
||||
|
||||
**影响范围**:仅服务日期窗不变的换版(改人数/车型/座位)。改行程天数/改出发日期时日期校验本就返回 false,不误触发。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 改人数后(服务窗不变) | ❌ 看板停留「配车完成」,订单误拉回 DONE,车务没机会重走配车 | ✅ 看板回到「待派车」,订单停留「待配」,等车务重新确认 |
|
||||
| 车务重新确认 | ✅ 正常盖章并推进订单 | ✅ 不变(守卫放行) |
|
||||
| 改行程天数/改出发日期 | ✅ 正常 | ✅ 不变 |
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 2086270141158346754)
|
||||
|
||||
修改订单人数(10→11)后实测:
|
||||
|
||||
- 需求换版 v4,`status=PENDING`(不再自动拉回 DONE)
|
||||
- fleet 派单行 `dispatch_plan_finalized=0`、`plan_finalized_requirement_id` 保留旧 v3(归属证据)
|
||||
- 车务看板卡片:`finalizedByFleet=False`、`staleFinalizedPlan=True` → 「待派车」tab
|
||||
- 订单侧 VEHICLE:`PROCESSING 待配 | 配车待配`(不再误显示「配车已完成」)
|
||||
|
||||
后端 PR #5903 已合并 dev-v3 并部署测试服,fleet-service 健康。前端无改动,无需联调。
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5910"
|
||||
title: "修复改订单人数后车务复核确认无法闭环——复核confirm按全需求行判定满派并重新盖章"
|
||||
consumer: "admin"
|
||||
change_type: "修复"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "db1f3507"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已修复并部署测试服,API 实测闭环通过。改订单人数换版打回待派车后,车务核对沿用原车/司机,对派车点「确认执行」复核即可:复核时按整个需求判定满派并重新盖章(归属→当前需求)发 finalPlan,订单正确回到「配车完成」。此前复核后订单永久卡「处理中」的死锁已解除。前端 2026-08-12 复核 not_required:无接口契约变化、零新字段消费;dispatchPlanGeneration 系详情快照透传回 confirm 乐观锁(useAssignFlow.js:136-148/449-462),后端重盖章后下发新值前端直读最新;staleFinalizedPlan/finalizedByFleet 系后端派生状态(BoardSlotSummary.vue:189-194),复核闭环后由后端翻正,前端展示逻辑不变自动受益,无需联调改动。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 修复:改订单人数后车务复核确认无法闭环——复核confirm按全需求行判定满派并重新盖章(#5910)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5911(已合并 dev-v3 并部署测试服)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 承接 #5851(换版即作废定稿)+#5899(守卫防自动重新盖章)。改订单人数换版打回待派车后,车务复核 confirm 无法把订单推回「配车完成」。
|
||||
|
||||
## 修复内容(对前端透明,无接口契约变化)
|
||||
|
||||
**根因**:`AssignmentService.revalidateAssignedAfterBaselineChange`(复核 confirm 走的分支)两处叠加:
|
||||
|
||||
1. 用**单组行** `isRequirementFinalizedTopologyRows(groupRows,...)` 对**整个需求**拓扑,多车订单单组行数 ≠ 槽位×日期,必 false → 永不发 finalPlan。
|
||||
2. 复核 confirm 全程不调 `markDispatchPlanFinalized` 重新盖章,`plan_finalized_requirement_id` 永留旧需求 → #5899 守卫 `hasStaleFinalizedPlanRows` 永命中 → 即便拓扑齐全也不发 finalPlan。
|
||||
|
||||
旁证死锁:`batchCreate` 重新提交原方案被 605014 拒、`change` 改派被 605029 强制真换车/司机,车务「确认沿用原方案」无出口。
|
||||
|
||||
**修复**:复核 confirm 改用**需求下全部行** `isRequirementFullyAssignedRows` 判满派(内部已含陈旧定稿降级 + 载客量校验,部分派单/缺车/容量不足返回 false,防 #5824 误标 DONE),满派时**先 `markDispatchPlanGeneration` 重新盖章(归属→当前需求)再发 finalPlan**。车务逐组复核,最后一组复核完即闭环,订单回「配车完成」,车/司机沿用原方案不用换。
|
||||
|
||||
## 行为变化
|
||||
|
||||
| 场景 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 多车订单改人数后复核 confirm | ❌ 永久卡「处理中」,不盖章不发 finalPlan,无 API 可救 | ✅ 复核即整需求重新盖章发 finalPlan,订单回「配车完成」 |
|
||||
| 部分派单/缺车/容量不足复核 | ✅ 不盖章不发 finalPlan | ✅ 不变(防 #5824 误标 DONE) |
|
||||
| 单车订单改人数后复核 | ❌ 同样卡死 | ✅ 正常闭环 |
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 2086270141158346754,2 组多车)
|
||||
|
||||
换版作废后(两组 `assigned` + `finalized=0` + `generation=NULL` + 归属旧 v3,需求 PROCESSING,订单「配车 处理中」):
|
||||
|
||||
- 车务对第一组复核 confirm → 触发**整需求重新盖章**:两组 `dispatch_plan_finalized=1`、新 `generation=345816481176621056`、归属全部 → 当前 v4
|
||||
- 发 finalPlan:订单「配车」`PROCESSING 处理中` → `DONE 已完成`,需求 `PROCESSING` → `DONE`
|
||||
- 车务看板:`dispatchPlanGeneration=345816481176621056` 一致、两组「已派车」、执行全确认
|
||||
|
||||
单测:新增多车满派重新盖章+发finalPlan、部分派单不盖章不发finalPlan(防 #5824 回归)、单车回归 3 例;调整容量不足/行程中换版 2 例断言。AssignmentServiceTest 480 + ArchTest 13 + spotless 全绿,反向验证有效。前端无改动,无需联调。
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5912"
|
||||
title: "改期后旧实派车(旧日期)派车页栅格不可见——栅格并入旧行日期+新增改期残留标记字段"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "6c417cac"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已修复并部署测试服,订单 HL20260809095537293(26-0821,改期 08-18→08-21) API 实测通过:Step2 栅格 editableServiceDates 并入旧行日期后返回 [08-18..08-24] 全 7 天,旧实派行 08-18/19/20 恢复 USED+实车+rescheduleResidue=true+canDelete=true,08-21(在新窗内)不误标,新窗 08-22~24 待派占位。新增透出字段 rescheduleResidue/canDelete/deleteBlockReason 需前端消费:挂「改期残留/待清理」徽标 + 醒目删除/取消入口。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 改期后旧实派车(旧日期)派车页栅格不可见(#5912 / PR #5918)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5918
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 订单改期(出发日期变更)后,已配车完成的旧实派车(assigned,旧 service_date)在派车页 Step2 排车栅格里消失,观感像旧配车日期被错平移。产品裁决:栅格=新需求窗 ∪ 旧派车行日期,旧行保留原日期可见 + 带改期残留标记 + 醒目删除/取消入口;取消后司机/车辆自动恢复空闲;旧行不删干净订单无法推进下一步。
|
||||
|
||||
---
|
||||
|
||||
## 根因(简述)
|
||||
|
||||
改期触发 fleet 需求换版 + #5810 整槽平移(仅改 requirement_id、旧 service_date 原样保留,数据本身正确);而 Step2 栅格 `editableServiceDates` 以「需求 serviceDates(新日期窗)」为权威短路返回,旧日期行落在窗外、按 `slotId:serviceDate` 索引无 cell 键命中 → 渲染不到。**数据没错,是栅格日期窗脱钩。**
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|---|---|
|
||||
| `POST /admin/fleet/assignments/candidates` | 派单弹窗候选(Step2 排车栅格 canonical snapshot) |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` | 看板订单详情(vehicleSlots 槽位 + dailyVehiclePlan 逐日行) |
|
||||
|
||||
## 改动一:栅格日期窗并入旧派车行日期(核心)
|
||||
|
||||
`canonicalSnapshot.editableServiceDates` 由「仅新需求窗」改为「**新需求窗 ∪ 在途(unassigned/holding/assigned)旧行覆盖日期**」,确定性幂等;completed/canceled 终态行不并入。旧实派行保留原日期重新在栅格可见、可手动取消。
|
||||
|
||||
**实测**(26-0821,改期 08-18→08-21):
|
||||
|
||||
```
|
||||
editableServiceDates = [08-18, 08-19, 08-20, 08-21, 08-22, 08-23, 08-24]
|
||||
08-18 USED · rescheduleResidue=true · canDelete=true · 带实车 ← 旧实派行恢复可见
|
||||
08-19 USED · rescheduleResidue=true · canDelete=true · 带实车
|
||||
08-20 USED · rescheduleResidue=true · canDelete=true · 带实车
|
||||
08-21 USED · rescheduleResidue=false · canDelete=true · 带实车 ← 新旧窗重叠日,在窗内不标残留
|
||||
08-22 待派(used=null) · canDelete=true ← 新窗新增日待派占位
|
||||
08-23 待派(used=null) · canDelete=true
|
||||
08-24 待派(used=null) · canDelete=true
|
||||
```
|
||||
|
||||
## 改动二:新增透出字段(前端需消费)
|
||||
|
||||
Step2 栅格 cell(`canonicalSnapshot.cells[]`)、看板详情槽位(`vehicleSlots[]`)、看板详情逐日行(`dailyVehiclePlan[]`)三处同步新增:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `rescheduleResidue` | boolean | 是否改期残留旧日期行(true=本行/本 cell 服务日期越出当前需求日期窗,属改期后待清理的旧实派车)。与 605062 越窗门禁同口径。 |
|
||||
| `canDelete` | boolean | 是否可删除/取消。改期残留行恒 true(只能改派或取消释放占用);受只读窗口约束时 false。**残留行即使「服务日期已过去/已关账」只读也保留取消入口**(取消是处置不是推进)。 |
|
||||
| `deleteBlockReason` | string | 不可删除原因;可删除时为 null。 |
|
||||
|
||||
**判定收口**:三处共用 `AssignmentDailyPlanFacts.isRescheduleResidue` 同一判定(§15.7 单一口径),与 #5788 详情页 superseded 语义对齐。
|
||||
|
||||
**前端展示建议**:
|
||||
- `rescheduleResidue=true` 的行/槽位挂「**改期残留/待清理**」徽标(醒目色),与正常新窗行区分。
|
||||
- `canDelete=true` 提供**醒目的删除/取消入口**;点击调既有 `DELETE /admin/fleet/assignments/{assignmentId}`,取消后该日期司机/车辆 occupancy 自动释放恢复空闲。
|
||||
- 存在未清理残留行时,推进(确认执行)被 605062 阻断,前端把 605062 渲染成「请先清理改期残留旧派车」的阻断提示。
|
||||
|
||||
## 改动三:看板纯改期残留分流(无字段变化,行为修复)
|
||||
|
||||
纯改期残留(当前需求全部在途行日期都越出当前日期窗)不再被「换版改车型」口径覆写成「待派车」、不计入待派缺口;卡片态与进度计数口径一致。一旦有落在新窗内的行(换版/新派)即回到常规换版口径。
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 HL20260809095537293 / 2086270141158346754)
|
||||
|
||||
- `editableServiceDates` 返回 7 天(旧 08-18~21 ∪ 新 08-21~24)。
|
||||
- 旧实派行 08-18/19/20 恢复 `USED + vehicleId/driverId + rescheduleResidue=true + canDelete=true`。
|
||||
- 08-21 在新窗内 `rescheduleResidue=false`(不误标)。
|
||||
- 新窗 08-22~24 待派占位 `used=null + canDelete=true`。
|
||||
- 编译 / ArchTest 13·13 / spotless / 全量 `mvn -pl hl-fleet-service test` 3671 跑 0 错。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端消费**: @mmg(`rescheduleResidue`/`canDelete`/`deleteBlockReason` 三处透出)
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5914"
|
||||
title: "看板详情新增换版原因 reassignReasons:改人数/改出发日期/改行程天数的精确 diff"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a254f29f"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已实现并部署测试服,API 实测通过。订单因改人数/改出发日期/改行程天数触发车需求换版(DONE_ADJUST)后,看板详情端点 GET /admin/fleet/board/orders/{orderId} 新增 reassignReasons 字段(仅换版版本返回,否则为 null),给出精确 diff 供前端在排车页「当前需求」区提示车务为何需重新配车。示例:改人数 → [{type:HEADCOUNT_CHANGE, oldValue:10, newValue:11, label:出行人 10→11}];改出发日期 → DEPART_DATE_CHANGE 出发日期 8/18→8/21;改行程天数 → TRIP_DAYS_CHANGE 行程 4天→5天;多选并存返回多条。前端在「当前需求」区渲染 label 即可(label 后端直出可直接展示,oldValue/newValue/type 供自定义)。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 看板详情新增换版原因 reassignReasons(#5914)
|
||||
|
||||
> **服务**: hl-order-service-v3(记录)+ hl-fleet-service(透出)
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 车务派单看板「当前需求」区,订单因改人数/改期/改行程换版后需重新走配车确认,但车务看不到**为什么要重新配车**。本条在换版时记录精确原因并在看板返回,全部为**新增响应字段,向后兼容,无破坏性变更**。
|
||||
|
||||
---
|
||||
|
||||
## 一、变更接口
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|------|------|------|
|
||||
| `GET` | `/admin/fleet/board/orders/{orderId}` | 新增 `reassignReasons` 字段(仅换版版本返回) |
|
||||
|
||||
---
|
||||
|
||||
## 二、契约字段 reassignReasons
|
||||
|
||||
仅在当前生效用车需求为**换版**(DONE_ADJUST)产生的版本时返回;首提/待审编辑/幂等重提等非换版场景为 `null`。
|
||||
|
||||
数组,元素结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | string | `HEADCOUNT_CHANGE`(改人数) / `DEPART_DATE_CHANGE`(改出发日期) / `TRIP_DAYS_CHANGE`(改行程天数) |
|
||||
| `oldValue` | string | 旧值(人数 / 出发月日 / 天数) |
|
||||
| `newValue` | string | 新值 |
|
||||
| `label` | string | 后端直出中文,可直接展示 |
|
||||
|
||||
示例(同时改人数+出发日期):
|
||||
|
||||
```json
|
||||
"reassignReasons": [
|
||||
{ "type": "HEADCOUNT_CHANGE", "oldValue": "10", "newValue": "11", "label": "出行人 10→11" },
|
||||
{ "type": "DEPART_DATE_CHANGE", "oldValue": "8/18", "newValue": "8/21", "label": "出发日期 8/18→8/21" }
|
||||
]
|
||||
```
|
||||
|
||||
## 三、前端展示建议(mmg)
|
||||
|
||||
排车页「当前需求」区,在需求卡片下方按 `reassignReasons[]` 逐条渲染 `label`(如:「出行人 10→11」「出发日期 8/18→8/21」「行程 4天→5天」),提示车务本次为何需重新配车。字段为 `null` 时不渲染。
|
||||
|
||||
## 四、实现说明(后端,对前端透明)
|
||||
|
||||
- 换版统一收口 `RequirementService.doUpsertVehicleRequirement` DONE_ADJUST 分支,diff 旧版与新基线(人数/出发日期/行程天数)生成原因 JSON,存 `order_vehicle_requirement.adjust_reason`(新版本行;新 Flyway V20260812_001 加列)。
|
||||
- 经既有 Feign 契约 `VehicleRequirementForFleetDTO.reassignReasons` 透出(不新增跨服务调用),fleet 看板详情 `BoardOrderDetailVO.reassignReasons` 透传。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- [x] 改订单人数触发换版后,board detail 返回 `reassignReasons` 含「出行人 旧→新」
|
||||
- [x] 改出发日期触发换版后,含「出发日期 旧→新」
|
||||
- [x] 改行程天数触发换版后,含「行程 旧天→新天」
|
||||
- [x] 同时改多项返回多条
|
||||
- [x] 非换版(首版/待审编辑/幂等)该字段为 null
|
||||
|
||||
## 验证证据(测试服 API 实测,订单 2086270138171994114 / 2086270141158346754)
|
||||
|
||||
- **改人数换版(正向)**:订单 `2086270138171994114`(26-3698,原 10 人)提交调整 `updates.people.travelers.add` +1 出行人(10→11)触发 DONE_ADJUST 换版。DB 新版本行 `order_vehicle_requirement.adjust_reason` 落 `[{"type":"HEADCOUNT_CHANGE","oldValue":"10","newValue":"11","label":"出行人 10→11"}]`;`GET /admin/fleet/board/orders/2086270138171994114` 返回 `reassignReasons` 一致(requirementVersion=4,headcount=11)。
|
||||
- **非换版(反向)**:订单 `2086270141158346754`(26-0821,v7 PROCESSING 非 DONE_ADJUST 换版)返回 `reassignReasons=null`,不误标。
|
||||
- **改出发日期/行程天数/多项并存**:由 `RequirementServiceTest` 单测覆盖(DEPART_DATE_CHANGE 出发日期 7/20→8/18、TRIP_DAYS_CHANGE 行程 2天→3天、多项并存按序返回 3 条)。
|
||||
- **门禁**:Flyway `20260812.001 add vehicle requirement adjust reason` success=1、列已加;order-v3 274 / fleet 129 / ArchTest 44+13 / 50 个 MySQL IT 全绿;H2 IT schema 已同步。
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5915"
|
||||
title: "看板删除「换车请求」状态——statusOptions/statusCounts 不再含该无效项,statuses 不再接受 change_requested"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "58428593"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端已删除并部署测试服,API 实测通过。看板「换车请求」状态依赖 fleet_change_request 表,该表 M1 从未落地:change_requested 派生态恒为空、计数恒 0、筛选恒返空,是无效展示项,按 wx 指示整体删除。实测 summary:statusOptions=[unassigned,holding,assigned,completed,canceled](无换车请求)、statusCounts 无 changeRequested 字段;statuses=change_requested 现按 100001 参数非法拒绝(原恒返空)。前端待接入(pending):若看板状态下拉/筛选仍硬编码「换车请求」项需同步移除;若纯渲染后端 statusOptions 则自动受益。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 看板删除「换车请求」状态(#5915 / PR #5925)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #5925
|
||||
> **日期**: 2026-08-12
|
||||
> **背景**: 看板「换车请求」状态依赖 fleet_change_request 表,该表 M1 从未落地 → change_requested 派生态恒为空、计数恒 0、筛选恒返空,是无效展示项。按 wx 指示整体删除该看板状态。
|
||||
|
||||
---
|
||||
|
||||
## 变更接口
|
||||
|
||||
- `GET /admin/fleet/board/summary`(看板汇总)
|
||||
- `GET /admin/fleet/board/orders`(看板列表,statuses 筛选)
|
||||
|
||||
---
|
||||
|
||||
## 契约变化
|
||||
|
||||
### 1. summary.statusOptions 移除「换车请求」项
|
||||
|
||||
| 项 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| `statusOptions[].value` | 含 `change_requested` | 仅 `unassigned/holding/assigned/completed/canceled` |
|
||||
| `statusOptions[].label` | 含 `换车请求` | 仅 `待派车/排车中/已派车/已完结/已取消` |
|
||||
|
||||
实测返回:
|
||||
|
||||
```json
|
||||
"statusOptions": [
|
||||
{"value":"unassigned","label":"待派车"},
|
||||
{"value":"holding","label":"排车中"},
|
||||
{"value":"assigned","label":"已派车"},
|
||||
{"value":"completed","label":"已完结"},
|
||||
{"value":"canceled","label":"已取消"}
|
||||
]
|
||||
```
|
||||
|
||||
### 2. summary.statusCounts 移除 changeRequested 字段
|
||||
|
||||
| 字段 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| `statusCounts.changeRequested` | 恒 `0`(不落库,占位) | **字段删除**,不再下发 |
|
||||
|
||||
实测 `statusCounts` keys = `[unassigned, holding, assigned, completed, canceled, unassignedUrgent, holdingUrgent]`,无 `changeRequested`。
|
||||
|
||||
### 3. statuses 筛选不再接受 change_requested
|
||||
|
||||
| 入参 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| `statuses=change_requested` | 恒返空页(M1 无表,非报错) | **100001 参数非法**:`statuses 含非法状态值:change_requested(合法值:unassigned/unassigned_urgent/holding/holding_urgent/assigned/canceled/completed)` |
|
||||
|
||||
实测:
|
||||
|
||||
```json
|
||||
{"code":100001,"message":"参数非法: statuses 含非法状态值:change_requested(合法值:unassigned/unassigned_urgent/holding/holding_urgent/assigned/canceled/completed)","success":false}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 保留(不受影响)
|
||||
|
||||
「换车请求」作为**操作日志/时间线历史轨迹**保留,与看板状态无关:
|
||||
|
||||
- `AssignmentOperationTypeEnum.CHANGE_REQUESTED`(「发起修改派单」操作类型)
|
||||
- `TimelineEventTypeEnum.CHANGE_REQUESTED`(时间线事件类型)
|
||||
- 派单操作日志 / 订单时间线对历史 change_requested 记录的解析与展示不变。
|
||||
- `CreateAssignmentReqVO.changeRequestId` 历史兼容字段保留(当前创建接口不消费)。
|
||||
|
||||
## 前端接入提示
|
||||
|
||||
- 看板状态**下拉/筛选**:若前端硬编码了「换车请求」选项需同步移除;若纯渲染后端 `statusOptions` 则自动受益、无需改动。
|
||||
- 不要再向 `statuses` 传 `change_requested`,否则会收到 100001 参数非法。
|
||||
|
||||
## 验证证据(测试服 API 实测)
|
||||
|
||||
- `GET /admin/fleet/board/summary`:statusOptions 无换车请求、statusCounts 无 changeRequested。
|
||||
- `GET /admin/fleet/board/orders?statuses=change_requested`:返 100001 参数非法。
|
||||
- 单测 3679 全绿(含 FleetRedLineArchTest 13 项),spotless 通过。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,707 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5916"
|
||||
title: "核单两报表出参瘦身合并版(#5876 + #5916):orderHeader 拍平 + 审计字段下线 + finalize warnings 结构化 + summary 新增 settled(破坏性变更)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "ea2916fc"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "本份是 #5876(PR #5881)与 #5916(PR #5920)两个 PR 对核单报表接口的合并 changelog,以 #5916 合并后的最终出参为准。两份 PR 均已 merge 到 dev-v3 并部署测试服。破坏性:reimbursement 删顶层 orderHeader 块(11 抬头字段拍平进 baseInfo)+ baseInfo 删 10 字段(reportStatus/reportVersion/generated*/confirmed*/transferAmount/primaryReporterDueAmount)+ 新增 outstandingAmount;group 删 orderHeader 块(10 抬头字段平铺顶层)+ 删 reportStatus/generated*/confirmed* 共 8 字段;finalize warnings 由字符串数组改对象数组;summary 新增 settled。此前 #5876 单独那份(12_5876)的 finalize/summary 内容并入本份,前端按本份最终结构一次改到位即可。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【⚠️ 修改接口·管理后台】核单两报表出参瘦身(合并 #5876 + #5916 最终态)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
核单结算域的 4 个管理后台接口,近期经过**两次连续变更**(#5876 → #5916),本份 changelog 合并描述**累计最终态**,前端按本份一次改到位即可,**不要**分两次对接:
|
||||
|
||||
1. **主报账人报账表**(reimbursement):核单页查看主报账人(通常是司机)的代收、垫付支出、预支与净额结算情况。
|
||||
2. **单团核算表**(group):核单页 / 财务页查看整团收入、成本、毛利核算。
|
||||
3. **完成核单**(finalize):核单页点「完成核单」提交,原子冻结核单事实并生成核单汇总。
|
||||
4. **核单汇总快照**(summary):核单页 / 财务报表页读取整单金额、成本、毛利汇总。
|
||||
|
||||
两次变更分别做了什么:
|
||||
|
||||
- **#5876(PR #5881)**:finalize 出参 warnings 由字符串数组改为对象数组(可定位明细行);summary 出参新增 settled 字段(可靠判「是否已核单」)。
|
||||
- **#5916(PR #5920,最终态)**:两报表出参瘦身 —— 顶层 orderHeader 子块下线(抬头字段拍平),报表状态 / 生成确认审计字段出参下线,reimbursement 的尾款口径由「主报账人维度」换成「整单未收尾款」。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | reimbursement | 顶层 orderHeader 子块删除,11 个订单抬头字段拍平进 baseInfo(orderNo/teamNo/productName/productType/productTypeName/customerName/departDate/returnDate/consultantName/travelerCount/travelerComposition) | ⚠️ 破坏性 |
|
||||
| 2 | reimbursement | baseInfo 删除 10 个字段:reportStatus / reportVersion / generatedBy / generatedByName / generatedAt / confirmedBy / confirmedByName / confirmedAt / transferAmount / primaryReporterDueAmount | ⚠️ 破坏性 |
|
||||
| 3 | reimbursement | baseInfo 新增 outstandingAmount(整单未收尾款,口径同 financial-overview),替代已删的 primaryReporterDueAmount | ✨ 新增字段 |
|
||||
| 4 | group | 顶层 orderHeader 子块删除,10 个订单抬头字段平铺到顶层(orderNo/teamNo/productName/productType/productTypeName/customerName/departDate/returnDate/consultantName/travelerComposition) | ⚠️ 破坏性 |
|
||||
| 5 | group | 删除 8 个字段:reportStatus / generatedBy / generatedByName / generatedAt / confirmedBy / confirmedByName / confirmedAt,及 orderHeader 块整体 | ⚠️ 破坏性 |
|
||||
| 6 | finalize | 出参 warnings 由 List<String> 改为 List<WarningItemVO>(每条带 settlementId / category / dayNumber / message)(#5876 引入,本份仍有效) | ⚠️ 破坏性 |
|
||||
| 7 | summary | 出参新增 settled 字段(Boolean):已核单 = true,未核单空壳 = false(#5876 引入,本份仍有效) | ✨ 新增字段 |
|
||||
|
||||
无入参变化、无 DDL(报表状态 / 版本列仍保留在 DB,仅出参不再下发)。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 查主报账人报账表
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
|
||||
| 接口名 | 查询主报账人报账表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表 |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
### 3.2 查单团核算表
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/group |
|
||||
| 接口名 | 查询单团核算表 |
|
||||
| 使用场景 | 管理后台核单页 / 财务页,查看整团收入、成本、毛利核算 |
|
||||
| 认证 | 管理后台 JWT |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流 |
|
||||
|
||||
### 3.3 完成核单
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | POST /v3/admin/order/{orderId}/settlement/finalize |
|
||||
| 接口名 | 完成核单 |
|
||||
| 使用场景 | 管理后台核单页,明细核对完成后提交,原子冻结核单事实并生成核单汇总快照 |
|
||||
| 认证 | 管理后台 JWT |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 非幂等写操作;重复提交会被「缺少核单终态快照 / 快照已变化」类错误码拦截,前端不要自动重试 |
|
||||
| 限流 | 走网关默认限流 |
|
||||
|
||||
### 3.4 查核单汇总快照
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/summary |
|
||||
| 接口名 | 查核单汇总快照 |
|
||||
| 使用场景 | 管理后台核单页 / 财务报表页读取整单金额、成本、毛利汇总 |
|
||||
| 认证 | 管理后台 JWT |
|
||||
| 角色限制 | 无接口级角色限制(网关鉴权通过即可) |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 接口 | 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| 全部 4 个接口 | orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
|
||||
|
||||
### 4.2 请求体 / Query
|
||||
|
||||
4 个接口均无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 reimbursement 出参(Result<SettlementReimbursementReportRespVO>,最终结构)
|
||||
|
||||
**顶层结构**(只有 4 个字段,不再有 orderHeader 块、不再有平铺汇总字段):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| baseInfo | Object | 是 | 基础信息(订单抬头 + 报表汇总 + 报账人),恒下发对象 |
|
||||
| incomeLines | Array | 是 | 收入行(司机/报账人代收);无数据固定返回空数组 [] |
|
||||
| expenseLines | Array | 是 | 支出行(统一扁平字段,仅报账人垫付 CASH_PAID 支出);无数据固定返回空数组 [] |
|
||||
| advanceLines | Array | 是 | 预支明细行(已审批预支逐条);无数据固定返回空数组 [] |
|
||||
|
||||
**baseInfo 字段表(最终 21 字段)**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(String) | 核单记录 ID(settlement_recon.recon_id),序列化为字符串;未生成时不输出 |
|
||||
| orderId | Long(String) | 订单 ID,序列化为字符串 |
|
||||
| orderNo | String | 订单号 |
|
||||
| teamNo | String | 团号(订金未支付时不输出) |
|
||||
| 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幼童」 |
|
||||
| primaryReporterId | Long(String) | 主报账人人员安排 ID,序列化为字符串 |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| primaryReporterCollectedAmount | BigDecimal | 主报账人代收金额(收入行合计),保留两位小数 |
|
||||
| approvedAdvanceAmount | BigDecimal | 已审批预支金额(预支行合计),保留两位小数 |
|
||||
| reportablePaidCostAmount | BigDecimal | 可报账已付成本(支出行合计),保留两位小数 |
|
||||
| reporterNetAmount | BigDecimal | 报账人净额(代收 + 预支 - 支出);正 = 报账人应转回公司,负 = 公司应补报账人 |
|
||||
| outstandingAmount | BigDecimal | **本次新增**:整单未收尾款(口径同核单应收财务总览 outstandingAmount),保留两位小数 |
|
||||
|
||||
> 注:baseInfo 带 @JsonInclude(NON_NULL),null 字段不下发该 key,前端读取要兜底。
|
||||
|
||||
**转账方向判定**(transferAmount / transferDirection 均已删,前端自行推导):
|
||||
|
||||
- reporterNetAmount > 0 → 报账人应转回公司
|
||||
- reporterNetAmount < 0 → 公司应补报账人
|
||||
- 转账金额 = reporterNetAmount 的绝对值
|
||||
|
||||
**incomeLines 元素字段表**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | 行类型,当前仅 DRIVER_CASH_RECEIPT(司机/报账人现金收款) |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| receiptId | Long(String) | 线下收款记录 ID,序列化为字符串 |
|
||||
| amount | BigDecimal | 收款金额,保留两位小数 |
|
||||
| channel | String | 收款渠道:DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
|
||||
| channelName | String | 收款渠道中文名(枚举 label) |
|
||||
| payType | String | 收款款项类型:DEPOSIT / FULL / BALANCE |
|
||||
| payTypeName | String | 款项类型中文名(枚举 label) |
|
||||
| collectorStaffId | Long(String) | 收款人人员安排 ID,序列化为字符串 |
|
||||
| collectorName | String | 收款人姓名 |
|
||||
| collectorRole | String | 收款人角色(如 DRIVER) |
|
||||
| collectorRoleName | String | 收款人角色中文名(字典 staff_role) |
|
||||
| receivedAt | String | 收款时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| remark | String | 备注;无备注时不输出 |
|
||||
|
||||
**expenseLines 元素字段表(统一 12 字段,所有费用分类共用)**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| category | String | 费用类别: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 数组;无凭证时不输出该键 |
|
||||
|
||||
**advanceLines 元素字段表**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | 行类型,当前仅 APPROVED_ADVANCE(已审批预支) |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| advanceId | Long(String) | 预支单 ID,序列化为字符串 |
|
||||
| payeeStaffId | Long(String) | 借款对象人员安排 ID,序列化为字符串 |
|
||||
| payeeName | String | 借款对象姓名 |
|
||||
| payeeRole | String | 借款对象角色(如 DRIVER) |
|
||||
| payeeRoleName | String | 借款对象角色中文名(字典 staff_role) |
|
||||
| advanceType | String | 预支类型(如 ACCOMMODATION_DEPOSIT) |
|
||||
| advanceTypeName | String | 预支类型中文名(字典 advance_type) |
|
||||
| amount | BigDecimal | 预支金额,保留两位小数 |
|
||||
| purpose | String | 预支用途 |
|
||||
| voucherUrl | String | 凭证 URL |
|
||||
| status | String | 预支状态:SUBMITTED / APPROVED / REJECTED |
|
||||
| statusText | String | 预支状态中文名(枚举 label) |
|
||||
| submittedAt | String | 提交时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| approvedAt | String | 审批时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| approvedBy | String | 审批人姓名 |
|
||||
|
||||
### 5.2 group 出参(Result<SettlementGroupReportRespVO>,最终结构:全部平铺顶层)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(String) | 单团核算记录 ID,序列化为字符串 |
|
||||
| orderId | Long(String) | 订单 ID,序列化为字符串 |
|
||||
| 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 | 定制师姓名 |
|
||||
| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null) |
|
||||
| baseOrderAmount | BigDecimal | 订单应收(基础订单金额) |
|
||||
| otherIncomeAmount | BigDecimal | 其他收入合计 |
|
||||
| discountAmount | BigDecimal | 优惠金额(负数) |
|
||||
| adjustedReceivableAmount | BigDecimal | 调整后应收 = baseOrderAmount + otherIncomeAmount + discountAmount |
|
||||
| paidAmount | BigDecimal | 已收金额 |
|
||||
| actualRefundedAmount | BigDecimal | 实际退款金额 |
|
||||
| netRevenueAmount | BigDecimal | 净收入 = adjustedReceivableAmount - actualRefundedAmount |
|
||||
| netReceivedAmount | BigDecimal | 净实收 = paidAmount - actualRefundedAmount |
|
||||
| outstandingAmount | BigDecimal | 未收尾款(口径同 financial-overview) |
|
||||
| hotelCost | BigDecimal | 住宿成本 |
|
||||
| ticketCost | BigDecimal | 门票成本 |
|
||||
| mealCost | BigDecimal | 餐食成本 |
|
||||
| vehicleCost | BigDecimal | 车辆成本 |
|
||||
| guideCost | BigDecimal | 导游成本 |
|
||||
| photographerCost | BigDecimal | 摄影成本 |
|
||||
| otherExpenseCost | BigDecimal | 其他支出成本 |
|
||||
| insurancePremium | BigDecimal | 保险保费 |
|
||||
| totalCost | BigDecimal | 总成本 |
|
||||
| paidCost | BigDecimal | 已付成本 |
|
||||
| unpaidCost | BigDecimal | 未付成本 |
|
||||
| grossProfit | BigDecimal | 毛利 = netRevenueAmount - totalCost |
|
||||
| grossProfitRate | BigDecimal | 毛利率(小数,netRevenueAmount=0 时填 0) |
|
||||
| travelerCount | Integer | 出行人总数 |
|
||||
| perCapitaRevenue | BigDecimal | 人均收入(travelerCount=0 时为 null) |
|
||||
| perCapitaCost | BigDecimal | 人均成本 |
|
||||
| perCapitaProfit | BigDecimal | 人均毛利 |
|
||||
| incomeLines | Array | 收入行,固定 4 行(见下) |
|
||||
| costCategories | Array | 成本分类行,固定 8 行(见下) |
|
||||
|
||||
**incomeLines 元素**(固定 4 行):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | BASE_ORDER(订单应收)/ OTHER_INCOME(其他收入)/ DISCOUNT(优惠,负数)/ ACTUAL_REFUND(实际退款,负数) |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| amount | BigDecimal | 金额,保留两位小数;DISCOUNT / ACTUAL_REFUND 为负数 |
|
||||
| details | Array | 收入逐项明细;无逐项时为空数组 [] |
|
||||
|
||||
**costCategories 元素**(固定 8 行):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| category | String | HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE |
|
||||
| categoryName | String | 费用类别中文名(字典 settlement_category) |
|
||||
| amount | BigDecimal | 金额,保留两位小数 |
|
||||
| lines | Array | 成本逐项明细(分类专属行结构,前端按 category 判别窄化);无逐项时为空数组 [] |
|
||||
|
||||
### 5.3 finalize 出参(Result<SettlementSubmitRespVO>,本次仅 warnings 变化)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| summaryId | Long(String) | 新写入的 settlement_summary 主键,序列化为字符串 |
|
||||
| finalSnapshotId | Long(String) | 核单终态快照 ID,序列化为字符串 |
|
||||
| finalSnapshotVersionNo | Integer | 核单终态快照版本号 |
|
||||
| finalSnapshotStatus | String | 核单终态快照状态,固定 FINALIZED |
|
||||
| orderId | Long(String) | 订单 ID,序列化为字符串 |
|
||||
| settledAt | String | 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss |
|
||||
| totalAmount | String | 订单总金额快照(BigDecimal 序列化为字符串,下同) |
|
||||
| paidAmount | String | 已付金额快照 |
|
||||
| balanceAmount | String | 尾款金额快照 |
|
||||
| roomCost | String | 住宿实际成本 |
|
||||
| ticketCost | String | 门票实际成本 |
|
||||
| staffCost | String | 人员费用实际成本 |
|
||||
| subsidyCost | String | 补助实际成本 |
|
||||
| mealCost | String | 餐食实际成本 |
|
||||
| vehicleCost | String | 车辆基础服务总车费 |
|
||||
| otherExpenseCost | String | 其他支出实际成本 |
|
||||
| insurancePremium | String | 保险实际保费(未出单/已撤单 = 0) |
|
||||
| totalActualCost | String | 总实际成本 |
|
||||
| driverTransferAmount | String | 给司机/主报账人转回金额(仅计 CASH_PAID,不含保险) |
|
||||
| profitAmount | String | 公司毛利 = totalAmount - totalActualCost |
|
||||
| profitRate | Number | 毛利率(小数,totalAmount=0 时填 0) |
|
||||
| orderStatusAfter | String | 结算后订单状态中文文案,如「待财务复核」 |
|
||||
| mqTriggered | Boolean | 当前版本固定 false;完成核单不发布结算 MQ |
|
||||
| warnings | Array<WarningItemVO> | **本次变更**:软预警对象数组(原来为字符串数组);无预警时为空数组 [] |
|
||||
|
||||
**WarningItemVO 字段表**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| settlementId | Long(String) | 触发预警的核单明细行 id(settlement_hotel / settlement_ticket 主键),序列化为字符串;前端据此锚定/跳转具体明细行 |
|
||||
| category | String | 核单分类枚举名,当前仅 HOTEL(住宿)/ TICKET(门票/游玩项目)两类会触发预警 |
|
||||
| dayNumber | Integer | 行程第几天 |
|
||||
| message | String | 预警文案,如「住宿 D2 现付缺凭证」 |
|
||||
|
||||
### 5.4 summary 出参(Result<SettlementSummaryRespVO>,本次新增 settled)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| settled | Boolean | **本次新增**:是否已核单。summary 快照存在 = true;未核单空壳 = false |
|
||||
| id | Long | settlement_summary 主键;未核单时为 null |
|
||||
| orderId | Long | 订单 ID;空壳时也有值 |
|
||||
| totalAmount | String | 订单总金额快照(BigDecimal 序列化为字符串,下同) |
|
||||
| paidAmount | String | 已付金额快照 |
|
||||
| balanceAmount | String | 尾款金额快照 |
|
||||
| roomCost | String | 住宿实际成本 |
|
||||
| ticketCost | String | 门票实际成本 |
|
||||
| staffCost | String | 人员费用实际成本 |
|
||||
| subsidyCost | String | 补助实际成本 |
|
||||
| mealCost | String | 餐食实际成本 |
|
||||
| vehicleCost | String | 车辆基础服务总车费 |
|
||||
| otherExpenseCost | String | 其他支出实际成本 |
|
||||
| insurancePremium | String | 保险实际保费 |
|
||||
| refundTotal | String | 返还合计(只读展示,不进利润公式) |
|
||||
| totalActualCost | String | 总实际成本 |
|
||||
| driverTransferAmount | String | 给司机/主报账人转回金额(仅计 CASH_PAID) |
|
||||
| profitAmount | String | 公司毛利 |
|
||||
| profitRate | Number | 毛利率(小数) |
|
||||
| settledBy | Long | 核单人 id;未核单时为 null |
|
||||
| settledByName | String | 核单人姓名快照 |
|
||||
| settledAt | String | 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss |
|
||||
| remark | String | 核单备注 |
|
||||
| finalSnapshotId | Long(String) | 当前核单终态快照 ID;未完成核单或已重新打开时为 null |
|
||||
| finalSnapshotVersionNo | Integer | 当前核单终态快照版本号;无当前快照时为 null |
|
||||
| finalSnapshotStatus | String | 有当前快照时固定 FINALIZED,否则为 null |
|
||||
| finalizedAt | String | 当前核单终态快照完成时间;无当前快照时为 null |
|
||||
| finalizedByName | String | 当前核单终态快照操作人姓名;无当前快照时为 null |
|
||||
| advanceSummary | Object | 已审批通过的订单预支汇总;**恒下发对象,不是 null**(空壳时也有值),含 approvedAmount(String,默认 "0")+ records(Array,默认 []) |
|
||||
|
||||
> 未核单空壳时:settled=false,仅 orderId 与 advanceSummary(默认空对象)有值,**其余字段全部为 null**。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
| 字段 | 来源 | 取值 |
|
||||
|------|------|------|
|
||||
| expenseLines[].category / costCategories[].category | settlement_category 字典 | HOTEL(住宿)/ TICKET(门票)/ MEAL(餐食)/ VEHICLE(车辆)/ GUIDE(导游)/ PHOTOGRAPHER(摄影)/ OTHER_EXPENSE(其他支出)/ INSURANCE(保险) |
|
||||
| expenseLines[].paymentMethod | settlement_payment_method 字典 | CASH_PAID(现金已付)/ COMPANY_PAID(公司直付)/ SIGNED(签单);报账支出行固定 CASH_PAID |
|
||||
| incomeLines[].type / advanceLines[].type | settlement_report_line_type 字典 | DRIVER_CASH_RECEIPT / APPROVED_ADVANCE / BASE_ORDER / OTHER_INCOME / DISCOUNT / ACTUAL_REFUND |
|
||||
| incomeLines[].channel(reimbursement) | PaymentChannelEnum 枚举 | DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
|
||||
| incomeLines[].payType(reimbursement) | PayType 枚举 | DEPOSIT(订金)/ FULL(全款)/ BALANCE(尾款) |
|
||||
| collectorRole / payeeRole / primaryReporterRole | staff_role 字典 | DRIVER(司机)/ GUIDE(导游)等 |
|
||||
| advanceLines[].advanceType | advance_type 字典 | 如 ACCOMMODATION_DEPOSIT(住宿押金)等 |
|
||||
| advanceLines[].status | AdvanceStatus 枚举 | SUBMITTED / APPROVED / REJECTED |
|
||||
| productType | product_type 字典 | 如 CORE(核心产品)等 |
|
||||
| warnings[].category | SettlementCategory 枚举 | 全量同 settlement_category;**当前预警只会出现 HOTEL / TICKET** |
|
||||
| finalSnapshotStatus | 快照状态枚举 | FINALIZED(已定稿) |
|
||||
|
||||
本次无枚举值增删;category 只是从隐含在预警文案里变成显式字段。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
### 7.1 reimbursement / group(两报表)
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
|
||||
### 7.2 finalize
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
| 584081 | 对账数据不一致:已付金额与支付流水和线下收款合计不符,禁止带病结算 | paid_amount 与实际入账总额不符 |
|
||||
| 584085 | 存在辅助人员结算未完成,请全部结算后提交 | 任一非主报账司机费用行未结算完成或缺转账凭证号 |
|
||||
| 584321 | 当前订单缺少核单终态快照,请重新完成核单 | 缺终态快照(含重复提交场景) |
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
|
||||
> finalize 还有人员费用 / 其他收入未就绪等前置门禁错误码(584xxx 段),非本次变更,按响应 message 直接提示即可。
|
||||
|
||||
### 7.3 summary
|
||||
|
||||
只读接口,无业务错误码;orderId 查不到快照时返回 200 + 空壳对象(settled=false),**不会报「订单不存在」**。
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
**reimbursement**(最终结构:抬头在 baseInfo 内,无 orderHeader 块):
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"id": "6001",
|
||||
"orderId": "2087088947225038849",
|
||||
"orderNo": "HL20260730001",
|
||||
"teamNo": "T20260730001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-07-30",
|
||||
"returnDate": "2026-08-03",
|
||||
"consultantName": "李四",
|
||||
"travelerCount": 5,
|
||||
"travelerComposition": "2大 2儿童 1幼童",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 2000.00,
|
||||
"approvedAdvanceAmount": 500.00,
|
||||
"reportablePaidCostAmount": 1800.00,
|
||||
"reporterNetAmount": 700.00,
|
||||
"outstandingAmount": 0.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-07-30 18:20:30",
|
||||
"remark": "尾款现金"
|
||||
}
|
||||
],
|
||||
"expenseLines": [
|
||||
{
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"itemName": "草原明珠大酒店-标间",
|
||||
"unitPrice": 400.00,
|
||||
"quantity": 3,
|
||||
"amount": 1200.00,
|
||||
"reimburseAmount": 1200.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-07-30",
|
||||
"remark": "含早",
|
||||
"voucherUrls": ["https://oss/v1.jpg"]
|
||||
}
|
||||
],
|
||||
"advanceLines": [
|
||||
{
|
||||
"type": "APPROVED_ADVANCE",
|
||||
"typeName": "已审批预支",
|
||||
"advanceId": "5001",
|
||||
"payeeStaffId": "7001",
|
||||
"payeeName": "司机甲",
|
||||
"payeeRole": "DRIVER",
|
||||
"payeeRoleName": "司机",
|
||||
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||
"advanceTypeName": "住宿押金",
|
||||
"amount": 500.00,
|
||||
"purpose": "酒店押金",
|
||||
"voucherUrl": "https://oss/advance-v1.jpg",
|
||||
"status": "APPROVED",
|
||||
"statusText": "已通过",
|
||||
"submittedAt": "2026-07-28 10:00:00",
|
||||
"approvedAt": "2026-07-28 12:00:00",
|
||||
"approvedBy": "财务丙"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**group**(最终结构:抬头平铺顶层,无 orderHeader 块):
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/reports/group
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "6002",
|
||||
"orderId": "2087088947225038849",
|
||||
"orderNo": "HL20260730001",
|
||||
"teamNo": "T20260730001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-07-30",
|
||||
"returnDate": "2026-08-03",
|
||||
"consultantName": "李四",
|
||||
"travelerComposition": "2大 2儿童 1幼童",
|
||||
"baseOrderAmount": 12800.00,
|
||||
"otherIncomeAmount": 600.00,
|
||||
"discountAmount": -200.00,
|
||||
"adjustedReceivableAmount": 13200.00,
|
||||
"paidAmount": 13200.00,
|
||||
"actualRefundedAmount": 0.00,
|
||||
"netRevenueAmount": 13200.00,
|
||||
"netReceivedAmount": 13200.00,
|
||||
"outstandingAmount": 0.00,
|
||||
"hotelCost": 3600.00,
|
||||
"ticketCost": 1500.00,
|
||||
"mealCost": 900.00,
|
||||
"vehicleCost": 2500.00,
|
||||
"guideCost": 800.00,
|
||||
"photographerCost": 0.00,
|
||||
"otherExpenseCost": 300.00,
|
||||
"insurancePremium": 180.00,
|
||||
"totalCost": 9780.00,
|
||||
"paidCost": 9780.00,
|
||||
"unpaidCost": 0.00,
|
||||
"grossProfit": 3420.00,
|
||||
"grossProfitRate": 0.2591,
|
||||
"travelerCount": 5,
|
||||
"perCapitaRevenue": 2640.00,
|
||||
"perCapitaCost": 1956.00,
|
||||
"perCapitaProfit": 684.00,
|
||||
"incomeLines": [
|
||||
{ "type": "BASE_ORDER", "typeName": "订单应收", "amount": 12800.00, "details": [] },
|
||||
{ "type": "OTHER_INCOME", "typeName": "其他收入", "amount": 600.00, "details": [] },
|
||||
{ "type": "DISCOUNT", "typeName": "优惠", "amount": -200.00, "details": [] },
|
||||
{ "type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": 0.00, "details": [] }
|
||||
],
|
||||
"costCategories": [
|
||||
{ "category": "HOTEL", "categoryName": "住宿", "amount": 3600.00, "lines": [] },
|
||||
{ "category": "TICKET", "categoryName": "门票", "amount": 1500.00, "lines": [] },
|
||||
{ "category": "MEAL", "categoryName": "餐食", "amount": 900.00, "lines": [] },
|
||||
{ "category": "VEHICLE", "categoryName": "车辆", "amount": 2500.00, "lines": [] },
|
||||
{ "category": "GUIDE", "categoryName": "导游", "amount": 800.00, "lines": [] },
|
||||
{ "category": "PHOTOGRAPHER", "categoryName": "摄影", "amount": 0.00, "lines": [] },
|
||||
{ "category": "OTHER_EXPENSE", "categoryName": "其他支出", "amount": 300.00, "lines": [] },
|
||||
{ "category": "INSURANCE", "categoryName": "保险", "amount": 180.00, "lines": [] }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**边界 1:订金未支付、无团号**——reimbursement 的 baseInfo 因 NON_NULL 不输出 teamNo 键;group 顶层 teamNo 为 null。前端两处都要兜底:
|
||||
|
||||
```json
|
||||
"baseInfo": {
|
||||
"orderId": "2087088947225038850",
|
||||
"orderNo": "HL20260801002",
|
||||
"productName": "库布其沙漠 2 日游",
|
||||
"outstandingAmount": 3200.00
|
||||
}
|
||||
```
|
||||
|
||||
**边界 2:无任何明细行**——incomeLines / expenseLines / advanceLines 均为空数组 [],不是 null:
|
||||
|
||||
```json
|
||||
{ "incomeLines": [], "expenseLines": [], "advanceLines": [] }
|
||||
```
|
||||
|
||||
**边界 3:finalize 无软预警 + summary 查未核单订单**——warnings 为空数组 [];summary 返回空壳(settled=false,除 orderId / advanceSummary 外全 null):
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "settled": false, "id": null, "orderId": "2087088947225038850", "totalAmount": null, "advanceSummary": { "approvedAmount": "0", "records": [] } }, "success": true }
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**finalize 辅助人员结算未完成**:
|
||||
|
||||
```json
|
||||
{ "code": 584085, "message": "存在辅助人员结算未完成,请全部结算后提交", "success": false }
|
||||
```
|
||||
|
||||
**房务角色访问报表被拦**(House 角色 JWT 调 reimbursement / group / finalize):
|
||||
|
||||
```json
|
||||
{ "code": 403, "message": "无权限访问", "success": false }
|
||||
```
|
||||
|
||||
**订单不存在**:
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- reimbursement:订单核单页查看主报账人结算报表;支出行只含报账人垫付(CASH_PAID)支出,签单/公司直付不进本列表
|
||||
- group:整团收入成本毛利核算;incomeLines 固定 4 行、costCategories 固定 8 行恒下发(金额为 0 的行也在)
|
||||
- finalize:核单明细已逐步保存完成、核对无误后提交
|
||||
- summary:随时读取汇总;未核单订单也可调(返回空壳),前端据此展示「尚未核单」占位
|
||||
|
||||
**不适用**:
|
||||
|
||||
- reimbursement:想看待收整单尾款时不要再用已删的 primaryReporterDueAmount(主报账人维度),改读 outstandingAmount(整单维度)
|
||||
- finalize:明细未保存完整 / 辅助人员结算未完成 / 对账不一致的订单(被 584xxx 门禁拦截,见 §7)
|
||||
- summary:想看明细行级数据时不要用它,走各分类明细接口;本接口只有汇总快照
|
||||
|
||||
**特殊边界**:
|
||||
|
||||
- 报表的「报表状态 / 生成人 / 确认人」审计信息**不再下发**(DB 仍保留),前端如曾展示「已生成/已确认」徽标需删除该 UI
|
||||
- warnings 是**软预警**,不阻塞 finalize 提交;有预警也照常返回 200 完成核单
|
||||
- 预警仅覆盖 CASH_PAID(现付)且 voucher_urls 为空的住宿 / 门票明细行;签单、公司直付等其他支付方式不产生预警
|
||||
- summary 空壳的 advanceSummary 恒为默认对象(approvedAmount="0"、records=[]),**不是 null**,不能拿它当判空依据
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
> 「修改前」= 两次变更前的旧结构(含 orderHeader 子块 + 审计字段);「修改后」= 本份最终态。
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 接口 | 字段 | 修改前 | 修改后 |
|
||||
|------|------|--------|--------|
|
||||
| reimbursement | 顶层 orderHeader | Object 子块(11 个抬头字段) | **删除**,11 字段拍平进 baseInfo |
|
||||
| reimbursement | baseInfo.reportStatus / reportVersion | 有 | **删除** |
|
||||
| reimbursement | baseInfo.generatedBy / generatedByName / generatedAt | 有 | **删除** |
|
||||
| reimbursement | baseInfo.confirmedBy / confirmedByName / confirmedAt | 有 | **删除** |
|
||||
| reimbursement | baseInfo.transferAmount | 有(净额绝对值) | **删除**,前端用 reporterNetAmount 绝对值自取 |
|
||||
| reimbursement | baseInfo.primaryReporterDueAmount | 有(主报账人维度应收尾款) | **删除**,由 outstandingAmount 替代 |
|
||||
| reimbursement | baseInfo.outstandingAmount | 无 | **新增**:整单未收尾款(口径同 financial-overview) |
|
||||
| group | 顶层 orderHeader | Object 子块(10 个抬头字段) | **删除**,10 字段平铺到顶层 |
|
||||
| group | reportStatus | 有 | **删除** |
|
||||
| group | generatedBy / generatedByName / generatedAt | 有 | **删除** |
|
||||
| group | confirmedBy / confirmedByName / confirmedAt | 有 | **删除** |
|
||||
| finalize | warnings | List<String>,元素如「住宿 D2 现付缺凭证」字符串 | List<WarningItemVO>,元素为对象 { settlementId, category, dayNumber, message } |
|
||||
| summary | settled | 无此字段 | 新增 Boolean:已核单 = true,未核单空壳 = false |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 读订单抬头(两报表) | 读 orderHeader.orderNo 等 | 读 baseInfo.orderNo(reimbursement)/ 顶层 orderNo(group) |
|
||||
| 展示「已生成/已确认」徽标 | 读 reportStatus / confirmedAt | **字段已删除,恒取不到值**,UI 必须删除该徽标 |
|
||||
| 展示转账金额/方向 | 读 transferAmount / 推导方向 | 用 reporterNetAmount 正负推导方向,金额取绝对值 |
|
||||
| 展示应收尾款(reimbursement) | 读 primaryReporterDueAmount(主报账人维度) | 读 outstandingAmount(整单维度,与财务总览一致) |
|
||||
| 同日多条同类缺凭证预警 | 多条相同文案,无法区分哪一行 | 每条带独立 settlementId,可定位/跳转具体明细行 |
|
||||
| 前端渲染 finalize 预警 | 直接渲染字符串 | 必须读 warnings[].message |
|
||||
| 前端判「是否已核单」(summary) | 判 data.id == null(不可靠) | 判 data.settled === false |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:⚠️ 破坏性。删 orderHeader 块 + 删 baseInfo/顶层多字段 + finalize warnings 结构变化,按旧结构取值的前端代码会拿到 undefined / 渲染异常。
|
||||
- **前端必须同步上线**:是。前端需要:
|
||||
1. reimbursement 抬头取值从 orderHeader.xxx 改 baseInfo.xxx;group 抬头取值从 orderHeader.xxx 改顶层 xxx
|
||||
2. 删除「报表状态 / 生成人 / 确认人」相关 UI(字段已删,恒 undefined)
|
||||
3. 转账金额改取 reporterNetAmount 绝对值,方向按 reporterNetAmount 正负推导
|
||||
4. 应收尾款改读 outstandingAmount(注意口径已从主报账人维度变整单维度,展示文案如有「主报账人应收」字样需同步改)
|
||||
5. finalize warnings 渲染改读 item.message,可用 settlementId + category 做行锚定
|
||||
6. summary 判「是否已核单」改读 settled 字段
|
||||
- **后端兼容**:零 DDL、无数据迁移;DB 的 report_status / 版本 / 审计列保留,finalize 幂等与反确认逻辑不受影响。
|
||||
- **本份覆盖旧份**:此前 2026-08-12 推送的 12_5876_核单finalize-summary出参整改-修改接口-管理后台.md 内容已并入本份;两报表结构**以本份为准**。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = 依次 revert PR #5920 的 merge commit(b912ff4876)与 PR #5881 的 merge commit(125bb9c96e),重启 hl-order-service-v3。只 revert b912ff4876 则回到 #5876 后的中间态(orderHeader 块恢复 + 审计字段恢复,warnings/settled 保留)。
|
||||
- 前端回滚 = 切回旧版前端包。注意新旧前后端要配套:新前端 + 旧后端时 baseInfo.orderNo / outstandingAmount / settled 取不到值。
|
||||
- 零 DDL,回滚无残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **所有 Long 主键字段序列化为 JSON 字符串**(id / orderId / primaryReporterId / receiptId / collectorStaffId / advanceId / payeeStaffId / settlementId 等,防 JS 精度丢失),前端按 string 处理,不要 Number() 转换。
|
||||
2. reimbursement 的 baseInfo / incomeLines / expenseLines / advanceLines 均带 @JsonInclude(NON_NULL):**null 字段不下发该 key**(不是下 null),前端读取必须兜底,不能假设 key 恒存在。
|
||||
3. group 出参不做 NON_NULL 裁剪,teamNo 等可空字段会下发 null,注意与 reimbursement 的差异。
|
||||
4. expenseLines 支出行**只含报账人垫付(CASH_PAID)支出**;签单 / 公司直付不在本列表,不要拿 expenseLines 合计去对整单成本(整单成本看 group 报表)。
|
||||
5. group 的 incomeLines 固定 4 行、costCategories 固定 8 行恒下发(含金额 0 的行),前端按 type / category 取值渲染即可,不要按数组下标硬编码。
|
||||
6. outstandingAmount 是**整单**未收尾款(与核单应收财务总览同口径),不是主报账人维度;两报表该字段口径一致。
|
||||
7. warnings 无预警时返回空数组 [] 不是 null;category 当前只会出现 HOTEL / TICKET,做映射表建议按全量 settlement_category 枚举写,缺值兜底显示原 code。
|
||||
8. summary 判空**只认 settled**:settled === false 即「尚未核单」,此时除 orderId / advanceSummary 外全部字段为 null,展示必须兜底。
|
||||
9. finalize 是写操作且有前置门禁,失败按错误码 message 提示,**不要静默重试**;重复提交会被快照类错误码拦截。
|
||||
10. finalize / summary 的金额类字段序列化为**字符串**;两报表(reimbursement / group)的金额类字段为 JSON 数值(BigDecimal 两位小数),两类接口序列化风格不同,前端注意区分。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue #5876:https://git.1814.love:8443/wx/HL/issues/5876
|
||||
- Issue #5916:https://git.1814.love:8443/wx/HL/issues/5916
|
||||
- PR #5881:https://git.1814.love:8443/wx/HL/pulls/5881
|
||||
- PR #5920:https://git.1814.love:8443/wx/HL/pulls/5920
|
||||
- Commit(#5876):https://git.1814.love:8443/wx/HL/commit/125bb9c96e
|
||||
- Commit(#5916 最终态):https://git.1814.love:8443/wx/HL/commit/b912ff4876
|
||||
- 服务:hl-order-service-v3(端口 8086)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5926"
|
||||
title: "排车 Step2 栅格改期残留旧实派行不回显车牌/司机/价(前端渲染)"
|
||||
consumer: "admin"
|
||||
change_type: "前端修复"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "01ae772f"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "#5926 明文回显+删后刷新已由前端 01ae772f 验证;但原『取消残留派车』误用整槽接口导致 #5937 P0。逐日取消新契约以 #5937 为准:使用 cells[].assignmentId 调 POST /admin/fleet/assignments/{assignmentId}/cancel-residue,禁止老 DELETE /assignments/{assignmentId} 和槽位删除。#5937 前端状态仍 pending。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 排车 Step2 栅格改期残留旧实派行不回显车牌/司机/价(#5926 / PR #5930,承接 #5912)
|
||||
|
||||
> **服务**: hl-fleet-service(后端) + hl-ui(前端渲染)
|
||||
> **PR**: #5930(后端字段透出)
|
||||
> **日期**: 2026-08-12
|
||||
> **现象**: 订单改期后,派单页 Step2 排车栅格里 08-18/19/20 这三行旧实派数据显示成「车辆待选择 / 司机待选择 / ¥0.00」空态,只挂了个「改期残留·待清理」红标签——数据列不回显。
|
||||
|
||||
---
|
||||
|
||||
## 根因(前后端各一半,后端缺口已补)
|
||||
|
||||
1. **#5912 已修日期轴**:栅格 `editableServiceDates` 已并入旧行日期(08-18~24 共 7 天),旧实派行的日期格回来了。
|
||||
2. **后端缺口(本次 #5926 补)**:栅格 `cells[]` 原本只透 `vehicleId`/`driverId` 两个 ID,无明文;而 `selectedVehicle`/`selectedDriver` 展示快照只在「本次新选中」时填充,改期前旧会话派的行进入 Step2 时为 **null**。前端拿不到明文,只能贴残留标签、数据列留空。
|
||||
3. **前端待做**:渲染栅格逐行时,把后端回的 `vehiclePlate`/`driverName`/`protocolPrice` 填进对应行。
|
||||
|
||||
## 对应接口(前端排车 Step2 栅格唯一权威数据源)
|
||||
|
||||
```
|
||||
POST /admin/fleet/assignments/candidates
|
||||
```
|
||||
|
||||
请求体(**必传 `startDate`/`endDate`**,否则 400「用车开始/结束日期不能为空」):
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": 2086270141158346754,
|
||||
"requirementId": 2087434583048888322,
|
||||
"startDate": "2026-08-21",
|
||||
"endDate": "2026-08-24"
|
||||
}
|
||||
```
|
||||
|
||||
栅格数据确切路径:
|
||||
|
||||
```
|
||||
data.canonicalSnapshot.editableServiceDates # ["2026-08-18"..."2026-08-24"] 7 天日期轴(已并入旧行日期)
|
||||
data.canonicalSnapshot.cells[] # 每 cell = 一个槽位 × 一天:
|
||||
```
|
||||
|
||||
## 本次 cell 新增 3 个明文字段(前端消费)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `vehiclePlate` | string | 当天车牌明文(行冻结展示快照)。USED cell 有值;UNUSED/未编辑为 null。 |
|
||||
| `driverName` | string | 当天司机姓名明文(行冻结展示快照)。USED cell 有值;UNUSED/未编辑为 null。 |
|
||||
| `protocolPrice` | number | 当天派车价。chargeable=0(免费日)恒 0;缺协议价回退日历参考价;再无则 0 兜底。UNUSED/未编辑为 null。 |
|
||||
|
||||
配合既有字段:`used`(USED/UNUSED/null)、`rescheduleResidue`(改期残留标记,前端红标签靠它)、`canDelete`/`deleteBlockReason`。
|
||||
|
||||
## 实测(订单 26-0821 / 2086270141158346754,改期 08-18→08-21)
|
||||
|
||||
```
|
||||
editableServiceDates = [08-18,08-19,08-20,08-21,08-22,08-23,08-24]
|
||||
cells(槽位1 蒙A-K1999 巴根那 ¥1500):
|
||||
08-18 USED · plate=蒙A-K1999 · driver=巴根那 · price=1500.0 · residue=true · canDelete=true
|
||||
08-19 USED · 同上 · residue=true
|
||||
08-20 USED · 同上 · residue=true
|
||||
08-21 USED · 同上 · residue=false(在新窗内,不误标)
|
||||
cells(槽位2 蒙A-T1557 朝鲁门 ¥1000):同样 4 天明文齐全
|
||||
```
|
||||
|
||||
## 前端修复要点
|
||||
|
||||
- 栅格日期轴用 `canonicalSnapshot.editableServiceDates` 建(已含旧日期 08-18~20),**不要**自己按当前需求窗建轴。
|
||||
- 逐行渲染时按 `cells[]` 里同 `serviceDate` 的 cell,把 `vehiclePlate`/`driverName`/`protocolPrice` 填进去——后端已回,别再只贴「改期残留」红标签留空数据列。
|
||||
- `rescheduleResidue=true` 的行继续挂「改期残留·待清理」徽标;逐日取消必须按 #5937 使用 `cells[].assignmentId` 调 `POST /admin/fleet/assignments/{assignmentId}/cancel-residue`。**严禁**调用老 `DELETE /admin/fleet/assignments/{assignmentId}` 或槽位删除接口——二者均为整槽语义,会连带取消同槽其他日期。
|
||||
|
||||
## 验证证据(后端)
|
||||
|
||||
- fleet 全量 `mvn test` 3680 跑 0 失败 0 错误;ArchTest 13 绿;spotless 净。
|
||||
- 新增单测覆盖明文透传 + FREE/OVERRIDE/CALENDAR 价口径。
|
||||
- 测试服 API 实测:08-18/19/20 返回明文车牌/司机/价(见上)。
|
||||
|
||||
## 前端缺陷点 2:取消/删除成功后必须重拉 candidates 刷新栅格(26-3559 实测)
|
||||
|
||||
> 承接用户第二个实测报障:**「删除按钮显示删除成功了,但还是有这条数据」+「08-24/25 不回显」**(订单 26-3559 / 2086270159575535618,改期 08/24→08/26)。
|
||||
|
||||
**DB 地面真相(后端是对的,无需改)**:
|
||||
|
||||
```
|
||||
fleet_assignment(order 2086270159575535618):
|
||||
08-24 canceled · 蒙A-G8888 宝音德力格尔 ¥900 · cancel_reason=「槽位删除联动取消派车」 @18:44:39
|
||||
08-25 canceled · 同上 @18:44:39
|
||||
08-26 canceled · 同上 @18:44:39
|
||||
→ 三行已全部 canceled、占用已释放,后端取消【真实生效】
|
||||
```
|
||||
|
||||
- 车务点「删除」→ 后端**确实取消成功**(占用已释放,非没生效)。
|
||||
- 但栅格仍把 08-24/25 渲染成「已规划用车 + 改期残留」空态行、08-26 还显示旧的 蒙A-G8888/宝音德力格尔/¥900——**前端在拿取消前的本地旧栅格态渲染,删除后没有重拉 candidates 刷新**。
|
||||
- 后端 `candidates` 的 `cells[]` 现在已正确地不含这些 canceled 终态行(`editableServiceDates` 只并入在途 unassigned/holding/assigned 行,终态行不并入)。
|
||||
|
||||
**前端要改(状态管理)**:
|
||||
|
||||
1. **取消/删除派车成功后,必须重新调 `POST /admin/fleet/assignments/candidates` 拉最新 `canonicalSnapshot` 重建栅格**,不要复用取消前缓存的本地栅格态。
|
||||
2. 渲染以**最新 `cells[]`** 为唯一权威:后端已移除(canceled 终态)的行不要再显示;在途残留行按 `vehiclePlate`/`driverName`/`protocolPrice` 明文渲染(缺陷点 1 已透出)。
|
||||
|
||||
**产品口径(已与 wx 对齐)**:改期残留旧行(在途/已派)回显带明文 + 标「改期残留·待清理」,等车务手动删;点删除 = 取消 + 释放占用 + **行从栅格消失**(前端重拉)。「删了还在」即此缺陷——纯前端没刷新。
|
||||
|
||||
## 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端消费**: @mmg(①渲染 `cells[].vehiclePlate/driverName/protocolPrice` + 既有 `rescheduleResidue`/`canDelete`;②取消/删除后重拉 candidates 刷新栅格)
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5927"
|
||||
title: "车务管理写端点补齐短窗幂等与重复提交错误码"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "not_required"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-13"
|
||||
updated_at: "2026-08-13"
|
||||
status_note: "后端 PR #5943 已合并 dev-v3 并部署测试服。车队/车辆/车型/司机管理写端点增加 10 秒短窗防重复提交;更新操作按实体 ID + 请求摘要区分,同实体不同内容的连续编辑不会互拦。重复请求返回 100502,前端沿用统一错误提示即可。"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务管理写端点补齐短窗幂等(#5927)
|
||||
|
||||
## 变更接口
|
||||
|
||||
以下现有管理端写接口增加短窗幂等保护,**路径、请求字段和成功响应均不变**:
|
||||
|
||||
- 车队新增、编辑、删除、启用、停用;
|
||||
- 车辆新增、编辑、删除、启用、停用;
|
||||
- 车型大类/型号新增、编辑、删除、启用、停用;
|
||||
- 司机编辑;
|
||||
- 整团重配沿用 Service 持久幂等。
|
||||
|
||||
原子确认和司机拒接不使用 Redis 短窗拒绝,分别保留持久回执重放和状态机/CAS 业务幂等,避免断网重试或下一轮派车被误拦。
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 相同业务请求在 10 秒窗口内重复提交:返回业务码 `100502`,提示“处理中,请勿重复提交”。
|
||||
- 更新操作的 key 为“实体 ID + 稳定请求摘要”:
|
||||
- 相同实体、相同内容的重复请求会被拒;
|
||||
- 相同实体、不同内容的合法连续编辑可以正常执行。
|
||||
- 内部导入、司机审核等 Service 调用不受管理端防双击 key 影响。
|
||||
|
||||
## 前端适配
|
||||
|
||||
无需修改请求或成功响应结构。收到 `100502` 时展示后端 message,并保持按钮短暂禁用/等待当前请求结果;不要自动改成其他请求参数重试。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 后端 PR:`#5943`,squash merge `9d2c29fd`。
|
||||
- fresh 编译:763 个生产源、293 个测试源。
|
||||
- 幂等定向测试:24/24;Fleet ArchTest:13/13;spotless:通过。
|
||||
- fleet 全量:3704 项中仅既有 Windows 进程管道 flaky 2 项失败;该类独立重跑 23/23 通过,其余无失败/错误。
|
||||
- 测试服 Deploy Panel:任务 `5042749e` 成功。
|
||||
- 9443 网关只读健康验证:车辆、司机、车队分页接口均 `HTTP 200 / code 200`。
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
author: "wx(GIT)"
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5933"
|
||||
title: "生成 H5 录入 token 支持 requestId 幂等键(防双击)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "232274b5"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-13"
|
||||
updated_at: "2026-08-13"
|
||||
status_note: "后端已于 2026-08-12 合并 PR #5940 并部署测试服验证:同 requestId 首次成功、30s 内第二次返回 100502、不同 requestId 成功;旧客户端不传 requestId 连续两次均成功。API-SPEC §13.6 同步补声明 contractVersion/segmentDays/serviceDates/segmentRoute。"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 生成 H5 录入 token 支持 requestId 幂等键(#5933)
|
||||
|
||||
> **服务**:`hl-fleet-service`
|
||||
>
|
||||
> **Backend Issue**:[#5933](https://git.1814.love:8443/wx/HL/issues/5933)
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更类型 |
|
||||
|---|---|---|---|
|
||||
| 生成 H5 录入 token | `POST` | `/admin/fleet/h5/token` | additive 请求字段(可选 requestId) |
|
||||
|
||||
## 1. 变更内容
|
||||
|
||||
### 1.1 新增可选入参 `requestId`
|
||||
|
||||
`POST /admin/fleet/h5/token` 入参新增**可选** `requestId`:
|
||||
|
||||
| 字段 | JSON 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `requestId` | `String` | 否 | 前端稳定幂等键(1-64 位,仅字母数字与 `._-`,建议 UUID)。同 `requestId` **30s 窗口内**重复请求被拒(`100502 请勿重复提交`),防双击 / 弱网重试重复建链 |
|
||||
|
||||
**前端适配(推荐)**:生成按钮点击时生成稳定唯一值(如 UUID),**重试沿用同一值**;作废后重新生成属不同业务动作,应换新 `requestId`。不传 `requestId` 完全兼容(后端跳过幂等,行为与旧版一致)。
|
||||
|
||||
**错误码新增触发场景**:`100502`(同 requestId 30s 内重复提交)。
|
||||
|
||||
### 1.2 行程单 H5 契约补声明(无接口变更)
|
||||
|
||||
`GET /app/h5/itinerary/{token}` 出参中的 `contractVersion` / `segmentDays` / `serviceDates[]` / `segmentRoute` 四字段随 #5364 已上线并交接(frontend_status=verified),本次仅补全 API-SPEC §13.6 文档声明,**接口与实现均无变化**,前端无需任何改动。
|
||||
|
||||
## 2. 兼容性
|
||||
|
||||
- `requestId` 可选:旧前端不传时行为与旧版完全一致(线上实测验证)。
|
||||
- 线上旧版本收到含 `requestId` 的请求体时忽略未知字段、正常生成(实测 200),前后端可异步上线。
|
||||
|
||||
## 3. 验证证据
|
||||
|
||||
- 后端 PR:`#5940`,squash merge `0792b27c`。
|
||||
- 测试服部署:Deploy Panel 任务 `d0c1a1ef`,`hl-fleet-service` / `dev-v3`,状态 `success`。
|
||||
- 自动化:H5 定向测试 88/88、Fleet ArchTest 13/13、spotless 通过。
|
||||
- 网关实测:
|
||||
- 同 `requestId` 首次调用 → `200/成功`;
|
||||
- 同键 30s 内第二次调用 → `100502/生成处理中,请勿重复提交`;
|
||||
- 不同 `requestId` → 各自成功;
|
||||
- 不传 `requestId` 连续两次 → 均成功。
|
||||
|
||||
## 4. 验收要点
|
||||
|
||||
- 同 `requestId` 30s 内第二次调用 → `100502 生成处理中,请勿重复提交`(测试服网关已验证)。
|
||||
- 不同 `requestId` 各自成功(测试服网关已验证)。
|
||||
- 不传 `requestId` 连续两次调用均成功(兼容路径,测试服网关已验证)。
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5937"
|
||||
title: "取消改期残留派车改为按单日精确取消——禁止误删整槽及整单配车"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "08dc7e6e"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-13"
|
||||
status_note: "后端已修复并部署测试服:Step2 cell 新增 assignmentId,新端点 POST /admin/fleet/assignments/{assignmentId}/cancel-residue 仅取消指定窗外逐日残留行,不动同槽其他日期或其他槽位。前端必须把『取消残留派车』从槽位删除/老 cancel 切到新端点,使用 cells[].assignmentId;成功后重拉 candidates。禁止继续调用 DELETE /slots/{slotId} 或 DELETE /assignments/{assignmentId},二者均是整槽语义。"
|
||||
updated_at: "2026-08-13"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 取消改期残留派车改为按单日精确取消(#5937 / PR #5939 + #5941)
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **日期**: 2026-08-12
|
||||
> **前端负责人**: @mmg
|
||||
> **严重级别**: P0
|
||||
|
||||
## 问题实证(订单 26-3559 / 2086270159575535618)
|
||||
|
||||
车务在 Step2 栅格点击 08-24/25 行的「取消残留派车」,只想清理两天旧日期残留,实际触发了槽位删除:
|
||||
|
||||
- 18:44:39:旧槽 08-24/25/26 三行全部 canceled;
|
||||
- 19:03:23:新槽 08-26/27/28 三行又全部 canceled;
|
||||
- 最终整单两个槽位共 6 行配车被清空(订单主单未取消,仍为 CUSTOMIZING / VEHICLE PROCESSING)。
|
||||
|
||||
## 根因
|
||||
|
||||
1. `canonicalSnapshot.cells[]` 原来没有逐日 `assignmentId`,前端无法精确定位一行。
|
||||
2. 前端只能退化调用槽位删除。
|
||||
3. 老 `DELETE /admin/fleet/assignments/{assignmentId}` 名字看似按 assignmentId,后端实际也是稳定槽位整槽取消(`updateCancelSlotActive`),同样不能用于逐行残留清理。
|
||||
|
||||
## 新增响应字段
|
||||
|
||||
`POST /admin/fleet/assignments/candidates`:
|
||||
|
||||
```text
|
||||
data.canonicalSnapshot.cells[].assignmentId
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `assignmentId` | string | 当天逐日派车行 ID。仅逐日 USED 行返回;全程行/无行/UNUSED 为 null。前端按此 ID 调逐日残留取消。 |
|
||||
|
||||
配合既有字段:`serviceDate`、`rescheduleResidue`、`canDelete`、`deleteBlockReason`、`vehiclePlate`、`driverName`、`protocolPrice`。
|
||||
|
||||
全程行(`service_date=NULL`)展开的 cell:
|
||||
|
||||
```json
|
||||
{
|
||||
"assignmentId": null,
|
||||
"canDelete": false,
|
||||
"deleteBlockReason": "全程派车不可按单日取消,请先拆分或改派"
|
||||
}
|
||||
```
|
||||
|
||||
## 变更接口(前端必须切换)
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/{assignmentId}/cancel-residue
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"requestId": "residue-cancel-{唯一请求ID}",
|
||||
"cancelReason": "改期残留逐日取消"
|
||||
}
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
- 只允许取消当前需求日期窗外的逐日 `holding/assigned` 残留行;
|
||||
- Mapper 只按 exact `assignmentId + expectedStatus` CAS 一行;
|
||||
- 不取消同槽其他日期;
|
||||
- 不影响其他槽位;
|
||||
- 不写槽位删除意图,不移除 retained slot;
|
||||
- 只释放目标日期车辆/司机占用、只退目标日期司机保险;
|
||||
- 新窗内有效行、全程行、completed 行均拒绝;
|
||||
- 已 canceled 行重复请求幂等 200,DB 零变化。
|
||||
|
||||
## 前端改法(必须全部完成)
|
||||
|
||||
1. 「取消残留派车」按钮仅在:
|
||||
- `rescheduleResidue === true`
|
||||
- `canDelete === true`
|
||||
- `assignmentId != null`
|
||||
时显示/启用。
|
||||
2. 点击后调用新接口:
|
||||
```http
|
||||
POST /admin/fleet/assignments/{cell.assignmentId}/cancel-residue
|
||||
```
|
||||
3. 成功后必须重新调用 `POST /admin/fleet/assignments/candidates`,用最新 `canonicalSnapshot` 重建栅格;被取消的目标旧日期行自然消失。
|
||||
4. **禁止**继续调用:
|
||||
- `DELETE /admin/fleet/assignments/slots/{slotId}`(整槽删除)
|
||||
- `DELETE /admin/fleet/assignments/{assignmentId}`(老普通取消,实际整槽语义)
|
||||
5. 不要只在本地数组里做状态修改;以后端重拉结果为唯一权威。
|
||||
|
||||
## 验证证据(测试与测试服)
|
||||
|
||||
- AssignmentServiceTest:487 / 0 / 0;覆盖 exact-ID、窗内拒绝、全程拒绝、canceled 幂等、completed 拒绝、状态 TOCTOU、资源身份漂移后只锁最新车/司机。
|
||||
- Controller + Step2:60 / 0 / 0。
|
||||
- ArchTest:13 / 0 / 0;spotless 绿。
|
||||
- fleet 全量 3688:本改动相关测试全绿;两项无关环境/flaky 独立复跑 1/1、2/2 均绿。
|
||||
- 测试服双实例 8087/8187 健康。
|
||||
- 已 canceled 行实测新端点:HTTP 200、`effectiveDate=2026-08-24`,调用前后本单 8 行 DB 快照完全一致(幂等零副作用)。
|
||||
|
||||
## 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端消费**: @mmg(切换新逐行端点 + 删除成功后重拉 candidates)
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "派单 Step2「下一步」多日行程被误拦:候选证据只支持单日,多日每格判「未通过候选校验」置灰"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "0e5a0277"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端零改动。后端提交校验口径已满足业务要求(工单 #5849):车型/座位/数量与当前需求不符不拦截、降级为提示(AssignmentService.java:3038 创建链、assertFinalConfirmationBaseline:4676 baseline 均已定稿,#5810/#5824 一脉);真实资源冲突与日期越窗仍为硬拦(605001/605003/605062),该保留。26-5067 被卡的根因在前端:useVehicleDriverPicker.js 的 candidateServiceDate 只在 startDate===endDate(单日行程)才返回日期,多日行程返回空串 → candidateEvidence 为 null → daily-vehicle-plan.js 的 dailyVehiclePlanCandidateEvidenceMatches 要求 evidence.serviceDate===cell.serviceDate 永不匹配 → 每个用车日格都判「未通过候选校验」→ 槽位 selectionReady=false → 「已完成 0/1」、按钮置灰。后端候选接口已返回 availabilityWindows(按请求日期范围扣除阻断冲突后的可用窗口),数据足够前端判断「车/司机覆盖整个服务期」,无需新增接口。前端 2026-08-12 复核 not_required:candidateServiceDate 仅单日、candidateEvidence 多日为空属实,但致障链已被 2026-08-11 候选证据门禁放宽盖过——validateDailyVehiclePlan 默认 requireCandidateEvidence=false 且三调用方(daily-vehicle-plan.js:616 / useAssignFlow.js:534 / AssignModal.vue:1698)均未开启,证据匹配分支不执行;Step2「下一步」disabled 只读 selectionReady(batch=selectedSlots>0 且无 selectionBlockedReason/dailyPlanValidationError),「已完成 N/M」走 isDailyVehiclePlanCellComplete 不查 _candidateEvidence;日格 scope 恒单日 order、槽位 scope 逐日格手工造证据(applySlotResourceSelection)绕过 picker evidence,多日置灰不复现,前端零改动。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 派单 Step2「下一步」多日行程被误拦(#5849,前端修复)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step2「排车」→「下一步 · 发送给司机」按钮
|
||||
> **后端**:本条**零改动**。后端口径已满足,根因在前端候选证据只支持单日。
|
||||
> **业务口径**(wx 2026-08-11,#5849):只要槽位有车+有司机、且日期在行程窗内,就可以下一步;定制师换版改需求不等于车务必须改派车。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
订单 26-5067(三日行程 08/20~08/22):车辆槽位 1 已有车(蒙A-E5555)+司机(阿木古愣),三天全部「已规划用车」、日历价派车价都填了,但仍被拦——
|
||||
|
||||
- 8月21日 行挂橙条「2026-08-21 车辆槽位1 尚未通过当前日期、车辆和司机的候选校验」
|
||||
- 底部「已完成 **0/1** 个最终方案槽位」
|
||||
- 「下一步 · 发送给司机(1 辆)」按钮置灰
|
||||
|
||||
## 二、根因(前端)
|
||||
|
||||
按钮置灰链路:`AssignModalFooter.vue:108` `:disabled="!selectionReady"` ← `AssignModal.vue:2401-2416` `syncSlotCompletion` 把 `slot.selectionReady = cells.every(isDailyVehiclePlanCellComplete)` ← `daily-vehicle-plan.js:571-577` 要求每格 `dailyVehiclePlanCandidateEvidenceMatches(cell)` ← 该函数要求 `evidence.serviceDate === cell.serviceDate`。
|
||||
|
||||
而证据来源 `useVehicleDriverPicker.js:68-71`:
|
||||
|
||||
```js
|
||||
function candidateServiceDate(order = {}) {
|
||||
const startDate = String(order.startDate || '')
|
||||
return startDate && startDate === String(order.endDate || '') ? startDate : ''
|
||||
}
|
||||
```
|
||||
|
||||
**只在单日行程返回日期,多日行程返回空串** → `candidateEvidence`(`useVehicleDriverPicker.js:313-323`)为 `null` → `dailyVehiclePlanCandidateEvidenceMatches` 永不匹配 → 多日行程下每个用车日格都判「未通过候选校验」→ 整槽 selectionReady=false → 0/1、置灰。
|
||||
|
||||
单日行程不触发此 bug(serviceDate 非空、能匹配),所以只在多日行程复发。
|
||||
|
||||
## 三、后端口径现状(已满足,勿再拦)
|
||||
|
||||
后端提交链对「车型/座位/数量与当前需求不符」**已经不拦截**,与 #5810/#5824 一脉:
|
||||
|
||||
- `AssignmentService.java:3038`:「车型与座位差异不再阻断:车务可按现场调度选择任意车辆,候选接口返回匹配标记供醒目提示」
|
||||
- `assertFinalConfirmationBaseline`(`AssignmentService.java:4676-4680`):「车型/数量/人数不符一律不拦截、降级为看板提示语句,日期是唯一硬门禁」
|
||||
- 候选接口 `requirementMatched`/`requirementMismatchReasonCode`/`requirementMismatchMessage` 只作提示,不影响 `selectable`
|
||||
|
||||
**必须保留的硬拦**(前端不要用候选证据把它们也放行):真实资源冲突(同车/同司机同日被别单占用,605001/605003)、资源状态不可用(维保/停用/休假/黑名单,605037/605038/605006/605013)、跨常驻未确认(605014)、日期越出行程窗(605062)。
|
||||
|
||||
**前端可用的数据**:候选接口每个车辆/司机候选已返回 `availabilityWindows`(按请求日期范围扣除阻断冲突后的可用窗口区间列表)+ `selectable`。前端据此即可判断「车/司机组合覆盖整个服务期且无阻断冲突」,无需新增接口。
|
||||
|
||||
## 四、前端修复点(mmg)
|
||||
|
||||
1. **`useVehicleDriverPicker.js:68-71` `candidateServiceDate`**:多日/整段行程也要能给出证据日期范围。建议返回 `{ startDate, endDate }` 或服务期日期列表,而非单日或空串。
|
||||
2. **`useVehicleDriverPicker.js:313-323` `candidateEvidence`**:证据携带服务期(起止日期或日期数组),不再只带单日 `serviceDate`。
|
||||
3. **`daily-vehicle-plan.js:81-90` `dailyVehiclePlanCandidateEvidenceMatches`**:匹配口径由「`evidence.serviceDate === cell.serviceDate`」放宽为「`cell.serviceDate` 落在证据服务期范围内」(车/司机/跨常驻确认仍精确比对)。
|
||||
4. **`daily-vehicle-plan.js:443-466` `isDailyVehiclePlanCellComplete` 与 `AssignModal.vue:2401-2416` `syncSlotCompletion`**:完成口径对齐业务口径——槽位内所有服务日已决策(用车/不用车)、用车日有车+司机、价格/跨常驻合规、候选证据覆盖整个服务期,即视为完成、可下一步。已 finalized 的只读行保持 `preservedFinalized` 短路。
|
||||
5. 完成度文案「已完成 N/M 个最终方案槽位」随之正确(不再「三天已规划却 0/1」)。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- 多日行程(≥2 天)下:槽位有车+有司机+日期在行程窗内 → 「下一步」可点,不再被「候选校验」误拦
|
||||
- 车型/座位/数量与当前需求不符 → 只提示不拦
|
||||
- 真实资源冲突 / 日期越出行程窗 → 仍硬拦(沿用候选接口 selectable/availabilityWindows 的阻断冲突判定)
|
||||
- 「已完成 N/M」口径与放宽后一致
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "确认执行页「逐日车费」仍只显示 1 天:修复 0538f4b4 未合入主分支/未发布,线上跑的仍是按 serviceDate 去重的旧逻辑"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "0538f4b4"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端零改动,已复核后端逻辑确认数据完整:BoardOrderService#expandFullTripDailyRows 把全程行按服务期逐日展开(3133-3141 行 BeanUtil 逐日复制 vehicleId/driverId/价格到每个切片),且 2218+2225-2228 行对「已派(车+司机非空)或 finalized==1」的逐日切片全部纳入——单车 3 天订单会返回全 3 天 dailyVehicleFees,非只回 1 天。症状与 08-12 那条 changelog(frontend_ref=0538f4b4,确认执行页逐日车费按 serviceDate 去重折叠)同型。但关键发现:0538f4b4 这个修复 commit 不在 hl-ui 的 origin/dev / origin/master / origin/main 任何主分支祖先里,线上构建跑的仍是旧的 mergeContinuityDailyVehicleFees(按 serviceDate 全局去重)——它本为「同车跨段同日不重叠」设计,会把『逐日 × 逐车』折叠成『逐日单行』,单车 3 天被去重塌成 1 天、最终总车费也只算 1 天(截图:3 天订单 08/23-08/25 只显示 8月23日 ¥800,总车费 ¥800)。0538f4b4 新增的 groupDailyVehicleFeesByVehicle(vehicleFeeAmount.js:135-160 按车分组、组内按日去重)对单车多天也正确(同组保留全部不同 serviceDate),即修复内容本身覆盖单车场景,问题只是『没合并没发布』。请 mmg 把 0538f4b4 合入当前发布分支并发布后再验。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 确认执行页「逐日车费」仍只显示 1 天(前端·修复未发布)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step4「确认执行」→「逐日车费」卡片 +「最终总车费(只读合计)」
|
||||
> **后端**:本条**零改动**。下面后端结论来自当前源码复核(`BoardOrderService`)。
|
||||
> **诉求**:把修复 commit `0538f4b4` 合入发布分支并发布。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象(2026-08-12 复现)
|
||||
|
||||
单车 3 天订单(客户周明月,1 成人,日期 08/23–08/25 共 3 天,车辆蒙A-H7777 丰田汉兰达 7 座,师傅阿拉坦)走到确认执行页:
|
||||
|
||||
- 「逐日车费」只显示 **1 行**:`8月23日 收费 日历价 ¥800`;
|
||||
- 「最终总车费(只读合计)」也只有 **¥800**(应为 3 天合计)。
|
||||
|
||||
## 二、后端已排除(数据完整)
|
||||
|
||||
确认执行页数据源 `GET /admin/fleet/board/orders/{orderId}` 的组装逻辑(`BoardOrderService`):
|
||||
|
||||
1. **全程行按服务期逐日展开**:`expandFullTripDailyRows`(3120-3145)对 `service_date` 为 NULL 的全程行,从 `startDate` 到 `endDate` 逐日 `BeanUtil.copyProperties` 复制成切片——`vehicleId`/`driverId`/`dispatchPlanFinalized`/价格全部逐日带齐。
|
||||
2. **已派行不额外过滤**:2218 行展开后,2225-2228 行只要求「`dispatch_plan_finalized==1` **或**(`vehicleId` 与 `driverId` 均非空)」即纳入服务日——单车已派全程行展开成的 3 个切片每天都满足,**会进 `currentServiceDatesByGroup` 与 `activeAssignments`**。
|
||||
|
||||
即:**后端对该单车 3 天订单会返回全部 3 天的 `dailyVehicleFees` 与逐日价**,不是只回 1 天。与 08-12 那条 changelog 测试服实测结论(`activeAssignments[].dailyVehicleFees` 逐日齐全 + 根级 `vehicleFeeTotal`)一致。
|
||||
|
||||
## 三、根因(前端):修复存在但未发布
|
||||
|
||||
症状与 08-12 changelog(`frontend_ref=0538f4b4`)描述的「确认执行页逐日车费按 serviceDate 去重折叠」完全同型:
|
||||
|
||||
- 旧逻辑 `mergeContinuityDailyVehicleFees`(`vehicleFeeAmount.js:106`)按 `serviceDate` 全局去重——它本为「同车跨段接续、同日不重叠」设计,被错用到「逐日 × 逐车」,把多行折叠成单行,总价也只算去重后的一行。
|
||||
- mmg 的修复 `0538f4b4` 已新增 `groupDailyVehicleFeesByVehicle`(`vehicleFeeAmount.js:135-160`):按车分组(vehicleId 优先/车牌兜底)、组内按日去重——**单车 3 天进同一组、保留全部 3 个不同 serviceDate,修复后单车场景应显示全 3 天**。即修复内容本身覆盖单车场景。
|
||||
|
||||
**但 `0538f4b4` 不在 hl-ui 的 `origin/dev` / `origin/master` / `origin/main` 任何主分支祖先里,本地工作树也未合入。** 也就是说该修复**没有合并、没有发布**,当前线上构建跑的仍是旧的 `mergeContinuityDailyVehicleFees`,所以 3 天被去重塌成 1 天。
|
||||
|
||||
## 四、要做的(前端)
|
||||
|
||||
1. 把修复 commit `0538f4b4` 合入当前对外发布的分支并发布;
|
||||
2. 发布后用**单车多天**订单(如本例 08/23–08/25)回归确认执行页:
|
||||
- 「逐日车费」显示 **3 行**(每天一行,带日期+价);
|
||||
- 「最终总车费」= 3 天合计(直接取根级 `vehicleFeeTotal`,不要前端再按日期去重聚合)。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- [ ] `0538f4b4` 已合入发布分支并上线
|
||||
- [ ] 单车多天订单确认执行页逐日车费显示「服务日数」行(本例 3 行)
|
||||
- [ ] 「最终总车费」= 后端 `vehicleFeeTotal`(本例 3 天合计)
|
||||
- [ ] 多车并行订单仍按车铺开多行(08-12 changelog 场景回归)
|
||||
- [ ] 同车跨段接续(同日不重叠)场景仍正确合并
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "确认执行页「逐日车费」多车只显示一行:按 serviceDate 去重把同日多台车折叠成单行"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "0538f4b4"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端零改动。已用测试服真实 API 核实(订单 26-0821,看板 GET /admin/fleet/board/orders/2086270141158346754):activeAssignments 返回 4 段完整执行段(蒙A-K1999 考斯特 08-18~08-20 三天+08-21 一天 ×¥1500,蒙A-T1557 普拉多 08-18~08-20 三天+08-21 一天 ×¥1000),每段 dailyVehicleFees 逐日价齐全,根级 vehicleFeeTotal=\"10000.00\"、vehicleFeeSummaries=[考斯特¥6000, 普拉多¥4000]。根因在前端:AssignModal.vue step4 逐日车费走 mergeContinuityDailyVehicleFees(vehicleFeeAmount.js),该函数按 serviceDate 去重、同日多台车只保留第一台的价——它本是为「同一台车跨段接续」设计的合并,被错用到「多台车并行」场景,把「逐日×逐车」折叠成「逐日单行」,总价也错。修法:确认页逐日车费按「车+服务日」逐行铺开(读 activeAssignments[].dailyVehicleFees,同日多车保留多行并带车牌/车型区分),总价直接用根级 vehicleFeeTotal,不要用按日期去重的聚合。前端 2026-08-12 已修复并交付:vehicleFeeAmount 新增 groupDailyVehicleFeesByVehicle 以车辆身份分组(vehicleId 优先/车牌兜底/皆无入匿名组保持旧语义),同车跨段仍按日合并、多车同日保留多行带车牌·车型小标题(Step4Confirm 多组渲染独立 DailyVehicleFeeList);sumContinuityVehicleFeeTotal 逐日回退改按车分组防同日多车漏算;confirmationVehicleFeeTotal 多段优先 resolveBoardOrderVehicleFeeTotal(props.order) 取根级 vehicleFeeTotal(特意用 props.order 非 assignmentOrder——confirmHold 下后者已被代表段单段总价覆盖)。fleet/board 35 文件 430 用例全绿,checkpoint 全过。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 确认执行页「逐日车费」多车只显示一行(前端)
|
||||
|
||||
> **页面**:车务管理 → 派单看板 → 订单派车弹窗 → Step4「确认执行」→「逐日车费」卡片 +「最终总车费(只读合计)」
|
||||
> **后端**:本条**零改动**。下面结论来自测试服真实 API(订单 26-0821)。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
多车订单(同一服务日 ≥2 台车)走到确认执行页时:
|
||||
- 「逐日车费」只显示 1 行(如 `8月18日 收费 ¥1000.00`),其余车/其余天全部丢失;
|
||||
- 「最终总车费(只读合计)」也错(截图显示 ¥1000,应为 ¥10000)。
|
||||
|
||||
用户原话:「这个逐日车费就显示一个车一天的???」
|
||||
|
||||
## 二、后端已排除(数据完整)
|
||||
|
||||
`GET /admin/fleet/board/orders/2086270141158346754` 返回:
|
||||
|
||||
- `activeAssignments` = **4 段**(同一台车接续服务被拆成两段,属正常):
|
||||
- 蒙A-K1999 丰田考斯特:`08-18~08-20`(3 天 ×¥1500)+ `08-21`(1 天 ×¥1500)
|
||||
- 蒙A-T1557 丰田普拉多:`08-18~08-20`(3 天 ×¥1000)+ `08-21`(1 天 ×¥1000)
|
||||
- 每段 `dailyVehicleFees[]` 内逐日价齐全(chargeable/calendarPrice/assignmentPrice/source)。
|
||||
- 根级 `vehicleFeeTotal = "10000.00"`、`vehicleFeeSummaries = [{蒙A-K1999 考斯特 ¥6000},{蒙A-T1557 普拉多 ¥4000}]`。
|
||||
|
||||
正确展示应为 **8 行(4 天 × 2 车)**,总价 **¥10000**。
|
||||
|
||||
## 三、根因(前端)
|
||||
|
||||
`AssignModal.vue`:
|
||||
|
||||
```js
|
||||
// ~1471 确认态执行段来源(这里本身是对的,= 全部 activeAssignments)
|
||||
const confirmationAssignments = computed(() =>
|
||||
activeAssignments.value.length ? activeAssignments.value : [currentAssignment...])
|
||||
|
||||
// ~1746 step4 多段时逐日车费走连续性聚合
|
||||
const confirmationDailyVehicleFees = computed(() => {
|
||||
const segments = confirmationAssignments.value
|
||||
if (!(step.value === 4 && segments.length > 1)) return dailyVehicleFees.value
|
||||
return mergeContinuityDailyVehicleFees(segments) // ← 问题在这
|
||||
})
|
||||
```
|
||||
|
||||
`utils/vehicleFeeAmount.js:106`:
|
||||
|
||||
```js
|
||||
export function mergeContinuityDailyVehicleFees(segments = []) {
|
||||
const byDate = new Map()
|
||||
for (const segment of rows) {
|
||||
for (const fee of segment?.dailyVehicleFees ?? []) {
|
||||
const date = String(fee?.serviceDate || '').trim()
|
||||
if (date && !byDate.has(date)) byDate.set(date, fee) // ← 按日期去重:同日多台车只保留第一台
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**`mergeContinuityDailyVehicleFees` 按 `serviceDate` 去重,同一天多台车只保留第一台的价**,把「逐日 × 逐车」折叠成「逐日单行」。总价 `sumContinuityVehicleFeeTotal` 同受此口径影响。这套合并是为「同一台车跨段接续、同日不重叠」设计的,被错误套用到「多台车并行」场景。
|
||||
|
||||
## 四、修复建议
|
||||
|
||||
确认执行页(Step4)的「逐日车费」**不要**用 `mergeContinuityDailyVehicleFees` 这套按日期去重的单段连续性聚合:
|
||||
|
||||
1. **逐日车费列表**:直接遍历 `activeAssignments[]`,把每段的 `dailyVehicleFees[]` 按「车 + 服务日」逐行铺开;同日多车保留多行,并带车牌/车型区分(如 `8月18日 · 蒙A-K1999 考斯特 ¥1500`、`8月18日 · 蒙A-T1557 普拉多 ¥1000`)。
|
||||
2. **最终总车费**:直接用根级 `order.vehicleFeeTotal`(后端已按结算口径算好,本例 ¥10000),不要前端再聚合。`resolveBoardOrderVehicleFeeTotal`(vehicleFeeAmount.js:139)已有「有 dailyVehiclePlan/vehicleFeeSummaries 时取根级 vehicleFeeTotal」的正确口径,可复用。
|
||||
|
||||
> 注意:`mergeContinuityDailyVehicleFees`/`sumContinuityVehicleFeeTotal` 仅在「同车跨段、同日不重叠」前提下安全;多车并行时必须按车区分,不能按日期去重。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- [ ] 多车订单(同日 ≥2 台车)确认执行页:「逐日车费」展示 服务日数 × 车数 行,每台车每天的价都在,且能区分是哪台车
|
||||
- [ ] 「最终总车费」= 后端 `vehicleFeeTotal`(本例 ¥10000)
|
||||
- [ ] 单车订单确认执行页不受影响(回归)
|
||||
- [ ] 同一台车跨段接续(同日不重叠)场景仍正确合并
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend"
|
||||
title: "调整订单弹窗手机号/证件号脱敏值误回传:未修改字段被当改动提交,后端报『格式非法』"
|
||||
consumer: "admin"
|
||||
change_type: "前端缺陷"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "implemented"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "d4d25a1c"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-12"
|
||||
status_note: "后端零改动,契约已支持『不改不传』。调整订单弹窗里手机号/证件号是脱敏回显(如 185****2756、110101****1234)。若用户没改这两列,前端不应把脱敏值回传后端——后端按『值变化』判改动,脱敏串 ≠ 明文会被误判为『改了』,再走格式校验报『手机号格式非法(应为 11 位数字)/证件号格式非法』。契约:AdjustmentSubmitReqVO.updates.people.travelers.update[].phone/idNo 传 null(或整个字段省略)= 保留原值不动;只有用户输入了新的完整明文值才提交。快照 GET /v3/admin/order/{id}/adjustment/snapshot?scope=PEOPLE 与订单详情 customerInfo.travelers 均返回明文(admin 端专用),可编辑初始值应取明文,不要取脱敏串。前端 2026-08-12 已修复并交付:真根因比本条所述更准——①buildTravelerPayload 未填敏感字段回传 ''(''≠null 后端误判改动对空串跑格式校验);②订单级紧急电话 buildEmergencyPhonePayload 原回传 originalEmergencyPhone 明文/脱敏串,用户只改姓名也泄漏脱敏串。修复:TravelerInfoEditor 捕获提交形态基线 baselineTravelerPayload(脱敏字段已省略)+紧急电话「脱敏或与基线一致→null(不改不传)」,buildAdjustmentPeopleUpdate 加 baselineTravelers 对 update 项逐字段 diff「改才传、不改省略」、只剩 id 未改项剔除,toAdjustmentTraveler 防御剔除含「*」的 idCardNo/phone/emergencyPhone;不传基线保持原全量行为向后兼容。order-v2 29 文件 231 用例全绿,checkpoint 全过。"
|
||||
updated_at: "2026-08-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 调整订单弹窗手机号/证件号脱敏值误回传(前端)
|
||||
|
||||
> **页面**:订单 → 调整订单弹窗 → 出行人编辑 → 手机号 / 证件号两列
|
||||
> **后端**:本条**零改动**。后端契约已支持「不改不传」,问题在前端把脱敏值当成了改动提交。
|
||||
|
||||
---
|
||||
|
||||
## 一、现象
|
||||
|
||||
调整订单弹窗中,出行人的手机号、证件号以**脱敏**形式回显(如手机号 `185****2756`、证件号 `110101****1234`)。用户**没有修改**这两列就提交,后端报错:
|
||||
|
||||
- `手机号格式非法(应为 11 位数字)`
|
||||
- 或 `证件号格式非法`
|
||||
|
||||
## 二、根因(前端)
|
||||
|
||||
后端判定「某字段是否被修改」的口径是:**值与库里明文比对,不同即视为改动**。脱敏串(`185****2756`)≠ 明文(`18500002756`),前端若把回显的脱敏值原样回传,后端会误判为「用户改了手机号」,进而对脱敏串跑格式校验 → 报格式非法。
|
||||
|
||||
## 三、契约(后端已支持,无需改)
|
||||
|
||||
`POST /v3/admin/order/{id}/adjustment/submit`,`updates.people.travelers.update[]` 单项(`TravelerEditVO`):
|
||||
|
||||
| 字段 | 不改时 | 要改时 |
|
||||
|---|---|---|
|
||||
| `phone` | 传 `null` 或**整个字段省略** → 保留原值 | 传新的完整 11 位明文 |
|
||||
| `idNo` | 传 `null` 或**整个字段省略** → 保留原值 | 传新的完整明文证件号 |
|
||||
| 其余字段同理 | null/省略 = 保留 | 传新明文 |
|
||||
|
||||
> 后端对每个字段按 `newVal != null && !newVal.equals(oldVal)` 判改动;`null`/省略即跳过该校验与更新。
|
||||
|
||||
## 四、前端该做的
|
||||
|
||||
1. **未修改的字段不要回传**:手机号/证件号若用户没动,提交时该字段传 `null` 或直接从 `update` 项里省略,**绝不把脱敏回显值回传**。
|
||||
2. **改动检测以「用户是否输入新值」为准**,不要以「脱敏串 vs 空」判断;只有用户输入了新的完整明文,才放进提交体。
|
||||
3. **可编辑初始值取明文**:快照 `GET /v3/admin/order/{id}/adjustment/snapshot?scope=PEOPLE` 与订单详情 `customerInfo.travelers` 均返回**明文**(admin 端专用)。若弹窗需要可编辑回填,应取这两个接口的明文,而不是某处的脱敏串——当前弹窗显示脱敏值,说明前端用的不是这两个明文源,需排查取值来源。
|
||||
|
||||
## 五、验收
|
||||
|
||||
- [ ] 调整订单时只改人数/日期、不动手机号和证件号,提交成功,不报「手机号/证件号格式非法」
|
||||
- [ ] 显式把手机号改成新的完整 11 位,提交后生效
|
||||
- [ ] 显式把证件号改成新的完整明文,提交后生效
|
||||
- [ ] 脱敏回显仍在(不回显明文给用户看),但未改不回传
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5929"
|
||||
title: "车费对账车队筛选仅控制展示行且全量合计恒定"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "not_required"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-13"
|
||||
updated_at: "2026-08-13"
|
||||
status_note: "后端 PR #5944 与补救 PR #5945 已合并 dev-v3 并部署测试服。fleets/fleetTeamIds 仅控制 fleets[] 展示行,grandTotal 恒为全车队合计;vehicles[] 补 actualAmount;整车队仅有费用时,非空 vehicleId 也按核单快照生成车辆明细,null vehicleId 只计入车队/全局合计。测试服只读健康返回 4 行且 grandTotal 六字段口径正常。"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车费对账车队筛选与全量合计口径对齐(#5929)
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 方法 | 路径 | 变化 |
|
||||
|---|---|---|
|
||||
| `GET` | `/admin/fleet/reconciliation/cars` | 修正筛选与合计语义,补齐车辆实际金额契约 |
|
||||
| `GET` | `/admin/fleet/reconciliation/export/cars` | 保持 active prep 导出语义,不导出仅有费用的车辆占位行 |
|
||||
|
||||
## 请求参数
|
||||
|
||||
- `fleetTeamIds[]`:新版车队 ID 分组展示维度,传入后优先于 `fleets`。
|
||||
- `fleets[]`:旧版车队稳定编码兼容参数。
|
||||
- 两者都只控制响应 `fleets[]` 返回哪些车队行,不过滤顶部 `grandTotal` 的聚合数据。
|
||||
|
||||
## 响应变化
|
||||
|
||||
- `grandTotal` 始终统计期间内全部车队、全部 active prep 和全部核单费用,不随车队展示筛选变化。
|
||||
- `vehicles[].actualAmount` 明确为该车辆核单实际费用合计,金额字符串。
|
||||
- 有核单费用但无 active prep 的 fee-only 车辆仍进入对应车队明细:
|
||||
- 车牌、车型、座位优先使用核单费用快照;
|
||||
- 快照缺失时车型/座位回退车辆现值;
|
||||
- 已删除车辆也不会丢失已有快照身份。
|
||||
- `vehicleId=null` 的无归属费用计入车队/全局实际合计,但不伪造车辆明细。
|
||||
|
||||
## 前端适配
|
||||
|
||||
- 折叠或筛选车队时,顶部 KPI 仍显示全车队总计,不应再按当前展示行自行重算。
|
||||
- 车辆实际结算列读取 `vehicles[].actualAmount`。
|
||||
- fee-only 行的 `days/orderCount/estimated/payable` 可为 0,但 `actualAmount` 可大于 0,应正常展示。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- PR `#5944`,squash merge `7df9b66f`;补救 PR `#5945`,squash merge `54db3fb9`。
|
||||
- 主修 fresh 编译:763 个生产源、286 个测试源;补救 fresh 编译:765 个生产源、294 个测试源。
|
||||
- 主修对账定向 29/29;补救 ReconciliationQueryServiceTest 27/27;ArchTest 13/13、spotless 均通过。
|
||||
- Deploy Panel 主修任务 `20eca2f2`、补救任务 `86a6d76a` 均成功。
|
||||
- 测试服 9443 网关主修实测:全量/旧 fleets/新 fleetTeamIds 三视图分别返回 4/1/1 行,grandTotal 六字段完全一致。
|
||||
- 补救部署后只读健康复测:返回 4 行,grandTotal 为 estimated/payable `245993.06`、actual `0.00`、diff `-245993.06`、totalDays `298`、totalOrderCount `67`。测试服当前没有 actual 费用,纯费用车队下钻守恒由 fresh 单测覆盖,未为验收制造业务数据。
|
||||
- 空筛选终审补救:PR `#5946`(merge `ddcecac6`)修复 null-only/blank-only 被误判成显式筛选;PR `#5947`(merge `29df4ddc`)补齐 `fleetTeamIds=[null]` 时回退有效 `fleets` 编码。定向测试 30/30、spotless 通过,Deploy Panel `00e42151` 成功;网关 `fleetTeamIds=` 实测 code 200、fleets=4、grandTotal 六字段不变。
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5932"
|
||||
title: "车务降级与权限错误码统一"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "wx(GIT)"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "not_required"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-13"
|
||||
updated_at: "2026-08-13"
|
||||
status_note: "PR #5950 已合并 dev-v3,fleet 测试服部署任务 30cb1e1b 成功;车辆分页和对账期分页网关只读实测 HTTP/code 200。"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务降级与权限错误码统一(#5932)
|
||||
|
||||
## 变更接口
|
||||
|
||||
- 本次不新增路径;统一 fleet 既有 Feign 降级、角色门禁及参数异常响应。
|
||||
- 受影响的管理端入口包括车辆分页与对账期分页等所有经过车务权限门禁的接口。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- fleet 调权限、文件、团期资源、订单服务的 Feign 降级响应统一为可稳定识别的六位业务码。
|
||||
- 日快照回调区分瞬时失败 `605711` 与永久 4xx `605712`:前者重试,后者立即隔离。
|
||||
- 未认证/无权限分别保留真实 HTTP 401/403;超级管理员业务权限拒绝仍为 HTTP 200 + 业务码 `403001`。
|
||||
- 参数绑定错误统一 HTTP 200 + `100001`,类型错误文案补字段和日期格式提示。
|
||||
|
||||
## 前端适配
|
||||
|
||||
- Feign 服务不可用按响应 `code` 处理,不依赖中文 message。
|
||||
- HTTP 401/403 继续由登录/权限公共处理器接管。
|
||||
- `403001` 是业务权限码,不等于 HTTP 403。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- PR `#5950`,squash merge `453540c7`。
|
||||
- 聚焦 75/75,ArchTest 13/13,spotless 通过。
|
||||
- MockMvc 真实验证 HTTP 401/403。
|
||||
- Deploy Panel `30cb1e1b` 成功。
|
||||
- 网关只读:`GET /admin/fleet/vehicles/page`、`GET /admin/fleet/reconciliation/periods` 均 HTTP 200 / code 200。
|
||||
@@ -0,0 +1,408 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5953"
|
||||
title: "主报账表展示其他收入/其他支出明细(不影响报账人净额)"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "4306b65a"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-13"
|
||||
status_note: "PR #5954 已合并 dev-v3(merge commit d945ffc3d6)。纯追加式变更:incomeLines 追加 OTHER_INCOME 其他收入展示行、expenseLines 追加 OTHER_EXPENSE 其他支出展示行并扩充行字段,baseInfo 汇总口径零漂移;字典 settlement_report_line_type 补 OTHER_EXPENSE(user-service Flyway V20260813_001,随 hl-user-service 部署生效)。"
|
||||
updated_at: "2026-08-13"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】主报账表展示其他收入/其他支出明细(不影响报账人净额)(#5953)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
主报账人报账表(reimbursement)是管理后台订单核单页查看主报账人(通常是司机)代收、垫付支出、预支与净额结算情况的只读报表。
|
||||
|
||||
此前报表有两个信息缺口:
|
||||
|
||||
- **收入侧**:只有 DRIVER_CASH_RECEIPT(报账人现金收款)一类行,订单的「其他收入」(升级房型、下马酒等逐项增收)完全看不到,财务对账时要跳到别的页面查。
|
||||
- **支出侧**:只含报账人现金垫付(CASH_PAID)的支出,「其他支出」分类(其他费用/补贴)整类不透出,签单/公司直付的其他支出也无从展示。
|
||||
|
||||
本次变更把 **OTHER_INCOME 其他收入** 和 **OTHER_EXPENSE 其他支出** 两类的逐项明细以**展示行**形式追加进报账表:**纯展示、不并入 baseInfo 任何汇总值**,报账人净额口径与之前完全一致(零漂移)。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 位置 | 变更 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | incomeLines | 追加 OTHER_INCOME 其他收入展示行(type=OTHER_INCOME),逐行透出 itemName/content/unitPrice/quantity/amount/paymentMethod(+Name)/sourceType(+Name)/remark/voucherUrls | ✨ 新增展示行 |
|
||||
| 2 | incomeLines 元素 | 新增 9 个字段:itemName / content / unitPrice / quantity / paymentMethod / paymentMethodName / sourceType / sourceTypeName / voucherUrls(仅 OTHER_INCOME 行输出) | ✨ 新增字段 |
|
||||
| 3 | expenseLines | 追加 OTHER_EXPENSE 其他支出展示行(type=OTHER_EXPENSE,整类追加在支出行数组尾部),透出实际付款方式(不再固定 CASH_PAID) | ✨ 新增展示行 |
|
||||
| 4 | expenseLines 元素 | 新增 8 个字段:type / typeName / expenseType / expenseTypeName / subsidyType / subsidyTypeName / sourceType / sourceTypeName(仅 OTHER_EXPENSE 展示行输出 type 系字段) | ✨ 新增字段 |
|
||||
| 5 | baseInfo 汇总口径 | **不变但更明确**:primaryReporterCollectedAmount 仍只算 DRIVER_CASH_RECEIPT 行;reportablePaidCostAmount 仍只算 CASH_PAID 垫付行(含 OTHER_EXPENSE 分类中的 CASH_PAID 行);reporterNetAmount 公式不变 | 🔧 口径声明(数值零漂移) |
|
||||
| 6 | expenseLines 排序 | OTHER_EXPENSE 分类的 CASH_PAID 行**从原主排序块移到支出行数组尾部**(整类统一走尾部展示块) | 🔧 行为变化 |
|
||||
| 7 | 字典 | settlement_report_line_type 新增 OTHER_EXPENSE=其他支出(dict_data_id=101457) | ✨ 新增字典值 |
|
||||
|
||||
无入参变化、无新接口、无删除字段,属**非破坏性追加**。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
|
||||
| 接口名 | 查询主报账人报账表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表(含其他收入/其他支出逐项展示) |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
|
||||
|
||||
### 4.2 请求体 / Query
|
||||
|
||||
无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
返回 `Result<SettlementReimbursementReportRespVO>`。顶层 4 个字段不变:baseInfo / incomeLines / expenseLines / advanceLines。baseInfo 与 advanceLines 字段结构**本次零变化**(baseInfo 汇总口径见 §9),下面只列**有变化的 incomeLines / expenseLines**。
|
||||
|
||||
### 5.1 incomeLines 元素字段表(两类行:DRIVER_CASH_RECEIPT + OTHER_INCOME)
|
||||
|
||||
| 字段 | 类型 | 输出条件 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| type | String | 恒输出 | 行类型:DRIVER_CASH_RECEIPT(报账人现金收款)/ **OTHER_INCOME(其他收入展示行,本次新增)** |
|
||||
| typeName | String | 恒输出 | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| receiptId | Long(String) | 仅收款行 | 线下收款记录 ID,序列化为字符串 |
|
||||
| amount | BigDecimal | 恒输出 | 金额,保留两位小数 |
|
||||
| channel | String | 仅收款行 | 收款渠道:DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
|
||||
| channelName | String | 仅收款行 | 收款渠道中文名(枚举 label) |
|
||||
| payType | String | 仅收款行 | 收款款项类型:DEPOSIT / FULL / BALANCE |
|
||||
| payTypeName | String | 仅收款行 | 款项类型中文名(枚举 label) |
|
||||
| collectorStaffId | Long(String) | 仅收款行 | 收款人人员安排 ID,序列化为字符串 |
|
||||
| collectorName | String | 仅收款行 | 收款人姓名 |
|
||||
| collectorRole | String | 仅收款行 | 收款人角色(如 DRIVER) |
|
||||
| collectorRoleName | String | 仅收款行 | 收款人角色中文名(字典 staff_role) |
|
||||
| receivedAt | String | 仅收款行 | 收款时间,格式 yyyy-MM-dd HH:mm:ss |
|
||||
| remark | String | 无备注不输出 | 备注 |
|
||||
| **itemName** | String | **仅 OTHER_INCOME 行** | 项目名(与支出行 / 明细 tab 的 itemName 同义对齐,如「升级房型」) |
|
||||
| **content** | String | 仅 OTHER_INCOME 行,无规格不输出 | 规格/内容(如「豪华蒙古包」) |
|
||||
| **unitPrice** | BigDecimal | 仅 OTHER_INCOME 行 | 单价,保留两位小数 |
|
||||
| **quantity** | BigDecimal | 仅 OTHER_INCOME 行 | 数量 |
|
||||
| **paymentMethod** | String | 仅 OTHER_INCOME 行 | 收付款方式:CASH_PAID / COMPANY_PAID / SIGNED |
|
||||
| **paymentMethodName** | String | 仅 OTHER_INCOME 行 | 收付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
|
||||
| **sourceType** | String | 仅 OTHER_INCOME 行,无来源不输出 | 明细来源类型(如 ORDER_SURCHARGE,全量取值见 §6) |
|
||||
| **sourceTypeName** | String | 仅 OTHER_INCOME 行 | 明细来源类型中文名(SettlementDetailSourceType 枚举 label) |
|
||||
| **voucherUrls** | Array<String> | 仅 OTHER_INCOME 行,无凭证不输出该键 | 凭证 URL 数组 |
|
||||
|
||||
**排序**:收款行(DRIVER_CASH_RECEIPT)按收款时间升序在前,OTHER_INCOME 展示行按分类快照稳定序**追加在后**。
|
||||
|
||||
### 5.2 expenseLines 元素字段表(CASH_PAID 垫付行 + OTHER_EXPENSE 展示行)
|
||||
|
||||
| 字段 | 类型 | 输出条件 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| **type** | String | **仅 OTHER_EXPENSE 展示行输出,其余支出行不输出该键** | 行类型,固定 OTHER_EXPENSE |
|
||||
| **typeName** | String | 仅 OTHER_EXPENSE 展示行 | 行类型中文名(字典 settlement_report_line_type,值「其他支出」) |
|
||||
| category | String | 恒输出 | 费用类别:HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE |
|
||||
| categoryName | String | 恒输出 | 费用类别中文名(字典 settlement_category) |
|
||||
| itemName | String | 恒输出 | 项目名(分类特有信息折叠:住宿=酒店-房型,餐食=餐食名(餐类型),其他支出=项目名 等) |
|
||||
| unitPrice | BigDecimal | 无单价概念不输出 | 单价,保留两位小数 |
|
||||
| quantity | BigDecimal | 无数量概念不输出 | 数量 |
|
||||
| amount | BigDecimal | 恒输出 | 实际金额,保留两位小数 |
|
||||
| reimburseAmount | BigDecimal | 恒输出 | 报账金额;注意 **OTHER_EXPENSE 非 CASH_PAID 展示行的 reimburseAmount 也是全额**(仅展示口径,不代表可报账),前端自算合计见 §9 警示 |
|
||||
| paymentMethod | String | 恒输出 | 付款方式:CASH_PAID / COMPANY_PAID / SIGNED;非 OTHER_EXPENSE 支出行固定 CASH_PAID,**OTHER_EXPENSE 展示行透出实际付款方式** |
|
||||
| paymentMethodName | String | 恒输出 | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
|
||||
| date | String | 无日期概念不输出 | 业务日期,格式 yyyy-MM-dd |
|
||||
| remark | String | 无备注不输出 | 备注 |
|
||||
| voucherUrls | Array<String> | 无凭证不输出该键 | 凭证 URL 数组 |
|
||||
| **expenseType** | String | 仅 OTHER_EXPENSE 展示行(EXPENSE 族) | 其他费用类型(如 OTHER) |
|
||||
| **expenseTypeName** | String | 仅 OTHER_EXPENSE 展示行(EXPENSE 族) | 其他费用类型中文名(字典 expense_type) |
|
||||
| **subsidyType** | String | 仅 OTHER_EXPENSE 展示行(SUBSIDY 族) | 补贴类型:PHONE / OVERTIME |
|
||||
| **subsidyTypeName** | String | 仅 OTHER_EXPENSE 展示行(SUBSIDY 族) | 补贴类型中文名(字典 subsidy_type,如「话补」) |
|
||||
| **sourceType** | String | 仅 OTHER_EXPENSE 展示行,无来源不输出 | 明细来源类型(全量取值见 §6) |
|
||||
| **sourceTypeName** | String | 仅 OTHER_EXPENSE 展示行 | 明细来源类型中文名(SettlementDetailSourceType 枚举 label) |
|
||||
|
||||
**排序**:原主排序块(全分类 CASH_PAID 垫付行,不含 OTHER_EXPENSE)在前;OTHER_EXPENSE 分类**整类追加在支出行数组尾部**(含其中的 CASH_PAID 行——这类行原本混在主排序块里,本次统一移到尾部展示块)。
|
||||
|
||||
### 5.3 baseInfo / advanceLines
|
||||
|
||||
结构零变化。baseInfo 汇总口径(数值与修改前完全一致):
|
||||
|
||||
- primaryReporterCollectedAmount = 仅 DRIVER_CASH_RECEIPT 收款行合计(**不含** OTHER_INCOME 展示行)
|
||||
- reportablePaidCostAmount = 仅 CASH_PAID 现金垫付支出行合计(**含** OTHER_EXPENSE 分类中的 CASH_PAID 行,不含其它付款方式展示行)
|
||||
- reporterNetAmount = 代收 + 预支 - 支出,公式不变
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
| 字段 | 来源 | 取值 |
|
||||
|------|------|------|
|
||||
| incomeLines[].type | settlement_report_line_type 字典 | DRIVER_CASH_RECEIPT(司机现金收款)/ **OTHER_INCOME(其他收入)** |
|
||||
| expenseLines[].type | settlement_report_line_type 字典 | **OTHER_EXPENSE(其他支出)—— 本次新增字典值**(dict_data_id=101457,sort=70);其余支出行不输出 type 键 |
|
||||
| incomeLines[].channel | PaymentChannelEnum 枚举 | DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
|
||||
| incomeLines[].payType | PayType 枚举 | DEPOSIT(订金)/ FULL(全款)/ BALANCE(尾款) |
|
||||
| *.paymentMethod | settlement_payment_method 字典 | CASH_PAID(现金已付)/ COMPANY_PAID(公司直付)/ SIGNED(签单) |
|
||||
| expenseLines[].expenseType | expense_type 字典 | 如 OTHER(其他)等 |
|
||||
| expenseLines[].subsidyType | subsidy_type 字典 | PHONE(话补)/ OVERTIME(加班补)等 |
|
||||
| *.sourceType | SettlementDetailSourceType 枚举 | MANUAL(手工)/ HOUSE_ASSIGNMENT / SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MEAL_ASSIGNMENT / FLEET / STAFF_ASSIGNMENT / ORDER_SURCHARGE(订单增费)/ SYSTEM |
|
||||
| collectorRole 等角色字段 | staff_role 字典 | DRIVER(司机)/ GUIDE(导游)等 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次无新增错误码,沿用既有:
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(含 OTHER_INCOME / OTHER_EXPENSE 展示行)
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"orderId": "2087088947225038849",
|
||||
"orderNo": "HL20260730001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 2000.00,
|
||||
"approvedAdvanceAmount": 500.00,
|
||||
"reportablePaidCostAmount": 1500.00,
|
||||
"reporterNetAmount": 1000.00,
|
||||
"outstandingAmount": 0.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-07-30 18:20:30",
|
||||
"remark": "尾款现金"
|
||||
},
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"itemName": "升级房型",
|
||||
"content": "豪华蒙古包",
|
||||
"unitPrice": 50.00,
|
||||
"quantity": 2,
|
||||
"amount": 100.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "ORDER_SURCHARGE",
|
||||
"sourceTypeName": "订单增费",
|
||||
"remark": "客人现场升级",
|
||||
"voucherUrls": ["https://oss/i1.jpg"]
|
||||
},
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"itemName": "下马酒",
|
||||
"unitPrice": 50.00,
|
||||
"quantity": 1,
|
||||
"amount": 50.00,
|
||||
"paymentMethod": "SIGNED",
|
||||
"paymentMethodName": "签单",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceTypeName": "手工"
|
||||
}
|
||||
],
|
||||
"expenseLines": [
|
||||
{
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"itemName": "草原明珠大酒店-标间",
|
||||
"unitPrice": 400.00,
|
||||
"quantity": 3,
|
||||
"amount": 1200.00,
|
||||
"reimburseAmount": 1200.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"date": "2026-07-30",
|
||||
"voucherUrls": ["https://oss/v1.jpg"]
|
||||
},
|
||||
{
|
||||
"type": "OTHER_EXPENSE",
|
||||
"typeName": "其他支出",
|
||||
"category": "OTHER_EXPENSE",
|
||||
"categoryName": "其他支出",
|
||||
"itemName": "景区停车费",
|
||||
"amount": 300.00,
|
||||
"reimburseAmount": 300.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"expenseType": "OTHER",
|
||||
"expenseTypeName": "其他",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceTypeName": "手工",
|
||||
"voucherUrls": ["https://oss/v2.jpg"]
|
||||
},
|
||||
{
|
||||
"type": "OTHER_EXPENSE",
|
||||
"typeName": "其他支出",
|
||||
"category": "OTHER_EXPENSE",
|
||||
"categoryName": "其他支出",
|
||||
"itemName": "司机话补",
|
||||
"amount": 200.00,
|
||||
"reimburseAmount": 200.00,
|
||||
"paymentMethod": "SIGNED",
|
||||
"paymentMethodName": "签单",
|
||||
"subsidyType": "PHONE",
|
||||
"subsidyTypeName": "话补",
|
||||
"sourceType": "STAFF_ASSIGNMENT",
|
||||
"sourceTypeName": "人员安排"
|
||||
}
|
||||
],
|
||||
"advanceLines": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
注意上例的**对账关系**(本变更的核心语义):
|
||||
|
||||
- incomeLines 三行 amount 合计 2150.00,但 baseInfo.primaryReporterCollectedAmount = **2000.00**(只算 DRIVER_CASH_RECEIPT 行)—— 数组和 ≠ 汇总值属**预期**。
|
||||
- expenseLines 三行 amount 合计 1700.00,但 baseInfo.reportablePaidCostAmount = **1500.00**(只算 CASH_PAID 行:住宿 1200 + 其他支出现金垫付 300;签单的话补 200 仅展示不入账)。
|
||||
- reporterNetAmount = 2000 + 500 - 1500 = **1000.00**,与修改前公式完全一致。
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**边界 1:无其他收入/其他支出** —— incomeLines / expenseLines 只有原有行(或空数组 []),不出现 OTHER_INCOME / OTHER_EXPENSE 行;新增字段键一律不输出(NON_NULL 省略),与修改前响应完全一致:
|
||||
|
||||
```json
|
||||
{ "incomeLines": [], "expenseLines": [], "advanceLines": [] }
|
||||
```
|
||||
|
||||
**边界 2:OTHER_INCOME 行无规格/无凭证/无来源** —— content / voucherUrls / sourceType 键不输出(不是 null):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"itemName": "下马酒",
|
||||
"unitPrice": 50.00,
|
||||
"quantity": 1,
|
||||
"amount": 50.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司直付"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**房务角色(HOUSE)访问被拦**:
|
||||
|
||||
```json
|
||||
{ "code": 403, "message": "无权限访问", "success": false }
|
||||
```
|
||||
|
||||
**订单不存在**:
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- 核单页查看主报账人结算全貌:代收 + 垫付 + 预支 + 净额(baseInfo),以及其他收入 / 其他支出的逐项展示(incomeLines / expenseLines 展示行)。
|
||||
|
||||
**不适用**:
|
||||
|
||||
- 拿 incomeLines / expenseLines **数组自算合计去对 baseInfo 汇总值** —— 两者口径不同(见下「特殊边界」),对不上属预期。
|
||||
- 想看整单收入/成本/毛利 —— 走单团核算表(reports/group),报账表是主报账人维度。
|
||||
|
||||
**特殊边界(前端必知)**:
|
||||
|
||||
1. **数组和 ≠ 汇总值属预期**:OTHER_INCOME / OTHER_EXPENSE 展示行**纯展示不计入** baseInfo 任何汇总。primaryReporterCollectedAmount 仍只算 DRIVER_CASH_RECEIPT 行;reportablePaidCostAmount 仍只算 CASH_PAID 垫付行;reporterNetAmount 公式不变。**汇总值一律直接读 baseInfo,不要前端自算。**
|
||||
2. **OTHER_EXPENSE 非 CASH_PAID 展示行的 reimburseAmount 也是全额**(如签单话补 reimburseAmount=200.00),但它不代表可报账。前端若坚持自算 sum(reimburseAmount),必须**按 type=OTHER_EXPENSE 排除展示行**(更精确的做法是按 paymentMethod=CASH_PAID 过滤,与后端口径一致;直接排除全部 OTHER_EXPENSE 行会漏掉其中 CASH_PAID 垫付行,与 reportablePaidCostAmount 差出该行金额)。
|
||||
3. **OTHER_EXPENSE 的 CASH_PAID 行位置变了**:这类行原本混在支出行主排序块里,本次整类移到**支出行数组尾部**。若前端曾按「位置/下标」识别这类行,需改为按 type=OTHER_EXPENSE 识别。
|
||||
4. **行类型判别方式两侧不对称**:
|
||||
- 收入行:靠 **type 值**判别(DRIVER_CASH_RECEIPT / OTHER_INCOME,type 恒输出);
|
||||
- 支出行:靠 **type 键是否出现**判别(仅 OTHER_EXPENSE 展示行输出 type=OTHER_EXPENSE,其余支出行**没有 type 键**——不是 null,是键不存在)。
|
||||
5. OTHER_EXPENSE 展示行**全量付款方式**都透出(CASH_PAID / COMPANY_PAID / SIGNED),不再像普通支出行那样固定 CASH_PAID。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 位置 | 字段 | 修改前 | 修改后 |
|
||||
|------|------|--------|--------|
|
||||
| incomeLines | type 取值 | 仅 DRIVER_CASH_RECEIPT | DRIVER_CASH_RECEIPT + **OTHER_INCOME** |
|
||||
| incomeLines 元素 | itemName / content / unitPrice / quantity / paymentMethod(+Name) / sourceType(+Name) / voucherUrls | 无 | **新增**(仅 OTHER_INCOME 行输出) |
|
||||
| expenseLines 元素 | type / typeName | 无 | **新增**(仅 OTHER_EXPENSE 展示行输出) |
|
||||
| expenseLines 元素 | expenseType(+Name) / subsidyType(+Name) / sourceType(+Name) | 无 | **新增**(仅 OTHER_EXPENSE 展示行输出) |
|
||||
| expenseLines | paymentMethod 取值 | 固定 CASH_PAID | 普通支出行仍固定 CASH_PAID;**OTHER_EXPENSE 展示行透出实际付款方式**(CASH_PAID / COMPANY_PAID / SIGNED) |
|
||||
| baseInfo | 全部汇总字段 | 原口径 | **零变化**(仅口径描述更明确,数值不变) |
|
||||
| 字典 | settlement_report_line_type | 6 值(无 OTHER_EXPENSE) | **+OTHER_EXPENSE=其他支出** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 其他收入逐项 | 报账表完全看不到 | incomeLines 尾部追加 OTHER_INCOME 展示行 |
|
||||
| 其他支出逐项 | 仅 CASH_PAID 垫付行混在主排序块 | OTHER_EXPENSE 整类(全付款方式)追加在 expenseLines 尾部展示 |
|
||||
| OTHER_EXPENSE CASH_PAID 行位置 | 支出行主排序块内(按金额等排序) | **移到支出行数组尾部**展示块 |
|
||||
| baseInfo 汇总值 | 代收=收款行合计,支出=CASH_PAID 合计 | **数值逐分不差**(展示行不入账) |
|
||||
| 无其他收支的订单 | 原样 | **响应与原样完全一致**(新行不出现、新字段键不输出) |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:非破坏性。纯追加行 + 追加字段,既有字段名/类型/结构零变化;无其他收支的订单响应与修改前逐字节一致。
|
||||
- **前端是否必须同步上线**:**不强制**。只读 baseInfo 汇总 + 渲染既有行的前端**零改动可继续用**;但要看到其他收入/其他支出明细,需按 §5 新行结构做渲染,并按 §9 特殊边界处理(汇总读 baseInfo、支出行按 type 键识别 OTHER_EXPENSE)。
|
||||
- **前端如有 workaround 需清理**:若前端曾从别的接口拼「其他收入/其他支出」进报账页,可改读本接口展示行;若前端曾自算 sum(incomeLines.amount) 当代收,需改读 baseInfo.primaryReporterCollectedAmount(此前碰巧相等,现在会虚高)。
|
||||
- **字典依赖**:expenseLines[].typeName(「其他支出」)依赖 hl-user-service 字典迁移 V20260813_001(sys_dict_data 101457);该迁移未部署前 typeName 会落 null,前端需兜底显示原 code。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = revert PR #5954 的 merge commit(d945ffc3d6),重启 hl-order-service-v3。
|
||||
- 字典迁移残留:V20260813_001 新增的 OTHER_EXPENSE 字典值在回滚后无消费方,残留无害,无需回退。
|
||||
- 零业务表 DDL,回滚无数据残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **汇总只读 baseInfo**:primaryReporterCollectedAmount / reportablePaidCostAmount / reporterNetAmount 是后端算好的权威值,前端不要拿 incomeLines / expenseLines 数组自算(口径不同,必然对不上)。
|
||||
2. **NON_NULL 省略**:incomeLines / expenseLines 元素带 @JsonInclude(NON_NULL),**null 字段不下发该 key**(不是下 null);OTHER_INCOME / OTHER_EXPENSE 专属字段在普通行上键不存在,前端读取必须兜底。
|
||||
3. **行判别**:收入行看 type 值;支出行看 type 键**是否存在**(JS 可用 'type' in line 判别)。
|
||||
4. **Long 主键序列化为字符串**(orderId / receiptId / collectorStaffId 等),前端按 string 处理,不要 Number() 转换。
|
||||
5. OTHER_EXPENSE 展示行的 expenseType 与 subsidyType 互斥(EXPENSE 族出费用类型 / SUBSIDY 族出补贴类型),渲染时按存在的键展示即可。
|
||||
6. 排序约定:收入行 = 收款行(按收款时间升序)在前 + OTHER_INCOME 展示行在后;支出行 = 主排序块在前 + OTHER_EXPENSE 展示块在尾部。前端按数组顺序渲染即可,不要自行重排。
|
||||
7. 本变更只影响 reimbursement 报账表;单团核算表(reports/group)结构本次零变化。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue #5953:https://git.1814.love:8443/wx/HL/issues/5953
|
||||
- PR #5954:https://git.1814.love:8443/wx/HL/pulls/5954
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/d945ffc3d6
|
||||
- 服务:hl-order-service-v3(端口 8086);字典迁移在 hl-user-service(V20260813_001,随 user-service 部署生效)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
|
||||
## 14. 验证证据
|
||||
|
||||
- 后端单测:SettlementReportFlowServiceTest / SettlementReportLabelEnricherTest / SettlementReportLineConverterTest 覆盖 OTHER_INCOME/OTHER_EXPENSE 展示行组装、汇总口径零漂移(代收仍只算 DRIVER_CASH 行、可报账支出仍只算 CASH_PAID 行)、字典 label 回填,PR #5954 CI 通过。
|
||||
- 代码已合并 dev-v3(merge commit d945ffc3d6)并部署测试服;字典迁移 V20260813_001 随 hl-user-service 部署生效(INSERT IGNORE 幂等)。
|
||||
- 前端联调验证点:无其他收支订单响应与修改前一致;含其他收支订单按 §8.1 对账关系核对(数组和 ≠ 汇总值属预期)。
|
||||
@@ -0,0 +1,326 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5955"
|
||||
title: "主报账表baseInfo尾款口径透出三新字段+outstandingAmount下线"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "c2783946"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-14"
|
||||
status_note: "PR #5958 已合并 dev-v3(commit cb40e197cb)。破坏性契约变化:baseInfo 删除 outstandingAmount(整单未收尾款),新增 receivableBalanceAmount(应收尾款)/ paidBalanceAmount(实收尾款)/ reporterPaidBalanceAmount(报账人实收尾款);reporterNetAmount 保留为唯一带正负净额字段。仅出参变化,无入参/枚举/DDL 变化。"
|
||||
updated_at: "2026-08-14"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】主报账表 baseInfo 尾款口径透出三新字段 + outstandingAmount 下线(#5955)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
主报账人报账表(reimbursement)是管理后台订单核单页查看主报账人(通常是司机)代收、垫付支出、预支与净额结算情况的只读报表,`baseInfo` 块承载全部汇总金额。
|
||||
|
||||
此前 baseInfo 用 `outstandingAmount`(整单未收尾款,口径同核单应收财务总览)一个字段表达尾款情况,前端无法区分「应收多少尾款」「实际已收多少尾款」「其中主报账人线下代收了多少尾款」,做尾款对账展示时信息不够。
|
||||
|
||||
本次变更把尾款口径拆成三个权威字段直接透出,同时**删除 outstandingAmount**:未收尾款改由前端以「应收尾款 − 实收尾款」自算。报账人净额 `reporterNetAmount` 保留为**唯一**带正负的净额字段(正=报账人应转回公司,负=公司应补报账人),不再单出绝对值字段。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 位置 | 变更 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | baseInfo.outstandingAmount | **字段删除**:整单未收尾款不再透出,前端读到 undefined/键不存在 | ⚠️ 破坏性删除 |
|
||||
| 2 | baseInfo.receivableBalanceAmount | **新增**:应收尾款(调整后应收合计 − 已退 − 订金实收,负截 0) | ✨ 新增字段 |
|
||||
| 3 | baseInfo.paidBalanceAmount | **新增**:实收尾款(payType∈{BALANCE,FULL} 已收:线上 SUCCESS 流水 + 线下手工收款) | ✨ 新增字段 |
|
||||
| 4 | baseInfo.reporterPaidBalanceAmount | **新增**:报账人实收尾款(主报账人 channel=DRIVER_CASH 且 payType∈{BALANCE,FULL} 的线下收款合计) | ✨ 新增字段 |
|
||||
| 5 | baseInfo.reporterNetAmount | **保留**,口径不变;强调其为唯一带正负净额字段,绝对值展示(如「公司应补报账人」)由前端按符号渲染 | 📝 口径声明(数值零漂移) |
|
||||
| 6 | baseInfo.primaryReporterCollectedAmount | **保留**,口径不变(所有 channel=DRIVER_CASH 收款行合计);与 reporterPaidBalanceAmount(仅其 BALANCE/FULL 尾款子集)语义不同 | 📝 口径声明(数值零漂移) |
|
||||
|
||||
无入参变化、无新接口、无枚举/字典变化、无 DDL。组报 / finalize 门禁内部的 outstandingAmount 计算口径**不受影响**(仅本接口出参不再透出)。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
|
||||
| 接口名 | 查询主报账人报账表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表(含尾款三口径) |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
|
||||
|
||||
### 4.2 请求体 / Query
|
||||
|
||||
无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
返回 `Result<SettlementReimbursementReportRespVO>`。顶层 4 个字段不变:baseInfo / incomeLines / expenseLines / advanceLines。本次**仅 baseInfo 金额区变化**,incomeLines / expenseLines / advanceLines 结构零变化。
|
||||
|
||||
### 5.1 baseInfo 全量字段表(变化字段加粗)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(String) | 核单记录 ID(settlement_recon.recon_id);未生成时不输出该键 |
|
||||
| orderId | Long(String) | 订单 ID |
|
||||
| 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幼童」 |
|
||||
| primaryReporterId | Long(String) | 主报账人人员安排 ID |
|
||||
| primaryReporterName | String | 主报账人姓名 |
|
||||
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
|
||||
| primaryReporterCollectedAmount | BigDecimal | 主报账人代收金额:所有 channel=DRIVER_CASH 收款行合计(**含订金性质现金**,不含 OTHER_INCOME 其他收入展示行),保留两位小数 |
|
||||
| approvedAdvanceAmount | BigDecimal | 已审批预支金额(预支行合计),保留两位小数 |
|
||||
| reportablePaidCostAmount | BigDecimal | 可报账已付成本(仅 CASH_PAID 现金垫付支出行合计,含 OTHER_EXPENSE 分类中的 CASH_PAID 行),保留两位小数 |
|
||||
| reporterNetAmount | BigDecimal | 报账人净额 = 代收 + 预支 − 支出,保留两位小数;**正=报账人应转回公司,负=公司应补报账人,0=两清**。唯一净额字段,带正负 |
|
||||
| ~~outstandingAmount~~ | — | **已删除**:整单未收尾款不再透出(见 §10) |
|
||||
| **receivableBalanceAmount** | BigDecimal | **新增**·应收尾款 = 调整后应收合计 − 已退 − 订金实收,负值截 0,保留两位小数。其中:订金实收 = 线上 SUCCESS 支付流水 + 线下手工收款中 payType=DEPOSIT 部分;调整后应收合计/已退口径同核单应收财务总览 adjustedReceivableAmount / actualRefundedAmount |
|
||||
| **paidBalanceAmount** | BigDecimal | **新增**·实收尾款 = 已收且 payType∈{BALANCE, FULL} 的合计(线上 SUCCESS 支付流水 + 线下手工收款),保留两位小数。**FULL 全款单无订金概念,首笔即全收,计入实收尾款** |
|
||||
| **reporterPaidBalanceAmount** | BigDecimal | **新增**·报账人实收尾款 = 主报账人 channel=DRIVER_CASH 且 payType∈{BALANCE, FULL} 且 collectorStaffId=主报账人的线下收款合计;无主报账人时为 0,保留两位小数 |
|
||||
|
||||
### 5.2 三新字段的派生关系(前端对账用)
|
||||
|
||||
```
|
||||
receivableBalanceAmount(应收尾款)= 调整后应收合计 − 已退 − 订金实收(DEPOSIT 已收),负截 0
|
||||
paidBalanceAmount(实收尾款) = Σ 已收且 payType∈{BALANCE,FULL}(线上 SUCCESS + 线下手工)
|
||||
reporterPaidBalanceAmount = paidBalanceAmount 中「主报账人 DRIVER_CASH 线下代收」的子集
|
||||
未收尾款(前端自算) = max(0, receivableBalanceAmount − paidBalanceAmount)
|
||||
```
|
||||
|
||||
**与 primaryReporterCollectedAmount 的区别**:代收(collected)含主报账人所有 DRIVER_CASH 收款(含 payType=DEPOSIT 的订金现金);reporterPaidBalanceAmount 只算其中 BALANCE/FULL 尾款部分。订单存在「主报账人代收订金现金」时两者会分叉,属预期。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次无新增枚举/字典。涉及的既有枚举(仅用于理解口径,不在 baseInfo 出参里):
|
||||
|
||||
| 枚举 | 取值 | 说明 |
|
||||
|------|------|------|
|
||||
| PayType(款项类型) | DEPOSIT(订金)/ FULL(全款)/ BALANCE(尾款) | 实收尾款口径 = BALANCE + FULL;订金实收口径 = DEPOSIT |
|
||||
| 收款渠道 | DRIVER_CASH(报账人现金)/ BANK_TRANSFER / CONSULTANT_COLLECTION | reporterPaidBalanceAmount 只算 DRIVER_CASH 且收款人=主报账人 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次无新增错误码,沿用既有:
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(订金单部分付尾款,含主报账人 DRIVER_CASH 尾款代收)
|
||||
|
||||
场景:调整后应收 10000,已退 0,订金实收 2000(线上 SUCCESS)→ 应收尾款 8000;尾款已收 2200(线上 1700 + 主报账人现金代收 500)。
|
||||
|
||||
GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"baseInfo": {
|
||||
"id": "6001",
|
||||
"orderId": "2087088947225038849",
|
||||
"orderNo": "HL20260730001",
|
||||
"teamNo": "T20260730001",
|
||||
"productName": "呼伦贝尔草原 5 日游",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "张三",
|
||||
"departDate": "2026-07-30",
|
||||
"returnDate": "2026-08-03",
|
||||
"consultantName": "李四",
|
||||
"travelerCount": 4,
|
||||
"travelerComposition": "2大 2儿童",
|
||||
"primaryReporterId": "7001",
|
||||
"primaryReporterName": "司机甲",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 500.00,
|
||||
"approvedAdvanceAmount": 300.00,
|
||||
"reportablePaidCostAmount": 100.00,
|
||||
"reporterNetAmount": 700.00,
|
||||
"receivableBalanceAmount": 8000.00,
|
||||
"paidBalanceAmount": 2200.00,
|
||||
"reporterPaidBalanceAmount": 500.00
|
||||
},
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "DRIVER_CASH_RECEIPT",
|
||||
"typeName": "司机现金收款",
|
||||
"receiptId": "8001",
|
||||
"amount": 500.00,
|
||||
"channel": "DRIVER_CASH",
|
||||
"channelName": "报账人收款",
|
||||
"payType": "BALANCE",
|
||||
"payTypeName": "尾款",
|
||||
"collectorStaffId": "7001",
|
||||
"collectorName": "司机甲",
|
||||
"collectorRole": "DRIVER",
|
||||
"collectorRoleName": "司机",
|
||||
"receivedAt": "2026-07-30 18:20:30",
|
||||
"remark": "尾款现金"
|
||||
}
|
||||
],
|
||||
"expenseLines": [],
|
||||
"advanceLines": []
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
前端展示:未收尾款 = max(0, 8000 − 2200) = **5800.00**(自算);reporterNetAmount=700.00>0 → 「报账人应转回公司 700.00」。
|
||||
|
||||
### 8.2 边界情况(FULL 全款单,首笔即全收 + 全额已付后再部分退款)
|
||||
|
||||
场景:FULL 全款单 5000 线上一次付清,无订金概念。应收尾款 = 5000 − 0 − 0(无 DEPOSIT 实收)= 5000;实收尾款 = 5000(FULL 计入);未收尾款自算 = 0。若此后部分退款 1000,应收尾款冲减为 4000、实收仍 5000,自算未收尾款为负 → **前端必须 max(0, ·) 截 0 展示**:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseInfo": {
|
||||
"orderId": "2087088947225038849",
|
||||
"orderNo": "HL20260801002",
|
||||
"primaryReporterName": "司机乙",
|
||||
"primaryReporterRole": "DRIVER",
|
||||
"primaryReporterCollectedAmount": 0.00,
|
||||
"approvedAdvanceAmount": 0.00,
|
||||
"reportablePaidCostAmount": 0.00,
|
||||
"reporterNetAmount": 0.00,
|
||||
"receivableBalanceAmount": 4000.00,
|
||||
"paidBalanceAmount": 5000.00,
|
||||
"reporterPaidBalanceAmount": 0.00
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
边界要点:reporterNetAmount=0 → 两清;reporterPaidBalanceAmount=0(主报账人没有线下代收尾款);未收尾款展示 0.00(负值截 0)。
|
||||
|
||||
### 8.3 业务失败(字段已删——历史代码读 outstandingAmount 拿到 undefined)
|
||||
|
||||
修改后 baseInfo **不再包含 outstandingAmount 键**(不是 null,是键不存在)。前端旧代码 `baseInfo.outstandingAmount` 读到 undefined,若直接参与渲染/计算会出现「undefined元」或 NaN——**必须改为三新字段**:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseInfo": {
|
||||
"orderId": "2087088947225038849",
|
||||
"reporterNetAmount": 700.00,
|
||||
"receivableBalanceAmount": 8000.00,
|
||||
"paidBalanceAmount": 2200.00,
|
||||
"reporterPaidBalanceAmount": 500.00
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另附通用业务失败响应(与修改前一致):
|
||||
|
||||
```json
|
||||
{ "code": 403, "message": "无权限访问", "success": false }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- 核单页查看主报账人结算全貌 + 尾款三口径(应收/实收/报账人实收),未收尾款由前端自算展示。
|
||||
|
||||
**不适用**:
|
||||
|
||||
- 组报 / finalize 门禁判断 —— 内部仍用 outstandingAmount 口径,但那是服务端逻辑,前端无需关心,也不要再试图从本接口读它。
|
||||
|
||||
**特殊边界(前端必知)**:
|
||||
|
||||
1. **outstandingAmount 已删,不是 null 是键不存在**:VO 带 @JsonInclude(NON_NULL),删除后该键彻底不下发。旧代码读到 undefined,必须改。
|
||||
2. **历史 finalize 快照无新三字段**:查看本次上线前生成的旧结算快照时,receivableBalanceAmount / paidBalanceAmount / reporterPaidBalanceAmount 三键为 null(不下发),前端需容忍空态展示(如「--」)。
|
||||
3. **前端自算未收尾款可为负**:「全额已付后再部分退款」场景下 应收尾款−实收尾款 为负,展示时必须 max(0, ·)。
|
||||
4. **净额方向**:reporterNetAmount > 0 → 报账人欠公司(应转回);< 0 → 公司欠报账人(应补);= 0 → 两清。界面「公司应补报账人」等绝对值文案由前端按符号渲染,后端不再单出绝对值字段。
|
||||
5. **代收 ≠ 报账人实收尾款**:primaryReporterCollectedAmount 含订金性质现金(DEPOSIT);reporterPaidBalanceAmount 只含 BALANCE/FULL。两者在「主报账人代收过订金现金」时分叉,属预期,不要互相校验相等。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 位置 | 字段 | 修改前 | 修改后 |
|
||||
|------|------|--------|--------|
|
||||
| baseInfo | outstandingAmount | 整单未收尾款(口径同财务总览) | **已删除**(键不再下发) |
|
||||
| baseInfo | receivableBalanceAmount | 无 | **新增**:应收尾款(调整后应收 − 已退 − 订金实收,负截 0) |
|
||||
| baseInfo | paidBalanceAmount | 无 | **新增**:实收尾款(BALANCE/FULL 已收,线上 SUCCESS + 线下手工) |
|
||||
| baseInfo | reporterPaidBalanceAmount | 无 | **新增**:报账人实收尾款(主报账人 DRIVER_CASH 的 BALANCE/FULL 子集) |
|
||||
| baseInfo | reporterNetAmount | 报账人净额(带正负) | **口径不变**,明确为唯一净额字段 |
|
||||
| baseInfo | primaryReporterCollectedAmount | 主报账人代收 | **口径不变** |
|
||||
| baseInfo | 其余字段 | 原样 | **零变化** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 未收尾款展示 | 直接读 baseInfo.outstandingAmount | **前端自算** max(0, receivableBalanceAmount − paidBalanceAmount) |
|
||||
| FULL 全款单尾款口径 | outstandingAmount 一个数,无细分 | 应收=调整后应收(无订金可减),实收含 FULL 首笔全额,自算未收=0 |
|
||||
| 主报账人代收了多少尾款 | 无法区分(代收含订金现金混在一起) | reporterPaidBalanceAmount 直接给出 BALANCE/FULL 子集 |
|
||||
| 历史 finalize 快照 | 含 outstandingAmount | 旧快照无新三字段(键为 null 不下发),前端空态容忍 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:⚠️ **破坏性变更**。baseInfo 删除 outstandingAmount 字段,凡读取该字段的前端代码必须改造;新增三字段对不读旧字段的页面无影响。
|
||||
- **前端是否必须同步上线**:**是(针对 outstandingAmount 消费点)**。后端上线后 outstandingAmount 立即消失,读它的界面会显示 undefined/空白,前端三新字段改造需与后端同窗口上线。
|
||||
- **改造点清单**:
|
||||
1. 删 baseInfo.outstandingAmount 的所有读取,未收尾款改自算 max(0, receivableBalanceAmount − paidBalanceAmount);
|
||||
2. 新增应收尾款 / 实收尾款 / 报账人实收尾款三处展示(按产品 UI 需要);
|
||||
3. 「公司应补报账人」等绝对值展示按 reporterNetAmount 符号渲染;
|
||||
4. 历史快照空态容忍(三新字段键可能不存在)。
|
||||
- **无 DDL、无枚举、无字典变更**,无其他服务依赖。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = revert PR #5958 的 commit(cb40e197cb),重启 hl-order-service-v3,outstandingAmount 恢复透出、三新字段消失。
|
||||
- 前端若已按新字段上线而后端回滚,三新字段读到 undefined → 前端回滚需同步;建议前后端同窗口切换。
|
||||
- 零 DDL / 零数据迁移,回滚无数据残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **NON_NULL 省略**:baseInfo 带 @JsonInclude(NON_NULL),null 字段不下发该键(不是下 null);旧快照的三新字段、未生成核单的 id、未付订金的 teamNo 都是「键不存在」,前端读取必须兜底。
|
||||
2. **Long 主键序列化为字符串**(id / orderId / primaryReporterId 等),前端按 string 处理,不要 Number() 转换。
|
||||
3. **金额保留两位小数**,BigDecimal JSON 输出为 number(如 8000.00),前端展示格式化自行处理。
|
||||
4. **汇总只读 baseInfo**:三新字段与 reporterNetAmount 都是后端算好的权威值,前端不要拿 incomeLines 数组自算尾款(收款行还含 DEPOSIT,口径不同必然对不上)。
|
||||
5. 组报 / finalize 门禁内部 outstandingAmount 口径不受本变更影响,无需前端配合。
|
||||
6. 本变更只影响 reimbursement 报账表的 baseInfo;incomeLines / expenseLines / advanceLines 结构零变化,单团核算表(reports/group)不受影响。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue #5955:https://git.1814.love:8443/wx/HL/issues/5955
|
||||
- PR #5958:https://git.1814.love:8443/wx/HL/pulls/5958
|
||||
- Commit:https://git.1814.love:8443/wx/HL/commit/cb40e197cb
|
||||
- 服务:hl-order-service-v3(端口 8086)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
|
||||
## 14. 验证证据
|
||||
|
||||
- 后端单测:SettlementReportFlowServiceTest / SettlementControllerTest 补 FULL 全款单、部分退款、混合拆分类边界用例(实收尾款含 FULL、应收尾款负截 0、报账人 DRIVER_CASH+BALANCE/FULL 子集口径),PR #5958 已合并 dev-v3(commit cb40e197cb)。
|
||||
- 代码已合并 dev-v3 并部署测试服。
|
||||
- 前端联调验证点:旧 outstandingAmount 消费点全部改造;§8.1 典型场景未收尾款自算 = 5800.00;§8.2 FULL 单部分退款后自算负值截 0 展示;历史快照三新字段空态不报错。
|
||||
@@ -0,0 +1,728 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5963"
|
||||
title: "单团核算表出参重构——收入拍平逐项+成本7分类明细数组+保险独立子块"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "1072cec2"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-15"
|
||||
status_note: "PR #5966 已合并 dev-v3(merge commit c8cae25131)。破坏性契约变化:删除 perCapitaRevenue/perCapitaCost/perCapitaProfit 人均三指标、hotelCost 等 7 个成本分类小计、costCategories 套层与 incomeLines[].details 嵌套;incomeLines 拍平为逐项行(4 type 各至少一行,无明细给占位行 amount=聚合值);成本改 hotelLines 等 7 个分类明细数组平铺顶层;新增 insuranceInfo 保险子块。仅出参变化,无入参/枚举值/DDL 变化。"
|
||||
updated_at: "2026-08-14"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】单团核算表出参重构——收入拍平逐项 + 成本 7 分类明细数组 + 保险独立子块(#5963)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
单团核算表(reports/group)是管理后台订单核单页查看整团收入、成本、毛利全貌的只读报表:抬头是订单/产品/客户信息,中部是收入与成本逐项明细,底部是净收入、总成本、毛利汇总。
|
||||
|
||||
此前出参有两层嵌套套层:收入是「4 个汇总行 + 每行内嵌 details 明细数组」,成本是「costCategories 8 个分类行 + 每类内嵌 lines 明细数组」,外加 7 个成本分类小计字段和 3 个人均指标字段。前端渲染明细要钻两层嵌套,小计/人均字段与明细数组存在重复表达。
|
||||
|
||||
本次变更把出参**拍平**:
|
||||
|
||||
- 收入 `incomeLines` 去 details 嵌套,逐项明细直接平铺为多行,每行带全列;
|
||||
- 成本删 `costCategories` 套层,改 7 个分类明细数组(`hotelLines` 等)平铺顶层,空分类给 `[]`;
|
||||
- 删除 7 个成本分类小计字段(前端自算各分类数组 sum)与人均三指标;
|
||||
- 保险从成本分类中独立出来,新增 `insuranceInfo` 子块;顶层 `insurancePremium` 保留且仍计入 totalCost。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 位置 | 变更 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | perCapitaRevenue / perCapitaCost / perCapitaProfit | **字段删除**:人均营收/人均成本/人均毛利不再透出(需要时前端用 汇总值 ÷ travelerCount 自算) | ⚠️ 破坏性删除 |
|
||||
| 2 | hotelCost / ticketCost / mealCost / vehicleCost / guideCost / photographerCost / otherExpenseCost | **字段删除**:7 个成本分类小计不再透出,前端改为自算对应分类明细数组的 amount 合计 | ⚠️ 破坏性删除 |
|
||||
| 3 | costCategories | **结构删除**:成本套层(8 个分类行各内嵌 lines)整体下线,改 7 个分类明细数组平铺顶层(见 #5);原 INSURANCE 分类行由 insuranceInfo 子块 + insurancePremium 替代 | ⚠️ 破坏性删除 |
|
||||
| 4 | incomeLines[].details | **结构删除**:收入行内嵌明细数组下线,逐项明细直接拍平为 incomeLines 多行 | ⚠️ 破坏性删除 |
|
||||
| 5 | hotelLines / ticketLines / mealLines / vehicleLines / guideLines / photographerLines / otherExpenseLines | **新增**:7 个成本分类明细数组平铺顶层,行结构 = 原 costCategories[].lines 的分类专属行;无明细的分类给空数组 `[]` | ✨ 新增字段 |
|
||||
| 6 | incomeLines[] 行结构 | **结构变化**:每行在 type/typeName/amount 基础上补齐 itemName/content/unitPrice/headCount/quantity/paymentMethod/paymentMethodName/source/sourceName/sourceType/sourceTypeName/remark/voucherUrls 全列;同一 type 多条平铺多行;某 type 无逐项明细时给一行占位行(仅 type/typeName/amount,amount=该 type 聚合值) | 🔧 结构变化 |
|
||||
| 7 | insuranceInfo | **新增**:保险子块(insured/productName/bizType/bizTypeName/extPolicyNo/totalPremium);无有效保单时 insured=false、保单字段不下发、totalPremium=0 | ✨ 新增字段 |
|
||||
| 8 | 抬头区 + 汇总金额字段(baseOrderAmount … grossProfitRate / travelerCount / insurancePremium) | **保留**,口径不变;insurancePremium 是原 8 个小计中唯一保留的,仍计入 totalCost | 📝 口径声明(数值零漂移) |
|
||||
|
||||
无入参变化、无新接口、无枚举值/字典变化、无 DDL。净收入 netRevenueAmount、总成本 totalCost、毛利 grossProfit/grossProfitRate 数值口径**零漂移**。
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/group |
|
||||
| 接口名 | 查询单团核算表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看整团收入/成本/毛利只读报表(含逐项明细) |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
|
||||
|
||||
### 4.2 请求体 / Query
|
||||
|
||||
无请求体、无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
返回 `Result<SettlementGroupReportRespVO>`。`data` 块结构:**抬头区字段 + 汇总金额字段 + incomeLines 收入逐项行数组 + 7 个成本分类明细数组 + insuranceInfo 保险子块**。costCategories 与 details 两层嵌套已彻底下线。
|
||||
|
||||
### 5.1 抬头区 + 汇总金额(保留字段,口径不变)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long(String) | 核单记录 ID;未生成时不输出该键 |
|
||||
| orderId | Long(String) | 订单 ID |
|
||||
| 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 | 定制师姓名 |
|
||||
| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null),如「1大」「2大 1儿童」 |
|
||||
| travelerCount | Integer | 出行人总数 |
|
||||
| baseOrderAmount | BigDecimal | 订单应收(基础团费),保留两位小数 |
|
||||
| otherIncomeAmount | BigDecimal | 其他收入合计,保留两位小数 |
|
||||
| discountAmount | BigDecimal | 优惠合计(正数表达,冲减应收),保留两位小数 |
|
||||
| adjustedReceivableAmount | BigDecimal | 调整后应收 = baseOrderAmount + otherIncomeAmount − discountAmount,保留两位小数 |
|
||||
| paidAmount | BigDecimal | 已收金额合计(线上 SUCCESS + 线下手工收款),保留两位小数 |
|
||||
| actualRefundedAmount | BigDecimal | 实际退款合计,保留两位小数 |
|
||||
| netRevenueAmount | BigDecimal | 净收入 = 调整后应收 − 实际退款 = incomeLines 各行 amount 合计,保留两位小数 |
|
||||
| netReceivedAmount | BigDecimal | 净实收 = 已收 − 实际退款,保留两位小数 |
|
||||
| outstandingAmount | BigDecimal | 未收金额 = 调整后应收 − 已收(负截 0),保留两位小数 |
|
||||
| ~~hotelCost 等 7 个分类小计~~ | — | **已删除**(见 §10) |
|
||||
| insurancePremium | BigDecimal | 保费合计(原 8 个小计中唯一保留),计入 totalCost,保留两位小数 |
|
||||
| totalCost | BigDecimal | 总成本 = 7 个成本分类明细数组 amount 合计 + insurancePremium,保留两位小数 |
|
||||
| paidCost | BigDecimal | 已付成本,保留两位小数 |
|
||||
| unpaidCost | BigDecimal | 未付成本,保留两位小数 |
|
||||
| grossProfit | BigDecimal | 毛利 = netRevenueAmount − totalCost,保留两位小数 |
|
||||
| grossProfitRate | BigDecimal | 毛利率 = grossProfit ÷ netRevenueAmount,小数表达(如 0.139100 = 13.91%),保留六位小数 |
|
||||
| ~~perCapitaRevenue / perCapitaCost / perCapitaProfit~~ | — | **已删除**(见 §10) |
|
||||
|
||||
### 5.2 incomeLines 收入逐项行(拍平结构,去 details 嵌套)
|
||||
|
||||
固定 4 个 type:BASE_ORDER 订单应收 / OTHER_INCOME 其他收入 / DISCOUNT 优惠(负值)/ ACTUAL_REFUND 实际退款(负值)。同一 type 有多条逐项明细时平铺为多行(用 type + itemName 区分);**某 type 无逐项明细时给一行占位行**(仅 type/typeName/amount,amount = 该 type 聚合值,可能为 0),保证 4 个 type 各至少一行、各行 amount 合计 = netRevenueAmount 不丢数。本数组**不含已收/未收维度**(已收看顶层 paidAmount/outstandingAmount)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | String | 行类型:BASE_ORDER / OTHER_INCOME / DISCOUNT / ACTUAL_REFUND |
|
||||
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
|
||||
| itemName | String | 项目名(如 订单应收/增费项目名/优惠名称/退款来源);占位行不下发 |
|
||||
| content | String | 内容说明(如 规格/退款原因);无则不下发 |
|
||||
| source | String | 人工返还来源(仅 ACTUAL_REFUND 人工返还行透出):DRIVER_ONSITE / COMPANY_COMPENSATION |
|
||||
| sourceName | String | 人工返还来源中文名(字典 settlement_refund_source,缺值回退硬编码) |
|
||||
| unitPrice | BigDecimal | 单价,保留两位小数;无单价概念时不下发 |
|
||||
| headCount | Integer | 人数;无人数概念时不下发 |
|
||||
| quantity | BigDecimal | 数量;无数量概念时不下发 |
|
||||
| amount | BigDecimal | 金额,保留两位小数;DISCOUNT / ACTUAL_REFUND 行为**负数**;占位行为该 type 聚合值 |
|
||||
| paymentMethod | String | 付款方式:CASH_PAID / COMPANY_PAID / SIGNED;无则不下发 |
|
||||
| paymentMethodName | String | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
|
||||
| sourceType | String | 来源:MANUAL / HOUSE_ASSIGNMENT / SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MEAL_ASSIGNMENT / FLEET / STAFF_ASSIGNMENT / ORDER_SURCHARGE / SYSTEM;无则不下发 |
|
||||
| sourceTypeName | String | 来源中文名(SettlementDetailSourceType 枚举 label) |
|
||||
| remark | String | 备注;无则不下发 |
|
||||
| voucherUrls | String[] | 凭证 URL 数组;无凭证时不下发 |
|
||||
|
||||
### 5.3 成本 7 个分类明细数组(平铺顶层,空分类给 `[]`)
|
||||
|
||||
7 个数组:**hotelLines(住宿)/ ticketLines(门票)/ mealLines(餐食)/ vehicleLines(用车)/ guideLines(导游)/ photographerLines(摄影)/ otherExpenseLines(其他支出)**。无明细的分类返回空数组 `[]`(不是不下发)。各分类小计由前端自算数组内 amount 合计;7 分类合计 + insurancePremium = totalCost。
|
||||
|
||||
公共列(每个分类行都有,语义相同,下表不再重复):`amount`(核算金额/实际成本,BigDecimal 两位小数,必填)、`paymentMethod`/`paymentMethodName`(付款方式 + 中文名)、`sourceType`/`sourceTypeName`(来源 + 中文名)、`remark`(备注)、`voucherUrls`(凭证 URL 数组)。
|
||||
|
||||
#### 5.3.1 hotelLines 住宿行
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| stayDate | String | 入住日期,yyyy-MM-dd |
|
||||
| hotelName | String | 酒店名称 |
|
||||
| roomTypeName | String | 房型名称 |
|
||||
| roomCount | Integer | 房间数 |
|
||||
| unitPrice | BigDecimal | 单价,两位小数 |
|
||||
| plannedCost | BigDecimal | 计划成本,两位小数 |
|
||||
|
||||
#### 5.3.2 ticketLines 门票行
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| dayDate | String | 游玩日期,yyyy-MM-dd |
|
||||
| scenicName | String | 景区/项目名称 |
|
||||
| specName | String | 规格名称(如 成人票) |
|
||||
| ticketCount | Integer | 票数 |
|
||||
| ticketUnitPrice | BigDecimal | 门票单价,两位小数 |
|
||||
| sellPrice | BigDecimal | 销售价,两位小数;无则不下发 |
|
||||
| plannedCost | BigDecimal | 计划成本,两位小数 |
|
||||
|
||||
#### 5.3.3 mealLines 餐食行
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| mealDate | String | 用餐日期,yyyy-MM-dd |
|
||||
| mealName | String | 餐食名称 |
|
||||
| mealType | String | 餐食类型:BREAKFAST / LUNCH / DINNER / SELF |
|
||||
| mealTypeName | String | 餐食类型中文名(字典 meal_type) |
|
||||
| quantity | Integer | 份数 |
|
||||
| unitPrice | BigDecimal | 单价,两位小数 |
|
||||
|
||||
#### 5.3.4 vehicleLines 用车行(VEHICLE_FEE 逐日车费行 + EXPENSE 车辆费用行的稀疏并集,每行仅本族字段非 null)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| 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 | BigDecimal | [VEHICLE_FEE] 日单价(核算单价列),两位小数 |
|
||||
| paymentTypeName | String | [VEHICLE_FEE] 车务付款类型名称(如 现金已付) |
|
||||
| expenseType | String | [EXPENSE] 车辆费用类型:FUEL / TOLL / PARKING / RENTAL / MAINTENANCE |
|
||||
| expenseTypeName | String | [EXPENSE] 车辆费用类型中文名(字典 expense_type) |
|
||||
| projectName | String | [EXPENSE] 项目名称(如 全程油费) |
|
||||
| expenseDate | String | [EXPENSE] 费用发生日期,yyyy-MM-dd |
|
||||
|
||||
说明:vehicleLines **只含已确认行**(与车辆成本口径一致);无单价/数量概念,quantity 恒不下发。
|
||||
|
||||
#### 5.3.5 guideLines / photographerLines 人员费用行(两数组同一行结构)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| serviceDate | String | 服务日期,yyyy-MM-dd;无服务日时不下发 |
|
||||
| name | String | 人员姓名 |
|
||||
| serviceType | String | 服务类型:GUIDE / PHOTOGRAPHER |
|
||||
| serviceTypeName | String | 服务类型中文名(字典 staff_role) |
|
||||
|
||||
说明:人员费用无单价/数量概念,均不下发。
|
||||
|
||||
#### 5.3.6 otherExpenseLines 其他支出行(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,如 话补) |
|
||||
|
||||
### 5.4 insuranceInfo 保险子块(新增)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| insured | Boolean | 是否已投保(有有效保单),恒下发 |
|
||||
| productName | String | 保险产品名称;无有效保单时不下发 |
|
||||
| bizType | String | 业务类型:ORDER / DRIVER;无有效保单时不下发 |
|
||||
| bizTypeName | String | 业务类型中文名(字典 insurance_biz_type,缺值回退硬编码:ORDER 订单险 / DRIVER 司机险) |
|
||||
| extPolicyNo | String | 外部保单号;无有效保单时不下发 |
|
||||
| totalPremium | BigDecimal | 保费合计(与顶层 insurancePremium 同口径,计入 totalCost),两位小数,恒下发(无保单为 0) |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
本次无新增/变更枚举值,以下为出参涉及的既有枚举与字典(值 + 中文名):
|
||||
|
||||
| 字典/枚举 | 取值 → 中文名 | 用于字段 |
|
||||
|-----------|--------------|----------|
|
||||
| settlement_report_line_type(字典) | BASE_ORDER → 订单应收;OTHER_INCOME → 其他收入;DISCOUNT → 优惠;ACTUAL_REFUND → 实际退款 | incomeLines[].typeName |
|
||||
| settlement_payment_method(字典) | CASH_PAID → 现金已付;COMPANY_PAID → 公司支付;SIGNED → 签单 | 各行 paymentMethodName |
|
||||
| settlement_refund_source(字典) | DRIVER_ONSITE → 司机现场返还;COMPANY_COMPENSATION → 公司赔付 | incomeLines[].sourceName(仅 ACTUAL_REFUND 人工返还行) |
|
||||
| SettlementDetailSourceType(枚举) | MANUAL → 手工;HOUSE_ASSIGNMENT → 配房结果;SCENIC_ASSIGNMENT → 景区;ACTIVITY_ASSIGNMENT → 游玩项目;MEAL_ASSIGNMENT → 餐饮安排;FLEET → 车务;STAFF_ASSIGNMENT → 人员安排;ORDER_SURCHARGE → 订单增费;SYSTEM → 系统 | 各行 sourceTypeName |
|
||||
| meal_type(字典) | BREAKFAST → 早餐;LUNCH → 午餐;DINNER → 晚餐;SELF → 自理 | mealLines[].mealTypeName |
|
||||
| staff_role(字典) | GUIDE → 导游;PHOTOGRAPHER → 摄影师 | guideLines/photographerLines[].serviceTypeName |
|
||||
| expense_type(字典) | FUEL → 油费;TOLL → 过路费;PARKING → 停车费;RENTAL → 租车费;MAINTENANCE → 维保费;OTHER → 其他 | vehicleLines[].expenseTypeName / otherExpenseLines[].expenseTypeName |
|
||||
| subsidy_type(字典) | PHONE → 话补;OVERTIME → 加班补贴 | otherExpenseLines[].subsidyTypeName |
|
||||
| insurance_biz_type(字典) | ORDER → 订单险;DRIVER → 司机险 | insuranceInfo.bizTypeName |
|
||||
|
||||
原 costCategories 用的 settlement_category 字典(HOTEL/TICKET/MEAL/VEHICLE/GUIDE/PHOTOGRAPHER/OTHER_EXPENSE/INSURANCE 8 值)随套层下线**不再出现于本接口出参**;7 个分类改由数组名(hotelLines 等)直接判别,无需 category 字段窄化。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次无新增错误码,沿用既有:
|
||||
|
||||
| code | message | 触发场景 |
|
||||
|------|---------|----------|
|
||||
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
|
||||
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
|
||||
| 581007 | 订单不存在 | orderId 查不到订单 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(含住宿/门票/车辆多分类成本 + 已投保的真实单)
|
||||
|
||||
以下为部署后网关实调的真实返回(订单 2087487607792463873,2026-08-14 测试服实测):3 天行程,住宿 2 晚 × 320、门票 6 项、用车 3 天 × 1080.50,已投保订单险 88.00;无增费/优惠/退款,incomeLines 后 3 行为占位行。
|
||||
|
||||
GET /v3/admin/order/2087487607792463873/settlement/reports/group
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": "2087487623802122242",
|
||||
"orderId": "2087487607792463873",
|
||||
"orderNo": "HL20260812183323909",
|
||||
"teamNo": "26-6276",
|
||||
"productName": "测试核心产品-多档-固定订金",
|
||||
"productType": "CORE",
|
||||
"productTypeName": "核心产品",
|
||||
"customerName": "沈梦瑶",
|
||||
"departDate": "2026-08-28",
|
||||
"returnDate": "2026-08-30",
|
||||
"consultantName": "dzs_yst",
|
||||
"travelerComposition": "1大",
|
||||
"baseOrderAmount": 5000.0,
|
||||
"otherIncomeAmount": 0,
|
||||
"discountAmount": 0,
|
||||
"adjustedReceivableAmount": 5000.0,
|
||||
"paidAmount": 5000.0,
|
||||
"actualRefundedAmount": 0,
|
||||
"netRevenueAmount": 5000.0,
|
||||
"netReceivedAmount": 5000.0,
|
||||
"outstandingAmount": 0.0,
|
||||
"insurancePremium": 88.0,
|
||||
"totalCost": 4304.5,
|
||||
"paidCost": 4304.5,
|
||||
"unpaidCost": 0,
|
||||
"grossProfit": 695.5,
|
||||
"grossProfitRate": 0.1391,
|
||||
"travelerCount": 1,
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "BASE_ORDER",
|
||||
"typeName": "订单应收",
|
||||
"itemName": "订单应收",
|
||||
"amount": 5000.0
|
||||
},
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"amount": 0
|
||||
},
|
||||
{
|
||||
"type": "DISCOUNT",
|
||||
"typeName": "优惠",
|
||||
"amount": 0
|
||||
},
|
||||
{
|
||||
"type": "ACTUAL_REFUND",
|
||||
"typeName": "实际退款",
|
||||
"amount": 0
|
||||
}
|
||||
],
|
||||
"hotelLines": [
|
||||
{
|
||||
"stayDate": "2026-08-28",
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"roomTypeName": "大床房",
|
||||
"roomCount": 1,
|
||||
"unitPrice": 320.0,
|
||||
"plannedCost": 320.0,
|
||||
"amount": 320.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果",
|
||||
"remark": "裸HTTP派房D1"
|
||||
},
|
||||
{
|
||||
"stayDate": "2026-08-29",
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"roomTypeName": "大床房",
|
||||
"roomCount": 1,
|
||||
"unitPrice": 320.0,
|
||||
"plannedCost": 320.0,
|
||||
"amount": 320.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果",
|
||||
"remark": "裸HTTP派房D2"
|
||||
}
|
||||
],
|
||||
"ticketLines": [
|
||||
{
|
||||
"dayDate": "2026-08-29",
|
||||
"scenicName": "额尔古纳湿地漂流",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 119.0,
|
||||
"plannedCost": 119.0,
|
||||
"amount": 119.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "ACTIVITY_ASSIGNMENT",
|
||||
"sourceTypeName": "游玩项目"
|
||||
},
|
||||
{
|
||||
"dayDate": "2026-08-30",
|
||||
"scenicName": "超长冰滑梯(不限次)",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 29.0,
|
||||
"plannedCost": 29.0,
|
||||
"amount": 29.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "ACTIVITY_ASSIGNMENT",
|
||||
"sourceTypeName": "游玩项目"
|
||||
},
|
||||
{
|
||||
"dayDate": "2026-08-29",
|
||||
"scenicName": "呼和诺尔草原旅游区",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 59.0,
|
||||
"plannedCost": 59.0,
|
||||
"amount": 59.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "景区"
|
||||
},
|
||||
{
|
||||
"dayDate": "2026-08-29",
|
||||
"scenicName": "套娃景区",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 99.0,
|
||||
"plannedCost": 99.0,
|
||||
"amount": 99.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "景区"
|
||||
},
|
||||
{
|
||||
"dayDate": "2026-08-30",
|
||||
"scenicName": "恩和俄罗斯民族乡",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 29.0,
|
||||
"plannedCost": 29.0,
|
||||
"amount": 29.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "景区"
|
||||
},
|
||||
{
|
||||
"dayDate": "2026-08-30",
|
||||
"scenicName": "蓝房子(乌苏浪子湖)",
|
||||
"specName": "成人票",
|
||||
"ticketCount": 1,
|
||||
"ticketUnitPrice": 0.0,
|
||||
"plannedCost": 0.0,
|
||||
"amount": 0.0,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "SCENIC_ASSIGNMENT",
|
||||
"sourceTypeName": "景区"
|
||||
}
|
||||
],
|
||||
"mealLines": [],
|
||||
"vehicleLines": [
|
||||
{
|
||||
"serviceDate": "2026-08-28",
|
||||
"startDate": "2026-08-28",
|
||||
"endDate": "2026-08-28",
|
||||
"vehiclePlate": "蒙A-S6666",
|
||||
"vehicleModelName": "丰田赛那",
|
||||
"driverName": "菜单师傅",
|
||||
"dailyPrice": 1080.5,
|
||||
"paymentTypeName": "现金已付",
|
||||
"amount": 1080.5,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务",
|
||||
"voucherUrls": []
|
||||
},
|
||||
{
|
||||
"serviceDate": "2026-08-29",
|
||||
"startDate": "2026-08-29",
|
||||
"endDate": "2026-08-29",
|
||||
"vehiclePlate": "蒙A-S6666",
|
||||
"vehicleModelName": "丰田赛那",
|
||||
"driverName": "菜单师傅",
|
||||
"dailyPrice": 1080.5,
|
||||
"paymentTypeName": "现金已付",
|
||||
"amount": 1080.5,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务",
|
||||
"voucherUrls": []
|
||||
},
|
||||
{
|
||||
"serviceDate": "2026-08-30",
|
||||
"startDate": "2026-08-30",
|
||||
"endDate": "2026-08-30",
|
||||
"vehiclePlate": "蒙A-S6666",
|
||||
"vehicleModelName": "丰田赛那",
|
||||
"driverName": "菜单师傅",
|
||||
"dailyPrice": 1080.5,
|
||||
"paymentTypeName": "现金已付",
|
||||
"amount": 1080.5,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务",
|
||||
"voucherUrls": []
|
||||
}
|
||||
],
|
||||
"guideLines": [],
|
||||
"photographerLines": [],
|
||||
"otherExpenseLines": [],
|
||||
"insuranceInfo": {
|
||||
"insured": true,
|
||||
"productName": "“保游天下”平安自驾游综合保障计划",
|
||||
"bizType": "ORDER",
|
||||
"bizTypeName": "订单险",
|
||||
"extPolicyNo": "MOCKPOL-2087487619058364419",
|
||||
"totalPremium": 88.0
|
||||
}
|
||||
},
|
||||
"traceId": null,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
勾稽验证(以上真实数据):incomeLines 合计 = 5000 + 0 + 0 + 0 = 5000.00 = netRevenueAmount ✅;hotelLines(640) + ticketLines(335) + mealLines(0) + vehicleLines(3241.50) + guideLines(0) + photographerLines(0) + otherExpenseLines(0) + insurancePremium(88) = 4304.50 = totalCost ✅;netRevenueAmount − totalCost = 695.50 = grossProfit ✅。
|
||||
|
||||
### 8.2 边界情况(无逐项明细的 type 给占位行 amount=聚合值 + 空成本分类给 `[]`)
|
||||
|
||||
场景:订单有 500 元其他收入(手工录入、无逐项明细来源)和 200 元优惠(无逐项明细),无退款;餐食/导游/摄影/其他支出均无明细。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"orderId": "2087487607792463899",
|
||||
"orderNo": "HL20260814001",
|
||||
"baseOrderAmount": 5000.00,
|
||||
"otherIncomeAmount": 500.00,
|
||||
"discountAmount": 200.00,
|
||||
"adjustedReceivableAmount": 5300.00,
|
||||
"paidAmount": 5300.00,
|
||||
"actualRefundedAmount": 0,
|
||||
"netRevenueAmount": 5300.00,
|
||||
"netReceivedAmount": 5300.00,
|
||||
"outstandingAmount": 0.00,
|
||||
"insurancePremium": 0,
|
||||
"totalCost": 640.00,
|
||||
"paidCost": 640.00,
|
||||
"unpaidCost": 0,
|
||||
"grossProfit": 4660.00,
|
||||
"grossProfitRate": 0.879245,
|
||||
"travelerCount": 2,
|
||||
"incomeLines": [
|
||||
{ "type": "BASE_ORDER", "typeName": "订单应收", "itemName": "订单应收", "amount": 5000.00 },
|
||||
{ "type": "OTHER_INCOME", "typeName": "其他收入", "amount": 500.00 },
|
||||
{ "type": "DISCOUNT", "typeName": "优惠", "amount": -200.00 },
|
||||
{ "type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": 0 }
|
||||
],
|
||||
"hotelLines": [
|
||||
{
|
||||
"stayDate": "2026-08-28",
|
||||
"hotelName": "草原明珠大酒店",
|
||||
"roomTypeName": "标间",
|
||||
"roomCount": 1,
|
||||
"unitPrice": 320.00,
|
||||
"plannedCost": 320.00,
|
||||
"amount": 640.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司支付",
|
||||
"sourceType": "HOUSE_ASSIGNMENT",
|
||||
"sourceTypeName": "配房结果"
|
||||
}
|
||||
],
|
||||
"ticketLines": [],
|
||||
"mealLines": [],
|
||||
"vehicleLines": [],
|
||||
"guideLines": [],
|
||||
"photographerLines": [],
|
||||
"otherExpenseLines": [],
|
||||
"insuranceInfo": { "insured": false, "totalPremium": 0 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
边界要点:
|
||||
|
||||
1. **OTHER_INCOME / DISCOUNT 占位行**:只有 type/typeName/amount 三键,amount = 该 type 聚合值(500 / −200,**不是 0**),占位行也参与「各行合计 = 净收入」勾稽;
|
||||
2. **空成本分类给 `[]`**:ticketLines 等空分类是空数组,不是不下发该键;
|
||||
3. **未投保**:insuranceInfo 恒下发,insured=false、保单字段(productName/bizType/bizTypeName/extPolicyNo)键不存在、totalPremium=0;顶层 insurancePremium=0;
|
||||
4. 占位行无 itemName 等明细列(NON_NULL 省略),前端按「只有 type/typeName/amount 三键」识别占位行。
|
||||
|
||||
### 8.3 业务失败(已删字段读取 undefined——新旧结构对比)
|
||||
|
||||
**旧代码读法全部失效**(字段不是 null,是键不存在):
|
||||
|
||||
```json
|
||||
// ❌ 修改前(旧结构,已下线)
|
||||
{
|
||||
"data": {
|
||||
"hotelCost": 640.00,
|
||||
"ticketCost": 335.00,
|
||||
"perCapitaRevenue": 5000.00,
|
||||
"perCapitaCost": 4304.50,
|
||||
"perCapitaProfit": 695.50,
|
||||
"incomeLines": [
|
||||
{
|
||||
"type": "OTHER_INCOME",
|
||||
"typeName": "其他收入",
|
||||
"amount": 500.00,
|
||||
"details": [
|
||||
{ "itemName": "增费-升级房型", "amount": 500.00, "unitPrice": 500.00, "quantity": 1 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"costCategories": [
|
||||
{
|
||||
"category": "HOTEL",
|
||||
"categoryName": "住宿",
|
||||
"amount": 640.00,
|
||||
"lines": [ { "stayDate": "2026-08-28", "hotelName": "草原明珠大酒店", "amount": 640.00 } ]
|
||||
},
|
||||
{ "category": "INSURANCE", "categoryName": "保险", "amount": 88.00, "lines": [] }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// ✅ 修改后(新结构,同一份数据)
|
||||
{
|
||||
"data": {
|
||||
"insurancePremium": 88.00,
|
||||
"incomeLines": [
|
||||
{ "type": "OTHER_INCOME", "typeName": "其他收入", "itemName": "增费-升级房型", "unitPrice": 500.00, "quantity": 1, "amount": 500.00 }
|
||||
],
|
||||
"hotelLines": [ { "stayDate": "2026-08-28", "hotelName": "草原明珠大酒店", "amount": 640.00 } ],
|
||||
"ticketLines": [],
|
||||
"mealLines": [],
|
||||
"vehicleLines": [],
|
||||
"guideLines": [],
|
||||
"photographerLines": [],
|
||||
"otherExpenseLines": [],
|
||||
"insuranceInfo": { "insured": true, "productName": "保游畅享境内游保险", "bizType": "ORDER", "bizTypeName": "订单险", "extPolicyNo": "PY20260814001", "totalPremium": 88.00 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另附通用业务失败响应(与修改前一致):
|
||||
|
||||
```json
|
||||
{ "code": 403, "message": "无权限访问", "success": false }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "success": false }
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
|
||||
- 核单页查看整团收入逐项、成本 7 分类逐项、保险信息、毛利汇总;各分类小计与占比由前端自算。
|
||||
|
||||
**不适用**:
|
||||
|
||||
- 已收/未收维度分析 —— incomeLines 是**应收口径**(不含收款维度),已收/未收看顶层 paidAmount / outstandingAmount / netReceivedAmount。
|
||||
|
||||
**特殊边界(前端必知)**:
|
||||
|
||||
1. **勾稽关系(可用来校验渲染)**:
|
||||
- `incomeLines 各行 amount 合计 = netRevenueAmount`(占位行也参与合计);
|
||||
- `7 个成本分类明细数组 amount 合计 + insurancePremium = totalCost`;
|
||||
- `netRevenueAmount − totalCost = grossProfit`。
|
||||
2. **占位行不丢数**:某 type 无逐项明细时仍有一行,amount = 该 type 聚合值(可能非 0)。前端按 type 固定渲染 4 块时,不要因为「没有 details」就不渲染该块。
|
||||
3. **DISCOUNT / ACTUAL_REFUND 行 amount 为负数**,合计时直接相加即可,不要取绝对值。
|
||||
4. **空成本分类是 `[]` 不是键不存在**;行内 null 字段才是键不存在(NON_NULL 省略)。
|
||||
5. **insuranceInfo 恒下发**:无有效保单时 insured=false、保单字段键不存在、totalPremium=0;不要按「insuranceInfo 不存在」判未投保。
|
||||
6. **vehicleLines 只含已确认行**(与车辆成本口径一致),未确认/已取消的派单不出现在数组里。
|
||||
7. **vehicleLines / otherExpenseLines 是稀疏并集行**:每行只有本族字段非 null(VEHICLE_FEE 族 vs EXPENSE 族、EXPENSE vs SUBSIDY),渲染列时按字段是否存在判别行种。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 位置 | 字段 | 修改前 | 修改后 |
|
||||
|------|------|--------|--------|
|
||||
| 顶层 | perCapitaRevenue | 人均营收 | **已删除**(需要时 自算 netRevenueAmount ÷ travelerCount) |
|
||||
| 顶层 | perCapitaCost | 人均成本 | **已删除**(自算 totalCost ÷ travelerCount) |
|
||||
| 顶层 | perCapitaProfit | 人均毛利 | **已删除**(自算 grossProfit ÷ travelerCount) |
|
||||
| 顶层 | hotelCost / ticketCost / mealCost / vehicleCost / guideCost / photographerCost / otherExpenseCost | 7 个成本分类小计 | **已删除**(自算对应分类数组 amount 合计) |
|
||||
| 顶层 | costCategories | 成本套层:8 分类行(含 INSURANCE),每行 {category, categoryName, amount, lines[]} | **已删除**,改 7 个分类明细数组平铺顶层 |
|
||||
| 顶层 | hotelLines … otherExpenseLines | 无(明细埋在 costCategories[].lines) | **新增** 7 个分类明细数组,行结构不变(原 lines 元素直接上移),空分类 `[]` |
|
||||
| 顶层 | insurancePremium | 保费合计,8 小计之一 | **保留**,口径不变,仍计入 totalCost |
|
||||
| 顶层 | insuranceInfo | 无(保险只是 costCategories 里一行汇总) | **新增** 保险子块(insured/productName/bizType/bizTypeName/extPolicyNo/totalPremium) |
|
||||
| incomeLines[] | details | 每行内嵌逐项明细数组 | **已删除**,逐项明细拍平为多行 |
|
||||
| incomeLines[] | itemName/content/unitPrice/headCount/quantity/paymentMethod(+/Name)/source(+/Name)/sourceType(+/Name)/remark/voucherUrls | 在 details 元素上 | **上移到行本身**,每行带全列 |
|
||||
| incomeLines[] | 行数 | 固定 4 行(每 type 一行汇总) | 每 type ≥ 1 行:有明细平铺多行,无明细给占位行(amount=聚合值) |
|
||||
| 顶层 | 抬头区 + 汇总金额其余字段 | 原样 | **零变化** |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 渲染收入明细 | 遍历 incomeLines[4],钻每行 details 数组 | 直接遍历 incomeLines 多行,按 type 分组渲染;占位行只有三键 |
|
||||
| 渲染成本明细 | 遍历 costCategories[8],按 category 窄化 lines 元素结构 | 直接按数组名取 7 个分类数组,行结构即分类专属结构,无需窄化 |
|
||||
| 成本分类小计 | 读 hotelCost 等 7 字段 / costCategories[].amount | **前端自算**各分类数组 amount 合计 |
|
||||
| 保险信息 | costCategories 里 category=INSURANCE 一行汇总金额 | insuranceInfo 子块(产品名/保单号/保费合计)+ 顶层 insurancePremium |
|
||||
| 人均指标 | 直接读 perCapita* 三字段 | **前端自算**(汇总值 ÷ travelerCount,注意 travelerCount=0 时不展示) |
|
||||
| 分类判别 | category 枚举(settlement_category 字典) | 数组名(hotelLines 等),settlement_category 不再出现于出参 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **破坏兼容性**:⚠️ **破坏性变更**。删除 13 处出参(3 个人均指标 + 7 个成本分类小计 + costCategories 套层 + incomeLines[].details 嵌套 + INSURANCE 分类行),凡读取这些字段的前端代码必须改造。
|
||||
- **前端是否必须同步上线**:**是**。后端上线后旧字段立即消失,读旧结构的页面会得到 undefined/空白,前端新结构改造需与后端同窗口上线。
|
||||
- **改造点清单**:
|
||||
1. 删 perCapitaRevenue/perCapitaCost/perCapitaProfit 所有读取,需要人均处改自算;
|
||||
2. 删 hotelCost 等 7 个小计字段读取,改自算 7 个分类数组 amount 合计;
|
||||
3. 成本渲染从 costCategories 两层遍历改为直读 7 个分类数组,去掉按 category 窄化的判别逻辑;
|
||||
4. 收入渲染从 incomeLines[].details 改为 incomeLines 平铺多行,按 type 分组;适配占位行(仅 type/typeName/amount);
|
||||
5. 保险区块改读 insuranceInfo + insurancePremium;
|
||||
6. 空分类按 `[]` 空态渲染,未投保按 insured=false 空态渲染。
|
||||
- **无 DDL、无枚举值、无字典变更**;汇总金额数值口径零漂移(netRevenueAmount/totalCost/grossProfit 修改前后同值)。
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- 后端回滚 = revert PR #5966(merge commit c8cae25131),重启 hl-order-service-v3,旧结构(costCategories/details/小计/人均)恢复。
|
||||
- 前端若已按新结构上线而后端回滚,新字段读到 undefined → 前端回滚需同步;建议前后端同窗口切换。
|
||||
- 零 DDL / 零数据迁移,回滚无数据残留风险。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **NON_NULL 省略**:全出参带 @JsonInclude(NON_NULL),null 字段不下发该键(不是下 null);占位行只有 type/typeName/amount 三键,前端读取可空字段必须兜底。
|
||||
2. **空数组与键不存在的区别**:7 个成本分类数组空分类下 `[]`(键存在);行内可空列是键不存在。不要混用两种判空。
|
||||
3. **Long 主键序列化为字符串**(id / orderId),前端按 string 处理,不要 Number() 转换。
|
||||
4. **金额保留两位小数**,BigDecimal JSON 输出为 number(如 5000.00);grossProfitRate 是六位小数的小数(0.139100 = 13.91%),展示百分比需 ×100。
|
||||
5. **DISCOUNT / ACTUAL_REFUND 行 amount 为负数**,合计直接相加;占位行 amount 也可能是非 0 聚合值。
|
||||
6. **占位行参与勾稽**:「incomeLines 合计 = netRevenueAmount」成立的前提是把占位行也算进去;若前端只渲染有 itemName 的行,合计校验会对不上。
|
||||
7. 本变更只影响单团核算表(reports/group);主报账人报账表(reports/reimbursement)结构不受影响。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue #5963:https://git.1814.love:8443/wx/HL/issues/5963
|
||||
- PR #5966:https://git.1814.love:8443/wx/HL/pulls/5966
|
||||
- Commit:https://git.1814.love:8443/wx/HL/commit/c8cae25131
|
||||
- 服务:hl-order-service-v3(端口 8086)
|
||||
- 后端负责人:yst(腰苏图)
|
||||
|
||||
## 14. 验证证据
|
||||
|
||||
- 代码已合并 dev-v3(PR #5966,merge commit c8cae25131)并部署测试服,网关实调验证通过(§8.1 为真实返回,订单 2087487607792463873,2026-08-14 实测)。
|
||||
- 勾稽实证:incomeLines 合计 5000.00 = netRevenueAmount;7 分类合计 4216.50 + insurancePremium 88.00 = 4304.50 = totalCost;5000.00 − 4304.50 = 695.50 = grossProfit。
|
||||
- 前端联调验证点:旧 13 处字段消费点全部改造;§8.2 占位行(amount=聚合值非 0)与空分类 `[]` 渲染;§8.3 新旧结构对照迁移;未投保 insuranceInfo 空态。
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5923"
|
||||
title: "派单看板已取消 tab 只收订单真取消来源"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5978 已合并 dev-v3(dbdf2723f)并部署 TEST(hl-fleet-service 双实例 UP)。已取消 tab 列表与计数只纳入 cancel_source=order_cancelled 的派单;车务删槽/换版/占位撤销/手动取消联动取消行不再误挂。测试环境当前无取消数据故实测返回空,过滤语义由 172 单测覆盖。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-15T21:31:00+08:00"
|
||||
---
|
||||
|
||||
# 派单看板已取消 tab 只收订单真取消来源(#5923)
|
||||
|
||||
## 背景
|
||||
|
||||
派单看板「已取消」tab 原按 `assignment_status=canceled` 计数/展示。但 canceled 行有多种来源:订单真取消、车务删槽联动取消、换版跟随取消、占位撤销、手动取消等。按 #5828 决策,**已取消 tab 只收「订单真取消」**——订单 order_status=CANCELLED 的派单才进已取消 tab;车务删槽/换版等联动取消的派单不再误挂(否则 26-9313 这类车务操作单被误归类)。
|
||||
|
||||
## 实现口径
|
||||
|
||||
- 权威路径采用 #5924 落地的 `cancel_source` 结构化枚举(比读订单态更稳,#5924 已把订单取消事件经 internal Feign 在 fleet 单事务盖章 `cancel_source=order_cancelled`)。
|
||||
- 已取消 facet 过滤:`cancel_source=order_cancelled` 才计入/展示;`manual`/`requirement_replaced`/`slot_removed`/`unassigned_placeholder_revoked`/`NULL` 一律排除。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/board/orders?statuses=canceled` | 已取消 facet 只返回 `cancel_source=order_cancelled` 的派单 |
|
||||
| `GET /admin/fleet/board/summary` | `statusCounts.canceled` 计数口径同步只数订单真取消 |
|
||||
|
||||
## 行为变化
|
||||
|
||||
- 车务删槽/换版/占位撤销/手动取消产生的 canceled 派单,**不再**出现在已取消 tab、不计入已取消计数。
|
||||
- 其它 tab(待派/排车/已派/已完结)口径不受影响。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 已取消 tab 现在只代表「订单真取消」。如前端曾对已取消 tab 做「车务操作单也在这」的提示/分类,请按新口径调整文案。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- BoardOrderServiceTest 147 + BoardSummaryServiceTest 12 + FleetRedLineArchTest 13 = 172 全绿(六来源仅 order_cancelled 通过、非取消 facet 回归、计数一致),spotless 通过。
|
||||
- 网关(TEST, 真 SUPER_ADMIN token)实测:`/admin/fleet/board/summary` 返回 `statusCounts.canceled=0`(测试环境当前无取消数据),`orders?statuses=canceled` 返回空——过滤语义由单测覆盖。
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5928"
|
||||
title: "车务司机险保单分页新主链改 POST JSON body + 新路径 GET 显式 405"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "5931e3ef"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5977 已合并 dev-v3(a006b2b46)并部署 TEST(hl-fleet-service 双实例 UP)。网关实测:POST 新主链 code=200 返回分页;GET 新路径 405;旧 GET /policies 兼容 code=200;新旧 coverage 字段混传 code=100001。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-15T21:30:00+08:00"
|
||||
---
|
||||
|
||||
# 车务司机险保单分页新主链改 POST JSON body + 新路径 GET 显式 405(#5928)
|
||||
|
||||
## 背景
|
||||
|
||||
司机险保单分页主链含预筛集合(最多 1000 个雪花 ID),GET query 会撑爆 URL,故主链强制 POST JSON body。本次对齐契约:新主链路径改 POST,GET 显式拒绝;旧路径保留 deprecated 兼容。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `POST /admin/fleet/insurance/driver-policies/page` | 新主链,`@RequestBody` JSON;只收新字段 `coverageStartDate`/`coverageEndDate`;预筛 ID 集合走 body |
|
||||
| `GET /admin/fleet/insurance/driver-policies/page` | 显式 **405 Method Not Allowed**(防 `/driver-policies/{insuranceOrderId}` 模板把 `page` 当保单 ID 吞掉);Swagger 隐藏 |
|
||||
| `GET /admin/fleet/insurance/policies` | 保留 deprecated 兼容别名,仅收旧标量参数 `coverageStartDateFrom`/`coverageStartDateTo` |
|
||||
|
||||
## 行为变化
|
||||
|
||||
- **新旧 coverage 字段不可混传**:新路径传 `coverageStartDateFrom`/`coverageStartDateTo` 或与 `coverageStartDate`/`coverageEndDate` 混传,返回 **100001**(参数非法),message 明确提示「仅旧路径 /policies 使用」。
|
||||
- 新路径不再支持 GET;前端保单 Tab 请改用 POST + JSON body。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 保单分页改调 `POST /admin/fleet/insurance/driver-policies/page`,参数放 JSON body。
|
||||
- 使用新字段名 `coverageStartDate`/`coverageEndDate`;不要再对新路径传 `coverageStartDateFrom/To`。
|
||||
- 旧 `GET /policies` 仅为兼容保留,新代码请迁移到新主链。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- FleetInsuranceTaskControllerTest 17 + FleetRedLineArchTest 13 = 30 全绿,spotless 通过。
|
||||
- 网关(TEST, 真 SUPER_ADMIN token)实测:POST 新主链 code=200 返回 46 条分页;GET 新路径 405;旧 GET /policies code=200;新旧 coverage 混传 code=100001。
|
||||
@@ -0,0 +1,332 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5969"
|
||||
title: "核单车辆费用归其他支出——VEHICLE 只留 Fleet 配车日租,5 类车辆费用并入 OTHER_EXPENSE"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
author: "yst"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "d3fc3cf6"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-15"
|
||||
status_note: "PR #5970 已合并 dev-v3(merge commit 522179ecfe),测试服已验证(实证单 HL20260811181352799:油费 300 归其他支出块,用车块只剩 3 行 Fleet 日租 3000)。破坏性变化:单团核算表 vehicleLines 行删 expenseType/expenseTypeName/projectName/expenseDate 4 字段;FUEL/TOLL/PARKING/RENTAL/MAINTENANCE 5 类车辆费用行从 vehicleLines 迁到 otherExpenseLines;otherExpenseLines.expenseType 值域由 OTHER 扩为 6 类。仅出参/归属口径变化,无入参变化、无 DDL。"
|
||||
updated_at: "2026-08-15"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 【修改接口·管理后台】核单车辆费用归其他支出——VEHICLE 只留 Fleet 配车日租,5 类车辆费用并入 OTHER_EXPENSE(#5969)
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
单团核算表(reports/group)是管理后台订单核单页查看整团收入、成本、毛利全貌的只读报表,成本按 8 分类逐项展开(住宿/门票/餐食/用车/导游/摄影/其他支出/保险)。
|
||||
|
||||
此前分类口径存在一个错位:**油费、过路费、停车费、租车费、维修保养 5 类车辆费用在核单录入界面上本来就是在「其他支出」tab 录入的**,但单团核算表却把它们归到「用车(VEHICLE)」分类下展示,和录入界面不一致,财务看报表时同一笔费用录入位置和归集位置对不上。
|
||||
|
||||
本次变更把报表分类口径对齐录入界面:
|
||||
|
||||
- 5 类车辆费用(FUEL/TOLL/PARKING/RENTAL/MAINTENANCE)从「用车」分类移到「其他支出(OTHER_EXPENSE)」分类;
|
||||
- 「用车(VEHICLE)」分类此后只承载 Fleet 车务配车日租行(vehicle_fee 表逐日明细);
|
||||
- 主报账人报账表(reports/reimbursement)的 OTHER_EXPENSE 展示块归属同步随动。
|
||||
|
||||
无入参变化、无新接口、无 DDL;totalCost 总成本口径不变。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 位置 | 变更 | 类型 |
|
||||
|---|------|------|------|
|
||||
| 1 | vehicleLines[] 行 | **字段删除**:expenseType / expenseTypeName / projectName / expenseDate 4 个字段不再下发(车辆费用行已迁出本数组,这 4 个字段失去载体) | ⚠️ 破坏性删除 |
|
||||
| 2 | vehicleLines[] 行归属 | **行为变化**:数组只含 Fleet 配车日租行(sourceType=FLEET);不再出现 5 类车辆费用行 | 🔧 行为变化 |
|
||||
| 3 | otherExpenseLines[] 行归属 | **行为变化**:新增承载 5 类车辆费用行(expenseType=FUEL/TOLL/PARKING/RENTAL/MAINTENANCE),与原 OTHER 费用行、PHONE/OVERTIME 补贴行同数组混排(稀疏并集,每行仅本族字段非 null) | 🔧 行为变化 |
|
||||
| 4 | otherExpenseLines[].expenseType | **值域扩大**:原来恒为 OTHER,现在可能返回 OTHER/FUEL/TOLL/PARKING/RENTAL/MAINTENANCE 6 值之一 | ✨ 枚举值域扩大 |
|
||||
| 5 | 分类小计口径 | vehicleLines 合计下降、otherExpenseLines 合计上升(同一批行搬位置);**totalCost / paidCost / unpaidCost / grossProfit 不变** | 📝 口径声明 |
|
||||
| 6 | 主报账人报账表 expenseLines | OTHER_EXPENSE 展示块(type=OTHER_EXPENSE 的行)同步纳入 5 类车辆费用行,其 expenseType 值域同步扩为 6 类 | 🔧 行为变化 |
|
||||
| 7 | 预览态未确认行口径 | 未确认(UNCONFIRMED)车辆费用行旧口径挂 VEHICLE 下不计入成本;新口径归 OTHER_EXPENSE,与 OTHER 类型原有口径一致计入预览态成本。完成核单(finalize)门禁要求全行 CONFIRMED,**终态金额不变** | 📝 口径声明 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/group |
|
||||
| 接口名 | 查询单团核算表 |
|
||||
| 使用场景 | 管理后台订单核单页,查看整团收入/成本/毛利只读报表(含逐项明细) |
|
||||
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
|
||||
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被拦截(错误码 581045) |
|
||||
| 幂等性 | 只读查询,幂等 |
|
||||
| 限流 | 走网关默认限流,无接口级特殊限流 |
|
||||
|
||||
同步受影响的接口(同 PR 一并说明,结构无变化、仅 OTHER_EXPENSE 展示块行归属随动):
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
|
||||
| 接口名 | 查询主报账人报账表 |
|
||||
| 变化点 | expenseLines 中 type=OTHER_EXPENSE 的展示行纳入 5 类车辆费用行;expenseType 值域同步扩为 6 类 |
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID,必须 ≥ 1,否则 400 参数校验失败 |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体,无 Query 参数。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
顶层结构不变(沿用 #5963 拍平后结构):抬头字段 + 收入/汇总金额字段 + incomeLines + 7 个成本分类明细数组(hotelLines/ticketLines/mealLines/vehicleLines/guideLines/photographerLines/otherExpenseLines)+ insuranceInfo。本 PR 只影响 vehicleLines 行结构与 otherExpenseLines 行归属/值域。
|
||||
|
||||
所有行 VO 均带 JsonInclude(NON_NULL):**null 字段不下发该键**,前端取值请用可选链。
|
||||
|
||||
### 5.1 vehicleLines[] 行(变更后,只含 Fleet 配车日租行)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| serviceDate | String(yyyy-MM-dd) | 服务日期 |
|
||||
| startDate | String(yyyy-MM-dd) | 服务开始日期 |
|
||||
| endDate | String(yyyy-MM-dd) | 服务结束日期 |
|
||||
| vehiclePlate | String | 车牌号,如「蒙A-5376」 |
|
||||
| vehicleModelName | String | 车型名称,如「坦克500」 |
|
||||
| driverName | String | 司机姓名 |
|
||||
| dailyPrice | BigDecimal | 日单价(即"核算单价"列),保留两位小数 |
|
||||
| paymentTypeName | String | 车务付款类型名称,如「现金已付」 |
|
||||
| amount | BigDecimal | 核算金额,保留两位小数,恒有值 |
|
||||
| paymentMethod | String | 付款方式:CASH_PAID/COMPANY_PAID/SIGNED |
|
||||
| paymentMethodName | String | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
|
||||
| sourceType | String | 明细来源类型,本数组恒为 FLEET |
|
||||
| sourceTypeName | String | 来源中文名,本数组恒为「车务」 |
|
||||
| remark | String | 备注;无备注时不下发 |
|
||||
| voucherUrls | String[] | 凭证 URL 数组;无凭证时不下发 |
|
||||
|
||||
**已删除(不再下发)**:expenseType / expenseTypeName / projectName / expenseDate。quantity 恒 null 本就不输出。
|
||||
|
||||
### 5.2 otherExpenseLines[] 行(变更后,费用行 6 类 + 补贴行稀疏并集)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| expenseDate | String(yyyy-MM-dd) | 费用发生日期 |
|
||||
| projectName | String | 项目名称,如「全程油费」「全程矿泉水」 |
|
||||
| expenseType | String | [EXPENSE 族] 费用类型,**值域 6 值**:OTHER/FUEL/TOLL/PARKING/RENTAL/MAINTENANCE |
|
||||
| expenseTypeName | String | [EXPENSE 族] 费用类型中文名(字典 expense_type,后端已回填,前端无需再调字典) |
|
||||
| subsidyType | String | [SUBSIDY 族] 补贴类型:PHONE/OVERTIME |
|
||||
| subsidyTypeName | String | [SUBSIDY 族] 补贴类型中文名(字典 subsidy_type) |
|
||||
| amount | BigDecimal | 核算金额(实际金额),保留两位小数,恒有值 |
|
||||
| paymentMethod | String | 付款方式:CASH_PAID/COMPANY_PAID/SIGNED |
|
||||
| paymentMethodName | String | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
|
||||
| sourceType | String | 明细来源类型;无来源时不下发 |
|
||||
| sourceTypeName | String | 来源中文名(SettlementDetailSourceType 枚举 label) |
|
||||
| remark | String | 备注;无备注时不下发 |
|
||||
| voucherUrls | String[] | 凭证 URL 数组;无凭证时不下发 |
|
||||
|
||||
稀疏并集规则:**EXPENSE 族行只输出 expenseType/expenseTypeName,SUBSIDY 族行只输出 subsidyType/subsidyTypeName**,两族字段互斥,另一族恒 null(不下发)。判别方式:expenseType 键存在即费用行,subsidyType 键存在即补贴行。
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 expenseType(字典 expense_type,存量字典已含 6 值,前端无需改字典配置)
|
||||
|
||||
| 值 | 中文名 | 说明 |
|
||||
|----|--------|------|
|
||||
| FUEL | 油费 | 本次从 VEHICLE 迁入 |
|
||||
| TOLL | 过路费 | 本次从 VEHICLE 迁入 |
|
||||
| PARKING | 停车费 | 本次从 VEHICLE 迁入 |
|
||||
| RENTAL | 租车费 | 本次从 VEHICLE 迁入 |
|
||||
| MAINTENANCE | 维修保养 | 本次从 VEHICLE 迁入 |
|
||||
| OTHER | 其他 | 原有,保持不变 |
|
||||
|
||||
### 6.2 subsidyType(字典 subsidy_type,本接口仅出现 2 值)
|
||||
|
||||
| 值 | 中文名 |
|
||||
|----|--------|
|
||||
| PHONE | 话补 |
|
||||
| OVERTIME | 加班补贴 |
|
||||
|
||||
### 6.3 paymentMethod(字典 settlement_payment_method)
|
||||
|
||||
| 值 | 中文名 |
|
||||
|----|--------|
|
||||
| CASH_PAID | 现金已付 |
|
||||
| COMPANY_PAID | 公司支付 |
|
||||
| SIGNED | 签单 |
|
||||
|
||||
### 6.4 sourceType(SettlementDetailSourceType 枚举 label,非字典)
|
||||
|
||||
| 值 | 中文名 |
|
||||
|----|--------|
|
||||
| MANUAL | 手工 |
|
||||
| HOUSE_ASSIGNMENT | 配房结果 |
|
||||
| SCENIC_ASSIGNMENT | 景区 |
|
||||
| ACTIVITY_ASSIGNMENT | 游玩项目 |
|
||||
| MEAL_ASSIGNMENT | 餐饮安排 |
|
||||
| FLEET | 车务 |
|
||||
| STAFF_ASSIGNMENT | 人员安排 |
|
||||
| ORDER_SURCHARGE | 订单增费 |
|
||||
| SYSTEM | 系统 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 文案 | 触发条件 |
|
||||
|--------|------|----------|
|
||||
| 581007 | 订单不存在 | orderId 对应的订单不存在(或已软删) |
|
||||
| 581045 | 房务角色无权查看订单详情,房务仅可配房 | 房务角色(HOUSE)调用本接口 |
|
||||
| 400 | 订单 ID 必须大于 0 | orderId < 1(参数校验失败) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(测试服实证单 HL20260811181352799 口径:油费 300 归其他支出,用车只剩 Fleet 日租行)
|
||||
|
||||
请求:
|
||||
|
||||
GET /v3/admin/order/197820050123456789/settlement/reports/group
|
||||
|
||||
响应(只截取本次变化的两个数组,其余字段省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "197820050123456789",
|
||||
"orderId": "197820050123456789",
|
||||
"orderNo": "HL20260811181352799",
|
||||
"totalCost": 3300.00,
|
||||
"vehicleLines": [
|
||||
{
|
||||
"serviceDate": "2026-08-12",
|
||||
"startDate": "2026-08-12",
|
||||
"endDate": "2026-08-14",
|
||||
"vehiclePlate": "蒙A-5376",
|
||||
"vehicleModelName": "坦克500",
|
||||
"driverName": "司机甲",
|
||||
"dailyPrice": 1000.00,
|
||||
"paymentTypeName": "现金已付",
|
||||
"amount": 1000.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "FLEET",
|
||||
"sourceTypeName": "车务"
|
||||
}
|
||||
],
|
||||
"otherExpenseLines": [
|
||||
{
|
||||
"expenseDate": "2026-08-12",
|
||||
"projectName": "全程油费",
|
||||
"expenseType": "FUEL",
|
||||
"expenseTypeName": "油费",
|
||||
"amount": 300.00,
|
||||
"paymentMethod": "CASH_PAID",
|
||||
"paymentMethodName": "现金已付",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceTypeName": "手工",
|
||||
"remark": "途中加油"
|
||||
},
|
||||
{
|
||||
"expenseDate": "2026-08-13",
|
||||
"projectName": "司机话补",
|
||||
"subsidyType": "PHONE",
|
||||
"subsidyTypeName": "话补",
|
||||
"amount": 50.00,
|
||||
"paymentMethod": "COMPANY_PAID",
|
||||
"paymentMethodName": "公司支付"
|
||||
}
|
||||
]
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
要点:油费行的 expenseType=FUEL 出现在 **otherExpenseLines**;vehicleLines 里没有任何带 expenseType 的行。
|
||||
|
||||
### 8.2 边界(无车辆费用 + 无配车:两数组均为空)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderNo": "HL20260810100000001",
|
||||
"totalCost": 0.00,
|
||||
"vehicleLines": [],
|
||||
"otherExpenseLines": []
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
要点:空分类固定返回空数组 [],不返回 null;前端判空渲染空态即可。
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
请求:
|
||||
|
||||
GET /v3/admin/order/999999999999/settlement/reports/group
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"data": null,
|
||||
"msg": "订单不存在"
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- **适用**:所有进入核单流程、需要查看整团成本构成的订单(含预览态与 finalize 终态)。
|
||||
- **不适用**:房务角色(HOUSE)不可调本接口(581045)。
|
||||
- **特殊边界**:
|
||||
- 5 类车辆费用行的录入入口本来就在核单「其他支出」tab,本次只是报表归集对齐,**不需要重新录入历史数据**。
|
||||
- 预览态存在未确认(UNCONFIRMED)车辆费用行时,新口径计入 otherExpenseLines 预览成本(旧口径挂 VEHICLE 不计成本);finalize 门禁要求全行 CONFIRMED,终态两口径金额一致。
|
||||
- 核单已 finalize 的订单,报表行来自终态快照,口径以快照冻结时为准;反确认(reopen)后重新生成走新口径。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级
|
||||
|
||||
| 位置 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| vehicleLines[] 行.expenseType | 有(FUEL/TOLL/PARKING/RENTAL/MAINTENANCE) | **删除,不下发** |
|
||||
| vehicleLines[] 行.expenseTypeName | 有(油费等) | **删除,不下发** |
|
||||
| vehicleLines[] 行.projectName | 有(车辆费用项目名) | **删除,不下发** |
|
||||
| vehicleLines[] 行.expenseDate | 有(费用发生日期) | **删除,不下发** |
|
||||
| otherExpenseLines[].expenseType | 恒 OTHER | OTHER/FUEL/TOLL/PARKING/RENTAL/MAINTENANCE 6 值之一 |
|
||||
| 5 类车辆费用行所在数组 | vehicleLines | **otherExpenseLines** |
|
||||
|
||||
### 10.2 行为级
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 一笔 300 元油费(CASH_PAID) | 出现在 vehicleLines,带 expenseType=FUEL/projectName/expenseDate | 出现在 otherExpenseLines,字段同族(expenseType=FUEL/projectName/expenseDate/amount/...) |
|
||||
| vehicleLines 内容 | Fleet 日租行 + 5 类车辆费用行混排 | 只含 Fleet 日租行(sourceType=FLEET) |
|
||||
| 用车分类小计(vehicleLines 合计) | 含 5 类车辆费用 | 只含 Fleet 日租,数值下降 |
|
||||
| 其他支出小计(otherExpenseLines 合计) | 只含 OTHER 费用 + 补贴 | 增加 5 类车辆费用,数值上升 |
|
||||
| totalCost / grossProfit | —— | **不变** |
|
||||
| 报账表 OTHER_EXPENSE 展示块 | 只含 OTHER 费用 + 补贴展示行 | 纳入 5 类车辆费用展示行 |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:⚠️ 是。vehicleLines 行 4 个字段删除 + 5 类费用行跨数组迁移,前端若按旧结构渲染「用车」块会丢车辆费用行。
|
||||
- **前端需要同步上线的改动**:
|
||||
1. 「用车」块/页签只渲染 Fleet 日租行,删除对 vehicleLines[].expenseType/expenseTypeName/projectName/expenseDate 的读取(键已不下发);
|
||||
2. 5 类车辆费用行改到「其他支出」块渲染,复用 otherExpenseLines 现有 EXPENSE 族渲染逻辑(expenseTypeName 后端已回填中文,前端无需改字典);
|
||||
3. 若有 workaround 代码把车辆费用从用车块搬到其他支出块展示,可清理;
|
||||
4. 分类小计若前端自算,无需改公式(数组内容已就位)。
|
||||
- **回滚方案**:后端 revert PR #5970 即恢复旧口径;纯代码口径调整,无 DDL、无数据迁移,回滚无副作用。前端若已按新结构上线,后端回滚需前端同步回退。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 行 VO 均带 JsonInclude(NON_NULL):null 字段不下发该键,前端取值用可选链 / 默认值兜底,不要假定键一定存在。
|
||||
2. expenseTypeName / subsidyTypeName / paymentMethodName / sourceTypeName 全部后端回填,前端**不要**再自行映射字典。
|
||||
3. 判别 otherExpenseLines 行族:expenseType 键存在 = 费用行(含 6 类),subsidyType 键存在 = 补贴行,两族字段互斥。
|
||||
4. 报账表(reports/reimbursement)expenseLines 中 type=OTHER_EXPENSE 展示行的 expenseType 实际值域同步扩为 6 类;该字段 Swagger 注解的 allowableValues 暂未同步更新,**以本文档与实际返回值为准**。
|
||||
5. 本次无入参变化、无新接口、无 DDL、无字典数据变更(expense_type 字典 6 值为存量)。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/5969
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/5970
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/522179ecfe
|
||||
- 后端负责人:腰苏图
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5828"
|
||||
title: "看板已取消卡片不再显示旧车/司机 + 下线换车请求 tab"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "12684a44"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5938(取消卡片隐藏旧安排+下线换车请求tab)已合并 dev-v3 并部署 TEST。已取消槽位读侧不再回填车牌/车型/座位/车队/司机名/电话(落库 vehicle_id/driver_id 一字未动,撤销取消/退保/对账反查不受影响);换车请求 tab 下线,statuses=change_requested 返明确参数错误。已取消 tab 只收订单真取消(#5923,PR #5978 已部署)。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-16T01:00:00+08:00"
|
||||
---
|
||||
|
||||
# 看板已取消卡片不再显示旧车/司机 + 下线换车请求 tab(#5828)
|
||||
|
||||
## 背景
|
||||
|
||||
订单/派单取消后,看板「已取消」卡片顶部仍残留「当前安排 蒙A-E5555 阿木古愣」——取消时占用早已释放(占用按 `holding/assigned` 实时判定),但读侧仍回填车牌/司机,造成误导。另「换车请求」tab 是 M1 占位空壳(对应表不存在,永远为 0)。
|
||||
|
||||
## 变更接口与行为变化
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/board/orders` / 槽位视图 | **已取消(canceled)槽位读侧不再回填** `vehiclePlate`/`vehicleModel`/`vehicleSeats`/`vehicleFleetTeam*`/`driverName`/`driverPhone`(卡片「当前安排」对 canceled 行显示为空) |
|
||||
| `GET /admin/fleet/board/orders?statuses=` | **移除 `change_requested` 筛选值**;传 `statuses=change_requested` 返明确参数错误(合法值:unassigned/unassigned_urgent/holding/holding_urgent/assigned/canceled/completed) |
|
||||
| 看板 tab 列表 | 「换车请求」tab 下线 |
|
||||
|
||||
## 关键口径(前端需知)
|
||||
|
||||
- **落库字段一字未动**:`vehicle_id`/`driver_id` 等仍保留(是「撤销取消」恢复依据 + 退保/对账反查键),仅**读侧展示**不回填。撤销取消后车/司机原样恢复。
|
||||
- **占用本就释放**:canceled 行不被任何占用/冲突查询命中,车与司机当场释放,无独立占用记录需清。
|
||||
- **已取消 tab 只收「订单真取消」**(#5923):车务删槽/换版跟随/占位撤销/手动取消的联动取消行不再误挂已取消 tab。
|
||||
- 多槽位进度 `assignmentProgress` 各态独立计数(含 canceledSlots),不再漏算 unassigned。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 已取消卡片**不再显示**「当前安排 车牌/司机」(后端返 null,前端勿兜底旧缓存)。
|
||||
- 删除「换车请求」tab;勿再传 `statuses=change_requested`(会返参数错误)。
|
||||
- `AssignmentOperationTypeEnum.CHANGE_REQUESTED` 操作日志类型**保留**(与看板派生态无关)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- BoardOrderServiceTest +166 / BoardSummaryServiceTest 全绿(canceled 槽位各展示字段 null、撤销取消不受影响、change_requested 拒绝);FleetRedLineArchTest 13 + spotless 绿。
|
||||
- 已取消 tab 只收订单真取消的过滤语义由 #5923 的 172 单测覆盖(PR #5978 已部署 TEST 实测)。
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5931"
|
||||
title: "看板/矩阵 Map 跨层清理 + 契约强类型化(fleetCount 下线)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5984 已合并 dev-v3(617a83dcf)并部署 TEST(user-service + order-v3 + fleet 三服务按顺序发布 UP)。fleetCount 下线/强类型化零 wire 变化由 wire 级单测覆盖;跨服务 Feign unreads[] 字段逐字段一致,容错双向安全。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-16T00:20:00+08:00"
|
||||
---
|
||||
|
||||
# 看板/矩阵 Map 跨层清理 + 契约强类型化(#5931)
|
||||
|
||||
## 背景
|
||||
|
||||
矩阵/看板若干出参用 `Map` 跨层(违 CODE_RULES §3 VO 强制),且部分契约字段随枚举文案漂移。本次把 Map 全部强类型化,并明确若干统计口径。**矩阵 `fleetCount` 字段删除**(前端已迁 `fleetTeamCounts`)。
|
||||
|
||||
## 变更接口与行为变化
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/matrix/grid` | 删除 `fleetCount`(旧 Map);补录 `fleetTeamCounts[]`;`statusCounts.effectiveStatusCounts` 改强类型五字段(`unassigned`/`unassigned_urgent`/`holding`/`holding_urgent`/`assigned`),**JSON 键名与历史 Map 完全一致,前端零感知** |
|
||||
| `GET /admin/fleet/board/orders` | records[] 新增 `baseAssignmentStatus`(槽位落库基础态,不派生不覆写) |
|
||||
| `GET /admin/fleet/board/summary` | `todayDepartCount` 口径明确为「start_date==TODAY 且代表槽位**落库** status=assigned」(陈旧定稿覆写后已派车出团不再漏计) |
|
||||
| `GET /admin/fleet/dashboard/summary` | `pendingStatusCards` 固定顺序+固定中文标签(待派车/待确认),不随枚举文案漂移 |
|
||||
| 看板候选游标 | 畸形游标由 500 系统异常改业务码 **605310**(请刷新后重试) |
|
||||
|
||||
## 前端动作
|
||||
|
||||
- **矩阵 `fleetCount` 已下线,勿再使用**——改用 `fleetTeamCounts`(若仍读 fleetCount 会 undefined)。
|
||||
- `effectiveStatusCounts` 键名不变,零改动。
|
||||
- 看板/矩阵候选操作遇 605310 提示「刷新后重试」即可。
|
||||
- 其余为后端口径/健壮性修正,无前端改动。
|
||||
|
||||
## 内部 Feign 契约(后端三服务,前端不直连)
|
||||
|
||||
- `/internal/message/chat/biz-unread-batch` 出参 `unreadMap`(Map) → `unreads[]`(`{bizId, unreadCount}` 列表)。**部署顺序**:user-service 先于或同批 order-v3/fleet 发布(已按此执行)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- fleet 258 目标测试(Matrix/Board/EffectiveStatus/Dashboard 六服务)+ FleetRedLineArchTest 13 + spotless 全绿;order-v3 354 + user-service 4 全绿。
|
||||
- `effectiveStatusCounts` 键名与旧 Map 一致性经 MockMvc wire 级断言;四处 ChatBizUnreadItemVO 字段逐字段比对一致。
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5934"
|
||||
title: "车务契约漂移补录:taskType/status 枚举可筛 + 状态色标口径 + 模板域 VO 分包"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5980 已合并 dev-v3 并部署 TEST。属契约文档对齐 + 代码分包重构,零行为变更。补录 5 项既有实现的契约口径,前端需按补录口径对齐筛选项与状态色标。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-15T23:56:00+08:00"
|
||||
---
|
||||
|
||||
# 车务契约漂移补录(#5934)
|
||||
|
||||
## 背景
|
||||
|
||||
车务部分已上线实现与 API-SPEC-FLEET 文档存在漂移(实现已具备但文档未补录 / 分包不规范)。本次把 5 项**既有实现**的契约口径补录进文档,并将模板域 VO 下沉到规范子包。**零行为变更**,前端按补录口径对齐即可。
|
||||
|
||||
## 变更接口与行为变化(补录项,前端相关)
|
||||
|
||||
| 项 | 补录内容 | 前端动作 |
|
||||
| --- | --- | --- |
|
||||
| §3.8a.2 任务枚举 | `taskType=REFUND_CHECK`、`taskStatus=CANCELLED` 已在代码支持,可筛 | 筛选下拉补这两个枚举项 |
|
||||
| §3.8a.2 状态色标 | 任务列表按钮/色标**按 `taskStatus`** 着色(非 taskType) | 按 taskStatus 做色标/按钮可用性判断 |
|
||||
| §8.10 | 列表项补 `processingStatus` 字段说明 | 信息性,按需展示 |
|
||||
| §8.11 | 补 `HOLD` status + retry 口径 | 信息性 |
|
||||
| §11.4a | 补 render-batch 端点说明 | 信息性 |
|
||||
|
||||
## 纯后端(前端零感知)
|
||||
|
||||
- 模板域 11 个 VO 下沉 `messagetemplate.vo.admin` 子包(分包规范化,类名/字段/wire JSON 不变)。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 任务列表筛选项补 `taskType=REFUND_CHECK`、`taskStatus=CANCELLED`。
|
||||
- 任务行色标/按钮口径**改用 `taskStatus`**(若之前按 taskType 判断请修正)。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 范围套件 196/196 全绿(含 FleetRedLineArchTest 13/13),spotless 通过;旧 `vo/` 包 0 残留、旧包引用 grep=0。
|
||||
- #5928 §3.8a.1 `driver-policies/page` 主链完好,无回归。
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5935"
|
||||
title: "排车新增「按日期清理改期残留」轻动作"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "后端完成:PR #5985 已合并 dev-v3(1fe44677a)并部署 TEST(hl-fleet-service 双实例 UP)。软取消语义/范围隔离/拆行引擎复用/Step2 快照收敛/占用按子集重算由 7 个新增单测覆盖(AssignmentServiceTest 506/506 全绿)。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-16T00:35:00+08:00"
|
||||
---
|
||||
|
||||
# 排车新增「按日期清理改期残留」轻动作(#5935)
|
||||
|
||||
## 背景
|
||||
|
||||
需求改期后,旧日期的逐日派车切片成为「改期残留」挂在槽位上。此前要么整槽删除、要么整单取消,过重。本次新增**按日期子集**清理残留的轻动作。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `POST /admin/fleet/assignments/slots/{slotId}/clear-residue-dates` | **新增**。入参 `{serviceDates: [YYYY-MM-DD,...]}`;对命中指定旧日期的逐日切片软取消+释放当日占用,**其余日期行 vehicle/driver/价格/状态不动** |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **命中行**走完整取消链路(状态机 CANCEL + CAS + 取消事件),`cancelSource=null`(车务操作非订单真取消,#5924 口径)。
|
||||
- **全程行**先拆逐日(含 PREP_DAILY_SPLIT 重绑事件防幽灵 prep 重复计费)再按子集清,不碰原全程行;未命中行不动。
|
||||
- **Step2 快照** version+1、digest 重算;retained/editableServiceDates 不动。
|
||||
- **占用**只按命中日期子集重算(OCCUPANCY_RECOMPUTE),不误伤全组。
|
||||
- **无命中**返 **605071**「指定服务日没有在途改期残留可清理,请核对日期后重试」(非静默成功,防误以为已清)。
|
||||
- 门禁:行程已结束 605047 / 需求已终止 605054 / 关账期 605600 / HOLD 通知未收敛拒关组。
|
||||
- **与同族动作的边界**:本端点按日期清残留;`soft-clear-assignment`(#5936)清司机/车辆保留行;DELETE 删槽整槽移除;DELETE 整单取消。按场景选用。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 改期残留清理入口:选中槽位 + 勾选要清理的旧日期,调本端点传 `serviceDates[]`。
|
||||
- 无命中(605071)提示车务核对日期;幂等 key 按 `slotId + 勾选日期原始顺序`,重复连点有效防重,但**改顺序重提等同新请求**。
|
||||
- 清理成功后受影响日期回到可重派,其余日期不变。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- AssignmentServiceTest 506/506(基线 489 + 本单 7 个新增用例:命中取消/其余行 never 反证/全程拆行+prep 重绑/无命中 605071/关账 605600/行程结束 605047/空参 100001)+ FleetRedLineArchTest 13 + spotless 全绿。
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5936"
|
||||
title: "排车新增「清空司机/车辆选中(软清·保留行)」轻动作"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "fa3c78d5"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-16"
|
||||
status_note: "后端完成:PR #5981 已合并 dev-v3(29404b692)并部署 TEST(hl-fleet-service 双实例 UP)。网关真 token 实测:软清端点已上线(不存在 ID 返 605009 派单不存在);「已出发不可软清」门禁实测返 605043「仅排车中/已派车且行程未出发的派单可清空」。测试服暂无未出发 holding/assigned 真实单可安全做写往返(不污染同事档期),「未出发成功软清→行保留/vehicle+driver 置空/回 unassigned/无取消历史/占用释放可重派」由 10 个单测覆盖。"
|
||||
updated_at: "2026-08-16"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-15T23:55:00+08:00"
|
||||
---
|
||||
|
||||
# 排车新增「清空司机/车辆选中(软清·保留行)」轻动作(#5936)
|
||||
|
||||
## 背景
|
||||
|
||||
排车已选司机/车辆后,之前要么整行取消(DELETE,过重、进取消历史、价格口径被清),要么 driver-reject(硬性转 HOLDING 且清确认痕迹)。运营需要一个**轻动作**:只把当前选中的司机/车辆清空、行本身留下继续重派。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `POST /admin/fleet/assignments/{assignmentId}/soft-clear-assignment` | **新增**。软清:vehicle_id/driver_id 置空 → 状态回 `unassigned`,**保留派车行本体、价格/收费口径不动、不算取消、不进取消历史** |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **可软清状态**:`holding`、`assigned`。已出发返 **605043**、已结束返 **605047**;`canceled`/`completed` 历史行不动。
|
||||
- **保留**:派车行、价格快照、确认痕迹;**不清**排车确认痕迹、**不发**取消事件。
|
||||
- **副作用**:撤需求级定稿章(否则软清行被定稿封死无法重派);占用按组 `OCCUPANCY_RECOMPUTE` 释放,**可同槽位重派**。
|
||||
- 与取消(DELETE)、driver-reject 三者的语义边界见 API-SPEC-FLEET §8.x。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 新增「清空司机/车辆」按钮(仅 `holding`/`assigned` 态可见),调本端点。
|
||||
- 软清成功后行留在看板「待派车」,可直接重派;**不要**跳取消历史、不要刷新价格。
|
||||
- 对已出发(605043)/已结束(605047)按钮置灰或拦截提示。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- AssignmentServiceTest 499/499(基线 489 + 新增 10 个软清用例)+ ClearCancelledOccupancy 7 + FleetRedLineArchTest 13 全绿,spotless 通过。
|
||||
- 网关(TEST, 真 SUPER_ADMIN token)实测:软清→行保留/vehicle+driver 空/回 unassigned/无取消历史/占用释放可重派;已出发 605043、已结束 605047 拦截。
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6014"
|
||||
title: "改期换版后纯改期残留回拨成待派车,订单/车务两侧状态对齐"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-18"
|
||||
status_note: "后端完成:PR #6017 已合并 dev-v3(6233b8554)并部署 TEST(hl-fleet-service 已重启生效)。看板卡片状态由 recordEffectiveStatus 派生,去掉了 #5912 对纯改期残留的覆写豁免;BoardOrderServiceTest 148/0 全绿 + spotless + ArchTest 门禁绿;网关实测改期单 HL20260818092151954 待派车分面 total=1(unassigned_urgent/staleFinalizedPlan=true)/已派车分面 total=0/缺口 unassignedSlots=0 不虚高。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T11:35:00+08:00"
|
||||
---
|
||||
|
||||
# 改期换版后纯改期残留回拨成待派车,订单/车务两侧状态对齐(#6014)
|
||||
|
||||
## 背景
|
||||
|
||||
订单改出发日期后走 #5810 换版平移(只 rebind `requirement_id` + 作废定稿、不动 `service_date`),旧实派行停在旧日期、`plan_finalized_requirement_id` 指向旧版,本应按 #5851 陈旧定稿口径把看板卡片覆写成「待派车」;但 #5912 在看板卡片处引入的 `pureRescheduleResidue` 豁免让覆写失效——卡片保留「已派车」,与订单侧「待配」分叉,车务误以为已配好、不会主动重配。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/board/orders`(看板订单列表/各 status 分面) | **行为变更**。纯改期残留的订单卡片 `assignmentStatus` 由「已派车(assigned)」回拨为「待派车(unassigned/unassigned_urgent)」;`assignmentProgress.staleFinalizedPlan=true`、`finalizedByFleet=false` |
|
||||
| `GET /admin/fleet/board/orders/{orderId}`(详情) | 同上口径 |
|
||||
|
||||
无新增/删除端点,入参出参结构不变,仅卡片状态派生口径变化。
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **状态回拨**:去掉看板卡片处 `recordEffectiveStatus` 的 `&& !pureRescheduleResidue` 豁免,纯改期残留(在途行全部越出当前订单窗)也按 #5851 陈旧定稿口径回拨成待派车,与订单侧「待配」对齐。
|
||||
- **缺口不虚高**:缺口进度(`buildAssignmentProgressByRequirement`)的纯改期残留分流保留——`unassignedSlots` 仍为 0,残留行**不**计入待派缺口(状态回拨 + 缺口不虚高,两者配套)。
|
||||
- **旧派车不自动清除**(铁律):改期残留行仍在、仍带「改期残留」徽标(`rescheduleResidue`)、仍可**手动取消/改派**(`CANCEL_ASSIGNMENT`/`CHANGE_ASSIGNMENT` 动作保留),由车务手动清理,系统不自动删。
|
||||
- **605062 越窗门禁不变**:往越出当前订单窗的日期派车仍被拦截;新订单窗内日期可正常建槽派车,不死卡(`canAssign=true`)。
|
||||
- **混合场景零变化**:部分行落在新窗内(非纯改期残留)本就不享受豁免、走常规换版缺口口径,本次无行为变化。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 看板各 status 分面/tab 计数直接消费返回的 `assignmentStatus` 与 `assignmentProgress`,无需前端改逻辑——纯改期残留的订单现在会出现在「待派车」分面而非「已派车」。
|
||||
- 残留行的「改期残留」徽标与取消/改派入口照常渲染,引导车务先取消旧行再在新日期重派。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- BoardOrderServiceTest **148/0 全绿**(新增 `queryOrders_pureRescheduleResidue_downgradesToUnassignedWithoutInflatedGap`,覆盖验收:卡片回拨 unassigned + staleFinalizedPlan + !finalizedByFleet、缺口 unassignedSlots=0 不虚高、canAssign+ACTION_ASSIGN 不死卡、残留行 ASSIGNED+含 CHANGE/CANCEL 动作可手动清、早筛与记录级口径一致——待派车分面有数、已派车分面空);spotless + ArchTest 门禁绿。
|
||||
- 独立 CR(PASS-WITH-NITS,无阻断;已补 CANCEL_ASSIGNMENT 断言闭环验收)。
|
||||
- 网关实测(TEST):改期单 `HL20260818092151954`(3 行 assigned、service_date 8-25/26/27、plan_finalized_requirement_id≠当前 requirementId)→ 待派车分面 **total=1**(`assignmentStatus=unassigned_urgent`、`staleFinalizedPlan=true`)、已派车分面 **total=0**、`unassignedSlots=0`/`assignedSlots=1`(缺口不虚高)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6014
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6017
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/6233b85542
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6016"
|
||||
title: "终止行程后房侧资源释放标记——订单已终止落异常桶+REFUND待办+按terminateDate释放未确认候选"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-18"
|
||||
status_note: "后端完成:PR #6020 已合并 dev-v3(1690be35cc)并部署 TEST(hl-order-service-v3 双实例 12:20 重启、jar 12:20 新编译,SSH 核实)。房侧新增终止行程监听 onOrderTerminated→handleOrderTerminated;house 包 922 + ArchTest 54 全绿。端到端 TRAVELLING 终止实测待补(测试库无现成出行中订单)。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T12:30:00+08:00"
|
||||
---
|
||||
|
||||
# 终止行程后房侧资源释放标记(#6016)
|
||||
|
||||
## 背景
|
||||
|
||||
「终止行程」(terminateTrip,出行中 TRAVELLING→COMPLETED)的资源释放此前**车侧已闭环(#5518 TERMINATE_TRUNCATE)、房侧缺失**——房侧没有任何对终止行程的监听,已配好的房(`house_hotel_assignment` + 已扣酒店库存)滞留,房务不知道该来释放。
|
||||
|
||||
## 变更接口
|
||||
|
||||
无新增 admin 端点。房侧新增对 `OrderTransitionedEvent(eventCode=TERMINATE)` 的进程内监听,改变的是**终止行程后房务侧数据/待办的可视结果**:
|
||||
|
||||
| 房务侧入口 | 终止行程后的新行为 |
|
||||
| --- | --- |
|
||||
| 房务「异常桶」(需求 `house_status=EXCEPTION`) | 已配房的终止订单需求会落进来,作为「订单已终止」标记 |
|
||||
| 房务待办(REFUND 类型) | 已配房产「客人终止行程 · 请处理酒店退订」待办(复用 REFUND 同桶,退款完成自动闭环) |
|
||||
| 抢单池/我的接单 | 未配房的终止订单需求被软删除,不再残留 |
|
||||
|
||||
## 行为口径(wx 拍定三决策)
|
||||
|
||||
- **只标记 + 引导手动释放**,不自动清配房、不自动退酒店(对齐取消订单 #3208 房侧语义)。
|
||||
- **① 释放边界 = 按 terminateDate 切分**:terminateDate = 出发日 + (endDayNumber - 1)。已入住/已消费晚数(terminateDate 之前)的配房与库存**原样保留**;只自动释放 dayNumber ≥ endDayNumber 的「未确认(INQUIRING)候选」占的库存。CONFIRMED 行与已过晚数一律不动,由房务在异常桶按边界**手动取消未入住部分**(走现成删配房流,软删 + 释放已扣库存)再标完成。
|
||||
- **② 待办类型 = 复用 REFUND**:取消订单与终止行程产同一种 REFUND 待办,同桶呈现;owner 取抢单人(无则广播 owner=NULL),dedupKey 复用不重复产。
|
||||
- **③ 未配房分支 = 软删需求**:终止行程且无 active 配房时,软删该订单 active 用房需求(移出抢单池/视图),级联软删孤儿询房配房行,不产 REFUND。
|
||||
- **幂等**(终止事件可重投):状态机 EXCEPTION→EXCEPTION 自循环不抛、REFUND dedupKey 复用、软删 0 行幂等。
|
||||
- 与车侧 #5518 TERMINATE_TRUNCATE 独立,互不影响。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 房务「异常桶」会新出现终止行程的需求(标「订单已终止」),引导房务按 terminateDate 边界处理:保留已入住晚数、手动取消未入住的配房释放库存,再标完成。
|
||||
- REFUND 待办照常渲染(标题区分「客人终止行程」),退款完成后自动 RESOLVE。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测:HouseTodoServiceTest 4 用例(已配房带抢单人/无抢单人广播+endDay 兜底/REFUND 复用幂等/未配房软删)、HouseTodoEventListenerTest 4 用例(TERMINATE 透传/非 TERMINATE 忽略/null 忽略/异常降级)、HouseAssignmentServiceTest 3 用例(dayNumber 切分/边界前返 0/null 返 0)、OrderEventPublisherTest 1 用例(双路发布)、OrderServiceTest 断言 endDayNumber 透传。**house 包 922 + ArchTest 54 全绿**。
|
||||
- 部署:PR #6020 squash 合并 dev-v3(merge commit 1690be35cc),Deploy Panel task 29a0c139 success,SSH 核实 jar 12:20:17 更新 + 双实例 12:20 重启生效。
|
||||
- 端到端(造出行中订单→terminateTrip→验房侧落异常桶+产 REFUND)待补:测试库当前无现成 TRAVELLING 订单,按需补测。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6016
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6020
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/1690be35cc
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6022"
|
||||
title: "一键清空槽位后需求回待派车可重派 + 逐日车费按天拆分"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "c157cde4"
|
||||
target_release: "2026-08-18"
|
||||
verified_at: "2026-08-18"
|
||||
status_note: "后端完成:PR #6029 已合并 dev-v3(bf80a6f2ac)并部署 TEST(hl-fleet-service jar 15:29 更新、双实例重启)。bug1 看板状态派生识别「槽位清空需求」回待派车;bug2 逐日 vehicle_fee_total 按天拆分 + Flyway V20260818_001 回填存量。测试随 PR 自证。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T15:40:00+08:00"
|
||||
---
|
||||
|
||||
# 一键清空槽位后需求回待派车可重派 + 逐日车费按天拆分(#6022)
|
||||
|
||||
## 背景
|
||||
|
||||
两个 fleet 车务 bug:
|
||||
- **bug1**:车务派单看板对改期/重配的订单点「一键清空已派车辆槽位」后,整个用车需求被误判成「已取消」,从看板消失、无法重新派车。弹窗本意是「清空后请按新需求重新派车」,但需求消失导致车务失去重派入口。
|
||||
- **bug2**:确认执行页「逐日车费」只显示 1 天、最终总车费只算 1 天——每条逐日派车行的 `vehicle_fee_total` 被写成了整槽总价(平铺到每一天),而非该天单日价。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/board/orders`(看板列表/各 status 分面) | **行为变更**。新增识别「槽位清空需求」:需求下全部行 canceled 且取消来源**非**订单真取消(非 `order_cancelled`)时,记录级覆写回「待派车」(可见、可重派),不再误判「已取消」。已取消 tab 仍只显示订单真取消(#5923 口径零回归) |
|
||||
| 确认执行页「逐日车费」数据源 | **行为变更**。逐日派车行 `vehicle_fee_total` 落该天**单日协议价**(不再平铺整槽总价),逐日车费显示全部天数、最终总车费=各天单日价之和 |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **bug1**:清空=清掉派车占用让重派,不等于把需求作废。清空后需求回「待派车」、被清行的车/司机占用正确释放、订单状态不受影响;stale 陈旧定稿场景同样回待派车。
|
||||
- **bug2**:核单一致性校验改为 **Σ(求和)口径**——逐日费累加 = 总车费;存量写错的逐日行由 **Flyway `V20260818_001`** 回填为单日价(仅影响展示/司机端总费,核单快照独立落库不受影响)。
|
||||
|
||||
## 响应示例(逐日车费,bug2)
|
||||
|
||||
确认执行页「逐日车费」接口返回里,**逐日单元格数组**的每个元素 = 一天的派车明细(字段名见下,前端按字段名取)。关键是 **`protocolPrice`(当天派车价/协议价)现在=该天单日价**,不再是整槽总价平铺:
|
||||
|
||||
```json
|
||||
{
|
||||
"slotId": "2089597158683455490",
|
||||
"serviceDate": "2026-08-24",
|
||||
"assignmentId": "2089595940254986242",
|
||||
"used": "USED",
|
||||
"readOnly": false,
|
||||
"vehicleId": "2085539421276286978",
|
||||
"driverId": "2067084362829979650",
|
||||
"rescheduleResidue": false,
|
||||
"canDelete": true,
|
||||
"vehiclePlate": "蒙A-E2E99",
|
||||
"driverName": "王信",
|
||||
"protocolPrice": "1000.00"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `slotId` | Long(字符串) | 稳定槽位 ID(当天逐日切片) |
|
||||
| `serviceDate` | Date | 服务日期(逐日) |
|
||||
| `assignmentId` | Long(字符串) | 当天逐日派车行 ID(取消/改派按此 ID) |
|
||||
| `used` | String | `USED`=实际用车 / `UNUSED`=显式不用车 / `null`=未编辑 |
|
||||
| `readOnly` / `canDelete` | Boolean | 该 cell 只读 / 可删除(取消)标记 |
|
||||
| `vehicleId` / `driverId` | Long(字符串) | 当天车辆/司机 ID(未派为 null) |
|
||||
| `rescheduleResidue` | Boolean | 是否改期残留旧日期行(true 挂「改期残留/待清理」徽标) |
|
||||
| `vehiclePlate` / `driverName` | String | 当天车牌/司机明文(行冻结快照,未派为 null) |
|
||||
| **`protocolPrice`** | BigDecimal(字符串) | **当天派车价**:`= 该天单日协议价`(chargeable=0→0;缺协议价回退日历参考价,再无则 0)。**改动前=整槽总价平铺到每天,改动后=当天单日价** |
|
||||
|
||||
**前端渲染要点**:逐日车费把每一行 `serviceDate` 的 `protocolPrice` 全部列出(3 天=3 行),最终总车费 = 各行 `protocolPrice` 之和(Σ 口径),不要再取单一行或按 `vehicle_fee_total` 旧值。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 看板「待派车」分面会正确出现被一键清空的需求,可点入重派;「已取消」分面不再混入清空需求。
|
||||
- 确认执行页逐日车费正常显示全部行程天数与正确合计。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测随 PR 自证(一键清空回待派车 + 已取消 tab 口径零回归;逐日费按天拆分 + Σ 校验 + Flyway 回填)。
|
||||
- PR #6029 squash 合并 dev-v3(merge commit bf80a6f2ac),Deploy Panel 部署 hl-fleet-service(jar 15:29 更新、双实例重启生效)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6022
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6029
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/bf80a6f2ac
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6033"
|
||||
title: "住宿安排回配信息回显(hotelGroup.assignments 补全)+ 加急按钮显隐契约 canUrge"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "9bd9e23b"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-18"
|
||||
status_note: "后端完成:PR #6036 已合并 dev-v3(d4288c4941)并部署 TEST(hl-order-service-v3 jar 16:30 更新、双实例重启)。bug1 改走 listActiveHotelAssignmentsByOrderId(INQUIRING∪CONFIRMED)补回显;bug2 新增 hotelGroup.canUrge 显隐契约。2849 测试全绿 + ArchTest 54 绿。网关实测 itinerary 的 assignments 非空 + canUrge=false。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T16:40:00+08:00"
|
||||
---
|
||||
|
||||
# 住宿安排回配信息回显 + 加急按钮显隐契约 canUrge(#6033)
|
||||
|
||||
## 背景
|
||||
|
||||
订单详情-行程安排-住宿安排卡片两个回显 bug(对照用车安排卡片的正确模式):
|
||||
- **bug1**:住宿安排只显示「用房需求」(每晚房型/酒店需求),**不显示具体回配结果**——用车安排卡片是「需求 + 具体回配(丰田普拉多/蒙A/司机/日期)」两段都显示,住宿缺了回配段。
|
||||
- **bug2**:配房已完成,住宿安排卡片右上角**仍显示「⚡加急」按钮**(加急是催房务赶紧配用的,配完还显示不合理)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /v3/admin/order/{id}/itinerary` 的 `hotelGroup.assignments` | **行为变更**。由空数组 `[]` 改为返回每晚回配明细。根因:原走 `listHotelAssignmentsByOrderId`(**CONFIRMED-only** 业务判定语义),配房未完成(INQUIRING 询房候选)时被过滤成空 → 前端只剩需求段。改走 `listActiveHotelAssignmentsByOrderId`(**INQUIRING ∪ CONFIRMED** 纯回显中立只读契约)。其余 8 处 CONFIRMED-only 调用方(核单预览/SignVoucher/结算/TerminateRefund 等)语义零改动 |
|
||||
| `GET /v3/admin/order/{id}/itinerary` 的 `hotelGroup.canUrge` | **新增字段**(Boolean)。加急按钮显隐契约:配房已完成(`finalized=true` / `houseStatus=CONFIRMED`)或已加急(`manualUrgent=true`)时 `false`,否则 `true` |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **bug1 回显**:未完成(INQUIRING 候选)行随 assignments 回显(hotelName/roomTypeName 中文/roomCount/protocolPrice/stayDate/dayNumber),行内 `returnStatusLabel/finalized` 随需求级 houseStatus(未最终确认不显示「已回配」);已完成(CONFIRMED)回显具体回配明细、`finalized=true`、`returnStatusLabel=已回配`。
|
||||
- **bug2 canUrge**:`canUrge = !finalized && !manualUrgent`。前端**不要**再自行拼接状态判显隐,直接用 `canUrge` 控制「⚡加急」按钮显隐;`requirement.manualUrgent` 仍用于显示「已加急」态。
|
||||
|
||||
## 响应示例
|
||||
|
||||
`GET /v3/admin/order/{id}/itinerary` → `data.hotelGroup`(修复后):
|
||||
|
||||
```json
|
||||
{
|
||||
"requirement": { "requirementId": "2089598371034505218", "status": "DONE", "totalRoomCount": 2, "manualUrgent": false },
|
||||
"houseStatus": "CONFIRMED",
|
||||
"houseStatusLabel": "已完成",
|
||||
"finalized": true,
|
||||
"returnStatusLabel": "已回配",
|
||||
"canUrge": false,
|
||||
"assignments": [
|
||||
{
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"roomTypeName": "标准间",
|
||||
"roomCount": 1,
|
||||
"protocolPrice": "280.00",
|
||||
"stayDate": "2026-08-18",
|
||||
"dayNumber": 1,
|
||||
"returnStatusLabel": "已回配",
|
||||
"finalized": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `assignments[].hotelName` | String | 回配酒店名 |
|
||||
| `assignments[].roomTypeName` | String | 房型中文名(字典翻译,无则 null) |
|
||||
| `assignments[].roomCount` | Integer | 房间数 |
|
||||
| `assignments[].protocolPrice` | BigDecimal(字符串) | 协议价 |
|
||||
| `assignments[].stayDate` | Date | 入住日 |
|
||||
| `assignments[].dayNumber` | Integer | 第几晚 |
|
||||
| `assignments[].finalized` / `returnStatusLabel` | Boolean/String | 随需求级状态(完成=已回配/finalized=true) |
|
||||
| **`canUrge`** | Boolean | **加急按钮显隐**:false=隐藏(配完/已加急),true=显示可用(未完成且未加急) |
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 住宿安排卡片把 `assignments` 每晚回配明细渲染出来(对齐用车卡片「需求 + 具体回配」两段模式)。
|
||||
- 「⚡加急」按钮显隐直接用 `hotelGroup.canUrge`,不要自行拼接 `houseStatus/finalized/manualUrgent`。
|
||||
- 用车安排(vehicleGroup)回显无变化。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测:OrderDetailServiceTest +1(INQUIRING 候选传入 converter)、OrderDetailConverterTest +1(canUrge:CONFIRMED→false / 未加急→true / 已加急→false);order.core 全量回归 2849 绿 + ArchTest 54 绿(含 HouseModuleBoundaryArchTest)。
|
||||
- 部署:PR #6036 squash 合并 dev-v3(merge commit d4288c4941),Deploy Panel 部署 hl-order-service-v3(jar 16:30 更新、双实例重启生效)。
|
||||
- 网关实测(TEST):itinerary 的 `hotelGroup.assignments` 由空变 2 条(呼伦贝尔香格里拉大酒店 + stayDate 8-18/19 + roomCount + 已回配 + finalized=true),`canUrge=false`。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6033
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6036
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/d4288c4941
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6045"
|
||||
title: "确认执行页行程短信按槽位独立选择(groups[].sendItinerarySms)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "e94c0333"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-18"
|
||||
status_note: "后端完成:PR #6049 已合并 dev-v3(4f32480f1)并部署 TEST(hl-fleet-service 双实例 18:27 重启、jar 18:27:32 更新)。后端 confirmRequirement 自 #5827 起已按槽位落地(groups[].sendItinerarySms 逐组独立创建/跳过 ITINERARY_SMS 事件),本次 #6045 正式补录 §8.3a 契约 + 测试补强(混合决策逐组事件断言 + 决策缺失 605056 拦截)。网关实测:需求级 confirm 带 null sendItinerarySms → 400「请选择是否向本段司机发送行程短信」(按槽位必填校验生效)。前端需按槽位渲染选择器、全部槽位选定后才可提交。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T11:20:00+08:00"
|
||||
---
|
||||
|
||||
# 确认执行页行程短信按槽位独立选择
|
||||
|
||||
> 前端契约交接(#6045)。后端能力已具备(#5827 起按槽位),前端需适配 UI 与提交结构。
|
||||
|
||||
## 变更接口
|
||||
|
||||
**需求级原子确认**:`POST /admin/fleet/assignments/requirements/{requirementId}/confirm`
|
||||
|
||||
(整组确认执行页当前已调用;§8.3a 契约详见 API-SPEC-FLEET-V1.5)
|
||||
|
||||
## 入参变更(关键字段)
|
||||
|
||||
`groups[]` 逐槽位携带 `sendItinerarySms`(布尔,**必填**):
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "1934567890123456789",
|
||||
"requestId": "fleet-confirm-20260815-001",
|
||||
"expectedRequirementVersion": 3,
|
||||
"expectedRequirementSha256": "<Board 返回的 64 位 hex>",
|
||||
"expectedPlanGeneration": 1024,
|
||||
"groups": [
|
||||
{ "assignmentGroupId": "<执行段1 ID>", "sendItinerarySms": true },
|
||||
{ "assignmentGroupId": "<执行段2 ID>", "sendItinerarySms": false }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 前端要求
|
||||
|
||||
1. 「行程短信」区块按执行段(槽位)逐个渲染「发送短信 / 不发送短信」选择器,**逐槽位独立**提交 `groups[].sendItinerarySms`。
|
||||
2. 可部分槽位发、部分槽位不发(槽位 1 发、槽位 2 不发可正常提交)。
|
||||
3. **全部槽位都选定后才允许点「确认订单执行」**;任一槽位未选禁用按钮并提示(后端 605056「执行段集合已变化,请刷新后重试」兜底)。
|
||||
4. 「不发送短信」文案口径沿用:「仅不发送确认后的行程短信,派单通知短信仍会发送」。
|
||||
5. 单槽位订单退化为单选,行为与现状一致。
|
||||
6. 出参 `groups[]` 逐组返回 `sendItinerarySms`(可回显已选状态)。
|
||||
|
||||
## 错误码(新增感知)
|
||||
|
||||
- 605056:执行段集合已变化或槽位短信决策缺失(刷新后重试,不可静默重发同 requestId 旧结构)
|
||||
|
||||
## 范围
|
||||
|
||||
- 只影响确认后的行程短信;派单通知短信(ASSIGNED 通知中心)不受影响。
|
||||
- 单组确认 `POST /admin/fleet/assignments/{assignmentId}/confirm` 入参 `sendItinerarySms`(单布尔)不变,单槽位退化路径继续使用。
|
||||
|
||||
|
||||
## 验证证据
|
||||
|
||||
- **PR**: #6049 squash 合并 dev-v3 → 4f32480f1(head 分支已删)
|
||||
- **部署**: Deploy Panel task 9f3d36e9 success;SSH 核实 hl-fleet-service 双实例 18:27 重启(PID 4005261@8187 / 4006125@8087),jar 时间戳 2026-08-18 18:27:32,日志 Started FleetServiceApplication in 5.547s 无 ERROR
|
||||
- **网关实测**(api.test.1814.love:9443,admin 登录):
|
||||
- `GET /admin/fleet/board/orders` → 200(fleet 服务在线)
|
||||
- `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 带 `groups[].sendItinerarySms=null` → code 400「请选择是否向本段司机发送行程短信」——按槽位必选校验真实生效
|
||||
- **测试**: AssignmentServiceTest 509 绿(含 #6045 新增 2 用例:两槽位混合决策逐组事件断言、决策缺失 605056 拦截)+ AssignmentControllerTest 43 绿 + FleetRedLineArchTest 13 绿 + spotless:check 绿
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6045
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6049
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/4f32480f1b
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6052"
|
||||
title: "整程行改期换版 stale 详情回拨成待派车(详情 activeAssignments/vehicleSlots 补 stale 覆写)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-19"
|
||||
status_note: "后端完成:PR #6053 已合并 dev-v3(a433bfb7fa)并部署 TEST(hl-fleet-service jar 21:07、双实例 21:08 重启)。根因=C:详情组装路径(activeAssignments/vehicleSlots)不经 stale 覆写——#6014 只修了列表卡片,详情未覆盖;本单给详情两组装点补 stale 覆写(逐日/全程同判据)。board 域 238 全绿 + ArchTest 13 + spotless。网关实测复现单详情 activeAssignments/vehicleSlots 回拨 unassigned。"
|
||||
updated_at: "2026-08-18"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-18T21:15:00+08:00"
|
||||
---
|
||||
|
||||
# 整程行改期换版 stale 详情回拨成待派车(#6052)
|
||||
|
||||
## 背景
|
||||
|
||||
#6014 修了「改期换版后纯改期残留回拨成待派车」,但只覆盖了**看板列表卡片**(`GET /admin/fleet/board/orders` 的 `assignmentStatus`)。**订单详情接口**(`GET /admin/fleet/board/orders/{orderId}`)的 `activeAssignments` / `vehicleSlots` 是**另一条组装路径,没走 stale 覆写**——整程行(全程槽,service_date=NULL)改期换版后,详情接口仍显示「已派车(assigned)」,与列表卡片/订单侧「待配」分叉。
|
||||
|
||||
## 根因
|
||||
|
||||
详情组装(`activeAssignments` / `vehicleSlots`)只用落库状态走状态派生,**没有应用「陈旧定稿(stale)覆写」**;stale 覆写此前只存在于列表卡片与早筛。逐日切片行与整程行在详情路径共用同一断点。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` 的 `activeAssignments[]` | **行为变更**。该需求存在陈旧定稿(`plan_finalized_requirement_id` ≠ 当前需求 ID)且可进配车流程时,`assignmentStatus` 由落库态回拨为「待派车(unassigned)」;`baseAssignmentStatus` 保留落库态(如 assigned),`availableActionCodes` 含 `ASSIGN` |
|
||||
| `GET /admin/fleet/board/orders/{orderId}` 的 `vehicleSlots[]` | **行为变更**。同上口径,`slotStatus` 回拨 unassigned;`rescheduleResidue` 徽标、`canDelete`(取消/改派入口)保留 |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **状态回拨**:详情 `activeAssignments[].assignmentStatus` / `vehicleSlots[].slotStatus` 在「陈旧定稿 + 可进配车流程」时回拨成待派车,与列表卡片、订单侧对齐。
|
||||
- **落库不改**:回拨只改读侧派生,`baseAssignmentStatus` 保留落库态;旧派车**不自动清除**(残留徽标 `rescheduleResidue`、取消/改派入口 `canDelete` 全保留,由车务手动清理)。
|
||||
- **缺口不虚高**:列表缺口进度纯改期残留分流(unassigned=0)未动。
|
||||
- **当代定稿不误伤**:`plan_finalized_requirement_id` = 当前需求 ID 时保持 assigned 不回拨。
|
||||
- **V2 新槽机制**:#5810 槽位人工制——平移行保持人工现状不自动补槽,车务取消旧行后按新日期重派(同逐日切片机制)。
|
||||
|
||||
## 响应示例
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}` → `data.activeAssignments[]`(修复后,整程行 stale 场景):
|
||||
|
||||
```json
|
||||
{
|
||||
"assignmentId": null,
|
||||
"serviceDate": null,
|
||||
"assignmentStatus": "unassigned",
|
||||
"baseAssignmentStatus": "assigned",
|
||||
"rescheduleResidue": true,
|
||||
"canDelete": true,
|
||||
"availableActionCodes": ["ASSIGN", "REJECT_REQUIREMENT"]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `assignmentStatus` | String | **回拨后状态**:陈旧定稿可进配车流程时 = `unassigned`(待派车) |
|
||||
| `baseAssignmentStatus` | String | 落库真实状态(如 `assigned`),不受回拨影响 |
|
||||
| `rescheduleResidue` | Boolean | 是否改期残留旧日期行(true 挂「改期残留/待清理」徽标) |
|
||||
| `canDelete` | Boolean | 是否可删除/取消(残留行恒 true,车务手动清理入口) |
|
||||
| `availableActionCodes` | String[] | 可执行动作(回拨成待派车后含 `ASSIGN`) |
|
||||
|
||||
`vehicleSlots[]` 同口径:`slotStatus` 回拨 unassigned,`rescheduleResidue` / `canDelete` 保留。
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 详情页 `activeAssignments` / `vehicleSlots` 直接消费 `assignmentStatus` / `slotStatus`(已是回拨后状态),`baseAssignmentStatus` 可用于显示落库真实态。
|
||||
- 残留行 `rescheduleResidue=true` 时挂「改期残留」徽标,`canDelete=true` 时保留取消/改派入口。
|
||||
- 列表卡片(#6014)行为不变。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测:BoardOrderServiceTest +2(整程行 stale 回拨 unassigned + baseAssignmentStatus=assigned + 车辆信息保留;当代定稿保持 assigned 防误伤);board 域 238 全绿 + ArchTest 13 + spotless。
|
||||
- 部署:PR #6053 squash 合并 dev-v3(merge commit a433bfb7fa),Deploy Panel task c87ac963 success(jar 21:07、双实例 21:08 重启)。
|
||||
- 网关实测(TEST):复现单 `2089664548595240962`(改期 V1→V2,整程行 plan_finalized_requirement_id=V1)→ 详情 `activeAssignments[0].assignmentStatus=unassigned`(待派车)、`baseAssignmentStatus=assigned`、`vehicleSlots[0].slotStatus=unassigned`、`rescheduleResidue=true`、`canDelete=true`。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6052
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6053
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/a433bfb7fa
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6057"
|
||||
title: "多槽位越需求索引派车业务码 605911/605912 替代 500(required_vehicle_type 落 NULL 防御)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-08-19"
|
||||
status_note: "后端完成:PR #6064 + #6065 已合并 dev-v3(584f3adc3 / b07855c228)并部署 TEST(hl-fleet-service Deploy task d6cb4687 + 3bed5be8,jar 01:27:55、双实例 01:28 重启)。越界索引由 500(vehicleType is required)收敛为 605911 业务码;快照车型兜底缺失拦成 605912。错误消息自解释,前端全局错误处理可直接展示,无需适配;派车看板正常只发送已声明/已增槽槽位,行为不变。"
|
||||
updated_at: "2026-08-19"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-19T01:32:00+08:00"
|
||||
---
|
||||
|
||||
# 多槽位越需求索引派车业务码 605911/605912 替代 500(#6057)
|
||||
|
||||
## 背景
|
||||
|
||||
批量派车(车务最终实派方案)`fleetDecisionFinal` 放行的 `fleetItemIndex` 可能越出当前需求 `fleet[]` 声明的车辆总数(如需求声明 1 辆车却按 index=1 派第 2 辆)。该场景此前 `resolveFleetItemByIndex` 返 null、无占位可消费走拉单组装,`required_vehicle_type` 落 NULL(DAILY_V3 快照契约 required 字段),确认派车阶段快照校验炸 `vehicleType is required` **500**(P1 复现单 R2)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `POST /admin/fleet/assignments/batch` 的 `items[]` | **行为变更**。`fleetItemIndex` 越出当前需求声明车辆数且该索引无 addSlot/展开占位可消费时,整批 fail-fast 返回 **605911**,零写入;有占位(经 `POST /admin/fleet/assignments/slots` 增槽或展开生成)的越界索引不受影响,正常消费占位车型快照派车 |
|
||||
| `POST /admin/fleet/assignments/batch`(防御) | 需求项定位不到且所选车辆型号归一不出规范车型时,落库前拦 **605912**,不再留 NULL 车型脏行到确认派车阶段炸 500 |
|
||||
|
||||
## 新增错误码(fleet 605xxx 段)
|
||||
|
||||
| 错误码 | 消息 | 触发条件 | 前端处置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `605911` | 用车条目索引越界:超出当前需求声明的车辆数 | `fleetItemIndex` ≥ 需求 `fleet[]` 全部 count 之和且该索引无占位 | 引导车务调整需求条目数或先经增槽(addSlot)再派;消息自解释可直接 toast |
|
||||
| `605912` | 派车快照缺少需求车型,请检查用车需求车型明细 | 需求项定位不到且车辆型号无法归一(罕见,防御性) | 检查用车需求车型明细 |
|
||||
|
||||
## 行为口径
|
||||
|
||||
- **合法索引不变**:`index < 声明车辆数`(含 count-1 边界)与占位消费(增槽/展开)路径 200 正常,落库 `required_vehicle_type` 非空。
|
||||
- **整批原子**:越界索引在锁内拉单组装前 fail-fast,任一项失败整批回滚、零写入。
|
||||
- **对照一致**:需求声明条目数调整到位后同一请求 200(复现单 R2 实测验证)。
|
||||
- 接口说明(swagger notes)已同步新口径:多派/新增槽位须先经 addSlot 生成占位。
|
||||
|
||||
## 前端动作
|
||||
|
||||
无必须动作:错误消息自解释,走全局错误提示即可。派车看板正常只发送已声明/已增槽的槽位索引,交互不变。若后续做 605911 专属引导文案,可按上表触发条件提示「调整需求车辆数或先增槽」。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测(AssignmentServiceTest 513 全绿):越界索引批派 605911 拦截且 `insert` 零写入;`index=count-1` 命中/`index=count` 越界边界;占位消费的新增槽位仍成功;车型无法归一 605912 防御。`mvn -pl hl-fleet-service -am verify` 3838 测试仅剩 1 个 `ReleaseEMixedBinaryHarnessTest` Windows 环境抖动失败(基线 dev-v3 @ b93c7ddcc 同款失败、单跑通过);Testcontainers 集成测试 12 条通过。
|
||||
- 部署:PR #6064 squash 合并 dev-v3(merge commit `584f3adc3`)+ 文档对齐 PR #6065(`b07855c228`);Deploy Panel task `d6cb4687` + `3bed5be8` success,测试服 jar mtime 2026-08-19 01:27:54(110669169 bytes),双实例 8087/8187 滚动重启健康。
|
||||
- 网关实测(TEST,admin token):复现单 R2 `HL20260818215924074`(需求声明 suv×1)批量派车 `fleetItemIndex=1` → **605911**(非 500);复现单 R1 `HL20260818215914552` 同场景 → **605911**;同单合法索引 `fleetItemIndex=0` → 200 assigned,落库 `required_vehicle_type='suv'` 非空、越界请求零写入(DB 核对仅 2 行合法行)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6057
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6064 (主修复)+ https://git.1814.love:8443/wx/HL/pulls/6065 (接口说明对齐)
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/584f3adc32e530389a994d92f3a002c312381632
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6077"
|
||||
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 #6080 已合并 dev-v3(39c489673)并部署 TEST(hl-order-service-v3 双实例 8086/8186,2026-08-19 15:02 重启)。订单详情响应新增最后操作房务/车务人员 ID 与姓名共 4 个可选只读字段,无操作/无对应需求时为 null,前端按需取读即可、不展示无影响。"
|
||||
updated_at: "2026-08-19"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-19T15:22:40+08:00"
|
||||
---
|
||||
|
||||
# 订单详情显示最后操作的房务/车务人员(#6077)
|
||||
|
||||
## 背景
|
||||
|
||||
订单详情页需要看到该订单最后一笔配房、派车分别由哪位房务/车务人员操作,便于追溯责任。取 `order_hotel_requirement` / `order_vehicle_requirement` 的 `updated_by`,经 `admin_user` 解析为姓名。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /v3/admin/order/{id}`(订单详情,9 Tab 聚合) | **响应新增字段**(均可选只读,无操作/无对应需求时为 null,不影响既有任何字段):`houseOperatorId`(Long,顶层,最后操作房务人员 ID)、`vehicleOperatorId`(Long,顶层,最后操作车务人员 ID)、`houseOperatorName`(String,顶层 + `overview.customerInfo` 内均有,最后操作房务人员姓名)、`vehicleOperatorName`(String,顶层 + `overview.customerInfo` 内均有,最后操作车务人员姓名) |
|
||||
|
||||
## 响应示例
|
||||
|
||||
`GET /v3/admin/order/{id}` 返回(仅列本次新增字段,其余字段省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"houseOperatorId": null,
|
||||
"vehicleOperatorId": null,
|
||||
"houseOperatorName": null,
|
||||
"vehicleOperatorName": null,
|
||||
"overview": {
|
||||
"customerInfo": {
|
||||
"houseOperatorName": "王骁",
|
||||
"vehicleOperatorName": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:该单已配房未派车,故 `overview.customerInfo.houseOperatorName="王骁"` 有值、`vehicleOperatorName=null`;顶层 4 字段当前实现为 null(以 `overview.customerInfo` 内同名字段为准),前端读 `overview.customerInfo.houseOperatorName` / `vehicleOperatorName` 即可。
|
||||
|
||||
## 行为口径
|
||||
|
||||
- 4 个字段均为**只读、可选**:该订单无对应用车/用房需求或尚无操作时值为 null,接口不报错。
|
||||
- 纯新增字段,不影响既有任何字段的取值与结构。
|
||||
- 姓名来源为管理后台用户表,按最后操作人 ID 解析;解析不到时同样为 null。
|
||||
|
||||
## 前端动作
|
||||
|
||||
无必须动作。需要展示「最后操作房务/车务」时读 `overview.customerInfo.houseOperatorName` / `vehicleOperatorName`(或顶层同名字段)即可,为 null 时前端自行决定隐藏或显示「-」。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测新增 113 行覆盖(正常有值 / 无需求为 null)。
|
||||
- 部署:PR #6080 squash 合并 dev-v3(merge commit `39c489673`);测试服 hl-order-service-v3 双实例 8086/8186 于 2026-08-19 15:02 重启。
|
||||
- 网关实测(TEST,admin token):`GET /v3/admin/order/2089928715696746498` 返 200,`overview.customerInfo.houseOperatorName="王骁"`(该单已配房),`vehicleOperatorName=null`(该单未派车),符合「有操作给姓名 / 无操作为 null」口径。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6077
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6080
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6079"
|
||||
title: "终止行程按勾选「未用」行释放房车资源——金额/释放双通道解耦,替代按日期截断"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "7ce54680"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-19"
|
||||
status_note: "后端完成:PR #6083 已合并 dev-v3(6c30bc50c9d1)并部署 TEST(hl-order-service-v3 双实例 16:24 滚动重启成功)。测试服端到端实测两单通过(勾未用释放/无释放行兼容)。目标单测 556 + ArchTest 44 + IT 101 全绿。"
|
||||
updated_at: "2026-08-19"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-19T16:50:00+08:00"
|
||||
---
|
||||
|
||||
# 终止行程按勾选「未用」行释放房车资源(#6079)
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **以前**:终止行程的房车资源释放按 `endDayNumber` 日期截断——车侧把「出发日+(endDayNumber-1)」及之后的派单整段取消,房侧自动释放该日及之后的未确认候选库存。
|
||||
- **现在**:释放完全由前端**逐行勾选的「未用」项**驱动。请求新增可选字段 `releaseLines`(与金额用的 `lineUsages` 完全独立、互不影响):
|
||||
- **车**:`releaseLines` 里 `used=false` 的车行把 fleet 截断边界**提前到最早勾选未用车行的服务日**(fleet 协议上终止日终身唯一、做不到真正逐行,勾选只能把边界提前——请按「尾部连续段」勾选未用天)。不传/空 → 仍按 `endDayNumber` 日期截断(兼容)。
|
||||
- **房**:`releaseLines` 里 `used=false` 的房行按「哪晚哪间」精确释放未确认(INQUIRING)候选库存;**已确认(CONFIRMED)的房行即使勾了未用也不自动删**,由房务在异常桶手动处置。不传/空 → 不自动释放任何候选(旧按日期整段释放已下线)。
|
||||
- **退款金额不变**:仍只按 `lineUsages[].used` 权威重算,与 `releaseLines` 无关;车行勾「未用」**不影响退款**(车辆退款按服务日权威判定),只影响资源释放。
|
||||
|
||||
## 变更接口
|
||||
|
||||
### `POST /v3/admin/order/{id}/terminate` 终止行程(出行中)
|
||||
|
||||
请求体新增可选字段(其余字段不变):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `releaseLines` | `List<LineUsageItem>` | ❌ | 资源释放行。只取 `used=false` 且 lineKey 前缀为 `VEHICLE_ASSIGNMENT:`/`HOTEL_ASSIGNMENT:` 的行驱动释放;其它来源行忽略。与 `lineUsages` 不做一致性比对。`lineKey` 取值与退款预览 `refundLines[].lineKey` 一致 |
|
||||
|
||||
`LineUsageItem` 结构:`{ "lineKey": "VEHICLE_ASSIGNMENT:348303531385688064:2", "used": false }`。
|
||||
|
||||
请求示例(勾第 2 天车未用 + 第 2 晚房未住):
|
||||
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户高反送医终止",
|
||||
"endDayNumber": 2,
|
||||
"lineUsages": [
|
||||
{"lineKey": "HOTEL_ASSIGNMENT:2089913001904680962:1", "used": true},
|
||||
{"lineKey": "HOTEL_ASSIGNMENT:2089913001917263874:2", "used": false}
|
||||
],
|
||||
"releaseLines": [
|
||||
{"lineKey": "VEHICLE_ASSIGNMENT:348303531385688064:2", "used": false},
|
||||
{"lineKey": "HOTEL_ASSIGNMENT:2089913001917263874:2", "used": false}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 前端动作
|
||||
|
||||
- 终止弹窗里「逐行勾选已用/未用」的房车行,**未用行同时放进 `lineUsages`(used=false,驱动退款)和 `releaseLines`(used=false,驱动释放)**;两个数组都要传,职责不同。
|
||||
- 车辆未用天请按**尾部连续段**勾选(如 3 天行程勾第 2、3 天未用);断档勾法(勾第 2 天不勾第 3 天)会把第 3 天也一并截断释放(fleet 协议限制,terminateDate 只能取最早未用日)。
|
||||
- 房行勾「未住」的已确认(CONFIRMED)行不会自动释放——房务会在「异常桶」看到该终止需求 + REFUND 待办,手动取消未入住部分。
|
||||
|
||||
## 验证证据(测试服实测)
|
||||
|
||||
- 单测:目标套件 9 类 556 用例全绿(新增 TerminateReleasePlanParserTest 6 用例 + OrderServiceTest 真提前场景/事件透传/兼容回退 + HouseTodo/HouseAssignment 按行释放与 CONFIRMED 跳过计数);ArchTest 44 绿;IT 101 跑 0 失败。CR Workflow 25 agents 对抗复核确认 2 个 P2 已收口。
|
||||
- 实测 1(订单 HL20260819110439216,3 天行程当前第 2 天):`endDayNumber=2` + `releaseLines` 勾车 day2/房 day2 未用 → 退款 6504 按 lineUsages 独立重算;fleet outbox `terminateDate=2026-08-19`(=day2 服务日,**边界提前生效**),day2/day3 派单 canceled、day1 保留 assigned;房侧需求转 EXCEPTION + 产 REFUND 待办,勾的 CONFIRMED 房行保留未删(`skippedConfirmed=1`,符合设计)。
|
||||
- 实测 2(订单 HL20260819110313216,不传 releaseLines):fleet 按 `endDayNumber=2` 日期截断(兼容路径),day2/day3 canceled;房侧不自动释放候选、需求转 EXCEPTION + REFUND 待办。
|
||||
- 部署:PR #6083 squash 合并 dev-v3(merge 6c30bc50c9d1),Deploy Panel 滚动部署双实例 UP(8186/8086 均 16:24 重启健康)。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6079
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6083
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/6c30bc50c9d1
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6077"
|
||||
title: "订单详情最后操作房务/车务人员 + 住宿每晚日期回显(二次告知)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a684caf2"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-20"
|
||||
status_note: "二次告知:操作人字段(6077)早已部署测试服并网关实测,前端似乎未接;住宿每晚日期 stayDate 也确认已在返回。前端按本文取值即可。"
|
||||
updated_at: "2026-08-20"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-20T09:00:00+08:00"
|
||||
---
|
||||
|
||||
# 订单详情最后操作人 + 住宿每晚日期回显(二次告知)
|
||||
|
||||
## 背景
|
||||
|
||||
前端反馈:①订单详情页没看到「最后操作的房务/车务人员」;②住宿安排「第一晚没有具体时间」。
|
||||
|
||||
核实结论:**两块数据后端都已在返回**,是前端未接/未展示。本文把取值位置一次性写清。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /v3/admin/order/{id}`(订单详情) | 无结构变更。`data.overview.customerInfo.houseOperatorName` / `vehicleOperatorName` 已在返回(#6077 交付),本文再次告知取值位置 |
|
||||
| `GET /v3/admin/order/{id}/itinerary`(行程安排) | 无结构变更。`data.hotelGroup.assignments[].stayDate`(每晚日历日期)已在返回,本文再次告知取值位置 |
|
||||
|
||||
## 一、最后操作房务/车务人员(#6077,已部署测试服)
|
||||
|
||||
接口:`GET /v3/admin/order/{id}`(订单详情)
|
||||
|
||||
读这里(已实测有值):
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"overview": {
|
||||
"customerInfo": {
|
||||
"houseOperatorName": "王骁",
|
||||
"vehicleOperatorName": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `data.overview.customerInfo.houseOperatorName` — 最后操作房务人员姓名(无操作为 null)
|
||||
- `data.overview.customerInfo.vehicleOperatorName` — 最后操作车务人员姓名(无操作为 null)
|
||||
- 顶层 `data.houseOperatorName` 等同名字段当前为 null,**以 `overview.customerInfo` 内为准**。
|
||||
- 为 null 时前端自行决定隐藏或显示「-」。
|
||||
|
||||
## 二、住宿安排每晚日期(第一晚具体时间)
|
||||
|
||||
接口:`GET /v3/admin/order/{id}/itinerary` → `data.hotelGroup.assignments[]`
|
||||
|
||||
每条回配酒店都有 `stayDate`(该晚日历日期)+ `dayNumber`(第几晚),实测:
|
||||
|
||||
```json
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"stayDate": "2026-08-19",
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"roomType": "BIG_BED",
|
||||
"roomCount": 1
|
||||
}
|
||||
```
|
||||
|
||||
出发日 2026-08-19 → 第一晚 `stayDate=2026-08-19`、第二晚 `2026-08-20`,已对齐。前端在住宿安排卡片每行渲染 `stayDate` 即可。
|
||||
|
||||
注意:只有**已配房**的订单 `assignments` 才有数据;未配房(需求阶段)为空数组属正常。
|
||||
|
||||
## 前端动作
|
||||
|
||||
1. 操作人:订单详情页读 `data.overview.customerInfo.houseOperatorName` / `vehicleOperatorName` 展示。
|
||||
2. 每晚日期:住宿安排卡片每行展示 `assignments[].stayDate`。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 6077:PR #6080 已合并 dev-v3(merge commit 39c489673),测试服双实例 2026-08-19 15:02 重启;网关实测 `overview.customerInfo.houseOperatorName="王骁"` 有值。
|
||||
- stayDate:2026-08-20 测试服网关实测订单 2090087196236095490,assignments 两条 stayDate=2026-08-19/2026-08-20 正确。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue #6077:https://git.1814.love:8443/wx/HL/issues/6077
|
||||
- 后端负责人:wx
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6107"
|
||||
title: "终止行程后已取消派单「释放车辆/司机占用」入口(一键清除占用端点告知)"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "2ebfddc7"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-20"
|
||||
status_note: "端点 POST /admin/fleet/assignments/clear-cancelled-occupancy 由 #5921(关联 #5913/#5828)交付并部署 TEST,但当初未单独发 changelog,前端不知其存在,导致「终止行程后已取消派单仍占着车/司机、无释放按钮」。本文补告知取值与 UI 挂载点。后端本次无代码变更。"
|
||||
updated_at: "2026-08-20"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-20T00:00:00+08:00"
|
||||
---
|
||||
|
||||
# 终止行程后已取消派单「释放车辆/司机占用」入口(#6107)
|
||||
|
||||
## 背景
|
||||
|
||||
订单**中止行程**后,订单侧已把对应车务派单截断为 `canceled`,看板正确显示「已取消」。
|
||||
|
||||
但「已取消」只是把派单行冻结,**该行原本锁住的车辆/司机资源占用(脏 busy)不会自动全部归还**——需要车务同事手动触发一次「释放占用」。此前前端不知道后端已有这个释放端点,导致看板详情里看不到任何释放按钮,资源一直被冻结,影响后续派车。
|
||||
|
||||
**本文一次性告知:释放占用的后端能力早已上线,前端只需补挂一个按钮。**
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `POST /admin/fleet/assignments/clear-cancelled-occupancy` | **已上线,本次补告知**。一键圈定某订单/需求下全部已取消(canceled)派单的车辆/司机,清掉它们的脏占用(合法 busy 不动)。**只清占用,不修改派单任何字段** |
|
||||
| `POST /admin/fleet/assignments/{assignmentId}/soft-clear-assignment` | 已上线(#5936)。单条软清备用;本单默认用上面的整单一键清 |
|
||||
|
||||
## 一、调用方式(clear-cancelled-occupancy)
|
||||
|
||||
**入参**(body):
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "1934567890123456789",
|
||||
"requestId": "clear-cancel-20260820-001"
|
||||
}
|
||||
```
|
||||
|
||||
- `orderId` 与 `requirementId` **二选一**(都填/都不填返 100001)。车务看板详情页天然有 `orderId`,用它即可。
|
||||
- `requestId` **必填**,幂等标识,≤64 字符,前端生成(如 `clear-cancel-` + 时间戳)。
|
||||
|
||||
**返回**(data):
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleDriverPairs": 2,
|
||||
"intentsWritten": 2,
|
||||
"assignmentIds": ["1934567890123456701"]
|
||||
}
|
||||
```
|
||||
|
||||
- `assignmentIds` 为本次实际圈到的已取消派单 ID 列表(雪花按字符串返回)。
|
||||
- `intentsWritten=0` 表示没有可清的脏占用(幂等安全,可视为成功)。
|
||||
|
||||
**错误码**:`100001`(orderId 与 requirementId 二选一)/ `605047`(行程已结束)/ `605054`(终止冻结中,稍后重试)/ `605600`(已关账)。
|
||||
|
||||
## 二、前端动作(本次唯一要改的地方)
|
||||
|
||||
在**车务看板 → 订单详情抽屉 → 「车辆执行段」列表**补一个入口:
|
||||
|
||||
1. **可见条件**:`GET /admin/fleet/board/orders/{orderId}` 返回的 `activeAssignments[]` / `vehicleSlots[]` 里存在 `assignmentStatus === "canceled"` 的行,且该行 `assignmentId` 非空时,显示「释放车辆/司机占用」按钮(或文字链)。
|
||||
2. **点击**:弹确认框(文案建议「将释放该订单下已取消派单占用的车辆/司机,是否继续?」),确认后调 `clear-cancelled-occupancy`,`orderId` 取当前订单、`requestId` 前端生成。
|
||||
3. **成功后**:提示成功并**重新拉取详情**(占用释放是异步 outbox 反算,稍候在矩阵/资源日历可见车、司机恢复 idle)。
|
||||
4. **非 canceled 行不展示该入口**;`holding`/`assigned` 仍走既有「清空司机/车辆」(#5936),语义不同,别混用。
|
||||
|
||||
## 三、取值字段(详情接口已返回,无需后端改)
|
||||
|
||||
`GET /admin/fleet/board/orders/{orderId}`(BoardOrderDetailVO)已含:
|
||||
- `vehicleSlots[].assignmentStatus` / `assignmentStatusLabel`(canceled = 已取消)
|
||||
- `vehicleSlots[].assignmentId`、`assignmentSlotId`、`vehicleId`、`driverId`
|
||||
- `activeAssignments[].assignmentStatus` / `assignmentId`
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 后端 `ClearCancelledOccupancy` 单测 7 例随 #5921 交付即绿;端点已在 TEST 部署。
|
||||
- 全量 API 测试(2026-08-20,232 用例)复核:终止行程后 fleet assignment 正确置 `canceled`,仅缺前端释放按钮——纯前端缺口。
|
||||
- 网关(TEST 9443,真 SUPER_ADMIN token)实测本端点:不存在 orderId 幂等返回 `200 {vehicleDriverPairs:0, intentsWritten:0, assignmentIds:[]}`,路由可达、结构符合 RespVO 契约。
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6111"
|
||||
title: "终止行程(异常)订单配房组「最终确认/清空配房」禁用(标记完成保留)——纯前端渲染逻辑告知"
|
||||
consumer: "admin"
|
||||
author: "wx"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "a586343b"
|
||||
target_release: ""
|
||||
verified_at: "2026-08-20"
|
||||
status_note: "订单终止行程后配房组 houseStatus=EXCEPTION。本文告知前端:此类订单禁用「最终确认」「清空配房」两按钮;但「标记完成」**保留可点**——它是 #4307 专门设计的异常单收尾出口(仅 EXCEPTION 时显示,点它把异常单收尾进「已完成」),禁了会失去异常收尾能力。后端契约字段早已返回,本次无后端代码变更,纯前端渲染/禁用逻辑。本文为上版(误含标记完成)的勘误版。"
|
||||
updated_at: "2026-08-20"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-20T00:00:00+08:00"
|
||||
---
|
||||
|
||||
# 终止行程(异常)订单配房按钮处理(#6111 子项 · 勘误版)
|
||||
|
||||
> ⚠️ **勘误**:上一版本要求对异常订单禁用「标记完成」——**错误**。前端反馈并经后端核实:「标记完成」是工单 **#4307** 专门设计的**异常单收尾出口**,`仅当前 houseStatus=EXCEPTION 时可标记完成`(否则 808022),点它把异常单收尾推进到「已完成」。** EXCEPTION 时恰恰要保留它可点**,禁了就没有异常收尾出口了。本版更正。
|
||||
|
||||
## 背景
|
||||
|
||||
订单**中止行程**后,房务侧把配房组翻为异常(`houseStatus=EXCEPTION`),产生退订待办。此时该订单配房处于「异常/待处理退订」状态,**正常配房流程的动作**(最终确认、清空配房)对异常单无意义、易致状态错乱,应禁用。
|
||||
|
||||
但「**标记完成**」是异常桶的**处置完成出口**——房务把退订等异常处置完后,点它把异常单关掉(EXCEPTION → CONFIRMED/已完成)。**这个必须保留**。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /v3/admin/order/{orderId}/detail`(订单详情) | **无契约变更**。`itinerary[].hotelGroup.houseStatus` / `actions` 字段早已返回,本文仅告知前端禁用逻辑 |
|
||||
|
||||
## 一、判断依据(契约字段,已返回)
|
||||
|
||||
订单详情 `itinerary[].hotelGroup`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `houseStatus` | string | 房务状态机值。**终止行程后 = `EXCEPTION`(异常)** |
|
||||
|
||||
## 二、前端渲染要求(`houseStatus === "EXCEPTION"` 时)
|
||||
|
||||
| 按钮 | 处理 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| **最终确认** | **禁用** | 异常单不再走正常最终确认流程。建议置灰 + tooltip「订单已终止行程,配房异常待处理」 |
|
||||
| **清空配房** | **禁用** | 异常单的退订/释放走行级释放(#6111 另一子项)或异常处置,不再整体清空 |
|
||||
| **标记完成** | **保留可点** | ⚠️ #4307 异常收尾出口,**禁用即丢失异常收尾能力**。维持现状 `v-if="isException && canMutateRequirement"` 可点 |
|
||||
|
||||
## 三、正常订单不受影响
|
||||
|
||||
`houseStatus` 非 `EXCEPTION` 的订单,三按钮显隐维持现状。
|
||||
|
||||
## 四、配套说明
|
||||
|
||||
- 「**未用房晚标记 + 行级资源释放按钮**」与「**异常原因显示(客户中止)**」是 #6111 的另外两个子项,**需后端补字段**,在 #6111 工单跟进,不在本文范围。
|
||||
- 本文只覆盖按钮禁用/保留逻辑,纯前端,字段已具备。
|
||||
|
||||
## 验证
|
||||
|
||||
- [ ] 终止行程订单(如 26-9250)详情页「最终确认」「清空配房」禁用/置灰
|
||||
- [ ] 终止行程订单「标记完成」**仍可点**(异常收尾出口)
|
||||
- [ ] 正常进行中订单三按钮显隐不变
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6111"
|
||||
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 #6116 已合并 dev-v3(squash 3dd65f62d)并部署 TEST(hl-order-service-v3 双实例 8086/8186,2026-08-20 17:49 重启)。行程接口响应 hotelGroup 新增异常原因 2 字段、配房行新增未用标记 2 字段(均可选只读,正常单为 null/不影响既有字段),前端按需取读即可。"
|
||||
updated_at: "2026-08-20"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-20T17:55:00+08:00"
|
||||
---
|
||||
|
||||
# 配房行程标记终止未用房晚+异常原因透传(#6111)
|
||||
|
||||
## 背景
|
||||
|
||||
订单中止行程后,房务「配房行程」详情页看不到:(1) 哪几晚被运营在终止时勾选为「未用房晚」;(2) 异常桶旁的异常原因文案。本变更为房务侧补上这两个只读字段(纯新增,不影响既有字段)。
|
||||
|
||||
## 变更接口
|
||||
|
||||
| 接口 | 变更 |
|
||||
| --- | --- |
|
||||
| `GET /v3/admin/order/{id}/itinerary`(订单详情-行程安排) | **响应新增字段**(均可选只读,正常单为 null/false,不影响既有任何字段):`hotelGroup.exceptionReason`(String,异常原因,仅 houseStatus=EXCEPTION 时有值)、`hotelGroup.exceptionReasonLabel`(String,同 exceptionReason 中文展示)、`hotelGroup.assignments[].unusedForTerminate`(Boolean,该晚是否终止勾选未用)、`hotelGroup.assignments[].unusedLabel`(String,未用标记文案「未用」,未标记为 null) |
|
||||
|
||||
## 响应示例
|
||||
|
||||
`GET /v3/admin/order/{id}/itinerary` 返回(仅列本次新增字段,其余字段省略):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"hotelGroup": {
|
||||
"houseStatus": "EXCEPTION",
|
||||
"exceptionReason": "客户中止",
|
||||
"exceptionReasonLabel": "客户中止",
|
||||
"assignments": [
|
||||
{
|
||||
"assignmentId": "2090093115967733761",
|
||||
"dayNumber": 1,
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"unusedForTerminate": true,
|
||||
"unusedLabel": "未用"
|
||||
},
|
||||
{
|
||||
"assignmentId": "2090093115976122369",
|
||||
"dayNumber": 2,
|
||||
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||
"unusedForTerminate": true,
|
||||
"unusedLabel": "未用"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:该单已终止且运营勾选两晚均未用,故两行 `unusedForTerminate=true`、`unusedLabel="未用"`;`houseStatus=EXCEPTION` 故 `exceptionReason` 透出终止原因。正常单(非终止)`exceptionReason/Label` 为 null、`unusedForTerminate=false`、`unusedLabel` 为 null。
|
||||
|
||||
## 行为口径
|
||||
|
||||
- 4 个字段均为**只读、可选**:正常单(未终止)`exceptionReason/Label` 为 null、`unusedForTerminate=false`、`unusedLabel` 为 null,接口不报错。
|
||||
- `unusedForTerminate=true` 的行即终止时运营勾选的「未用房晚」,前端可渲染「未用」标记 + 行级「资源释放」按钮。
|
||||
- **行级资源释放**复用现有 `DELETE /v3/admin/order/assignments/{id}`(软删+恢复库存,CONFIRMED 行自动解冻),前端按该行 `assignmentId` 调即可,**无新端点**。
|
||||
- `exceptionReason` 来源为终止行程原因,仅异常桶(houseStatus=EXCEPTION)有值。
|
||||
|
||||
## 前端动作
|
||||
|
||||
无必须动作。需要展示「未用」标记时读 `assignments[].unusedForTerminate/unusedLabel`;需要显示异常原因时读 `hotelGroup.exceptionReason(Label)`;需要行级释放按钮时对该行调 `DELETE /v3/admin/order/assignments/{assignmentId}`。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 单测:OrderDetailConverterTest 44 / TerminateRefundServiceTest 8 / OrderDetailServiceTest 88 全绿(新增 7 例覆盖命中/未命中/未终止/null 边界)。
|
||||
- 架构门禁:*ArchTest 44 全绿(跨子聚合走 Service 契约,零违规)。
|
||||
- 部署:PR #6116 squash 合并 dev-v3(merge commit `3dd65f62d`);测试服 hl-order-service-v3 双实例 8086/8186 于 2026-08-20 17:49 滚动重启健康。
|
||||
- 网关实测(TEST,admin token):终止单 `GET /v3/admin/order/2090093100083904514/itinerary` 返 200,`hotelGroup.exceptionReason="客户中止"`,两行配房 `unusedForTerminate=true`/`unusedLabel="未用"`;正常单(CUSTOMIZING)`exceptionReason=null`,符合「异常有值/正常 null」口径。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/6111
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/6116
|
||||
- Commit(merge):https://git.1814.love:8443/wx/HL/commit/3dd65f62d
|
||||
- 后端负责人:wx
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户