比较提交
369
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
37e951eb49 | ||
|
|
22a51bdd09 | ||
|
|
7f0b279d93 | ||
|
|
1eb4246e3a | ||
|
|
fd02530c35 | ||
|
|
21b5b5cf01 | ||
|
|
e2b305a708 | ||
|
|
f75c679c9d | ||
|
|
8c44c113f3 | ||
|
|
938ca1702f | ||
|
|
bb3034ac3d | ||
|
|
3fc70f0cf4 | ||
|
|
a20540e6ee | ||
|
|
aa6f8b0897 | ||
|
|
60be944703 | ||
|
|
9bb8004bb1 | ||
|
|
a9f9a4a783 | ||
|
|
a31b427897 | ||
|
|
b676873eec | ||
|
|
f1b8cdf123 | ||
|
|
58c7675b2f | ||
|
|
1a319196ed | ||
|
|
2049120145 | ||
|
|
42fff93c72 | ||
|
|
f88592504e | ||
|
|
f8b70ec7a8 | ||
|
|
d8cb0d6128 | ||
|
|
f96f5302af | ||
|
|
02c4ef0552 | ||
|
|
142f81ffb3 | ||
|
|
c0d07a0db4 | ||
|
|
d8355c3c57 | ||
|
|
abe546707c | ||
|
|
9c4d8eab39 | ||
|
|
c53a6d6875 | ||
|
|
33d0999a68 | ||
|
|
39acbeb0e8 | ||
|
|
12f5dffb07 | ||
|
|
0910cbae7c | ||
|
|
27c7442900 | ||
|
|
9a6edddd38 | ||
|
|
1076c68896 | ||
|
|
5951c3023b | ||
|
|
5b59232713 | ||
|
|
a93d1f7fe6 | ||
|
|
642bed1e69 | ||
|
|
9ff8eb4c8c | ||
|
|
fcb002f662 | ||
|
|
364828e5ee | ||
|
|
3ad9f1ec85 | ||
|
|
da06fa538c | ||
|
|
2608ed2bfa | ||
|
|
fe6b35d235 | ||
|
|
e8b3414967 | ||
|
|
bf138d29d3 | ||
|
|
319cc684ac | ||
|
|
584f0b76ed | ||
|
|
ea6f5bacb1 | ||
|
|
317e71ef52 | ||
|
|
c3f0ebd9fd | ||
|
|
7332ac282a | ||
|
|
62ab4cdb2c | ||
|
|
eb96028029 | ||
|
|
7c7914bd34 | ||
|
|
bb01b978b8 | ||
|
|
7a90d75b5a | ||
|
|
e94e8d7e6e | ||
|
|
493b62dd8c | ||
|
|
8bb947ea99 | ||
|
|
07dbd2b595 | ||
|
|
2bc7571953 | ||
|
|
d197a6c3aa | ||
|
|
5e2c374ab1 | ||
|
|
cb2bbc7d46 | ||
|
|
ce5bac10e6 | ||
|
|
74461331cb | ||
|
|
575111baee | ||
|
|
4a88454172 | ||
|
|
cb3c557702 | ||
|
|
a9fafb8a14 | ||
|
|
e3c0209f5b | ||
|
|
8319198461 | ||
|
|
1e47fb9069 | ||
|
|
3d59972940 | ||
|
|
88fb95dabb | ||
|
|
e6b9c86b0e | ||
|
|
f852024d4a | ||
|
|
98740244ca | ||
|
|
da7c3808c9 | ||
|
|
e8ce58c503 | ||
|
|
86b3f7c799 | ||
|
|
d5941cb36c | ||
|
|
f49d448d1d | ||
|
|
41b7152d8d | ||
|
|
43cc42cecb | ||
|
|
1cb2149e08 | ||
|
|
301a05d5cb | ||
|
|
255427ca51 | ||
|
|
686a38cd2c | ||
|
|
c7e99515de | ||
|
|
2b37f6124b | ||
|
|
5954565ec5 | ||
|
|
ce13244c9e | ||
|
|
07dac023cf | ||
|
|
7dd6fc52c6 | ||
|
|
a43f11f767 | ||
|
|
13c0027073 | ||
|
|
dbc1737725 | ||
|
|
6e96521aa7 | ||
|
|
8f2f40c670 | ||
|
|
87c8da5c27 | ||
|
|
8ff0f5accc | ||
|
|
97e4adfdfe | ||
|
|
b3a59759a6 | ||
|
|
5b1659a048 | ||
|
|
3888c46f94 | ||
|
|
0a082cd3bb | ||
|
|
61f23b9190 | ||
|
|
20d782c02b | ||
|
|
b651f41f67 | ||
|
|
b89cb95b3b | ||
|
|
3de756a5f3 | ||
|
|
0dc29ef073 | ||
|
|
bc82060cab | ||
|
|
ccbe202637 | ||
|
|
77ba6986b1 | ||
|
|
edcf5a27ca | ||
|
|
6ed33870b9 | ||
|
|
1df4ac1a01 | ||
|
|
6d40278133 | ||
|
|
0ab6e12bbb | ||
|
|
7724cc0d3a | ||
|
|
d9d9026194 | ||
|
|
037748dcd2 | ||
|
|
1f77becb70 | ||
|
|
318917e6cf | ||
|
|
5aad9d2ed3 | ||
|
|
54d03ac956 | ||
|
|
63b70a9aec | ||
|
|
ed32d5bfc8 | ||
|
|
96797a8e93 | ||
|
|
b91e01f563 | ||
|
|
aea6b4091d | ||
|
|
f2c9864c02 | ||
|
|
f3b433a034 | ||
|
|
236f2517bd | ||
|
|
03d3a05116 | ||
|
|
0c5c848630 | ||
|
|
2a878856b8 | ||
|
|
fd0296c10f | ||
|
|
9bbf8efddb | ||
|
|
fb963d2133 | ||
|
|
31b047daf9 | ||
|
|
c7aec1217b | ||
|
|
09e2753f5b | ||
|
|
eaa784994d | ||
|
|
1815d4008f | ||
|
|
83cbc282aa | ||
|
|
532afb1c97 | ||
|
|
88d4cd5a8a | ||
|
|
d3568c4ec1 | ||
|
|
92549d7697 | ||
|
|
cbc05a9650 | ||
|
|
ab87358753 | ||
|
|
df0179375f | ||
|
|
be4f1ddd78 | ||
|
|
6f1945966e | ||
|
|
162c0ca6d8 | ||
|
|
0bd37e49aa | ||
|
|
e559045701 | ||
|
|
946290b259 | ||
|
|
93b4742dd7 | ||
|
|
bc0ae2c3f8 | ||
|
|
32374f6be7 | ||
|
|
2a01ff4c79 | ||
|
|
146576f3b3 | ||
|
|
7515b06fd9 | ||
|
|
d1b8a3ef15 | ||
|
|
849c4987dd | ||
|
|
67e50e3d1e | ||
|
|
6257f89d90 | ||
|
|
42a6367528 | ||
|
|
15f43e9cf9 | ||
|
|
c46f388641 | ||
|
|
31e2bc4fec | ||
|
|
68627376f1 | ||
|
|
ed8448c625 | ||
|
|
bddb222604 | ||
|
|
e11300080b | ||
|
|
d9ecc04a93 | ||
|
|
6d6ee1ced1 | ||
|
|
98094ec861 | ||
|
|
9e5b45e7a5 | ||
|
|
53b8c8b115 | ||
|
|
7d96056258 | ||
|
|
ed801356d9 | ||
|
|
81914a2244 | ||
|
|
dd53a9131c | ||
|
|
0458bbaa31 | ||
|
|
4cf844e182 | ||
|
|
57d49a7210 | ||
|
|
fab5a63922 | ||
|
|
7e9f743fe5 | ||
|
|
573ff6e2e3 | ||
|
|
d13fe33798 | ||
|
|
e0cefb1c07 | ||
|
|
f1d36b8522 | ||
|
|
c765f2b285 | ||
|
|
55944610fb | ||
|
|
f4999c9848 | ||
|
|
39988e5380 | ||
|
|
fe28a296e9 | ||
|
|
129c8eafd9 | ||
|
|
da656908cc | ||
|
|
5d65d2c241 | ||
|
|
f9634974c8 | ||
|
|
50c1cb9b20 | ||
|
|
cde332a840 | ||
|
|
0e32011617 | ||
|
|
985240047c | ||
|
|
53e36e9fdf | ||
|
|
7d68720e9b | ||
|
|
36178799e6 | ||
|
|
c005372652 | ||
|
|
faca68852a | ||
|
|
3de5eecbe7 | ||
|
|
eae2998409 | ||
|
|
10037c3266 | ||
|
|
ba20cd65b7 | ||
|
|
1a7103fed3 | ||
|
|
3506d7980c | ||
|
|
4b062a298a | ||
|
|
d9a38d433d | ||
|
|
4861573da1 | ||
|
|
e21f97ab92 | ||
|
|
34a657af60 | ||
|
|
e942c1f872 | ||
|
|
0c90b14bc5 | ||
|
|
0e1780b8b9 | ||
|
|
dc65788d5d | ||
|
|
6ccdd10866 | ||
|
|
c5d914d073 | ||
|
|
072645cc30 | ||
|
|
aff12f1333 | ||
|
|
28c627b8e1 | ||
|
|
234527639f | ||
|
|
8de86f6680 | ||
|
|
0b8d673b20 | ||
|
|
884d6a42b9 | ||
|
|
268ca6e116 | ||
|
|
e4c66f3fe0 | ||
|
|
d7a9137f08 | ||
|
|
7ee03a8399 | ||
|
|
34a65ef657 | ||
|
|
319751c781 | ||
|
|
ff9310192c | ||
|
|
ea9d7ed769 | ||
|
|
8941096a45 | ||
|
|
a4f4889e09 | ||
|
|
1cd17d2dfc | ||
|
|
b7b0e45f64 | ||
|
|
24fd4d7e3b | ||
|
|
83fcfd8eb3 | ||
|
|
eaf6c7f3cd | ||
|
|
8ad3ce4111 | ||
|
|
f7fbc5dacf | ||
|
|
4836fc9229 | ||
|
|
558425c4f8 | ||
|
|
c88cc66ffd | ||
|
|
98e35c6096 | ||
|
|
93aac89a3e | ||
|
|
037b82eaaa | ||
|
|
19ee4919ee | ||
|
|
f041ab68c9 | ||
|
|
1a12466b30 | ||
|
|
de1292823a | ||
|
|
ede5dd5c30 | ||
|
|
23d659a74b | ||
|
|
b9f588737e | ||
|
|
bc140004b6 | ||
|
|
e0be4ce0d0 | ||
|
|
8819ddbf24 | ||
|
|
69070e1d39 | ||
|
|
500f824518 | ||
|
|
6cbed28e94 | ||
|
|
799981cbe8 | ||
|
|
e27954e833 | ||
|
|
c8e3769e3f | ||
|
|
c1155e138d | ||
|
|
92f129c52c | ||
|
|
0277561bc3 | ||
|
|
c9be57e4b2 | ||
|
|
402e9a0444 | ||
|
|
9fedff29d7 | ||
|
|
f01e19f62c | ||
|
|
1d4ffe4ad3 | ||
|
|
2d52ce1513 | ||
|
|
49a9fb9fc9 | ||
|
|
5561ff5b26 | ||
|
|
50b65f0d41 | ||
|
|
996996c6f7 | ||
|
|
10340d0104 | ||
|
|
a3bc9a3e17 | ||
|
|
76dcf10531 | ||
|
|
3d9ec7021d | ||
|
|
b05004cebb | ||
|
|
71b96ad0d8 | ||
|
|
5b026fd920 | ||
|
|
e7cc66b833 | ||
|
|
a22e9c1ae3 | ||
|
|
597096129b | ||
|
|
d06d8ed6b4 | ||
|
|
9ec621d8ec | ||
|
|
e10e9a0b69 | ||
|
|
529ea1cbef | ||
|
|
805bd176f9 | ||
|
|
a9e26fa47b | ||
|
|
43d55d0a4d | ||
|
|
9965b110fb | ||
|
|
e6febaa22b | ||
|
|
ccb71ead90 | ||
|
|
cc06bb796a | ||
|
|
a6a18e9943 | ||
|
|
45e2af81f8 | ||
|
|
4b866a0371 | ||
|
|
3c32adb73e | ||
|
|
f85aa74eb5 | ||
|
|
3e00271082 | ||
|
|
b658d8671d | ||
|
|
a47eeeedcb | ||
|
|
bfa877f3db | ||
|
|
79468ab61f | ||
|
|
1e9f9ad881 | ||
|
|
b30dee3d22 | ||
|
|
b5cb91defd | ||
|
|
2a24c42ba5 | ||
|
|
7907e2aaa6 | ||
|
|
3f0c4037fd | ||
|
|
65d5980a7d | ||
|
|
a5c7d90b8d | ||
|
|
a63cadb822 | ||
|
|
c3060cc10a | ||
|
|
2f8156e0ff | ||
|
|
f0443f4368 | ||
|
|
01bb12416e | ||
|
|
75dea54f2f | ||
|
|
b661ff7e89 | ||
|
|
41acbe58bb | ||
|
|
9618b1f163 | ||
|
|
56be7eedae | ||
|
|
df6bee83b8 | ||
|
|
56c5498167 | ||
|
|
3d4e3967e7 | ||
|
|
ee75e0ca36 | ||
|
|
8144e7d197 | ||
|
|
3c50202413 | ||
|
|
8f9153e630 | ||
|
|
6e457dad42 | ||
|
|
d564ea71e7 | ||
|
|
1750cb69ab | ||
|
|
0df61a7b61 | ||
|
|
38960ac9d3 | ||
|
|
efa63eca69 | ||
|
|
fd965deb4f | ||
|
|
60350ddecf | ||
|
|
70f9e64dcb | ||
|
|
b52da8dd2f | ||
|
|
cbad66ecc7 | ||
|
|
a9185984e7 |
@@ -0,0 +1,33 @@
|
||||
# 🔴 安全热修:小程序相册接口横向越权(IDOR)修复 — 小程序
|
||||
|
||||
> 变更类型:安全修复(后端归属校验加固,**正常使用无影响**,无契约变更)
|
||||
> 端类型:小程序(相册)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-order-service-v2(相册域)
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3911
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
|
||||
`api-contract-audit` 审计小程序 BFF 时发现:相册的 4 个接口存在**横向越权(IDOR)**——后端收到当前用户 userId 后未做归属校验,任意登录用户改 URL 里的 orderId/folderId/albumFileId 即可读取/操作他人订单的相册。本次已修复:后端强制校验"相册所属订单归属当前登录用户",越权一律返回"不存在"。
|
||||
|
||||
涉及接口(小程序相册):
|
||||
- 订单文件夹列表、文件夹文件列表、文件下载地址获取、文件夹公开授权(写)。
|
||||
|
||||
## 前端影响:无需改动
|
||||
|
||||
- **正常使用完全不受影响**:用户访问自己订单的相册照常工作。
|
||||
- 仅"访问他人订单相册"这种越权请求会被拒(返回资源不存在)——前端正常逻辑不会触发。
|
||||
- 接口路径、入参、返回结构均未变。
|
||||
|
||||
> 如果前端有任何调试/测试代码用了非当前用户的 orderId/folderId 访问相册,请改为只访问当前用户自己的资源(之前能拿到是 bug,现已封堵)。
|
||||
|
||||
## 校验
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3911(squash 合并 dev-v3,via api-contract-audit workflow) |
|
||||
| 部署 | 已部署测试服,order-v2 双实例健康(滚动重启 uptime 刷新) |
|
||||
| 测试 | 相册模块单测全绿(新增越权拒绝反例:非本人订单/null-userId/订单缺失 + happy path + 防穿透 verify) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,85 @@
|
||||
# 小程序 BFF 接口契约审计修复(金额 String + 字段映射 + 可见性 + 缓存)— 修改接口 — 小程序
|
||||
|
||||
> 变更类型:⚠️ 多项修复(金额类型 / 字段补全 / 可见性收紧 / 缓存行为,多数前端正向受益)
|
||||
> 端类型:小程序(hl-mp-service BFF)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-mp-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3915
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
`api-contract-audit` 全量审计小程序 BFF 40 个核心 Controller,对抗复核实锤 100 项契约偏差。mp 是聚合层,本次修复 **mp BFF 自身的 67 处**;另有约 40 项根因在下游服务(订单/用户/资源服务),列在文末「待修清单」,会在后续审计中处理。已合并 dev-v3、部署测试服、双实例健康。
|
||||
|
||||
最影响前端的是第 1 节(金额→字符串)和第 2 节(原本恒 null 的字段现已修复)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 金额字段统一为 JSON 字符串
|
||||
|
||||
多个 mp 响应 VO 的金额(BigDecimal)此前输出成 JSON 数字,本次统一改为**字符串**(与平台口径一致)。涉及:退款(退款预览/进度/详情)、发票、订单详情/列表、票务、价格明细(含按人群/优惠/附加费的逐项明细)、保费、推荐产品起价等。
|
||||
|
||||
> 前端处理:相关金额字段一律按**字符串**接收;若做过数字运算需先转数值。非金额(里程/经纬度/评分)不变。
|
||||
|
||||
---
|
||||
|
||||
## 2. 原本恒为 null 的字段已修复(前端可正常取值)
|
||||
|
||||
以下字段此前因 BFF 与下游字段名不一致(反序列化失败)或 BFF 未填充而恒为 null,现已修复:
|
||||
|
||||
| 模块 | 字段 |
|
||||
|---|---|
|
||||
| 相册文件 | thumbnailUrl、fileName、fileSize、createdAt |
|
||||
| 相册订单 | coverUrl、fileCount |
|
||||
| 发票详情 | invoiceTitle、taxNumber、createdAt |
|
||||
| 合同 | 状态日志 *Label、出行人证件类型标签 |
|
||||
| 出行人 | race、roomGroupNo(订单出行人)、travelerTypeLabel |
|
||||
| 到达计划 | updateTime |
|
||||
|
||||
> 前端处理:这些字段现在会返回真实值,可直接使用(之前拿到的是 null)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 行为收紧(前端需配合)
|
||||
|
||||
### 3.1 下架资源详情不再返回(可见性收紧)
|
||||
景点 / 酒店 / 餐厅 / 活动 详情接口在 BFF 层补了「是否上架」校验,**下架(status=0)的资源详情不再返回**给小程序(之前会原样返回)。前端访问已下架资源详情会得到「不存在/不可见」,请正常处理空态。
|
||||
|
||||
### 3.2 浏览量统计 / 错误不再被缓存
|
||||
- 探索/百科详情的「浏览量+1」此前在缓存命中时不执行(统计失真),现已修复(浏览量正确累加)。
|
||||
- BFF 缓存不再缓存下游降级错误(避免 TTL 内持续返回陈旧错误)。
|
||||
- 前端无感,仅数据更准确。
|
||||
|
||||
### 3.3 定制需求状态字典修正
|
||||
定制(/mp/custom)相关接口的状态字典此前文档值有误,实际枚举为 `PENDING/PROCESSING/REPLIED/CONVERTED/CANCELLED`,请前端按此映射。
|
||||
|
||||
---
|
||||
|
||||
## 4. 待修清单(根因在下游服务,本次未修,后续处理)
|
||||
|
||||
以下问题审计已发现,但根因在下游服务,不在本 PR;记录在此供前端知晓(暂仍按现状):
|
||||
|
||||
- **订单服务(order-v2)相关 ~21 项**:部分字段缺失/状态机/错误码等(其中相册 4 个越权 P0 已由 PR #3911 安全热修修复)。
|
||||
- **用户服务相关 ~11 项**:出行人默认删除守卫、token 轮转、资料完善校验、足迹 HOTEL 等 — 将在用户服务审计时在源头修复。
|
||||
- **资源服务相关 ~3 项**:百科推荐排序、足迹等 — 将在资源服务审计时修复。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端 Action 清单
|
||||
|
||||
1. 第 1 节金额字段按字符串解析。
|
||||
2. 第 2 节字段现在有值,可接入使用。
|
||||
3. 下架资源详情做空态处理(第 3.1)。
|
||||
4. 定制状态映射用真实枚举(第 3.3)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联
|
||||
|
||||
| 项目 | 信息 |
|
||||
|---|---|
|
||||
| PR | https://git.1814.love:8443/wx/HL/pulls/3915(squash 合并 dev-v3,via api-contract-audit + mp-internal-fix workflow) |
|
||||
| 部署 | 已部署测试服,mp 双实例健康(滚动重启 uptime 刷新);公开端点 /mp/banner/active 等返 code:200 |
|
||||
| 测试 | 963 单测,本次改动 add 0 新失败(另有 3 个 dev-v3 预存红与本次无关,已单独反馈后端) |
|
||||
| 后端负责人 | wx |
|
||||
@@ -0,0 +1,37 @@
|
||||
# 用户服务接口契约审计修复(mp 侧字段/契约对齐)— 修改接口 — 小程序
|
||||
|
||||
> 变更类型:字段/契约对齐(无破坏性删除),小程序侧影响较小
|
||||
> 端类型:小程序(用户中心/首页配置/收藏足迹/出行人 OCR)
|
||||
> 日期:2026-06-17
|
||||
> 服务:hl-user-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/3924
|
||||
> 说明:本文件随 user-service 契约审计一并产出,**暂缓推送**(与小程序侧统一节奏)。admin 侧见 changelogs-v2/2026-06/17_3924_*.md。
|
||||
|
||||
---
|
||||
|
||||
## 关键说明
|
||||
|
||||
用接口契约语义审计工作流扫描 hl-user-service 全部 Controller,mp(小程序 BFF)侧修复一批字段名/null 语义/响应结构与 Swagger 契约不符项。已合并 dev-v3、部署测试服、本地全量 2793 单测零新增回归。下面列 mp 侧对接相关变更。
|
||||
|
||||
---
|
||||
|
||||
## 1. 收藏 / 足迹(FavoriteRespVO / FootprintRespVO)
|
||||
|
||||
- 响应 VO 字段名/缺字段与契约对齐;金额类字段(如有)补 `@JsonSerialize(ToStringSerializer)` 输出字符串。
|
||||
- 字段语义不变,前端按现有字段名接收即可;如此前对金额字段做数字运算,改为先转数值。
|
||||
|
||||
## 2. 首页配置(MpHomeConfig / MpHomeScreen2VO)
|
||||
|
||||
- 响应结构与 Swagger 契约对齐(字段名/层级稳定化),无破坏性删除。
|
||||
|
||||
## 3. 出行人 OCR / 内容安全(MpTravelerOcr / MpContentSecurity)
|
||||
|
||||
- 入参校验(`@Valid` / 校验分组)与错误码规范化;正常路径返回结构不变。
|
||||
|
||||
## 4. 站内信(InternalMpMessage / 未读数 UnreadCountRespVO)
|
||||
|
||||
- 未读数等响应改强类型 VO,字段名不变。
|
||||
|
||||
---
|
||||
|
||||
> 待小程序侧统一接线时一并对照本文件;如需提前对接某一节请单独知会。
|
||||
@@ -0,0 +1,341 @@
|
||||
# 售后工单接入 wx 内容安全机审 — 修改接口(小程序端)
|
||||
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-22
|
||||
- **Issue**:[#4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[#4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[8882eb156](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
用户在小程序发起售后工单(投诉/申诉)时,若附带图片或视频,前端需先通过微信内容安全 `msgSecCheck` 接口对媒体文件做内容审核,得到微信返回的 `traceId` 后,连同创建工单请求一并透传给后端。后端不直接调用微信安全接口,只存储 traceId,等待微信异步回调后更新审核状态。无图/无视频的工单跳过机审,直接置为 `APPROVED`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 字段 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ⚠️ 入参新增 | `POST /v3/mp/aftersale/ticket` | `mediaTraceIds` | 新增可选字段,机审 traceId 数组,最多 12 个 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/{id}` | `auditStatus` | 工单 wx 内容安全机审状态 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/list` | `auditStatus` | 工单列表每条记录新增机审状态 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 发起售后工单
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `POST /v3/mp/aftersale/ticket` |
|
||||
| 接口描述 | 用户发起售后工单(投诉/申诉),支持附件 + 机审 traceId 透传 |
|
||||
| 认证 | 需要用户 JWT(小程序登录 token) |
|
||||
| 幂等性 | 非幂等,每次调用创建一条新工单 |
|
||||
| 限流 | 无独立限流(受网关全局限流) |
|
||||
|
||||
### 3.2 工单详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/{id}` |
|
||||
| 接口描述 | 获取单条售后工单详情 |
|
||||
| 认证 | 需要用户 JWT,仅可查自己的工单 |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
### 3.3 工单列表
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/list` |
|
||||
| 接口描述 | 查询当前用户的售后工单列表(分页) |
|
||||
| 认证 | 需要用户 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
`GET /v3/mp/aftersale/ticket/{id}`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-------|------|------|------|
|
||||
| id | Long | 是 | 工单 ID |
|
||||
|
||||
### 4.2 请求体字段(POST /v3/mp/aftersale/ticket)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验 | 说明 |
|
||||
|-------|------|------|------|------|
|
||||
| orderId | Long | 是 | 不能为空 | 关联订单 ID |
|
||||
| category | String | 是 | 枚举:COMPLAINT / APPEAL | 工单分类:投诉 / 申诉 |
|
||||
| type | String | 是 | 枚举,见 §6 | 具体类型(如 HOTEL / GUIDE / PRICE) |
|
||||
| title | String | 是 | max=100 | 工单标题 |
|
||||
| description | String | 是 | max=2000 | 问题描述 |
|
||||
| attachments | List\<String\> | 否 | max=10 | 附件 URL 列表(图片/视频) |
|
||||
| **mediaTraceIds** | **List\<String\>** | **否** | **max=12** | **微信内容安全 traceId 数组(前端透传,后端只存)** |
|
||||
|
||||
> `mediaTraceIds` 与 `attachments` 一一对应关系由前端维护。后端不校验对应关系,仅存储用于异步机审回流。无附件时可不传或传空数组。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### TicketMpRespVO(工单详情/列表单条)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| id | Long | 工单 ID |
|
||||
| orderId | Long | 关联订单 ID |
|
||||
| orderNo | String | 订单编号 |
|
||||
| category | String | 工单分类枚举:COMPLAINT / APPEAL |
|
||||
| type | String | 工单类型 |
|
||||
| title | String | 标题 |
|
||||
| description | String | 描述 |
|
||||
| status | String | 工单状态:SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN |
|
||||
| statusName | String | 工单状态中文名 |
|
||||
| canWithdraw | Boolean | 是否可撤回 |
|
||||
| **auditStatus** | **String** | **wx 内容安全机审状态,取值见 §6** |
|
||||
| attachments | List\<String\> | 附件 URL |
|
||||
| createdAt | String | 创建时间(ISO 8601) |
|
||||
| updatedAt | String | 更新时间(ISO 8601) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### auditStatus — wx 内容安全机审状态
|
||||
|
||||
| 值 | 含义 | 触发条件 |
|
||||
|----|------|---------|
|
||||
| `PENDING` | 机审中 | 创建工单时有 mediaTraceIds(异步等待微信回调) |
|
||||
| `APPROVED` | 已通过 | 无 mediaTraceIds 的工单直接置此(免审);微信回调结果合规也置此 |
|
||||
| `MANUAL_REVIEW` | 人工复审 | 微信机审认为需人工介入 |
|
||||
| `REJECTED` | 已驳回 | 微信机审判定内容违规 |
|
||||
|
||||
> **重要**:`PENDING` 状态为异步,工单创建成功后微信回调可能在数秒到数分钟内到达。前端建议在工单详情页轮询或基于页面刷新展示最新状态,不要把 `PENDING` 当作错误。
|
||||
|
||||
### category — 工单分类
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `COMPLAINT` | 投诉 |
|
||||
| `APPEAL` | 申诉 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|-------|---------|------|---------|
|
||||
| 581001 | 400 | 工单关联订单不存在 | orderId 无效 |
|
||||
| 581002 | 400 | 工单已存在,不可重复提交 | 同一订单同分类已有活跃工单 |
|
||||
| 581003 | 400 | 订单状态不允许发起售后 | 订单未处于可售后状态 |
|
||||
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
|
||||
| 200-002 | 403 | 无权操作 | 工单不属于当前用户 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——带图片机审
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"mediaTraceIds": [
|
||||
"trace_abc123def456",
|
||||
"trace_xyz789uvw012"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654321,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "PENDING",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"createdAt": "2026-06-22T10:30:00",
|
||||
"updatedAt": "2026-06-22T10:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `auditStatus` 初始为 `PENDING`,等待微信异步回调后更新为 `APPROVED` / `MANUAL_REVIEW` / `REJECTED`。
|
||||
|
||||
### 8.2 边界情况——无附件工单(直接 APPROVED)
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"attachments": [],
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654322,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "APPROVED",
|
||||
"attachments": [],
|
||||
"createdAt": "2026-06-22T10:35:00",
|
||||
"updatedAt": "2026-06-22T10:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 无 mediaTraceIds 或传空数组时,`auditStatus` 直接为 `APPROVED`,无需等待回调。
|
||||
|
||||
### 8.3 业务失败——订单已有活跃工单
|
||||
|
||||
**请求**(同一订单同分类已存在未关闭工单)
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "GUIDE",
|
||||
"title": "导游服务差",
|
||||
"description": "导游态度恶劣。",
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 581002,
|
||||
"msg": "工单已存在,不可重复提交",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 订单处于可售后状态(已出行、出行中、已完成等,具体由后端校验)
|
||||
- 有图片/视频附件时前端必须先完成 `wx.msgSecCheck` 得到 traceId 再调此接口
|
||||
- 无附件或不需要机审时,`mediaTraceIds` 可省略或传空数组
|
||||
|
||||
**不适用场景**:
|
||||
- 已取消订单不可发起售后
|
||||
- 同一订单同分类已有活跃(非 CLOSED/WITHDRAWN)工单时,不可重复发起
|
||||
|
||||
**特殊边界**:
|
||||
- `mediaTraceIds` 最多 12 个,超出返回参数校验错误
|
||||
- `auditStatus` 为 `PENDING` 时工单照常流转(客服可正常处理),机审结果不阻塞工单流程
|
||||
- `auditStatus` 为 `REJECTED` 时,仅作展示标记,工单处理流程由客服决定是否关闭
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
**CreateTicketReqVO(入参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| mediaTraceIds | 不存在 | 新增,List\<String\>,可选,max=12 |
|
||||
|
||||
**TicketMpRespVO(出参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| auditStatus | 不存在 | 新增,String,wx 机审状态 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| 带图片发起工单 | 无机审,无审核状态 | 存 traceId,初始 auditStatus=PENDING,微信异步回调更新 |
|
||||
| 无图片发起工单 | 无机审状态字段 | auditStatus 直接置 APPROVED |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| 破坏兼容性 | 否(入参新增可选字段,旧版不传兼容;出参新增字段,旧版忽略) |
|
||||
| 前端同步上线 | 建议同步:入参传 mediaTraceIds 发挥机审能力;出参展示 auditStatus |
|
||||
| 回滚方案 | 后端回滚 PR #4203 即可,无 DDL 数据损失风险(DDL 只加列不删) |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. `mediaTraceIds` 由前端在调用微信 `wx.msgSecCheck` 后获得,后端不直接调微信安全接口,只做存储和转发。
|
||||
2. `auditStatus=PENDING` 是异步状态,不代表工单异常,客服后台可正常处理。
|
||||
3. 历史工单(此 PR 上线前创建的)`auditStatus` 字段为 `null`,前端展示时建议视 `null` 等同于 `APPROVED` 处理。
|
||||
4. 微信机审结果 `REJECTED` 不自动关闭工单,仅打标记,由客服决定后续处理。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **关联 Issue**:[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,222 @@
|
||||
# 退款申诉(新增)+ 统一售后工单接口下线(小程序端)
|
||||
|
||||
- 端类型:小程序端
|
||||
- 变更类型:新增接口(4)+ 删除接口(统一售后工单 mp 端)
|
||||
- 关联 Issue:#4250 PR:#4251 #4254
|
||||
- 日期:2026-06-23
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
售后体系重构:原"投诉 + 申诉统一售后工单"方向作废,拆为**投诉**与**申诉**两套独立能力。
|
||||
- **申诉**:用户对"退款被拒"或"退款金额"不服时发起,**小程序入口仍是「退款申诉」**。申诉记录单独存(不再混入退款申请表)。
|
||||
- **关键变化**:申诉**复用既有退款审批**——发起申诉后系统自动建一笔 PENDING 退款申请,由管理员在退款审批里复核(通过则退款 / 驳回则结束);申诉本身不再有独立审批。
|
||||
- 原统一售后工单接口(`/v3/mp/aftersale/ticket*`)**全部下线**。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST | `/v3/mp/refund/appeal` | 🆕 新增(发起申诉)|
|
||||
| 2 | GET | `/v3/mp/refund/appeal/my` | 🆕 新增(我的申诉,分页)|
|
||||
| 3 | GET | `/v3/mp/refund/appeal/{id}` | 🆕 新增(申诉详情)|
|
||||
| 4 | POST | `/v3/mp/refund/appeal/{id}/withdraw` | 🆕 新增(撤回申诉)|
|
||||
| 5 | ALL | `/v3/mp/aftersale/ticket*` | ❌ 删除(统一售后工单下线,调用返回 404)|
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 发起申诉 `POST /v3/mp/refund/appeal`
|
||||
用户对一笔退款申请发起申诉。两种类型:`REFUND_REJECTED`(退款被拒,原退款 status=REJECTED 才可申诉)/ `REFUND_AMOUNT_DISPUTE`(退款金额异议,原退款 status=REFUNDED 求补差)。
|
||||
> 发起成功后:① 落一条申诉记录(PENDING)② **自动建一笔 PENDING 退款申请**走退款审批 ③ 订单进入「售后中」。
|
||||
|
||||
### 2. 我的申诉 `GET /v3/mp/refund/appeal/my`
|
||||
当前登录用户的申诉分页列表。
|
||||
|
||||
### 3. 申诉详情 `GET /v3/mp/refund/appeal/{id}`
|
||||
单条申诉详情,含关联的两笔退款(来源退款 + 申诉触发的退款)。
|
||||
|
||||
### 4. 撤回申诉 `POST /v3/mp/refund/appeal/{id}/withdraw`
|
||||
仅 `PENDING`(审批中)状态可撤回;撤回会一并取消申诉触发的那笔 PENDING 退款申请。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口1 create(`@RequestBody`,登录态 userId 由网关注入)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| refundApplicationId | long | 是 | 来源退款申请 ID(被申诉的那笔退款)|
|
||||
| appealType | string | 是 | 申诉类型:`REFUND_REJECTED` / `REFUND_AMOUNT_DISPUTE` |
|
||||
| appealReason | string | 是 | 申诉理由(≤2000 字)|
|
||||
| appealAmount | string(decimal) | 否 | 期望退款金额(金额异议时填)|
|
||||
| mediaTraceIds | string[] | 否 | 凭证图片机审 traceId 列表(前端上传后透传,≤12)|
|
||||
|
||||
### 接口2 my
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| pageNo | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||||
|
||||
### 接口3 detail / 接口4 withdraw
|
||||
| 参数 | 位置 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | path | long | 申诉 ID |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口1 create `Result<Long>`
|
||||
`data` = 新建申诉 ID(字符串化 Long)。
|
||||
|
||||
### 接口2 my `Result<PageResult<AppealMpRespVO>>`;接口3 detail `Result<AppealMpRespVO>`
|
||||
`PageResult`:`{ records[], total, page, pageSize }`。`AppealMpRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string(Long) | 申诉 ID |
|
||||
| orderId | string(Long) | 订单 ID |
|
||||
| refundApplicationId | string(Long) | 来源退款申请 ID |
|
||||
| appealType | string | 申诉类型(枚举值,见⑥)|
|
||||
| appealTypeLabel | string | 申诉类型中文名 |
|
||||
| appealReason | string | 申诉理由 |
|
||||
| appealAmount | string(decimal) | 申诉退款金额 |
|
||||
| appealStatus | string | 申诉状态(枚举值,见⑥)|
|
||||
| appealStatusLabel | string | 申诉状态中文名 |
|
||||
| applicantName | string | 申请人姓名 |
|
||||
| reviewedAt | datetime | 审批完成时间(可空)|
|
||||
| createTime | datetime | 创建时间 |
|
||||
| sourceRefund | object | 来源退款信息(被申诉的原退款),见下 |
|
||||
| triggeredRefund | object | 申诉触发的退款信息(申诉建单后自动生成的退款),见下 |
|
||||
|
||||
`sourceRefund` / `triggeredRefund` 结构(关联退款信息):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refundApplicationId | string(Long) | 退款申请 ID |
|
||||
| status | string | 退款状态(PENDING/APPROVED/REJECTED/REFUNDING/REFUNDED/CANCELLED/ABNORMAL)|
|
||||
| actualAmount | string(decimal) | 实际退款金额(可空)|
|
||||
|
||||
### 接口4 withdraw `Result<Void>`
|
||||
`data` = null。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**申诉类型 appealType**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| REFUND_REJECTED | 退款被拒(原退款被驳回后申诉)|
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议(已退款但金额有异议,求补差)|
|
||||
|
||||
**申诉状态 appealStatus**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 审批中(申诉触发的退款申请待退款审批)|
|
||||
| REJECTED | 已驳回(退款审批驳回)|
|
||||
| REFUNDED | 退款到账(申诉成功,退款已退)|
|
||||
| WITHDRAWN | 已撤回 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码(段位 530600-530699)
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 530601 | 申诉必须关联一笔退款申请 |
|
||||
| 530602 | 关联的退款申请不存在 |
|
||||
| 530603 | 退款申请当前状态不满足申诉条件 |
|
||||
| 530604 | 该退款申请已有处理中的申诉,请勿重复提交 |
|
||||
| 530605 | 申诉退款金额超出可退余额 |
|
||||
| 530606 | 申诉记录不存在 |
|
||||
| 530607 | 无权操作此申诉 |
|
||||
| 530608 | 当前申诉状态不允许撤回,仅待审核状态可撤回 |
|
||||
| 530609 | 申诉类型无效 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:发起申诉(退款被拒)
|
||||
请求 `POST /v3/mp/refund/appeal`:
|
||||
```json
|
||||
{ "refundApplicationId": 20001, "appealType": "REFUND_REJECTED",
|
||||
"appealReason": "退款审核不合理,申请复核", "appealAmount": "300.00",
|
||||
"mediaTraceIds": [] }
|
||||
```
|
||||
响应:`{ "code":200, "message":"成功", "data":"40001", "success":true }`
|
||||
|
||||
### 典型:我的申诉
|
||||
请求 `GET /v3/mp/refund/appeal/my?pageNo=1&pageSize=10`,响应(节选一项):
|
||||
```json
|
||||
{ "code":200, "success":true, "data": { "total":1, "records":[
|
||||
{ "id":"40001", "orderId":"10001", "appealType":"REFUND_REJECTED", "appealTypeLabel":"退款被拒",
|
||||
"appealStatus":"PENDING", "appealStatusLabel":"审批中", "appealAmount":"300.00",
|
||||
"sourceRefund": { "refundApplicationId":"20001", "status":"REJECTED", "actualAmount":null },
|
||||
"triggeredRefund": { "refundApplicationId":"20009", "status":"PENDING", "actualAmount":null } }
|
||||
] } }
|
||||
```
|
||||
|
||||
### 异常:重复申诉
|
||||
对同一退款申请已有处理中申诉时再发起:
|
||||
```json
|
||||
{ "code":530604, "message":"该退款申请已有处理中的申诉,请勿重复提交", "success":false }
|
||||
```
|
||||
|
||||
### 异常:调用已下线的工单接口
|
||||
请求 `POST /v3/mp/aftersale/ticket`(旧统一售后工单接口):
|
||||
```json
|
||||
{ "code":404, "message":"Not Found" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 申诉**复用退款审批**:发起后自动建 PENDING 退款申请,管理员在退款审批里复核——**前端不要再调用任何"申诉审批"接口**(已无独立申诉审批)。
|
||||
- 申诉状态跟随其触发的退款申请:退款到账→REFUNDED,退款审批驳回→REJECTED。
|
||||
- 撤回仅 PENDING 可撤,且会取消触发的 PENDING 退款。
|
||||
- 申诉发起即把订单标记「售后中」,终结(REFUNDED/REJECTED/WITHDRAWN 且无其他活跃售后)后回切。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 申诉入口 | 统一售后工单 `POST /v3/mp/aftersale/ticket`(category=APPEAL)| `POST /v3/mp/refund/appeal` |
|
||||
| 我的申诉 | `/v3/mp/aftersale/ticket/my` | `/v3/mp/refund/appeal/my` |
|
||||
| 详情/撤回 | `/v3/mp/aftersale/ticket/{id}` `/{id}/withdraw` | `/v3/mp/refund/appeal/{id}` `/{id}/withdraw` |
|
||||
| 审批 | 申诉独立 OA 审批 | 无(复用退款审批)|
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:`/v3/mp/aftersale/ticket*` 已删,调用返回 404,前端涉及"退款申诉"的页面**必须切到新申诉接口**。
|
||||
- 投诉接口不在此列(投诉走 C 端经 BFF 的现有通道,无变化)。
|
||||
- 回滚:后端回滚 PR #4251 #4254。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段(appealAmount / actualAmount)均为**字符串**,前端按字符串处理防精度丢失。
|
||||
- Long 型 ID 均字符串化返回(防 JS 精度)。
|
||||
- 上传凭证图片仍走既有 wx 内容安全机审通道(mediaTraceIds 透传)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4250
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4251 ・ https://git.1814.love:8443/wx/HL/pulls/4254
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(申诉接口 200 / 工单接口 404)。
|
||||
@@ -0,0 +1,336 @@
|
||||
# 发票申请票种修正(最终契约)— 小程序端
|
||||
|
||||
> 变更类型:修改接口(破坏性)
|
||||
> 端类型:小程序端
|
||||
> 日期:2026-06-23 | Issue:#4296 | PR:#4292 / #4302 | 服务:hl-order-service-v3
|
||||
|
||||
> ⚠️ **破坏性变更**:`invoiceType` 删除 `ELECTRONIC`、`email` 改为始终必填 + 格式校验、专票限公司抬头 + 必填五项、删除 `mailAddress` 字段。小程序开票页与详情页均需同步改动,上线时前后端须同步发布。
|
||||
|
||||
> **勘误(截至 PR #4311 收口)**:抬头字段统一 `titleName`、金额 `amount` 改 JSON number(单位元,非分/x100)、票种仅 2 值(`VAT_NORMAL`/`VAT_SPECIAL`)、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。
|
||||
|
||||
---
|
||||
|
||||
## 接口背景
|
||||
|
||||
发票模型经 PR #4292 + #4302 多轮修正后定稿:删除「电子发票」票种(业务决策统一走普票),专票收严字段校验(限公司抬头 + 必填银行注册信息),email 改为始终必填(全系统电子交付),删除纸质邮寄字段 `mailAddress`。
|
||||
|
||||
本次涉及小程序两个接口:初次申请 `POST /v3/internal/mp/order/{orderId}/invoice/apply` 和重新开票 `POST /v3/internal/mp/invoice/{id}/reissue`,入参字段矩阵相同,出参亦做同步修正(删 `mailAddress`,invoiceType 去 ELECTRONIC)。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 说明 |
|
||||
|---|---------|------|
|
||||
| 1 | ⚠️ 申请入参删枚举值 | `invoiceType` 删除 `ELECTRONIC`,只保留 `VAT_NORMAL` / `VAT_SPECIAL` |
|
||||
| 2 | ⚠️ 申请入参删字段 | 删除 `mailAddress`(纸质邮寄地址) |
|
||||
| 3 | ⚠️ 申请入参校验收严 | `email` 改为始终必填 + 邮箱格式校验 |
|
||||
| 4 | ⚠️ 申请入参校验新增 | `taxNo`:单位抬头或专票时必填 |
|
||||
| 5 | ⚠️ 申请入参校验新增 | 专票:`bankName` / `bankAccount` / `registAddress` / `registPhone` 四项必填 |
|
||||
| 6 | ⚠️ 申请入参新增错误码 | `581523` 专票只能开给单位 |
|
||||
| 7 | ⚠️ 详情出参删字段 | `GET /v3/internal/mp/order/{orderId}/invoice/detail` 出参删除 `mailAddress` |
|
||||
| 8 | ⚠️ 列表出参枚举收窄 | `GET /v3/internal/mp/order/{orderId}/invoice/list` 出参 invoiceType 不再出现 ELECTRONIC |
|
||||
| 9 | 重开接口同步 | `POST /v3/internal/mp/invoice/{id}/reissue` 入参字段矩阵与申请接口保持一致 |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 小程序申请接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/order/{orderId}/invoice/apply` |
|
||||
| **接口名** | 小程序客户自主申请发票 |
|
||||
| **描述** | 客户在订单完成后自主提交开票申请,支持普票/专票,全电子交付 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
### 小程序重开接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/invoice/{id}/reissue` |
|
||||
| **接口名** | 小程序重新开票 |
|
||||
| **描述** | 旧发票作废后,客户重新提交开票申请;入参字段矩阵与 apply 接口相同 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 路径参数
|
||||
|
||||
**apply 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `orderId` | string(Long 雪花 ID) | 是 | 订单 ID |
|
||||
|
||||
**reissue 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `id` | string(Long 雪花 ID) | 是 | 被重开的旧发票 ID |
|
||||
|
||||
### 请求体字段(两接口共用)
|
||||
|
||||
| 字段名 | 类型 | 必填条件 | 说明 |
|
||||
|--------|------|---------|------|
|
||||
| `invoiceType` | string | 始终必填 | 发票类型:`VAT_NORMAL`(普票)/ `VAT_SPECIAL`(专票);**不含 ELECTRONIC** |
|
||||
| `titleType` | string | 始终必填 | 抬头类型:`COMPANY`(单位)/ `PERSONAL`(个人);**VAT_SPECIAL 只能 COMPANY** |
|
||||
| `titleName` | string | 始终必填 | 发票抬头 |
|
||||
| `taxNo` | string | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时必填 | 纳税人识别号 |
|
||||
| `bankName` | string | `invoiceType=VAT_SPECIAL` 时必填 | 开户银行 |
|
||||
| `bankAccount` | string | `invoiceType=VAT_SPECIAL` 时必填 | 银行账号 |
|
||||
| `registAddress` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册地址 |
|
||||
| `registPhone` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册电话 |
|
||||
| `amount` | number | 始终必填 | 开票金额,JSON number,单位**元**(如 `986.00`),须 > 0 且不超过订单总金额 |
|
||||
| `email` | string | 始终必填 | 收件邮箱,须通过邮箱格式校验 |
|
||||
| `remark` | string | 选填 | 备注 |
|
||||
|
||||
> ⚠️ `mailAddress` 字段**已删除**,不再接收。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
### apply / reissue 接口出参
|
||||
|
||||
返回结构:`Result<Long>`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `data` | string | 新建/重开发票 ID,字符串雪花 ID |
|
||||
|
||||
### 发票详情出参变化(GET /v3/internal/mp/order/{orderId}/invoice/detail)
|
||||
|
||||
| 字段名 | 变化 | 说明 |
|
||||
|--------|------|------|
|
||||
| `mailAddress` | **已删除** | 原出参中此字段已移除,前端不应再渲染邮寄地址 |
|
||||
| `invoiceType` | 值范围收窄 | 只会出现 `VAT_NORMAL` / `VAT_SPECIAL`,不再出现 `ELECTRONIC` |
|
||||
| `email` | 始终有值 | 原可能为 null,现始终有值 |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 | 适用抬头 | 专票字段要求 |
|
||||
|--------|--------|---------|------------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 | COMPANY / PERSONAL | 专票四项均不需要 |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 | **仅 COMPANY** | taxNo + bankName + bankAccount + registAddress + registPhone 全必填 |
|
||||
|
||||
> ~~`ELECTRONIC`~~ 已删除,不得传入。
|
||||
|
||||
### 抬头类型(titleType)
|
||||
|
||||
| 枚举值 | 中文名 | 可选票种 |
|
||||
|--------|--------|---------|
|
||||
| `COMPANY` | 单位 | VAT_NORMAL / VAT_SPECIAL |
|
||||
| `PERSONAL` | 个人 | 只能 VAT_NORMAL,选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `581510` | 订单未完成,不可开票 | 订单状态不是 COMPLETED |
|
||||
| `581511` | 一单一票,已有有效发票 | 已有 REQUESTED / ISSUED / PUSHED 状态发票 |
|
||||
| `581512` | 开票金额超订单总额 | `amount` > 订单 `orderAmount` |
|
||||
| `581513` | 发票类型非法 | `invoiceType` 不是 VAT_NORMAL 或 VAT_SPECIAL |
|
||||
| `581514` | 公司抬头或专票须填税号 | 单位抬头或专票时 `taxNo` 为空 |
|
||||
| `581515` | 专票须填银行及注册信息 | 专票时任一四项为空 |
|
||||
| `581516` | 收件邮箱不能为空 | HTTP 400,入参层 @NotBlank 校验;格式错误返回 400「邮箱格式不正确」 |
|
||||
| `581523` | 专票只能开给单位 | `invoiceType=VAT_SPECIAL` 且 `titleType=PERSONAL` |
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 / 越权 | 非订单归属用户操作 |
|
||||
|
||||
---
|
||||
|
||||
## 示例
|
||||
|
||||
### 典型成功(增值税普通发票,个人抬头)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000001/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "张三",
|
||||
"taxNo": null,
|
||||
"amount": 986.00,
|
||||
"email": "zhangsan@qq.com",
|
||||
"remark": "旅游报销"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456789"
|
||||
}
|
||||
```
|
||||
|
||||
### 典型成功(增值税专用发票,公司抬头,全字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000002/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "某某科技有限公司",
|
||||
"taxNo": "91310000XXXXXXXXXX",
|
||||
"bankName": "招商银行上海支行",
|
||||
"bankAccount": "1234567890123456",
|
||||
"registAddress": "上海市浦东新区XX路XX号",
|
||||
"registPhone": "021-88888888",
|
||||
"amount": 2980.00,
|
||||
"email": "finance@company.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456790"
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(专票个人抬头,错误码 581523)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000003/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "李四",
|
||||
"taxNo": null,
|
||||
"amount": 500.00,
|
||||
"email": "lisi@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581523,
|
||||
"msg": "增值税专用发票只能开给单位,个人抬头不可选专票",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 客户在小程序「我的订单 - 订单详情」中点「申请发票」,订单状态为 COMPLETED 时可用
|
||||
- 旧发票被作废后,客户在小程序发起重新申请(reissue 接口)
|
||||
|
||||
**不适用场景**
|
||||
- 订单未完成(非 COMPLETED 状态),返回 581510
|
||||
- 已有有效发票,须先由财务作废后才能 reissue,不能再次 apply
|
||||
- 管理后台代客申请走 admin 专属接口
|
||||
|
||||
**特殊边界**
|
||||
- 金额单位:`amount` 为 JSON number,单位**元**(如 `986.00`),前端输入框以元为单位直接提交,无需 x100
|
||||
- 一单一票:同一订单至多一张有效发票
|
||||
- 专票抬头联动:选专票后,抬头类型需强制为单位,个人选项需禁用或隐藏
|
||||
- 详情回显:`mailAddress` 字段已不存在,前端不应渲染邮寄地址块
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
### 发票类型枚举
|
||||
|
||||
| 旧值 | 新值 |
|
||||
|------|------|
|
||||
| VAT_NORMAL(保留) | VAT_NORMAL(保留) |
|
||||
| VAT_SPECIAL(保留) | VAT_SPECIAL(保留) |
|
||||
| ELECTRONIC(电子发票) | **已删除** |
|
||||
|
||||
### email 字段校验
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 条件必填(ELECTRONIC 时才必填) | **始终必填 + 邮箱格式校验** |
|
||||
|
||||
### mailAddress 字段
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 申请入参可选填;详情/列表出参包含此字段 | **申请入参已删除;详情出参已删除** |
|
||||
|
||||
### 专票限制
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| titleType 无限制 | 专票只能 COMPANY(个人返回 581523) |
|
||||
| bankName 等无强制要求 | 专票 taxNo + 银行四项全部必填(返回 581515) |
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
### 小程序开票页必须改动
|
||||
|
||||
| 模块 | 必须改动 |
|
||||
|------|---------|
|
||||
| 发票类型选择 | 去掉「电子发票」,只保留「增值税普通发票」/「增值税专用发票」 |
|
||||
| 专票表单区块 | 新增开户行、银行账号、注册地址、注册电话 4 个必填项,仅选专票时显示 |
|
||||
| 抬头类型联动 | 选专票时隐藏/禁用「个人」选项 |
|
||||
| email 输入框 | 改为必填(加红星),增加邮箱格式校验 |
|
||||
| mailAddress 输入框 | 移除,不再渲染邮寄地址块 |
|
||||
| 详情页 | 移除邮寄地址展示区,invoiceType 显示只有普票/专票两种文案 |
|
||||
|
||||
### 回滚方案
|
||||
|
||||
后端回滚至 PR #4292 前,ELECTRONIC 重新有效,email 恢复条件必填,mailAddress 重新可用。小程序需同步回滚开票页逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `ELECTRONIC` 已从枚举删除,小程序开票选项只有两项,禁止出现「电子发票」
|
||||
2. 重开接口(reissue)入参字段矩阵与 apply 完全相同,改动同步适用
|
||||
3. 专票五项(taxNo + 开户行 + 账号 + 注册地址 + 注册电话)在专票场景下全必填,缺一返回 581515
|
||||
4. 详情页 `mailAddress` 字段已消失,老版本小程序读到 undefined 需做好空值守卫(不报错)
|
||||
5. `amount` 为 JSON number,单位**元**(如 `986.00`),小程序输入框直接传元值,无需 x100
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue(功能)** | [#4290 发票申请票种修正](https://git.1814.love:8443/wx/HL/issues/4290) |
|
||||
| **Issue(小程序)** | [#4296 小程序发票申请同步修正](https://git.1814.love:8443/wx/HL/issues/4296) |
|
||||
| **PR(票种删 ELECTRONIC)** | [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) |
|
||||
| **PR(专票限公司/email 必填/删 mailAddress)** | [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,217 @@
|
||||
# 发票申请删 amount · 门槛放宽(小程序端)
|
||||
|
||||
- **接口**:POST /v3/internal/mp/order/{orderId}/invoice/apply
|
||||
- **变更类型**:修改接口,破坏性变更(申请入参删 amount;申请门槛扩展;新增错误码 581524)
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4353](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
小程序端发票申请接口(POST /v3/internal/mp/order/{orderId}/invoice/apply)本次同步两项改动:
|
||||
|
||||
1. **申请入参删 amount**:开票金额不再前端传入,开票时后端按规则自动计算(正常单=应收总额;取消单=净实收)。
|
||||
2. **申请门槛放宽**:原门槛仅允许 COMPLETED(已完成)订单申请;放宽为定制中及以后均可申请,同时支持已取消但净实收 > 0 的订单申请。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | 申请入参 amount | 必填字段,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 2 | 申请门槛(正常订单) | 订单状态必须 COMPLETED | 状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请 |
|
||||
| 3 | 申请门槛(取消订单) | 不允许 | CANCELLED 且净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 4 | 不满足门槛的错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED | ✨ 新增 581524 INVOICE_ORDER_NOT_APPLICABLE(更精确语义) |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | POST |
|
||||
| 路径 | /v3/internal/mp/order/{orderId}/invoice/apply |
|
||||
| 描述 | 客户自主申请开票(申请阶段不填金额,开票时后端自动算) |
|
||||
| 认证 | Bearer JWT(小程序用户) |
|
||||
| 幂等性 | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| orderId | string(Long 雪花) | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段(MpInvoiceApplyReqVO,⚠️ amount 已删)
|
||||
|
||||
当前完整字段列表(无 amount):
|
||||
|
||||
| 字段 | 类型 | 必填条件 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| invoiceType | string | 始终必填 | 发票类型,只有 VAT_NORMAL / VAT_SPECIAL 两值 |
|
||||
| titleType | string | 始终必填 | 抬头类型:COMPANY(单位)/ PERSONAL(个人);VAT_SPECIAL 只能 COMPANY |
|
||||
| titleName | string | 始终必填 | 发票抬头(企业全称或个人姓名) |
|
||||
| taxNo | string | titleType=COMPANY 或 invoiceType=VAT_SPECIAL 时必填 | 纳税人识别号 |
|
||||
| bankName | string | invoiceType=VAT_SPECIAL 时必填 | 开户银行名称 |
|
||||
| bankAccount | string | invoiceType=VAT_SPECIAL 时必填 | 银行账号 |
|
||||
| registAddress | string | invoiceType=VAT_SPECIAL 时必填 | 注册地址 |
|
||||
| registPhone | string | invoiceType=VAT_SPECIAL 时必填 | 注册电话 |
|
||||
| email | string | 始终必填 | 收件邮箱,需通过邮箱格式校验 |
|
||||
| remark | string | 选填 | 申请备注 |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应:成功返回新建发票 ID(字符串,雪花 ID)。
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 发票类型(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
|
||||
### 6.2 抬头类型(titleType)
|
||||
|
||||
| code | 说明 | 限制 |
|
||||
|------|------|------|
|
||||
| COMPANY | 单位 | 普票/专票均可 |
|
||||
| PERSONAL | 个人 | 只能选 VAT_NORMAL;选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
### 6.3 订单状态与可申请关系
|
||||
|
||||
| 订单状态(code) | 中文 | 可申请? | 说明 |
|
||||
|----------------|------|---------|------|
|
||||
| CUSTOMIZING | 定制中 | 是 | 放宽新增 |
|
||||
| PENDING_DEPARTURE | 待出行 | 是 | 放宽新增 |
|
||||
| TRAVELLING | 出行中 | 是 | 放宽新增 |
|
||||
| COMPLETED | 已完成 | 是 | 原有 |
|
||||
| CANCELLED | 已取消 | 条件是 | 净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 其他状态 | - | 否 | 报 581524 |
|
||||
|
||||
### 6.4 开票时金额自动计算规则
|
||||
|
||||
| 订单类型 | 金额计算公式 |
|
||||
|----------|-------------|
|
||||
| 正常单(非 CANCELLED) | 应收总额 = 订单总价 + 增项 - 优惠 |
|
||||
| 取消单(CANCELLED) | 净实收 = 已付金额 - 已退金额 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头/专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行/银行账号/注册地址/注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 收件邮箱为必填或格式错误(HTTP 400) |
|
||||
| 581518 | INVOICE_FORBIDDEN | 无权访问该订单(IDOR 防护) |
|
||||
| 581523 | INVOICE_VAT_SPECIAL_PERSONAL_FORBIDDEN | 专票只能开给单位 |
|
||||
| 581524 | INVOICE_ORDER_NOT_APPLICABLE | ✨ 新增:订单状态不在可申请集合内,或取消单净实收为 0 |
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——普票申请(无 amount 字段)
|
||||
|
||||
请求
|
||||
|
||||
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.2 边界情况——订单处于定制中提前申请
|
||||
|
||||
说明:订单状态 CUSTOMIZING(定制中),本次放宽后允许申请。开票金额将在财务执行开票操作时按当时应收总额计算。
|
||||
|
||||
请求(结构同 8.1,orderId 为定制中订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.3 业务失败——取消订单净实收为 0 被拒
|
||||
|
||||
说明:订单已取消,实付 1000 元且已全额退款,净实收 = 0,无法申请开票。
|
||||
|
||||
请求(结构同 8.1,orderId 为该取消订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
适用:
|
||||
- 订单状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请
|
||||
- CANCELLED 且净实收(已付 - 已退)> 0 时可申请
|
||||
|
||||
不适用:
|
||||
- PAID(已支付但未进入定制阶段)等其他状态报 581524
|
||||
- CANCELLED 且净实收 = 0 报 581524
|
||||
- 订单不属于当前登录用户,报 581518(IDOR 防护)
|
||||
|
||||
特殊边界:
|
||||
- 一单一票:同一订单只允许一张有效发票(REQUESTED/ISSUED/PUSHED 状态),重复申请报 581511
|
||||
- 开票金额由后端在财务执行 issue 操作时自动计算,小程序端申请时不确定最终金额
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
| 字段 / 规则 | 变更前 | 变更后 |
|
||||
|-------------|--------|--------|
|
||||
| 申请入参 amount | 必填,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 正常订单申请门槛 | 仅 COMPLETED | CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED |
|
||||
| 取消订单申请 | 不允许 | 净实收 > 0 时可申请 |
|
||||
| 不满足门槛错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED(已废弃) | 581524 INVOICE_ORDER_NOT_APPLICABLE(新增) |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:是
|
||||
|
||||
- 小程序申请发票表单必须删除金额输入框,不再传 amount 字段
|
||||
- 若现有流程中有基于 amount 的前端金额展示或校验逻辑,须同步移除
|
||||
- 不满足门槛的错误提示由旧 581510 改为 581524,前端若 hardcode 了 581510 的处理逻辑需同步更新
|
||||
|
||||
前端同步上线:申请表单删 amount 必须与后端同期上线。
|
||||
|
||||
回滚方案:回滚后端至 #4360 前版本,amount 字段恢复必填,申请门槛收回到仅 COMPLETED,581524 不再存在。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **amount 字段已删**:小程序申请发票表单必须移除金额输入框,不传 amount,传入后端会忽略。
|
||||
2. **申请门槛放宽的注意点**:定制中等状态提前申请,开票时金额由财务执行 issue 操作时后端按当时应收总额计算,小程序无法在申请时预知最终开票金额。
|
||||
3. **581524 vs 旧 581510**:新错误码 581524 取代了旧的 581510,含义更精确(包含取消单净实收为 0 的场景)。若前端有 581510 的 hardcode 判断需更新为 581524。
|
||||
4. **开票金额由财务操作确定**:小程序端只负责申请,金额由后端在财务开票(PUT issue)时写入,前端无法在申请时展示最终金额。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4353 发票模块门槛放宽与开票规则优化](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,374 @@
|
||||
# 早鸟优惠管理页面 v3 接口对接说明(管理后台)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **变更类型**:接口说明(路径迁移)
|
||||
- **日期**:2026-06-22
|
||||
- **关联 PR**:[#4053](https://git.1814.love:8443/wx/HL/pulls/4053) / [#4064](https://git.1814.love:8443/wx/HL/pulls/4064) / [#4076](https://git.1814.love:8443/wx/HL/pulls/4076) / [#4082](https://git.1814.love:8443/wx/HL/pulls/4082)
|
||||
- **负责人**:yst(腰苏图)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单服务 v3(hl-order-service-v3,端口 8086)已完整落地早鸟优惠管理后台 6 个接口(PR #4053 / #4064 / #4076 / #4082)。
|
||||
|
||||
v3 的早鸟接口与 v2(hl-order-service-v2,端口 8094)的接口**入参、出参、字段语义、枚举值、交互行为完全一致**,唯一区别是**请求路径加了 /v3 前缀**。
|
||||
|
||||
**前端对接结论:早鸟管理页面无需任何 UI / 字段 / 交互改动,唯一需要做的是切换接口路径。**
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 方法 | v2 路径(旧) | v3 路径(新) | 备注 |
|
||||
|---|------|--------------|--------------|------|
|
||||
| 1 | GET | /admin/order/early-bird 或 /admin/order/early-bird/list | /v3/admin/order/early-bird/list | v3 只有 /list,调空路径返回 404,必须带 /list |
|
||||
| 2 | GET | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
|
||||
| 3 | POST | /admin/order/early-bird | /v3/admin/order/early-bird | 仅加前缀 |
|
||||
| 4 | PUT | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
|
||||
| 5 | DELETE | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
|
||||
| 6 | PUT | /admin/order/early-bird/{planId}/toggle | /v3/admin/order/early-bird/{planId}/toggle | Query 参数 enabled 不变 |
|
||||
|
||||
v2 早鸟列表接口历史上同时挂了空路径和 /list 两个映射。v3 只保留 /list,调空路径会返回 404。前端如果之前调的是 /admin/order/early-bird(无 /list 后缀),切换时必须同时补上 /list。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **Base URL** | /v3/admin/order/early-bird |
|
||||
| **认证** | 管理后台 JWT Token(Authorization: Bearer token),与 v2 一致 |
|
||||
| **内容类型** | POST / PUT 请求体 Content-Type: application/json;GET 均为 Query 参数 |
|
||||
| **幂等性** | POST 创建非幂等;PUT 按 planId 幂等覆盖(全字段覆盖,不支持 PATCH) |
|
||||
| **限流** | 走全局网关限流,无独立限流规则 |
|
||||
| **服务端口** | 测试服网关统一 9443,无需直连 8086 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 GET /v3/admin/order/early-bird/list — 分页列表
|
||||
|
||||
Query 参数(继承 PageParam):
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| pageNo | Integer | 是 | 页码,从 1 开始 |
|
||||
| pageSize | Integer | 是 | 每页条数,建议 10 / 20 |
|
||||
|
||||
当前 v3 分页查询无额外筛选字段(EarlyBirdPlanPageReqVO 仅继承 PageParam),与 v2 保持一致。
|
||||
|
||||
### 4.2 GET /v3/admin/order/early-bird/{planId} — 详情
|
||||
|
||||
| 参数 | 类型 | 位置 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| planId | Long(字符串) | Path | 是 | 早鸟计划 ID(雪花 ID) |
|
||||
|
||||
### 4.3 POST /v3/admin/order/early-bird — 创建
|
||||
|
||||
请求体(EarlyBirdPlanSaveReqVO):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 | 说明 |
|
||||
|------|------|------|----------|------|
|
||||
| planName | String | 是 | NotBlank | 计划名称 |
|
||||
| remark | String | 否 | 无 | 优惠描述 |
|
||||
| discountType | String | 否 | 枚举三选一见第6节 | 折扣方式,默认 AMOUNT_TOTAL |
|
||||
| discountAmount | BigDecimal | 条件必填 | AMOUNT_PER_PERSON 或 AMOUNT_TOTAL 时必填 | 优惠金额(元) |
|
||||
| discountPercent | BigDecimal | 条件必填 | PERCENT 时必填,取值 (0, 100) 开区间 | 折扣率,80=8折(不是立减 80%) |
|
||||
| priority | Integer | 否 | >=0,默认 0 | 优先级,同区间多命中时大者优先 |
|
||||
| minPeople | Integer | 是 | >=1 | 最低适用人数(含) |
|
||||
| maxPeople | Integer | 否 | >=minPeople | 最高适用人数(含),不填=不限 |
|
||||
| startDate | LocalDate | 是 | yyyy-MM-dd | 生效开始日期 |
|
||||
| endDate | LocalDate | 是 | yyyy-MM-dd,需 >=startDate | 生效结束日期 |
|
||||
| productIds | List<Long> | 否 | 无 | 关联产品 ID 列表,空=不限产品 |
|
||||
| applicableTravelerTypes | List<String> | 否 | 枚举值见第6节 | 适用人群 code 列表,空=后端自动填充 [ADULT, CHILD, YOUNG_CHILD] |
|
||||
|
||||
### 4.4 PUT /v3/admin/order/early-bird/{planId} — 修改
|
||||
|
||||
路径参数 planId(Long/字符串)。请求体与 4.3 完全一致,全字段覆盖更新(不支持 PATCH,未传的选填字段会被重置为 null / 默认值)。
|
||||
|
||||
### 4.5 DELETE /v3/admin/order/early-bird/{planId} — 删除
|
||||
|
||||
路径参数 planId(Long/字符串)。无请求体。软删除,删除后 planId 不可再查询。
|
||||
|
||||
### 4.6 PUT /v3/admin/order/early-bird/{planId}/toggle — 启用 / 禁用
|
||||
|
||||
| 参数 | 类型 | 位置 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| planId | Long | Path | 是 | 早鸟计划 ID |
|
||||
| enabled | Boolean | Query | 是 | true=启用;false=禁用 |
|
||||
|
||||
禁用后不再参与下单时自动匹配。启用时若与已有启用计划冲突(同产品+时间段+人数区间重叠),返回 581610。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
所有接口返回 Result<T> 包装,成功 code=200。
|
||||
|
||||
| 接口 | 出参类型 |
|
||||
|------|---------|
|
||||
| 分页列表 | Result<PageResult<EarlyBirdPlanVO>> |
|
||||
| 详情 | Result<EarlyBirdPlanVO> |
|
||||
| 创建 | Result<EarlyBirdPlanVO> |
|
||||
| 修改 | Result<Void> |
|
||||
| 删除 | Result<Void> |
|
||||
| 启用/禁用 | Result<Void> |
|
||||
|
||||
### EarlyBirdPlanVO 字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| planId | Long(JSON 已序列化为字符串) | 计划 ID,雪花 ID,前端用字符串类型接收防 JS 精度丢失 |
|
||||
| planName | String | 计划名称 |
|
||||
| remark | String / null | 优惠描述 |
|
||||
| discountType | String | 折扣方式枚举 code(AMOUNT_TOTAL / AMOUNT_PER_PERSON / PERCENT) |
|
||||
| discountAmount | BigDecimal(JSON 序列化为字符串) / null | 优惠金额(元);discountType=PERCENT 时为 null |
|
||||
| discountPercent | BigDecimal / null | 折扣率 (0~100);非 PERCENT 时为 null;80=8折 |
|
||||
| priority | Integer | 优先级,默认 0 |
|
||||
| minPeople | Integer | 最低适用人数(含) |
|
||||
| maxPeople | Integer / null | 最高适用人数(含),null=不限 |
|
||||
| peopleRangeText | String | 后端拼好的人数区间文案,如 3-5 人 / 3 人及以上 |
|
||||
| discountText | String | 后端拼好的折扣说明,如 每人立减 100 元 / 整单立减 500 元 / 9 折优惠 |
|
||||
| startDate | String(yyyy-MM-dd) | 生效开始日期 |
|
||||
| endDate | String(yyyy-MM-dd) | 生效结束日期 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
| applicableTravelerTypes | List<String> | 适用人群 code 列表;配置为空时后端回填并持久化 [ADULT, CHILD, YOUNG_CHILD] |
|
||||
| applicableTravelerTypesText | String | 后端拼好的适用人群文案,如 成人/儿童/小童 |
|
||||
| productIds | List<Long> | 关联产品 ID 列表,空数组=不限产品 |
|
||||
| productNames | List<String> | 关联产品名称,下标与 productIds 对齐;产品已删除时填 产品[ID]已删除 |
|
||||
| products | List<{productId:Long, name:String}> | 关联产品结构化列表,供编辑弹窗 tag 回显用 |
|
||||
| createTime | String(ISO 8601) | 创建时间,如 2026-06-22T10:00:00 |
|
||||
| updateTime | String(ISO 8601) | 最后更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### discountType — 折扣方式
|
||||
|
||||
| code | 中文 | 计算公式 | 生效字段 |
|
||||
|------|------|----------|---------|
|
||||
| AMOUNT_TOTAL | 整单立减(默认) | 优惠额 = discountAmount | discountAmount 必填 |
|
||||
| AMOUNT_PER_PERSON | 每人立减 | 优惠额 = discountAmount x 计费人数 | discountAmount 必填 |
|
||||
| PERCENT | 按比例打折 | 优惠额 = 总价 x (100 - discountPercent) / 100 | discountPercent 必填 |
|
||||
|
||||
重要:discountPercent=80 表示打 8 折,不是优惠 80%。前端展示请转换为 X折 格式,切勿直接显示 80%。
|
||||
|
||||
### applicableTravelerTypes — 适用人群
|
||||
|
||||
| code | 中文 | 人数匹配规则 |
|
||||
|------|------|------------|
|
||||
| ADULT | 成人 | 计入人数口径 |
|
||||
| CHILD | 儿童 | 计入人数口径 |
|
||||
| YOUNG_CHILD | 小童 | 计入人数口径 |
|
||||
| BABY | 婴儿 | 默认不计入;需显式配置才计入 |
|
||||
|
||||
人数匹配口径:统计 applicableTravelerTypes 中包含的人群类别对应人数之和,判断是否满足 minPeople / maxPeople 区间。默认排除婴儿,人数口径 = adult + child + youngChild(不含 baby)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
早鸟模块错误码段位:581600 – 581699
|
||||
|
||||
| 错误码 | 描述 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| 581600 | 早鸟计划不存在 | planId 查不到记录(详情 / 修改 / 删除 / toggle) |
|
||||
| 581601 | 早鸟计划已禁用 | 对禁用计划执行不允许的操作 |
|
||||
| 581602 | 当前日期不在早鸟计划有效期内 | 当前日期不在 startDate ~ endDate 范围内 |
|
||||
| 581603 | 人数不满足最低人数要求({0}) | 计费人数 < minPeople,{0} 为阈值 |
|
||||
| 581604 | 人数超过最高人数限制({0}) | 计费人数 > maxPeople,{0} 为阈值 |
|
||||
| 581605 | PERCENT 类型时折扣率不能为空 | discountType=PERCENT 但未传 discountPercent |
|
||||
| 581606 | {0} 类型时优惠金额不能为空 | 立减类型但未传 discountAmount,{0} 为类型名 |
|
||||
| 581607 | 最高人数不能小于最低人数 | maxPeople < minPeople |
|
||||
| 581608 | 结束日期不能早于开始日期 | endDate < startDate |
|
||||
| 581609 | 适用人群包含非法取值:{0} | applicableTravelerTypes 含未知 code |
|
||||
| 581610 | 产品已存在生效时间与人数区间重叠的早鸟计划,不能重复创建或启用 | 同一产品同时间段同人数区间已有启用计划(防同订单冲突) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功 — 创建整单立减计划
|
||||
|
||||
请求:
|
||||
|
||||
POST /v3/admin/order/early-bird
|
||||
Content-Type: application/json
|
||||
|
||||
请求体:
|
||||
|
||||
{
|
||||
planName: 暑期早鸟立减300,
|
||||
remark: 7月出发整单立减300元,
|
||||
discountType: AMOUNT_TOTAL,
|
||||
discountAmount: 300,
|
||||
priority: 10,
|
||||
minPeople: 2,
|
||||
maxPeople: 6,
|
||||
startDate: 2026-07-01,
|
||||
endDate: 2026-07-31,
|
||||
productIds: [1900000000001, 1900000000002],
|
||||
applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD]
|
||||
}
|
||||
|
||||
响应:
|
||||
|
||||
{
|
||||
code: 200,
|
||||
msg: success,
|
||||
data: {
|
||||
planId: 1920000000001234,
|
||||
planName: 暑期早鸟立减300,
|
||||
discountType: AMOUNT_TOTAL,
|
||||
discountAmount: 300.00,
|
||||
discountPercent: null,
|
||||
priority: 10,
|
||||
minPeople: 2,
|
||||
maxPeople: 6,
|
||||
peopleRangeText: 2-6 人,
|
||||
discountText: 整单立减 300 元,
|
||||
startDate: 2026-07-01,
|
||||
endDate: 2026-07-31,
|
||||
enabled: true,
|
||||
applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD],
|
||||
applicableTravelerTypesText: 成人/儿童/小童,
|
||||
productIds: [1900000000001, 1900000000002],
|
||||
productNames: [云南大理亲子7日游, 丽江雪山5日徒步],
|
||||
products: [
|
||||
{productId: 1900000000001, name: 云南大理亲子7日游},
|
||||
{productId: 1900000000002, name: 丽江雪山5日徒步}
|
||||
],
|
||||
createTime: 2026-06-22T10:00:00
|
||||
}
|
||||
}
|
||||
|
||||
注意:discountAmount 在响应 JSON 中已序列化为字符串(300.00),planId 同理(1920000000001234)。前端用字符串接收,勿用 Number 解析雪花 ID。
|
||||
|
||||
### 8.2 边界情况 — 折扣率计划,不限人数,不限产品
|
||||
|
||||
maxPeople 不传=不限;productIds 不传=不限产品;applicableTravelerTypes 不传=后端自动填充默认值。
|
||||
|
||||
请求体:
|
||||
|
||||
{
|
||||
planName: 年底全产品9折,
|
||||
discountType: PERCENT,
|
||||
discountPercent: 90,
|
||||
minPeople: 1,
|
||||
startDate: 2026-12-01,
|
||||
endDate: 2026-12-31
|
||||
}
|
||||
|
||||
响应(关键字段):
|
||||
|
||||
{
|
||||
code: 200,
|
||||
data: {
|
||||
planId: 1920000000001235,
|
||||
discountType: PERCENT,
|
||||
discountAmount: null,
|
||||
discountPercent: 90.00,
|
||||
maxPeople: null,
|
||||
peopleRangeText: 1 人及以上,
|
||||
discountText: 9 折优惠,
|
||||
applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD],
|
||||
productIds: [],
|
||||
products: []
|
||||
}
|
||||
}
|
||||
|
||||
### 8.3 业务失败 — 产品时间区间与人数区间均重叠(错误码 581610)
|
||||
|
||||
同一产品在同一时间段、同一人数区间已存在启用状态的早鸟计划,新建或重新启用时返回:
|
||||
|
||||
请求体:
|
||||
|
||||
{planName:冲突测试,discountType:AMOUNT_TOTAL,discountAmount:100,minPeople:2,maxPeople:5,startDate:2026-07-01,endDate:2026-07-15,productIds:[1900000000001]}
|
||||
|
||||
响应:
|
||||
|
||||
{code:581610,msg:产品(ID=1900000000001)已存在生效时间与人数区间重叠的早鸟计划(planId=1920000000001234),不能重复创建或启用,data:null}
|
||||
|
||||
前端按 code === 200 判断成功,失败时把 msg 直接 toast 展示即可,后端消息已是可读中文。
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 管理员新建 / 修改 / 删除 / 查询早鸟优惠配置
|
||||
- 对已有计划快速启停
|
||||
- 查询当前所有早鸟计划状态列表
|
||||
|
||||
**不适用场景**:
|
||||
- 小程序 C 端用户查看早鸟优惠展示(走内部 Feign 接口,前端无需关心)
|
||||
- 下单时自动匹配优惠(订单服务内部逻辑,前端无需干预)
|
||||
- 已下单订单的优惠金额追溯修改(早鸟仅下单时一次性结算,改计划不追溯已有订单)
|
||||
|
||||
**特殊边界**:
|
||||
- 同一产品在同一时间段 + 同一人数区间内,只允许存在一个启用状态的早鸟计划;禁用状态不参与冲突检测;人数区间不重叠(阶梯档)不受限制
|
||||
- 修改或删除计划不影响已下单订单中已应用的早鸟优惠金额
|
||||
- applicableTravelerTypes 传空或不传,后端自动填充 [ADULT, CHILD, YOUNG_CHILD] 并持久化到数据库
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比(前端迁移指引)
|
||||
|
||||
核心结论:早鸟管理页面 UI / 字段 / 交互零改动,唯一动作 = 切换接口路径。
|
||||
|
||||
| 项目 | v2(旧) | v3(新) | 前端是否需要改动 |
|
||||
|------|---------|---------|----------------|
|
||||
| 列表接口路径 | /admin/order/early-bird 或 /admin/order/early-bird/list | /v3/admin/order/early-bird/list | 是,必须加 /v3 前缀且必须带 /list 后缀 |
|
||||
| 详情接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是,加 /v3 前缀 |
|
||||
| 创建接口路径 | /admin/order/early-bird | /v3/admin/order/early-bird | 是,加 /v3 前缀 |
|
||||
| 修改接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是,加 /v3 前缀 |
|
||||
| 删除接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是,加 /v3 前缀 |
|
||||
| 启/禁用接口路径 | /admin/order/early-bird/{planId}/toggle | /v3/admin/order/early-bird/{planId}/toggle | 是,加 /v3 前缀 |
|
||||
| 请求参数(入参字段) | 不变 | 完全一致 | 否 |
|
||||
| 响应字段(出参字段) | 不变 | 完全一致 | 否 |
|
||||
| 枚举值 / 数据字典 | 不变 | 完全一致 | 否 |
|
||||
| 页面 UI / 交互逻辑 | 不变 | 完全一致 | 否 |
|
||||
|
||||
**前端迁移三步走**:
|
||||
|
||||
1. 在 v3 管理后台新建「早鸟优惠」菜单入口(页面可直接照搬 v2 早鸟优惠管理页面,UI / 字段 / 交互不变;v3 管理后台为二期独立前端工程,菜单需要新增,不会从 v2 自动带过来)
|
||||
2. 将所有早鸟管理接口的请求路径加 /v3 前缀(/admin/order/early-bird* 改为 /v3/admin/order/early-bird*)
|
||||
3. 列表接口如果之前调的是空路径 /admin/order/early-bird(无 /list 后缀),改为 /v3/admin/order/early-bird/list
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 破坏兼容 | 无,v3 接口为新增路径,v2 接口同期仍可访问 |
|
||||
| 前端同步上线 | 无强制同步要求;v3 接口上线后可随时切换,v2 继续可用期间两者均可访问 |
|
||||
| 回滚方案 | 如切换后遇到问题,将请求路径回退到 v2(去掉 /v3 前缀)即可立即恢复;后端 v2 接口未下线 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 列表接口路径必须带 /list:v3 只有 /v3/admin/order/early-bird/list,调 /v3/admin/order/early-bird(无后缀)返回 404。这是 v2 到 v3 唯一的路径语义差异。
|
||||
2. discountPercent 陷阱:discountPercent=80 表示打 8 折,不是优惠 80%。页面展示请转换为 8折,切勿直接显示 80%。
|
||||
3. 金额字段为字符串:discountAmount、planId 在响应 JSON 中均已序列化为字符串。前端接收时用字符串类型,避免 JS 精度丢失。
|
||||
4. PUT 全字段覆盖:修改接口不支持 PATCH,未传的选填字段会被重置为 null / 默认值。前端编辑弹窗提交时需确保把所有当前值一并回传。
|
||||
5. toggle 启用触发冲突校验:将已禁用的计划重新启用时,若同产品 + 同时间段 + 同人数区间已有其他启用计划,接口返回 581610 并拒绝操作,前端需处理该错误码并展示 msg。
|
||||
6. 早鸟不追溯已有订单:管理员修改或删除计划后,已下单订单中已结算的早鸟优惠金额不变,仅影响后续新下单的订单。
|
||||
7. 需新建菜单:v3 管理后台是二期独立前端工程,早鸟优惠菜单不会从 v2 自动继承,前端需在 v3 后台手动新建「早鸟优惠」菜单入口(页面照搬 v2 即可)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| PR #4053 | https://git.1814.love:8443/wx/HL/pulls/4053(早鸟 v3 基础 CRUD 落地) |
|
||||
| PR #4064 | https://git.1814.love:8443/wx/HL/pulls/4064(早鸟管理接口补全) |
|
||||
| PR #4076 | https://git.1814.love:8443/wx/HL/pulls/4076(早鸟管理 CRUD 6 接口完整上线) |
|
||||
| PR #4082 | https://git.1814.love:8443/wx/HL/pulls/4082(早鸟 v3 路径对齐与修复) |
|
||||
| 后端负责人 | yst(腰苏图) |
|
||||
| 变更服务 | hl-order-service-v3(端口 8086,网关统一走 9443) |
|
||||
@@ -0,0 +1,237 @@
|
||||
# 售后工单接入 wx 内容安全机审 — 修改接口(管理后台)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-22
|
||||
- **Issue**:[#4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[#4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[8882eb156](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
售后工单(投诉/申诉)接入微信内容安全机审后,管理后台在查看工单详情或列表时,可以看到每条工单的机审结果(`auditStatus`)。客服可据此判断用户提交内容是否经机审标记为违规,辅助处理决策。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 字段 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ✨ 出参新增 | `GET /v3/admin/aftersale/ticket/{id}` | `auditStatus` | 工单 wx 内容安全机审状态 |
|
||||
| ✨ 出参新增 | `GET /v3/admin/aftersale/ticket/page` | `auditStatus` | 工单列表每条记录新增机审状态 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 工单详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/admin/aftersale/ticket/{id}` |
|
||||
| 接口描述 | 管理后台查看单条售后工单详情 |
|
||||
| 认证 | 需要管理员 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
### 3.2 工单分页列表
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/admin/aftersale/ticket/page` |
|
||||
| 接口描述 | 管理后台分页查询售后工单列表 |
|
||||
| 认证 | 需要管理员 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
本次变更**仅涉及出参**,入参无变化。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### TicketAdminRespVO(工单详情/列表单条)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| id | Long | 工单 ID |
|
||||
| orderId | Long | 关联订单 ID |
|
||||
| orderNo | String | 订单编号 |
|
||||
| category | String | 工单分类枚举:COMPLAINT / APPEAL |
|
||||
| type | String | 工单类型 |
|
||||
| title | String | 标题 |
|
||||
| description | String | 描述 |
|
||||
| status | String | 工单状态:SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN |
|
||||
| statusName | String | 工单状态中文名 |
|
||||
| linkedRefundId | Long | 关联退款单 ID(申诉退款回填) |
|
||||
| **auditStatus** | **String** | **wx 内容安全机审状态,取值见 §6** |
|
||||
| attachments | List\<String\> | 附件 URL |
|
||||
| createdAt | String | 创建时间(ISO 8601) |
|
||||
| updatedAt | String | 更新时间(ISO 8601) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### auditStatus — wx 内容安全机审状态
|
||||
|
||||
| 值 | 含义 | 触发条件 |
|
||||
|----|------|---------|
|
||||
| `PENDING` | 机审中 | 工单创建时携带了 mediaTraceIds(等待微信异步回调) |
|
||||
| `APPROVED` | 已通过 | 无媒体附件的工单直接置此;微信回调合规也置此 |
|
||||
| `MANUAL_REVIEW` | 需人工复审 | 微信机审认为需要人工介入 |
|
||||
| `REJECTED` | 已驳回 | 微信机审判定内容违规 |
|
||||
|
||||
> **管理后台可操作建议**:`REJECTED` 的工单表示用户上传的媒体内容被微信判定违规,客服可将此作为处置参考,但工单本身流程不受影响,需客服手动决定是否关闭。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|-------|---------|------|---------|
|
||||
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
|
||||
| 200-002 | 403 | 无权操作 | 非管理员角色 |
|
||||
| 581001 | 404 | 工单不存在 | 工单 ID 无效 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——查询已通过机审的工单详情
|
||||
|
||||
**请求**
|
||||
```http
|
||||
GET /v3/admin/aftersale/ticket/987654321
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654321,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订四星,实际三星,差价未退。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"linkedRefundId": null,
|
||||
"auditStatus": "APPROVED",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg"
|
||||
],
|
||||
"createdAt": "2026-06-22T10:30:00",
|
||||
"updatedAt": "2026-06-22T10:32:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——机审中(PENDING)的工单
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654322,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "临时收费未在合同内。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"linkedRefundId": null,
|
||||
"auditStatus": "PENDING",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/receipt.jpg"
|
||||
],
|
||||
"createdAt": "2026-06-22T10:35:00",
|
||||
"updatedAt": "2026-06-22T10:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `auditStatus=PENDING` 表示微信回调尚未到达,属正常状态,客服可照常处理工单。
|
||||
|
||||
### 8.3 业务失败——工单不存在
|
||||
|
||||
```http
|
||||
GET /v3/admin/aftersale/ticket/9999999999
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581001,
|
||||
"msg": "工单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 客服查看任意用户提交的售后工单,可见机审状态
|
||||
- 列表页可按 `auditStatus` 过滤(需前端实现过滤 UI,后端支持按此字段查询)
|
||||
|
||||
**特殊边界**:
|
||||
- 历史工单(PR #4203 上线前创建)`auditStatus` 为 `null`,前端可展示为「-」或「已通过」
|
||||
- `auditStatus=REJECTED` 仅为机审标记,不影响工单的正常流转,客服仍需手动处置
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比(TicketAdminRespVO)
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| auditStatus | 不存在 | 新增,String,wx 内容安全机审状态 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 维度 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| 机审可见性 | 无任何机审信息 | 管理后台可见用户提交内容的机审结论 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| 破坏兼容性 | 否(出参新增字段,旧版忽略) |
|
||||
| 前端同步上线 | 建议同步展示 auditStatus,辅助客服判断违规内容 |
|
||||
| 回滚方案 | 后端回滚 PR #4203 即可,无数据损失风险 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. `auditStatus=REJECTED` 的工单,客服可参考机审结论处理,但系统不会自动关闭工单。
|
||||
2. 历史工单 `auditStatus` 为 `null`,前端建议展示为「-」。
|
||||
3. `MANUAL_REVIEW` 状态表示微信需要人工介入,平台客服可主动跟进或等待微信反馈。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **关联 Issue**:[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,243 @@
|
||||
# 订单「售后中」展示激活(aftersale_status) — 修改接口(管理后台)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-22
|
||||
- **Issue**:[#4197](https://git.1814.love:8443/wx/HL/issues/4197)
|
||||
- **PR**:[#4208](https://git.1814.love:8443/wx/HL/pulls/4208)
|
||||
- **Commit**:[334272517](https://git.1814.love:8443/wx/HL/commit/3342725174cd706175a2bea0a162eea2c1333206)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
v3 订单主表 `order_main` 有预留字段 `aftersale_status`,此前零写入、无枚举、VO 不输出(字段恒为 null)。本次激活该字段:用户发起任意售后工单(投诉/申诉)时,后端自动将订单置为「售后中」(`IN_PROGRESS`);当该订单所有售后工单全部终结(CLOSED 或 WITHDRAWN)后,自动回切为「售后完成」(`RESOLVED`)。管理后台订单列表和订单详情均新增此字段,可据此实现「售后中」Tab 或标记。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 字段 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ✨ 出参新增 | `GET /v3/admin/order/list`(订单列表) | `aftersaleStatus` | 订单售后汇总状态枚举 |
|
||||
| ✨ 出参新增 | `GET /v3/admin/order/list`(订单列表) | `aftersaleStatusName` | 售后状态中文名 |
|
||||
| ✨ 出参新增 | `GET /v3/admin/order/{id}`(订单详情) | `aftersaleStatus` | 订单售后汇总状态枚举 |
|
||||
| ✨ 出参新增 | `GET /v3/admin/order/{id}`(订单详情) | `aftersaleStatusName` | 售后状态中文名 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 订单列表
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/admin/order/list` |
|
||||
| 接口描述 | 管理后台订单列表(分页),新增售后汇总状态字段 |
|
||||
| 认证 | 需要管理员 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
### 3.2 订单详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/admin/order/{id}` |
|
||||
| 接口描述 | 管理后台订单详情,新增售后汇总状态字段 |
|
||||
| 认证 | 需要管理员 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
本次变更**仅涉及出参**,入参无变化。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### OrderListItemRespVO(订单列表单条)— 新增字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| **aftersaleStatus** | **String** | **订单售后汇总状态枚举,取值见 §6** |
|
||||
| **aftersaleStatusName** | **String** | **售后状态中文名(如「售后中」)** |
|
||||
|
||||
(其余字段不变,以下仅列新增部分)
|
||||
|
||||
### OrderMainVO(订单详情)— 新增字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| **aftersaleStatus** | **String** | **订单售后汇总状态枚举,取值见 §6** |
|
||||
| **aftersaleStatusName** | **String** | **售后状态中文名(如「售后中」)** |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### aftersaleStatus — 订单售后汇总状态
|
||||
|
||||
> 此枚举为状态机字段(非业务分类字段),`aftersaleStatusName` 来自枚举自身的 label,**不走数据字典**。
|
||||
|
||||
| 值 | 中文名(aftersaleStatusName) | 含义 | 触发条件 |
|
||||
|----|------------------------------|------|---------|
|
||||
| `NONE` | 无售后 | 订单无售后工单,或全部已终结且从未有活跃工单 | 初始默认态;DB `null` 也兜底为此 |
|
||||
| `IN_PROGRESS` | 售后中 | 订单存在至少 1 条活跃售后工单(状态非 CLOSED/WITHDRAWN) | 用户发起投诉或申诉时自动置入 |
|
||||
| `RESOLVED` | 售后完成 | 订单所有售后工单均已终结(均为 CLOSED 或 WITHDRAWN) | 最后一条活跃工单关闭/撤回时自动回切 |
|
||||
|
||||
**注意**:`aftersaleStatus` 是订单维度的**汇总状态**,与单条工单的 `status`(SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN)是**两个独立维度**,不要混淆。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|-------|---------|------|---------|
|
||||
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
|
||||
| 200-002 | 403 | 无权操作 | 非管理员角色 |
|
||||
| 580001 | 404 | 订单不存在 | 订单 ID 无效 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——订单列表含售后中状态
|
||||
|
||||
**请求**
|
||||
```http
|
||||
GET /v3/admin/order/list?page=1&size=10
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
**响应(节选单条)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 100,
|
||||
"list": [
|
||||
{
|
||||
"id": "1234567890",
|
||||
"orderNo": "HL20260622001",
|
||||
"status": "PAID",
|
||||
"statusName": "已支付",
|
||||
"aftersaleStatus": "IN_PROGRESS",
|
||||
"aftersaleStatusName": "售后中",
|
||||
"totalAmount": 9800.00,
|
||||
"createdAt": "2026-06-20T08:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——订单无售后工单(默认 NONE)
|
||||
|
||||
**响应(订单详情节选)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "1234567891",
|
||||
"orderNo": "HL20260622002",
|
||||
"status": "CONFIRMED",
|
||||
"statusName": "已确认",
|
||||
"aftersaleStatus": "NONE",
|
||||
"aftersaleStatusName": "无售后"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 历史订单(字段激活前创建)DB 中 `aftersale_status` 为 `null`,后端自动兜底为 `NONE`,前端无需特殊处理。
|
||||
|
||||
### 8.3 业务失败——订单不存在
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9999999999
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 580001,
|
||||
"msg": "订单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 前端订单列表希望展示「售后中」Tab,筛选 `aftersaleStatus=IN_PROGRESS` 的订单
|
||||
- 订单详情页顶部展示售后状态标记
|
||||
- 客服看板中识别需跟进的售后订单
|
||||
|
||||
**不适用场景**:
|
||||
- `aftersaleStatus` 不反映具体工单的处理进度,需查工单列表接口获取详情
|
||||
|
||||
**特殊边界**:
|
||||
- `IN_PROGRESS` 的触发是用户发起任意工单(投诉 or 申诉),只要存在一条非终结工单即为此态
|
||||
- 多条工单的情况:必须全部终结(全部 CLOSED 或 WITHDRAWN)才回切 `RESOLVED`
|
||||
- `RESOLVED` 态表示所有工单已关闭,但不代表用户诉求已满足(仅流程终态)
|
||||
- 后端激活写入逻辑:工单 CLOSED 到终态(5 个路径均已覆盖:管理员驳回/整改完成/用户撤回/OA 驳回/退款成功)
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
**OrderListItemRespVO(订单列表)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| aftersaleStatus | 不存在 | 新增,String,订单售后汇总状态枚举 |
|
||||
| aftersaleStatusName | 不存在 | 新增,String,中文名如「售后中」 |
|
||||
|
||||
**OrderMainVO(订单详情)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| aftersaleStatus | 不存在(字段预留但零输出) | 新增,String,订单售后汇总状态枚举 |
|
||||
| aftersaleStatusName | 不存在 | 新增,String,中文名 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 维度 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| 用户发起售后工单后,订单 aftersaleStatus | 恒为 null(字段无写入) | 自动置为 IN_PROGRESS |
|
||||
| 所有工单终结后,aftersaleStatus | 恒为 null | 自动回切为 RESOLVED |
|
||||
| 订单列表/详情 aftersaleStatus | 不返回此字段 | 返回枚举值 + 中文名 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| 破坏兼容性 | 否(出参新增字段,旧版忽略) |
|
||||
| 前端同步上线 | 建议同步展示:订单列表加「售后中」Tab;订单详情头部加状态标记 |
|
||||
| 回滚方案 | 后端回滚 PR #4208 即可;零 DDL(`aftersale_status` 列早已存在),无数据损失风险 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 历史订单 `aftersale_status` 为 `null`,后端兜底输出 `NONE`,前端不需要判空。
|
||||
2. `aftersaleStatus` 与单条工单的 `status` 字段是两个维度,不要用工单 status 推断订单 aftersaleStatus。
|
||||
3. v3 C 端订单接口(`/v3/mp/orders`)尚未建立,该接口就绪后后端将同步补充此字段,届时会另推 changelog(follow-up)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4197](https://git.1814.love:8443/wx/HL/issues/4197)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4208](https://git.1814.love:8443/wx/HL/pulls/4208)
|
||||
- **Commit**:[https://git.1814.love:8443/wx/HL/commit/3342725174cd706175a2bea0a162eea2c1333206](https://git.1814.love:8443/wx/HL/commit/3342725174cd706175a2bea0a162eea2c1333206)
|
||||
- **关联 Issue**:[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,387 @@
|
||||
# 订单财务明细查询(优惠/增项清单)— 接口说明 + 变更预览历史数据源用法
|
||||
|
||||
- **日期**: 2026-06-22
|
||||
- **端类型**: 管理后台
|
||||
- **服务**: hl-order-service-v3(端口 8086)
|
||||
- **接口路径前缀**: /v3/admin/order
|
||||
- **Issue**: [#4205](https://git.1814.love:8443/wx/HL/issues/4205)
|
||||
- **PR**: [#4207](https://git.1814.love:8443/wx/HL/pulls/4207)
|
||||
- **后端负责人**: 腰苏图(yaosutu)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单增减项弹窗(POST /v3/admin/order/{orderId}/adjustment)需要在打开时先展示该订单的历史增减项明细与合计,让定制师在看到存量数据的前提下再填写本次变更金额。
|
||||
|
||||
本接口 `GET /v3/admin/order/{orderId}/discount-surcharge/list` 承担这一**历史数据源**角色:
|
||||
|
||||
- 打开弹窗 → 前端调本接口(`includeReversed=false`)→ 渲染历史明细列表 + 合计
|
||||
- 定制师填入本次金额 → 前端用 `summary.activeDiscountTotal` / `activeSurchargeTotal` + 本次 amount 实时推算新综合价/应收尾款(本地计算,不调接口)
|
||||
- 定制师确认提交 → 调 POST /adjustment → 响应 `discountAmountAfter` / `surchargeAmountAfter` 即最新合计,直接刷新 UI,不必重查本接口
|
||||
|
||||
与 `22_4205_订单增减项改造-新增接口-管理后台.md`(写入端 /adjustment)配套阅读,两份合起来构成完整的增减项弹窗对接说明。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更类型 | 接口 / 字段 | 说明 |
|
||||
|---|----------|------------|------|
|
||||
| 1 | ✨ 新增字段 | discounts[].itemCode | 优惠字典编码(来自 order_discount_item 字典) |
|
||||
| 2 | ✨ 新增字段 | discounts[].itemName | 优惠字典中文名(如:老客户回馈、临时优惠) |
|
||||
| 3 | ✨ 新增字段 | surcharges[].itemCode | 增项字典编码(来自 order_surcharge_item 字典) |
|
||||
| 4 | ✨ 新增字段 | surcharges[].itemName | 增项字典中文名(如:加项景点、用车费用) |
|
||||
| 5 | 📝 接口说明 | GET /v3/admin/order/{orderId}/discount-surcharge/list | 补充变更预览场景下的标准调用方式 |
|
||||
|
||||
> 本文件重点说明**列表查询**接口的字段含义与调用场景。写入端(POST /adjustment)见同目录另一份 changelog。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/{orderId}/discount-surcharge/list |
|
||||
| 接口名 | 查询订单优惠+附加费清单 |
|
||||
| 认证 | Bearer JWT(管理后台 token) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
| Content-Type | 无请求体 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long | 是 | 订单 ID(雪花 ID,前端以字符串传输防精度丢失) |
|
||||
|
||||
### 4.2 Query 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| includeReversed | Boolean | 否 | true | false=仅返 ACTIVE 有效项;true=含 REVERSED/REVERSAL 历史撤销行。**变更预览弹窗固定传 false** |
|
||||
| category | String | 否 | ALL | 暂未启用,固定传 ALL 或直接不传 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 顶层结构
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| orderId | Long | 订单 ID |
|
||||
| discounts | DiscountVO[] | 历史优惠/减项列表(按 created_at 升序) |
|
||||
| surcharges | SurchargeVO[] | 历史增项/附加费列表(按 created_at 升序) |
|
||||
| summary | SummaryVO | 聚合统计 |
|
||||
|
||||
### 5.2 discounts[] 字段(DiscountVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 主键(雪花 ID,JSON 序列化为字符串) |
|
||||
| discountType | String | 优惠类型,固定值 MANUAL |
|
||||
| itemCode | String | 优惠字典编码(如 LOYAL_CUSTOMER),来自 order_discount_item;存量历史行可能为 null |
|
||||
| itemName | String | 优惠字典中文名(如 老客户回馈);存量历史行可能为 null,用 discountName 兜底 |
|
||||
| discountName | String | 优惠描述文本 |
|
||||
| discountAmount | String | 金额(元,BigDecimal 序列化为字符串,正数) |
|
||||
| sourceType | String | 来源类型:MANUAL=手动录入 |
|
||||
| sourceRefId | Long | 来源关联 ID,MANUAL 时为 null |
|
||||
| status | String | 行状态(ACTIVE / REVERSED / REVERSAL),见 6.1 节 |
|
||||
| reverseRefId | Long | 指向被撤销的原行 ID,仅 REVERSAL 行有值,ACTIVE 时 null |
|
||||
| operatorId | Long | 操作人 ID |
|
||||
| operatorName | String | 操作人姓名快照(写入时取,历史不变) |
|
||||
| createdAt | String | 写入时间(ISO-8601,如 2026-05-10T16:08:32) |
|
||||
|
||||
### 5.3 surcharges[] 字段(SurchargeVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 主键(字符串序列化) |
|
||||
| itemCode | String | 增项字典编码(如 EXTRA_ATTRACTION),来自 order_surcharge_item;存量历史行可能为 null |
|
||||
| itemName | String | 增项字典中文名(如 加项景点);存量历史行可能为 null,用 surchargeName 兜底 |
|
||||
| surchargeName | String | 增项描述文本 |
|
||||
| surchargeAmount | String | 金额(元,String 序列化,正数) |
|
||||
| sourceType | String | 来源类型:MANUAL=手动 / ITINERARY_EDIT=行程编辑联动 |
|
||||
| sourceRefId | Long | 来源关联 ID(MANUAL 时 null,ITINERARY_EDIT 时为行程编辑记录 ID) |
|
||||
| status | String | 行状态(ACTIVE / REVERSED / REVERSAL) |
|
||||
| reverseRefId | Long | 指向被撤销的原行 ID,仅 REVERSAL 行有值 |
|
||||
| operatorId | Long | 操作人 ID |
|
||||
| operatorName | String | 操作人姓名快照 |
|
||||
| createdAt | String | 写入时间(ISO-8601) |
|
||||
|
||||
### 5.4 summary 字段(SummaryVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| activeDiscountTotal | String | 当前生效优惠合计(仅 ACTIVE 行 SUM,String) |
|
||||
| activeSurchargeTotal | String | 当前生效增项合计(仅 ACTIVE 行 SUM,String) |
|
||||
| discountAmountMirror | String | order_main.discount_amount 的镜像值,与 activeDiscountTotal 正常相等,双校验用 |
|
||||
| surchargeAmountMirror | String | order_main.surcharge_amount 的镜像值 |
|
||||
|
||||
**变更预览推算公式(前端本地计算,不调接口):**
|
||||
|
||||
```
|
||||
新优惠合计 = activeDiscountTotal + 本次 discountAmount(减项为正数)
|
||||
新增项合计 = activeSurchargeTotal + 本次 surchargeAmount
|
||||
新综合价 = orderAmount - 新优惠合计 + 新增项合计
|
||||
应收尾款 = 新综合价 - 已收款(前端自有字段)
|
||||
```
|
||||
|
||||
> 必须用 BigNumber / Decimal.js 运算,禁止 JS 原生浮点。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 status 枚举(discounts[].status / surcharges[].status)
|
||||
|
||||
| 值 | 含义 | 前端渲染建议 |
|
||||
|----|------|------------|
|
||||
| ACTIVE | 当前有效 | 正常渲染 |
|
||||
| REVERSED | 已被撤销(原行被抹除) | 仅 includeReversed=true 时返回;渲染删除线 |
|
||||
| REVERSAL | 反向冲销行(撤销动作本身) | 仅 includeReversed=true 时返回;灰显或隐藏 |
|
||||
|
||||
> 变更预览弹窗传 includeReversed=false,后端不返 REVERSED/REVERSAL 行,前端无需再过滤。
|
||||
|
||||
### 6.2 sourceType 枚举
|
||||
|
||||
| 值 | 来源场景 |
|
||||
|----|--------|
|
||||
| MANUAL | 定制师在订单增减项弹窗手动录入 |
|
||||
| ITINERARY_EDIT | 行程编辑模块联动产生的费用增减 |
|
||||
|
||||
### 6.3 order_discount_item 字典(减项选项)
|
||||
|
||||
> 通过 GET /admin/dict/data/order_discount_item 动态获取;以下为发版参考值。
|
||||
|
||||
| dictValue(itemCode) | dictLabel(itemName) |
|
||||
|----------------------|----------------------|
|
||||
| LOYAL_CUSTOMER | 老客户回馈 |
|
||||
| PROMO_CODE | 优惠码减免 |
|
||||
| GROUP_DISCOUNT | 团体优惠 |
|
||||
| HOLIDAY_PROMO | 节假日促销 |
|
||||
| REFERRAL | 推荐奖励 |
|
||||
| COMPLAINT_COMP | 投诉补偿 |
|
||||
| MANUAL_ADJUST | 临时调价 |
|
||||
| OTHER_DISCOUNT | 其他优惠 |
|
||||
|
||||
### 6.4 order_surcharge_item 字典(增项选项)
|
||||
|
||||
| dictValue(itemCode) | dictLabel(itemName) |
|
||||
|----------------------|----------------------|
|
||||
| EXTRA_ATTRACTION | 加项景点 |
|
||||
| VEHICLE_FEE | 用车费用 |
|
||||
| ACCOMMODATION | 住宿升级 |
|
||||
| CATERING | 餐饮加项 |
|
||||
| SERVICE_UPGRADE | 服务升级 |
|
||||
| OTHER_SURCHARGE | 其他增项 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 前端处理建议 |
|
||||
|--------|-----------|------|------------|
|
||||
| 200 | 200 | 正常(discounts/surcharges 为空数组也返 200) | 渲染空列表 |
|
||||
| 587001 | 404 | 订单不存在 | 关闭弹窗并 toast 订单不存在 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(弹窗打开,includeReversed=false)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/1234567890001/discount-surcharge/list?includeReversed=false
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": 1234567890001,
|
||||
"discounts": [
|
||||
{
|
||||
"id": "8800001234567",
|
||||
"discountType": "MANUAL",
|
||||
"itemCode": "LOYAL_CUSTOMER",
|
||||
"itemName": "老客户回馈",
|
||||
"discountName": "老客户回馈减免 200",
|
||||
"discountAmount": "200.00",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceRefId": null,
|
||||
"status": "ACTIVE",
|
||||
"reverseRefId": null,
|
||||
"operatorId": 30001,
|
||||
"operatorName": "李定制师",
|
||||
"createdAt": "2026-05-10T16:08:32"
|
||||
}
|
||||
],
|
||||
"surcharges": [
|
||||
{
|
||||
"id": "8800009876543",
|
||||
"itemCode": "EXTRA_ATTRACTION",
|
||||
"itemName": "加项景点",
|
||||
"surchargeName": "加项景点:羊卓雍措一日游",
|
||||
"surchargeAmount": "240.00",
|
||||
"sourceType": "MANUAL",
|
||||
"sourceRefId": null,
|
||||
"status": "ACTIVE",
|
||||
"reverseRefId": null,
|
||||
"operatorId": 30001,
|
||||
"operatorName": "李定制师",
|
||||
"createdAt": "2026-05-10T16:12:10"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"activeDiscountTotal": "200.00",
|
||||
"activeSurchargeTotal": "240.00",
|
||||
"discountAmountMirror": "200.00",
|
||||
"surchargeAmountMirror": "240.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
本次新增减项 100 元时,弹窗变更预览本地推算:
|
||||
|
||||
```
|
||||
新优惠合计 = 200.00 + 100.00 = 300.00
|
||||
新增项合计 = 240.00(本次无增项)
|
||||
新综合价 = orderAmount - 300.00 + 240.00
|
||||
```
|
||||
|
||||
### 8.2 边界情况(无历史增减项)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/9999999999001/discount-surcharge/list?includeReversed=false
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"orderId": 9999999999001,
|
||||
"discounts": [],
|
||||
"surcharges": [],
|
||||
"summary": {
|
||||
"activeDiscountTotal": "0.00",
|
||||
"activeSurchargeTotal": "0.00",
|
||||
"discountAmountMirror": "0.00",
|
||||
"surchargeAmountMirror": "0.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 空数组时渲染「暂无历史增减项」;summary 全零,变更预览公式仍正确。
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/0000000000001/discount-surcharge/list
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 587001,
|
||||
"msg": "订单不存在"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
### 适用场景
|
||||
|
||||
- 订单增减项弹窗打开时,拉历史明细和合计用于变更预览(传 includeReversed=false)
|
||||
- 订单详情页财务明细 Tab 展示完整流水(传 includeReversed=true)
|
||||
|
||||
### 不适用场景
|
||||
|
||||
- 本接口只读;录入新增减项必须调 POST /v3/admin/order/{orderId}/adjustment
|
||||
- 不适合用作提交后刷新数据源;POST /adjustment 响应已携带 discountAmountAfter/surchargeAmountAfter,直接用
|
||||
|
||||
### 特殊边界
|
||||
|
||||
- discountAmountMirror / surchargeAmountMirror 是 order_main 持久列快照;极端并发下与 activeTotal 可能短暂不一致,以 Mirror 值为账面权威
|
||||
- includeReversed=true 时,REVERSED 与 REVERSAL 两种行都返回,前端可按 status 差异渲染(删除线 / 灰色)
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 | 影响 |
|
||||
|------|------|------|------|
|
||||
| discounts[].itemCode | 不存在 | 新增 String | 前端可渲染字典标签 chip |
|
||||
| discounts[].itemName | 不存在 | 新增 String | 前端直接展示中文名,无需自行查字典 |
|
||||
| surcharges[].itemCode | 不存在 | 新增 String | 同上 |
|
||||
| surcharges[].itemName | 不存在 | 新增 String | 同上 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| discountName/surchargeName | 纯自由文本 | 由字典 itemName 生成,标准化 |
|
||||
| 变更预览历史合计 | 无此字段,弹窗无法预算 | summary.activeDiscountTotal/activeSurchargeTotal 支撑弹窗实时推算 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 兼容性
|
||||
|
||||
- discounts[] / surcharges[] 新增 itemCode / itemName 为纯增量字段,不影响已渲染旧字段
|
||||
- 存量历史记录 itemCode/itemName 为 null,前端用 discountName/surchargeName 兜底
|
||||
|
||||
### 前端同步上线
|
||||
|
||||
- 变更预览弹窗对接:打开时调本接口(includeReversed=false),拿 summary 用于预览推算
|
||||
- 现有财务明细渲染:按需接入 itemCode/itemName,新旧字段共存,可渐进迁移
|
||||
|
||||
### 回滚方案
|
||||
|
||||
- 后端回滚后 itemCode/itemName 不返回,前端渲染降级为 discountName/surchargeName,无破坏
|
||||
- 无 DDL 变更,回滚零风险
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **金额全部 String**:discountAmount、surchargeAmount、summary 四个合计字段均为字符串。前端必须用 BigNumber/Decimal.js 运算,禁止 JS 原生浮点。
|
||||
|
||||
2. **includeReversed 必须显式传 false**:变更预览弹窗调用时必须明确传 includeReversed=false,后端默认值为 true,不传则返回撤销历史行,导致合计计算错误。
|
||||
|
||||
3. **提交后不必重查本接口**:POST /adjustment 成功响应直接携带 discountAmountAfter / surchargeAmountAfter 最新合计,前端用这两个字段刷新 UI,无需再调 list。
|
||||
|
||||
4. **itemCode/itemName 存量行为 null**:早于本次改造写入的记录 itemCode/itemName 为 null,前端渲染时以 discountName/surchargeName 兜底。
|
||||
|
||||
5. **变更预览推算是前端本地计算**:不需要向后端预查,直接用 summary + 本次输入金额在前端推算,减少往返。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| Issue | [#4205 订单增减项改造](https://git.1814.love:8443/wx/HL/issues/4205) |
|
||||
| PR(写入端 /adjustment) | [#4207 订单增减项改造](https://git.1814.love:8443/wx/HL/pulls/4207) |
|
||||
| 配套 changelog(写入端) | changelogs-v2/2026-06/22_4205_订单增减项改造-新增接口-管理后台.md |
|
||||
| 后端负责人 | 腰苏图(yaosutu) |
|
||||
| Gitea 仓库 | https://git.1814.love:8443/wx/HL |
|
||||
@@ -0,0 +1,69 @@
|
||||
# 【后端已修 + 前端待改·管理后台】配房需求回显修复:段×候选结构 + 晚数 + 候选≠分住 (#4220)
|
||||
|
||||
> **后端 PR**: #4220(已合 dev-v3 + 已部署测试服 + API 实测通过) | **服务**: hl-order-service-v3 | **更新**: 2026-06-22
|
||||
> **前端负责人**: @mmg —— 本文后半「前端待改」是回显 bug,需前端配合改渲染。
|
||||
|
||||
## 1. 背景(前端反馈的回显 bug)
|
||||
|
||||
3 天 2 晚的订单,配房回显出现两类错误:
|
||||
- **订单详情「住宿安排」**:2 晚的行程显示成「第 1-2 晚 / 第 3-4 晚」(共 4 晚)。
|
||||
- **抢单池「配房行程」**:同一段里房控择一的 2 个候选酒店,被标成「同晚分住 1/2、2/2」。
|
||||
|
||||
排查(直查测试库 + 调真实 API)定论:**需求数据本身正确**(2 天、每天 1 段、每段 2 候选)。根因有二——后端缺数据 + 前端渲染口径错。
|
||||
|
||||
**核心语义(务必对齐)**:一晚的用房 = `segments[]`(分住段,**多段才是同晚分住,房数相加**);一个 segment 内 = `candidates[]`(**房控择一的候选,房数/预算不叠加**)。**候选 ≠ 分住**。本例每天只有 1 段、段内 2 候选 → 是「2 个候选择一」,不是「2 晚」也不是「2 个分住」。
|
||||
|
||||
## 2. 后端已修(#4220,数据现已就绪)
|
||||
|
||||
两个真实后端缺陷已修复并部署:
|
||||
|
||||
| # | 缺陷 | 修复 |
|
||||
|---|------|------|
|
||||
| A | 候选 `hotelId` 恒为 `null`(JSON 里 hotelId 是字符串,后端旧解析只认数字跳过了)→ 点名高亮/跳转酒店失效 | 统一解析 String/Number,hotelId 现正确返回 |
|
||||
| B | 订单详情 `/itinerary` 的 `requirement.days[]` 只有扁平 `hotels[]`、无 `hotelName`、无 `segments` → 前端无法区分候选/分住、无法展示完整语句 | 新增 `days[].segments[]`(段×候选)+ `hotels[].hotelName`,对齐房务侧 |
|
||||
|
||||
**实测确认**(`GET /v3/admin/order/{id}/itinerary` 已返回):
|
||||
- `days[].hotels[].hotelId` / `hotelName` 非 null;
|
||||
- 新增 `days[].segments[]`:每段含 `roomCategory`/`roomCategoryLabel`/`roomCount`/`budget`/`remark` + `candidates[]`(`hotelId`/`hotelName`/`remark`)。
|
||||
- 房务侧 `GET /admin/house/orders/{id}` 的 `requirement.current.days[]` 本就有 `segments[]`(同结构),hotelId 现也修复。
|
||||
|
||||
## 3. 前端待改(回显修复)
|
||||
|
||||
### 3.1 订单详情「住宿安排」(行程安排 Tab,源 `/v3/admin/order/{id}/itinerary`)
|
||||
|
||||
- **晚数标签用 `days[].dayNumber`**(第 1 晚 / 第 2 晚)。**严禁把同一天的多个 `hotels[]`/候选条目当成多晚累加** —— 这正是「2 晚显示成第 1-2 / 3-4 晚」的根因(day1 的 2 个候选被当成第 1、2 晚,day2 的 2 候选被当第 3、4 晚)。
|
||||
- **改用新增的 `days[].segments[]` 渲染**:一个 segment 一行(房型 × `roomCount` 间 × `budget` 预算/晚),段内 `candidates[]` 作为「候选酒店(择一)」列表展示(带 `hotelName`)。仅当某天 `segments.length > 1` 才是真·同晚分住(房数相加)。
|
||||
- **完整结构化语句**(解决「语句不全」):每晚 = 房型(`roomCategoryLabel` 优先,降级 `roomCategory`) + `roomCount` 间 + `budget` 预算/晚 + 候选酒店名(`candidates[].hotelName`);整单级 = `specialTags`(诉求)+ `remark`(备注)。
|
||||
- 旧 `hotels[]` 扁平字段后端**加性保留**(不破坏现有渲染),但建议迁移到 `segments[]`。
|
||||
- **「已回配」徽章**:仅当 `assignments[]` 非空才显示。当前需求 `status=PENDING`、`assignments=[]` 时仍显示「已回配」属误导,应改为按实配存在性判定。
|
||||
|
||||
### 3.2 抢单池 / 房务详情「配房行程」(源 `/admin/house/orders/{id}` → `requirement.current.days[]`)
|
||||
|
||||
- **抢单池预览(未抢单)不显示需求酒店名**,只显示行程:`dayNumber` / `stayDate` / `city` / 房型 / 间数 / 预算即可(按反馈「只显示行程就行」)。
|
||||
- 若需展示候选:用 `days[].segments[].candidates[]`,标「**候选 X/N(房控择一)**」,**不要标「同晚分住 X/N」**。「同晚分住」只适用于 `segments.length > 1` 的场景(同一晚住多家、房数相加)。
|
||||
|
||||
### 3.3 「内部留言」tab 展示定制师需求的结构化语句(前端只读卡片)
|
||||
|
||||
需求:房务在「内部留言」tab 能直接看到定制师提出的需求(结构化语句)。**目标格式**(wx 确认):
|
||||
|
||||
```
|
||||
第 1 晚 · 1间 普通标间 · ¥380.00/晚
|
||||
第 2 晚 · 1间 俄式标准房 · ¥280.00/晚
|
||||
诉求:安静楼层、高楼层、景观房、无烟房、含早餐、可加床、含双早、可拆分双床、相邻房间
|
||||
备注:客人怕吵,尽量安排安静房间,要求高
|
||||
```
|
||||
|
||||
- 在「内部留言」tab 顶部加一张**只读「定制师需求」卡片**,用上述格式渲染。**数据源同 §3.1**(`requirement.days[]`:晚=`dayNumber`、间数=段 `roomCount`、房型=`roomCategoryLabel`/`roomCategory`、预算=段 `budget`;诉求=`specialTags`;备注=`remark`)。一段一行,晚数严格按 `dayNumber`。
|
||||
- 这是**只读展示**,复用 §3.1 的渲染逻辑即可,**不需要新建聊天消息 / 不动站内信系统**(与「给定制师留言」的真实消息流分开)。
|
||||
- 注:若产品后续要「定制师提交需求时自动往内部留言**发一条系统消息**」(而非只读卡片),那属站内信/聊天链路(当前 deferred 等新原型),需 wx 另定,不在本次。
|
||||
|
||||
## 4. 影响 / 兼容
|
||||
|
||||
- 后端纯加性(新增 `segments`/`hotelName`,保留 `hotels`),不破坏现有前端;hotelId 由恒 null 变为真值(修复,非破坏)。
|
||||
- 前端改完后回显应为:本例 = 第 1 晚(普通标间×1,¥380,候选:香格里拉/嘉世豪择一)、第 2 晚(俄式标准房×1,¥280,候选:瓦西里民宿/香格里拉择一)。
|
||||
|
||||
## 5. 关联
|
||||
|
||||
- **后端 PR**: [#4220](https://git.1814.love:8443/wx/HL/pulls/4220)(前端反馈驱动,无关联 Issue)
|
||||
- **后端负责人**: @wx | **前端**: @mmg
|
||||
- 「内部留言显示定制师需求」=§3.3 前端只读卡片(不动站内信系统);仅「提交需求自动发系统消息」变体涉 deferred 聊天,wx 另定。
|
||||
@@ -0,0 +1,91 @@
|
||||
# 配房需求面板(定制师侧)前端接入 —— 真实房型 roomTypeId 模型 + 酒店/备注渲染(#4209 / #4216 / #4221,关联 #4220)
|
||||
|
||||
> 变更类型:🔧 后端契约已就绪(部署 + 实测验证)→ 前端接入
|
||||
> 端类型:管理后台(订单详情·定制师侧「行程安排 → 住宿安排」+「调整订单 → 配房需求」面板)
|
||||
> 日期:2026-06-22
|
||||
> 工单:#4209 / #4216 / #4221(关联另一会话 #4220)| 服务:hl-order-service-v3 + hl-resource-service
|
||||
|
||||
---
|
||||
|
||||
## TL;DR(前端这轮要做的)
|
||||
|
||||
1. **住宿安排结构化语句补「酒店名 + 候选备注」**(后端已就绪 + 实测,可立即接)。
|
||||
2. **房型:先选酒店 → 选该酒店真实房型**,候选提交 `roomTypeId`(不再把房型名塞段级)。
|
||||
3. **段级 `roomCategory` 传字典大类码**,别塞房型名。
|
||||
4. **`hotelId` 可空** = 无酒店候选。
|
||||
5. **预算只读**,编辑态显示候选最高协议价。
|
||||
6. **删「城市/区域」输入框**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 住宿安排:补酒店名 + 候选备注(✅ 已就绪 + 实测,可立即接)
|
||||
|
||||
行程 Tab「住宿安排」当前结构化语句「第 N 晚 · X 间 · 房型 · ¥价/晚」**漏了酒店和候选备注**。后端 #4220 已补全段 × 候选结构并部署测试服,实测确认数据齐全:
|
||||
|
||||
`GET /v3/admin/order/{id}/itinerary` → `hotelGroup.requirement.days[]`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"segments": [{
|
||||
"roomCategory": "...", "roomCount": 1, "budget": "380.00", "remark": "段备注",
|
||||
"candidates": [
|
||||
{ "hotelId": "...", "hotelName": "呼伦贝尔香格里拉大酒店", "remark": "候选备注 777555" },
|
||||
{ "hotelId": "...", "hotelName": "海拉尔嘉世豪酒店", "remark": "候选备注 77777222" }
|
||||
]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**前端**:每段渲染 `房型 · X 间 · ¥budget/晚`,并把 `candidates[]` 的**酒店名 + 候选备注**展示出来(多候选平铺),段备注 `segments[].remark` 单列。完整结构见 #4220 changelog。
|
||||
|
||||
## 2. 房型模型:先选酒店 → 真实房型(roomTypeId)
|
||||
|
||||
⚠️ **实测发现当前前端把房型名("普通标间" / "俄式标准房")塞进了段级 `roomCategory`** —— 这不对:`roomCategory` 是房型**大类字典码**,具体房型名属于候选的真实房型。新模型:
|
||||
|
||||
- 候选选定酒店后,调**候选源**拿该酒店的**真实房型列表**(每项含 `roomTypeId` + `roomCategory` 大类 + 房型名 + 协议价)。
|
||||
- 定制师选具体房型 → 候选提交 `roomTypeId` / `roomTypeName` / `protocolPrice`(候选级)。
|
||||
- 段级 `roomCategory` 传**字典大类码**(room_category:STANDARD / SINGLE / TWIN / QUEEN / KING / SUITE / FAMILY / YURT / SPECIAL),**勿传房型名**;**选填**(#4221 放开),无酒店候选时可空。
|
||||
|
||||
> 候选源端点返回真实房型的详细字段契约,以「后端验证后的更新版 changelog」为准,本节先给方向、勿提前联调。
|
||||
|
||||
## 3. 提交契约放开(✅ #4221 已实测)
|
||||
|
||||
`PUT /v3/admin/order/{id}/hotel-requirement`:
|
||||
|
||||
- `segments[].roomCategory`:**选填**(原必填)。
|
||||
- `segments[].candidates[].hotelId`:**选填**(原必填)= **无酒店候选**(定制师只提房数 X 间,酒店留房控统筹)。
|
||||
- 仅 `segments[].roomCount` 必填。
|
||||
|
||||
实测:无酒店候选 / 缺 roomCategory 的提交体均通过校验(不再 400)。
|
||||
|
||||
## 4. 预算(只读)
|
||||
|
||||
- 预算后端按酒店协议价强制覆盖,**前端只读**、传值不作数。
|
||||
- 一段多候选时段预算 = 候选中**最高**协议价(成本上限,房控择一不论选哪家都不超)。编辑态实时显示读候选 `protocolPrice`,多候选取最高。
|
||||
|
||||
## 5. 删「城市/区域」输入框(✅ #4216)
|
||||
|
||||
候选面板的「城市/区域」字段后端已下线,提交体不再接收 → **删除该输入框**。
|
||||
|
||||
---
|
||||
|
||||
## 字段速查
|
||||
|
||||
| 字段 | 位置 | 状态 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `candidates[].hotelName` | 回显 itinerary | ✅ 已就绪 | 候选酒店名 |
|
||||
| `candidates[].remark` | 回显 itinerary | ✅ 已就绪 | 候选备注 |
|
||||
| `segments[].remark` | 回显 itinerary | ✅ 已就绪 | 段备注 |
|
||||
| `candidates[].roomTypeId` | 提交 | 待前端接 | 候选真实房型 ID(候选级) |
|
||||
| `candidates[].roomTypeName` | 提交 | 待前端接 | 候选真实房型名快照 |
|
||||
| `segments[].roomCategory` | 提交 | 待前端改 | 房型大类字典码,选填,**勿塞房型名** |
|
||||
| `candidates[].hotelId` | 提交 | ✅ 已放开 | 选填 = 无酒店候选 |
|
||||
| `segments[].budget` | 提交 | 只读 | 后端协议价覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## 节奏
|
||||
|
||||
- **第 1 / 3 / 4 / 5 项**:后端已合并 + 部署测试服 + 实测验证,可立即接。
|
||||
- **第 2 项(房型 roomTypeId 模型)**:方向已定,候选源真实房型详细字段待后端验证后更新版给全,属较大 reflow,建议与第 1 项分批。
|
||||
@@ -0,0 +1,289 @@
|
||||
# 订单列表售后 Tab — 修改接口(管理后台)
|
||||
|
||||
- **日期**:2026-06-22
|
||||
- **端类型**:管理后台
|
||||
- **Issue**:[#4222](https://git.1814.love:8443/wx/HL/issues/4222)
|
||||
- **PR**:[#4225](https://git.1814.love:8443/wx/HL/pulls/4225)
|
||||
- **Commit**:[82c669b2a](https://git.1814.love:8443/wx/HL/commit/82c669b2a)
|
||||
- **后端负责人**:腰苏图
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
管理后台订单列表原有 5 个 Tab(全部 / 出行前 / 出行中 / 核单 / 异常取消),均以 `orderStatus`(订单状态)为过滤维度。
|
||||
|
||||
本次新增第 6 个「售后」Tab,以 `aftersale_status=IN_PROGRESS` 为过滤维度。售后是**横切(叠加)语义**,与出行阶段正交:一个订单可同时出现在某出行阶段 Tab 和售后 Tab,且售后计数**不计入**「全部」(全部仍 = 出行前 + 出行中 + 核单 + 异常取消四项之和)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 具体内容 |
|
||||
|---|------|----------|----------|
|
||||
| 1 | `GET /v3/admin/order/status-group-counts` | ⚠️ 出参新增 | 返回数组由 5 项变 6 项,末尾新增 `{ group: "AFTERSALE", label: "售后", orderCount: N }` |
|
||||
| 2 | `GET /v3/admin/order` | ✨ 入参枚举新增 | `statusGroup` 新增可选值 `AFTERSALE` |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 接口一:订单列表 Tab 分组计数
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 方法 + 路径 | `GET /v3/admin/order/status-group-counts` |
|
||||
| 功能描述 | 返回各 Tab 的订单计数,供顶部 Tab 展示角标数字 |
|
||||
| 认证 | Bearer Token(管理员 JWT,必填) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无独立限流策略 |
|
||||
|
||||
### 接口二:订单列表
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 方法 + 路径 | `GET /v3/admin/order` |
|
||||
| 功能描述 | 管理后台订单列表,支持多维度过滤与分页 |
|
||||
| 认证 | Bearer Token(管理员 JWT,必填) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无独立限流策略 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 接口一入参(Query 参数,与 GET /v3/admin/order 共用过滤字段)
|
||||
|
||||
> 计数接口忽略分页字段(page / pageSize),只消费过滤字段。
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| keyword | String | 否 | 关键词(订单号 / 客户姓名 / 手机号) |
|
||||
| departureDateFrom | String | 否 | 出发日期起(yyyy-MM-dd) |
|
||||
| departureDateTo | String | 否 | 出发日期止(yyyy-MM-dd) |
|
||||
| consultantName | String | 否 | 定制师姓名 |
|
||||
| createSource | String | 否 | 订单来源(字典值) |
|
||||
| tagNames | Array\<String\> | 否 | 标签名列表,多值 AND |
|
||||
|
||||
### 4.2 接口二入参(Query 参数)
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | Integer | 否 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 每页数量,默认 20 |
|
||||
| keyword | String | 否 | 关键词 |
|
||||
| departureDateFrom | String | 否 | 出发日期起(yyyy-MM-dd) |
|
||||
| departureDateTo | String | 否 | 出发日期止(yyyy-MM-dd) |
|
||||
| consultantName | String | 否 | 定制师姓名 |
|
||||
| createSource | String | 否 | 订单来源字典值 |
|
||||
| tagNames | Array\<String\> | 否 | 标签名列表 |
|
||||
| orderStatus | String | 否 | 订单状态下拉(与 statusGroup 并存时为 AND) |
|
||||
| statusGroup | String | 否 | Tab 分组标识,见枚举表,**本次新增 `AFTERSALE`** |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 接口一出参
|
||||
|
||||
`Result<List<StatusGroupCountVO>>`,数组共 **6 项**(固定顺序)。
|
||||
|
||||
**StatusGroupCountVO 字段:**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| group | String | 分组标识,见枚举表 |
|
||||
| label | String | 分组中文标签(直接展示) |
|
||||
| orderCount | long | 该分组订单数 |
|
||||
|
||||
**6 项固定顺序:**
|
||||
|
||||
| 顺序 | group | label | 语义 |
|
||||
|------|-------|-------|------|
|
||||
| 1 | ALL | 全部 | 全部订单(= 出行前+出行中+核单+异常取消之和,**不含**售后独立计数) |
|
||||
| 2 | BEFORE_TRIP | 出行前 | orderStatus IN (PENDING_PAY, CUSTOMIZING, PENDING_DEPARTURE) |
|
||||
| 3 | ON_TRIP | 出行中 | orderStatus = TRAVELLING |
|
||||
| 4 | SETTLEMENT | 核单 | orderStatus = COMPLETED |
|
||||
| 5 | ABNORMAL | 异常/取消 | orderStatus = CANCELLED |
|
||||
| 6 | AFTERSALE | 售后 | aftersale_status = IN_PROGRESS(横切,不限出行阶段) |
|
||||
|
||||
### 接口二出参
|
||||
|
||||
`PageResult<OrderListItemVO>`,字段结构与本次无变化。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### statusGroup 枚举值(接口二入参 + 接口一出参 group 字段)
|
||||
|
||||
| 枚举值 | 含义 | 过滤维度 | 是否横切 |
|
||||
|--------|------|----------|----------|
|
||||
| ALL | 全部 | 不过滤(含取消) | 否 |
|
||||
| BEFORE_TRIP | 出行前 | orderStatus IN (PENDING_PAY, CUSTOMIZING, PENDING_DEPARTURE) | 否 |
|
||||
| ON_TRIP | 出行中 | orderStatus = TRAVELLING | 否 |
|
||||
| SETTLEMENT | 核单 | orderStatus = COMPLETED | 否 |
|
||||
| ABNORMAL | 异常/取消 | orderStatus = CANCELLED | 否 |
|
||||
| **AFTERSALE** | **售后(新增)** | **aftersale_status = IN_PROGRESS** | **是** |
|
||||
|
||||
**横切说明**:`AFTERSALE` 不按 orderStatus 过滤,而是按 `aftersale_status=IN_PROGRESS` 过滤。一个出行中的订单如果同时有在途售后工单,该订单会出现在「出行中」Tab 和「售后」Tab 两处。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 401 | 未认证 | 未携带或 Token 失效 |
|
||||
| 403 | 无权限 | 非管理员角色访问 |
|
||||
| 422 | 参数校验失败 | statusGroup 传了无法识别的值(后端宽松处理:未知 group 忽略,不报 422;前端只传枚举表中的值即可) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——获取各 Tab 计数(含售后)
|
||||
|
||||
**请求:**
|
||||
```
|
||||
GET /v3/admin/order/status-group-counts
|
||||
Authorization: Bearer <admin_token>
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{ "group": "ALL", "label": "全部", "orderCount": 120 },
|
||||
{ "group": "BEFORE_TRIP","label": "出行前", "orderCount": 50 },
|
||||
{ "group": "ON_TRIP", "label": "出行中", "orderCount": 30 },
|
||||
{ "group": "SETTLEMENT", "label": "核单", "orderCount": 35 },
|
||||
{ "group": "ABNORMAL", "label": "异常/取消","orderCount": 5 },
|
||||
{ "group": "AFTERSALE", "label": "售后", "orderCount": 8 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:ALL.orderCount(120) = 50+30+35+5 = 120,不含 AFTERSALE 的 8。
|
||||
|
||||
### 8.2 边界情况——无任何在途售后工单
|
||||
|
||||
**请求:**
|
||||
```
|
||||
GET /v3/admin/order/status-group-counts
|
||||
Authorization: Bearer <admin_token>
|
||||
```
|
||||
|
||||
**响应(售后 orderCount = 0):**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "group": "ALL", "label": "全部", "orderCount": 30 },
|
||||
{ "group": "BEFORE_TRIP","label": "出行前", "orderCount": 20 },
|
||||
{ "group": "ON_TRIP", "label": "出行中", "orderCount": 8 },
|
||||
{ "group": "SETTLEMENT","label": "核单", "orderCount": 2 },
|
||||
{ "group": "ABNORMAL", "label": "异常/取消","orderCount": 0 },
|
||||
{ "group": "AFTERSALE", "label": "售后", "orderCount": 0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败——筛售后 Tab + 订单状态 AND 组合
|
||||
|
||||
**请求(出行中且在售后的单):**
|
||||
```
|
||||
GET /v3/admin/order?statusGroup=AFTERSALE&orderStatus=TRAVELLING&page=1&pageSize=20
|
||||
Authorization: Bearer <admin_token>
|
||||
```
|
||||
|
||||
**响应(无出行中的在售后单时返回空列表):**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 0,
|
||||
"records": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- `GET /v3/admin/order/status-group-counts` 在任意过滤条件下均会返回 6 项(含 AFTERSALE)
|
||||
- `GET /v3/admin/order` 传 `statusGroup=AFTERSALE` 返回所有 `aftersale_status=IN_PROGRESS` 的订单,不限出行阶段(含已取消订单,只要售后工单仍在途)
|
||||
|
||||
**不适用:**
|
||||
- 售后 Tab 不会从其他 Tab 移走订单:用户在「出行中」Tab 看到的订单不会因为开了售后工单而消失
|
||||
- 售后计数不纳入「全部」总数,前端角标与「全部」Tab 数字不加 AFTERSALE.orderCount
|
||||
|
||||
**特殊边界:**
|
||||
- `statusGroup=AFTERSALE` 与 `orderStatus=XXX` 同时传为 AND 关系(先按 aftersale_status=IN_PROGRESS 过滤,再按 orderStatus 收窄)
|
||||
- `statusGroup=ALL` 时包含取消的单,即 `cancelled=true`,行为不变
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 出参对比(接口一 status-group-counts)
|
||||
|
||||
**字段级:**
|
||||
|
||||
| 字段/项 | 修改前 | 修改后 |
|
||||
|---------|--------|--------|
|
||||
| 返回数组长度 | 5 项(ALL + 4 阶段) | **6 项**(ALL + 4 阶段 + 售后) |
|
||||
| 第 6 项 group | 无 | `AFTERSALE` |
|
||||
| 第 6 项 label | 无 | `售后` |
|
||||
| 第 6 项 orderCount | 无 | long,`aftersale_status=IN_PROGRESS` 的订单数 |
|
||||
| ALL.orderCount 口径 | 四阶段之和 | **不变**,仍 = 四阶段之和,不含 AFTERSALE |
|
||||
|
||||
**行为级:**
|
||||
|
||||
| 行为 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 下拉菜单 statusGroup 可选值 | ALL / BEFORE_TRIP / ON_TRIP / SETTLEMENT / ABNORMAL | 同上 + **AFTERSALE** |
|
||||
| 传 AFTERSALE 的过滤逻辑 | 传入忽略(未知 group) | 按 `aftersale_status=IN_PROGRESS` 过滤 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
**前端同步上线:**
|
||||
- 若前端对 status-group-counts 返回长度做了硬编码(如 `data.length === 5`)或索引访问,需更新为动态渲染
|
||||
- 需在 Tab 列表末尾渲染新的「售后」Tab,点击时传 `statusGroup=AFTERSALE`
|
||||
- ALL.orderCount 口径不变,前端逻辑无需调整
|
||||
|
||||
**兼容性:**
|
||||
- 不传 statusGroup 或传旧值的请求行为不变,向下兼容
|
||||
- status-group-counts 固定多返回 1 项,前端旧版不会报错(JSON 数组多一项无副作用),但售后 Tab 不会渲染,属预期降级
|
||||
|
||||
**回滚方案:**
|
||||
- 后端回滚:恢复 `OrderStatusGroup` 枚举(删除 AFTERSALE),countByStatusGroup 重新返回 5 项
|
||||
- 前端回滚:无感知(旧前端代码仍正常,只是不展示售后 Tab)
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **横切语义,计数不累加到 ALL**:前端不要把 AFTERSALE.orderCount 加到 ALL 的角标里,否则显示总数虚高。
|
||||
|
||||
2. **AND 过滤**:`statusGroup=AFTERSALE` 与 `orderStatus` 下拉同时传是合法且有意义的用法(筛"出行中且在售后"),前端过滤 UI 无需屏蔽两者同时选择。
|
||||
|
||||
3. **硬编码 5 个 Tab 需更新**:若之前按固定 5 项渲染 Tab,须改为遍历 API 返回数组动态渲染,否则「售后」Tab 不出现。
|
||||
|
||||
4. **售后 Tab 包含已取消订单**:`aftersale_status=IN_PROGRESS` 的订单不论 orderStatus 是什么(含 CANCELLED)都会出现在售后 Tab。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4222 管理后台订单列表加「售后」Tab](https://git.1814.love:8443/wx/HL/issues/4222)
|
||||
- **PR**:[#4225 feat(order-v3): 管理后台订单列表加「售后」Tab](https://git.1814.love:8443/wx/HL/pulls/4225)
|
||||
- **Commit**:[399288d4d](https://git.1814.love:8443/wx/HL/commit/399288d4d97b26238d292b7c7768b69b67f46300)
|
||||
- **关联 PR**:[#4197 订单售后中展示 aftersale_status(售后状态激活)](https://git.1814.love:8443/wx/HL/pulls/4197)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,185 @@
|
||||
# 房务「联系房务」面板真实化:真实在线态 + 抢单前发消息 + claimerId(已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 新增/修改接口(已部署测试服并 API 验证,可对接)
|
||||
> 端类型:管理后台(订单详情·定制师侧「联系房务」聊天面板)
|
||||
> 日期:2026-06-22 | 工单:#4223 | PR:#4247 | 服务:hl-user-service(聊天/在线态)、hl-order-service-v3(订单详情/抢单)
|
||||
> 关联:本条**取代**同日「需求澄清·实现中」版(那版的"先别删在线态"结论已落地为下方真实契约)。
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
此前「联系房务」面板的房务专员/在线态是前端假数据。wx 拍板:**在线/离线要做成真的**(不是删功能),并加「抢单前也能发消息、抢单后一并送达房务」。后端已补真实能力,本条是正式对接契约。
|
||||
|
||||
## 三态总览(前端按此渲染)
|
||||
| 抢单状态 | 房务专员 | 在线态 | 发消息 |
|
||||
|---|---|---|---|
|
||||
| **抢单前**(claimerId=null) | 显示「未知」 | 不显示 | **可发**,存订单房务会话,抢单后送达 |
|
||||
| **抢单后**(claimerId 有值) | 真实接单人姓名 | **真实在线/离线**(peerOnline) | 正常聊 |
|
||||
| **claimerId==当前登录人**(你既定制师又抢了本单房务) | 不显示「联系房务」 | — | 不能跟自己聊 |
|
||||
|
||||
## 1. 订单详情新增 `claimerId`
|
||||
`GET /v3/admin/order/{id}/itinerary` 的住宿需求摘要 `hotelRequirementBrief` **新增** `claimerId`(接单房务 adminId,**String** 雪花;未抢单为 `null`)。
|
||||
- `claimerId == null` → 前端房务专员显示「**未知**」、不显示在线点、「联系房务」可引导但开的是 pending 会话(见 §3)。
|
||||
- `claimerId == 当前登录 adminId` → 前端**隐藏/置灰**「联系房务」(本单房务是你自己)。
|
||||
|
||||
## 2. 聊天出参新增 `peerOnline`(真实在线态)
|
||||
`POST /admin/message/chat/open`、`/open-house`、`GET /admin/message/chat/conversations` 出参新增 `peerOnline`(Boolean)。
|
||||
- 语义:对方(房务专员)**当前登录后台且有活跃实时(SSE)连接 = 在线**;离线靠 Redis TTL 反映(**near-live,最多约 45s 延迟**,本期不做在线态即时推送)。
|
||||
- 前端:`peerOnline==true` 绿点在线 / `false` 灰点离线;抢单前(peer 为占位)恒 false、配合「未知」不显示状态即可。
|
||||
|
||||
## 3. 新端点 `POST /admin/message/chat/open-house`(HOUSE 会话订单维度)
|
||||
**HOUSE 房务会话改为订单维度单会话**(键 `HOUSE:{orderId}`,一单一条;**「联系房务」与「联系定制师」收敛到同一条**,不再分叉)。FLEET/DIRECT 不变。
|
||||
|
||||
`POST /admin/message/chat/open-house`
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | String | ✅ | 订单 id(房务会话维度) |
|
||||
| peerAdminId | String | 否 | 对端 adminId:**抢单前定制师开聊不传**(pending);抢单后传 `claimerId`(定制师→房务)或 `consultantId`(房务→定制师) |
|
||||
|
||||
出参(实测):`{conversationKey, peerAdminId, peerName, peerRole, peerOnline, unreadCount, isNew}`。
|
||||
- 抢单前不传 peerAdminId → 返 `peerAdminId:0, peerName:null, peerRole:"HOUSE", peerOnline:false`(占位「待接单房务」),定制师即可发消息(走现有 `POST /chat/{conversationKey}/messages`)。
|
||||
- `peerAdminId==自己` → 281005(不能跟自己开会话)。
|
||||
|
||||
## 4. 抢单前发消息 · 抢单/转单后送达(后端自动,前端无需特殊处理)
|
||||
- 抢单前定制师在 `HOUSE:{orderId}` 会话发的消息先暂存。
|
||||
- 房务**抢单**后:后端自动把历史消息变成该房务的未读 + SSE `im-chat` 通知;定制师/房务双方此后正常聊。
|
||||
- **转单**:会话历史跟随交给新房务(前端拉 `GET /chat/{conversationKey}/messages` 即见全历史)。
|
||||
- 边界(定制师==房务本人抢单):消息仅作历史、不自发未读。
|
||||
- 前端只需:抢单前用 open-house(pending) 让定制师能发;抢单后用 `claimerId` open-house 正常聊。**送达逻辑后端全包**。
|
||||
|
||||
## 5. curl 实测(2026-06-22 测试服,过网关 9443)
|
||||
```bash
|
||||
# A) 订单详情 claimerId
|
||||
curl 'https://api.test.1814.love:9443/v3/admin/order/2068234602970828802/itinerary' -H 'Authorization: Bearer <token>'
|
||||
# → 200, hotelRequirementBrief.claimerId 字段存在(未抢单为 null)
|
||||
|
||||
# B) open-house(抢单前 pending)
|
||||
curl -X POST 'https://api.test.1814.love:9443/admin/message/chat/open-house' \
|
||||
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' -d '{"orderId":"2068234602970828802"}'
|
||||
# → 200 {"conversationKey":"HOUSE:2068234602970828802","peerAdminId":0,"peerName":null,
|
||||
# "peerRole":"HOUSE","peerOnline":false,"unreadCount":0,"isNew":true}
|
||||
```
|
||||
|
||||
## 6. 前端处理建议
|
||||
- 房务专员名:`claimerId` 空→「未知」;非空→真实姓名(姓名走会话 `peerName`)。
|
||||
- 在线点:读 `peerOnline`(抢单后才有意义);抢单前/未知不显示。
|
||||
- 「联系房务」:`claimerId==当前人`→隐藏;否则 open-house(抢单前不传 peerAdminId、抢单后传 claimerId)。
|
||||
- 「联系定制师」(房务侧):改走 open-house(传 consultantId),与「联系房务」同一条订单会话。
|
||||
- 删除写死的「舒心 在线」假数据。
|
||||
|
||||
## 7. 影响 / 回滚
|
||||
- 兼容性:均为加字段 / 加端点,旧前端不读新字段不受影响。
|
||||
- **无 DB 迁移**(复用 admin_message / admin_conversation_member)。回滚 = revert PR #4247 重新部署 hl-user-service + hl-order-service-v3。
|
||||
|
||||
---
|
||||
|
||||
## 8. 补充裁决(2026-06-23 · 复核 + 答前端阻塞,本篇为唯一权威)
|
||||
|
||||
前端反馈本篇与同日另一篇 `23_定制师侧联系房务…管理后台前端.md` 契约打架(两个 open 端点 + 抢单前/后语义相反),无法切共享聊天基础设施。重新实测测试服后裁决:
|
||||
|
||||
**① 以本篇(22_4223 / open-house)为唯一权威。** 同日那篇 `23_…` **作废已删**——它基于**落后 90 个提交的 stale 代码**误写(写成 `POST /chat/open` + `bizModule/peerAdminId` + 「抢单后才能聊」),与真实部署不符。
|
||||
|
||||
**② 端点(实测 live,2026-06-23 过网关 9443)**:
|
||||
`POST /admin/message/chat/open-house {"orderId":"…"}` → `200 {"conversationKey":"HOUSE:{orderId}","peerAdminId":0,"peerName":null,"peerRole":"HOUSE","peerOnline":false,"unreadCount":0,"isNew":…}`。
|
||||
HOUSE 订单会话**只走 open-house**。通用 `/chat/open`(bizModule+peerAdminId,@NotNull)仍在,但用于 FLEET/DIRECT/显式 peer,**HOUSE 别用它**。
|
||||
|
||||
**③ 门控(更正「抢单后才能聊」的误解)**:定制师侧「联系房务」**抢单前也能发消息**——open-house 不传 peerAdminId → pending 占位「待接单房务」,消息暂存,房务抢单后后端自动送达。所以**抢单前按钮照常可点可发**,只是对端显「未知/待接单房务」、不显在线点;**不要禁用按钮**。仅 `claimerId == 当前登录人` 时隐藏(自己别跟自己聊)。
|
||||
(注:与房务侧「房务管家」未抢单隐藏入口不冲突——房务在抢单池里没理由先聊;能抢单前留言的是**定制师**单向投递。)
|
||||
|
||||
**④ 在线态怎么拿(答「轮询还是 SSE」)**:读响应里的 `peerOnline`(Boolean)(open-house / conversations 都返)。它是**真实在线态**(后端 `adminPresenceService` Redis presence,对方有活跃后台连接=在线),**near-live、约 45s TTL 延迟**。刷新方式 = **轮询 / 重新拉**(re-open-house 或 re-conversations)。**没有专用在线态 SSE 推送**(本期不做)。消息实时到达走站内信 `im-chat` SSE(与在线态是两回事)。抢单前 peer 占位恒 `false`,配「未知」不显在线点即可。
|
||||
|
||||
**⑤ FLEET 车务聊天:未切真(deferred)。** 代码实证 `ConversationMemberService` 注「fleet 接入 deferred」——FLEET 会话的订单摘要 / 真实对端整合**没做**。通用 `/chat/open` 虽接受 `bizModule=FLEET`,但**无真实车务数据对接**。**前端 FLEET 聊天先别做切真**,等车务接入工单(wx「车务用了再加」)。mock 里没有 SSE/在线态属正常——FLEET 整条都还没切真。
|
||||
|
||||
---
|
||||
|
||||
## 9. 切真细节契约(2026-06-23 实测 origin/dev-v3 + LIVE,答前端 Q1/Q2/Q3)
|
||||
|
||||
### Q1 · peerAdminId 强不强制?后端解析对端吗?
|
||||
- **HOUSE 用 `POST /admin/message/chat/open-house`,`peerAdminId` 选填**(不传 = 抢单前 pending 占位「待接单房务」)。**别用通用 `/chat/open`**(它 `peerAdminId` `@NotNull` 强制,只给 FLEET/DIRECT/显式 peer)。
|
||||
- **后端不反查 order-v3 解析对端**(`ChatManager.openHouse` javadoc 明示「前端传 peer,不反查 order-v3」)。对端 id **前端传**,且前端数据里都有:
|
||||
- 定制师→房务:`peerAdminId = claimerId`(来自 `hotelRequirementBrief.claimerId`;抢单前为 null → 不传 = pending)。
|
||||
- 房务→定制师:`peerAdminId = consultantId`(订单/抢单数据里就有)。
|
||||
- 「两端收敛同一条会话」靠键 `HOUSE:{orderId}`(与 peer 无关);但建双方成员行需开方各自传对端 id(抢单前定制师不传 → 房务抢单后 order-v3 afterCommit 自动补房务成员行 + 送达历史,前端无需管)。
|
||||
- **FLEET**:无 open-fleet,通用 open 强制 `peerAdminId`,且 FLEET deferred → **先别做**。
|
||||
- 可选增强:若要前端只传 orderId、后端按 claim/consultant 自动解析对端 → 需后端加 Feign 反查 order-v3,**独立工单待 wx 定**(当前不做,前端传 id 即可跑通)。
|
||||
|
||||
### Q2 · 响应字段(实测对齐 #4020,别凭注释)
|
||||
- 线程 `GET /admin/message/chat/{conversationKey}/messages` → `{conversationKey, hasMore, nextCursor, list[]}`。**是 `list` 不是 `records`**;`list` 按**时间正序**(旧→新,从上往下渲染);游标 `nextCursor` = 本页最小 messageId,作下次 `beforeId` 上翻历史。
|
||||
- 消息项 `list[]`:`{messageId, senderAdminId, senderName, senderRole, msgType(TEXT/IMAGE/SYSTEM), priority(NORMAL/URGENT), content, isMine, sentAt}`。
|
||||
- **`isMine`** = Boolean,后端按 `senderAdminId == 当前登录人` 算(左右气泡布局用)。
|
||||
- **`sentAt`** = **String,格式 `yyyy-MM-dd HH:mm:ss`**(如 `2026-06-16 11:02:31`)——**不是毫秒、不是 ISO-8601**,本地时间到秒、无时区无毫秒。前端按此格式 parse,别当 epoch/ISO。
|
||||
|
||||
### Q3 · SSE 实时(**已有且 LIVE,别再轮询**)
|
||||
- **SSE 端点 `GET /ws/admin-msg/stream`(`text/event-stream`)实测 LIVE(200 + 正确 Content-Type)**。
|
||||
- 发消息:后端 `send` 事务 afterCommit 经 Redis Pub/Sub 广播 **CHAT 信令**给收件方 → SSE 实时冒泡(带 senderName / 预览 / priority + conversationKey)。
|
||||
- **前端应订阅 `/ws/admin-msg/stream`,收到 CHAT 信令即 live-append / 刷新对应会话**,替换「发送后短轮询 6 次(~8s)」的兜底。「没有 SSE」= 没接,不是没有。
|
||||
|
||||
### 在线/离线态走 SSE(wx 2026-06-23 新要求 → 后端工单 **#4273**)
|
||||
- 现状:`peerOnline` 是**轮询字段**(Redis presence key TTL 45s + SSE 心跳续期),**在线态变更不推送**。
|
||||
- wx 拍:改为 **SSE 实时推在线/离线**。已立后端工单 **#4273**(hl-user-service):SSE 连接建/断 → 标在线/离线 → 向会话对端推 `presence` 事件。**前端届时订阅 presence 事件、停 peerOnline 轮询**(保留字段做首屏兜底)。**✅ 已上线测试服,契约见 §12。**
|
||||
|
||||
---
|
||||
|
||||
## 12. ✅ 在线/离线实时 SSE 已上线测试服(2026-06-23 · #4273 · PR #4299 · 已部署实测)
|
||||
|
||||
`peerOnline` 轮询升级为 **SSE 实时推**。前端订阅后**在线点变更实时刷新,停掉 peerOnline 轮询**(`peerOnline` 仅留作首屏兜底)。
|
||||
|
||||
- **SSE 事件**:`presence`(与 `im-chat`/`im-chat-read` 同一条流 `GET /ws/admin-msg/stream`,实测 200 `text/event-stream`)。
|
||||
- **payload**:`{ "type":"PRESENCE", "presenceAdminId":<Long 在线态翻转的人>, "online":<Boolean> }`(另带 `adminId`=收件方路由,前端忽略)。
|
||||
- **语义**:当**与你有活跃会话的某对端**(`presenceAdminId`)上线/下线,你的流就收到一条 `presence` → 把会话列表 / 打开的聊天里**那个人的在线点**实时刷成 `online`。只推「与你有会话关系的人」,不会广播无关员工。
|
||||
- **前端**:订阅 `presence` 事件 → `presenceAdminId` 找到对应会话/对端 → 设其在线点=`online`。首屏仍用 `peerOnline`(open-house / conversations 返)兜底,之后全靠 `presence` 事件维持,**不再轮询**。
|
||||
- **时延**:上线≈即时(建连即推);下线干净断开≈即时、异常掉线≤25s(心跳探测补推)、45s TTL 兜底。
|
||||
- **实测**:部署后 `GET /ws/admin-msg/stream` 200 + `text/event-stream`,服务正常启动(新 presence 广播 bean 无循环依赖)。两端实时翻态属前端联调验。
|
||||
|
||||
---
|
||||
|
||||
## 10.「已读」是前端写死的 bug + 抢单前应显「留言」(2026-06-23 wx 评审,🐛 前端改,后端无需动)
|
||||
|
||||
**现象**(wx 评审,定制师侧「联系房务」面板):订单还在抢单池(无房务接单,对端显「待接单房务」),定制师发「你好 测试消息」,气泡下却显示 `14:05 **已读**`。**没有任何房务接单、对端是占位 0,谁来读?「已读」是错的。**
|
||||
|
||||
**根因(已对 origin/dev-v3 核实)**:**后端从不返回任何「已读/送达」状态**。线程消息 VO 就 §9 Q2 那 9 个字段 `{messageId, senderAdminId, senderName, senderRole, msgType, priority, content, isMine, sentAt}`——**没有 `isRead`/`readAt`/`delivered`/`status`**。`14:05 已读`是**前端在「我方」(`isMine=true`) 气泡下无条件硬拼的**,后端给不了这个信号,所以它永远是假的。
|
||||
|
||||
**正确显示(前端按 claim 状态自行渲染,后端已有的字段够用)**:
|
||||
| 我方消息所处状态 | 判据(前端已有) | 气泡下应显示 |
|
||||
|---|---|---|
|
||||
| **抢单前**(pending) | `claimerId == null`(或 open-house 返 `peerAdminId:0`/`peerName:null`) | 「**留言**」或「留言·待房务接单」——**不显已读** |
|
||||
| **抢单后** | `claimerId` 有值、对端真实 | 「**已送达**」或**什么都不显**——**不显「已读」** |
|
||||
| 对端发来的(`isMine=false`) | — | 不需要已读标 |
|
||||
|
||||
> 一句话:**把写死的「已读」删掉**。抢单前我方消息本质是**留言**(标「留言」);抢单后后端保证已送达接单房务(可标「已送达」)。**后端无读回执,别再伪造「已读」。**
|
||||
|
||||
**「留言→抢单后送达」后端全包,无需前端管(§4 重申 + 核实)**:
|
||||
- 抢单前定制师发的消息 → 后端存进 pending 桶(收件方=占位 0,**不累加任何人未读、不推 SSE**),就是「留言」。
|
||||
- 房务**抢单**那一刻 → 后端 `bindHouseClaimer` 自动把这些 pending 留言**整批改判给抢到的房务** + 置未读 + SSE 通知。**这就是你说的「发的消息抢单后一起把消息发过去」,已经实现。**
|
||||
- 所以你测试时「房务管家侧看不到留言」(Image #27) 是**对的**——那单**还在抢单池、没人抢**,留言正确地还压在 pending、尚未送达;**等真有房务点「抢这一单」,留言会连同未读角标一起送到 ta 那边**。
|
||||
|
||||
**房务管家侧(Image #27)抢单池显「抢单后可与定制师沟通」= 设计内门控**(见 `22_4237` item 1:房务未抢单时隐藏聊天入口)。**保持不变**,与上面定制师侧抢单前能留言不冲突——能抢单前**单向留言**的只有定制师;房务在抢单池里没接单、无身份,自然先不聊。抢单后双方正常互聊。
|
||||
|
||||
**两件待 wx 拍的「可选增强」(前端不必等,先按上表修假已读)**:
|
||||
1. **真·已读回执**(抢单后对方真读了才显「已读」)——需后端把 per-message 已读态(基于会话成员 `lastReadMessageId` 水位)暴露进线程 VO,是独立后端工单。当前最小修 = 前端按上表把假已读改成「留言/已送达」即可。
|
||||
2. **抢单池给房务「该单有 N 条定制师留言」预览做诱饵**——需后端在抢单池 VO 补 pending 留言计数,独立后端工单。当前抢单后才送达可见。
|
||||
|
||||
---
|
||||
|
||||
## 11. ✅ 真已读回执 + 房务侧留言可见 已上线测试服(2026-06-23 · #4288/#4289 · PR #4294 · 已 API 实测)
|
||||
|
||||
§10 标的两项 wx 已拍并实现上线,**取代 §10 的「待定/显已送达」口径**:
|
||||
|
||||
### 11.1 真实「已读/未读」回执(#4289)
|
||||
- 线程消息出参(`GET /admin/message/chat/{conversationKey}/messages` 的 `list[]`)**新增 `readByPeer`(Boolean)**:仅我方消息(`isMine=true`)有意义 = 对端已读到本条(对端成员已读水位 >= 本消息 id)。抢单前无对端恒 false;对端发来的消息(isMine=false)此字段无意义。
|
||||
- **气泡显示口径(升级版,取代 §10)**:
|
||||
|
||||
| 我方消息状态 | 气泡下显示 |
|
||||
|---|---|
|
||||
| 抢单前 | 「留言」 |
|
||||
| 抢单后 · `readByPeer=false` | 「已送达」 |
|
||||
| 抢单后 · `readByPeer=true` | 「已读」 |
|
||||
| 对端发来的(`isMine=false`) | 不标 |
|
||||
|
||||
- **实时刷已读**:对端调 `POST /admin/message/chat/{conversationKey}/read` 后,后端经 SSE 推 **`im-chat-read`** 事件给我方(payload `{conversationKey, readerAdminId, lastReadMessageId}`)→ 前端把本会话 `id <= lastReadMessageId` 的我方气泡刷「已读」。**订阅 `im-chat-read`**(与 `im-chat` 同一条 SSE 流 `/ws/admin-msg/stream`;NOTIFY/CHAT 事件不变向后兼容)。
|
||||
- 实测:线程出参已含 `readByPeer`(字段顺序 `…, isMine, readByPeer, sentAt`)。
|
||||
|
||||
### 11.2 房务侧「内部留言框」显示抢单前留言(#4288)
|
||||
- 详见 `22_4237`:房务订单详情 `GET /admin/house/orders/{orderId}` **新增 `messages` 列表** + `tabCounts.messageCount = 1(定制师需求) + N(留言)`。**抢单池/未抢单也返**(房务在池里浏览即见定制师留言)。
|
||||
- 实测(订单 2068234602970828802,抢单池态):`tabCounts.messageCount=3`、`messages`=[「你好 测试消息」2026-06-23 14:05、「确认单发我一下」14:32]。
|
||||
- `messages[].senderRole` 抢单前留言可能为 `null`(抢单前留言恒来自定制师,前端按「定制师」渲染即可);`senderName` 已是真实姓名。
|
||||
@@ -0,0 +1,219 @@
|
||||
# 退款待办工作台后端(统计 + 列表增强 + 失败重新发起)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:新增接口(2)+ 修改接口(1)
|
||||
- 关联 Issue:#4226 PR:#4245
|
||||
- 日期:2026-06-22
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单控制台「退款待办」是财务/管理员的退款审批+监控工作台,按状态分组(待审批 / 退款中 / 失败待处理 / 已完成)展示并处理退款申请。本次后端在已有退款申请(refund_application)能力上补齐三块:顶部统计卡片、列表展示字段与搜索增强、失败退款的重新发起。基于现有退款链路(审批 → 自动原路退回 → 微信回调 → 对账),无新建主流程。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/refund/application/stats` | 🆕 新增 |
|
||||
| 2 | POST | `/v3/admin/refund/record/{refundId}/resubmit` | 🆕 新增 |
|
||||
| 3 | GET | `/v3/admin/refund/application/page` | ✏️ 修改(出参增字段 + 入参增搜索) |
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, traceId, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 退款待办统计 `GET /v3/admin/refund/application/stats`
|
||||
返回各状态分组数量+金额 + 本月已退款汇总,供工作台顶部卡片。
|
||||
|
||||
### 2. 重新发起退款 `POST /v3/admin/refund/record/{refundId}/resubmit`
|
||||
对「失败待处理」的退款**记录**重新发起退款。仅 record 状态为 `FAILED`(微信退款执行失败终态)可重发;后端新建一条退款记录 + 新商户退款单号重走微信退款,旧 FAILED 记录保留作审计。
|
||||
> ⚠️ 维度是**退款记录(refund_record)**,不是退款申请。一个退款申请可能对应多条记录(按支付交易拆,定金+尾款等)。
|
||||
|
||||
### 3. 退款申请分页(增强) `GET /v3/admin/refund/application/page`
|
||||
已有列表接口,本次**出参新增 6 字段、入参新增 3 个搜索条件**。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口1 stats
|
||||
无入参。
|
||||
|
||||
### 接口2 resubmit
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| refundId | path | long | 是 | 退款记录 ID(FAILED 记录) |
|
||||
|
||||
无 body。
|
||||
|
||||
### 接口3 page(本次新增的搜索字段,其余原有字段不变)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| customerName | string | 否 | 客户名(联系人)模糊搜索(经 OrderService 反查 orderId 过滤) |
|
||||
| consultantName | string | 否 | 定制师姓名模糊搜索(经 OrderService 反查 orderId 过滤) |
|
||||
| reasonText | string | 否 | 退款原因模糊搜索 |
|
||||
|
||||
> 原有入参(不变):orderId / orderNo / applicantName / applicantId / status(多选) / auditStatus / createTimeFrom / createTimeTo / refundedAtFrom / refundedAtTo / sortField / sortOrder / pageNo / pageSize。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口1 stats `Result<RefundStatsRespVO>`
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| pendingCount | long | 待审批数 |
|
||||
| pendingAmount | string | 待审批合计应退金额 |
|
||||
| refundingCount | long | 退款中数 |
|
||||
| refundingAmount | string | 退款中合计应退金额 |
|
||||
| failedCount | long | 失败待处理数 |
|
||||
| failedAmount | string | 失败待处理合计应退金额 |
|
||||
| completedCount | long | 已完成数 |
|
||||
| completedAmount | string | 已完成合计实退金额 |
|
||||
| currentMonthRefundedAmount | string | 本月已退款金额 |
|
||||
| currentMonthRefundedCount | long | 本月已退款单数 |
|
||||
|
||||
> 金额字段均为**字符串**(BigDecimal 序列化,避免精度丢失)。
|
||||
|
||||
### 接口2 resubmit `Result<Void>`
|
||||
成功返回 `{ "code":200, "message":"成功", "data":null, "success":true }`。
|
||||
|
||||
### 接口3 page 列表项 `RefundApplicationPageItemRespVO` 本次**新增**字段
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| productName | string | 产品名称(经 OrderService 批量填充,可能为空) |
|
||||
| customerName | string | 客户名(联系人,经 OrderService 批量填充) |
|
||||
| consultantName | string | 定制师姓名(经 OrderService 批量填充) |
|
||||
| deduction | string | 扣除金额(派生 = 已付 − 应退,无扣除时可能为 null) |
|
||||
| refundChannel | string | 退款渠道(当前固定值「原路退回(微信支付)」) |
|
||||
| reasonDetail | string | 退款原因详情(申请时填写的详细描述) |
|
||||
|
||||
> 原有项字段(不变):applicationId / orderId / orderNo / refundType / reasonText / paidAmount / calculatedAmount / actualAmount / applicantType / applicantId / applicantName / status / statusText / auditStatus / reviewerNameLast / refundRecordCount / refundedAt / createTime / updateTime。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**退款申请状态 status / statusText**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 待审核 |
|
||||
| APPROVED | 已通过 |
|
||||
| REJECTED | 已拒绝 |
|
||||
| REFUNDING | 退款中 |
|
||||
| REFUNDED | 已退款 |
|
||||
| CANCELLED | 已取消 |
|
||||
| ABNORMAL | 退款异常 |
|
||||
|
||||
**统计分组口径**(前端 4 张卡片对应):
|
||||
- 待审批 = status `PENDING`
|
||||
- 退款中 = status `APPROVED` + `REFUNDING`
|
||||
- 失败待处理 = 底下有退款记录 record.status=`FAILED` 的申请
|
||||
- 已完成 = status `REFUNDED`
|
||||
- 本月已退款 = `REFUNDED` 且退款完成时间(refundedAt)在当月
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
接口2 resubmit(段位 530500-530599):
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 530501 | 仅 FAILED 终态的退款记录可重新发起 |
|
||||
| 530502 | 该退款申请已全额退款成功,无需重新发起 |
|
||||
| 530503 | 超退校验失败:已退¥{0},本次¥{1},已付¥{2} |
|
||||
| 530504 | 退款记录不存在 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:统计
|
||||
请求:`GET /v3/admin/refund/application/stats`
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"pendingCount": 0, "pendingAmount": "0.00",
|
||||
"refundingCount": 0, "refundingAmount": "0.00",
|
||||
"failedCount": 0, "failedAmount": "0.00",
|
||||
"completedCount": 9, "completedAmount": "4610.50",
|
||||
"currentMonthRefundedAmount": "4610.50", "currentMonthRefundedCount": 9
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 典型:列表(按定制师搜索)
|
||||
请求:`GET /v3/admin/refund/application/page?pageNo=1&pageSize=10&consultantName=李`
|
||||
响应(节选一项):
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": { "total": 13, "list": [
|
||||
{ "applicationId": "...", "orderNo": "...", "status": "REFUNDED", "statusText": "已退款",
|
||||
"productName": "测试核心产品-单档-固定订金", "customerName": "孙磊", "consultantName": "腰苏图",
|
||||
"paidAmount": "...", "actualAmount": "...", "deduction": null,
|
||||
"refundChannel": "原路退回(微信支付)", "reasonDetail": "..." }
|
||||
] }
|
||||
}
|
||||
```
|
||||
|
||||
### 异常:重新发起非 FAILED / 不存在的记录
|
||||
请求:`POST /v3/admin/refund/record/999999999/resubmit`
|
||||
响应:
|
||||
```json
|
||||
{ "code": 530504, "message": "退款记录不存在", "data": null, "success": false }
|
||||
```
|
||||
(对非 FAILED 记录返回 530501;已全额成功返回 530502;超退返回 530503)
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- **「重新发起」≠「重新申请」**:重新发起是对已审批通过、但调微信退款执行失败的记录重试(财务工作台);审批被驳回的退款要客户走「重新申请」(小程序新建退款申请),不在本工作台。
|
||||
- **可重发状态**:仅退款记录 `FAILED` 终态显示「重新发起」;退款中 / 已成功 / 审批驳回不可重发。
|
||||
- **账户侧失败**:如「原微信账户已注销」,微信只能原路退回,重新发起原路仍会失败——前端可据失败原因提示走线下,不应无限重试。
|
||||
- 重新发起做了幂等锁(同一记录并发只发一次)+ 超退校验(已退+本次≤已付)。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比(接口3 page)
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 列表项字段 | 无产品名/客户/定制师/扣除/渠道/原因详情 | 新增 productName / customerName / consultantName / deduction / refundChannel / reasonDetail |
|
||||
| 搜索维度 | orderId/orderNo/申请人/状态/机审/时间 | 增加 customerName / consultantName / reasonText |
|
||||
|
||||
接口1、2 为纯新增,无对比。
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- 接口3 为**出参增字段 + 入参增可选搜索**,向后兼容,前端不改也不报错(新字段不读即可)。
|
||||
- 新增字段经 OrderService 批量填充(防 N+1),不影响原列表性能口径。
|
||||
- 回滚:前端不调用新接口、不读新字段即可;后端回滚 PR #4245。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段(stats 各 amount、deduction、paidAmount/actualAmount 等)均为**字符串**,前端按字符串处理金额,勿用 number 解析以免精度丢失。
|
||||
- deduction 可能为 `null`(无扣除或已付=应退),前端兜底显示「-」。
|
||||
- refundChannel 当前固定「原路退回(微信支付)」,后续多渠道再扩展。
|
||||
- resubmit 是**记录(record)维度**,列表/详情若按申请聚合,重发入口需定位到具体 FAILED 记录。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4226
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4245
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(stats 返回真实数据 / page 新字段已填充 / resubmit 守卫生效)。
|
||||
@@ -0,0 +1,273 @@
|
||||
# 发票财务列表 - 未申请 Tab 与列表行订单信息补齐(管理后台)
|
||||
|
||||
> Issue: [#4228](https://git.1814.love:8443/wx/HL/issues/4228) / [#4229](https://git.1814.love:8443/wx/HL/issues/4229)
|
||||
> PR: [#4233](https://git.1814.love:8443/wx/HL/pulls/4233) / [#4235](https://git.1814.love:8443/wx/HL/pulls/4235)
|
||||
> 日期: 2026-06-22
|
||||
> 服务: hl-order-service-v3(invoice 域)
|
||||
> 端类型: 管理后台
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
发票财务管理列表(GET /v3/admin/order/invoice/page)本次做两处补齐:
|
||||
|
||||
1. **NONE Tab(未申请)**:新增 tab 可选值 NONE,返回「已完成(COMPLETED)且尚无有效发票」的订单列表,作为财务「代客申请」入口的候选。NONE Tab 下每行是「订单维度」,发票相关字段均为空,status 固定返回 NONE、statusText 固定返回「未申请」。
|
||||
2. **列表行补订单冗余信息**:所有 Tab 下的列表行新增 5 个订单展示字段(产品名、档位名、客户名、定制师名、出发日期),供列表直接渲染,无需二次请求。另,orderNo(订单号)字段此前已定义但未正确回填,本次修复。
|
||||
3. **tabCounts 新增 noneCount**:Tab 计数对象新增 noneCount 字段,表示「已完成且无发票」的订单数;allCount 含义不变(仍为 requested + issued + pushed 之和,不含 none)。
|
||||
|
||||
关联 PR #4233 修复 orderNo 回填 + 新增 5 个订单字段;PR #4235 新增 NONE Tab 查询与 noneCount 统计。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | GET /v3/admin/order/invoice/page | 修改接口 | 入参 tab 新增可选值 NONE |
|
||||
| 2 | GET /v3/admin/order/invoice/page | 修改接口 | 出参 records[] 新增 5 个字段(productName / tierName / contactName / customizerName / departureDate) |
|
||||
| 3 | GET /v3/admin/order/invoice/page | 修改接口 | 出参 records[].orderNo 字段由恒 null 修复为正确回填 |
|
||||
| 4 | GET /v3/admin/order/invoice/page | 修改接口 | 出参 tabCounts 新增 noneCount 字段 |
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
**接口**:GET /v3/admin/order/invoice/page
|
||||
**功能描述**:发票管理分页列表,含 Tab 过滤、统计数据与关键词搜索。
|
||||
**认证**:管理后台 JWT,Header 携带 Authorization: Bearer <token>。
|
||||
**请求方式**:GET,参数均以 Query String 传入。
|
||||
**幂等性**:只读查询,天然幂等。
|
||||
**限流**:网关全局限流,无接口级特殊限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | Integer | 否 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 20 |
|
||||
| tab | String | 否 | Tab 过滤,默认 ALL;本次新增可选值 NONE,见 6 节枚举 |
|
||||
| keyword | String | 否 | 关键词搜索(发票号 / 申请人);NONE Tab 下该参数暂不生效 |
|
||||
| startDate | String | 否 | 申请开始日期,格式 yyyy-MM-dd;NONE Tab 下暂不生效 |
|
||||
| endDate | String | 否 | 申请结束日期,格式 yyyy-MM-dd;NONE Tab 下暂不生效 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
GET 接口无请求体。
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**响应结构**(Result<PageResult<InvoicePageItemRespVO>>):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | Array | 发票 / 订单列表(见下方单条字段表) |
|
||||
| total | Long | 总记录数 |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
| tabCounts | Object | 各 Tab 计数,见下方 tabCounts 字段表 |
|
||||
| stats | Object | 当月统计(currentMonthIssuedCount / currentMonthIssuedAmount) |
|
||||
|
||||
**records 单条字段**(含本次新增字段,标注 NEW):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 发票 ID(Long 序列化);NONE Tab 下为 null |
|
||||
| orderId | String | 所属订单 ID(Long 序列化) |
|
||||
| orderNo | String | 订单号(此前未回填,本次修复) |
|
||||
| productName | String | 产品名 [NEW] |
|
||||
| tierName | String | 档位名 [NEW] |
|
||||
| contactName | String | 客户名(订单联系人) [NEW] |
|
||||
| customizerName | String | 定制师名 [NEW] |
|
||||
| departureDate | String | 出发日期,格式 yyyy-MM-dd [NEW] |
|
||||
| status | String | 发票状态枚举,见 6 节;NONE Tab 下固定返回 NONE |
|
||||
| statusText | String | 发票状态中文名;NONE Tab 下固定返回「未申请」 |
|
||||
| amount | String | 开票金额(字符串,单位元);NONE Tab 下为 null |
|
||||
| invoiceType | String | 发票类型枚举;NONE Tab 下为 null |
|
||||
| invoiceTypeName | String | 发票类型中文名;NONE Tab 下为 null |
|
||||
| titleType | String | 抬头类型;NONE Tab 下为 null |
|
||||
| title | String | 发票抬头;NONE Tab 下为 null |
|
||||
| taxNo | String | 税号;NONE Tab 下为 null |
|
||||
| fileUrl | String | 发票 PDF 地址;NONE Tab 下为 null |
|
||||
| pdfName | String | PDF 文件名;NONE Tab 下为 null |
|
||||
| pdfSize | Long | PDF 文件大小(字节);NONE Tab 下为 null |
|
||||
| invoiceNo | String | 发票号码;NONE Tab 下为 null |
|
||||
| issuedAt | String | 开票时间(ISO 8601);NONE Tab 下为 null |
|
||||
| issuedBy | String | 开票人姓名;NONE Tab 下为 null |
|
||||
| requestedAt | String | 申请时间(ISO 8601);NONE Tab 下为 null |
|
||||
| requestedBy | String | 申请人;NONE Tab 下为 null |
|
||||
| createTime | String | 记录创建时间;NONE Tab 下为 null |
|
||||
|
||||
**tabCounts 字段**(含本次新增字段,标注 NEW):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| requestedCount | Integer | 待开票数量 |
|
||||
| issuedCount | Integer | 已开票数量 |
|
||||
| pushedCount | Integer | 已推送数量 |
|
||||
| allCount | Integer | 全部(= requested + issued + pushed,不含 none) |
|
||||
| noneCount | Integer | 未申请(已完成订单无发票)数量 [NEW] |
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**Tab 过滤值(入参 tab 字段)**
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| ALL | 全部(默认),仅含有发票记录的数据 |
|
||||
| REQUESTED | 仅待开票 |
|
||||
| ISSUED | 仅已开票 |
|
||||
| PUSHED | 仅已推送 |
|
||||
| NONE | 未申请(已完成订单且无发票)[NEW] |
|
||||
|
||||
**InvoiceStatus - 发票状态(records[].status)**
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| REQUESTED | 待开票 | 用户 / 定制师已申请,等待财务处理 |
|
||||
| ISSUED | 已开票 | 财务已完成开票并上传 PDF |
|
||||
| PUSHED | 已推送 | 发票已推送给客户 |
|
||||
| VOIDED | 已作废 | 发票已作废 |
|
||||
| NONE | 未申请 | 仅 NONE Tab 下返回,表示该订单尚无发票 [NEW] |
|
||||
|
||||
**InvoiceType - 发票类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
| ELECTRONIC | 电子发票 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本次修改为纯出参扩展与入参新增可选值,无新增错误码。已有行为:
|
||||
|
||||
| 情况 | 行为 |
|
||||
|------|------|
|
||||
| tab 传入非法枚举值(如 tab=INVALID) | HTTP 400 参数校验错误 |
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:ALL Tab 列表(含新增 5 个订单字段)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
### 8.2 边界情况:NONE Tab(代客申请候选列表,发票字段全 null)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应(NONE Tab 下发票相关字段均为 null,id 为 null,status 固定 NONE):
|
||||
|
||||
|
||||
|
||||
### 8.3 业务失败:tab 传入非法枚举值
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- 财务人员查看所有发票记录,按 Tab 分类处理
|
||||
- NONE Tab:列出已完成(COMPLETED)且尚未申请发票的订单,财务可点击「代客申请」按钮(调 POST /v3/admin/order/{orderId}/invoice/apply,该接口本次未变更)
|
||||
|
||||
**不适用:**
|
||||
- NONE Tab 不展示非 COMPLETED 订单(如 CUSTOMIZING、PAID、TRAVELLING 等状态的订单不出现在 NONE Tab)
|
||||
- VOIDED 发票不算有效发票,该订单仍可出现在 NONE Tab(后端实现口径以实测为准)
|
||||
|
||||
**特殊边界:**
|
||||
- NONE Tab 下 keyword(关键词)与 startDate / endDate(日期范围)参数暂不生效,传了也不做过滤
|
||||
- noneCount 不计入 allCount;两者相互独立,前端 Tab 计数需分别展示
|
||||
- NONE Tab 每行「id」字段为 null,前端渲染「代客申请」按钮时须使用 orderId,而非 id
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
**入参 tab 字段变化:**
|
||||
|
||||
| tab 可选值 | 变更前 | 变更后 |
|
||||
|-----------|--------|--------|
|
||||
| ALL | 支持 | 不变 |
|
||||
| REQUESTED | 支持 | 不变 |
|
||||
| ISSUED | 支持 | 不变 |
|
||||
| PUSHED | 支持 | 不变 |
|
||||
| NONE | 不存在 | 新增(未申请订单) |
|
||||
|
||||
**出参 records[] 字段变化:**
|
||||
|
||||
| 字段名 | 变更前 | 变更后 |
|
||||
|--------|--------|--------|
|
||||
| orderNo | 有定义但恒 null(bug) | 正确回填订单号 |
|
||||
| productName | 不存在 | 新增(产品名) |
|
||||
| tierName | 不存在 | 新增(档位名) |
|
||||
| contactName | 不存在 | 新增(客户名) |
|
||||
| customizerName | 不存在 | 新增(定制师名) |
|
||||
| departureDate | 不存在 | 新增(出发日期,yyyy-MM-dd) |
|
||||
|
||||
**出参 tabCounts 字段变化:**
|
||||
|
||||
| 字段名 | 变更前 | 变更后 |
|
||||
|--------|--------|--------|
|
||||
| requestedCount | 存在 | 不变 |
|
||||
| issuedCount | 存在 | 不变 |
|
||||
| pushedCount | 存在 | 不变 |
|
||||
| allCount | 存在(= requested+issued+pushed) | 不变(仍不含 none) |
|
||||
| noneCount | 不存在 | 新增(未申请订单数) |
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
**破坏兼容性:**
|
||||
- 出参新增字段(productName / tierName / contactName / customizerName / departureDate / noneCount)为纯新增,不破坏旧字段,向后兼容。
|
||||
- orderNo 由 null 变为有值:如前端已有 null 防护(判 null 不渲染),行为不变;原先已渲染 orderNo 列但始终空白,现在将正常显示。
|
||||
- tab=NONE 为新可选值;旧版前端不传,不影响现有功能。
|
||||
|
||||
**前端同步上线:**
|
||||
- 新增 NONE Tab 入口与「代客申请」按钮需前端接线,否则 NONE Tab 功能不可见。
|
||||
- 新增 5 个订单字段可直接接线渲染到列表列,无需二次请求。
|
||||
|
||||
**回滚方案:**
|
||||
回滚后端至旧版本后:新增字段消失、orderNo 重新恒 null、tab=NONE 入参不识别(返回空列表或 400)、noneCount 消失。前端可对缺失字段做 null 防护兜底。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. NONE Tab 列表行的 id 为 null,前端渲染「代客申请」按钮须用 orderId(Long 序列化字符串)传给 POST /v3/admin/order/{orderId}/invoice/apply。
|
||||
2. departureDate 类型为字符串,格式固定 yyyy-MM-dd,前端直接展示,无需额外格式化。
|
||||
3. noneCount 与 allCount 互不包含,前端 Tab 头需分别渲染。
|
||||
4. NONE Tab 暂不支持 keyword / startDate / endDate 过滤;前端可在 NONE Tab 时隐藏对应搜索框,或保留但提示「当前 Tab 不支持筛选」。
|
||||
5. orderNo 恒 null 为历史 bug,本次修复后所有 Tab 下均有值;原先前端若有「有 orderNo 才渲染」的逻辑可直接保留,无需改动。
|
||||
6. amount 等金额字段均为 String 类型,前端须按字符串接收,避免 JS 大数精度丢失。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue(订单字段补齐): https://git.1814.love:8443/wx/HL/issues/4228
|
||||
- Issue(NONE Tab): https://git.1814.love:8443/wx/HL/issues/4229
|
||||
- PR(订单字段补齐 + orderNo 修复 #4233): https://git.1814.love:8443/wx/HL/pulls/4233
|
||||
- PR(NONE Tab + noneCount #4235): https://git.1814.love:8443/wx/HL/pulls/4235
|
||||
- 后端负责人: yaosutu
|
||||
@@ -0,0 +1,193 @@
|
||||
# 发票推送客户 - 新增接口(管理后台)
|
||||
|
||||
> Issue: [#4230](https://git.1814.love:8443/wx/HL/issues/4230)
|
||||
> PR: [#4238](https://git.1814.love:8443/wx/HL/pulls/4238)
|
||||
> 日期: 2026-06-22
|
||||
> 服务: hl-order-service-v3(invoice 域)
|
||||
> 端类型: 管理后台
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
invoice 域开票完成后,财务需要将发票推送给客户。本次新增 PUT /v3/admin/order/invoice/{id}/push 接口,财务指定推送渠道(邮件 / 微信 / 短信)后调用,发票状态置为 PUSHED 并记录推送时间与渠道。
|
||||
|
||||
**注意**:本期推送仅记录状态与渠道,后端**暂不真正发送**邮件 / 微信 / 短信通知(通知网关后续单独接入)。前端可正常调用并据返回展示「已推送」,但客户实际不会收到通知,功能上线后再由后端对接通知网关。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | PUT /v3/admin/order/invoice/{id}/push | 新增接口 | 推送发票给客户(记录状态与渠道,暂不真发通知) |
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
**接口**:PUT /v3/admin/order/invoice/{id}/push
|
||||
**功能描述**:推送已开票发票给客户,指定推送渠道。
|
||||
**认证**:管理后台 JWT,Header 携带 Authorization: Bearer <token>。
|
||||
**请求方式**:PUT,请求体 Content-Type: application/json。
|
||||
**幂等性**:不保证幂等,多次调用=多次推送记录(PUSHED 状态再次调用视为「再次推送」)。
|
||||
**限流**:网关全局限流,无接口级特殊限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | Long | 是 | 发票 ID(路径参数) |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| channels | Array<String> | 是 | 推送渠道列表,至少一个,合法值仅 email / wechat / sms,见 6 节枚举 |
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**响应类型**:Result<Void>(data 为 null)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | Integer | 200 表示推送成功 |
|
||||
| data | null | 固定为 null |
|
||||
| msg | String | 成功时为 null 或 success;失败时为错误描述 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**推送渠道(channels 数组合法值)**
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| email | 电子邮件 |
|
||||
| wechat | 微信通知 |
|
||||
| sms | 短信 |
|
||||
|
||||
**InvoiceStatus 发票状态(推送前后)**
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| REQUESTED | 待开票 | 不允许推送 |
|
||||
| ISSUED | 已开票 | 允许推送,推送后变 PUSHED |
|
||||
| PUSHED | 已推送 | 允许再次推送,状态维持 PUSHED |
|
||||
| VOIDED | 已作废 | 不允许推送 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 触发场景 |
|
||||
|--------|----------|
|
||||
| 581520 | 发票当前状态不允许推送(发票处于 REQUESTED 或 VOIDED 状态时调用) |
|
||||
| 581521 | 推送渠道不能为空(channels 为 null 或空数组) |
|
||||
| 581522 | 推送渠道非法(channels 中含 email / wechat / sms 以外的值) |
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:ISSUED 状态首次推送(邮件 + 微信)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
调用后,发票状态由 ISSUED 变为 PUSHED,记录推送时间与渠道 [email, wechat]。
|
||||
|
||||
### 8.2 边界情况:PUSHED 状态再次推送(新增 sms 渠道)
|
||||
|
||||
发票已处于 PUSHED 状态,再次调用视为「再次推送」,状态维持 PUSHED,渠道记录更新为 [sms]。
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.3 业务失败:发票处于 REQUESTED 状态(未开票)调用推送
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
|
||||
发票当前状态为 REQUESTED(待开票),响应:
|
||||
|
||||
|
||||
|
||||
追加:空 channels 触发 581521:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- 发票状态为 ISSUED(已开票)时可调用,推送后状态变为 PUSHED
|
||||
- 发票状态为 PUSHED(已推送)时可再次调用,状态维持 PUSHED,记录最新推送渠道
|
||||
|
||||
**不适用:**
|
||||
- 发票状态为 REQUESTED(待开票)时禁止推送,须先财务开票
|
||||
- 发票状态为 VOIDED(已作废)时禁止推送
|
||||
|
||||
**特殊边界:**
|
||||
- 本期推送为「记录意图」:调用成功仅更新状态 / 记录渠道,后端不真正发送邮件 / 微信 / 短信。通知网关对接为后续迭代。
|
||||
- channels 可同时传多个渠道(如 [email, wechat, sms]),后端均记录,但通知未接通时均不发送。
|
||||
- channels 中只要有一个非法值(如 [email, phone]),整个请求返回 581522 错误,不部分推送。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
(本文件为新增接口,无修改前对比。)
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
(本文件为新增接口,无兼容性破坏。)
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **推送不真发**:本期调用成功后客户实际收不到邮件 / 微信 / 短信。前端展示「已推送」状态,但需在 UI 说明或等通知网关接入后再对外宣传此功能。
|
||||
2. channels 字段必须是字符串数组(即使只有一个渠道也要用数组形式:[email]),不能传字符串 email。
|
||||
3. PUSHED 状态再次调用成功,可用于「重新推送」场景(如客户反馈未收到,财务再次操作)。
|
||||
4. id 为发票 ID(Long 序列化字符串),非订单 ID;调用前需先通过发票列表接口获取发票 id。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue(发票推送客户): https://git.1814.love:8443/wx/HL/issues/4230
|
||||
- PR(发票推送 #4238): https://git.1814.love:8443/wx/HL/pulls/4238
|
||||
- 后端负责人: yaosutu
|
||||
@@ -0,0 +1,38 @@
|
||||
# 【后端已修 + 前端待改·管理后台】配房工作台「抢了才能配」:未抢单拒绝配房(新增 808116)(#4231)
|
||||
|
||||
> **后端 PR**: #4231 | **服务**: hl-order-service-v3 | **更新**: 2026-06-22
|
||||
> **前端负责人**: @mmg —— 后半「前端待改」需配合:未抢单时置灰「配房」按钮。
|
||||
|
||||
## 1. 背景(房务测试反馈)
|
||||
订单还在**抢单池(无人抢单)**时,房务详情里每天的「配房」按钮仍可点、配房接口仍能提交,绕过了抢单流程。应「**抢了才能配**」。
|
||||
|
||||
## 2. 后端已修(#4231)
|
||||
配房写操作(提交配房 / 最终确认 / 修改 / 删除 / 房间分配)前置守卫补齐「未抢单」拦截:
|
||||
- 需求**未被任何人抢单**(claimerId 为空)→ 拒绝,返回新错误码 **808116**「订单未抢单, 请先抢单再配房」。
|
||||
- 需求**被他人抢单** → 仍拒绝,返回 808110「需求不属于当前用户」(原有行为不变)。
|
||||
- 内部 Feign / 系统调用(无 admin 上下文)不受影响。
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 提交配房 | POST | /v3/admin/order/hotel-requirements/{requirementId}/assignments | 未抢单→808116 |
|
||||
| 最终确认 | POST | /admin/house/assignments/requirements/{requirementId}/finalize | 未抢单→808116 |
|
||||
| 修改/删除配房、房间分配 | — | (同需求归属校验链路) | 未抢单→808116 |
|
||||
|
||||
## 3. 前端待改
|
||||
- **未抢单时置灰/隐藏每天的「配房」按钮**(以及抢单池预览里的配房入口)。信号现成:房务详情接口 `GET /admin/house/orders/{orderId}` 已返 `claim.isMine`(是否当前用户的单)——`isMine == false`(含未抢单 claimerId 为空、或被他人抢)即应禁用配房操作,仅保留「抢这一单」。
|
||||
- 若仍调用配房接口命中 **808116**,按 message 提示「请先抢单再配房」,不要静默失败。
|
||||
- 抢单池预览(「这单还在抢单池·下方信息仅供参考」)下方的「配房」按钮应整体禁用,与顶部「抢到才能开始配房」文案一致。
|
||||
|
||||
## 4. 错误码
|
||||
| code | 含义 | 触发 | 本次 |
|
||||
|---|---|---|---|
|
||||
| **808116** | 订单未抢单, 请先抢单再配房 | 对未抢单(claimerId 空)的需求提交配房/最终确认/改删/房间分配 | **新增** |
|
||||
| 808110 | 需求不属于当前用户 | 需求被他人抢单时操作 | 不变 |
|
||||
|
||||
## 5. 影响 / 兼容
|
||||
- 行为收紧:仅对「未抢单却调配房」的旧漏洞路径生效(属应修缺陷);正常「先抢单→再配房」流程不受影响。
|
||||
- 回滚:revert PR #4231 重新部署 hl-order-service-v3。
|
||||
|
||||
## 6. 关联
|
||||
- **PR**: [#4231](https://git.1814.love:8443/wx/HL/pulls/4231)(房务测试反馈驱动,无关联 Issue)
|
||||
- **后端负责人**: @wx | **前端**: @mmg
|
||||
@@ -0,0 +1,96 @@
|
||||
# 房务管家(订单详情 / 抢单池)渲染缺口 + 抢单门控(#4237,后端 PR #4239)
|
||||
|
||||
> 变更类型:🐛 后端数据已齐全 → 前端渲染对齐 + 交互门控
|
||||
> 端类型:房务管家(housekeeper)前端(订单详情弹窗 + 抢单池列表)
|
||||
> 日期:2026-06-22
|
||||
> 工单:#4237 | 服务:hl-order-service-v3(数据已就绪)
|
||||
|
||||
---
|
||||
|
||||
## 总览
|
||||
|
||||
wx 评审房务管家发现多处「前端渲染不全 / 交互缺门控」。**后端数据基本都齐全**(段×候选完整结构、productName、claim 信息),下列 1-3 项前端按完整结构渲染即可,**可立即接**。另有进度条 6 态 + 留言计数两项后端在改(PR #4239,合并部署后出字段更新版)。
|
||||
|
||||
## 1. 抢单前隐藏「联系定制师 / 给定制师留言」(可立即接)
|
||||
|
||||
**规则**:抢单后才能和定制师聊天(聊天 = 留言)。订单还在抢单池(未被本房务抢单)时,**不显示**「联系定制师」「给定制师留言」入口(订单详情顶部两个按钮 + 内部留言区的「给定制师留言」)。
|
||||
|
||||
**前端**:按订单详情的 `claim.isMine`(未抢单为 false)门控——未抢单 → 隐藏/禁用这些入口;抢单后 → 显示。
|
||||
|
||||
## 2. 抢单池列表补「产品名」(可立即接)
|
||||
|
||||
**现状**:列表只显挡位名(如"舒适·N晚"),无产品名。后端 pool VO 已含 `productName`(如"测试核心产品-单档-固定比例")。
|
||||
**前端**:列表行补显 `productName`(产品名 + 挡位,别只显挡位名)。
|
||||
|
||||
## 3.「定制师需求」按完整段×候选结构渲染(可立即接)
|
||||
|
||||
**现状**:① 内部留言 tab 的「定制师需求」② 配房行程行 —— 都只显 `第N晚 · X间 · 房型 · ¥价 · 备注` 的简化视图,**漏候选酒店 + 各候选备注 + 段备注**(例:DAY1 实有候选 呼伦贝尔香格里拉(备注777555) / 海拉尔嘉世豪(备注77777222),现只显一个备注、无酒店)。
|
||||
**后端已就绪**:`requirement.current.days[].segments[]` = `{roomCategory, roomCount, budget, remark(段备注), candidates:[{hotelId, hotelName, remark(候选备注)}]}`(房务侧 RequirementSnapshot,#4128 起完整;配房行程的 DayDetail 同样含 segments / hotels)。
|
||||
**前端**:按完整段×候选渲染——每段 `房型 · X间 · ¥预算 · 候选(择一):酒店A(候选备注..) / 酒店B(候选备注..) · 段备注`,对齐 admin 侧(#4220)。内部留言「定制师需求」与配房行程行都补全。
|
||||
|
||||
**✅ 实测复证(本单 HL20260620152849917,房务侧端点 `GET /admin/house/orders/{orderId}` → `data.requirement.current.days[]`)**:候选酒店字段后端**已返齐**,房务侧与管理后台定制师侧『行程安排 → 住宿安排』**同源同数据**,前端照渲即可一致:
|
||||
|
||||
```
|
||||
DAY1 普通标间 ×1 ¥380:candidates = [呼伦贝尔香格里拉大酒店(remark 777555), 海拉尔嘉世豪酒店(remark 77777222)]
|
||||
DAY2 俄式标准房 ×1 ¥280:candidates = [恩和瓦西里民宿, 呼伦贝尔香格里拉大酒店]
|
||||
```
|
||||
|
||||
> 取值:每段 `segments[].candidates[].hotelName`(+ `.remark` 候选备注);兼容字段 `days[].hotels[]` 同数据。**验收目标 = 房务管家「内部留言→定制师需求」渲染与管理后台定制师侧「住宿安排」逐段逐候选一致**。
|
||||
|
||||
## 4-5. 进度条 6 态 + 内部留言计数(✅ PR #4239 已合并部署测试服 + API 实测验证,可接)
|
||||
|
||||
- **进度条**:原 4 步硬编码「接单/配房中/待最终确认/已最终确认」+ 待配房单误显「配房中」。后端已改由房务侧 6 态驱动,字段契约:
|
||||
- `progress.steps[].label` 校正为固定 4 步「待配房 / 配房中 / 待最终确认 / 已确认」,`completed` 按是否越过该步。
|
||||
- `progress.currentStep`:待配房=1 / 配房中(含询房中)=2 / 待最终确认=3 / 已确认=4。
|
||||
- **新增** `progress.houseStatus`(6 态枚举 name:`PENDING_CLAIM`/`CLAIMING`/`IN_INQUIRY`/`PENDING_FINALIZE`/`CONFIRMED`/`EXCEPTION`)+ `progress.houseStatusLabel`(中文,含 4 步未单列的 **询房中 / 异常**)。**前端进度条按这两个权威字段渲染,别再用旧硬标签**。
|
||||
- 实测(抢单池单 `GET /admin/house/orders/{id}`):`houseStatus=PENDING_CLAIM, houseStatusLabel=待配房, currentStep=1, steps[0].completed=false`。
|
||||
- **内部留言计数**:`tabCounts.messageCount` 由恒 0 改为计入定制师需求(=1)。实测 `messageCount=1`。
|
||||
- **H 留言空态对齐(前端)**:既然口径是「定制师需求算 1 条留言」(messageCount=1),内部留言区**别再显「该订单暂无内部留言」**(与 count=1 自相矛盾,且该文案是前端写死,后端不返)。正确:把「定制师需求」当作**第 1 条留言**展示;真正空态只针对**抢单后**房务↔定制师聊天那部分——抢单前聊天关闭,可显「抢单后可与定制师沟通」之类引导而非笼统「暂无内部留言」。
|
||||
- **H2 抢单池「抢单后可与定制师沟通」门控保持不变(与定制师留言不冲突)**:定制师在抢单前可**单向留言**,这些留言由后端压在 pending、**房务抢单那刻自动整批送达抢到的人**(带未读角标 + SSE)。房务在抢单池里未接单、无身份,先不聊是对的;**抢单后**这些留言会出现在房务↔定制师聊天里。聊天气泡的「已读/留言/已送达」显示口径见 **`22_4223` §10/§11**(后端原无「已读」字段,抢单前标「留言」;#4289 已补真已读 `readByPeer`,抢单后 true=「已读」/false=「已送达」,**别写死「已读」**)。
|
||||
|
||||
## 8.「内部留言」框显示定制师留言(✅ #4288 已上线测试服 + API 实测,可接)
|
||||
|
||||
**现状(你 Image #28 反馈「有留言没显示」)**:房务侧订单详情「内部留言」tab 只显「定制师需求」+「抢单后可与定制师沟通」占位,**定制师抢单前发的留言不显示**(你 Image #29 定制师侧已显 2 条留言,房务侧却看不到)。
|
||||
|
||||
**后端已补(实测 `GET /admin/house/orders/{orderId}`)**:详情 **新增顶层 `messages` 列表**(抢单前定制师留言),`tabCounts.messageCount` 由恒 1 改为 **`1(定制师需求) + N(留言)`**。**抢单池/未抢单也返**——房务在池里浏览即见定制师留了什么话。
|
||||
|
||||
**字段契约**:
|
||||
```
|
||||
data.messages: [ { messageId(String雪花), senderName, senderRole, content, sentAt("yyyy-MM-dd HH:mm:ss"), msgType } ]
|
||||
data.tabCounts.messageCount = 1 + messages.length
|
||||
```
|
||||
**实测**(订单 2068234602970828802,抢单池态):`messageCount=3`,`messages`=[「你好 测试消息」14:05、「确认单发我一下」14:32],与定制师侧 Image #29 一致。
|
||||
|
||||
**前端**:「内部留言」框 = 第 1 条「定制师需求」(沿用现有 requirement 渲染)+ 下面按 `messages` 逐条渲染留言(`senderName` + `content` + `sentAt`)。`senderRole` 抢单前留言可能为 null(恒定制师,按「定制师」渲染即可)。抢单后这些留言并入房务↔定制师聊天,气泡已读口径见 `22_4223 §11`。
|
||||
|
||||
## 6. 操作日志「操作人」别打整串 JSON,渲染 `operator.name`(前端 bug,可立即接)
|
||||
|
||||
**现状**(订单详情弹窗 → 操作日志 Tab):操作人显示成
|
||||
`by { "userId": "2037350531801993218", "name": "腰苏图" }`
|
||||
—— 把后端返回的**结构化 `operator` 对象**整个 `JSON.stringify` 打了出来。
|
||||
|
||||
**后端正确**(实测 `GET /v3/admin/house/orders/{orderId}/operation-log` → `data.records[]`):每条 `operator` 是结构化对象 `{ userId, name }`,`operator.name` 已是干净姓名「腰苏图」:
|
||||
|
||||
```json
|
||||
{
|
||||
"opType": "REQUIREMENT_SUBMIT",
|
||||
"opTypeLabel": "定制师提交需求",
|
||||
"summary": "定制师提交房型需求 v1",
|
||||
"operator": { "userId": "2037350531801993218", "name": "腰苏图" }
|
||||
}
|
||||
```
|
||||
|
||||
**前端**:操作人只渲染 `operator.name`(→「腰苏图」/「by 腰苏图」),**别 `JSON.stringify(operator)`、别直接把对象插进模板**。`operator.userId` 仅用于跳详情/排查,不展示。其余照常:`summary` 后端已拼好直接显示、`opTypeLabel` 当中文徽标、`time` 当时间。
|
||||
|
||||
## 7. 配房行程逐日城市改显区县(✅ 后端 #4264 已上线,前端无需改动)
|
||||
|
||||
**现状/变更**:配房行程 Tab 每天行的城市原来显**市级**(呼伦贝尔市),现后端改为返**区县**(海拉尔区/陈巴尔虎旗)。来源 = 订单产品快照 `dismissalPlace.districtName`(下单冻结)→ 资源酒店 `district` 兜底 → 市级 city 兜底。
|
||||
|
||||
**字段契约**:`itinerary[].cityName` = **区县**(解析得到时;否则回退市级,绝不空);`itinerary[].cityCode` = **市级**(向后兼容,若前端某处按市级筛选/分组用这个)。
|
||||
|
||||
**前端**:继续渲染 `cityName` 即自动得区县,**无需改动**。实测(`GET /admin/house/orders/{orderId}`):DAY1 cityName=海拉尔区、DAY2 cityName=陈巴尔虎旗,cityCode 均=呼伦贝尔市。
|
||||
|
||||
---
|
||||
|
||||
## 说明(非 bug)
|
||||
- 抢单池列表「呼伦贝尔市」= 后端按候选酒店 hotelId 解析中文城市去重回填(#3172),非已下线的手填城市字段。是否保留待产品定。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 【修改接口·管理后台】房务订单详情「操作日志」记录定制师提交/调整需求 (#4246)
|
||||
|
||||
> **PR**: #4246 | **服务**: hl-order-service-v3 | **更新**: 2026-06-22
|
||||
> 姊妹项:#4239(内部留言计入定制师需求)。本次给「操作日志」tab 补上定制师提需求轨迹。
|
||||
|
||||
## 1. 背景(房务测试反馈)
|
||||
房务订单详情弹窗「操作日志」tab 应记录定制师提交需求的轨迹(对齐产品操作日志),此前恒空(计数 0 + 暂无)。`house_operation_log` 历来只记房务侧动作(抢单/转单/配房/询房/最终确认),定制师提需求不在其中。
|
||||
|
||||
## 2. 变更(读侧合成,对存量订单立即生效)
|
||||
定制师提需求的真实记录 = `order_hotel_requirement` 版本历史(每个 version 行 = 一次提交)。后端读「操作日志」时即时合成这些条目,与真实 `house_operation_log` 行合并返回。
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 操作日志列表 | GET | /v3/admin/house/orders/{orderId}/operation-log | 返回项新增「定制师提需求」合成条目(op_type=`REQUIREMENT_SUBMIT`),与真实日志行按时间倒序合并;total/summary 计入 |
|
||||
| 订单详情聚合 | GET | /admin/house/orders/{orderId} | `tabCounts.operationLogCount` 由恒 0 改为真值(真实日志行数 + 需求版本数) |
|
||||
|
||||
## 3. 合成条目结构(与真实日志行同 VO)
|
||||
- `opType` = `REQUIREMENT_SUBMIT`,`opTypeLabel` = 「定制师提交需求」。
|
||||
- `time` = 该需求版本提交时刻(submittedAt)。
|
||||
- `operator.userId/name` = 定制师(consultantId / consultantName,缺名兜底「定制师」)。
|
||||
- `summary` = v1「定制师提交房型需求 v1」;高版本「定制师调整房型需求 v{n}」。
|
||||
- `detail` = orderId + requirementId(便于跳转)。
|
||||
- `id` = null(合成项无 house_operation_log 主键;前端可据 `opType=REQUIREMENT_SUBMIT` 区分合成 vs 真实行)。
|
||||
|
||||
## 4. 前端处理建议
|
||||
- 「操作日志」tab 直接渲染列表项即可(新 op_type `REQUIREMENT_SUBMIT` 随列表返回,按 `opTypeLabel`/`summary` 展示);无需单独取数。
|
||||
- tab 徽标计数读 `tabCounts.operationLogCount`(现为真值)。
|
||||
- 若需视觉区分「定制师提需求」与房务侧动作,可按 `opType=REQUIREMENT_SUBMIT` 或 `id==null` 判定。
|
||||
|
||||
## 5. 说明 / 兼容
|
||||
- 纯读侧合成,不写新表、不改既有日志写入;草稿改(version 不增)按设计不单列,首版 + 各次完成版调整各一条。
|
||||
- 加性变更:原有真实日志行展示不变。
|
||||
|
||||
## 6. 关联
|
||||
- **PR**: [#4246](https://git.1814.love:8443/wx/HL/pulls/4246)(房务测试反馈驱动,无关联 Issue)
|
||||
- **后端负责人**: @wx | **前端**: @mmg
|
||||
@@ -0,0 +1,252 @@
|
||||
# 退款政策与退款原因管理接口 v2->v3 迁移说明(管理后台)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-22
|
||||
- **服务**:hl-order-service-v3(端口 8086)
|
||||
- **接口路径前缀**:`/v3/admin/order/refund-policy`
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
退款政策与退款原因管理功能已在 v3 就绪,与 v2 功能一致,路径整体由
|
||||
`/admin/order/refund-policy` 升版为 `/v3/admin/order/refund-policy`。
|
||||
本文档补充 v2→v3 完整路径对照表与退款金额计算算法差异,供前端迁移核对。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 描述 |
|
||||
|------|------|
|
||||
| 路径变更 | 退款政策 7 个端点:/admin/order/refund-policy/* 统一升为 /v3/admin/order/refund-policy/* |
|
||||
| 路径变更 | 退款原因 3 个端点:/admin/order/refund-policy/reason/* 统一升为 /v3/admin/order/refund-policy/reason/* |
|
||||
| 新增端点 | 补 reason/list 列表端点(PR #4243),返回全量退款原因含禁用 |
|
||||
| 无字段变更 | 入参/出参/枚举全部与 v2 一致 |
|
||||
| 算法差异 | 退款金额计算:v3 区间判定与舍入方式与 v2 不同(详见第 12 节) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **认证**:需携带管理端 JWT(Authorization: Bearer token),Gateway 注入 adminId
|
||||
- **幂等性**:GET 天然幂等;POST/PUT 非幂等,重复提交会新建或覆盖
|
||||
- **限流**:走 Gateway 全局策略,无独立限流
|
||||
- **Content-Type**:application/json
|
||||
|
||||
---
|
||||
|
||||
### 退款原因管理端点清单
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| GET | /v3/admin/order/refund-policy/reason/list | 退款原因列表(含禁用,按 sortOrder 升序,供管理后台维护) |
|
||||
| POST | /v3/admin/order/refund-policy/reason | 创建退款原因 |
|
||||
| PUT | /v3/admin/order/refund-policy/reason/{reasonId} | 修改退款原因 |
|
||||
| DELETE | /v3/admin/order/refund-policy/reason/{reasonId} | 软删除退款原因 |
|
||||
|
||||
> GET reason/list 返回 List<RefundReasonVO>,字段详见第 5 节。与 C 端区别:admin 接口调用 listReasons(false) 返回全量含禁用;C 端 GET /v3/internal/mp/order/refund/reasons 只返启用项(listReasons(true))。
|
||||
>
|
||||
> 出参示例:
|
||||
>
|
||||
|
||||
|
||||
## 4. 入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 接口 | 参数名 | 类型 | 必填 | 说明 |
|
||||
|------|--------|------|------|------|
|
||||
| GET /enabled | payType | String | 否 | FULL 或 DEPOSIT;不传返回全部启用政策 |
|
||||
| GET /{policyId} | policyId | Long | 是 | 路径参数 |
|
||||
| PUT /{policyId} | policyId | Long | 是 | 路径参数 |
|
||||
| DELETE /{policyId} | policyId | Long | 是 | 路径参数 |
|
||||
| PUT /{policyId}/toggle | policyId | Long | 是 | 路径参数 |
|
||||
| PUT /{policyId}/toggle | enabled | Boolean | 是 | Query 参数,true=启用 |
|
||||
| PUT /reason/{reasonId} | reasonId | Long | 是 | 路径参数 |
|
||||
| DELETE /reason/{reasonId} | reasonId | Long | 是 | 路径参数 |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
#### 退款政策请求 RefundPolicyRequest(创建/修改通用)
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyName | String | 是 | 非空,<=100 字 | 政策名称 |
|
||||
| remark | String | 否 | <=500 字 | 备注说明 |
|
||||
| applyPayType | String | 否 | FULL/DEPOSIT/BOTH | 适用支付类型;不传后端默认 BOTH |
|
||||
| rules | Array | 是 | 非空,至少 1 条 | 退款规则阶梯列表 |
|
||||
| rules[].minDays | Integer | 是 | >=0 | 距出发最少天数(含当天) |
|
||||
| rules[].refundRatio | Integer | 是 | 0~100 | 退款比例(百分比) |
|
||||
|
||||
#### 退款原因请求 RefundReasonRequest(创建/修改通用)
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| reasonText | String | 是 | 非空,<=200 字 | C 端可见原因文本 |
|
||||
| reasonCode | String | 否 | <=64 字 | 机器可读编码,如 TIME_CONFLICT/OTHER |
|
||||
| category | String | 否 | <=50 字 | 分类,取值来自字典 refund_reason_category |
|
||||
| sortOrder | Integer | 否 | — | 排序号,越小越靠前 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### RefundPolicyVO(退款政策)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| policyId | String(Long 序列化) | 政策 ID(雪花) |
|
||||
| policyName | String | 政策名称 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
| applyPayType | String | 适用支付类型(FULL/DEPOSIT/BOTH) |
|
||||
| remark | String | 备注,可为 null |
|
||||
| createdBy | String(Long 序列化) | 创建人 adminId |
|
||||
| createTime | String(ISO 8601) | 创建时间 |
|
||||
| updateTime | String(ISO 8601) | 更新时间 |
|
||||
| rules | Array | 退款规则阶梯列表,按 minDays 升序 |
|
||||
| rules[].ruleId | String(Long 序列化) | 规则 ID |
|
||||
| rules[].minDays | Integer | 距出发最少天数 |
|
||||
| rules[].refundRatio | Integer | 退款比例(百分比) |
|
||||
|
||||
> VO 不返回 maxDays(区间上界由后端按规则集排序推导),前端只需展示 minDays + refundRatio。
|
||||
|
||||
### RefundReasonVO(退款原因)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| reasonId | String(Long 序列化) | 原因 ID(雪花) |
|
||||
| reasonText | String | 原因描述(C 端展示文本) |
|
||||
| reasonCode | String | 业务编码,可为 null |
|
||||
| category | String | 分类(字典 refund_reason_category 值) |
|
||||
| sortOrder | Integer | 排序号 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### applyPayType(字典 payment_type)
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| FULL | 仅适用全款支付产品 |
|
||||
| DEPOSIT | 仅适用定金支付产品 |
|
||||
| BOTH | 全款与定金均适用(默认值) |
|
||||
|
||||
> GET /enabled 的 payType Query 参数只接受 FULL 或 DEPOSIT(不接受 BOTH)。
|
||||
> 传 FULL 返回 applyPayType 为 FULL 或 BOTH 的政策;传 DEPOSIT 同理。
|
||||
|
||||
### category(字典 refund_reason_category)
|
||||
|
||||
退款原因分类,取值由系统数据字典 refund_reason_category 维护。
|
||||
创建/修改时传字典中已有的 key,后端不做枚举校验,建议与字典保持一致。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 说明 | 触发条件 |
|
||||
|--------|------|----------|
|
||||
| 200 | 成功 | — |
|
||||
| 400 | 参数校验失败 | policyName 为空/rules 为空/refundRatio 超出 0~100/applyPayType 非法值 |
|
||||
| 530209 | 退款政策阶梯天数重复或重叠 | rules 中有相同 minDays |
|
||||
| 1002 | 资源不存在 | policyId/reasonId 不存在或已软删除 |
|
||||
| 401 | 未认证 | 未携带或 JWT 失效 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:创建退款政策(3 档阶梯)
|
||||
|
||||
请求:POST /v3/admin/order/refund-policy
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.2 边界情况:单档不可退政策(refundRatio=0,minDays=0)
|
||||
|
||||
请求:POST /v3/admin/order/refund-policy
|
||||
|
||||
|
||||
|
||||
响应结构同 8.1,rules 只有一条,remark 为 null。
|
||||
|
||||
### 8.3 业务失败:policyName 为空触发 400
|
||||
|
||||
请求体缺少 policyName 字段,响应:
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**
|
||||
- 管理员维护全局退款政策库和退款原因选项
|
||||
- 产品编排时从 GET /enabled 拉取可选政策列表(可按 payType 过滤)绑定到产品
|
||||
- 软删除:DELETE 政策/原因后数据保留,C 端不再展示
|
||||
|
||||
**不适用**
|
||||
- 接口仅管理端可用,不开放给小程序 C 端
|
||||
- 删除/禁用政策后,已绑定该政策的产品申请退款时降级使用默认政策
|
||||
|
||||
**特殊边界**
|
||||
- PUT 修改规则时全量替换原有规则列表,需携带完整规则集
|
||||
- 历史退款申请已快照政策信息,修改政策不影响已有退款申请金额
|
||||
- 退款原因无独立启停端点,通过 DELETE 软删下线
|
||||
|
||||
---
|
||||
|
||||
## 10. v2→v3 路径对照表
|
||||
|
||||
| 功能 | v2 路径 | v3 路径 | 契约变化 |
|
||||
|------|---------|---------|----------|
|
||||
| 退款政策列表(含禁用) | GET /admin/order/refund-policy/list | GET /v3/admin/order/refund-policy/list | 无 |
|
||||
| 启用的退款政策列表 | GET /admin/order/refund-policy/enabled | GET /v3/admin/order/refund-policy/enabled | 无 |
|
||||
| 退款政策详情 | GET /admin/order/refund-policy/{policyId} | GET /v3/admin/order/refund-policy/{policyId} | 无 |
|
||||
| 创建退款政策 | POST /admin/order/refund-policy | POST /v3/admin/order/refund-policy | 无 |
|
||||
| 修改退款政策 | PUT /admin/order/refund-policy/{policyId} | PUT /v3/admin/order/refund-policy/{policyId} | 无 |
|
||||
| 删除退款政策 | DELETE /admin/order/refund-policy/{policyId} | DELETE /v3/admin/order/refund-policy/{policyId} | 无 |
|
||||
| 启用/禁用退款政策 | PUT /admin/order/refund-policy/{policyId}/toggle | PUT /v3/admin/order/refund-policy/{policyId}/toggle | 无 |
|
||||
| 创建退款原因 | POST /admin/order/refund-policy/reason | POST /v3/admin/order/refund-policy/reason | 无 |
|
||||
| 修改退款原因 | PUT /admin/order/refund-policy/reason/{reasonId} | PUT /v3/admin/order/refund-policy/reason/{reasonId} | 无 |
|
||||
| 删除退款原因 | DELETE /admin/order/refund-policy/reason/{reasonId} | DELETE /v3/admin/order/refund-policy/reason/{reasonId} | 无 |
|
||||
| 退款原因列表(含禁用) | GET /admin/order/refund-policy/reason/list | GET /v3/admin/order/refund-policy/reason/list | 无;admin 返回全量含禁用,C 端 /internal/mp/order/refund/reasons 只返启用 |
|
||||
|
||||
v2 旧路径在 v2 服务下线后返回 404,v3 路径为新基线。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **影响**:前端需将 base URL 切换至 v3 服务(端口 8086),路径前缀由 /admin/ 改为 /v3/admin/,字段契约不变
|
||||
- **回滚方案**:前端回退 base URL 至 v2 服务即可,v3 与 v2 数据库独立互不影响
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
**退款金额计算算法差异(v2 vs v3)—— 存量政策迁移与对账时必须核对**
|
||||
|
||||
| 项目 | v2 | v3 |
|
||||
|------|----|----|
|
||||
| 区间判定 | minDays 单边匹配(>=minDays 命中最小 minDays 规则) | [minDays, maxDays) 双边区间,maxDays 由后端按规则集排序推导 |
|
||||
| 舍入模式 | HALF_EVEN(银行家舍入) | DOWN(向下截断) |
|
||||
| 结果差异 | 边界天数处两版计算结果可能不同 | 同政策同天数下,v3 退款金额 <= v2 退款金额 |
|
||||
|
||||
相同 minDays/refundRatio 配置,v2 和 v3 实际退款金额可能不同。
|
||||
若迁移存量政策并对历史退款金额做对账,需按上表核对两版计算结果。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
- **服务**:hl-order-service-v3,退款域 com.hulalv.refund
|
||||
- **Controller**:AdminRefundPolicyController,路径前缀 /v3/admin/order/refund-policy
|
||||
- **Gitea 项目**:https://git.1814.love:8443/wx/HL
|
||||
@@ -0,0 +1,229 @@
|
||||
# 申诉退款执行接口废弃 → 迁移至售后工单流程
|
||||
|
||||
- **日期**:2026-06-22
|
||||
- **变更类型**:删除接口 ⚠️
|
||||
- **端类型**:管理后台
|
||||
- **后端负责人**:腰苏图
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
v2 退款申诉流程在 refund 域有一个独立的「手动执行退款」步骤:企微 OA 申诉审批通过后,管理员还需要手动调用 execute-appeal 接口并填写实退金额,才会真正触发微信退款。
|
||||
|
||||
v3 将「退款申诉」整体迁移至**售后工单中心**(com.hulalv.aftersale),并把「两步(OA 通过 + 手动执行)」改为「一步(处置时核定金额,OA 通过后系统自动退款)」。因此 v2 的 execute-appeal 接口在 v3 **不存在**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| ⚠️ 删除 | `PUT /order/refund/{applicationId}/execute-appeal` | v3 无此接口,调用返回 404 |
|
||||
| ✨ 替代 | `POST /v3/admin/aftersale/ticket/{id}/resolve` | 处置工单并核定金额,OA 通过后自动退款 |
|
||||
| ✨ 新增 | `POST /v3/admin/aftersale/ticket/{id}/process` | 受理申诉工单(SUBMITTED→PROCESSING) |
|
||||
| ✨ 查询 | `GET /v3/admin/aftersale/ticket/page` | 申诉工单分页列表 |
|
||||
| ✨ 查询 | `GET /v3/admin/aftersale/ticket/{id}` | 申诉工单详情 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 废弃接口明细
|
||||
|
||||
### PUT /order/refund/{applicationId}/execute-appeal(v2,已删除)
|
||||
|
||||
**⚠️ 此接口在 v3 不存在,调用将返回 404。**
|
||||
|
||||
- **v2 路径**:`PUT /order/refund/{applicationId}/execute-appeal`
|
||||
- **v2 查询参数**:
|
||||
- `actualAmount`(BigDecimal,必填):管理员手填的实际退款金额
|
||||
- `remark`(String,选填):备注
|
||||
- **v2 语义**:企微 OA 申诉审批通过后,状态置为 `APPEAL_APPROVED`(此时还没退钱),管理员再调此接口填写 actualAmount,才触发微信退款。这是 OA 通过后的独立手动执行步。
|
||||
|
||||
---
|
||||
|
||||
## 4. 为什么没有了
|
||||
|
||||
v3 申诉流程整体迁入售后工单中心,执行模型发生了根本性变化:
|
||||
|
||||
| 维度 | v2 | v3 |
|
||||
|---|---|---|
|
||||
| 金额确认时机 | OA 通过后手动填 actualAmount | 处置时就填 resolutionAmount(OA 提交前确定) |
|
||||
| 退款触发方式 | 管理员手动调 execute-appeal | OA 通过后系统自动触发 |
|
||||
| 申诉入口 | refund 域独立接口 | 售后工单中心(菜单:售后 → 申诉) |
|
||||
|
||||
因为退款触发已经自动化,所以不再需要独立的手动执行接口。
|
||||
|
||||
---
|
||||
|
||||
## 5. 迁移指引:v3 申诉退款完整链路
|
||||
|
||||
> 以下是管理后台视角的完整操作步骤,对应 v2 的整个申诉退款流程。
|
||||
|
||||
| 步骤 | v3 接口 | 方法 | 说明 |
|
||||
|---|---|---|---|
|
||||
| ① 发起申诉(用户侧) | `POST /v3/mp/aftersale/ticket` | POST | C 端用户发起,category=APPEAL,type=REFUND_REJECTED 或 REFUND_AMOUNT_DISPUTE,targetRefId=关联退款申请 ID |
|
||||
| ② 受理工单 | `POST /v3/admin/aftersale/ticket/{id}/process` | POST | 工单状态 SUBMITTED→PROCESSING,可设 assigneeId/remark |
|
||||
| ③ 处置工单(核定金额) | `POST /v3/admin/aftersale/ticket/{id}/resolve` | POST | **对应 v2 的 execute-appeal**,填 decision=APPROVE、resolutionType=REFUND、resolutionAmount=核定金额,系统自动提企微 OA |
|
||||
| ④ 退款自动执行 | 无需调接口 | — | 企微 OA 审批通过后,系统自动触发微信退款,无手动执行步 |
|
||||
|
||||
**核心对应关系**:
|
||||
|
||||
```
|
||||
v2:PUT /order/refund/{id}/execute-appeal?actualAmount=500.00&remark=同意退差价
|
||||
|
||||
v3:POST /v3/admin/aftersale/ticket/{id}/resolve
|
||||
{ "decision":"APPROVE", "resolutionType":"REFUND", "resolutionAmount":500.00, "remark":"同意退差价" }
|
||||
→ OA 通过后自动退款,无额外操作
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. resolve 接口入参字段表
|
||||
|
||||
**接口**:`POST /v3/admin/aftersale/ticket/{id}/resolve`
|
||||
**认证**:JWT(管理后台 Token)
|
||||
**路径参数**:`id`(Long,工单 ID)
|
||||
|
||||
**请求体(ResolveTicketReqVO)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| decision | String | 必填 | 处置决定:`APPROVE`(同意)/ `REJECT`(驳回) |
|
||||
| resolutionType | String | APPROVE 时必填 | 处置方式:`REFUND`(退款)/ `RECTIFY`(整改)/ `REJECT`(维持原决定) |
|
||||
| resolutionAmount | BigDecimal | resolutionType=REFUND 时必填 | 后台核定退款金额,≥0,是退款的唯一金额依据(对应 v2 的 actualAmount) |
|
||||
| remark | String | 选填 | 处置说明,建议填写核定原因 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 场景 |
|
||||
|---|---|---|
|
||||
| 旧接口 404 | v2 路径已下线 | 调用 `/order/refund/*/execute-appeal` |
|
||||
| 工单相关错误码 | 见售后工单 changelog(Issue #4161) | 工单状态非法、金额超可退余额等 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(同意退款 500 元)
|
||||
|
||||
**v3 新调用**:
|
||||
```http
|
||||
POST /v3/admin/aftersale/ticket/9001/resolve
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"decision": "APPROVE",
|
||||
"resolutionType": "REFUND",
|
||||
"resolutionAmount": 500.00,
|
||||
"remark": "经核实酒店降级,同意退差价¥500"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
OA 通过后系统自动触发微信退款,无需再调任何接口。
|
||||
|
||||
### 8.2 边界情况(金额为 0,驳回申诉)
|
||||
|
||||
```http
|
||||
POST /v3/admin/aftersale/ticket/9002/resolve
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"decision": "REJECT",
|
||||
"remark": "经核实服务已按合同履行,驳回申诉"
|
||||
}
|
||||
```
|
||||
|
||||
decision=REJECT 时 resolutionType 和 resolutionAmount 可不填。
|
||||
|
||||
### 8.3 v2 旧接口调用(已失效,返回 404)
|
||||
|
||||
```http
|
||||
PUT /order/refund/8001/execute-appeal?actualAmount=500.00&remark=同意退差价
|
||||
```
|
||||
|
||||
```json
|
||||
404 Not Found
|
||||
(v3 此路径不存在,请迁移至 /v3/admin/aftersale/ticket/{id}/resolve)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
- 用户已通过 C 端(或客服代客)发起了申诉工单
|
||||
- 工单处于 PROCESSING 状态(已受理)
|
||||
- resolutionAmount ≤ 订单可退余额(由 resolve 接口前置校验)
|
||||
|
||||
**不适用**:
|
||||
- 工单尚未受理(SUBMITTED 状态),需先调 process 接口受理
|
||||
- 工单已处置(RESOLVED/CLOSED),不可重复 resolve
|
||||
- resolutionType=REFUND 但不传 resolutionAmount(接口拒绝)
|
||||
|
||||
**特殊边界**:
|
||||
- resolutionAmount 与用户诉求金额 claimAmount 是双金额模型,后台核定金额由管理员独立决定,不受用户填写的 claimAmount 约束(与 v2 actualAmount 语义一致)
|
||||
- 退款超额/累计校验在 refund 域兜底,resolve 时工单层也预检可退余额,两层校验
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 流程对比
|
||||
|
||||
| 阶段 | v2 流程 | v3 流程 |
|
||||
|---|---|---|
|
||||
| 用户发起申诉 | 调退款申诉接口(refund 域) | 创建售后工单(aftersale 域),category=APPEAL |
|
||||
| 管理员受理 | 无独立受理步骤 | `POST /v3/admin/aftersale/ticket/{id}/process` |
|
||||
| 核定金额 + 提 OA | 单独的 OA 提交步骤(金额 OA 通过后才填) | `POST /v3/admin/aftersale/ticket/{id}/resolve`(处置时即填金额,系统自动提 OA) |
|
||||
| 执行退款 | **管理员手动调 execute-appeal** | OA 通过后**系统自动退款** |
|
||||
|
||||
### 字段对比
|
||||
|
||||
| v2 字段 | v2 语义 | v3 字段 | v3 语义 |
|
||||
|---|---|---|---|
|
||||
| `actualAmount`(query param) | OA 通过后手填实退金额 | `resolutionAmount`(请求体) | 处置时预填核定金额,OA 通过后直接用 |
|
||||
| `remark`(query param) | 执行备注 | `remark`(请求体) | 处置说明 |
|
||||
| 无 | — | `decision` | APPROVE/REJECT,明确处置决定 |
|
||||
| 无 | — | `resolutionType` | REFUND/RECTIFY/REJECT,明确处置方式 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
**破坏兼容**:是。v2 execute-appeal 路径在 v3 不存在,直接 404。
|
||||
|
||||
**前端同步事项**:
|
||||
1. 删除调用 `PUT /order/refund/{id}/execute-appeal` 的代码
|
||||
2. 在售后工单详情页接入 `POST /v3/admin/aftersale/ticket/{id}/resolve` 替代
|
||||
3. 售后工单列表/详情见 Issue #4161 的 changelog(changelogs-v2/2026-06/22_4161_售后申诉中心-新增接口-管理后台.md)
|
||||
|
||||
**回滚方案**:如需临时降级,可在前端继续调 v2 的 execute-appeal(v2 服务 hl-order-service-v2 端口 8094 仍在运行);v3 no rollback,此接口不会补建。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `resolutionAmount` 是退款的唯一金额依据,一旦 OA 通过无法修改,请管理员在处置时确认准确金额再提交。
|
||||
- resolve 之后工单进入「待 OA 审批」状态,此时工单不可取消、不可重新处置;如需修改金额,需先联系技术撤回 OA 流程(当前无前端入口)。
|
||||
- 售后工单中心同时承载「投诉」和「申诉」两种类型,查询时注意用 `category=APPEAL` 过滤,避免与投诉工单混淆。
|
||||
- v3 申诉工单的发起方是 C 端用户(`/v3/mp/aftersale/ticket`),管理后台只有受理和处置权限,不能代客创建 APPEAL 类型工单(仅 COMPLAINT 类型支持管理端代客)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **关联 Issue**:https://git.1814.love:8443/wx/HL/issues/4161(售后申诉中心上线)
|
||||
- **v2 原接口 PR**:历史遗留,无单独 PR 记录
|
||||
- **后端负责人**:腰苏图(@yaosutu)
|
||||
- **售后工单 changelog 参考**:`changelogs-v2/2026-06/22_4161_售后申诉中心-新增接口-管理后台.md`
|
||||
@@ -0,0 +1,205 @@
|
||||
# 退款申诉(新增)+ 统一售后工单接口下线(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:新增接口(2)+ 删除接口(统一售后工单 admin 端)
|
||||
- 关联 Issue:#4250 PR:#4251 #4254
|
||||
- 日期:2026-06-23
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
售后体系重构:原"投诉 + 申诉统一售后工单"方向作废,拆为**投诉**与**申诉**两套独立能力。
|
||||
- **申诉**:用户对"退款被拒"或"退款金额"不服时发起;申诉记录单独存。管理后台新增**申诉查询**(只读列表 + 详情)。
|
||||
- **关键**:申诉**复用退款审批**——用户发起申诉后系统自动建一笔 PENDING 退款申请,管理员在**既有「退款审批」**里复核(通过退款 / 驳回结束),**不再有独立的"申诉审批"接口**。申诉列表用于查看与跟踪。
|
||||
- **投诉**:从 `com.hulalv.complaint` 迁入售后域(纯内部移包),**HTTP 路径与字段完全不变**(`/v3/admin/complaint/*`),管理后台无需改动。
|
||||
- 原统一售后工单 admin 接口(`/v3/admin/aftersale/ticket*`)**全部下线**。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/refund/appeal/page` | 🆕 新增(申诉分页查询,只读)|
|
||||
| 2 | GET | `/v3/admin/refund/appeal/{id}` | 🆕 新增(申诉详情,只读)|
|
||||
| 3 | ALL | `/v3/admin/aftersale/ticket*` | ❌ 删除(统一售后工单下线,调用返回 404)|
|
||||
|
||||
> 投诉 `/v3/admin/complaint/page`、`/v3/admin/complaint/{id}` **无变化**(仅后端移包,路径/入参/出参不变),不在本次变更内。
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 申诉分页查询 `GET /v3/admin/refund/appeal/page`
|
||||
管理后台查申诉列表(**只读**),每条关联展示两笔退款:来源退款(被申诉的原退款)+ 申诉触发的退款(申诉建单自动生成、走退款审批的那笔)。
|
||||
|
||||
### 2. 申诉详情 `GET /v3/admin/refund/appeal/{id}`
|
||||
单条申诉详情。
|
||||
|
||||
> 管理后台**无申诉审批接口**:申诉触发的退款在「退款审批」列表里复核(即对那笔退款做通过/驳回)。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口1 page(query 参数,登录态 adminId 由网关注入)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| page | int | 否 | 页码(从 1 开始),默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||||
| orderId | long | 否 | 订单 ID 精确匹配 |
|
||||
| appealType | string | 否 | 申诉类型:`REFUND_REJECTED` / `REFUND_AMOUNT_DISPUTE` |
|
||||
| appealStatus | string | 否 | 申诉状态:`PENDING` / `REJECTED` / `REFUNDED` / `WITHDRAWN` |
|
||||
| applicantName | string | 否 | 申请人姓名(模糊匹配)|
|
||||
| createTimeStart | datetime | 否 | 创建时间起 |
|
||||
| createTimeEnd | datetime | 否 | 创建时间止 |
|
||||
|
||||
### 接口2 detail
|
||||
| 参数 | 位置 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | path | long | 申诉 ID |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口1 page `Result<PageResult<AppealAdminRespVO>>`;接口2 detail `Result<AppealAdminRespVO>`
|
||||
`PageResult`:`{ records[], total, page, pageSize }`。`AppealAdminRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string(Long) | 申诉 ID |
|
||||
| orderId | string(Long) | 订单 ID |
|
||||
| refundApplicationId | string(Long) | 来源退款申请 ID |
|
||||
| appealType | string | 申诉类型(枚举值,见⑥)|
|
||||
| appealTypeLabel | string | 申诉类型中文名 |
|
||||
| appealReason | string | 申诉理由 |
|
||||
| appealAmount | string(decimal) | 申诉退款金额 |
|
||||
| appealStatus | string | 申诉状态(枚举值,见⑥)|
|
||||
| appealStatusLabel | string | 申诉状态中文名 |
|
||||
| linkedRefundId | string(Long) | 申诉触发的退款申请 ID |
|
||||
| auditStatus | string | 凭证机审状态(PENDING/APPROVED/MANUAL_REVIEW/REJECTED)|
|
||||
| applicantType | string | 申请人类型(USER/ADMIN/SYSTEM)|
|
||||
| applicantId | string(Long) | 申请人 ID |
|
||||
| applicantName | string | 申请人姓名 |
|
||||
| reviewedAt | datetime | 审批完成时间(可空)|
|
||||
| createTime | datetime | 创建时间 |
|
||||
| updateTime | datetime | 更新时间 |
|
||||
| sourceRefund | object | 来源退款信息(被申诉的原退款),见下 |
|
||||
| triggeredRefund | object | 申诉触发的退款信息(自动生成、走退款审批的退款),见下 |
|
||||
|
||||
`sourceRefund` / `triggeredRefund` 结构(关联退款信息):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refundApplicationId | string(Long) | 退款申请 ID |
|
||||
| status | string | 退款状态(PENDING/APPROVED/REJECTED/REFUNDING/REFUNDED/CANCELLED/ABNORMAL)|
|
||||
| actualAmount | string(decimal) | 实际退款金额(可空)|
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**申诉类型 appealType**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| REFUND_REJECTED | 退款被拒(原退款被驳回后申诉)|
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议(已退款但金额有异议,求补差)|
|
||||
|
||||
**申诉状态 appealStatus**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 审批中(申诉触发的退款待退款审批)|
|
||||
| REJECTED | 已驳回(退款审批驳回)|
|
||||
| REFUNDED | 退款到账(申诉成功)|
|
||||
| WITHDRAWN | 已撤回 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码(段位 530600-530699)
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 530601 | 申诉必须关联一笔退款申请 |
|
||||
| 530602 | 关联的退款申请不存在 |
|
||||
| 530603 | 退款申请当前状态不满足申诉条件 |
|
||||
| 530604 | 该退款申请已有处理中的申诉,请勿重复提交 |
|
||||
| 530605 | 申诉退款金额超出可退余额 |
|
||||
| 530606 | 申诉记录不存在 |
|
||||
| 530607 | 无权操作此申诉 |
|
||||
| 530608 | 当前申诉状态不允许撤回,仅待审核状态可撤回 |
|
||||
| 530609 | 申诉类型无效 |
|
||||
|
||||
> 查询类接口(page/detail)正常只返 200;上述错误码主要由 C 端发起/撤回触发,管理后台展示用。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:申诉分页
|
||||
请求 `GET /v3/admin/refund/appeal/page?page=1&pageSize=10&appealStatus=PENDING`,响应(节选一项):
|
||||
```json
|
||||
{ "code":200, "success":true, "data": { "total":1, "page":1, "pageSize":10, "records":[
|
||||
{ "id":"40001", "orderId":"10001", "refundApplicationId":"20001",
|
||||
"appealType":"REFUND_REJECTED", "appealTypeLabel":"退款被拒",
|
||||
"appealStatus":"PENDING", "appealStatusLabel":"审批中", "appealAmount":"300.00",
|
||||
"linkedRefundId":"20009", "auditStatus":"APPROVED", "applicantName":"孙磊",
|
||||
"sourceRefund": { "refundApplicationId":"20001", "status":"REJECTED", "actualAmount":null },
|
||||
"triggeredRefund": { "refundApplicationId":"20009", "status":"PENDING", "actualAmount":null } }
|
||||
] } }
|
||||
```
|
||||
|
||||
### 典型:申诉详情
|
||||
请求 `GET /v3/admin/refund/appeal/40001` → 返回上表单条 `AppealAdminRespVO` 全字段。
|
||||
|
||||
### 异常:调用已下线的工单接口
|
||||
请求 `GET /v3/admin/aftersale/ticket/page`(旧统一售后工单接口):
|
||||
```json
|
||||
{ "code":404, "message":"Not Found" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 管理后台**无申诉审批动作**:要处理申诉,去【退款审批】对"申诉触发的退款"(triggeredRefund / linkedRefundId)做通过/驳回。
|
||||
- 申诉列表 `triggeredRefund` 展示这笔退款的审批/到账状态,便于跟踪。
|
||||
- 申诉/投诉任一活跃 → 订单进「售后中」(订单列表「售后」Tab);都终结后回切。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 申诉列表 | 统一售后工单 `/v3/admin/aftersale/ticket/page`(category=APPEAL)| `/v3/admin/refund/appeal/page` |
|
||||
| 申诉详情 | `/v3/admin/aftersale/ticket/{id}` | `/v3/admin/refund/appeal/{id}` |
|
||||
| 申诉审批 | 工单处置(process/resolve)| 无独立审批,走退款审批 |
|
||||
| 投诉 | `/v3/admin/complaint/*` | **不变**(仅后端移包)|
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:`/v3/admin/aftersale/ticket*` 已删,调用返回 404,管理后台涉及"售后工单/申诉"的页面**必须切到新申诉接口**,并把申诉的处理引导到「退款审批」。
|
||||
- 投诉接口无变化(移包对前端透明)。
|
||||
- 回滚:后端回滚 PR #4251 #4254。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段(appealAmount / actualAmount)均为**字符串**,前端按字符串处理防精度丢失。
|
||||
- Long 型 ID 均字符串化返回。
|
||||
- 申诉为只读查询,无新增/编辑/审批写接口。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4250
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4251 ・ https://git.1814.love:8443/wx/HL/pulls/4254
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(申诉接口 200 / 投诉接口 200 / 工单接口 404)。
|
||||
@@ -0,0 +1,67 @@
|
||||
# 房务 6 态全返回:订单详情进度条 6 步 + 我的订单筛选 6 tab(已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 修改接口(响应结构,已部署测试服 + API 验证)
|
||||
> 端类型:管理后台(房务管家 `housekeeper`:订单详情 + 我的订单列表)
|
||||
> 日期:2026-06-23 | 工单:#4255 / #4261 | PR:#4257 / #4263 | 服务:hl-order-service-v3
|
||||
> 背景:wx 要求房务侧进度/筛选**完全按房务 6 态(HouseStateEnum)后端返回**,前端只渲染、不写死。
|
||||
|
||||
房务侧 6 态:`PENDING_CLAIM`待配房 / `CLAIMING`配房中 / `IN_INQUIRY`询房中 / `PENDING_FINALIZE`待最终确认 / `CONFIRMED`已确认 / `EXCEPTION`异常。
|
||||
|
||||
---
|
||||
|
||||
## 1. 订单详情进度条:4 步 → 6 步(#4255)
|
||||
|
||||
`GET /admin/house/orders/{orderId}` 的 `data.progress.steps` **从 4 个变 6 个**(每态一个节点,按态序):
|
||||
|
||||
| step | label | 对应态 |
|
||||
|---|---|---|
|
||||
| 1 | 待配房 | PENDING_CLAIM |
|
||||
| 2 | 配房中 | CLAIMING |
|
||||
| 3 | **询房中** | IN_INQUIRY |
|
||||
| 4 | 待最终确认 | PENDING_FINALIZE |
|
||||
| 5 | 已确认 | CONFIRMED |
|
||||
| 6 | **异常** | EXCEPTION |
|
||||
|
||||
- `progress.currentStep`:当前态对应的 step(**1-6**)。
|
||||
- 每个 step:`{step, label, desc, completed, completedAt}`。`completed`:前 4 节点=已越过该步;已确认=`state==CONFIRMED` 才完成;异常=`state==EXCEPTION`。
|
||||
- `progress.houseStatus` + `houseStatusLabel`(6 态枚举名 + 中文)仍透传,前端可据 `houseStatus==EXCEPTION` 对异常节点做特殊视觉(红/告警)。
|
||||
- 前端:进度条**遍历 steps 渲染 6 个节点**(别写死 4 个)。
|
||||
- 实测:`progress.steps` 返 6 个(待配房…异常)✓。
|
||||
|
||||
## 2. 我的订单筛选 tab:5 → 6(#4261)
|
||||
|
||||
`GET /v3/admin/order/grab-pool/my-claims/hotel` 的 `data.stats` **拆出「询房中」**。原 `inProgress` 把 配房中+询房中 合并,现新增独立计数:
|
||||
|
||||
| stats 字段 | tab | 对应态 |
|
||||
|---|---|---|
|
||||
| (total) | 全部 | — |
|
||||
| **`claiming`** | 配房中 | CLAIMING |
|
||||
| **`inInquiry`** | 询房中 | IN_INQUIRY |
|
||||
| `pendingConfirm` | 待确认 | PENDING_FINALIZE |
|
||||
| `confirmed` | 已确认 | CONFIRMED |
|
||||
| `exception` | 异常 | EXCEPTION |
|
||||
|
||||
- `inProgress`(=claiming+inInquiry)**保留**做向后兼容;**新前端用 `claiming` / `inInquiry` 分别显示「配房中」「询房中」两个 tab 的数字徽章**。
|
||||
- 列表筛选 `status` 参新增取值:`claiming`(只看配房中)/ `inInquiry`(只看询房中);原 `inProgress/pendingConfirm/confirmed/exception` 不变。
|
||||
- 实测:stats 返 `{inProgress, claiming, inInquiry, pendingConfirm, confirmed, exception}` 六字段 ✓。
|
||||
|
||||
## 3. curl 实测(2026-06-23 测试服,过网关 9443)
|
||||
```bash
|
||||
# 进度条 6 步
|
||||
curl '.../admin/house/orders/2068234602970828802' -H 'Authorization: Bearer <token>'
|
||||
# → data.progress.steps 6 个(待配房/配房中/询房中/待最终确认/已确认/异常), currentStep 1-6
|
||||
|
||||
# 我的订单 stats 6 字段
|
||||
curl '.../v3/admin/order/grab-pool/my-claims/hotel?page=1&pageSize=20' -H 'Authorization: Bearer <token>'
|
||||
# → data.stats = {inProgress, claiming, inInquiry, pendingConfirm, confirmed, exception}
|
||||
```
|
||||
|
||||
## 4. 前端处理建议
|
||||
- **进度条**:遍历 `progress.steps` 渲染 6 节点;当前态高亮 `currentStep`;异常态据 `houseStatus==EXCEPTION` 特殊样式。
|
||||
- **我的订单 tab**:渲染 6 个(全部 + 配房中`claiming` + 询房中`inInquiry` + 待确认`pendingConfirm` + 已确认`confirmed` + 异常`exception`);点 tab 时列表带 `status=claiming/inInquiry/...` 筛选。
|
||||
- 不要写死 4 步 / 5 tab。
|
||||
|
||||
## 5. 兼容 / 回滚
|
||||
- 进度条:steps 加 2 项 + currentStep 上限到 6(旧前端若按数组渲染自动变 6;按固定 4 渲染需改)。
|
||||
- tab:stats 加 claiming/inInquiry,inProgress 保留兼容。
|
||||
- 无 DB 迁移。回滚 = revert PR #4257 / #4263 重新部署 hl-order-service-v3。
|
||||
@@ -0,0 +1,54 @@
|
||||
# 【修改接口·管理后台】房务守卫一致性审计修复:询房守卫 + 配房终态闸口 + 详情按钮 enable 对齐 (#4256/#4266/#4274)
|
||||
|
||||
> **PR**: #4256 / #4266 / #4274 | **服务**: hl-order-service-v3 | **更新**: 2026-06-23
|
||||
> **前端**: @mmg —— 涉及新错误码 + 详情按钮可用性语义变化,需配合。
|
||||
> 来源:房务模块整体逻辑审计(4 路只读审计),本批修守卫/状态一致性,均已部署测试服 + API 实测。
|
||||
|
||||
## 1. 询房三写口补归属守卫(#4256)
|
||||
此前**配房**已要求「抢单且本人」,但**询房 send/reply/escalate 完全无归属守卫**——任何房务可对他人抢的/没人抢的单发询问、回填、加急。本次补齐(与配房同口径):
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 发起询房 | POST | /v3/admin/order/inquiry/send | 未抢单→808116 / 被他人抢→808110 |
|
||||
| 回填询房 | POST | /v3/admin/order/inquiry/{inquiryId}/reply | 同上 |
|
||||
| 询房加急 | POST | /v3/admin/order/inquiry/{inquiryId}/escalate | 同上 |
|
||||
|
||||
- reply 转配房(convertToAssignment=true)此前**不扣 resource 库存**(可超卖)→ 已补齐与配房一致的库存硬扣(不足直接拒)。
|
||||
|
||||
## 2. 配房终态闸口(#4266)
|
||||
配房**最终确认(house_status=CONFIRMED)后**仍可静默改/删/换已确认配房 → 收紧:
|
||||
|
||||
| 接口 | 变更 |
|
||||
|---|---|
|
||||
| 修改配房 PUT /assignments/{id} | CONFIRMED 后拒绝,**808121** |
|
||||
| 删除配房 DELETE /assignments/{id} | 同上 |
|
||||
| 房间分配 POST /assignments/{id}/rooms | 同上 |
|
||||
| 换酒店 commit | 同上(经 EXCEPTION 态的合法换店不受影响) |
|
||||
|
||||
> 如需变更已确认配房,请由定制师重新调整需求(DONE_ADJUST)。
|
||||
|
||||
## 3. 详情按钮 enable 与守卫对齐(#4266)—— 前端按 enabled 渲染即可
|
||||
`GET /admin/house/orders/{orderId}` 的 `actions` 此前 `canFinalize`/`canTransfer` 的 enabled 与接口实际守卫不一致(按钮亮但点了报错)。本次对齐:
|
||||
|
||||
| action | enabled 旧 | enabled 新(与接口同源) |
|
||||
|---|---|---|
|
||||
| `canFinalize` | 天数配齐 | `house_status==PENDING_FINALIZE`(有未闭环询问/已确认时为 false) |
|
||||
| `canTransfer` | 恒 true | `isMine`(本人持有才 true,附 disabledReason「仅本人持有的订单可转单」) |
|
||||
|
||||
- **前端**:直接按各 action 的 `enabled` / `disabledReason` 置灰即可(现为真实可用性);命中 808116/808110/808121 按 message 提示,不要静默失败。
|
||||
|
||||
## 4. 错误码
|
||||
| code | 含义 | 本次 |
|
||||
|---|---|---|
|
||||
| **808116** | 订单未抢单, 请先抢单再配房(询房/配房通用前置) | 询房接入(配房 #4231 已有) |
|
||||
| **808121** | 配房已最终确认, 不可直接修改; 如需变更请由定制师重新调整需求 | **新增** |
|
||||
| 808110 | 需求不属于当前用户(被他人抢) | 询房接入 |
|
||||
|
||||
## 5. 后端数据修复(#4274,无直接前端契约变化)
|
||||
- 定制师完成版重调需求(DONE_ADJUST)后旧配房实配此前不软删→孤儿+库存泄漏,已修(软删旧实配+restore 库存,车房对称)。前端读配房列表将不再混入废弃旧版实配。
|
||||
- finalize 此前在订单 trip_nights 为空时不可达死锁,已改用需求自身天数判定,恒可达。
|
||||
|
||||
## 6. 关联
|
||||
- **PR**: [#4256](https://git.1814.love:8443/wx/HL/pulls/4256) / [#4266](https://git.1814.love:8443/wx/HL/pulls/4266) / [#4274](https://git.1814.love:8443/wx/HL/pulls/4274)(房务整体审计驱动,无关联 Issue)
|
||||
- **后端**: @wx | **前端**: @mmg
|
||||
- 未纳入(设计向待定):取消订单后房务态彻底回退需新增 CANCELLED 房务态。
|
||||
@@ -0,0 +1,315 @@
|
||||
# 发票详情接口(财务开票弹窗 / 详情页)
|
||||
|
||||
> 变更类型:新增接口
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-23 | Issue:#4258 | PR:#4262 | 服务:hl-order-service-v3
|
||||
|
||||
---
|
||||
|
||||
> **勘误(2026-06-23,#4292 #4302)**:本文件初版(#4262)包含 3 处与最终实现不符的内容,已在本次修正:
|
||||
> 1. `invoiceType` 枚举已删除 `ELECTRONIC`(电子发票),只保留 `VAT_NORMAL`(增值税普通发票)和 `VAT_SPECIAL`(增值税专用发票)
|
||||
> 2. 出参已删除 `mailAddress` 字段(无纸质邮寄,全电子交付)
|
||||
> 3. `email` 字段始终有值(全电子交付),不再是"电子发票为 null"
|
||||
> 4. `amount` 字段改为 JSON number(单位**元**,如 `986.00`),原「字符串/单位分」均已作废;边界示例专票 `email` 由 null 改为始终有值(全电子交付)。关联 PR [#4311](https://git.1814.love:8443/wx/HL/pulls/4311),Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4310](https://git.1814.love:8443/wx/HL/issues/4310)。
|
||||
|
||||
## 接口背景
|
||||
|
||||
财务在「上传发票 PDF」弹窗或发票详情页中,需要读取完整的开票申请明细:抬头信息、开票金额、专票四项(银行账号、注册地址、注册电话)、开票后的发票号 / PDF / 推送记录。本接口返回单张发票的全量字段,供弹窗回显和详情页展示使用。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 说明 |
|
||||
|---|---------|------|
|
||||
| 1 | 新增接口 | `GET /v3/admin/order/invoice/{id}` 发票详情 |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `GET /v3/admin/order/invoice/{id}` |
|
||||
| **接口名** | 发票详情(财务开票弹窗 / 详情页) |
|
||||
| **描述** | 返回单张发票的完整申请明细、状态、开票痕迹、推送痕迹及订单冗余信息 |
|
||||
| **认证** | 管理后台 JWT(Bearer Token) |
|
||||
| **幂等性** | 只读,天然幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `id` | string(Long 雪花 ID) | 是 | 发票 ID,前端以字符串传输防精度丢失 |
|
||||
|
||||
### 请求体
|
||||
|
||||
无。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
返回结构:`Result<AdminInvoiceDetailRespVO>`
|
||||
|
||||
### 标识字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `id` | string | 发票 ID(雪花 ID,字符串) |
|
||||
| `orderId` | string | 所属订单 ID |
|
||||
| `orderNo` | string | 订单编号,如 `ORD2026062300001` |
|
||||
|
||||
### 开票申请明细
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `invoiceType` | string | 发票类型枚举值,见枚举节 |
|
||||
| `invoiceTypeText` | string | 发票类型中文名,如「增值税普通发票」 |
|
||||
| `titleType` | string | 抬头类型,`COMPANY` 或 `PERSONAL` |
|
||||
| `titleName` | string | 发票抬头(企业名称或个人姓名) |
|
||||
| `taxNo` | string / null | 税号,个人抬头为 null |
|
||||
| `bankName` | string / null | 开户银行,**仅专票**有值,其余 null |
|
||||
| `bankAccount` | string / null | 银行账号,**仅专票**有值,其余 null |
|
||||
| `registAddress` | string / null | 注册地址,**仅专票**有值,其余 null |
|
||||
| `registPhone` | string / null | 注册电话,**仅专票**有值,其余 null |
|
||||
| `amount` | number | 开票金额,JSON number,单位**元**(如 `986.00`) |
|
||||
| `email` | string | 收件邮箱(始终有值,全电子交付) |
|
||||
| `applyReason` | string / null | 备注(申请原因) |
|
||||
|
||||
### 状态字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `status` | string | 发票状态枚举值,见枚举节 |
|
||||
| `statusText` | string | 发票状态中文名,如「待开票」 |
|
||||
| `requestedBy` | string / null | 申请人姓名 |
|
||||
| `requestedAt` | string / null | 申请时间,ISO 8601,如 `"2026-06-20T14:30:00"` |
|
||||
|
||||
### 开票痕迹(ISSUED 后有值)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `invoiceNo` | string / null | 发票号码,开票后回填 |
|
||||
| `fileUrl` | string / null | 发票 PDF 下载 URL(OSS 直链),开票后回填 |
|
||||
| `pdfName` | string / null | PDF 文件名,如 `"invoice_202606230001.pdf"` |
|
||||
| `pdfSize` | string / null | PDF 大小(字节数),字符串,如 `"204800"` |
|
||||
| `issuedBy` | string / null | 开票操作人姓名 |
|
||||
| `issuedAt` | string / null | 开票时间,ISO 8601 |
|
||||
|
||||
### 推送痕迹(PUSHED 后有值)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `pushedAt` | string / null | 推送时间,ISO 8601 |
|
||||
| `pushedChannels` | array(string) / null | 推送渠道列表,如 `["EMAIL","WECHAT"]`;未推送为 null |
|
||||
|
||||
### 订单冗余字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `productName` | string | 产品名称 |
|
||||
| `tierName` | string / null | 产品档次名称 |
|
||||
| `contactName` | string | 客户联系人姓名 |
|
||||
| `customizerName` | string / null | 定制师姓名 |
|
||||
| `departureDate` | string | 出发日期,格式 `YYYY-MM-DD` |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 | 普票,专票四项字段均为 null |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 | 专票,bankName / bankAccount / registAddress / registPhone 有值 |
|
||||
|
||||
### 抬头类型(titleType)
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| `COMPANY` | 企业 |
|
||||
| `PERSONAL` | 个人 |
|
||||
|
||||
### 发票状态(status)
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `REQUESTED` | 待开票 | 申请已提交,财务未处理 |
|
||||
| `ISSUED` | 已开票(待推送) | 财务已上传 PDF,尚未推送给客户 |
|
||||
| `PUSHED` | 已推送 | 已推送给客户 |
|
||||
| `VOIDED` | 已作废 | 该发票已作废 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `581500` | 发票不存在 | 传入的 `id` 在库中不存在或已软删除 |
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 | 当前账号无发票管理权限 |
|
||||
|
||||
---
|
||||
|
||||
## 示例
|
||||
|
||||
### 典型成功(REQUESTED 状态,普票,全字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/1934567890123456789
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "1934567890123456789",
|
||||
"orderId": "1920000000000000001",
|
||||
"orderNo": "ORD2026062300001",
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"invoiceTypeText": "增值税普通发票",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "呼籁科技有限公司",
|
||||
"taxNo": "91310000XXXXXXXXXX",
|
||||
"bankName": null,
|
||||
"bankAccount": null,
|
||||
"registAddress": null,
|
||||
"registPhone": null,
|
||||
"amount": 986.00,
|
||||
"email": "finance@hulalv.com",
|
||||
"applyReason": "报销使用",
|
||||
"status": "REQUESTED",
|
||||
"statusText": "待开票",
|
||||
"requestedBy": "张三",
|
||||
"requestedAt": "2026-06-20T14:30:00",
|
||||
"invoiceNo": null,
|
||||
"fileUrl": null,
|
||||
"pdfName": null,
|
||||
"pdfSize": null,
|
||||
"issuedBy": null,
|
||||
"issuedAt": null,
|
||||
"pushedAt": null,
|
||||
"pushedChannels": null,
|
||||
"productName": "云南香格里拉深度游5日",
|
||||
"tierName": "标准档",
|
||||
"contactName": "李四",
|
||||
"customizerName": "王五",
|
||||
"departureDate": "2026-07-10"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 边界情况(ISSUED 状态,含 fileUrl / invoiceNo,专票四项有值)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/1934567890123456790
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "1934567890123456790",
|
||||
"orderId": "1920000000000000002",
|
||||
"orderNo": "ORD2026062200001",
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"invoiceTypeText": "增值税专用发票",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "某某贸易有限公司",
|
||||
"taxNo": "91310000YYYYYYYYYY",
|
||||
"bankName": "工商银行上海支行",
|
||||
"bankAccount": "6222 0000 0000 0001",
|
||||
"registAddress": "上海市浦东新区XX路XX号",
|
||||
"registPhone": "021-12345678",
|
||||
"amount": 1500.00,
|
||||
"email": "finance@company.com",
|
||||
"applyReason": null,
|
||||
"status": "ISSUED",
|
||||
"statusText": "已开票(待推送)",
|
||||
"requestedBy": "赵六",
|
||||
"requestedAt": "2026-06-21T09:00:00",
|
||||
"invoiceNo": "20260623000001",
|
||||
"fileUrl": "https://oss.hulalv.com/invoice/202606/invoice_20260623000001.pdf",
|
||||
"pdfName": "invoice_20260623000001.pdf",
|
||||
"pdfSize": "204800",
|
||||
"issuedBy": "财务小陈",
|
||||
"issuedAt": "2026-06-22T16:00:00",
|
||||
"pushedAt": null,
|
||||
"pushedChannels": null,
|
||||
"productName": "西藏拉萨朝圣7日",
|
||||
"tierName": "尊享档",
|
||||
"contactName": "赵六",
|
||||
"customizerName": "孙七",
|
||||
"departureDate": "2026-08-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(发票不存在)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/9999999999999999999
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581500,
|
||||
"msg": "发票不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 财务在「上传发票 PDF」弹窗打开时调用,回显完整申请明细
|
||||
- 发票详情页查看全量开票及推送信息
|
||||
|
||||
**不适用场景**
|
||||
- 批量查询多张发票(用列表接口 `GET /v3/admin/order/invoice/page`)
|
||||
- 小程序端查询发票(本接口仅管理后台)
|
||||
|
||||
**特殊边界**
|
||||
- 专票五项字段(`taxNo` / `bankName` / `bankAccount` / `registAddress` / `registPhone`):taxNo 普票公司抬头也需填,专票四项仅 `invoiceType=VAT_SPECIAL` 时有值,前端按 invoiceType 决定是否展示
|
||||
- 开票痕迹字段(`invoiceNo` / `fileUrl` / `pdfName` / `pdfSize` / `issuedBy` / `issuedAt`):状态为 `ISSUED` 或 `PUSHED` 时有值,`REQUESTED` 和 `VOIDED` 时为 null
|
||||
- 推送痕迹字段(`pushedAt` / `pushedChannels`):仅 `PUSHED` 时有值
|
||||
- `amount` 为 JSON number,单位**元**(如 `986.00`),前端直接渲染,无需除以 100
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `id` / `orderId` 为 Long 雪花 ID,前端必须以**字符串**接收,不可用 JS number 类型(会丢失精度)
|
||||
2. `amount` 为 JSON number,单位**元**(如 `986.00`),前端直接渲染,无需除以 100
|
||||
3. `pdfSize` 单位为**字节**,字符串格式,展示时自行换算为 KB / MB
|
||||
4. `pushedChannels` 未推送时为 null,不是空数组 `[]`,前端判断时注意区分
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue** | [#4258 发票详情接口](https://git.1814.love:8443/wx/HL/issues/4258) |
|
||||
| **PR** | [#4262](https://git.1814.love:8443/wx/HL/pulls/4262) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,447 @@
|
||||
# 发票列表 page 结构调整(破坏性变更)
|
||||
|
||||
> 变更类型:修改接口(破坏性)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-23 | Issue:#4265 / #4271 | PR:#4267 / #4271 / #4272 | 服务:hl-order-service-v3
|
||||
|
||||
> **破坏性变更**:`tabCounts` 和 `stats` 两个字段的数据结构均已从扁平对象改为数组,前端 tab 角标读取、统计卡渲染、列表行新增字段**全部需要改动**,上线时前后端需同步发布。
|
||||
|
||||
---
|
||||
|
||||
> **勘误(2026-06-23,#4292 #4302)**:`invoiceType` 枚举已删除 `ELECTRONIC`(电子发票),当前系统只有 `VAT_NORMAL`(增值税普通发票)和 `VAT_SPECIAL`(增值税专用发票)两种类型。列表行 `invoiceType` 字段不会出现 `ELECTRONIC` 值,前端发票类型筛选下拉也只保留普票/专票两项。
|
||||
>
|
||||
> **同步修正(截至 PR [#4311](https://git.1814.love:8443/wx/HL/pulls/4311) 收口)**:records 行 `amount` 及 stats `amount` 均改为 JSON number(单位**元**,如 `986.00`),原「字符串/单位分」已全部作废。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4310](https://git.1814.love:8443/wx/HL/issues/4310)。
|
||||
|
||||
## 接口背景
|
||||
|
||||
财务发票管理列表本轮进行了三项结构升级:
|
||||
|
||||
1. **列表行**新增订单冗余字段(产品、出行人数、定制师等),让财务在列表页即可看到关键订单信息
|
||||
2. **新增 NONE tab**(未申请),支持代客申请场景——展示已完成但尚未申请发票的订单
|
||||
3. **`tabCounts` 和 `stats` 从扁平对象改为数组**,便于前端遍历渲染、后端动态扩展 tab 和统计维度
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 具体内容 |
|
||||
|---|---------|---------|
|
||||
| 1 | 出参新增字段 | `records[]` 行新增 `orderNo`(补回填)、`productName`、`tierName`、`contactName`、`customizerName`、`departureDate`、`adultCount`、`childCount`、`youngChildCount` 共 9 个字段 |
|
||||
| 2 | 入参新增枚举值 | `tab` 参数新增 `NONE`(未申请),返回「已完成且无有效发票」的订单行 |
|
||||
| 3 | 出参结构破坏性变更 | `tabCounts` 从扁平对象改为数组 TabCountItem[] |
|
||||
| 4 | 出参结构破坏性变更 | `stats` 从扁平对象改为数组 StatItem[] |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `GET /v3/admin/order/invoice/page` |
|
||||
| **接口名** | 财务发票管理列表(分页) |
|
||||
| **描述** | 分页查询发票列表,支持多 tab 过滤、统计卡汇总、tab 计数角标 |
|
||||
| **认证** | 管理后台 JWT(Bearer Token) |
|
||||
| **幂等性** | 只读,天然幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### Query 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `tab` | string | 否 | Tab 过滤,见枚举节;默认 `ALL`;**本次新增 `NONE`** |
|
||||
| `pageNo` | integer | 否 | 页码,默认 1 |
|
||||
| `pageSize` | integer | 否 | 每页条数,默认 20,最大 100 |
|
||||
| `keyword` | string | 否 | 关键词搜索(订单号、客户名、定制师名) |
|
||||
| `invoiceType` | string | 否 | 发票类型过滤,见枚举 |
|
||||
| `startDate` | string | 否 | 申请日期起,格式 `YYYY-MM-DD` |
|
||||
| `endDate` | string | 否 | 申请日期止,格式 `YYYY-MM-DD` |
|
||||
|
||||
### 请求体
|
||||
|
||||
无。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
返回结构:`Result<PageResult<AdminInvoicePageRespVO>>`,分页对象顶层附带 `tabCounts`(数组)和 `stats`(数组)。
|
||||
|
||||
### 分页包装层
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `total` | integer | 总条数 |
|
||||
| `pageNo` | integer | 当前页码 |
|
||||
| `pageSize` | integer | 每页大小 |
|
||||
| `records` | array | 当前页数据行,见下表 |
|
||||
| `tabCounts` | array | 各 tab 计数数组(**已改为数组**),见 TabCountItem |
|
||||
| `stats` | array | 统计卡汇总数组(**已改为数组**),见 StatItem |
|
||||
|
||||
### records[] 行字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `id` | string / null | 发票 ID;**NONE tab 行为 null** |
|
||||
| `orderId` | string | 订单 ID |
|
||||
| `orderNo` | string | 订单编号(本次补充回填,原定义但无值) |
|
||||
| `invoiceType` | string / null | 发票类型;NONE tab 行为 null |
|
||||
| `invoiceTypeText` | string / null | 发票类型中文名;NONE tab 行为 null |
|
||||
| `titleName` | string / null | 发票抬头;NONE tab 行为 null |
|
||||
| `amount` | number / null | 开票金额,JSON number,单位**元**(如 `986.00`);NONE tab 行为 null |
|
||||
| `status` | string | 发票状态;NONE tab 行固定为 `"NONE"` |
|
||||
| `statusText` | string | 发票状态中文名;NONE tab 行固定为 `"未申请"` |
|
||||
| `requestedAt` | string / null | 申请时间;NONE tab 行为 null |
|
||||
| `productName` | string | 产品名称(新增) |
|
||||
| `tierName` | string / null | 产品档次名称(新增) |
|
||||
| `contactName` | string | 客户联系人姓名(新增) |
|
||||
| `customizerName` | string / null | 定制师姓名(新增) |
|
||||
| `departureDate` | string | 出发日期,格式 `YYYY-MM-DD`(新增) |
|
||||
| `adultCount` | integer | 成人数(新增) |
|
||||
| `childCount` | integer | 儿童数(新增) |
|
||||
| `youngChildCount` | integer | 婴儿数(新增) |
|
||||
|
||||
> 出行人数展示:将 `adultCount` / `childCount` / `youngChildCount` 三值组合为「2成人1儿童」等文案,数量为 0 的维度可省略。
|
||||
|
||||
### TabCountItem 字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `code` | string | Tab 枚举值 |
|
||||
| `name` | string | Tab 中文名 |
|
||||
| `count` | integer | 该 tab 下的条数 |
|
||||
|
||||
### StatItem 字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `code` | string | 统计维度枚举值 |
|
||||
| `name` | string | 统计维度中文名(直接渲染,无需前端自行拼接) |
|
||||
| `count` | integer | 该维度数量 |
|
||||
| `amount` | number | 该维度金额,JSON number,单位**元**(如 `986.00`) |
|
||||
|
||||
---
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### tab 入参枚举(含新增 NONE)
|
||||
|
||||
| 枚举值 | 说明 | records 行特征 |
|
||||
|--------|------|--------------|
|
||||
| `REQUESTED` | 待开票 | 正常发票行,发票字段有值 |
|
||||
| `ISSUED` | 待推送(已开票) | 正常发票行,fileUrl 有值 |
|
||||
| `PUSHED` | 已推送 | 正常发票行,pushedAt 有值 |
|
||||
| `NONE` | 未申请(新增) | 订单行,id / invoiceType 等发票字段均为 null,status="NONE" |
|
||||
| `ALL` | 全部 | 混合行,包含 NONE 行和发票行 |
|
||||
|
||||
### tabCounts 数组 code 枚举
|
||||
|
||||
| code | name |
|
||||
|------|------|
|
||||
| `REQUESTED` | 待开票 |
|
||||
| `ISSUED` | 待推送 |
|
||||
| `PUSHED` | 已推送 |
|
||||
| `NONE` | 未申请 |
|
||||
| `ALL` | 全部 |
|
||||
|
||||
### stats 数组 code 枚举
|
||||
|
||||
| code | name | 备注 |
|
||||
|------|------|------|
|
||||
| `REQUESTED` | 待开票 | 待处理发票的金额汇总 |
|
||||
| `ISSUED` | 已开票·待推送 | 含义与 tabCounts 中 ISSUED 相同,语境不同 |
|
||||
| `PUSHED` | 已推送 | 已推送给客户的金额汇总 |
|
||||
| `CURRENT_MONTH_ISSUED` | 本月已开票 | 当月开票汇总(跨 ISSUED + PUSHED) |
|
||||
|
||||
> `ISSUED` 在 tabCounts 中文名是「待推送」,在 stats 中文名是「已开票·待推送」,含义相同但语境不同。后端按语境在 `name` 字段直接返回正确文案,前端**直接渲染 `name`,不要按 code 自行拼文案**。
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 | 当前账号无发票管理权限 |
|
||||
| `400` | 参数错误 | `tab` 传入不存在的枚举值 |
|
||||
|
||||
---
|
||||
## 示例
|
||||
|
||||
### 典型成功(ALL tab,返回数组结构)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 12,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20,
|
||||
"records": [
|
||||
{
|
||||
"id": "1934567890123456789",
|
||||
"orderId": "1920000000000000001",
|
||||
"orderNo": "ORD2026062300001",
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"invoiceTypeText": "增值税普通发票",
|
||||
"titleName": "呼籁科技有限公司",
|
||||
"amount": 986.00,
|
||||
"status": "REQUESTED",
|
||||
"statusText": "待开票",
|
||||
"requestedAt": "2026-06-20T14:30:00",
|
||||
"productName": "云南香格里拉深度游5日",
|
||||
"tierName": "标准档",
|
||||
"contactName": "李四",
|
||||
"customizerName": "王五",
|
||||
"departureDate": "2026-07-10",
|
||||
"adultCount": 2,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0
|
||||
}
|
||||
],
|
||||
"tabCounts": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
|
||||
{ "code": "ISSUED", "name": "待推送", "count": 2 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2 },
|
||||
{ "code": "NONE", "name": "未申请", "count": 5 },
|
||||
{ "code": "ALL", "name": "全部", "count": 12 }
|
||||
],
|
||||
"stats": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": 1000.00 },
|
||||
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": 98600.00 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": 50000.00 },
|
||||
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": 148600.00 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 边界情况(NONE tab,发票字段为 null 的订单行)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=NONE&pageNo=1&pageSize=20
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 5,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20,
|
||||
"records": [
|
||||
{
|
||||
"id": null,
|
||||
"orderId": "1920000000000000099",
|
||||
"orderNo": "ORD2026062200099",
|
||||
"invoiceType": null,
|
||||
"invoiceTypeText": null,
|
||||
"titleName": null,
|
||||
"amount": null,
|
||||
"status": "NONE",
|
||||
"statusText": "未申请",
|
||||
"requestedAt": null,
|
||||
"productName": "西藏拉萨朝圣7日",
|
||||
"tierName": null,
|
||||
"contactName": "钱八",
|
||||
"customizerName": "孙九",
|
||||
"departureDate": "2026-06-15",
|
||||
"adultCount": 4,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 1
|
||||
}
|
||||
],
|
||||
"tabCounts": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
|
||||
{ "code": "ISSUED", "name": "待推送", "count": 2 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2 },
|
||||
{ "code": "NONE", "name": "未申请", "count": 5 },
|
||||
{ "code": "ALL", "name": "全部", "count": 12 }
|
||||
],
|
||||
"stats": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": 1000.00 },
|
||||
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": 98600.00 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": 50000.00 },
|
||||
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": 148600.00 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(tab 枚举值不存在)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=INVALID
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "参数错误:tab 枚举值不合法",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 财务管理后台「发票管理」列表页
|
||||
- 代客申请场景:切换到 NONE tab 查看「已完成但未申请发票」的订单,点击进入代客申请流程
|
||||
|
||||
**不适用场景**
|
||||
- 小程序端查看发票状态(走小程序端专属接口)
|
||||
- 查询单张发票全量明细(走 `GET /v3/admin/order/invoice/{id}` 详情接口)
|
||||
|
||||
**特殊边界**
|
||||
- NONE tab 行是订单行,不是发票行:`id` 为 null,发票字段均为 null,`status` 固定为字符串 `"NONE"`,前端需按 status 判断是否展示发票操作按钮
|
||||
- ALL tab 混合 NONE 行和发票行:遍历 records 时需按 `status === "NONE"` 区分行类型
|
||||
- `amount` 为 JSON number,单位**元**(如 `986.00`),前端直接渲染,无需除以 100
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
### tabCounts 字段
|
||||
|
||||
旧(扁平对象):
|
||||
```json
|
||||
{
|
||||
"tabCounts": {
|
||||
"requestedCount": 3,
|
||||
"issuedCount": 2,
|
||||
"pushedCount": 2,
|
||||
"allCount": 12,
|
||||
"noneCount": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新(数组,现行):
|
||||
```json
|
||||
{
|
||||
"tabCounts": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
|
||||
{ "code": "ISSUED", "name": "待推送", "count": 2 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2 },
|
||||
{ "code": "NONE", "name": "未申请", "count": 5 },
|
||||
{ "code": "ALL", "name": "全部", "count": 12 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### stats 字段
|
||||
|
||||
旧(扁平对象):
|
||||
```json
|
||||
{
|
||||
"stats": {
|
||||
"requestedCount": 3,
|
||||
"requestedAmount": 1000,
|
||||
"issuedCount": 2,
|
||||
"issuedAmount": 98600,
|
||||
"pushedCount": 2,
|
||||
"pushedAmount": 50000,
|
||||
"currentMonthIssuedCount": 4,
|
||||
"currentMonthIssuedAmount": 148600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新(数组,现行):
|
||||
```json
|
||||
{
|
||||
"stats": [
|
||||
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": 1000.00 },
|
||||
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": 98600.00 },
|
||||
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": 50000.00 },
|
||||
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": 148600.00 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### tab 入参新增值
|
||||
|
||||
| 旧可选值 | 新增值 |
|
||||
|---------|--------|
|
||||
| REQUESTED / ISSUED / PUSHED / ALL | 新增 `NONE`(未申请) |
|
||||
|
||||
### records[] 行新增字段
|
||||
|
||||
| 字段 | 变化 |
|
||||
|------|------|
|
||||
| `orderNo` | 原已定义,本次补充回填(实际有值) |
|
||||
| `productName` | 新增 |
|
||||
| `tierName` | 新增 |
|
||||
| `contactName` | 新增 |
|
||||
| `customizerName` | 新增 |
|
||||
| `departureDate` | 新增 |
|
||||
| `adultCount` | 新增 |
|
||||
| `childCount` | 新增 |
|
||||
| `youngChildCount` | 新增 |
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
### 破坏兼容性评估
|
||||
|
||||
| 模块 | 是否影响 | 必须改动 |
|
||||
|------|---------|---------|
|
||||
| Tab 角标数字渲染 | 是 | 旧代码读 `tabCounts.requestedCount` 等扁平字段会 undefined,需改为遍历数组按 `code` 取值 |
|
||||
| 统计卡渲染 | 是 | 旧代码读 `stats.requestedCount` / `stats.requestedAmount` 等扁平字段会 undefined,需改为遍历数组按 `code` 取 `count` / `amount` |
|
||||
| 列表行渲染 | 需新增 | 新增 9 个字段需接线渲染(产品名、出行人数组合、定制师、出发日期等) |
|
||||
| NONE tab 处理 | 需新增 | NONE tab 下行无发票 `id`,代客申请按钮需按 `status==="NONE"` 判断 |
|
||||
| ALL tab 混合行 | 需新增 | ALL tab 下需区分 NONE 行和正常发票行 |
|
||||
|
||||
### 前端同步上线要求
|
||||
|
||||
本次变更前后端需同步发布。旧前端读新后端的数组结构,tab 角标和统计卡均会渲染为空。
|
||||
|
||||
### 回滚方案
|
||||
|
||||
如需回滚后端,通知前端同步回滚对应代码;两端数据结构不匹配会导致 tab 角标消失和统计卡白屏。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `tabCounts` 和 `stats` **均已变为数组**,旧的扁平对象键名已全部废弃,前端读取方式必须更新
|
||||
2. NONE tab 行中 `status` 是字符串 `"NONE"`,而不是 null,前端用 `status === "NONE"` 判断行类型
|
||||
3. `stats` 中的 `amount` 为 JSON number,单位**元**(如 `986.00`),前端直接渲染,无需除以 100
|
||||
4. `ISSUED` 在 tabCounts 和 stats 中 `name` 不同,直接渲染 `name` 字段即可,不要自行拼接
|
||||
5. `adultCount` / `childCount` / `youngChildCount` 为整数,0 的维度建议展示时省略
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue** | [#4265 发票列表 NONE tab](https://git.1814.love:8443/wx/HL/issues/4265) / [#4271 列表订单冗余字段](https://git.1814.love:8443/wx/HL/issues/4271) |
|
||||
| **PR** | [#4267](https://git.1814.love:8443/wx/HL/pulls/4267) / [#4271](https://git.1814.love:8443/wx/HL/pulls/4271) / [#4272 stats 拍平](https://git.1814.love:8443/wx/HL/pulls/4272) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,37 @@
|
||||
# 车辆档案: VIN 重复错误码 600107 → 600113
|
||||
|
||||
> **服务**: hl-fleet-service
|
||||
> **PR**: #4279
|
||||
> **Issue**: #4278
|
||||
> **日期**: 2026-06-23
|
||||
> **影响范围**: 管理后台 · 车辆管理(新增/编辑车辆时 VIN/车架号重复校验)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
新增/编辑车辆时**车架号(VIN)已存在**的业务错误码,从 **600107** 改为 **600113**(message 不变,仍为「车架号已存在」)。
|
||||
|
||||
- 以前:VIN 重复返 `code=600107`
|
||||
- 现在:VIN 重复返 `code=600113`
|
||||
- 同时 **600107 现在专指另一含义**:车型「型号已配置价格,不能删除」(`MODEL_HAS_PRICING`)
|
||||
|
||||
**前端只要按 `message` 文案展示则无需改动**(文案没变)。**只有当前端对 `code=600107` 写了特殊分支逻辑时,才需把「VIN 重复」分支改判 `600113`**,否则会把 VIN 重复误判成「型号已配价不能删」。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
600107 此前被**两个**业务错误码同 owner(hl-fleet-service)双占:车辆档案的「VIN 重复」与车型库的「型号已配价不能删」。错误码 Registry 不校验同 owner 段内重叠,导致前端拿到 600107 无法区分到底是哪种错误。第 3 轮审计整改:把「VIN 重复」让位下移到车辆档案段下一空闲码 600113,600107 归还车型库专用。
|
||||
|
||||
## 二、变更错误码清单
|
||||
|
||||
| 含义 | 旧 code | 新 code | message | 触发场景 |
|
||||
|------|--------|--------|---------|----------|
|
||||
| VIN/车架号已存在 | 600107 | **600113** | 车架号已存在 | 新增/编辑车辆,VIN 与库内已有车辆重复 |
|
||||
| 型号已配价不能删 | (600107 双占) | 600107(独占) | 型号已配置价格… | 删除车型时该型号已挂价格日历 |
|
||||
|
||||
## 三、前端动作
|
||||
|
||||
- 默认展示后端 `message`:**无需改动**。
|
||||
- 若有针对 `600107` 的特殊 UI 分支:将「VIN 重复」判定改为 `600113`。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 车务派单: §7.2「未派看板」数据打通(定制师提用车需求 → 自动出现未派车)
|
||||
|
||||
> **服务**: hl-order-service-v3 + hl-fleet-service
|
||||
> **PR**: #4284 / #4286 / #4295
|
||||
> **Issue**: #4283 / #4285 / #4295
|
||||
> **日期**: 2026-06-23
|
||||
> **影响范围**: 管理后台 · 车管控制台(§7.2 矩阵未派清单 / §7 矩阵甘特)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化(数据打通,**接口契约不变**)
|
||||
|
||||
§13.5 用车需求 → 派单 expand 链路已打通并经测试服真实数据 e2e 验证。**`GET /admin/fleet/matrix/unassigned-orders` 等 §7 看板接口的请求/响应结构没有任何改动**,唯一变化是:**这些接口现在会返回真实的未派车占位数据**(此前因 expand 未接线、看板长期为空)。
|
||||
|
||||
车管控制台可以开始用真实数据联调 §7 矩阵 / §7.2 未派清单了。
|
||||
|
||||
---
|
||||
|
||||
## 一、未派数据怎么产生 / 怎么消失(前端理解数据流即可,无需调用 expand 接口)
|
||||
|
||||
| 触发动作(已有接口) | 结果(§7.2 未派看板) |
|
||||
|---|---|
|
||||
| 定制师提交用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`(fleet[] 填 N 个车型) | 对应订单在未派清单出现 **N 行**未派占位(一车型一行,按 fleetItemIndex 区分) |
|
||||
| 定制师**改提**用车需求(同接口,PENDING_EDIT/DONE_ADJUST) | 未派行按新需求刷新;旧需求的未派占位自动清除(#4295 修复,**不再残留孤儿**) |
|
||||
| 订单取消 `POST /v3/admin/order/{id}/cancel/pre-trip` | 该订单的未派占位自动从看板消失 |
|
||||
| 车管在看板对某未派行配车(§8 派单) | 该行从「未派」转入「已派」 |
|
||||
|
||||
## 二、测试服 e2e 实证(已验证,可放心联调)
|
||||
|
||||
- 核心订单提 2 车型用车需求 → §7.2 未派看板即时出现 2 行(车型/接送日期/紧急徽标 T-N 正确)。
|
||||
- 订单取消 → 2 行未派占位即时消失。
|
||||
- 改提需求(1 车→2 车)→ 看板正好 2 行(#4295 修复前会残留 1 条孤儿,现已修复)。
|
||||
|
||||
## 三、前端动作
|
||||
|
||||
- **无接口改动,无需改代码**。§7 / §7.2 看板接口照原契约调用即可。
|
||||
- 若此前因看板返回空数据而无法联调,现可用真实数据继续。
|
||||
@@ -0,0 +1,33 @@
|
||||
# 【行为变更·管理后台】订单取消房务侧生命周期收口 + control_status 词汇单源化 (#4287 / #4293)
|
||||
|
||||
> **PR**: #4287 / #4293 | **服务**: hl-order-service-v3 | **更新**: 2026-06-23
|
||||
> **前端**: @mmg —— #4287 房务侧取消后行为有变化(影响抢单池/我的接单/异常桶展示),需知悉;#4293 纯后端重构无契约变化。
|
||||
> 来源:房务模块整体审计 flag 收尾。均已部署测试服(双实例滚动 UP)+ API smoke + DB 地面真相核对。
|
||||
|
||||
## 1. 订单取消 → 房务侧生命周期收口(#4287,行为变更)
|
||||
|
||||
此前订单取消后房务侧需求/配房**不做任何回退**(残留在抢单池/我的接单,已扣库存不释放)。本次按是否已配房二分处理:
|
||||
|
||||
| 场景 | 取消后房务侧行为 |
|
||||
|---|---|
|
||||
| **未抢单 / 已抢未配房**(无 active 配房) | 该房型需求**直接软删** → 从抢单池 / 我的接单消失;**不产生退款待办**(无已配房屋) |
|
||||
| **已配房**(有 active 配房) | 需求**转「异常」态** → 进入异常桶;同时给接单房务产一条 **REFUND(退款核对)待办**,由房务手动取消已配房屋、释放库存后再标完成「订单已取消无需配房」 |
|
||||
| 两种场景共同 | 配房前置工作类待办(非 REFUND)**自动 RESOLVE**,备注「订单已取消」 |
|
||||
|
||||
- 前端影响:
|
||||
- 抢单池 / 我的接单列表:取消的未配房需求不再出现(后端软删,前端无需特殊处理)。
|
||||
- 异常桶 / 房务工作台:已配房订单取消后,对应需求出现在异常态,附 REFUND 待办;房务按现有「异常桶手动取消已配房屋 → 标完成」流程处理。
|
||||
- 无新增/变更接口字段,房务列表/详情按既有 house_status / 待办类型渲染即可。
|
||||
|
||||
## 2. control_status 词汇单源化(#4293,无前端契约变化)
|
||||
|
||||
`order_main.room_control_status` / `vehicle_control_status` 列此前混用三套词汇(RequirementStatus / 残留废枚举 ResourceControlStatus / 裸字面量 "PROCESSING")。本次统一到单一权威值集 **RequirementStatus**,删除残留废枚举。
|
||||
|
||||
- **存库值集不变**:仍为 `PENDING / PROCESSING / PENDING_REVIEW / REJECTED_TO_CONSULTANT / REJECTED_TO_ADMIN / DONE`(NULL=待提交需求)。
|
||||
- 详情接口 `progressStepper` 的 HOTEL/VEHICLE 子流程 status / statusName 派生口径**完全不变**(仍由 OrderInfoConverter 同一套映射产出)。
|
||||
- 纯后端内部重构,**前端无需任何改动**。
|
||||
|
||||
## 3. 关联
|
||||
- **PR**: [#4287](https://git.1814.love:8443/wx/HL/pulls/4287) / [#4293](https://git.1814.love:8443/wx/HL/pulls/4293)(房务整体审计驱动,无关联 Issue)
|
||||
- **后端**: @wx | **前端**: @mmg
|
||||
- 补记:前序 changelog(#4256/#4266/#4274)§6 曾标「取消订单后房务态彻底回退(待定)」,本批 #4287 已落地(采用「已配房转异常 + 退款待办」而非新增 CANCELLED 房务态,复用现有异常桶流程)。
|
||||
@@ -0,0 +1,307 @@
|
||||
# 发票申请入参票种修正(最终契约)
|
||||
|
||||
> 变更类型:修改接口(破坏性)
|
||||
> 端类型:管理后台
|
||||
> 日期:2026-06-23 | Issue:#4290 | PR:#4292 / #4302 | 服务:hl-order-service-v3
|
||||
|
||||
> ⚠️ **破坏性变更**:`invoiceType` 删除 `ELECTRONIC`、`email` 改为始终必填 + 格式校验、新增 `taxNo`/银行/注册信息专票五项校验、删除 `mailAddress` 字段、专票限 `COMPANY` 抬头。前端申请表单需同步改动,上线时前后端须同步发布。
|
||||
|
||||
> **勘误(截至 PR #4311 收口)**:抬头字段统一 `titleName`、金额 `amount` 改 JSON number(单位元,非分/×100)、票种仅 2 值(`VAT_NORMAL`/`VAT_SPECIAL`)、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。
|
||||
|
||||
> **勘误(截至 PR #4311 收口)**:抬头字段统一 `titleName`、金额 `amount` 改 JSON number(单位元,非分/x100)、票种仅 2 值(`VAT_NORMAL`/`VAT_SPECIAL`)、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。
|
||||
|
||||
---
|
||||
|
||||
## 接口背景
|
||||
|
||||
发票模型经 PR #4292 + #4302 多轮修正后定稿:删除原设计中的「电子发票」票种(业务决策统一走 VAT_NORMAL 普票),对专票收严字段校验(专票限公司抬头 + 必填五项),email 改为始终必填(全系统电子交付),删除纸质邮寄字段 `mailAddress`。
|
||||
|
||||
本文档为最终契约,与本月早前 `23_4172_...` 系列文件中的历史设计存在差异,以本文档为准。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 说明 |
|
||||
|---|---------|------|
|
||||
| 1 | ⚠️ 删枚举值 | `invoiceType` 删除 `ELECTRONIC`,只保留 `VAT_NORMAL` / `VAT_SPECIAL` |
|
||||
| 2 | ⚠️ 删字段 | 申请入参 + 详情出参均删除 `mailAddress`(纸质邮寄地址) |
|
||||
| 3 | ⚠️ 校验收严 | `email` 改为始终必填(原为条件必填),并加邮箱格式校验 |
|
||||
| 4 | ⚠️ 新增校验 | `taxNo`:单位抬头或专票时必填(原仅"公司抬头"须填) |
|
||||
| 5 | ⚠️ 新增校验 | `bankName` / `bankAccount` / `registAddress` / `registPhone`:专票必填(原无此四项限制) |
|
||||
| 6 | ⚠️ 新增错误码 | `581523` 专票只能开给单位(`titleType` 须 `COMPANY`) |
|
||||
| 7 | ✨ 行为明确 | `VAT_SPECIAL` 自动限 `COMPANY` 抬头,个人选专票被拒(581523) |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 管理后台申请接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/admin/order/{orderId}/invoice/apply` |
|
||||
| **接口名** | 管理后台代客申请发票 |
|
||||
| **描述** | 财务或定制师代客提交开票申请,支持普票/专票,全电子交付 |
|
||||
| **认证** | 管理后台 JWT(Bearer Token) |
|
||||
| **幂等性** | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `orderId` | string(Long 雪花 ID) | 是 | 订单 ID,字符串传输防精度丢失 |
|
||||
|
||||
### 请求体字段
|
||||
|
||||
| 字段名 | 类型 | 必填条件 | 说明 |
|
||||
|--------|------|---------|------|
|
||||
| `invoiceType` | string | 始终必填 | 发票类型,只有 `VAT_NORMAL` / `VAT_SPECIAL` 两值,**不含 ELECTRONIC** |
|
||||
| `titleType` | string | 始终必填 | 抬头类型:`COMPANY`(单位)/ `PERSONAL`(个人);**VAT_SPECIAL 只能 COMPANY** |
|
||||
| `titleName` | string | 始终必填 | 发票抬头(企业全称或个人姓名) |
|
||||
| `taxNo` | string | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时必填 | 纳税人识别号 |
|
||||
| `bankName` | string | `invoiceType=VAT_SPECIAL` 时必填 | 开户银行名称 |
|
||||
| `bankAccount` | string | `invoiceType=VAT_SPECIAL` 时必填 | 银行账号 |
|
||||
| `registAddress` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册地址 |
|
||||
| `registPhone` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册电话 |
|
||||
| `amount` | number | 始终必填 | 开票金额,JSON number,单位**元**(如 `986.00`),须 > 0 且 ≤ 订单总金额 |
|
||||
| `email` | string | 始终必填 | 收件邮箱,全电子交付,须通过邮箱格式校验 |
|
||||
| `remark` | string | 选填 | 申请备注 |
|
||||
| `applyReason` | string | 选填 | 申请原因(与 remark 二选一或并用) |
|
||||
|
||||
> ⚠️ `mailAddress` 字段**已删除**,后端不接收,填了也忽略。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
返回结构:`Result<Long>` — 返回新建发票 ID(字符串,雪花 ID)。
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `data` | string | 新建发票 ID,如 `"1934567890123456789"` |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 | 校验规则 |
|
||||
|--------|--------|---------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 | bankName / bankAccount / registAddress / registPhone 均不需要 |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 | taxNo + 银行四项均必填,且 titleType 必须 COMPANY |
|
||||
|
||||
> ~~`ELECTRONIC`~~ 已删除,禁止传入(传入返回 581513)。
|
||||
|
||||
### 抬头类型(titleType)
|
||||
|
||||
| 枚举值 | 中文名 | 限制 |
|
||||
|--------|--------|------|
|
||||
| `COMPANY` | 单位 | 普票/专票均可 |
|
||||
| `PERSONAL` | 个人 | 只能选 `VAT_NORMAL`,选 `VAT_SPECIAL` 返回 581523 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `581510` | 订单未完成,不可开票 | 订单状态不是 `COMPLETED` |
|
||||
| `581511` | 一单一票,已有有效发票 | 该订单已存在 REQUESTED / ISSUED / PUSHED 状态发票 |
|
||||
| `581512` | 开票金额超订单总额 | `amount` > 订单 `orderAmount` |
|
||||
| `581513` | 发票类型非法 | `invoiceType` 不是 `VAT_NORMAL` 或 `VAT_SPECIAL` |
|
||||
| `581514` | 公司抬头或专票须填税号 | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时 `taxNo` 为空 |
|
||||
| `581515` | 专票须填银行及注册信息 | `invoiceType=VAT_SPECIAL` 时 taxNo / bankName / bankAccount / registAddress / registPhone 任一为空 |
|
||||
| `581516` | 收件邮箱不能为空 | 入参层 `@NotBlank`,返回 HTTP 400「收件邮箱不能为空」;格式错误返回 400「邮箱格式不正确」 |
|
||||
| `581523` | 专票只能开给单位 | `invoiceType=VAT_SPECIAL` 且 `titleType=PERSONAL` |
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 | 当前账号无发票申请权限 |
|
||||
|
||||
> 注意:581516 在入参校验层(`@NotBlank` / `@Email`)返回 HTTP 400,不走业务错误码;581513~581523 走 `BusinessException` 返回 200 包体。
|
||||
|
||||
---
|
||||
|
||||
## 示例
|
||||
|
||||
### 典型成功(增值税普通发票,个人抬头)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/admin/order/1920000000000000001/invoice/apply
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "李四",
|
||||
"taxNo": null,
|
||||
"amount": 986.00,
|
||||
"email": "lisi@example.com",
|
||||
"remark": "报销使用"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456789"
|
||||
}
|
||||
```
|
||||
|
||||
### 典型成功(增值税专用发票,公司抬头,全字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/admin/order/1920000000000000002/invoice/apply
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "某某贸易有限公司",
|
||||
"taxNo": "91310000YYYYYYYYYY",
|
||||
"bankName": "工商银行上海支行",
|
||||
"bankAccount": "6222000000000001",
|
||||
"registAddress": "上海市浦东新区XX路XX号",
|
||||
"registPhone": "021-12345678",
|
||||
"amount": 1500.00,
|
||||
"email": "finance@company.com",
|
||||
"remark": null
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456790"
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(专票个人抬头被拒,错误码 581523)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/admin/order/1920000000000000003/invoice/apply
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "张三",
|
||||
"taxNo": null,
|
||||
"amount": 500.00,
|
||||
"email": "zhangsan@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581523,
|
||||
"msg": "增值税专用发票只能开给单位,个人抬头不可选专票",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 财务在「发票管理 - 未申请 tab」找到已完成订单,代客提交开票申请
|
||||
- 定制师在订单详情页为客户发起申请
|
||||
|
||||
**不适用场景**
|
||||
- 重新开票(走 `POST /v3/admin/order/invoice/{id}/reissue` 重开接口,入参相同)
|
||||
- 小程序客户自主申请(走小程序端专属接口 `POST /v3/internal/mp/order/{orderId}/invoice/apply`)
|
||||
|
||||
**特殊边界**
|
||||
- 一单一票:同一订单只允许一张有效发票(REQUESTED / ISSUED / PUSHED 状态),若已有则返回 581511,须先作废旧发票再申请
|
||||
- 订单状态:仅 `COMPLETED`(已完成)状态订单可申请,其他状态返回 581510
|
||||
- 金额单位:`amount` 为 JSON number,单位**元**(如 `986.00`),>0,≤ 订单总金额;前端输入框以元为单位直接提交,无需 ×100
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
### 发票类型(invoiceType 枚举)
|
||||
|
||||
| 旧值(历史设计) | 新值(最终实现) |
|
||||
|--------|--------|
|
||||
| `VAT_NORMAL` 增值税普通发票 | `VAT_NORMAL` 增值税普通发票(保留) |
|
||||
| `VAT_SPECIAL` 增值税专用发票 | `VAT_SPECIAL` 增值税专用发票(保留) |
|
||||
| `ELECTRONIC` 电子发票 | **已删除,禁止传入** |
|
||||
|
||||
### email 字段
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 条件必填(电子发票时必填,普票/专票选填) | **始终必填** + 邮箱格式校验(全电子交付) |
|
||||
|
||||
### mailAddress 字段
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 纸质发票邮寄地址(选填) | **已删除**(无纸质邮寄,全电子交付) |
|
||||
|
||||
### 专票校验
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 无 titleType 限制 | **专票只能 COMPANY**(个人返回 581523) |
|
||||
| bankName 等四项无强制 | **专票 bankName / bankAccount / registAddress / registPhone 全部必填**(返回 581515) |
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
### 破坏兼容性评估(前端申请表单必须改动)
|
||||
|
||||
| 模块 | 必须改动 |
|
||||
|------|---------|
|
||||
| 发票类型下拉 | 去掉「电子发票」选项,只保留「增值税普通发票」/「增值税专用发票」 |
|
||||
| 专票表单区块 | 新增开户行、银行账号、注册地址、注册电话 4 个必填输入框,仅在选专票时显示 |
|
||||
| 抬头类型联动 | 选专票时,抬头类型只能选「单位」,隐藏或禁用「个人」选项 |
|
||||
| email 字段 | 改为始终必填,加邮箱格式校验(正则或 HTML input type=email) |
|
||||
| mailAddress 字段 | 移除,不再向后端传此字段 |
|
||||
|
||||
### 回滚方案
|
||||
|
||||
后端回滚至 #4292 前版本后,`ELECTRONIC` 枚举值重新有效,`email` 恢复条件必填,`mailAddress` 重新可用。前端需同步回滚或做前向兼容处理。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `ELECTRONIC` 枚举值已删除,前端下拉不可出现此选项;老订单历史数据如有该枚举值,出参展示时可显示「未知类型」或联系后端处理
|
||||
2. `email` 无论票种均必填,前端不可根据票种做条件隐藏
|
||||
3. 专票表单区块(税号 + 开户行 + 账号 + 注册地址 + 注册电话)共 5 个字段,在选专票时全部必填,不可缺任何一项
|
||||
4. `amount` 为 JSON number,单位**元**(如 `986.00`),前端输入框直接传元值,无需 ×100;后端已按元存储和校验
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue(功能)** | [#4290 发票申请票种修正](https://git.1814.love:8443/wx/HL/issues/4290) |
|
||||
| **Issue(小程序)** | [#4296 小程序发票申请同步修正](https://git.1814.love:8443/wx/HL/issues/4296) |
|
||||
| **PR(票种删 ELECTRONIC)** | [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) |
|
||||
| **PR(专票限公司/email 必填/删 mailAddress)** | [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,46 @@
|
||||
# 房务订单详情:逐日城市改显区县(跨区县拆分)+ 配房需求候选酒店暴露真实房型 roomTypeId(已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 修改接口(字段语义变更 + 新增字段,已部署测试服并 API 验证,可对接)
|
||||
> 端类型:管理后台(房务订单详情「配房行程 / 定制师需求」DAY 卡片)
|
||||
> 日期:2026-06-23 | 工单:#4297、#4204(P3) | PR:#4300、#4305 | 服务:hl-order-service-v3
|
||||
> 接口:`GET /admin/house/orders/{orderId}` → `data.requirement.current.days[]`(及历史版本 `recentHistory[].snapshot.days[]`)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
1. 之前 DAY 卡片的「城市」显示**市级**(呼伦贝尔市)。wx 要求改显**区县**(海拉尔区),且**同一天多家候选酒店分属不同区县时,全部拆分展示**。
|
||||
2. wx 要求配房需求**按酒店分类、一个酒店一个房型**——候选酒店应携带**自己的真实房型**(#4204 epic 的房控侧读取 P3)。
|
||||
|
||||
## 1. `days[].city` 语义变更:市级 → 区县(可多值)
|
||||
`requirement.current.days[].city`(字段名**不变**)取值由市级改为**区县级**,取该天**全部候选酒店**的资源区县去重:
|
||||
- 单区县:`"海拉尔区"`
|
||||
- **多区县**(候选跨区县):用顿号 `、` 拼接,如 `"满洲里市、海拉尔区"`、`"额尔古纳市、满洲里市"`
|
||||
- 区县均解析不到时兜底市级,**绝不为空**。
|
||||
|
||||
**前端注意**:该字段现在**可能是多个区县拼接的字符串**,按整串原样展示即可,**勿假设单值/勿按固定宽度截断**。无需改数据结构,仅展示语义。
|
||||
|
||||
## 2. 候选/酒店新增真实房型字段(#4204 P3,additive)
|
||||
`days[].hotels[]`(逐酒店平铺)与 `days[].segments[].candidates[]`(逐段候选)各**新增 3 字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `roomTypeId` | String(雪花,可空) | 该候选酒店的**真实房型 ID**(指向 resource 域具体房型) |
|
||||
| `roomTypeName` | String(可空) | 该候选酒店的**真实房型名称**(如「豪华大床房」),定制师提交时所选 |
|
||||
| `protocolPrice` | 数值(可空) | 该候选的协议价快照 |
|
||||
|
||||
**前端「按酒店分类」渲染**:以 `hotels[]` 为主体逐家展示,每家显示**它自己的** `roomTypeName`(而非段级抽象 `roomCategory`)。段级 `roomCategory/roomCount/budget` 仍保留(兼容、分住分组用)。
|
||||
|
||||
**数据可见性**:`roomTypeId/roomTypeName` 仅当定制师提交需求时**录入了每家酒店的真实房型**才有值;历史/旧单缺数据时为 `null`(前端回退展示段级 `roomCategory` 即可)。
|
||||
|
||||
## 3. 待前端配合(#4204 P5,mmg)
|
||||
要让候选酒店都带上真实房型,定制师**提交配房需求的面板**需改为「**先选候选酒店 → 再从该酒店真实房型里选**」,把所选 `roomTypeId` 随候选提交(后端写入侧 P1 已就绪)。本条后端读侧(P3)已备好,该面板改造完成后真实房型即贯穿展示。
|
||||
|
||||
---
|
||||
|
||||
## 附:聊天发送人名(#4309,纯后端,前端无需改)
|
||||
内部留言/聊天发送人名解析(hl-user-service `resolveName`)改为「**绑企微 → 企微名,未绑 → 用户名**」三级口径(落地已登记 P2)。`senderName` 字段**不变**,为发送时快照(改名不影响历史),**对新发消息生效**,历史消息保持原快照。前端无需任何改动。
|
||||
|
||||
## 验证(测试服 order `2068234602970828802` 实测)
|
||||
- `days[0].city = "满洲里市、海拉尔区"`、`days[1].city = "额尔古纳市、满洲里市"`(多区县拆分生效)。
|
||||
- `days[].hotels[].roomTypeId/roomTypeName` 已暴露(如 `普通标间`/`俄式标准房` + 真实 roomTypeId)。
|
||||
- order-v3 全量单测 + ArchTest 门禁全绿;双实例滚动部署健康。
|
||||
@@ -0,0 +1,309 @@
|
||||
# 退款待办列表状态 Tab 后端驱动(PR #4303/#4308)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **变更类型**:修改接口
|
||||
- **日期**:2026-06-23
|
||||
- **关联 Issue**:[#4303](https://git.1814.love:8443/wx/HL/issues/4303)
|
||||
- **PR**:[#4308](https://git.1814.love:8443/wx/HL/pulls/4308)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
退款待办工作台新增「分 Tab 展示」能力。统计接口出参由旧版扁平字段对象改为数组(每项含 code/name/count/amount),分页接口新增 `statusGroup` 入参,支持按 Tab 口径过滤。
|
||||
|
||||
前端改造要点:
|
||||
- 统计接口出参结构已**破坏性变更**(旧字段全部废弃),前端读法必须修改。
|
||||
- 分页接口新增可选参数 `statusGroup`,点 Tab 时传对应值即可,与既有 `status` 多选参数可并存(AND 关系)。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 影响 |
|
||||
|---|------|----------|------|
|
||||
| 1 | `GET /v3/admin/refund/application/stats` | ⚠️ 破坏性修改——出参由扁平对象改为数组 | 前端必须修改 |
|
||||
| 2 | `GET /v3/admin/refund/application/page` | ✨ 新增可选入参 `statusGroup` | 前端按需传参 |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 接口 1:退款待办统计
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | `/v3/admin/refund/application/stats` |
|
||||
| 描述 | 退款待办各状态分组统计(数量 + 金额),用于列表 Tab 角标与顶部卡片 |
|
||||
| 认证 | 需要 JWT(管理后台 Bearer Token) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
|
||||
### 接口 2:退款申请分页
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | `/v3/admin/refund/application/page` |
|
||||
| 描述 | 退款申请分页列表,新增 `statusGroup` 参数支持 Tab 口径过滤 |
|
||||
| 认证 | 需要 JWT(管理后台 Bearer Token) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
### 接口 1:`GET /v3/admin/refund/application/stats`
|
||||
|
||||
无入参(Query 参数为空)。
|
||||
|
||||
---
|
||||
|
||||
### 接口 2:`GET /v3/admin/refund/application/page`
|
||||
#### 4.1 Query 参数(全量,新增字段用 ✨ 标注)
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `pageNo` | Integer | 是 | 页码,从 1 开始 |
|
||||
| `pageSize` | Integer | 是 | 每页条数 |
|
||||
| `statusGroup` | String | 否 | ✨ Tab 分组过滤,取值见枚举表。不传则不约束(等同全部)。与 `status` 多选参数 AND 叠加 |
|
||||
| `status` | String[] | 否 | 精确状态多选,取值见 `RefundApplicationStatus` 枚举 |
|
||||
| `keyword` | String | 否 | 关键词搜索(订单号/申请单号/客人姓名) |
|
||||
| `createTimeStart` | String | 否 | 申请时间起,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `createTimeEnd` | String | 否 | 申请时间止,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### 接口 1:`GET /v3/admin/refund/application/stats`
|
||||
|
||||
**返回类型**:`Result<List<RefundStatItemVO>>`
|
||||
|
||||
数组元素 `RefundStatItemVO`:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `code` | String | 分组唯一标识,见枚举表 |
|
||||
| `name` | String | 分组中文名,可直接展示为 Tab 文案 |
|
||||
| `count` | long | 该分组申请单数量 |
|
||||
| `amount` | String | 该分组退款金额合计(元,2 位小数,如 `"1234.50"`) |
|
||||
|
||||
数组顺序固定,共 6 项:
|
||||
|
||||
| 顺序 | code | name |
|
||||
|------|------|------|
|
||||
| 1 | ALL | 全部 |
|
||||
| 2 | PENDING | 待审批 |
|
||||
| 3 | REFUNDING | 退款中 |
|
||||
| 4 | FAILED | 失败待处理 |
|
||||
| 5 | COMPLETED | 已完成 |
|
||||
| 6 | CURRENT_MONTH_REFUNDED | 本月已退款 |
|
||||
|
||||
**前端使用建议**:列表 Tab 角标取前 5 项(ALL/PENDING/REFUNDING/FAILED/COMPLETED),顶部金额卡片另取第 6 项(CURRENT_MONTH_REFUNDED)。
|
||||
|
||||
---
|
||||
|
||||
### 接口 2:`GET /v3/admin/refund/application/page`
|
||||
|
||||
返回类型与原来相同(`Result<PageResult<RefundApplicationRespVO>>`),本次无出参字段变化,不赘述。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
### `statusGroup` 枚举(新增,接口 1 和接口 2 共用同一套口径)
|
||||
|
||||
| code | 中文名 | 后端过滤口径 |
|
||||
|------|--------|-------------|
|
||||
| `ALL` | 全部 | status ∈ (PENDING, APPROVED, REFUNDING, REFUNDED),**排除** REJECTED(已驳回)和 CANCELLED(已取消) |
|
||||
| `PENDING` | 待审批 | status = PENDING |
|
||||
| `REFUNDING` | 退款中 | status ∈ (APPROVED, REFUNDING),**且**最新退款记录不为失败 |
|
||||
| `FAILED` | 失败待处理 | 最新退款记录 = 失败(优先级最高,从退款中挪出;重发成功后不再算失败) |
|
||||
| `COMPLETED` | 已完成 | status = REFUNDED |
|
||||
| `CURRENT_MONTH_REFUNDED` | 本月已退款 | status = REFUNDED,且退款完成时间在本自然月内 |
|
||||
|
||||
**说明**:
|
||||
- 五组互斥(ALL/PENDING/REFUNDING/FAILED/COMPLETED),ALL = 其余四组之和。
|
||||
- FAILED 优先级最高:一笔申请只要最新退款记录是失败,无论 status 是 REFUNDING 还是 APPROVED,都归入 FAILED 而不在 REFUNDING 中计数。
|
||||
- ALL 列表**包含** FAILED 的申请(status=REFUNDING 但最新记录失败),前端如需对失败单标红,自行判断最新退款记录状态。
|
||||
- REJECTED(已驳回)和 CANCELLED(已取消)**不进**任何 Tab,属于终态归档,不在待办范围内。
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发条件 |
|
||||
|--------|------|----------|
|
||||
| `400` | 参数校验失败 | `statusGroup` 传了非法枚举值 |
|
||||
| `401` | 未登录 | JWT 缺失或过期 |
|
||||
| `403` | 无权限 | 非管理后台角色 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功:获取统计数组
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/refund/application/stats
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{ "code": "ALL", "name": "全部", "count": 38, "amount": "52340.00" },
|
||||
{ "code": "PENDING", "name": "待审批", "count": 5, "amount": "8800.00" },
|
||||
{ "code": "REFUNDING", "name": "退款中", "count": 12, "amount": "17200.00" },
|
||||
{ "code": "FAILED", "name": "失败待处理", "count": 3, "amount": "4100.00" },
|
||||
{ "code": "COMPLETED", "name": "已完成", "count": 18, "amount": "22240.00" },
|
||||
{ "code": "CURRENT_MONTH_REFUNDED", "name": "本月已退款", "count": 11, "amount": "13500.00" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 边界情况:某 Tab 无数据
|
||||
|
||||
当 FAILED 分组无数据时,count=0、amount="0.00",数组项仍然返回(不缺项):
|
||||
|
||||
```json
|
||||
{ "code": "FAILED", "name": "失败待处理", "count": 0, "amount": "0.00" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.3 业务失败:statusGroup 传了非法值
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/refund/application/page?statusGroup=UNKNOWN&pageNo=1&pageSize=10
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "参数错误:statusGroup 枚举值不合法",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 管理后台退款待办工作台 Tab 切换与角标显示。
|
||||
- 顶部「本月已退款」金额卡片。
|
||||
|
||||
**不适用场景**:
|
||||
- 小程序端(无此工作台)。
|
||||
- `REJECTED` / `CANCELLED` 状态的申请不在任何 Tab 中,若需展示历史归档申请请单独传 `status=REJECTED` 或 `status=CANCELLED` 参数。
|
||||
|
||||
**特殊边界**:
|
||||
- `FAILED` 判定依赖最新退款记录状态,与申请单自身的 `status` 字段不完全对应。点击「失败待处理」Tab 时后端已按此口径过滤,前端无需再做二次判断来决定哪些展示在该 Tab。
|
||||
- `ALL` Tab 中包含 `FAILED` 的申请,前端展示全部时如需区分失败/正常退款中,可读申请单内的退款记录列表字段。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 接口 1:统计接口出参结构对比
|
||||
|
||||
**旧出参(已废弃,前端需移除)**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"pendingCount": 5,
|
||||
"pendingAmount": "8800.00",
|
||||
"refundingCount": 12,
|
||||
"refundingAmount": "17200.00",
|
||||
"failedCount": 3,
|
||||
"failedAmount": "4100.00",
|
||||
"completedCount": 18,
|
||||
"completedAmount": "22240.00",
|
||||
"currentMonthRefundedAmount": "13500.00",
|
||||
"currentMonthRefundedCount": 11
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**新出参(当前版本)**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "code": "ALL", "name": "全部", "count": 38, "amount": "52340.00" },
|
||||
{ "code": "PENDING", "name": "待审批", "count": 5, "amount": "8800.00" },
|
||||
{ "code": "REFUNDING", "name": "退款中", "count": 12, "amount": "17200.00" },
|
||||
{ "code": "FAILED", "name": "失败待处理", "count": 3, "amount": "4100.00" },
|
||||
{ "code": "COMPLETED", "name": "已完成", "count": 18, "amount": "22240.00" },
|
||||
{ "code": "CURRENT_MONTH_REFUNDED", "name": "本月已退款", "count": 11, "amount": "13500.00" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
字段级对比:
|
||||
|
||||
| 旧字段 | 新位置 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `pendingCount` | `data[code=PENDING].count` | 已移至数组元素 |
|
||||
| `pendingAmount` | `data[code=PENDING].amount` | 已移至数组元素 |
|
||||
| `refundingCount` | `data[code=REFUNDING].count` | 已移至数组元素 |
|
||||
| `refundingAmount` | `data[code=REFUNDING].amount` | 已移至数组元素 |
|
||||
| `failedCount` | `data[code=FAILED].count` | 已移至数组元素 |
|
||||
| `failedAmount` | `data[code=FAILED].amount` | 已移至数组元素 |
|
||||
| `completedCount` | `data[code=COMPLETED].count` | 已移至数组元素 |
|
||||
| `completedAmount` | `data[code=COMPLETED].amount` | 已移至数组元素 |
|
||||
| `currentMonthRefundedAmount` | `data[code=CURRENT_MONTH_REFUNDED].amount` | 已移至数组元素 |
|
||||
| `currentMonthRefundedCount` | `data[code=CURRENT_MONTH_REFUNDED].count` | 已移至数组元素 |
|
||||
| —(新增) | `data[code=ALL].count/amount` | 新增「全部」汇总项(旧版无此项) |
|
||||
|
||||
### 接口 2:分页接口入参对比
|
||||
|
||||
| 字段 | 变化 | 说明 |
|
||||
|------|------|------|
|
||||
| `statusGroup` | 新增(可选) | 新增 Tab 口径过滤参数,不传等同旧行为 |
|
||||
| 其余参数 | 不变 | 保持向后兼容 |
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
**破坏兼容**:
|
||||
- 接口 1 出参结构**已破坏兼容**。旧代码读 `data.pendingCount` 等扁平字段将返回 `undefined`,Tab 角标全部显示为空。**前端必须同步改造**。
|
||||
|
||||
**前端同步上线**:
|
||||
- 接口 1 读取逻辑需改为遍历数组,按 `code` 取值。
|
||||
- 接口 2 点击 Tab 时加 `statusGroup` 参数,旧版不传时行为不变,**可兼容部署**(接口 2 无破坏)。
|
||||
|
||||
**回滚方案**:
|
||||
- 若需回滚接口 1,后端可恢复旧版扁平结构;前端需配合回滚。
|
||||
- 接口 2 的 `statusGroup` 参数为可选,后端回滚直接忽略此参数,前端不受影响。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **金额字段均为 String**(元,2 位小数),如 `"1234.50"`。禁止用 `Number()` / `parseFloat()` 解析后再做展示(防 JS 精度丢失)。直接展示即可。
|
||||
2. **count 字段为 long(整数)**,无需特殊处理。
|
||||
3. 数组顺序由后端固定,前端按顺序渲染 Tab 即可,无需按 code 排序。
|
||||
4. `FAILED` 口径:最新退款记录失败即归入,与申请单 `status` 不完全对等,不要用 status === "REFUNDING" 来判断是否是「退款中」Tab(因为 FAILED 单的 status 也可能是 REFUNDING)。
|
||||
5. 分页接口新增的 `statusGroup` 与既有 `status` 多选是 AND 关系,避免同时传两者造成无结果(如 `statusGroup=PENDING&status=REFUNDING` 必然为空)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4303](https://git.1814.love:8443/wx/HL/issues/4303)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4308](https://git.1814.love:8443/wx/HL/pulls/4308)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,49 @@
|
||||
# 房务配房:异常态加「标记完成」终态 + 「已确认」文案统一改「已完成」 + 释放/转单弹窗 body 必填 400 修复
|
||||
|
||||
> 变更类型:✅ 新增接口 + 🔧 修复(已部署测试服;契约层已 round-trip 实测,数据依赖行为见下「验证说明」)
|
||||
> 端类型:管理后台(房务管家·订单详情/异常桶/转单·释放弹窗)
|
||||
> 日期:2026-06-23 | 工单:#4307 #4315 #4317 | PR:#4312 #4318 #4319 | 服务:hl-order-service-v3 / hl-user-service
|
||||
> 关联:承接 #4301(房务状态机修复)的 P1 设计项;本批多为房务侧体验修复。
|
||||
|
||||
---
|
||||
|
||||
## 一、需要前端动手的
|
||||
|
||||
### 1. 异常态加「标记完成」按钮(新增端点)✅契约已实测
|
||||
房务 6 态里「异常」原本没有「处置完成」出口(取消单/放弃单永卡异常)。现新增统一终态出口。
|
||||
|
||||
- **端点**:`POST /v3/admin/order/hotel-requirements/{requirementId}/close`
|
||||
- **body**:可空。可选 `{"reason":"异常已处置"}`(reason 选填,≤200 字)。
|
||||
- **成功**:`house_status` → `CONFIRMED`(label =「已完成」)。
|
||||
- **前置**:仅 `house_status == EXCEPTION` 可调;非异常态返 **808022「当前房务状态非异常,不可标记完成」**;需求不存在返 808002。
|
||||
- **前端**:异常桶/订单详情,当 `houseStatus==EXCEPTION` 时显示「标记完成」按钮 → 调本端点 → 成功后该单进「已完成」。
|
||||
|
||||
### 2. 房务终态文案:「已确认」→「已完成」
|
||||
房务侧 `CONFIRMED` 态的中文 label 由「已确认」统一改为「已完成」(配房回传完成 与 异常处置完成 都落这个终态)。
|
||||
- 后端 `houseStatusLabel` / 进度条 step5 等已返「已完成」。
|
||||
- **前端**:若有**硬编码**「已确认」字样(房务终态徽章/进度条/筛选 Tab)需同步改「已完成」;若直接用后端返回的 label 则无需改。
|
||||
|
||||
---
|
||||
|
||||
## 二、后端已修复 / 行为变化(前端一般无需改,知会)
|
||||
|
||||
### 3. 释放/转单确认弹窗 body 必填 400 — 已修复 ✅实测
|
||||
之前「确认释放」「确认转单」弹窗不带 body 时后端报 `400 Required request body is missing`。
|
||||
- 现 `release` / `close` 端点 body 改可选;`release/close/transfer` 的 `reason` 改可选(reason 留空后端兜底)。
|
||||
- `transfer` 的 `toUserId` 仍必填;超管指派 reason≥10 字校验保留。
|
||||
- **前端无需改**:原来不传 body 的调用现在不再 400。
|
||||
|
||||
### 4. 转单候选去企微强制过滤(纯后端)
|
||||
「转单/指派」候选「选择目标房务」原只返回**已绑企微**的房务,导致未绑企微账号不显示、下拉「无数据」。现改为按**房务角色**(house_keeper/house_lead,多角色感知)+ 活跃查,**企微可选**(未绑企微者名字回落用户名)。
|
||||
- **前端无需改**。
|
||||
- 注意:候选仍要求环境里有除自己外、持房务角色、活跃的账号;若仍空请确认环境已给测试账号分配 house_keeper/house_lead 角色。
|
||||
|
||||
### 5. 释放回池后会话消息转「留言」+ 重抢转发(纯后端)
|
||||
房务释放回池后,原房务↔定制师的会话消息退回「留言池」(抢单前留言态);之后别的房务再抢单时,这些留言会转发给新房务(计入未读 + SSE)。
|
||||
- **前端无需改**:沿用现有「内部留言框」/未读展示,行为自然生效。
|
||||
|
||||
---
|
||||
|
||||
## 验证说明(如实)
|
||||
- ✅ **测试服 round-trip 实测通过**:`release` / `close` 端点已上线、body 可空(无 body 调用返业务码而非 `Required request body is missing` 400)、`/close` 已路由(非 404)。
|
||||
- ⚠️ **数据依赖行为未在测试服演示**:测试服当前无房务订单/房务角色账号,故「候选有数据」「异常单标记完成→已完成」「释放后留言转发」三项**完整业务流程**未在测试服跑通演示;均已被后端**单元测试覆盖**且已部署(hl-order-service-v3 双实例 8086/8186、hl-user-service 双实例 8081/8181 health UP)。建议在有房务数据的环境二次确认完整流程。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 定制师侧订单详情/调整快照:配房需求候选酒店暴露真实房型 roomTypeId/roomTypeName(已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 修改接口(新增字段,已部署测试服并 API 验证,可对接)
|
||||
> 端类型:管理后台(定制师侧·订单详情行程 / 调整「酒店安排」快照)
|
||||
> 日期:2026-06-23 | 工单:#4204(P2) | PR:#4313 | 服务:hl-order-service-v3
|
||||
> 关联:承接同日 #4305(房务侧 P3,房务详情已暴露同字段)。这是**定制师侧**对称回显。
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
#4204 把配房需求房型从「段级抽象 roomCategory」下沉到「每家候选酒店各自的真实房型 roomTypeId」。P1(#4209) 已写库、P3(#4305) 房务侧已读出,本期(P2)**定制师侧**订单详情/调整快照也读出真实房型。
|
||||
|
||||
## 新增字段(两个接口、候选酒店维度)
|
||||
1. `GET /v3/admin/order/{id}/itinerary` → 行程「配房需求」每天的候选酒店 `RequirementHotelVO`
|
||||
2. `GET /v3/admin/order/{id}/adjustment/snapshot` → 「酒店安排」每天每段 `HotelSegmentVO`
|
||||
|
||||
两处的候选/酒店项各**新增**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `roomTypeId` | String(雪花,可空) | 该候选酒店的**真实房型 ID** |
|
||||
| `roomTypeName` | String(可空) | 该候选酒店的**真实房型名称** |
|
||||
|
||||
**前端渲染**:展示候选酒店时,用 `roomTypeName`(真实房型)替代/优先于段级 `roomCategory`(抽象大类)。二者可能不同——例:同为段级「普通标间」,香格里拉真实房型=「普通标间」、嘉世豪真实房型=「标准间」。`roomTypeId/roomTypeName` 为 null(旧单未录真实房型)时回退展示 `roomCategory`。
|
||||
|
||||
## 实测(测试服 order 2068234602970828802)
|
||||
- itinerary:香格里拉 `roomTypeName=普通标间`、嘉世豪 `roomTypeName=标准间`(真实房型,与段级大类不同)。
|
||||
- adjustment/snapshot:同样暴露 `roomTypeId/roomTypeName`(source=MANUAL)。
|
||||
|
||||
## 待前端(#4204 P5,mmg)
|
||||
定制师**提交需求面板**改「先选酒店→选该酒店真实房型」录入 `roomTypeId` 后,新单真实房型贯穿展示(后端读写两侧已就绪)。
|
||||
@@ -0,0 +1,169 @@
|
||||
# 财务详情新增「应收总额」payableAmount 字段(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(3 个接口出参各新增 1 字段)
|
||||
- 关联 Issue:#4321 PR:#4322
|
||||
- 日期:2026-06-24
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单财务此前只返回**订单总价 totalAmount(= 产品原价,未扣优惠、未加增项)**,从总价直接到「待收尾款」中间缺一个承接节点,导致「订单总价 / 增加费用 / 优惠费用 / 待收尾款」勾稽链看着对不上。
|
||||
|
||||
本次新增**派生字段 `payableAmount`(应收总额)**,补齐勾稽链:
|
||||
|
||||
```
|
||||
订单总价 totalAmount
|
||||
+ 增加费用 surchargeAmount
|
||||
− 优惠费用 discountAmount
|
||||
─────────────────────────
|
||||
= 应收总额 payableAmount ← 本次新增
|
||||
− 已付 paidAmount
|
||||
− 已退 refundAmount
|
||||
─────────────────────────
|
||||
= 待收尾款 balanceAmount
|
||||
```
|
||||
|
||||
`payableAmount` 是后端实时算出的派生值(不落库),与既有 `balanceAmount` 口径完全一致,老订单自动对上。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/order/{id}/finance` | 出参 FinanceVO 新增 `payableAmount` |
|
||||
| 2 | GET | `/v3/admin/order/{id}` | 出参 `data.main`(OrderMainVO)新增 `payableAmount` |
|
||||
| 3 | GET | `/v3/admin/order/list`(别名 `/v3/admin/order`)| 出参列表项 OrderListItemRespVO 新增 `payableAmount` |
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
> 仅新增字段,无入参变化、无字段删除/改名、无枚举变化。前端可按需取用,不取不影响既有逻辑。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 财务 Tab `GET /v3/admin/order/{id}/finance`
|
||||
出参 `FinanceVO` 新增 `payableAmount`。其余字段不变。
|
||||
|
||||
### 2. 订单详情头部 `GET /v3/admin/order/{id}`
|
||||
出参 `data.main`(OrderMainVO)新增 `payableAmount`。其余字段不变。
|
||||
|
||||
### 3. 订单列表 `GET /v3/admin/order/list`
|
||||
出参每个列表项(OrderListItemRespVO)新增 `payableAmount`。其余字段不变。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
无变化(本次仅出参新增字段)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
新增字段(3 个接口一致):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| payableAmount | string(decimal) | **应收总额** = 订单总价 + 增加费用 − 优惠费用(≥0,字符串化防精度丢失)|
|
||||
|
||||
与之关联的既有字段(口径参考,本次不变):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| totalAmount | string(decimal) | 订单总价(产品原价,未扣优惠/未加增项)|
|
||||
| surchargeAmount | string(decimal) | 增加费用汇总(仅 FinanceVO 有)|
|
||||
| discountAmount | string(decimal) | 优惠费用汇总(仅 FinanceVO 有)|
|
||||
| paidAmount | string(decimal) | 已付金额 |
|
||||
| refundAmount | string(decimal) | 已退金额(仅 FinanceVO 有)|
|
||||
| balanceAmount | string(decimal) | 待收尾款 = 应收总额 − 已付 − 已退(≥0)|
|
||||
|
||||
> 注:详情头部 main 与订单列表项不含 surchargeAmount/discountAmount 明细字段,但 payableAmount 已是含增减项后的应收净额,可直接展示。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
无。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无新增(查询接口正常返回 200)。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:财务 Tab(原价 3105 / 优惠 150 / 无增项 / 未支付)
|
||||
请求 `GET /v3/admin/order/2068235251926114306/finance`,响应(节选):
|
||||
```json
|
||||
{ "code":200, "success":true, "data": {
|
||||
"totalAmount":"3105.00",
|
||||
"surchargeAmount":"0.00",
|
||||
"discountAmount":"150.00",
|
||||
"payableAmount":"2955.00",
|
||||
"paidAmount":"0.00",
|
||||
"refundAmount":"0.00",
|
||||
"balanceAmount":"2955.00"
|
||||
} }
|
||||
```
|
||||
校验:payableAmount 2955 = 3105 + 0 − 150;balanceAmount 2955 = 2955 − 0 − 0。
|
||||
|
||||
### 边界:含增项 + 优惠 + 部分已付
|
||||
原价 3105 / 增项 300 / 优惠 350 / 已付 1000:
|
||||
```json
|
||||
{ "totalAmount":"3105.00", "surchargeAmount":"300.00", "discountAmount":"350.00",
|
||||
"payableAmount":"3055.00", "paidAmount":"1000.00", "refundAmount":"0.00", "balanceAmount":"2055.00" }
|
||||
```
|
||||
payableAmount 3055 = 3105 + 300 − 350;balanceAmount 2055 = 3055 − 1000 − 0。
|
||||
|
||||
### 订单列表
|
||||
请求 `GET /v3/admin/order/list?pageNo=1&pageSize=1`:
|
||||
```json
|
||||
{ "code":200, "data": { "records":[ { "totalAmount":"503.00", "payableAmount":"503.00", "balanceAmount":"0.00" } ] } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- `payableAmount` 是后端实时派生(不落库),任意时刻 = totalAmount + surchargeAmount − discountAmount,最小为 0。
|
||||
- 与 `balanceAmount` 关系恒为:`balanceAmount = payableAmount − paidAmount − refundAmount`(≥0)。
|
||||
- 增减项/优惠变化后,刷新接口即得到最新 payableAmount,无需前端自算。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 财务/详情/列表出参 | 只有 totalAmount(原价)、balanceAmount(待收)| 新增 payableAmount(应收总额),勾稽链闭合 |
|
||||
| 前端展示成交净额 | 需前端自己用 总价+增项−优惠 计算 | 直接取后端 payableAmount |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **非破坏性**:仅新增出参字段,不删不改既有字段,前端零改动即兼容;需要展示「应收总额/成交价」时取用本字段。
|
||||
- 回滚:后端回滚 PR #4322(字段消失,其余不变)。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段均为**字符串**,前端按字符串处理防精度丢失。
|
||||
- payableAmount 已含增减项,前端不要再二次加减增项/优惠(会重复计算)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4321
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4322
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(finance/详情头部/列表 三接口均返 payableAmount 且勾稽对账一致)。
|
||||
@@ -0,0 +1,50 @@
|
||||
# 房务聊天/站内信 4 修复:收件箱去聊天行 + 房务侧 peer 名 + 内部留言留言池 + 未读总数实时 SSE
|
||||
|
||||
> 变更类型:🔧 修复 ×3(纯后端,前端一般无需改)+ ✨ 实时能力 ×1(前端可接 SSE 停轮询)
|
||||
> 端类型:管理后台(站内信收件箱 / 房务管家·订单详情·聊天/内部留言 / 顶部未读角标)
|
||||
> 日期:2026-06-24 | 工单:#4325 | PR:#4326 | 服务:hl-user-service(双实例 8081/8181 已部署 health UP,A/C/D 测试服实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 一、纯后端修复(前端无需改,知会)
|
||||
|
||||
### A. 站内信收件箱不再混入聊天消息(修复)
|
||||
站内信收件箱列表 `GET /admin/message/list`、分类未读汇总、「全部已读/分类已读」原先漏过滤消息特性 `kind`,把 `kind=CHAT` 的 1:1 聊天消息(无标题)也查进了收件箱,显示成「空标题」行。
|
||||
- 现收件箱口径只含 `kind=NOTIFY`(系统通知);聊天消息只走聊天 Tab(`/admin/message/chat/*`),不再污染收件箱列表/分类红点/一键已读。
|
||||
- **顶部铃铛合并角标** `GET /admin/message/unread-count` 仍为「通知+聊天」合并口径,不变。
|
||||
- **前端无需改**:收件箱列表此后只返真站内信,空标题聊天行自然消失。
|
||||
|
||||
### B. 房务侧聊天「联系定制师」对方名显示错(修复)
|
||||
房务**首次**抢某单后,房务侧聊天/会话列表里「对方(定制师)」的姓名快照被错写成房务自己的名字(如显示成 admin 自己);抢过其它单的房务走另一分支则正常。
|
||||
- 现首次抢单建房务会话成员行时,对方名正确取**订单定制师**姓名。
|
||||
- **前端无需改**:`peerName` 此后返订单真实定制师名。
|
||||
- 注意:修复前已产生的错误会话行(历史脏数据)不会自动回填,下次该单抢单/转单/释放重绑时自动纠正。
|
||||
|
||||
### C. 抢单后「内部留言」不再显示已转聊天的消息(修复)
|
||||
房务详情「内部留言框」(`leaveMessages`,定制师抢单前发的留言)原先返回会话全部聊天行;抢单后这些留言已转为房务的聊天消息,却仍显示在内部留言框。
|
||||
- 现内部留言框只显示**仍在留言池(无房务接收)**的留言:抢单后留言转给房务即从框内消失(进聊天),释放回池后重新出现,与留言池语义一致。
|
||||
- **前端无需改**:`leaveMessages` 此后只含留言池消息。
|
||||
|
||||
---
|
||||
|
||||
## 二、未读总数实时化(前端可接,停轮询)
|
||||
|
||||
### D. 合并未读总数并入 SSE 长连接(新增实时事件)
|
||||
原 `GET /admin/message/unread-count`(通知+聊天合并未读)需前端轮询;现并入 SSE 长连接实时推送。
|
||||
- **SSE 端点不变**:`GET /ws/admin-msg/stream`(已有长连接)。
|
||||
- **新增事件**:`event: unread-count`,`data` 为 JSON,关键字段 `unreadCount`(合并未读总数,等同 `/admin/message/unread-count` 的值)、`type:"UNREAD"`、`adminId`。
|
||||
- **推送时机**:①**建连即推一次**初始未读(前端首屏直接拿角标,无需先调 unread-count 接口)②**标记已读后实时推**更新(角标下降)③新消息到达仍走原 `message`(通知)/`im-chat`(聊天)事件,本就携带 `unreadCount`。
|
||||
- **前端建议**:监听 SSE `unread-count` 事件刷新顶部角标,**停止轮询** `/admin/message/unread-count`(接口保留兜底,可在 SSE 断线时回退轮询)。
|
||||
|
||||
---
|
||||
|
||||
## 三、前端待办(站内信「类型」列展示)
|
||||
站内信列表项后端**早已返回**消息类型字段,前端未建列展示,建议加「类型」列:
|
||||
- `messageTypeLabel`(中文,如「订单消息」「普通消息」)/ `messageType`(ORDER/NORMAL);亦可用 `categoryCode`(SYSTEM 等)/`bizType`(ORDER/HOUSE/REFUND)。
|
||||
- 直接渲染 `messageTypeLabel` 即可,无需新接口。
|
||||
|
||||
---
|
||||
|
||||
## 验证说明(如实)
|
||||
- ✅ **测试服实测**:A 收件箱列表已只返 NOTIFY(聊天行消失);C 已抢单单的 `leaveMessages` 返空;D SSE 建连即收 `unread-count` 事件(`{adminId,unreadCount,type:UNREAD}`)。
|
||||
- ✅ 本域单测全绿(chat/notification/sse/adminmessage);B 由单测覆盖(首次抢单对方名取定制师),线上 update 分支本就正确。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 房务聊天:对方角色徽章返中文 label(后端已做)+ 前端去掉「给定制师留言」按钮(只留聊天)
|
||||
|
||||
> 变更类型:✨ 后端补中文 label 字段(已部署)+ 🎨 前端 UI 简化(去留言入口)
|
||||
> 端类型:管理后台(房务管家·订单详情·聊天面板 / 会话列表 / 内部留言 Tab)
|
||||
> 日期:2026-06-24 | 工单:#4327 | PR:#4328 | 服务:hl-user-service(双实例 8081/8181 已部署 health UP,实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 一、后端已做:对方角色徽章返中文 label(前端改用即可)
|
||||
聊天面板/会话列表「对方角色」原只返英文码 `peerRole`(如 `CUSTOMIZER`),徽章显示成英文。现后端**新增中文 label 字段**:
|
||||
|
||||
- 受影响接口:
|
||||
- `POST /admin/message/chat/open`、`POST /admin/message/chat/open-house`(出参 `ChatOpenRespVO`)
|
||||
- `GET /admin/message/chat/conversations`(出参 list 项 `ChatConversationRespVO`)
|
||||
- **新增字段 `peerRoleLabel`**(中文):`CUSTOMIZER→定制师`、`HOUSE→房务`、`FLEET→车务`、`SYSTEM→系统`。
|
||||
- 占位「待接单」会话 `peerRole` 为空时 `peerRoleLabel` 同为空;未知码回退原码。
|
||||
- **前端**:角色徽章改用 `peerRoleLabel` 展示(原 `peerRole` 英文码保留不动,可继续做逻辑判断)。
|
||||
- ✅ 测试服实测:`open-house` 与 `conversations` 均返 `peerRoleLabel=定制师`。
|
||||
|
||||
---
|
||||
|
||||
## 二、前端待办:去掉「给定制师留言」按钮,沟通只走聊天
|
||||
房务管家订单详情目前有两套沟通入口——「联系定制师」(实时聊天) 和「给定制师留言」(异步留言)。实时聊天已可用,**留言入口冗余,去掉,只保留聊天**:
|
||||
|
||||
- **移除**「给定制师留言」按钮(订单详情头部右上 + 「内部留言」Tab 底部各一个)。
|
||||
- 房务↔定制师沟通**统一走「联系定制师」**(实时聊天,对方角色徽章已中文化)。
|
||||
- 「内部留言」Tab:**保留**上方「定制师需求」展示(晚数/酒店/诉求/备注,有用);**移除**下方留言输入区/留言记录区(改去聊天看沟通记录)。Tab 名是否保留/改名由前端定。
|
||||
|
||||
### 后端说明(前端无需关心,仅备注)
|
||||
- 留言「池」机制(抢单前定制师消息暂存 + 换房务时转发给新房务)**作为聊天的底座保留**,不随留言按钮移除而下线——它是聊天在「还没房务接单 / 释放回池再被抢」时的消息连续性保障,对前端透明。
|
||||
- 取留言列表的内部接口 `listHouseLeaveMessages`(房务详情 `leaveMessages`)在前端撤掉留言展示后即闲置,**暂留兜底不删**;后续确认无调用方再弃用。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 房务释放回池:冻结旧房务会话 + 旧房务消息整段转「留言池」(链路修通真生效)
|
||||
|
||||
> 变更类型:🔧 修复(后端,前端按下方「留言态展示」配合)
|
||||
> 端类型:管理后台(房务管家·订单详情聊天 / 定制师侧「待接单房务」会话)
|
||||
> 日期:2026-06-24 | 工单:#4329 #4331 | PR:#4330 #4332 | 服务:hl-user-service(双实例已部署 health UP,**测试服 DB+API ground-truth 实测全通过**)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
房务订单聊天=方案A(一单一会话 `HOUSE:{orderId}`,房务轮换)。此前「释放回抢单池」后两个 bug:
|
||||
1. 定制师还能跟**旧房务**继续聊(旧房务会话没冻结);
|
||||
2. 上次聊天内容没转成「留言」(下个房务抢单看不到对话连续性)。
|
||||
|
||||
根因有两层:
|
||||
- **#4331(根因)**:释放事件经 Feign 调 user 绑定时传 `houseAdminId=null`,但 user 端入参 VO 该字段有 `@NotNull` → `@Valid` 直接 400 打回 → 释放绑定逻辑**从未真正执行**(软依赖被 fallback 静默吞掉,主流程照常成功,潜伏数周)。去掉该 `@NotNull` 后链路才通。
|
||||
- **#4330(逻辑)**:释放绑定逻辑本身补全——归档旧房务成员行 + 旧房务名下消息整段转留言池。
|
||||
|
||||
## 后端行为变化(前端一般无需改)
|
||||
释放回抢单池后:
|
||||
- **旧房务**:会话成员行 `status→ARCHIVED`,脱离该会话——其会话列表不再显示本会话、且**不能再发消息**(发消息返业务码「无权访问该会话」)。
|
||||
- **该会话全部聊天消息**(含旧房务已读的)整段改判回「留言池」(无房务接收态)。
|
||||
- **定制师**:其「联系房务」回退「待接单房务」态(peer 占位),后续发的消息进留言池。
|
||||
- **下个房务抢单**:留言池消息整段转发给新房务(计入未读 + 转为正常聊天)。
|
||||
- 同一房务**重新抢回**该单:归档行自动复活(`status→ACTIVE`),聊天恢复。
|
||||
|
||||
## 前端「留言态」展示配合(重点)
|
||||
释放回池后,定制师打开会话标题显示「待接单房务」,**该会话历史消息整体呈现为「留言」态**——这是预期效果(无房务接单期间的消息都是留言,等下个房务抢单转为实时聊天):
|
||||
- 定制师侧:会话仍可打开、可继续发(进留言池),消息标「留言」属正常,**不是 bug**。
|
||||
- 房务侧(未抢单):订单详情显示「抢单后可与定制师实时沟通」占位(前端已去掉旧「内部留言」入口,符合「只留聊天」要求)。
|
||||
- 旧房务侧:释放后该会话从其会话列表消失(已归档),符合预期。
|
||||
|
||||
## 验证说明(如实·ground-truth)
|
||||
✅ 测试服 DB + API 实测全通过:
|
||||
- 释放后旧房务(admin)成员行 `status=ARCHIVED`、其会话列表无此会话;
|
||||
- 旧房务释放后发消息被拒(「无权访问该会话」);
|
||||
- 该单留言池消息齐全(定制师留言 admin_id=0 就绪,下个房务抢单可转发);
|
||||
- 同房务重抢→成员行复活 `ACTIVE`、聊天恢复。
|
||||
@@ -0,0 +1,146 @@
|
||||
# 待收尾款 balanceAmount 退款口径修正 + 详情头部/列表补 refundAmount(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(balanceAmount 语义修正 ⚠️数值会变 + 2 接口出参新增 refundAmount)
|
||||
- 关联 Issue:#4333 PR:#4334
|
||||
- 日期:2026-06-24
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
修复「待收尾款」`balanceAmount` 在**有退款订单上算错**的 bug,并补齐「已退款」字段。
|
||||
|
||||
此前 `balanceAmount` 把已退款金额**多减了一次**:order_main 的 `paidAmount`(已付)是毛累计、退款不回减,`refundedAmount`(已退)是另一个毛累计,二者独立。商家实际净收 = 已付 − 已退。但旧公式 `应收 − 已付 − 已退` 等于把退款反向减了,导致有退款订单的待收尾款偏小(误差 = 2×退款额)。
|
||||
|
||||
**修正后口径**:
|
||||
```
|
||||
待收尾款 balanceAmount = 应收总额 − 净已付
|
||||
= payableAmount − (paidAmount − refundAmount)
|
||||
= totalAmount + surchargeAmount − discountAmount − paidAmount + refundAmount (≥0)
|
||||
```
|
||||
|
||||
> 无退款订单(refundAmount=0)`balanceAmount` **数值不变**;仅有退款订单会变大(修正为正确值)。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/order/{id}/finance` | `balanceAmount` 口径修正(⚠️有退款订单数值变大)|
|
||||
| 2 | GET | `/v3/admin/order/{id}` | `data.main`:`balanceAmount` 口径修正 + **新增 `refundAmount`** |
|
||||
| 3 | GET | `/v3/admin/order/list`(别名 `/v3/admin/order`)| 列表项:`balanceAmount` 口径修正 + **新增 `refundAmount`** |
|
||||
|
||||
> finance 接口此前已有 `refundAmount`,本次仅口径修正;详情头部 main 与列表项本次新增 `refundAmount`。
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
3 个接口的「待收尾款」`balanceAmount` 统一改为「应收 − 净已付(已付 − 已退)」口径。详情头部、订单列表新增 `refundAmount`(已退款金额),供财务明细展示「已退款」行,使「应收 − 已付 + 已退 = 待收」可见链闭合。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
无变化。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
| 字段 | 类型 | 说明 | 本次 |
|
||||
|---|---|---|---|
|
||||
| balanceAmount | string(decimal) | 待收尾款 = 应收 − 已付 + 已退(≥0)| **口径修正**(有退款订单数值变大)|
|
||||
| refundAmount | string(decimal) | 已退款金额(= order_main.refunded_amount)| 详情头部/列表 **新增**(finance 已有)|
|
||||
|
||||
关联既有字段(口径参考,不变):
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| totalAmount | 订单总价(产品原价)|
|
||||
| payableAmount | 应收总额 = 总价 + 增项 − 优惠 |
|
||||
| paidAmount | 已付金额(毛累计,退款不回减)|
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
无。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:付定金后又全额退款(净已付 0)
|
||||
订单原价 3105 / 优惠 300 / 应收 2805 / 付定金 500 / 全退 500:
|
||||
```json
|
||||
{ "code":200, "data": {
|
||||
"totalAmount":"3105.00", "discountAmount":"300.00", "surchargeAmount":"0.00",
|
||||
"payableAmount":"2805.00", "paidAmount":"500.00", "refundAmount":"500.00",
|
||||
"balanceAmount":"2805.00"
|
||||
} }
|
||||
```
|
||||
校验:balanceAmount 2805 = 2805 − (500 − 500)。客户净付 0,仍欠全额。
|
||||
(修正前此单 balanceAmount 错误返回 1805。)
|
||||
|
||||
### 边界:部分退款
|
||||
应收 6010 / 已付 1000 / 已退 300:
|
||||
```json
|
||||
{ "payableAmount":"6010.00", "paidAmount":"1000.00", "refundAmount":"300.00", "balanceAmount":"5310.00" }
|
||||
```
|
||||
balanceAmount 5310 = 6010 − (1000 − 300)。
|
||||
|
||||
### 无退款(不受影响)
|
||||
应收 2805 / 已付 0 / 已退 0:`balanceAmount`=2805(与修正前一致)。
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- `balanceAmount` 派生(不落库),= payableAmount − paidAmount + refundAmount,最小 0。
|
||||
- `paidAmount` 是毛累计已付(退款不会减少它),`refundAmount` 是毛累计已退,净已付 = 二者之差。
|
||||
- 财务明细建议展示:订单总价 → −优惠 → 应收总额 → −已付 → +已退 → 待收尾款。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| balanceAmount(有退款单)| 应收 − 已付 − 已退(退款被多减,偏小)| 应收 − 已付 + 已退(正确)|
|
||||
| balanceAmount(无退款单)| 应收 − 已付 | 不变 |
|
||||
| 详情头部/列表 refundAmount | 无 | 新增 |
|
||||
| 示例单 2068651577349992450 | 1805(错)| 2805(对)|
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **⚠️ 数值变化(非破坏字段结构)**:**有退款订单**的 `balanceAmount` 返回值会变大(这是修正,旧值是错的);无退款订单不变。前端财务/列表展示的待收尾款会随之更新,无需改字段,但需知悉数值口径已修正。
|
||||
- 详情头部/列表新增 `refundAmount` 为非破坏新增,前端按需取用展示「已退款」行。
|
||||
- 回滚:后端回滚 PR #4334。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段均为字符串,前端按字符串处理防精度丢失。
|
||||
- 财务明细的「待收尾款」= 应收 − 已付 + 已退;展示「已退款」行时金额取 `refundAmount`。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4333
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4334
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(36 单恒等式校验全过,含 4 单有退款;示例单 balanceAmount 1805→2805,refundAmount=500 出现在 finance/详情/列表)。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 房务释放回池:整段对话进「留言池」+ 抢单前展示完整留言(前端需展示)
|
||||
|
||||
> 变更类型:🔧 修复(后端,**前端需配合:抢单池/未抢单详情展示留言**)
|
||||
> 端类型:管理后台(房务管家·抢单池/订单详情·内部留言/聊天)
|
||||
> 日期:2026-06-24 | 工单:#4335 | PR:#4336 | 服务:hl-user-service(双实例已部署 health UP,测试服实测 leaveMessages 返 17 条完整对话)
|
||||
|
||||
---
|
||||
|
||||
## 需求
|
||||
「变成留言就应该在抢单的时候看到」——房务释放回抢单池后,这单之前的对话变成留言,**下个房务在抢单池/抢单前**就应能看到完整对话(了解为什么被释放/换人/客人诉求),而不是抢单后才看到。
|
||||
|
||||
## 后端修复(已做)
|
||||
释放回池时,把该会话**整段**聊天消息(读+未读、**含旧房务自己发出的**)整段转入「留言池」。
|
||||
- 此前只把「发给旧房务的」消息转池,旧房务**发出**的对话(如「客人难搞/酒店满房」)没进池,抢单前 `leaveMessages` 看不到。现已整段转入。
|
||||
- 抢单前查询 `leaveMessages`(房务订单详情聚合的 `messages` 字段,#4288)即返完整对话。
|
||||
- 不影响抢单后:下个房务抢单时,留言整段转为该房务的聊天,`leaveMessages` 复归空(抢单后内部留言不重复显示,#4326 行为保持)。
|
||||
|
||||
## 前端需配合(重点)
|
||||
**抢单池 / 未抢单订单详情**,请展示该单 `leaveMessages`(完整留言对话),供房务抢单决策参考——不要只显示「抢单后可与定制师实时沟通」占位。
|
||||
- 数据源:房务订单详情接口的 `messages`(留言列表,每条含 `senderName` / `senderRole`(HOUSE=房务/CUSTOMIZER=定制师) / `content` / `sentAt`)。
|
||||
- 展示位:抢单池卡片/未抢单订单详情的「留言/沟通记录」区。空会话返空数组(无留言时可显示占位)。
|
||||
- 抢单后:转为实时聊天(现有「联系定制师」),`leaveMessages` 自然变空。
|
||||
|
||||
## 验证说明(如实·实测)
|
||||
✅ 测试服实测:claim→旧房务发 3 条→release→房务详情 `messages` 返 **17 条完整对话**(含旧房务 [HOUSE] 角色发的「客人难搞/酒店满房/我先释放了」),抢单前可见完整上下文。
|
||||
@@ -0,0 +1,416 @@
|
||||
# 财务发票管理列表 - ALL tab 改订单视角(破坏性变更)
|
||||
|
||||
- **接口**:GET /v3/admin/order/invoice/page
|
||||
- **变更类型**:修改接口 破坏性变更(tab=ALL 列表语义 + tabCounts.ALL 计数口径均变)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-24
|
||||
- **Issue**:[#4337](https://git.1814.love:8443/wx/HL/issues/4337)
|
||||
- **PR**:[#4338](https://git.1814.love:8443/wx/HL/pulls/4338)
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
财务发票管理列表原先以发票记录为视角。ALL tab 只返回已有发票记录的订单行,tabCounts.ALL 计数只统计发票记录总数,导致:
|
||||
|
||||
- ALL 数量 < NONE(未申请)数量,前端展示出现矛盾
|
||||
- ALL 列表无法让财务在一个页面纵览所有已完成订单的发票状态
|
||||
|
||||
本次将 ALL tab 切换为**订单视角**:以全部 COMPLETED(已完成)订单为数据源,有发票的行展示发票字段,无发票的行 status=NONE,对齐前端原型设计。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | tabCounts.ALL 计数口径 | 仅统计发票记录数(REQUESTED+ISSUED+PUSHED),不含未申请 | 统计全部 COMPLETED 订单数(含未申请),永远 >= 任何单个 tab |
|
||||
| 2 | tab=ALL 列表数据源 | 只返回有发票记录的订单行 | 返回全部 COMPLETED 订单,无发票行 status=NONE |
|
||||
| 3 | titleName/requestedBy 在 ALL/NONE tab 的行为 | 未定义(可能干扰结果) | 静默忽略(两字段仅在 REQUESTED/ISSUED/PUSHED 三个发票视角 tab 生效) |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/invoice/page |
|
||||
| 描述 | 财务发票管理列表(分页),含 tab 计数与汇总统计 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 Query 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| tab | string | 否 | 当前 tab,枚举值见第 6 节;默认 ALL |
|
||||
| pageNo | integer | 否 | 页码,默认 1 |
|
||||
| pageSize | integer | 否 | 每页条数,默认 20 |
|
||||
| keyword | string | 否 | 关键词搜索(订单号 / 产品名 / 联系人),所有 tab 均生效 |
|
||||
| titleName | string | 否 | 发票抬头名称,**仅 REQUESTED/ISSUED/PUSHED tab 生效,ALL/NONE tab 静默忽略** |
|
||||
| requestedBy | string | 否 | 申请人(定制师),**仅 REQUESTED/ISSUED/PUSHED tab 生效,ALL/NONE tab 静默忽略** |
|
||||
| departureStartDate | string | 否 | 出发日期起,格式 YYYY-MM-DD |
|
||||
| departureEndDate | string | 否 | 出发日期止,格式 YYYY-MM-DD |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无(GET 接口)
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应外层结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [...],
|
||||
"stats": [...],
|
||||
"records": [...],
|
||||
"total": 0,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.1 tabCounts(5 项固定顺序)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | string | tab 枚举值(见第 6 节) |
|
||||
| name | string | tab 中文标签 |
|
||||
| count | integer | 该 tab 对应的记录数 |
|
||||
|
||||
固定顺序:REQUESTED → ISSUED → PUSHED → NONE → ALL
|
||||
|
||||
> **ALL.count 语义已变**:现为全部 COMPLETED 订单数(含未申请);之前仅含发票记录数。
|
||||
|
||||
### 5.2 stats(4 项固定顺序)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | string | 枚举值(见第 6 节) |
|
||||
| name | string | 统计项中文标签 |
|
||||
| count | integer | 数量 |
|
||||
| amount | number | 金额(元,JSON number) |
|
||||
|
||||
固定顺序:REQUESTED → ISSUED → PUSHED → CURRENT_MONTH_ISSUED
|
||||
|
||||
### 5.3 records 列表行(InvoicePageItemRespVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | string | 发票记录 ID(雪花);**无发票行为 null** |
|
||||
| orderId | string | 订单 ID(雪花) |
|
||||
| orderNo | string | 订单号 |
|
||||
| productName | string | 产品名称 |
|
||||
| tierName | string | 档期名称 |
|
||||
| contactName | string | 联系人姓名 |
|
||||
| customizerName | string | 定制师姓名 |
|
||||
| departureDate | string | 出发日期,格式 YYYY-MM-DD |
|
||||
| adultCount | integer | 成人数 |
|
||||
| childCount | integer | 儿童数 |
|
||||
| youngChildCount | integer | 婴幼儿数 |
|
||||
| invoiceType | string | 发票类型枚举(见第 6 节);无发票行为 null |
|
||||
| invoiceTypeText | string | 发票类型中文;无发票行为 null |
|
||||
| titleType | string | 抬头类型枚举(见第 6 节);无发票行为 null |
|
||||
| titleName | string | 抬头名称;无发票行为 null |
|
||||
| taxNo | string | 税号;无发票行为 null |
|
||||
| amount | number | 发票金额(元);无发票行为 null |
|
||||
| email | string | 接收邮箱;无发票行为 null |
|
||||
| status | string | 发票状态枚举(见第 6 节);**无发票行固定为 NONE** |
|
||||
| statusText | string | 发票状态中文;**无发票行固定为 未申请** |
|
||||
| requestedBy | string | 申请人(定制师);无发票行为 null |
|
||||
| requestedAt | string | 申请时间 ISO 8601;无发票行为 null |
|
||||
| issuedAt | string | 开票时间 ISO 8601;无发票行为 null |
|
||||
| issuedBy | string | 开票人;无发票行为 null |
|
||||
| invoiceNo | string | 发票号;无发票行为 null |
|
||||
| fileUrl | string | 发票文件 URL;无发票行为 null |
|
||||
| pdfName | string | PDF 文件名;无发票行为 null |
|
||||
| pdfSize | string | PDF 文件大小(字符串,如 128KB);无发票行为 null |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 tab 枚举(查询参数 & tabCounts.code)
|
||||
|
||||
| code | name | 说明 |
|
||||
|------|------|------|
|
||||
| REQUESTED | 待开票 | 已申请、待财务开票 |
|
||||
| ISSUED | 待推送 | 已开票、待推送给客户 |
|
||||
| PUSHED | 已推送 | 已推送给客户 |
|
||||
| NONE | 未申请 | 已完成订单但尚未申请发票 |
|
||||
| ALL | 全部 | 订单视角:全部 COMPLETED 订单(含无发票行) |
|
||||
|
||||
### 6.2 stats.code 枚举
|
||||
|
||||
| code | name |
|
||||
|------|------|
|
||||
| REQUESTED | 待开票 |
|
||||
| ISSUED | 已开票(待推送) |
|
||||
| PUSHED | 已推送 |
|
||||
| CURRENT_MONTH_ISSUED | 本月已开票 |
|
||||
|
||||
### 6.3 发票状态枚举(records.status)
|
||||
|
||||
| code | statusText | 说明 |
|
||||
|------|------------|------|
|
||||
| REQUESTED | 待开票 | 已申请 |
|
||||
| ISSUED | 待推送 | 已开票 |
|
||||
| PUSHED | 已推送 | 已推送 |
|
||||
| NONE | 未申请 | tab=ALL/NONE 时无发票行专用 |
|
||||
|
||||
### 6.4 发票类型枚举(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| ELECTRONIC | 电子发票 |
|
||||
| PAPER | 纸质发票 |
|
||||
|
||||
### 6.5 抬头类型枚举(titleType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| PERSONAL | 个人 |
|
||||
| COMPANY | 企业 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 含义 | 前端处理建议 |
|
||||
|--------|------|-------------|
|
||||
| 200 | 成功 | 正常渲染 |
|
||||
| 401 | 未认证 | 跳登录 |
|
||||
| 403 | 无权限 | 提示无访问权限 |
|
||||
| 500 | 服务端异常 | 通用错误提示 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——tab=ALL,混合行(有发票 + 无发票)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 3,},
|
||||
{"code": "ISSUED", "name": "待推送", "count": 1,},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 5,},
|
||||
{"code": "NONE", "name": "未申请", "count": 12,},
|
||||
{"code": "ALL", "name": "全部", "count": 21}
|
||||
],
|
||||
"stats": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 3, "amount": 8800.00},
|
||||
{"code": "ISSUED", "name": "已开票", "count": 1, "amount": 3200.00},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 5, "amount": 15600.00},
|
||||
{"code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": 12000.00}
|
||||
],
|
||||
"records": [
|
||||
{
|
||||
"id": "1923456789012345678",
|
||||
"orderId": "1823456789012345678",
|
||||
"orderNo": "HL20260620001",
|
||||
"productName": "云南深度游7日",
|
||||
"tierName": "2026-07-01班期",
|
||||
"contactName": "张三",
|
||||
"customizerName": "李定制",
|
||||
"departureDate": "2026-07-01",
|
||||
"adultCount": 2,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"invoiceType": "ELECTRONIC",
|
||||
"invoiceTypeText": "电子发票",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "北京科技有限公司",
|
||||
"taxNo": "91110000123456789X",
|
||||
"amount": 6800.00,
|
||||
"email": "finance@example.com",
|
||||
"status": "REQUESTED",
|
||||
"statusText": "待开票",
|
||||
"requestedBy": "李定制",
|
||||
"requestedAt": "2026-06-20T10:30:00+08:00",
|
||||
"issuedAt": null,
|
||||
"issuedBy": null,
|
||||
"invoiceNo": null,
|
||||
"fileUrl": null,
|
||||
"pdfName": null,
|
||||
"pdfSize": null
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"orderId": "1823456789012345679",
|
||||
"orderNo": "HL20260619002",
|
||||
"productName": "西藏圣地朝圣8日",
|
||||
"tierName": "2026-06-25班期",
|
||||
"contactName": "王五",
|
||||
"customizerName": "赵定制",
|
||||
"departureDate": "2026-06-25",
|
||||
"adultCount": 4,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 0,
|
||||
"invoiceType": null,
|
||||
"invoiceTypeText": null,
|
||||
"titleType": null,
|
||||
"titleName": null,
|
||||
"taxNo": null,
|
||||
"amount": null,
|
||||
"email": null,
|
||||
"status": "NONE",
|
||||
"statusText": "未申请",
|
||||
"requestedBy": null,
|
||||
"requestedAt": null,
|
||||
"issuedAt": null,
|
||||
"issuedBy": null,
|
||||
"invoiceNo": null,
|
||||
"fileUrl": null,
|
||||
"pdfName": null,
|
||||
"pdfSize": null
|
||||
}
|
||||
],
|
||||
"total": 21,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——tab=ALL 传了 titleName(被静默忽略)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&titleName=北京科技&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**说明**:titleName 参数不参与过滤,接口正常返回全量 COMPLETED 订单分页。records 中仍会出现 status=NONE 的无发票行,响应结构与 8.1 相同,此处不重复。
|
||||
|
||||
### 8.3 业务失败——无已完成订单时 tab=ALL 返回空列表
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**(无 COMPLETED 订单时)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 0},
|
||||
{"code": "ISSUED", "name": "待推送", "count": 0},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 0},
|
||||
{"code": "NONE", "name": "未申请", "count": 0},
|
||||
{"code": "ALL", "name": "全部", "count": 0}
|
||||
],
|
||||
"stats": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 0, "amount": 0},
|
||||
{"code": "ISSUED", "name": "已开票", "count": 0, "amount": 0},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 0, "amount": 0},
|
||||
{"code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 0, "amount": 0}
|
||||
],
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
**适用**:
|
||||
- 订单状态为 COMPLETED(已完成)的订单才出现在 ALL/NONE tab
|
||||
- REQUESTED/ISSUED/PUSHED 三个 tab 仍以发票记录为视角,只返回有对应状态发票的订单行
|
||||
|
||||
**不适用**:
|
||||
- 进行中(未完成)订单不出现在任何 tab
|
||||
- 已取消订单不出现
|
||||
|
||||
**特殊边界**:
|
||||
- ALL tab 下:tabCounts.ALL.count = REQUESTED + ISSUED + PUSHED + NONE,四者无重叠
|
||||
- 同一订单只有一张有效发票记录,不会重复出现
|
||||
- titleName/requestedBy 在 ALL/NONE tab 传入后静默忽略,不报错、不影响结果
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| tabCounts.ALL.count | 仅 REQUESTED+ISSUED+PUSHED 发票记录数之和 | 全部 COMPLETED 订单数(含无发票订单) |
|
||||
| records(tab=ALL 时) | 仅返回有发票记录的订单行,无 status=NONE 行 | 返回全部 COMPLETED 订单,无发票行 status=NONE |
|
||||
| records[].id(tab=ALL 时) | 所有行均有值 | 无发票行为 null |
|
||||
| records[].amount(tab=ALL 时) | 所有行均有值 | 无发票行为 null |
|
||||
|
||||
### tab=ALL 计数对比(假设 2 张发票 + 12 个未申请)
|
||||
|
||||
变更前:
|
||||
```
|
||||
ALL.count = 2 (仅发票记录,ALL < NONE,矛盾)
|
||||
NONE.count = 12
|
||||
```
|
||||
|
||||
变更后:
|
||||
```
|
||||
ALL.count = 14 (= 2 + 12,ALL >= NONE,正确)
|
||||
NONE.count = 12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
**破坏兼容性**:是
|
||||
|
||||
- 前端若假设 tab=ALL 列表行的 id 一定不为 null,需修改:无发票行 id 为 null
|
||||
- 前端若用 id !== null 判断是否显示开票按钮,需改为判断 status 是否为 NONE
|
||||
- tabCounts.ALL.count 现在总是 >= 之前的值,若前端有基于此的断言需同步更新
|
||||
- stats 块不受影响,无需改动
|
||||
|
||||
**前端同步上线**:建议与本次后端部署同期上线,避免显示数据矛盾窗口期。
|
||||
|
||||
**回滚方案**:回滚后端至上一个版本 jar 即可恢复旧行为;若前端已适配新结构则需同步回滚前端。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **无发票行的 id 字段为 null**,前端不可用 id 做有无发票判断,应改用 status 字段值是否为 NONE。
|
||||
2. **amount 字段类型为 JSON number(元)**,不是字符串,渲染时直接用数值格式化。
|
||||
3. **titleName/requestedBy 在 ALL/NONE tab 下静默忽略**,搜索框可保留,后端不过滤,行为符合预期。
|
||||
4. **stats 块不受 tab 切换影响**,始终返回全局四项汇总统计,与当前 tab 无关。
|
||||
5. **tabCounts 固定 5 项、顺序不变**,前端可按 code 匹配或按索引渲染。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4337 发票管理列表 ALL tab 改订单视角](https://git.1814.love:8443/wx/HL/issues/4337)
|
||||
- **PR**:[#4338](https://git.1814.love:8443/wx/HL/pulls/4338)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,50 @@
|
||||
# 抢单池紧急度 + 配房选酒店默认不锁城市 + 站内信消息类型筛选
|
||||
|
||||
> 变更类型:✨ 新增能力 ×2 + 🔧 修复 ×1(后端,**前端需配合:抢单池标签/加急按钮、候选标签、站内信类型Tab**)
|
||||
> 端类型:管理后台(房务管家·抢单池/配房选酒店 / 站内信收件箱 / 订单详情定制师侧)
|
||||
> 日期:2026-06-24 | 工单:#4341 #4345 #4347 | PR:#4342 #4346 #4348 | 服务:hl-order-service-v3 / hl-resource-service / hl-user-service(均已部署 health UP,测试服实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 一、抢单池紧急度(#4342,前端需配合渲染)
|
||||
|
||||
### 1. 自动紧急度(按出发日临近,后端派生)
|
||||
抢单池列表项 `GET /v3/admin/order/hotel-requirements`(抢单池) 每条新增:
|
||||
- `urgencyLevel`:`CRITICAL`(特急) / `URGENT`(紧急) / `NORMAL`(正常)。规则:出发日距今 **≤3天=特急(红)、≤7天=紧急(橙)、其余正常**;已过出发日也算特急。
|
||||
- `urgencyLabel`:中文(特急/紧急/正常)。
|
||||
- `daysToDepart`:距出发剩余天数(负=已过)。
|
||||
- **前端**:按 `urgencyLevel` 渲染颜色/标签(特急红/紧急橙)。默认排序已「紧急优先」。
|
||||
|
||||
### 2. 定制师手动加急(新增端点)
|
||||
订单详情定制师侧加「加急」按钮:
|
||||
- 加急:`POST /v3/admin/order/hotel-requirement/{requirementId}/urgent`
|
||||
- 取消加急:`POST /v3/admin/order/hotel-requirement/{requirementId}/urgent/cancel`
|
||||
- 权限:该订单定制师本人 或 超管(非本单定制师返 582084)。
|
||||
- 抢单池列表项新增 `manualUrgent`(bool):手动加急的单**置顶**(优先级高于自动紧急度),前端显示「加急」标签。
|
||||
- **前端**:订单详情加「加急/取消加急」按钮(已加急显示取消);抢单池 `manualUrgent=true` 显示加急标签+置顶。
|
||||
|
||||
---
|
||||
|
||||
## 二、配房选酒店默认不锁城市(#4346,修复,前端一般无需改)
|
||||
配房页「选择酒店」候选 `GET /v3/admin/hotel-candidates`:那晚行程取不到城市(如第2/3晚行程无城市)时原**返空**、看不到酒店。现修复:
|
||||
- city 取不到时**不返空**,改按「池内酒店 ∪ 定制师点名酒店」展示(不依赖城市)。
|
||||
- city 非空时:城市酒店 + 补全外城的池内/定制师点名酒店(定制师点的外城酒店也可见)。
|
||||
- **排序**:定制师点名 > 池内 > 普通(带 `isConsultantRecommended` / `isPoolMatch` 标签,前端已有字段)。
|
||||
- **前端**:候选列表渲染 `isPoolMatch`(池内)/`isConsultantRecommended`(定制师需求)/`tags` 标签即可(字段早已返回)。
|
||||
- 实测:第2/3晚由 0 条 → 返非空候选,top 为池内酒店。
|
||||
|
||||
---
|
||||
|
||||
## 三、站内信消息类型筛选(#4348,前端需配合 Tab)
|
||||
站内信收件箱 `GET /admin/message/list` 新增入参 `messageType`:
|
||||
- `ORDER`=只看订单消息 / `NORMAL`=只看普通消息 / 留空=全部。
|
||||
- 后端按派生口径下推 SQL 过滤,分页 total 准确、ORDER+NORMAL 两类互补=全集。
|
||||
- **前端**:站内信列表加「订单消息 / 普通消息」类型 Tab,切换传 `messageType`。
|
||||
- 说明:当前内部员工站内信多为系统/自定义推送(派生普通消息),**订单类型消息的数据源(订单事件给内部员工发站内信)后续单独接入**——本次先提供筛选能力。
|
||||
|
||||
---
|
||||
|
||||
## 验证说明(如实·实测)
|
||||
- ✅ 抢单池 item 返 urgencyLevel/urgencyLabel/daysToDepart/manualUrgent;加急/取消接口 + 非本单定制师 582084。
|
||||
- ✅ 配房候选第2/3晚(city=None)由 0 条 → 返非空(池内酒店置顶);第1晚 10 条不回归。
|
||||
- ✅ 站内信 messageType=ORDER/NORMAL/空 三态筛选正确(total 准确、互补)。
|
||||
@@ -0,0 +1,188 @@
|
||||
# 退款待办列表新增团号/档位/人员数字段
|
||||
|
||||
- **接口**:`GET /v3/admin/refund/application/page`
|
||||
- **变更类型**:修改接口(出参新增 3 字段,非破坏性,原字段全部保留)
|
||||
- **日期**:2026-06-24
|
||||
- **端类型**:管理后台
|
||||
- **Issue**:https://git.1814.love:8443/wx/HL/issues/4343
|
||||
- **PR**:https://git.1814.love:8443/wx/HL/pulls/4344
|
||||
- **负责人**:腰苏图(yst)
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
退款待办列表(`/v3/admin/refund/application/page`)原本已返回订单号、产品名、客户名、定制师名等订单基础信息。本次在 `records[]` 每条记录中补充团号、规格档位、人员数描述三个字段,便于定制师在退款工作台快速识别退款所属团期及人员规模,无需再跳转订单详情页核对。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 变更 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `records[].teamNo` 新增 | ✨ 出参新增字段 | 订单所属团号,无团号时为 null |
|
||||
| `records[].tierName` 新增 | ✨ 出参新增字段 | 订单规格/套餐档位名称,无档位时为 null |
|
||||
| `records[].peopleSummary` 新增 | ✨ 出参新增字段 | 人员数描述,后端已拼接好中文字符串(如 `2成人1儿童`),前端直接展示 |
|
||||
|
||||
**无破坏性变更**:入参不变,原有出参字段(orderNo / productName / customerName / consultantName 等)不变,枚举值不变,错误码不变。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| **方法** | GET |
|
||||
| **路径** | `/v3/admin/refund/application/page` |
|
||||
| **功能** | 退款申请待办列表分页查询 |
|
||||
| **认证** | 需要管理后台 JWT(`Authorization: Bearer <token>`) |
|
||||
| **幂等** | 是(只读) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
入参无任何变化,完整入参见原有接口契约(statusGroup、pageNo、pageSize 等字段保持不变)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### 新增字段(位于 `data.records[]` 每条记录内)
|
||||
|
||||
| 字段名 | 类型 | 可为 null | 说明 |
|
||||
|---|---|---|---|
|
||||
| `teamNo` | string | ✅ 是 | 订单所属团号;订单无团号(非团期订单)时返回 null |
|
||||
| `tierName` | string | ✅ 是 | 订单规格/套餐档位名称;无档位时返回 null |
|
||||
| `peopleSummary` | string | ✅ 是 | 人员数描述,格式如 `2成人1儿童`、`1成人2儿童1幼儿`;仅含非零人数段,顺序固定为成人→儿童→幼儿→婴儿;各人数全为 0 时返回 null |
|
||||
|
||||
### 已有字段(保持不变,供参考)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderNo` | string | 订单号 |
|
||||
| `productName` | string | 产品名称 |
|
||||
| `customerName` | string | 客户姓名 |
|
||||
| `consultantName` | string | 定制师姓名 |
|
||||
| `applicationNo` | string | 退款申请单号 |
|
||||
| `status` | string | 退款申请状态(枚举值不变) |
|
||||
| ... | ... | 其余字段均保持不变 |
|
||||
|
||||
**说明**:`teamNo` / `tierName` / `peopleSummary` 三字段与 `orderNo` / `productName` 等字段同样通过订单服务批量查询填充。若订单已被删除或查询不到,这三个新字段连同 `orderNo` / `productName` / `customerName` / `consultantName` 均返回 null,退款申请自身字段正常返回,不影响列表展示。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
无新增或变更枚举值。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无新增或变更错误码。本次为纯出参扩展,不引入新的业务校验路径。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功(团期订单,含完整三字段)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/refund/application/page?pageNo=1&pageSize=20
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应(records[] 节选一条)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"pageNo": 1,
|
||||
"pageSize": 20,
|
||||
"total": 5,
|
||||
"records": [
|
||||
{
|
||||
"applicationNo": "RF2026062400001",
|
||||
"orderNo": "ORD20260624000123",
|
||||
"productName": "云南深度7日游",
|
||||
"teamNo": "GB260710",
|
||||
"tierName": "标准大床房",
|
||||
"peopleSummary": "2成人1儿童",
|
||||
"customerName": "张三",
|
||||
"consultantName": "李定制",
|
||||
"status": "PENDING_REVIEW",
|
||||
"refundAmount": "1200.00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(非团期订单 / 人数全 0 / 无档位)
|
||||
|
||||
**响应(records[] 节选一条)**
|
||||
```json
|
||||
{
|
||||
"applicationNo": "RF2026062400002",
|
||||
"orderNo": "ORD20260624000456",
|
||||
"productName": "西藏定制5日",
|
||||
"teamNo": null,
|
||||
"tierName": null,
|
||||
"peopleSummary": null,
|
||||
"customerName": "王五",
|
||||
"consultantName": "赵定制",
|
||||
"status": "PENDING_REVIEW",
|
||||
"refundAmount": "3500.00"
|
||||
}
|
||||
```
|
||||
|
||||
> `teamNo`/`tierName`/`peopleSummary` 均为 null 属正常,前端渲染时做空值保护(不展示 / 展示占位符均可)。
|
||||
|
||||
### 8.3 业务失败(statusGroup 参数非法)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/refund/application/page?statusGroup=INVALID_GROUP&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"data": null,
|
||||
"msg": "参数校验失败"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用**:
|
||||
- 退款待办工作台所有 Tab(statusGroup 筛选不影响新字段的填充逻辑)
|
||||
- 团期订单:`teamNo` 有值,`tierName` 视产品是否配置档位
|
||||
- 非团期(CORE/ROUTE/CUSTOM)订单:`teamNo` 恒为 null
|
||||
|
||||
**不适用 / 特殊边界**:
|
||||
- 订单已删除或异常查询不到时,三字段同其他订单摘要字段一并返回 null,退款申请数据不受影响
|
||||
- `peopleSummary` 的人数统计口径为订单创建时记录的 adult/child/youngChild/baby 字段;若订单出行人后续有改动,summary 展示的是订单主表快照值,非实时出行人计数
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **前端渲染空值保护**:三个新字段均可能为 null,渲染时务必做 null 判断,避免显示 `null` 字符串或崩溃。
|
||||
2. **`peopleSummary` 直接展示**:后端已按「非零段顺序拼接 + 中文单位」格式组装完毕,前端不需要自己拼接,直接 `v-if="item.peopleSummary"` 渲染即可。
|
||||
3. **非破坏性变更**:无需改动已有字段的读取逻辑,只需在列表 UI 中选择性展示三个新字段即可。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- **Issue**:https://git.1814.love:8443/wx/HL/issues/4343
|
||||
- **PR**:https://git.1814.love:8443/wx/HL/pulls/4344
|
||||
- **后端负责人**:腰苏图(yst)
|
||||
@@ -0,0 +1,48 @@
|
||||
# 配房需求「段×候选」候选酒店补真实房型 roomTypeId/roomTypeName(itinerary 接口,已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 修改接口(新增字段,已部署测试服并 API + 前端 UI 双实证,可对接)
|
||||
> 端类型:管理后台(定制师侧·订单详情「行程安排」→「修改需求 / 调整订单」弹窗「酒店安排」)
|
||||
> 日期:2026-06-24 | 工单:#4351 | PR:#4352 | 服务:hl-order-service-v3
|
||||
> 关联:补 #4313(#4204 P2)的遗漏——#4313 只给 itinerary 平铺 `hotels[]`(RequirementHotelVO)加了房型,**遗漏了「段×候选」结构 `segments[].candidates[]`(RequirementCandidateVO)**,而调整弹窗实际用后者回显,故房型仍不回显。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
- **现象**:调整订单弹窗「酒店安排」每个候选酒店的**房型下拉恒显示「该酒店暂无可选房型」**,提交时已选的真实房型(如「精品标间」「普通标间」)修改时无法回显。
|
||||
- **根因**:itinerary 接口 `RequirementCandidateVO`(段×候选的候选项)缺 `roomTypeId/roomTypeName`,#4313 只补了平铺 `hotels[]`、漏了这里。后端 `order_hotel_requirement.days` JSON 已存房型,纯属 VO + Converter 透传遗漏。
|
||||
- **已修复并实证**:候选项补字段 + Converter 透传,已部署测试服,API + 前端 UI 双实证房型正确回显,纯 additive 向后兼容。
|
||||
|
||||
## 1. 新增字段(itinerary 接口·段×候选的候选维度)
|
||||
`GET /v3/admin/order/{id}/itinerary` → `data.hotelGroup.requirement.days[].segments[].candidates[]`(`RequirementCandidateVO`)各**新增**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `roomTypeId` | String(雪花,可空) | 该候选酒店的**真实房型 ID** |
|
||||
| `roomTypeName` | String(可空) | 该候选酒店的**真实房型名称** |
|
||||
|
||||
为 null(旧单 / 无房型候选 / 定制师未录真实房型)时,前端回退展示段级 `roomCategory`(与 #4313 平铺 hotels[] 口径一致)。
|
||||
|
||||
## 2. 真实响应示例(测试服 order 2069593503129636865,本次实测)
|
||||
```json
|
||||
"segments": [{
|
||||
"roomCount": 1, "budget": "320.00", "remark": "测试信仰 要窗户",
|
||||
"candidates": [
|
||||
{"hotelId":"3001000000000000002","hotelName":"海拉尔海棠酒店","roomTypeId":"3002000000000000002","roomTypeName":"精品标间","remark":"测试信仰 要窗户"},
|
||||
{"hotelId":"2023714929877450753","hotelName":"呼伦贝尔香格里拉大酒店","roomTypeId":"2023727403196502017","roomTypeName":"普通标间","remark":"要窗户"}
|
||||
]
|
||||
}]
|
||||
```
|
||||
(未选房型的候选 `roomTypeId/roomTypeName` 为 null / 空串,属正常。)
|
||||
|
||||
## 3. 前端动作
|
||||
- 调整弹窗「酒店安排」候选酒店房型下拉,回显值改从 `segments[].candidates[].roomTypeId/roomTypeName` 取(原先该结构无此字段,致「该酒店暂无可选房型」)。
|
||||
|
||||
## 4. 另需前端处理(同弹窗其余回显缺失·非本次后端范围)
|
||||
排查「修改房需求回显信息不全」时实测发现,同弹窗以下两项后端 itinerary **早已返回**(在 `data.hotelGroup.requirement` 顶层),属**前端未渲染**,后端无需改:
|
||||
|
||||
| 缺失项 | 后端返回路径 | 测试服实测值 | 前端待办 |
|
||||
|---|---|---|---|
|
||||
| 特殊诉求标签 | `requirement.specialTags`(String 数组) | `["高楼层","安静楼层","无烟房","景观房","可加床","含双早","含早餐","可拆分双床","相邻房间","远离电梯"]` | 调整弹窗补「特殊诉求标签」回显 + 编辑区 |
|
||||
| 整单备注 | `requirement.remark`(String) | `"客人怕吵"` | 调整弹窗补「整单备注」回显 + 编辑区 |
|
||||
|
||||
⚠️ **数据丢失风险**:当前这两项不回显,定制师在弹窗改房需求并「提交调整」时,若提交 payload 不带这两个字段,可能覆盖丢失已填的诉求标签 + 整单备注,建议优先补回显。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 订单详情配房需求 brief 补 manualUrgent(定制师侧加急状态回显,补 #4342,已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✅ 修改接口(新增字段,已部署测试服并 API round-trip 实证,可对接)
|
||||
> 端类型:管理后台(定制师侧·订单详情「加急/取消加急」按钮)
|
||||
> 日期:2026-06-24 | 工单:#4354 | PR:#4355 | 服务:hl-order-service-v3
|
||||
> 关联:补 #4342——#4342 只给抢单池列表项(HouseGrabPageItemRespVO)加了 manualUrgent,订单详情 brief 漏透传,定制师侧加急按钮在详情读不到当前加急态。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
- **现象**:#4342 定制师侧「加急 / 取消加急」按钮要切换,需读当前加急状态,但订单详情接口的配房需求 brief 没返回 `manualUrgent`。前端只能防御性恒显「加急」,已加急单进详情的「取消加急」态失真。
|
||||
- **修复**:itinerary 接口的配房需求 brief 补 `manualUrgent`(Boolean),与抢单池列表项口径一致。entity 早有此字段(Integer 0/1),纯透传补齐。
|
||||
|
||||
## 1. 新增字段
|
||||
`GET /v3/admin/order/{id}/itinerary` → `data.hotelGroup.requirement`(`HotelRequirementBriefVO`)新增:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `manualUrgent` | Boolean | 定制师是否手动加急。`true`=已加急(按钮显「取消加急」),`false`=未加急(显「加急」)。**不返回 null**(后端 Integer 0/1/null → Boolean)。 |
|
||||
|
||||
## 2. 测试服 round-trip 实证(order 2069593503129636865)
|
||||
| 步骤 | 操作 | brief.manualUrgent |
|
||||
|---|---|---|
|
||||
| ① 初始 | — | `false` |
|
||||
| ② 加急 | `POST /v3/admin/order/hotel-requirement/{reqId}/urgent` | → `true` |
|
||||
| ③ 取消 | `POST /v3/admin/order/hotel-requirement/{reqId}/urgent/cancel` | → `false` |
|
||||
|
||||
加急 / 取消端点见 #4342 changelog(权限:该订单定制师本人 或 超管)。
|
||||
|
||||
## 3. 前端动作
|
||||
- 订单详情「加急 / 取消加急」按钮的当前态,改读 `requirement.manualUrgent`(此前该接口无此字段)。你已做的防御处理(`req.manualUrgent` 有则显「取消加急」、无则显「加急」)可保留——补字段后「取消加急」态即保真,不再依赖字段缺省。
|
||||
@@ -0,0 +1,254 @@
|
||||
# 退款详情 orderInfo 补订单字段(原恒为 null,本次接通并扩 3 字段)
|
||||
|
||||
- **接口**:`GET /v3/admin/refund/application/{id}`
|
||||
- **变更类型**:修改接口(出参 orderInfo 块由恒为 null 修复为正常填充,并新增 3 字段;非破坏性,其余字段不变)
|
||||
- **日期**:2026-06-24
|
||||
- **端类型**:管理后台
|
||||
- **Issue**:https://git.1814.love:8443/wx/HL/issues/4356
|
||||
- **PR**:https://git.1814.love:8443/wx/HL/pulls/4359
|
||||
- **负责人**:腰苏图(yst)
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
退款申请详情接口(`GET /v3/admin/refund/application/{id}`)的出参包含一个 `orderInfo` 嵌套对象,用于展示退款所属订单的概要快照(订单号、产品名、出发日期、联系人等)。
|
||||
|
||||
此前由于后端未正确接通 OrderService,`orderInfo` 字段**恒为 null**,导致详情页无法展示任何订单信息。本次修复接通 OrderService,`orderInfo` 现可正常填充;同时与退款列表接口(`/application/page`,PR #4344)保持口径一致,补充了团号、规格档位、人员数描述三个字段。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 变更 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderInfo` 由恒为 null 修复为正常填充 | 🔧 行为修复 | 后端已接通 OrderService,原有 orderInfo 字段(orderNo / productName / departureDate / contactName / contactPhone)现可正常返回 |
|
||||
| `orderInfo.teamNo` 新增 | ✨ 出参新增字段 | 订单所属团号,无团号时为 null |
|
||||
| `orderInfo.tierName` 新增 | ✨ 出参新增字段 | 订单规格/套餐档位名称,无档位时为 null |
|
||||
| `orderInfo.peopleSummary` 新增 | ✨ 出参新增字段 | 人员数描述,后端已拼接好中文字符串(如 `2成人2儿童`),全为 0 时 null |
|
||||
|
||||
**无破坏性变更**:入参不变,顶层出参其余字段不变,枚举值不变,错误码不变。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| **方法** | GET |
|
||||
| **路径** | `/v3/admin/refund/application/{id}` |
|
||||
| **功能** | 退款申请详情查询 |
|
||||
| **认证** | 需要管理后台 JWT(`Authorization: Bearer <token>`) |
|
||||
| **幂等** | 是(只读) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `id` | long | ✅ | 退款申请 ID |
|
||||
|
||||
无请求体,无 Query 参数变化。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### orderInfo 对象(修复后完整字段列表)
|
||||
|
||||
位于 `data.orderInfo`,订单查不到时整个对象返回 null。
|
||||
|
||||
| 字段名 | 类型 | 可为 null | 说明 | 本次变化 |
|
||||
|---|---|---|---|---|
|
||||
| `orderNo` | string | ✅ | 订单号 | 原有字段,修复前恒 null,现正常返回 |
|
||||
| `productName` | string | ✅ | 产品名称 | 原有字段,修复前恒 null,现正常返回 |
|
||||
| `departureDate` | string (yyyy-MM-dd) | ✅ | 出发日期 | 原有字段,修复前恒 null,现正常返回 |
|
||||
| `contactName` | string | ✅ | 联系人姓名(管理端明文) | 原有字段,修复前恒 null,现正常返回 |
|
||||
| `contactPhone` | string | ✅ | 联系电话(管理端明文) | 原有字段,修复前恒 null,现正常返回 |
|
||||
| `teamNo` | string | ✅ | 订单所属团号;非团期订单为 null | **本次新增** |
|
||||
| `tierName` | string | ✅ | 规格/套餐档位名称;无档位为 null | **本次新增** |
|
||||
| `peopleSummary` | string | ✅ | 人员数描述,如 `2成人2儿童`;各人数全为 0 时为 null | **本次新增** |
|
||||
|
||||
> 注意:详情顶层出参(`data` 根节点)另有一个独立的 `departureDate` 字段,为退款申请自身的快照值,与 `data.orderInfo.departureDate`(从订单实时查询)含义不同,本次未改。
|
||||
|
||||
### 顶层出参其余字段(保持不变)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | long | 退款申请 ID |
|
||||
| `applicationNo` | string | 退款申请单号 |
|
||||
| `orderId` | long | 关联订单 ID |
|
||||
| `status` | string | 退款申请状态枚举 |
|
||||
| `refundAmount` | number | 退款金额(分) |
|
||||
| `reason` | string | 退款原因 |
|
||||
| `departureDate` | string (yyyy-MM-dd) | 退款申请快照出发日期(非 orderInfo 内的字段) |
|
||||
| `createTime` | string (datetime) | 申请创建时间 |
|
||||
| `travelerList` | array | 出行人列表 |
|
||||
| `timeline` | array | 审批时间线 |
|
||||
| ... | ... | 其余字段均保持不变 |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
无新增或变更枚举值。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无新增错误码。以下为原有通用错误码:
|
||||
|
||||
| 错误码 | 说明 | 前端处理建议 |
|
||||
|---|---|---|
|
||||
| 200 | 成功 | — |
|
||||
| 404 / 业务错误码 | 退款申请不存在 | 提示「退款申请不存在」并返回列表页 |
|
||||
|
||||
> `orderInfo` 为 null 不触发接口报错,仅表示关联订单查不到,前端需对 `orderInfo` 做空判断后再读取内部字段。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功(orderInfo 正常填充)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/refund/application/1234567890123456789
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应(data 节选)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": 1234567890123456789,
|
||||
"applicationNo": "RF2026062400001",
|
||||
"orderId": 9876543210987654321,
|
||||
"status": "PENDING_REVIEW",
|
||||
"refundAmount": 99800,
|
||||
"reason": "行程取消",
|
||||
"departureDate": "2026-08-10",
|
||||
"orderInfo": {
|
||||
"orderNo": "ORD20260601001",
|
||||
"productName": "呼籁·云南深度7日游",
|
||||
"departureDate": "2026-08-10",
|
||||
"contactName": "张三",
|
||||
"contactPhone": "13800138000",
|
||||
"teamNo": "GB20260801001",
|
||||
"tierName": "豪华双人间",
|
||||
"peopleSummary": "2成人2儿童"
|
||||
},
|
||||
"travelerList": [],
|
||||
"timeline": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况(非团期订单,teamNo 为 null;无档位,tierName 为 null;全为成人,peopleSummary 有值)
|
||||
|
||||
**响应(orderInfo 节选)**
|
||||
```json
|
||||
{
|
||||
"orderInfo": {
|
||||
"orderNo": "ORD20260602002",
|
||||
"productName": "定制私家团",
|
||||
"departureDate": "2026-09-01",
|
||||
"contactName": "李四",
|
||||
"contactPhone": "13900139000",
|
||||
"teamNo": null,
|
||||
"tierName": null,
|
||||
"peopleSummary": "3成人"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(订单已删除,orderInfo 整体为 null)
|
||||
|
||||
> 此情况接口不报错,HTTP 200 正常返回,仅 orderInfo 为 null。
|
||||
|
||||
**响应(data 节选)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": 1234567890000000001,
|
||||
"applicationNo": "RF2026062400002",
|
||||
"orderId": 9999999999999999999,
|
||||
"status": "PENDING_REVIEW",
|
||||
"refundAmount": 50000,
|
||||
"orderInfo": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用**
|
||||
- 退款申请 ID 有效,关联订单存在时,`orderInfo` 正常填充所有字段。
|
||||
- 订单为团期订单时,`teamNo` 填充团号;否则为 null。
|
||||
- 订单有规格档位时,`tierName` 填充档位名称;否则为 null。
|
||||
- `peopleSummary` 仅包含非零人数段(成人→儿童→幼儿→婴儿顺序),各人数全为 0 时返回 null。
|
||||
|
||||
**不适用 / 特殊边界**
|
||||
- 关联订单已删除或 orderId 为空时,`orderInfo` 整体返回 null,接口不报错,前端需做空判断。
|
||||
- `data.departureDate`(顶层)与 `data.orderInfo.departureDate` 含义不同:前者是退款申请创建时的快照,后者来自订单实时查询,两者可能不一致,前端不可混用。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `orderInfo` | 恒为 `null`(后端未接通 OrderService) | 正常对象,订单查不到时才为 null |
|
||||
| `orderInfo.orderNo` | 不可用(整体 null) | 订单号字符串 |
|
||||
| `orderInfo.productName` | 不可用(整体 null) | 产品名称字符串 |
|
||||
| `orderInfo.departureDate` | 不可用(整体 null) | `yyyy-MM-dd` 格式日期字符串 |
|
||||
| `orderInfo.contactName` | 不可用(整体 null) | 联系人姓名(管理端明文) |
|
||||
| `orderInfo.contactPhone` | 不可用(整体 null) | 联系电话(管理端明文) |
|
||||
| `orderInfo.teamNo` | 字段不存在 | **新增**,无团号时 null |
|
||||
| `orderInfo.tierName` | 字段不存在 | **新增**,无档位时 null |
|
||||
| `orderInfo.peopleSummary` | 字段不存在 | **新增**,全 0 时 null |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 退款详情订单信息可用性 | 完全不可用(恒 null) | 正常可用 |
|
||||
| 与退款列表口径一致性 | 不一致(列表有订单信息,详情没有) | 一致(新增三字段与列表 PR #4344 对齐) |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
**兼容性**:非破坏性变更。`orderInfo` 从 null 变为有值,不影响前端已有的空判断逻辑;新增三字段前端不读即无感知。
|
||||
|
||||
**前端同步上线**:无需同步上线。但建议前端在退款详情页接入 `orderInfo` 内的订单信息展示(原先恒 null 导致该区块无法显示),本次修复后数据已可用。
|
||||
|
||||
**回滚方案**:若需回滚,后端回退 PR #4359 即可。`orderInfo` 恢复为 null,前端已有空判断逻辑不受影响。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **双 departureDate 区分**:出参顶层 `data.departureDate` 是退款申请快照日期;`data.orderInfo.departureDate` 是订单侧出发日期,二者含义不同,前端读取时注意字段路径。
|
||||
2. **orderInfo 空判断**:订单查不到时 `orderInfo` 整体为 null,前端需在读取 `orderInfo` 内任意字段前先做非空判断,否则会报 NPE。
|
||||
3. **peopleSummary 格式**:字符串由后端拼接,格式固定为中文(如 `2成人1儿童`),前端直接展示无需再做格式化。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- **Issue**:https://git.1814.love:8443/wx/HL/issues/4356
|
||||
- **PR**:https://git.1814.love:8443/wx/HL/pulls/4359
|
||||
- **退款列表同期关联 PR**:https://git.1814.love:8443/wx/HL/pulls/4344(退款列表新增相同三字段,口径对齐)
|
||||
- **后端负责人**:腰苏图(yst)
|
||||
@@ -0,0 +1,39 @@
|
||||
# 房务抢单池 SSE 实时广播(新单入池/被抢/释放/加急 → 在线房务实时刷列表)
|
||||
|
||||
> 变更类型:✨ 实时能力(后端,**前端需接 SSE 实时刷新抢单池**)
|
||||
> 端类型:管理后台(房务管家·抢单池)
|
||||
> 日期:2026-06-24 | 工单:#4357 | PR:#4358 | 服务:hl-order-service-v3 / hl-user-service(均已部署 health UP)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
抢单池此前列表**不实时刷新**:A 房务抢走某单后,B 房务列表还显示「抢单」按钮,点了才返 `808001 需求已被其他房务抢到`(后端 CAS 早已防并发,但前端不知情)。现加 SSE 实时广播,前端实时刷列表(已抢单自动移除)。
|
||||
|
||||
## 后端能力(前端需接)
|
||||
抢单池任一变更 → 实时广播给在线房务(经现有 SSE 长连接 `GET /ws/admin-msg/stream`)。
|
||||
- **新增 SSE 事件**:`event: grab-pool-changed`,`data` JSON 关键字段:
|
||||
- `grabPoolEvent`:`ADDED`(新单入池) / `CLAIMED`(被抢,从池移除) / `RELEASED`(释放回池) / `URGENT`(加急,重排序)
|
||||
- `orderId` / `requirementId`:变更的订单/配房需求 ID
|
||||
- `type`:`GRAB_POOL`
|
||||
- **触发时机**:
|
||||
- `ADDED`:定制师提交配房需求、核心订单进抢单池(团期审核单不进池不广播)。
|
||||
- `CLAIMED`:房务抢单成功(该单应从所有人的抢单池移除)。
|
||||
- `RELEASED`:房务释放回池(该单重新出现在抢单池)。
|
||||
- `URGENT`:定制师加急/取消加急(抢单池重排序,加急单置顶)。
|
||||
|
||||
## 前端配合(重点)
|
||||
抢单池页面**监听 SSE `grab-pool-changed` 事件**,按 `grabPoolEvent` 实时更新列表:
|
||||
- `CLAIMED` → 从当前抢单池列表移除该单(`orderId`/`requirementId` 匹配),解决「已被抢还显示抢单按钮」。
|
||||
- `ADDED` → 刷新/插入新入池单。
|
||||
- `RELEASED` → 刷新/插回该单。
|
||||
- `URGENT` → 重新拉列表(加急单置顶,配合 #4342 紧急度排序)。
|
||||
- 最简实现:收到任一 `grab-pool-changed` 就**重新拉一次抢单池列表**;进阶可按事件做局部增删。
|
||||
- 其他页面(非抢单池)忽略 `grab-pool-changed` 事件(广播给所有在线连接,按 event 名过滤)。
|
||||
|
||||
## 说明
|
||||
- 后端 CAS 防并发(被抢单他人再抢返 `808001`)一直生效,本次 SSE 是**体验层**实时刷新,不改并发正确性。
|
||||
- 广播给所有在线连接(抢单池变更频率低、载荷轻);将来如需精确到房务角色再优化。
|
||||
|
||||
## 验证说明
|
||||
- ✅ 两服务部署 health UP;order-v3 全量 4443/0/0(含 IT)、user SSE 域单测全绿。
|
||||
- SSE 端到端(claim/release 触发 grab-pool-changed)建议前端接入后联调确认。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 配房选酒店默认不按城市(列全部在售·跨城,池内/定制师优先;搜索框可筛;已上线测试服·可对接)
|
||||
|
||||
> 变更类型:✨ 行为变更(后端 order + resource 一并改,已部署测试服并 API 实测)
|
||||
> 端类型:管理后台(配房·「选择酒店」弹窗)
|
||||
> 日期:2026-06-24 | 工单:#4363 | PR:#4364 | 服务:hl-order-service-v3 + hl-resource-service
|
||||
> 关联:承接 #4346(本单做全)。**更正本目录早前「配房选择酒店候选列表后端返全集」那篇**——彼时后端实为按行程城市过滤(非纯前端 bug,我先前定性有误,致歉),现已改为默认不按城市。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
- **行为变更**:配房「选择酒店」`GET /v3/admin/hotel-candidates` **默认不再按行程城市过滤**,改为**列全部在售酒店**(带标签的池内 / 定制师推荐由后端排最前)。
|
||||
- 用户在搜索框输入城市 / 关键词时才按城市 / 关键词筛(`city` / `keyword` 入参,行为不变)。
|
||||
- 接口**无 total / 分页字段**,`candidates` 是完整数组(默认上限 30,排序后截断)。**前端全量顺序渲染 candidates,勿按标签过滤**。
|
||||
|
||||
## 1. 入参 / 出参(契约不变,仅默认行为变)
|
||||
`GET /v3/admin/hotel-candidates`:
|
||||
- `orderId`(必传)/ `dayNumber`(推算 stayDate,**不再用于推算城市**)/ `city`(选填,搜索框指定才按城市筛)/ `keyword`(选填,跨城 / 省关键词搜)/ `limit`(默认 **30**,上限 50)。
|
||||
- 出参 `data.candidates[]` 字段同前(hotelName / protoPrice / isPoolMatch / isConsultantRecommended / tags / ...);排序:定制师点名 > 池内 > 普通。
|
||||
|
||||
## 2. 测试服实测(order 2069593503129636865 第 1 晚)
|
||||
| 调用 | data.city | 候选城市 | 条数 |
|
||||
|---|---|---|---|
|
||||
| 默认(不传 city) | null | **兴安盟 + 呼伦贝尔市(跨城)** | 10(全部在售,池内海拉尔海棠排第 1) |
|
||||
| 传 `city=海拉尔` | 海拉尔 | 呼伦贝尔市 | 1(按城市筛) |
|
||||
|
||||
改前同一调用只返呼伦贝尔市 10 条(锁行程城市);改后跨城返全部在售(含兴安盟阿尔山成悦酒店)。
|
||||
|
||||
## 3. 前端动作
|
||||
- **⚠️ 默认别传 `city`(实测抓包发现的关键 bug)**:当前配房「选择酒店」弹窗自动把行程城市(如「海拉尔区、满洲里市」)塞进 `city` 入参,后端就按城市前缀匹配——而「海拉尔区、满洲里市」是多地名拼接串,库内酒店 city 是「呼伦贝尔市」,前缀匹配不到 → 城市查 0 条,只剩定制师指定(preferredHotelId)的 1 条。**默认请勿传 city**(去掉自动带的行程城市标签),只在用户搜索框输入城市时才传 city,这样才返全部跨城(实测不传 city 返 10 条跨城)。
|
||||
- **全量顺序渲染 `candidates`**(后端已排好序,带标签的在最前),弹窗「共 N 条」**= `candidates.length`**(实测前端「共 8 条」与实际 candidates 条数对不上,请改用数组长度),勿按标签过滤。
|
||||
- 搜索框:输入城市 → 传 `city`;跨城 / 关键词 → 传 `keyword`。
|
||||
@@ -0,0 +1,55 @@
|
||||
# 配房「选酒店一步定房型+房间数」(后端提交链路 + 回显已就绪,前端做弹窗交互;已上线测试服)
|
||||
|
||||
> 变更类型:✅ 修改接口(回显补 roomCount,additive)+ 📖 提交契约说明(提交链路本就支持,前端按此对接)
|
||||
> 端类型:管理后台(房务管家·配房「选择酒店」弹窗)
|
||||
> 日期:2026-06-24 | 工单:#4367 | PR:#4368 | 服务:hl-order-service-v3
|
||||
> 关联:配合产品需求「配房选酒店时选房型+填房间数,一天多酒店多房型,房间数与房型绑定」。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
- **后端提交链路早就支持**多酒店多房型 + 房间数绑房型,前端按下方契约对接即可;本次后端只补了**回显** `roomCount`(原漏接)。
|
||||
- 「房间数与房型绑定」= 提交时 `roomTypeId` 和 `roomCount` 在**同一个 item** 里;一天多酒店多房型 = 一天提交多个 item。
|
||||
|
||||
## 1. 提交配房方案(房控选酒店实配)
|
||||
`POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`
|
||||
|
||||
请求体 `AssignmentSubmitReqVO`:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{"dayNumber":1, "hotelId":3001000000000000002, "roomTypeId":3002000000000000002, "roomCategory":"STANDARD", "roomCount":2, "sellPrice":320.00, "remark":"靠湖"},
|
||||
{"dayNumber":1, "hotelId":2023714929877450753, "roomTypeId":2023727403196502017, "roomCategory":"STANDARD", "roomCount":1, "sellPrice":320.00, "remark":""}
|
||||
]
|
||||
}
|
||||
```
|
||||
`items[]`(`AssignmentItemReqVO`)字段:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `dayNumber` | Integer | ✓ | 第几晚(1-based)。**一天多酒店多房型 = 多个 item 用同一 dayNumber** |
|
||||
| `hotelId` | Long | ✓ | 酒店 ID(取自 hotel-candidates 候选) |
|
||||
| `roomTypeId` | Long | ✓ | 房型 ID(取自候选的 `roomTypes[].roomTypeId`) |
|
||||
| `roomCategory` | String | ✓ | 房型字典 code(取 `roomTypes[].roomCategory`,如 STANDARD/TWIN/KING) |
|
||||
| `roomCount` | Integer | ✓ | **间数(≥1)。与本 item 的 roomTypeId 同行 = 房间数绑房型** |
|
||||
| `sellPrice` | BigDecimal | ✓ | 售价(元/间·晚) |
|
||||
| `remark` | String | — | 备注 |
|
||||
|
||||
→ 后端每个 item 落一行 `house_hotel_assignment`(含 room_type_id + room_count)。
|
||||
|
||||
## 2. 回显(本次后端补 roomCount)
|
||||
`GET /v3/admin/order/{id}/itinerary` → `data.hotelGroup.assignments[]`(`HotelAssignmentVO`)**新增**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `roomCount` | Integer | 本晚本酒店本房型的间数(房间数与房型同行绑定回显;原漏接,本次补) |
|
||||
|
||||
(assignments 其余字段 dayNumber/stayDate/hotelName/roomType/plannedCost/remark 不变。)
|
||||
|
||||
## 3. 前端动作(弹窗交互,后端无需再改)
|
||||
- 「选择酒店」弹窗每个候选酒店行,操作区加:
|
||||
1. **房型下拉**:用 `candidate.roomTypes` 生成(展示 name + 协议价 + 可用房;`inventoryStatus≠AVAILABLE` 置灰)。
|
||||
2. **房间数输入**:整数,默认 1。
|
||||
- 支持**一天加多行**(同一晚选多个酒店 / 同酒店多房型),每行 = 一个酒店+房型+房间数。
|
||||
- 提交时构造 `items[]`(每行一个 item,同晚多行用同 dayNumber),调上面的 assignments 接口。
|
||||
- 详情回显读 `assignments[].roomCount` 展示已配间数。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 配房选酒店候选数量修复(CORE 产品不再被截到 10 家)
|
||||
|
||||
> 变更类型:🔧 修复(后端,**前端一般无需改,仅候选数量变多**)
|
||||
> 端类型:管理后台(房务管家·配房选酒店)
|
||||
> 日期:2026-06-24 | 工单:无(每日代码审查批量修复)| PR:#4369 | 服务:hl-order-service-v3(已部署 health UP,网关实测)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
每日代码审查发现:配房「选择酒店」候选接口 `GET /v3/admin/hotel-candidates` 此前对 **CORE(核心订单)产品**内部被截断到最多 **10 家**,CUSTOM/团期最多 20 家——使 #4346/#4364「默认不按城市、列全部在售、取前 N」对 CORE 实际只返 10 家,`limit` 参数(默认 30、上限 50)不生效。
|
||||
|
||||
## 修复(已上线)
|
||||
候选列表截断改为**单一真源**:以请求 `limit`(默认 30、上限 50)为准,不再被内部固定上限(10/20)提前截断。
|
||||
- 排序逻辑不变:定制师点名 > 池内 > 普通(`isConsultantRecommended` / `isPoolMatch` 标签照常返回)。
|
||||
- 现在 CORE 产品也能返回最多 `limit`(默认 30)家候选,与 CUSTOM/团期口径统一。
|
||||
|
||||
## 前端配合
|
||||
- **一般无需改**:字段不变,仅候选条数可能变多(CORE 之前最多 10、现在最多 30)。
|
||||
- 若前端列表有「只展示前 N」的本地硬编码,可放开到后端 `limit` 口径(默认 30)。
|
||||
|
||||
## 说明(本 PR 其余为内部加固,无前端影响)
|
||||
同 PR #4369 还修了一批内部问题(不影响前端契约):车队派单占位并发守卫(防孤儿占用)、配房段预算取最高协议价口径、§13.8 车务回写归属校验、错误码/注释规范等。
|
||||
|
||||
## 验证
|
||||
- order-v3 全量 4466/0/0、fleet 全量 1304/0/0(含架构门禁)全绿;两服务部署双实例 health UP,网关实测服务可达。
|
||||
@@ -0,0 +1,35 @@
|
||||
# 配房「选择酒店」弹窗需补「选房型 + 填房间数」(后端 roomTypes 已就绪,前端加 UI;后端无改动)
|
||||
|
||||
> 变更类型:📖 前端待办(后端数据 / 契约已齐备,本篇为对接说明)
|
||||
> 端类型:管理后台(配房·「选择酒店」弹窗)
|
||||
> 日期:2026-06-24 | 服务:hl-order-service-v3 | 接口:`GET /v3/admin/hotel-candidates`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(前端功能缺失,后端无需改)
|
||||
- **现象**:配房「选择酒店」弹窗当前只能勾选酒店,不能选房型、不能填房间数。
|
||||
- **定位**:**后端已备好数据**——`hotel-candidates` 每个候选酒店已返回完整 `roomTypes`(房型选项数组);提交配房需求 VO 也支持 `roomTypeId` + `roomCount`。**缺的是前端弹窗的房型下拉 + 房间数输入控件**。
|
||||
|
||||
## 1. 候选房型选项(已返回,前端据此生成房型下拉)
|
||||
`GET /v3/admin/hotel-candidates` → `data.candidates[].roomTypes[]`(`RoomTypeOption`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `roomTypeId` | String(雪花) | 房型 ID(提交配房需求时回传) |
|
||||
| `name` | String | 房型名称(如「精品标间」「豪华大床房」) |
|
||||
| `roomCategory` | String | 房型分类(字典 room_category:TWIN/KING/...) |
|
||||
| `bedType` | String | 床型 |
|
||||
| `maxOccupancy` | Integer | 最大入住人数 |
|
||||
| `available` / `stock` | Integer | 今日可用房数 / 总库存 |
|
||||
| `protocolPrice` / `basePrice` | String | 协议价 / 标价 |
|
||||
| `inventoryStatus` | String | AVAILABLE / FULL / CLOSED |
|
||||
|
||||
## 2. 前端动作
|
||||
- 弹窗每个候选酒店行,**操作区加**:
|
||||
1. **房型下拉**:用该酒店的 `candidate.roomTypes` 生成(展示 `name`,可附协议价 / 可用房;`inventoryStatus≠AVAILABLE` 的房型置灰或标记)。
|
||||
2. **房间数输入框**:整数,默认 1。
|
||||
- 选定酒店 + 房型 + 房间数后回填配房需求候选,**提交时带回**:候选 `roomTypeId`(选中房型)+ 段 `roomCount`(房间数)。
|
||||
- 提交契约见配房需求提交 VO `HotelRequirementReqVO`:`days[].segments[].roomCount`(房间数)+ `segments[].candidates[].roomTypeId` / `roomTypeName`(房型)。
|
||||
|
||||
## 3. 说明
|
||||
后端在工单 #4204 已把房型下沉到候选级、`hotel-candidates` 暴露 `roomTypes`,本期「默认不按城市」(#4363)也已让候选跨城列全。房型选择与房间数填写纯属前端弹窗交互补全,后端无改动。
|
||||
@@ -0,0 +1,422 @@
|
||||
# 发票管理列表 - ALL 改订单视角 + 列表行新增封面图(管理后台)
|
||||
|
||||
- **接口**:GET /v3/admin/order/invoice/page
|
||||
- **变更类型**:修改接口 破坏性变更(tab=ALL 列表语义 + tabCounts.ALL 计数口径均变)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4337](https://git.1814.love:8443/wx/HL/issues/4337)
|
||||
- **PR**:[#4338](https://git.1814.love:8443/wx/HL/pulls/4338)(ALL 改订单视角)+ [#4349](https://git.1814.love:8443/wx/HL/pulls/4349)(行新增 productCoverImg)
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
财务发票管理列表原先以发票记录为视角。ALL tab 只返回已有发票记录的订单行,tabCounts.ALL 计数只统计发票记录总数,导致:
|
||||
|
||||
- ALL 数量 < NONE(未申请)数量,前端展示出现矛盾
|
||||
- ALL 列表无法让财务在一个页面纵览所有已完成订单的发票状态
|
||||
|
||||
本次将 ALL tab 切换为**订单视角**:以全部 COMPLETED(已完成)订单为数据源,有发票的行展示发票字段,无发票的行 status=NONE,对齐前端原型设计。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | tabCounts.ALL 计数口径 | 仅统计发票记录数(REQUESTED+ISSUED+PUSHED),不含未申请 | 统计全部 COMPLETED 订单数(含未申请),永远 >= 任何单个 tab |
|
||||
| 2 | tab=ALL 列表数据源 | 只返回有发票记录的订单行 | 返回全部 COMPLETED 订单,无发票行 status=NONE |
|
||||
| 3 | titleName/requestedBy 在 ALL/NONE tab 的行为 | 未定义(可能干扰结果) | 静默忽略(两字段仅在 REQUESTED/ISSUED/PUSHED 三个发票视角 tab 生效) |
|
||||
| 4 | 列表行出参 productCoverImg | 无此字段 | ✨ 新增,返回产品封面图 URL,无封面时为 null |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/invoice/page |
|
||||
| 描述 | 财务发票管理列表(分页),含 tab 计数与汇总统计 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 Query 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| tab | string | 否 | 当前 tab,枚举值见第 6 节;默认 ALL |
|
||||
| pageNo | integer | 否 | 页码,默认 1 |
|
||||
| pageSize | integer | 否 | 每页条数,默认 20 |
|
||||
| keyword | string | 否 | 关键词搜索(订单号 / 产品名 / 联系人),所有 tab 均生效 |
|
||||
| titleName | string | 否 | 发票抬头名称,**仅 REQUESTED/ISSUED/PUSHED tab 生效,ALL/NONE tab 静默忽略** |
|
||||
| requestedBy | string | 否 | 申请人(定制师),**仅 REQUESTED/ISSUED/PUSHED tab 生效,ALL/NONE tab 静默忽略** |
|
||||
| departureStartDate | string | 否 | 出发日期起,格式 YYYY-MM-DD |
|
||||
| departureEndDate | string | 否 | 出发日期止,格式 YYYY-MM-DD |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无(GET 接口)
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应外层结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [...],
|
||||
"stats": [...],
|
||||
"records": [...],
|
||||
"total": 0,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.1 tabCounts(5 项固定顺序)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | string | tab 枚举值(见第 6 节) |
|
||||
| name | string | tab 中文标签 |
|
||||
| count | integer | 该 tab 对应的记录数 |
|
||||
|
||||
固定顺序:REQUESTED → ISSUED → PUSHED → NONE → ALL
|
||||
|
||||
> **ALL.count 语义已变**:现为全部 COMPLETED 订单数(含未申请);之前仅含发票记录数。
|
||||
|
||||
### 5.2 stats(4 项固定顺序)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | string | 枚举值(见第 6 节) |
|
||||
| name | string | 统计项中文标签 |
|
||||
| count | integer | 数量 |
|
||||
| amount | number | 金额(元,JSON number) |
|
||||
|
||||
固定顺序:REQUESTED → ISSUED → PUSHED → CURRENT_MONTH_ISSUED
|
||||
|
||||
### 5.3 records 列表行(InvoicePageItemRespVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | string | 发票记录 ID(雪花);**无发票行为 null** |
|
||||
| orderId | string | 订单 ID(雪花) |
|
||||
| orderNo | string | 订单号 |
|
||||
| productName | string | 产品名称 |
|
||||
| productCoverImg | string | 产品封面图 URL(✨新增字段);无封面时为 null |
|
||||
| tierName | string | 档期名称 |
|
||||
| contactName | string | 联系人姓名 |
|
||||
| customizerName | string | 定制师姓名 |
|
||||
| departureDate | string | 出发日期,格式 YYYY-MM-DD |
|
||||
| adultCount | integer | 成人数 |
|
||||
| childCount | integer | 儿童数 |
|
||||
| youngChildCount | integer | 婴幼儿数 |
|
||||
| invoiceType | string | 发票类型枚举(见第 6 节);无发票行为 null |
|
||||
| invoiceTypeText | string | 发票类型中文;无发票行为 null |
|
||||
| titleType | string | 抬头类型枚举(见第 6 节);无发票行为 null |
|
||||
| titleName | string | 抬头名称;无发票行为 null |
|
||||
| taxNo | string | 税号;无发票行为 null |
|
||||
| amount | number | 发票金额(元);无发票行为 null |
|
||||
| email | string | 接收邮箱;无发票行为 null |
|
||||
| status | string | 发票状态枚举(见第 6 节);**无发票行固定为 NONE** |
|
||||
| statusText | string | 发票状态中文;**无发票行固定为 未申请** |
|
||||
| requestedBy | string | 申请人(定制师);无发票行为 null |
|
||||
| requestedAt | string | 申请时间 ISO 8601;无发票行为 null |
|
||||
| issuedAt | string | 开票时间 ISO 8601;无发票行为 null |
|
||||
| issuedBy | string | 开票人;无发票行为 null |
|
||||
| invoiceNo | string | 发票号;无发票行为 null |
|
||||
| fileUrl | string | 发票文件 URL;无发票行为 null |
|
||||
| pdfName | string | PDF 文件名;无发票行为 null |
|
||||
| pdfSize | string | PDF 文件大小(字符串,如 128KB);无发票行为 null |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 tab 枚举(查询参数 & tabCounts.code)
|
||||
|
||||
| code | name | 说明 |
|
||||
|------|------|------|
|
||||
| REQUESTED | 待开票 | 已申请、待财务开票 |
|
||||
| ISSUED | 待推送 | 已开票、待推送给客户 |
|
||||
| PUSHED | 已推送 | 已推送给客户 |
|
||||
| NONE | 未申请 | 已完成订单但尚未申请发票 |
|
||||
| ALL | 全部 | 订单视角:全部 COMPLETED 订单(含无发票行) |
|
||||
|
||||
### 6.2 stats.code 枚举
|
||||
|
||||
| code | name |
|
||||
|------|------|
|
||||
| REQUESTED | 待开票 |
|
||||
| ISSUED | 已开票(待推送) |
|
||||
| PUSHED | 已推送 |
|
||||
| CURRENT_MONTH_ISSUED | 本月已开票 |
|
||||
|
||||
### 6.3 发票状态枚举(records.status)
|
||||
|
||||
| code | statusText | 说明 |
|
||||
|------|------------|------|
|
||||
| REQUESTED | 待开票 | 已申请 |
|
||||
| ISSUED | 待推送 | 已开票 |
|
||||
| PUSHED | 已推送 | 已推送 |
|
||||
| NONE | 未申请 | tab=ALL/NONE 时无发票行专用 |
|
||||
|
||||
### 6.4 发票类型枚举(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
|
||||
### 6.5 抬头类型枚举(titleType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| PERSONAL | 个人 |
|
||||
| COMPANY | 企业 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 含义 | 前端处理建议 |
|
||||
|--------|------|-------------|
|
||||
| 200 | 成功 | 正常渲染 |
|
||||
| 401 | 未认证 | 跳登录 |
|
||||
| 403 | 无权限 | 提示无访问权限 |
|
||||
| 500 | 服务端异常 | 通用错误提示 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——tab=ALL,混合行(有发票 + 无发票)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 3,},
|
||||
{"code": "ISSUED", "name": "待推送", "count": 1,},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 5,},
|
||||
{"code": "NONE", "name": "未申请", "count": 12,},
|
||||
{"code": "ALL", "name": "全部", "count": 21}
|
||||
],
|
||||
"stats": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 3, "amount": 8800.00},
|
||||
{"code": "ISSUED", "name": "已开票", "count": 1, "amount": 3200.00},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 5, "amount": 15600.00},
|
||||
{"code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": 12000.00}
|
||||
],
|
||||
"records": [
|
||||
{
|
||||
"id": "1923456789012345678",
|
||||
"orderId": "1823456789012345678",
|
||||
"orderNo": "HL20260620001",
|
||||
"productName": "云南深度游7日",
|
||||
"tierName": "2026-07-01班期",
|
||||
"contactName": "张三",
|
||||
"customizerName": "李定制",
|
||||
"departureDate": "2026-07-01",
|
||||
"adultCount": 2,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"invoiceTypeText": "增值税普通发票",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "北京科技有限公司",
|
||||
"taxNo": "91110000123456789X",
|
||||
"amount": 6800.00,
|
||||
"email": "finance@example.com",
|
||||
"status": "REQUESTED",
|
||||
"statusText": "待开票",
|
||||
"requestedBy": "李定制",
|
||||
"requestedAt": "2026-06-20T10:30:00+08:00",
|
||||
"issuedAt": null,
|
||||
"issuedBy": null,
|
||||
"invoiceNo": null,
|
||||
"fileUrl": null,
|
||||
"pdfName": null,
|
||||
"pdfSize": null
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"orderId": "1823456789012345679",
|
||||
"orderNo": "HL20260619002",
|
||||
"productName": "西藏圣地朝圣8日",
|
||||
"tierName": "2026-06-25班期",
|
||||
"contactName": "王五",
|
||||
"customizerName": "赵定制",
|
||||
"departureDate": "2026-06-25",
|
||||
"adultCount": 4,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 0,
|
||||
"invoiceType": null,
|
||||
"invoiceTypeText": null,
|
||||
"titleType": null,
|
||||
"titleName": null,
|
||||
"taxNo": null,
|
||||
"amount": null,
|
||||
"email": null,
|
||||
"status": "NONE",
|
||||
"statusText": "未申请",
|
||||
"requestedBy": null,
|
||||
"requestedAt": null,
|
||||
"issuedAt": null,
|
||||
"issuedBy": null,
|
||||
"invoiceNo": null,
|
||||
"fileUrl": null,
|
||||
"pdfName": null,
|
||||
"pdfSize": null
|
||||
}
|
||||
],
|
||||
"total": 21,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——tab=ALL 传了 titleName(被静默忽略)
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&titleName=北京科技&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**说明**:titleName 参数不参与过滤,接口正常返回全量 COMPLETED 订单分页。records 中仍会出现 status=NONE 的无发票行,响应结构与 8.1 相同,此处不重复。
|
||||
|
||||
### 8.3 业务失败——无已完成订单时 tab=ALL 返回空列表
|
||||
|
||||
**请求**
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
|
||||
```
|
||||
|
||||
**响应**(无 COMPLETED 订单时)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tabCounts": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 0},
|
||||
{"code": "ISSUED", "name": "待推送", "count": 0},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 0},
|
||||
{"code": "NONE", "name": "未申请", "count": 0},
|
||||
{"code": "ALL", "name": "全部", "count": 0}
|
||||
],
|
||||
"stats": [
|
||||
{"code": "REQUESTED", "name": "待开票", "count": 0, "amount": 0},
|
||||
{"code": "ISSUED", "name": "已开票", "count": 0, "amount": 0},
|
||||
{"code": "PUSHED", "name": "已推送", "count": 0, "amount": 0},
|
||||
{"code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 0, "amount": 0}
|
||||
],
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
**适用**:
|
||||
- 订单状态为 COMPLETED(已完成)的订单才出现在 ALL/NONE tab
|
||||
- REQUESTED/ISSUED/PUSHED 三个 tab 仍以发票记录为视角,只返回有对应状态发票的订单行
|
||||
|
||||
**不适用**:
|
||||
- 进行中(未完成)订单不出现在任何 tab
|
||||
- 已取消订单不出现
|
||||
|
||||
**特殊边界**:
|
||||
- ALL tab 下:tabCounts.ALL.count = REQUESTED + ISSUED + PUSHED + NONE,四者无重叠
|
||||
- 同一订单只有一张有效发票记录,不会重复出现
|
||||
- titleName/requestedBy 在 ALL/NONE tab 传入后静默忽略,不报错、不影响结果
|
||||
- productCoverImg 无封面时为 null,前端需做防空处理
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| tabCounts.ALL.count | 仅 REQUESTED+ISSUED+PUSHED 发票记录数之和 | 全部 COMPLETED 订单数(含无发票订单) |
|
||||
| records(tab=ALL 时) | 仅返回有发票记录的订单行,无 status=NONE 行 | 返回全部 COMPLETED 订单,无发票行 status=NONE |
|
||||
| records[].id(tab=ALL 时) | 所有行均有值 | 无发票行为 null |
|
||||
| records[].amount(tab=ALL 时) | 所有行均有值 | 无发票行为 null |
|
||||
| records[].productCoverImg | 无此字段 | 新增,产品封面图 URL,无封面为 null |
|
||||
|
||||
### tab=ALL 计数对比(假设 2 张发票 + 12 个未申请)
|
||||
|
||||
变更前:
|
||||
```
|
||||
ALL.count = 2 (仅发票记录,ALL < NONE,矛盾)
|
||||
NONE.count = 12
|
||||
```
|
||||
|
||||
变更后:
|
||||
```
|
||||
ALL.count = 14 (= 2 + 12,ALL >= NONE,正确)
|
||||
NONE.count = 12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
**破坏兼容性**:是
|
||||
|
||||
- 前端若假设 tab=ALL 列表行的 id 一定不为 null,需修改:无发票行 id 为 null
|
||||
- 前端若用 id !== null 判断是否显示开票按钮,需改为判断 status 是否为 NONE
|
||||
- tabCounts.ALL.count 现在总是 >= 之前的值,若前端有基于此的断言需同步更新
|
||||
- productCoverImg 为新增字段,前端需在合适位置渲染,null 时不显示
|
||||
- stats 块不受影响,无需改动
|
||||
|
||||
**前端同步上线**:建议与本次后端部署同期上线,避免显示数据矛盾窗口期。
|
||||
|
||||
**回滚方案**:回滚后端至上一个版本 jar 即可恢复旧行为;若前端已适配新结构则需同步回滚前端。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **无发票行的 id 字段为 null**,前端不可用 id 做有无发票判断,应改用 status 字段值是否为 NONE。
|
||||
2. **amount 字段类型为 JSON number(元)**,不是字符串,渲染时直接用数值格式化。
|
||||
3. **titleName/requestedBy 在 ALL/NONE tab 下静默忽略**,搜索框可保留,后端不过滤,行为符合预期。
|
||||
4. **stats 块不受 tab 切换影响**,始终返回全局四项汇总统计,与当前 tab 无关。
|
||||
5. **tabCounts 固定 5 项、顺序不变**,前端可按 code 匹配或按索引渲染。
|
||||
6. **productCoverImg 为 null 时**,前端不显示图片占位,不报错,直接跳过渲染。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4337 发票管理列表 ALL tab 改订单视角](https://git.1814.love:8443/wx/HL/issues/4337)
|
||||
- **PR**:[#4338](https://git.1814.love:8443/wx/HL/pulls/4338)(ALL 改订单视角)+ [#4349](https://git.1814.love:8443/wx/HL/pulls/4349)(行新增 productCoverImg)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,314 @@
|
||||
# 发票申请删 amount · 门槛放宽 · 开票/重传核单门槛(管理后台)
|
||||
|
||||
- **接口**:POST /v3/admin/order/{orderId}/invoice/apply + PUT /v3/admin/order/invoice/{id}/issue + PUT /v3/admin/order/invoice/{id}/reupload
|
||||
- **变更类型**:修改接口,破坏性变更(申请入参删 amount;新增错误码 581524/581525;门槛扩展)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4353](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
|
||||
本次三项改动:1. 申请入参删 amount,开票时后端自动算;2. 申请门槛放宽至定制中及以后;3. 开票/重传新增核单门槛。
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
发票模块本次 PR #4360 三项改动:
|
||||
|
||||
1. **申请入参删 amount**:开票金额不再前端传入,开票时后端按规则自动计算(正常单=应收总额;取消单=净实收)。
|
||||
2. **申请门槛放宽**:原仅允许 COMPLETED 订单申请;放宽为定制中及以后均可申请,同时支持已取消但净实收 > 0 的订单。
|
||||
3. **开票/重传新增核单门槛**:完成开票(issue)和重新上传(reupload)操作要求订单核单完成(reviewStatus=COMPLETED),防止未结算的订单开出金额有误的发票。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | POST apply 入参 amount | 必填字段,前端传开票金额 | 已删除,后端开票时自动算 |
|
||||
| 2 | POST apply 申请门槛(正常订单) | 订单状态必须 COMPLETED | 状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请 |
|
||||
| 3 | POST apply 申请门槛(取消订单) | 不允许 | CANCELLED 且净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 4 | PUT issue 操作门槛 | 仅检查发票状态为 REQUESTED | 增加:订单核单完成(reviewStatus=COMPLETED)才可开票,否则 581525 |
|
||||
| 5 | PUT reupload 操作门槛 | 仅检查发票状态为 ISSUED/PUSHED | 增加:订单核单完成才可重传,否则 581525 |
|
||||
| 6 | 新错误码 581524 | 无 | INVOICE_ORDER_NOT_APPLICABLE:订单不满足申请条件 |
|
||||
| 7 | 新错误码 581525 | 无 | INVOICE_ORDER_NOT_REVIEWED:订单核单未完成,不可开票/重传 |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
### 3.1 代客申请发票
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | POST |
|
||||
| 路径 | /v3/admin/order/{orderId}/invoice/apply |
|
||||
| 描述 | 财务或定制师代客提交开票申请(申请阶段不填金额,开票时后端自动算) |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
### 3.2 完成开票
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | PUT |
|
||||
| 路径 | /v3/admin/order/invoice/{id}/issue |
|
||||
| 描述 | 财务上传发票 PDF 并完成开票,状态 REQUESTED -> ISSUED;需订单核单完成 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 状态守卫,非幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
### 3.3 重新上传发票
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | PUT |
|
||||
| 路径 | /v3/admin/order/invoice/{id}/reupload |
|
||||
| 描述 | 财务重新上传发票文件(ISSUED/PUSHED 均可);需订单核单完成 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 覆盖写,非幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 接口 | 参数名 | 类型 | 必填 | 说明 |
|
||||
|------|--------|------|------|------|
|
||||
| POST apply | orderId | string(Long 雪花) | 是 | 订单 ID |
|
||||
| PUT issue | id | string(Long 雪花) | 是 | 发票记录 ID |
|
||||
| PUT reupload | id | string(Long 雪花) | 是 | 发票记录 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
**POST /v3/admin/order/{orderId}/invoice/apply(AdminInvoiceApplyReqVO,⚠️ amount 已删)**
|
||||
|
||||
当前完整字段列表(无 amount):
|
||||
|
||||
| 字段 | 类型 | 必填条件 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| invoiceType | string | 始终必填 | 发票类型,只有 VAT_NORMAL / VAT_SPECIAL 两值 |
|
||||
| titleType | string | 始终必填 | 抬头类型:COMPANY(单位)/ PERSONAL(个人);VAT_SPECIAL 只能 COMPANY |
|
||||
| titleName | string | 始终必填 | 发票抬头(企业全称或个人姓名) |
|
||||
| taxNo | string | titleType=COMPANY 或 invoiceType=VAT_SPECIAL 时必填 | 纳税人识别号 |
|
||||
| bankName | string | invoiceType=VAT_SPECIAL 时必填 | 开户银行名称 |
|
||||
| bankAccount | string | invoiceType=VAT_SPECIAL 时必填 | 银行账号 |
|
||||
| registAddress | string | invoiceType=VAT_SPECIAL 时必填 | 注册地址 |
|
||||
| registPhone | string | invoiceType=VAT_SPECIAL 时必填 | 注册电话 |
|
||||
| email | string | 始终必填 | 收件邮箱,需通过邮箱格式校验 |
|
||||
| remark | string | 选填 | 申请备注 |
|
||||
|
||||
**PUT /v3/admin/order/invoice/{id}/issue**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| fileUrl | string | 是 | upload 接口返回的 OSS 地址 |
|
||||
| pdfName | string | 否 | PDF 文件名(展示用) |
|
||||
| pdfSize | Long | 否 | PDF 文件大小(字节) |
|
||||
| invoiceNo | string | 否 | 发票号码 |
|
||||
|
||||
**PUT /v3/admin/order/invoice/{id}/reupload**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| fileUrl | string | 是 | 新发票 PDF 的 OSS 地址 |
|
||||
| pdfName | string | 否 | 新 PDF 文件名 |
|
||||
| pdfSize | Long | 否 | 新 PDF 文件大小(字节) |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
**POST apply 响应**:成功返回新建发票 ID(字符串,雪花 ID)。
|
||||
|
||||
|
||||
|
||||
**PUT issue / reupload 响应**:操作成功返回 HTTP 200,data 为 null。
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 发票类型(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
|
||||
### 6.2 抬头类型(titleType)
|
||||
|
||||
| code | 说明 | 限制 |
|
||||
|------|------|------|
|
||||
| COMPANY | 单位 | 普票/专票均可 |
|
||||
| PERSONAL | 个人 | 只能选 VAT_NORMAL;选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
### 6.3 订单状态与可申请关系
|
||||
|
||||
| 订单状态(code) | 中文 | 可申请? | 说明 |
|
||||
|----------------|------|---------|------|
|
||||
| CUSTOMIZING | 定制中 | 是 | 放宽新增 |
|
||||
| PENDING_DEPARTURE | 待出行 | 是 | 放宽新增 |
|
||||
| TRAVELLING | 出行中 | 是 | 放宽新增 |
|
||||
| COMPLETED | 已完成 | 是 | 原有 |
|
||||
| CANCELLED | 已取消 | 条件是 | 净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 其他状态 | - | 否 | 报 581524 |
|
||||
|
||||
### 6.4 开票时金额自动计算规则
|
||||
|
||||
| 订单类型 | 金额计算公式 | 说明 |
|
||||
|----------|-------------|------|
|
||||
| 正常单(非 CANCELLED) | 应收总额 = 订单总价 + 增项 - 优惠 | 申请时不确定,开票时算 |
|
||||
| 取消单(CANCELLED) | 净实收 = 已付金额 - 已退金额 | 净实收 = 0 则拒绝申请 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581502 | INVOICE_CANNOT_ISSUE | 发票状态非 REQUESTED,不允许开票 |
|
||||
| 581503 | INVOICE_CANNOT_REUPLOAD | 发票状态非 ISSUED/PUSHED,不允许重新上传 |
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头/专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行/银行账号/注册地址/注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 收件邮箱为必填或格式错误(HTTP 400) |
|
||||
| 581523 | INVOICE_VAT_SPECIAL_PERSONAL_FORBIDDEN | 专票只能开给单位 |
|
||||
| 581524 | INVOICE_ORDER_NOT_APPLICABLE | ✨ 新增:订单状态不在可申请集合内,或取消单净实收为 0 |
|
||||
| 581525 | INVOICE_ORDER_NOT_REVIEWED | ✨ 新增:开票/重传时订单核单未完成(reviewStatus != COMPLETED) |
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——普票申请(无 amount 字段)
|
||||
|
||||
请求
|
||||
```
|
||||
POST /v3/admin/order/1920000000000000001/invoice/apply
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "李四",
|
||||
"email": "lisi@example.com",
|
||||
"remark": "报销使用"
|
||||
}
|
||||
```
|
||||
|
||||
响应
|
||||
```json
|
||||
{"code": 200, "msg": "success", "data": "1934567890123456789"}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——取消订单且净实收 > 0 可申请
|
||||
|
||||
说明:订单已取消,实付 3000 元、退款 1000 元,净实收 2000 元 > 0,申请通过。开票时金额自动计算为净实收 2000 元。
|
||||
|
||||
请求(结构同 8.1,orderId 换为该取消订单 ID)
|
||||
|
||||
响应
|
||||
```json
|
||||
{"code": 200, "msg": "success", "data": "1934567890123456700"}
|
||||
```
|
||||
|
||||
### 8.3 业务失败——订单核单未完成,完成开票被拒
|
||||
|
||||
请求
|
||||
```
|
||||
PUT /v3/admin/order/invoice/1934567890123456789/issue
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"fileUrl": "https://oss.example.com/invoice/2026/06/xxx.pdf",
|
||||
"pdfName": "发票_北京科技.pdf",
|
||||
"invoiceNo": "12345678"
|
||||
}
|
||||
```
|
||||
|
||||
响应(订单 reviewStatus 非 COMPLETED)
|
||||
```json
|
||||
{"code": 581525, "msg": "订单核单未完成,不可开具发票", "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
适用:
|
||||
- POST apply:订单状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED},或 CANCELLED 且净实收 > 0
|
||||
- PUT issue:发票状态为 REQUESTED,且订单 reviewStatus = COMPLETED
|
||||
- PUT reupload:发票状态为 ISSUED/PUSHED,且订单 reviewStatus = COMPLETED
|
||||
|
||||
不适用:
|
||||
- POST apply:订单为 PAID(已支付但未进入定制阶段)、或 CANCELLED 且净实收 = 0,报 581524
|
||||
- PUT issue/reupload:订单核单未完成(reviewStatus != COMPLETED),报 581525
|
||||
|
||||
特殊边界:
|
||||
- 取消单开票金额 = 净实收(已付 - 已退),不是订单总价;正常单 = 应收总额
|
||||
- 一单一票:同一订单只允许一张有效发票(REQUESTED/ISSUED/PUSHED 状态),若已有则报 581511
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
### apply 入参字段对比
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| amount | 始终必填,JSON number,单位元 | ⚠️ 已删除,后端开票时自动算 |
|
||||
|
||||
### apply 申请门槛对比
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 正常订单 | 仅 COMPLETED 可申请,其余报 581510 | CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED 均可申请 |
|
||||
| 取消订单 | 不允许 | 净实收 > 0 时可申请,= 0 报 581524 |
|
||||
| 不满足时错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED(已废弃) | 581524 INVOICE_ORDER_NOT_APPLICABLE(新增) |
|
||||
|
||||
### issue / reupload 门槛对比
|
||||
|
||||
| 操作 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| PUT issue | 仅检查发票状态为 REQUESTED | 增加:订单核单完成(reviewStatus=COMPLETED),否则 581525 |
|
||||
| PUT reupload | 仅检查发票状态为 ISSUED/PUSHED | 增加:订单核单完成,否则 581525 |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:是
|
||||
|
||||
- 前端申请表单必须删除开票金额输入框,不再传 amount 字段
|
||||
- 前端申请成功弹窗/提示中不展示金额(金额在开票时才确定)
|
||||
- 财务点击「完成开票」,若订单未核单则收到 581525,前端需展示「订单核单未完成,不可开票」提示
|
||||
|
||||
前端同步上线:申请表单删 amount 必须在本次后端部署后同步更新;财务开票侧 581525 错误提示须同步适配。
|
||||
|
||||
回滚方案:回滚后端至 #4360 前版本,amount 字段恢复必填,申请门槛收回到仅 COMPLETED,581524/581525 不再存在。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **amount 字段已删**:前端申请表单必须移除金额输入框,不传 amount。传入后端会忽略,导致用户困惑(以为填了金额实际无效)。
|
||||
2. **开票时金额自动写入**:发票金额由后端在 issue 操作时根据订单类型自动计算,财务无需手填金额。
|
||||
3. **申请门槛放宽的注意点**:定制中/待出行/出行中提前申请的发票,开票时(issue)后端按当时应收总额计算金额。财务须在核单完成后再开票(581525 已强制保证)。
|
||||
4. **核单门槛(581525)**:完成开票和重传均要求核单完成。财务若收到此错误,需先在核单管理页完成核单,再回来开票。
|
||||
5. **取消单申请**:CANCELLED 订单申请发票,开票金额为净实收(已付 - 已退)。若净实收为 0,申请被 581524 拒绝。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4353 发票模块门槛放宽与开票规则优化](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,48 @@
|
||||
# 配房「选择酒店」弹窗三连修(① 定制师点名标记·后端已修上线 ② 分页·前端 ③ 售价列·前端)
|
||||
|
||||
> 变更类型:① ✅ 后端修接口行为(已部署测试服 + 网关 API 实测) ② / ③ 📖 前端待办(后端无改动)
|
||||
> 端类型:管理后台(房务管家·配房「选择酒店」弹窗)
|
||||
> 日期:2026-06-25 | 工单/PR:① #4373 / #4374 | 服务:hl-order-service-v3
|
||||
> 关联:承接 #4363(默认不按城市) / #4367(选房型房间数)。本篇修「定制师点名多家只标一家」+ 列出分页/售价两个前端 bug。
|
||||
|
||||
---
|
||||
|
||||
## ① 定制师点名标记:后端已修(前端可不传 preferredHotelId)
|
||||
|
||||
**现象**:定制师在用房需求里点名多家候选酒店(房控择一),弹窗候选列表只有第一家标「定制师推荐」,其余漏标。
|
||||
|
||||
**根因(抓包实证)**:前端 `GET /v3/admin/hotel-candidates` 只传单个 `preferredHotelId` → 后端只标这一个。
|
||||
|
||||
**后端修复(已上线测试服)**:hotel-candidates 改为**按 `orderId` + `dayNumber` 自动查当前生效用房需求、标记当天全部定制师点名候选**,且只标当天、不跨天。
|
||||
|
||||
**前端动作(可选简化,不改也能工作)**:
|
||||
- 调 hotel-candidates **只需传 `orderId` + `dayNumber`**(`roomCount` 可选),后端即自动把当天定制师点名的全部候选标 `isConsultantRecommended=true`。
|
||||
- `preferredHotelId` / `requirementId` 可不再传(传了也兼容,但不再是标记的唯一来源)。
|
||||
- 实测 order 2069593503129636865:`dayNumber=1` → 标 2 家(海拉尔海棠+香格里拉);`dayNumber=2` → 标 1 家(海拉尔嘉世豪),不含 DAY1 候选。
|
||||
|
||||
## ② 弹窗「共 N 条」与分页错误(前端 bug,后端无改动)
|
||||
|
||||
**现象**:弹窗底部「共 8 条」与实际不符;分页不可用。
|
||||
|
||||
**定位**:后端 hotel-candidates **无 total / 分页字段**,`data.candidates` 是**完整数组**(实测不传 city 返 **21 条**全部在售)。「共 8 条」是前端把 pageSize(8) 当成了 total。
|
||||
|
||||
**前端动作**:
|
||||
- 「共 N 条」**= `data.candidates.length`**(不要写死 8、不要拿 pageSize 当 total)。
|
||||
- 需要分页就对完整数组做**纯前端分页**(后端一次性返全部、已按推荐度排好序);不需要分页可直接滚动渲染全部。
|
||||
|
||||
## ③ 列表「售价」列:去掉,改到选中酒店后填写(默认协议价)(前端,后端无改动)
|
||||
|
||||
**现象 / 诉求**:候选列表显示了「售价」列(值=协议价镜像)。结算价不应在候选列表预显示,应在**选中酒店后填写**、默认用协议价。
|
||||
|
||||
**定位**:后端 candidate VO **只返协议价 `protoPrice`,本就没有「售价」字段**——列表那列「售价」是前端用协议价镜像出来的。
|
||||
|
||||
**前端动作**:
|
||||
- 候选列表**去掉「售价」列**,只展示协议价 `protoPrice`(参考价)。
|
||||
- 把「结算价」输入挪到**选中酒店后的填写环节**(与房型下拉 + 房间数一起,见 #4367),**默认值 = 该酒店/房型协议价**(`protoPrice` 或 `roomTypes[].protocolPrice`)。
|
||||
- 提交时仍带 `AssignmentItemReqVO.sellPrice`(房务填的结算价,后端早支持,无改动)。
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
- ① 已部署测试服并网关 API 实测通过;②③ 后端无改动,纯前端弹窗交互修正。
|
||||
- 「同步结算价到协议价」开关为另一独立后端工单(随后单独 changelog),本篇不含。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 配房填结算价加「同步到资源协议价」开关(写回价格日历;后端已部署,前端加开关 UI)
|
||||
|
||||
> 变更类型:✅ 新增接口字段(additive,已部署测试服 boot + 单测充分 verify 写回逻辑)
|
||||
> 端类型:管理后台(房务管家·配房填写「结算价」处)
|
||||
> 日期:2026-06-25 | 工单/PR:#4377 / #4378 | 服务:hl-order-service-v3(写回经 Feign → hl-resource-service)
|
||||
> 关联:复用 #4072「房务改协议价写回 resource」现成设施;承接配房填结算价(#4367 选房型房间数)。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
- 房务配房填「结算价」(sellPrice)时,可勾选开关「同步到资源协议价(改价格日历)」。
|
||||
- 勾选 → 后端在提交成功后(afterCommit)**用该项结算价写回 resource 价格日历的协议价**(`room_price_calendar.protocol_price`,resource 是协议价单源)。
|
||||
- 不勾(默认)→ 行为不变,只落配房、不动 resource 协议价。
|
||||
|
||||
## 1. 提交配房 — 单项加字段
|
||||
`POST /v3/admin/order/hotel-requirements/{requirementId}/assignments` → `items[]`(`AssignmentItemReqVO`)**新增**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `syncProtocolPrice` | Boolean | 否(默认 false) | true=把本项 `sellPrice` 同步写回 resource 该(roomTypeId, stayDate)协议价;false/不传=不同步 |
|
||||
|
||||
(`dayNumber`/`hotelId`/`roomTypeId`/`roomCategory`/`roomCount`/`sellPrice`/`remark` 字段不变。)
|
||||
|
||||
## 2. 修改单条配房 — 同步加字段
|
||||
`PUT /v3/admin/order/assignments/{id}`(`AssignmentUpdateReqVO`)**新增** `syncProtocolPrice`(Boolean,同上语义)。
|
||||
- 勾选 `syncProtocolPrice=true` 且传了 `sellPrice` → 用 **sellPrice** 写回协议价(**优先**于 #4072 的「改 protoPrice 字段触发写回」)。
|
||||
- 不勾 → 保留 #4072 原逻辑:改了 `protoPrice` 字段才写回 protoPrice 值。
|
||||
|
||||
## 3. 前端动作
|
||||
- 配房填「结算价」处加一个开关/勾选框「同步到资源协议价(改价格日历)」,默认**不勾**。
|
||||
- 提交时把开关值放进对应 item 的 `syncProtocolPrice`(提交配房)或 body 的 `syncProtocolPrice`(修改配房)。
|
||||
- 写回是后端行为(afterCommit 经 Feign 写 resource),前端无需关心写回细节;写回失败不阻断配房(后端只记日志)。
|
||||
|
||||
## 4. 验证状态(诚实标注)
|
||||
- 后端已部署测试服:order-v3 双实例 boot 成功(`Started OrderServiceV3Application` 12.3s)、网关 health code=200,接口已接受 `syncProtocolPrice` 字段。
|
||||
- 写回逻辑由单测充分覆盖:`HouseAssignmentServiceTest` 56 绿(2 新用例 verify 写回 Feign 被调 + 写回值=该项 sellPrice、且 update 场景 sellPrice 优先于 protoPrice)+ 门禁全绿。
|
||||
- **端到端实测通过**(部署后补做):order 2069593503129636865 DAY3 配额尔古纳白桦主题标间、`syncProtocolPrice=true`、`sellPrice=288.88` → resource 该房型 2026-06-28 协议价 **220.00 → 288.88**(精确等于 sellPrice,写回真生效);验证后已删配房 + 协议价改回 220 恢复测试数据原状。
|
||||
@@ -0,0 +1,189 @@
|
||||
# 发票推送渠道改走数据字典 invoice_push_channel(管理后台)
|
||||
|
||||
- **接口**:PUT /v3/admin/order/invoice/{id}/push
|
||||
- **变更类型**:修改接口(推送渠道合法值由后端硬编码改为数据字典 `invoice_push_channel` 驱动)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4379](https://git.1814.love:8443/wx/HL/issues/4379)
|
||||
- **PR**:[#4379](https://git.1814.love:8443/wx/HL/pulls/4379)
|
||||
|
||||
发票「推送客户」的渠道合法值(email/wechat/sms)原为后端硬编码,本次改为读取数据字典 `invoice_push_channel`,便于后续在字典中增删渠道而无需改代码。当前可选值与含义不变。
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
发票详情页「推送客户」操作(PUT push),入参为渠道列表 `channels`。原后端在代码里硬编码合法渠道集合 `{email, wechat, sms}` 做校验;本次改为运行时读取数据字典 `invoice_push_channel` 的全部 ACTIVE 字典值作为合法集合。
|
||||
|
||||
- 字典为空或字典服务不可用时,**降级回硬编码 `{email, wechat, sms}`**,保证可用性。
|
||||
- 本期渠道值与含义**未变**,仍为 email/wechat/sms 三项。
|
||||
- 前端渠道下拉/多选项建议改为从字典接口拉取(见第 6 节),不再前端写死,后续字典增删渠道前端可自动同步。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | push 渠道合法值来源 | 后端硬编码 `{email, wechat, sms}` | 数据字典 `invoice_push_channel` 的 ACTIVE 字典值 |
|
||||
| 2 | 字典缺失/异常时行为 | 不适用 | 降级回硬编码 `{email, wechat, sms}` |
|
||||
| 3 | 入参字段 / 渠道值 | channels: string[],值 email/wechat/sms | 不变(同前) |
|
||||
| 4 | 错误码 581521/581522 | 已存在 | 不变(语义同前,仅合法集合来源变化) |
|
||||
|
||||
> 接口路径、入参结构、出参、错误码均无变化;仅「合法渠道值的来源」从硬编码改为字典。
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | PUT |
|
||||
| 路径 | /v3/admin/order/invoice/{id}/push |
|
||||
| 描述 | 将已开具发票推送给客户(多渠道多选);本期仅落状态与渠道记录,不真发通知 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 状态守卫,非幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | string(Long 雪花) | 是 | 发票记录 ID |
|
||||
|
||||
### 4.2 请求体字段(InvoicePushReqVO)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| channels | string[] | 是 | 推送渠道,多选;每个值须为字典 `invoice_push_channel` 中的 ACTIVE 字典值(当前 email/wechat/sms)。空数组报 581521,含非法值报 581522 |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
操作成功返回 HTTP 200,data 为 null。
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 推送渠道字典(invoice_push_channel)
|
||||
|
||||
合法渠道值来源由本次改为该字典。前端可调用字典接口 `GET /admin/dict/data/invoice_push_channel` 动态获取下拉项(dictValue 作为提交值,dictLabel 作为展示文案)。
|
||||
|
||||
| dictValue | dictLabel | sortOrder | status |
|
||||
|-----------|-----------|-----------|--------|
|
||||
| email | 邮件 | 10 | ACTIVE |
|
||||
| wechat | 微信 | 20 | ACTIVE |
|
||||
| sms | 短信 | 30 | ACTIVE |
|
||||
|
||||
> 提交 push 时 channels 传 dictValue(如 `email`),非 dictLabel。
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581521 | INVOICE_PUSH_CHANNEL_REQUIRED | channels 为空(至少选一个渠道) |
|
||||
| 581522 | INVOICE_PUSH_CHANNEL_INVALID | channels 含非法值(不在字典 invoice_push_channel 的 ACTIVE 字典值内) |
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——推送邮件 + 微信
|
||||
|
||||
请求
|
||||
```
|
||||
PUT /v3/admin/order/invoice/2069957731908784129/push
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"channels": ["email", "wechat"]
|
||||
}
|
||||
```
|
||||
|
||||
响应
|
||||
```json
|
||||
{"code": 200, "msg": "success", "data": null}
|
||||
```
|
||||
|
||||
### 8.2 边界——单渠道短信
|
||||
|
||||
```json
|
||||
{"channels": ["sms"]}
|
||||
```
|
||||
响应
|
||||
```json
|
||||
{"code": 200, "msg": "success", "data": null}
|
||||
```
|
||||
|
||||
### 8.3 业务失败——非法渠道值
|
||||
|
||||
请求
|
||||
```json
|
||||
{"channels": ["fax"]}
|
||||
```
|
||||
响应
|
||||
```json
|
||||
{"code": 581522, "msg": "推送渠道非法(须为 email/wechat/sms)", "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
适用:
|
||||
- channels 非空,且每个值均为字典 `invoice_push_channel` 中的 ACTIVE 字典值。
|
||||
|
||||
不适用:
|
||||
- channels 为空数组 → 581521
|
||||
- channels 含任意非字典值 → 581522(即便其余值合法,整体拒绝)
|
||||
|
||||
特殊边界:
|
||||
- 字典 `invoice_push_channel` 不可用或为空时,后端降级回硬编码 `{email, wechat, sms}`,校验行为与改前一致。
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
| 维度 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 合法渠道来源 | 代码硬编码 `{email, wechat, sms}` | 数据字典 `invoice_push_channel`(ACTIVE 字典值) |
|
||||
| 增删渠道 | 改代码 + 发版 | 维护字典即可(前端从字典接口取项可自动同步) |
|
||||
| 入参 / 出参 / 错误码 | — | 无变化 |
|
||||
| 当前可选渠道 | email/wechat/sms | email/wechat/sms(不变) |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:否
|
||||
|
||||
- 当前渠道值与含义未变,已写死 email/wechat/sms 的前端仍可正常提交,不会立即报错。
|
||||
- 建议前端将渠道选项改为从 `GET /admin/dict/data/invoice_push_channel` 拉取,以便后续字典增删渠道时自动同步。
|
||||
|
||||
回滚方案:回滚后端至 #4379 前版本,合法渠道恢复为硬编码 `{email, wechat, sms}`;字典 `invoice_push_channel` 保留不影响其他功能。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. 提交 channels 传 dictValue(`email`/`wechat`/`sms`),不是中文 dictLabel。
|
||||
2. 字典维护需保持 status=ACTIVE 才生效;置为非 ACTIVE 的渠道值将被视为非法(581522)。
|
||||
3. 本期 push 仅落状态与渠道记录,不真实发送通知。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4379](https://git.1814.love:8443/wx/HL/issues/4379)
|
||||
- **PR**:[#4379 发票推送渠道改走数据字典 invoice_push_channel](https://git.1814.love:8443/wx/HL/pulls/4379)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,139 @@
|
||||
# 站内信收件箱混排聊天消息(订单类消息恢复可见)(管理后台)
|
||||
|
||||
- **接口**:GET /admin/message/list
|
||||
- **变更类型**:修改接口(收件箱列表现在混排返回「聊天消息」+「系统通知」;出参 VO 新增 3 字段)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4381](https://git.1814.love:8443/wx/HL/issues/4381)
|
||||
- **PR**:[#4382](https://git.1814.love:8443/wx/HL/pulls/4382)
|
||||
|
||||
房务/车务在「站内信」收件箱里**看不到订单类聊天消息**(聊天内容)的问题已修复。收件箱列表(GET /admin/message/list)现按设计「聊天每条都落收件箱、与通知混排」返回:系统通知(kind=NOTIFY)+ 聊天消息(kind=CHAT)按时间倒序混排。聊天消息的业务类型派生为「订单消息」(messageType=ORDER),其 content 即聊天内容。出参 VO 新增 `kind`/`senderName`/`conversationKey` 三字段,供前端区分聊天行、显示发件人、跳转会话线程。
|
||||
|
||||
---
|
||||
|
||||
## 1 背景
|
||||
|
||||
站内信已升级为「聊天」后端:聊天消息与系统通知共用一张收件箱(admin_message),聊天行 kind=CHAT。原实现给收件箱列表查询误加了 `kind=NOTIFY` 过滤,把聊天行从列表里抹掉,导致:
|
||||
|
||||
- 顶部铃铛角标(GET /admin/message/unread-count)**有未读数字**(角标统计含聊天);
|
||||
- 但点进站内信列表却**看不到聊天内容**(被 kind 过滤掉)。
|
||||
|
||||
本次去除该过滤,恢复混排。聊天行 bizType=HOUSE、bizId=订单ID,派生为「订单消息」,即用户口中的「订单类消息」。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | 收件箱列表返回内容 | 仅系统通知(kind=NOTIFY) | 系统通知 + 聊天消息(kind=CHAT)混排,createTime DESC |
|
||||
| 2 | 出参 VO `kind` | 无 | 新增:NOTIFY=系统通知 / CHAT=聊天消息 |
|
||||
| 3 | 出参 VO `senderName` | 无 | 新增:聊天行发件人姓名(仅 kind=CHAT 有值,通知为 null) |
|
||||
| 4 | 出参 VO `conversationKey` | 无 | 新增:聊天行会话键(仅 kind=CHAT 有值,前端据此跳转会话线程,通知为 null) |
|
||||
| 5 | 聊天行 messageType | (此前不返回聊天行) | ORDER(订单消息),messageTypeLabel=订单消息 |
|
||||
| 6 | 顶部角标 unread-count | 含 NOTIFY+CHAT | 不变(本就含两者) |
|
||||
|
||||
> 接口路径 / 入参 / 鉴权不变;仅「列表返回内容」与「出参 VO 字段」变化,且 VO 为新增字段(向后兼容)。
|
||||
|
||||
---
|
||||
|
||||
## 3 出参字段(AdminMessageRespVO,本次新增 3 字段加粗)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| messageId | string | 消息 ID(雪花,String 透传) |
|
||||
| categoryCode | string | 分类编码;系统通知为 SYSTEM,**聊天消息为 null**(类型看 kind/messageType) |
|
||||
| title | string | 标题。系统通知=其本身标题;**聊天消息=后端派生「【聊天】发件人(角色)」**(角色为空省略括号,如「【聊天】王骁」;PR #4386),收件箱标题列一眼可辨「谁发来的聊天」,正文仍看 content |
|
||||
| content | string | 正文;聊天消息时即**聊天内容** |
|
||||
| link | string | 跳转链接(通知用,聊天为 null) |
|
||||
| bizId | string | 关联业务 ID;聊天消息为订单 ID |
|
||||
| bizType | string | 关联业务类型;聊天消息为 HOUSE |
|
||||
| isRead | int | 0 未读 / 1 已读 |
|
||||
| createTime | long | 创建时间戳(ms) |
|
||||
| messageType | string | ORDER=订单消息 / NORMAL=普通消息(派生)。聊天消息恒为 ORDER |
|
||||
| messageTypeLabel | string | 订单消息 / 普通消息 |
|
||||
| **kind** | string | **NOTIFY=系统通知 / CHAT=聊天消息**(区分行类型) |
|
||||
| **senderName** | string | **发件人姓名(仅 kind=CHAT 有值;系统通知为 null)** |
|
||||
| **conversationKey** | string | **会话键(仅 kind=CHAT 有值,前端据此跳转会话线程;系统通知为 null)** |
|
||||
|
||||
---
|
||||
|
||||
## 4 前端接入指引(重要)
|
||||
|
||||
1. **区分行类型**:用 `kind` 判断渲染样式——`NOTIFY` 走原系统通知样式(title+content+link);`CHAT` 走聊天行样式(发件人 senderName + content=聊天内容,归类为「订单消息」)。
|
||||
2. **聊天行点击 = 进会话线程**:点击 kind=CHAT 行时,用 `conversationKey` 进入会话线程(聊天线程/会话详情),由会话侧标记已读(会同步清掉该会话未读水位)。**不要对聊天行调 PUT /{id}/read 或 PUT /read-all 来标已读**。
|
||||
3. **批量已读只清通知**:PUT /read-all(全部已读)、PUT /category/{code}/read(分类已读)、GET /categories(分类未读汇总)**只作用于系统通知(kind=NOTIFY),不碰聊天行**——聊天读态由会话维度权威维护(防误清未点开的聊天未读、防未读水位漂移)。所以点「全部已读」后聊天行仍可能保持未读,属预期。
|
||||
4. **顶部角标**:GET /admin/message/unread-count 含 NOTIFY+CHAT 全部未读(口径与列表一致)。
|
||||
|
||||
---
|
||||
|
||||
## 5 示例
|
||||
|
||||
请求
|
||||
```
|
||||
GET /admin/message/list?pageNo=1&pageSize=20
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
响应(节选,混排:一条聊天 + 一条通知)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 26,
|
||||
"records": [
|
||||
{
|
||||
"messageId": "20699...",
|
||||
"kind": "CHAT",
|
||||
"messageType": "ORDER",
|
||||
"messageTypeLabel": "订单消息",
|
||||
"senderName": "张三",
|
||||
"conversationKey": "HOUSE:2069593503129636865",
|
||||
"bizType": "HOUSE",
|
||||
"bizId": "2069593503129636865",
|
||||
"title": null,
|
||||
"content": "改日期走修改订单 我没有权限",
|
||||
"categoryCode": null,
|
||||
"isRead": 0,
|
||||
"createTime": 1750..."
|
||||
},
|
||||
{
|
||||
"messageId": "20698...",
|
||||
"kind": "NOTIFY",
|
||||
"messageType": "ORDER",
|
||||
"messageTypeLabel": "订单消息",
|
||||
"senderName": null,
|
||||
"conversationKey": null,
|
||||
"bizType": "HOUSE",
|
||||
"bizId": "2069...",
|
||||
"title": "房务询单已超时",
|
||||
"content": "订单 ... 的房务询单已超时未确认",
|
||||
"categoryCode": "SYSTEM",
|
||||
"isRead": 1,
|
||||
"createTime": 1750..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:否(VO 为新增字段;老前端忽略新字段即可,原有字段不变)。
|
||||
|
||||
- 行为变化:收件箱列表会**多出聊天行**(kind=CHAT,显示为订单消息)。若前端未按 kind 区分渲染,聊天行会以「无标题、content=聊天内容」的形态出现在列表里——建议按第 4 节接入。
|
||||
- 已在测试服实测:admin(adminId=1001)收件箱 26 条(21 聊天 + 5 通知)全部混排返回,与 DB 计数一致。
|
||||
|
||||
回滚方案:回滚后端至 #4382 前版本,收件箱恢复仅返系统通知(聊天行重新被隐藏)。
|
||||
|
||||
---
|
||||
|
||||
## 7 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4381](https://git.1814.love:8443/wx/HL/issues/4381)
|
||||
- **PR**:[#4382 站内信收件箱混排聊天——房务订单类消息恢复可见](https://git.1814.love:8443/wx/HL/pulls/4382)
|
||||
- **设计依据**:站内信升级聊天 INAPP-CHAT v1.0 决策②(聊天每条都落收件箱、与通知混排)
|
||||
- **后端负责人**:王骁(wx)
|
||||
@@ -0,0 +1,222 @@
|
||||
# 发票管理列表搜索补齐(订单号接线 + 客户名 + 跨 tab 完整搜索)(管理后台)
|
||||
|
||||
- **接口**:GET /v3/admin/order/invoice/page
|
||||
- **变更类型**:修改接口(新增入参 `contactName`;`orderNo` 由声明变为生效;搜索跨全部 tab)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4383](https://git.1814.love:8443/wx/HL/issues/4383)
|
||||
- **PR**:[#4384](https://git.1814.love:8443/wx/HL/pulls/4384)
|
||||
|
||||
发票管理列表搜索原为半成品:`orderNo` 声明了但未生效,`titleName`/`requestedBy` 仅部分 tab 生效,ALL/NONE 不支持搜索。本次补齐为四维度(订单号 / 客户名 / 开票抬头 / 申请人)跨全部 tab 完整生效。
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
发票管理列表 `GET /v3/admin/order/invoice/page` 支持按 tab(待开票/待推送/已推送/未申请/全部)分页查询。原搜索能力残缺:
|
||||
|
||||
- `orderNo`(订单号)入参已声明但后端未接线(无效字段)。
|
||||
- `titleName`(开票抬头)/ `requestedBy`(申请人)仅在 REQUESTED/ISSUED/PUSHED 三个 tab 生效。
|
||||
- ALL(全部)/ NONE(未申请)两个 tab 完全不支持搜索。
|
||||
- 无「客户名」搜索入参。
|
||||
|
||||
本次补齐为四维度搜索,跨全部 tab 完整生效。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | 入参 `contactName` | 无 | **新增**,客户名模糊搜索 |
|
||||
| 2 | 入参 `orderNo` | 已声明但未生效 | **生效**,订单号模糊搜索 |
|
||||
| 3 | 入参 `titleName` | 仅状态 tab 生效 | 全部相关 tab 生效(NONE 除外,无发票) |
|
||||
| 4 | 入参 `requestedBy` | 仅状态 tab 生效 | 全部相关 tab 生效(NONE 除外,无发票) |
|
||||
| 5 | ALL / NONE tab 搜索 | 不支持 | 支持(订单号/客户名直接搜;开票抬头/申请人对 ALL 跨表生效) |
|
||||
| 6 | 角标计数 tabCounts / 顶部统计 stats | 全局 | **保持全局**,不随搜索过滤(仅列表 records 过滤) |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/invoice/page |
|
||||
| 描述 | 发票管理列表分页查询(tab 过滤 + 四维度模糊搜索 + 角标计数 + 顶部统计) |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
| 幂等性 | 查询接口,幂等 |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参(Query 参数)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| pageNo | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||||
| tab | string | 否 | tab 过滤:REQUESTED / ISSUED / PUSHED / NONE / ALL,默认 ALL |
|
||||
| orderNo | string | 否 | **订单号模糊搜索(本次接线生效)** |
|
||||
| contactName | string | 否 | **客户名模糊搜索(本次新增)** |
|
||||
| titleName | string | 否 | 开票抬头模糊搜索 |
|
||||
| requestedBy | string | 否 | 申请人模糊搜索 |
|
||||
|
||||
> 四个搜索字段均为模糊匹配,可任意组合;为空的字段自动跳过。
|
||||
|
||||
### 搜索维度按 tab 生效矩阵
|
||||
|
||||
| 维度 | 字段来源 | REQUESTED/ISSUED/PUSHED | ALL | NONE |
|
||||
|------|---------|:---:|:---:|:---:|
|
||||
| orderNo(订单号) | 订单 | ✅ | ✅ | ✅ |
|
||||
| contactName(客户名) | 订单 | ✅ | ✅ | ✅ |
|
||||
| titleName(开票抬头) | 发票 | ✅ | ✅ | ⛔(未申请无发票,命中即空) |
|
||||
| requestedBy(申请人) | 发票 | ✅ | ✅ | ⛔(未申请无发票,命中即空) |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应为分页结构(`InvoicePageRespVO`),字段同改前未变:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | array | 当前页发票/订单行列表 |
|
||||
| total | long | 符合搜索条件的总条数 |
|
||||
| page | int | 当前页码 |
|
||||
| pageSize | int | 每页条数 |
|
||||
| tabCounts | array | 各 tab 角标计数(**全局口径,不随搜索过滤**) |
|
||||
| stats | array | 顶部统计(**全局口径,不随搜索过滤**) |
|
||||
|
||||
> 行字段(orderNo / contactName / customizerName / titleName / status / productName / productCoverImg 等)维持原有结构,本次不变。
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 tab 取值
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| REQUESTED | 待开票 |
|
||||
| ISSUED | 待推送(已开票) |
|
||||
| PUSHED | 已推送 |
|
||||
| NONE | 未申请(已完成出行、尚未申请发票的订单) |
|
||||
| ALL | 全部 |
|
||||
|
||||
本次无新增枚举/字典。
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
本次无新增错误码。查询接口异常走全局统一处理。
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——按订单号搜索(任意 tab)
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?pageNo=1&pageSize=20&tab=ALL&orderNo=HL20260620152823663
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
响应(命中 1 条)
|
||||
```json
|
||||
{"code":200,"msg":"success","data":{"records":[{"orderNo":"HL20260620152823663","contactName":"冯雷","titleName":"张三","status":"PUSHED"}],"total":1,"page":1,"pageSize":20,"tabCounts":[],"stats":[]}}
|
||||
```
|
||||
|
||||
### 8.2 边界——客户名搜索(一个客户多单)
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&contactName=冯雷
|
||||
```
|
||||
响应(命中 2 条,同客户多单)
|
||||
```json
|
||||
{"code":200,"msg":"success","data":{"records":[{"contactName":"冯雷"},{"contactName":"冯雷"}],"total":2}}
|
||||
```
|
||||
|
||||
### 8.3 跨表搜索——发票视角 tab 按订单号过滤
|
||||
|
||||
说明:PUSHED 等状态 tab 走发票表,订单号属订单表,后端自动反查订单 ID 限定发票结果。
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=PUSHED&orderNo=HL20260620152823663
|
||||
```
|
||||
响应(命中 1 条)
|
||||
```json
|
||||
{"code":200,"msg":"success","data":{"records":[{"orderNo":"HL20260620152823663","status":"PUSHED"}],"total":1}}
|
||||
```
|
||||
|
||||
### 8.4 无命中——返回空列表(非报错)
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/page?tab=ALL&orderNo=NOSUCH999
|
||||
```
|
||||
响应
|
||||
```json
|
||||
{"code":200,"msg":"success","data":{"records":[],"total":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
- 四个搜索字段可任意组合,均为模糊匹配,空字段跳过。
|
||||
- NONE(未申请)tab 不含发票,传 `titleName` / `requestedBy` 时结果为空(属预期,非报错)。
|
||||
- 搜索仅影响列表 `records` 与 `total`;`tabCounts`(角标计数)和 `stats`(顶部统计)保持全局口径,不随搜索过滤。
|
||||
- 无命中返回空列表 + total=0,HTTP 200,不报错。
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
### 入参对比
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| contactName | 无 | 新增,客户名模糊搜索 |
|
||||
| orderNo | 声明但无效 | 生效,订单号模糊搜索 |
|
||||
| titleName / requestedBy | 仅状态 tab 生效 | 全部相关 tab 生效(NONE 除外) |
|
||||
|
||||
### 搜索覆盖对比
|
||||
|
||||
| tab | 变更前可搜字段 | 变更后可搜字段 |
|
||||
|-----|---------------|---------------|
|
||||
| REQUESTED/ISSUED/PUSHED | titleName / requestedBy | orderNo / contactName / titleName / requestedBy |
|
||||
| ALL | 无 | orderNo / contactName / titleName / requestedBy |
|
||||
| NONE | 无 | orderNo / contactName |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:否(纯增强,新增可选入参 + 原无效入参生效)
|
||||
|
||||
- 前端可在发票管理列表搜索框接入四个维度(订单号 / 客户名 / 开票抬头 / 申请人),任意组合提交。
|
||||
- 原已传 `titleName`/`requestedBy` 的前端不受影响,行为兼容并扩展到更多 tab。
|
||||
- 角标计数/顶部统计为全局口径,前端若期望随搜索联动,请另行沟通(当前不联动)。
|
||||
|
||||
回滚方案:回滚后端至本 PR 前版本,`contactName` 入参失效、`orderNo` 回到无效状态、ALL/NONE 不支持搜索。前端入参传了也无副作用(被忽略)。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. `contactName` 搜的是订单「客户名」(联系人),与「申请人 requestedBy」是两个维度,勿混。
|
||||
2. NONE tab 传开票抬头/申请人会得到空列表,属预期——未申请订单尚无发票记录。
|
||||
3. 无命中是空列表 + total=0,不是错误码;前端按空态展示即可。
|
||||
4. 角标计数/统计为全局,搜索后角标数字与列表条数可能不一致,属设计预期。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4383 发票管理列表搜索补齐](https://git.1814.love:8443/wx/HL/issues/4383)
|
||||
- **PR**:[#4384](https://git.1814.love:8443/wx/HL/pulls/4384)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,63 @@
|
||||
# 房务去「询房中」顶层态 — 进度条 6→4 步 + 异常红标 + 我的接单去询房中
|
||||
|
||||
> 变更类型:♻️ 重构(后端,**房务管家前端需配合渲染**)
|
||||
> 端类型:管理后台(房务管家·订单详情进度条 / 我的接单 / 日历)
|
||||
> 日期:2026-06-25 | 工单:#4380 | PR:#4389 | 服务:hl-order-service-v3(双实例已部署 health UP,**测试服 API 实测全通过**)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
房务管家进度条原 6 步(待配房 / 配房中 / **询房中** / 待最终确认 / 已完成 / **异常**)两处口径不对:
|
||||
1. 配房和询房本就**一间间一起干**,把「询房中」单拎成流程节点(还排在配房中之后)不合理。
|
||||
2. 异常是**任意时刻可发生的旁支**,不是「已完成」的下一站,放线性流程条里语义就错。
|
||||
|
||||
本单把房务内部模型拉回与**对外 4 态一致**:询房降级为房间级动作(只在待办露出,订单维持配房中),异常移出线性流程条、改为当前步红标 + 待办/我的接单处置。**修订** #4255/#4257/#4261/#4239「4 步改 6 步拆询房中」口径。
|
||||
|
||||
---
|
||||
|
||||
## 接口 / 响应结构变化(前端需改)
|
||||
|
||||
### 1. 订单详情进度条 `GET /admin/house/orders/{orderId}` → `data.progress`
|
||||
- `currentStep` 取值 **1-6 → 1-4**(待配房→配房中→待最终确认→已完成)。
|
||||
- `steps` 数组 **6 节点 → 4 节点**(去掉「询房中」「异常」两节点)。
|
||||
- `houseStatus` **不再返 `IN_INQUIRY`**(存量数据已迁移成 `CLAIMING`)。剩 5 态:`PENDING_CLAIM`/`CLAIMING`/`PENDING_FINALIZE`/`CONFIRMED`/`EXCEPTION`。
|
||||
- **新增 2 字段**:
|
||||
- `progress.exception`(Boolean):是否处异常态。
|
||||
- `progress.exceptionLabel`(String):异常态返「异常」,否则 null。
|
||||
- 异常**不占线性节点**,前端在当前步(异常态时 currentStep=2 配房中)叠加红色「异常」标。
|
||||
|
||||
### 2. 我的接单 `GET /v3/admin/order/grab-pool/my-claims/hotel`(+ stats)
|
||||
- `stats.inInquiry` **废弃,恒返 0**(向后兼容,前端去掉「询房中」tab)。
|
||||
- `stats.inProgress` 口径 `CLAIMING+IN_INQUIRY` → **= CLAIMING**。
|
||||
- `status` 入参去掉 `inInquiry`(仍传 `inInquiry` 会兼容映射到 `claiming`,不报错)。
|
||||
- **异常筛选 `status=exception` + `stats.exception` 改为「待办驱动并集」口径**:
|
||||
- = `house_status=EXCEPTION`(客户退改/取消转异常)**∪** 有 OPEN 异常类待办(询价超时 / 酒店拒单 / 换酒店 / 退款)的订单。
|
||||
- 即**拒单/超时的订单现在也会进异常筛选**(它们 house_status 维持 CLAIMING、但挂着异常类待办)。与日历异常桶、列表行内 `exceptionCount` 角标**三处同源**。
|
||||
|
||||
### 3. 询房降级为房间级
|
||||
- 详情/日历**不再有「询房中」整团态**。那一天「待回询价」靠该天的 inquiry(PENDING) 记录派生展示。
|
||||
- 日历「询房中」桶语义改房间级(读 PENDING 询房记录,不读 house_status)。
|
||||
|
||||
### 4. 日历「异常」桶 = 待办驱动
|
||||
- 日历异常点**不再等价于 `house_status=EXCEPTION`**,而是「有 OPEN 异常类待办」(含超时/拒单)。这是「异常处置=待办露出」的预期行为。
|
||||
|
||||
### 5. `houseStatus` 全链路不再返 `IN_INQUIRY`
|
||||
- 前端若有硬编码判断 `houseStatus === 'IN_INQUIRY'` 的分支,请清理。
|
||||
|
||||
---
|
||||
|
||||
## 后端行为变化(前端无需改,知悉即可)
|
||||
- 房务发询价 / 酒店回复:**不再改订单态**(维持配房中),询价记录照常落库。
|
||||
- 询价超时(4h) / 酒店拒单:**不再把整单翻异常**,改生成待办(询价超时待办 / 换酒店待办)在待办列表处置,订单维持配房中。顺带治掉「一间房酒店慢回复/拒单连累整单异常」。
|
||||
- 仍会让订单进「异常」的只剩真·整单级:客户退改 / 订单取消 / 超占。
|
||||
- 换酒店不再改 house_status(本就发生在配房中),换完自动 RESOLVE 换酒店待办。
|
||||
|
||||
---
|
||||
|
||||
## 前端待办(房务管家,mmg)
|
||||
1. 订单详情进度条按 **4 步**渲染;异常态在当前步叠**红色异常标**(用新 `progress.exception` / `exceptionLabel`)。
|
||||
2. 我的接单**去掉「询房中」tab**;异常 tab 保留(现含拒单/超时单)。
|
||||
3. 日历 / 详情的「询房中」改**房间级**显示(那天待回询价),不再当整团态。
|
||||
4. 清理硬编码 `IN_INQUIRY` 判断。
|
||||
|
||||
> 注:DB 列 `order_hotel_requirement.house_status` 的 COMMENT 仍写 6 态(Flyway 已冻结不改原文件,仅文字陈旧,不影响逻辑)。
|
||||
@@ -0,0 +1,183 @@
|
||||
# 订单详情「合同/保险」Tab 出参终态(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(出参丰富 + 保险结构重构,**含破坏性**)
|
||||
- 关联 Issue:#4390 #4403 PR:#4391 #4399 #4406
|
||||
- 日期:2026-06-25
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单详情「合同/保险」懒加载 Tab `GET /v3/admin/order/{id}/contract-insurance` 原返回过于单薄(时间线仅 eventType+occurredAt,缺方案名/签署人/产品名/投保人数等),且保险按"代表保单"折叠,多保单(多段方案)丢失各张信息。本次经 3 个 PR 改造到**终态**,一次性说明最终出参。
|
||||
|
||||
> **保险一单多保是设计如此**:一个保险方案含多个分段(segment),自动投保逐段投保,每段建一张保单 → 一单可能 N 张并存。故保险改为 **policies 数组**,每张独立展示。合同仍单对象(一单一合同)。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| GET | `/v3/admin/order/{id}/contract-insurance` | contract 顶层补字段 + 富时间线;**insurance 结构重构为 policies 数组** |
|
||||
|
||||
统一响应 `Result<T>`:`{ code, message, data, success }`,`code=200` 成功。`data = { contract, insurance }`。
|
||||
|
||||
**破坏性提示**:insurance 节点**删除**旧顶层字段 `insurancePolicyNo` / `insurancePremium` / 顶层 `events`,改为聚合层 + `policies[]`(events 移入每张 policy)。前端涉及保险展示**必须改按 policies 数组渲染**。
|
||||
|
||||
---
|
||||
|
||||
## ③ 出参结构(终态)
|
||||
|
||||
```jsonc
|
||||
"data": {
|
||||
"contract": {
|
||||
"contractStatus": "SIGNED", // 合同状态(走 order_main 镜像)GENERATING/GENERATED/SIGNED/VOIDED/RESIGNING;无合同 null
|
||||
"contractSchemeName": "标准跟团方案 v3.2", // 合同方案名
|
||||
"signerName": "张三", // 签署人(isSigner 出行人,无则联系人)
|
||||
"signMethod": "电子签", // 签署方式
|
||||
"contractSignedAt": "2026-04-18 17:10:00", // 签约时间(镜像)
|
||||
"contractFileUrl": "https://oss.../x.pdf", // 合同文件(镜像)
|
||||
"events": [ /* 富时间线,固定5步,见⑤ */ ]
|
||||
},
|
||||
"insurance": {
|
||||
// —— 聚合层(订单维度) ——
|
||||
"insuranceStatus": "INSURED", // 聚合状态(走镜像)INSURING/INSURED/CANCELLED/FAILED;无保险 null
|
||||
"insuranceStatusName": "已出单", // 聚合状态中文
|
||||
"totalPremium": "468.00", // 所有 INSURED 保单保费之和(镜像)
|
||||
"policyCount": 2, // 保单张数
|
||||
// —— 逐张保单 ——
|
||||
"policies": [
|
||||
{
|
||||
"insuranceOrderId": "92000...",
|
||||
"productName": "安联境内旅行险·尊享版",
|
||||
"policyNo": "AL-DEMO-00001",
|
||||
"premium": "384.00",
|
||||
"insuredCount": 4,
|
||||
"coverAmount": "意外身故10万;医疗30万",
|
||||
"policyHolderName": "张三",
|
||||
"status": "INSURED", // 单张状态
|
||||
"statusName": "已出单",
|
||||
"events": [ /* 该张自己的富时间线,固定5步,见⑤ */ ]
|
||||
}
|
||||
// ...多段方案有多张;单段长度=1;无保险 policies=[]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
无变化(仅 path `id`)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 时间线节点(events)字段
|
||||
|
||||
合同/保险 events 均为**固定步骤模板**(合同 5 步、保险 5 步),节点结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| stepCode | string | 步骤码(合同 GENERATE/TEMPLATE/PUSH_SIGN/SIGN/ARCHIVE;保险 TRIGGER/UNDERWRITE/AUDIT/ISSUE/ARCHIVE)|
|
||||
| eventType | string | 兼容旧字段,值同 stepCode(前端用 stepCode 即可)|
|
||||
| eventName | string | 步骤中文名(触发生成合同 / 客户电子签 …)|
|
||||
| actorType | string | 操作方类型 `SYSTEM` / `THIRD_PARTY` / `CUSTOMER` |
|
||||
| actorTypeName | string | 操作方中文名 `系统` / `第三方` / `客户` |
|
||||
| actorLabel | string | 来源标签(订单控制台 / 法大大 e-Sign / 安联保险 / 客户本人(电子签))|
|
||||
| statusLabel | string | 步骤状态 `已完成` / `进行中` / `待处理`(按当前合同/保单状态派生)|
|
||||
| description | string | 描述(插值真实数据,如"模板 标准跟团方案 v3.2"/"保费 ¥384 已扣")|
|
||||
| occurredAt | datetime | 发生时间(里程碑日志填充;该步对应里程碑未发生 → null)|
|
||||
|
||||
**合同 5 步**:触发生成合同(系统) → 生成合同模板(第三方) → 推送签署链接(第三方) → 客户电子签(**客户**) → 回执入库(系统)。
|
||||
**保险 5 步**:触发出保(系统) → 调用承保接口(系统) → 核保·扣保费(第三方) → 出具电子保单(第三方) → 保单入库·推送客户(系统)。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
- 合同状态 contractStatus:GENERATED 生成 / SIGNED 已签 / VOIDED 已作废 / UPLOADED 已上传回执 / RESIGNING 重签中
|
||||
- 保险状态(聚合 insuranceStatus / 单张 status):INSURING 出单中 / INSURED 已出单 / CANCELLED 已取消 / FAILED 出单失败
|
||||
- actorType:SYSTEM 系统 / THIRD_PARTY 第三方 / CUSTOMER 客户
|
||||
- statusLabel:已完成 / 进行中 / 待处理
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无(查询接口正常返 200)。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 多保单(一单 2 张)保险节选
|
||||
```json
|
||||
{ "insurance": {
|
||||
"insuranceStatus":"INSURED","insuranceStatusName":"已出单","totalPremium":"468.00","policyCount":2,
|
||||
"policies":[
|
||||
{ "policyNo":"AL-DEMO-00001","productName":"安联境内旅行险·尊享版","premium":"384.00","insuredCount":4,
|
||||
"status":"INSURED","statusName":"已出单",
|
||||
"events":[
|
||||
{"stepCode":"TRIGGER","eventName":"触发出保","actorType":"SYSTEM","actorTypeName":"系统","actorLabel":"订单控制台","statusLabel":"已完成","description":"按 4 位出行人投保","occurredAt":"2026-04-18 09:30:00"},
|
||||
{"stepCode":"AUDIT","eventName":"核保·扣保费","actorType":"THIRD_PARTY","actorTypeName":"第三方","actorLabel":"安联保险","statusLabel":"已完成","description":"保费 ¥384 已扣","occurredAt":"2026-04-18 09:30:00"},
|
||||
{"stepCode":"ISSUE","eventName":"出具电子保单","actorType":"THIRD_PARTY","actorTypeName":"第三方","actorLabel":"安联保险","statusLabel":"已完成","description":"保单号 AL-DEMO-00001","occurredAt":"2026-04-18 12:00:00"}
|
||||
] },
|
||||
{ "policyNo":"AL-DEMO-00002","productName":"附加意外险","premium":"84.00","status":"INSURED","statusName":"已出单","events":[ /* 该张自己5步 */ ] }
|
||||
] } }
|
||||
```
|
||||
|
||||
### 合同时间线节选
|
||||
```json
|
||||
{ "stepCode":"SIGN","eventName":"客户电子签","actorType":"CUSTOMER","actorTypeName":"客户","actorLabel":"客户本人(电子签)","statusLabel":"已完成","description":"法大大 e-Sign 回传签署回执","occurredAt":"2026-04-18 17:10:00" }
|
||||
```
|
||||
|
||||
### 无合同/无保险
|
||||
`contract` 顶层字段 null + `events:[]`;`insurance` 聚合字段 null + `policies:[]`。
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- events 是后端按固定流程模板合成(非逐条原始日志),时间用真实里程碑填充;中间子步(生成模板/推送链接)时间≈所属里程碑时间。
|
||||
- 单段方案 policies 长度=1;前端统一按数组渲染。
|
||||
- 顶层 totalPremium = 所有 INSURED 保单保费之和;各张 premium 为单张保费。
|
||||
- statusLabel 按当前状态派生:已到达里程碑=已完成,当前=进行中,未到=待处理(occurredAt=null)。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 合同顶层 | 仅 contractStatus/signedAt/fileUrl | + contractSchemeName/signerName/signMethod |
|
||||
| 时间线节点 | eventType + occurredAt | + stepCode/eventName/actorType/actorTypeName/actorLabel/statusLabel/description |
|
||||
| 保险结构 | 单对象(代表保单折叠):insurancePolicyNo/insurancePremium/productName/coverAmount/insuredCount/events 在顶层 | **聚合层(insuranceStatus/insuranceStatusName/totalPremium/policyCount) + policies 数组**(每张独立 + 各自 events)|
|
||||
| 保险多保单 | 只显示一张,丢其余 | 每张独立展示 |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性(保险)**:保险旧顶层 `insurancePolicyNo`/`insurancePremium`/`events` **已删**,改 `policies[]`。前端保险展示**必须**改按数组渲染(取 `insurance.policies[*]`),时间线从 `insurance.events` 改 `insurance.policies[i].events`。
|
||||
- 合同为**非破坏新增**(顶层补字段 + 时间线节点补字段,旧字段保留)。
|
||||
- 前端尚未对接本 Tab,按此终态一次对接即可。
|
||||
- 回滚:后端回滚 PR #4391 #4399 #4406。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额(totalPremium/premium)+ Long ID(insuranceOrderId)均**字符串化**返回(防 JS 精度)。
|
||||
- 时间线节点用 `stepCode` 不用 `eventType`(后者仅兼容)。
|
||||
- policyHolderName/signerName 为投保人/签署人真实姓名(管理后台财务/客服核对保单用)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4390 ・ https://git.1814.love:8443/wx/HL/issues/4403
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4391 ・ https://git.1814.love:8443/wx/HL/pulls/4399 ・ https://git.1814.love:8443/wx/HL/pulls/4406
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(合同 5 步富时间线 + 保险多保单 policies 数组各张独立时间线均生效)。
|
||||
@@ -0,0 +1,202 @@
|
||||
# 发票推送明细日志(push-logs 接口 + 详情 pushLogs 数组)(管理后台)
|
||||
|
||||
- **接口**:GET /v3/admin/order/invoice/{id}/push-logs(新增) + GET /v3/admin/order/invoice/{id}(详情出参新增 pushLogs)
|
||||
- **变更类型**:新增接口 + 修改接口(详情出参新增数组字段)
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4395](https://git.1814.love:8443/wx/HL/issues/4395)
|
||||
- **PR**:[#4397](https://git.1814.love:8443/wx/HL/pulls/4397)
|
||||
|
||||
发票「推送客户」由原来只记最后一次,升级为每渠道每次一条明细记录(含操作人/时间/渠道/状态)。新增推送历史查询接口,发票详情出参新增 `pushLogs` 数组。
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
发票推送(PUT /v3/admin/order/invoice/{id}/push)原仅在发票行覆盖记最后一次推送的时间/渠道,无操作人、无多次历史、无逐渠道记录,财务无法回查「是否给客户推送过、推过几次、哪个渠道、谁推的」。
|
||||
|
||||
本次新增独立推送明细:每次推送按渠道逐条落库(一次推 email+sms = 2 条),并提供两个查询入口——独立的推送历史列表接口,以及发票详情出参内联 `pushLogs` 数组。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 说明 |
|
||||
|---|--------|------|
|
||||
| 1 | 新增接口 GET /{id}/push-logs | 查发票推送明细历史(每渠道每次一条,按推送时间倒序) |
|
||||
| 2 | 详情出参新增 pushLogs | GET /{id} 响应新增推送明细数组,同一结构 |
|
||||
| 3 | 推送行为 | 每次推送按渠道逐条落明细(含操作人/时间/渠道/状态),多次推送累积不覆盖 |
|
||||
|
||||
> 推送提交接口(PUT /{id}/push)入参/出参/错误码本次不变。
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
### 3.1 推送明细列表(新增)
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/invoice/{id}/push-logs |
|
||||
| 描述 | 查询某发票的推送明细历史,每渠道每次一条,按推送时间倒序 |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
|
||||
### 3.2 发票详情(出参新增 pushLogs)
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | GET |
|
||||
| 路径 | /v3/admin/order/invoice/{id} |
|
||||
| 描述 | 发票详情,本次出参新增 `pushLogs` 数组(结构同 3.1 列表项) |
|
||||
| 认证 | Bearer JWT(管理员) |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 push-logs 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | string(Long 雪花) | 是 | 发票记录 ID |
|
||||
|
||||
无请求体、无 Query 参数。
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
### 5.1 push-logs 响应(`Result<List<InvoicePushLogRespVO>>`)
|
||||
|
||||
data 为推送明细数组,每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| channel | string | 推送渠道(单值):email / wechat / sms |
|
||||
| pushStatus | string | 推送状态:SUCCESS / FAILED / PENDING |
|
||||
| statusText | string | 推送状态文案:成功 / 失败 / 待发 |
|
||||
| pushedBy | string | 操作人真实姓名 |
|
||||
| pushedAt | string(datetime) | 推送时间 |
|
||||
| failReason | string | 失败原因(预留,SUCCESS 时为 null) |
|
||||
|
||||
> 列表按 pushedAt 倒序。一次推送多渠道 → 每渠道一条;多次推送累积,不覆盖。
|
||||
|
||||
### 5.2 发票详情新增字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| pushLogs | array | 推送明细数组,元素结构同 5.1,按 pushedAt 倒序 |
|
||||
|
||||
> 详情其余字段不变。
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 推送状态(pushStatus)
|
||||
|
||||
| code | 文案 | 说明 |
|
||||
|------|------|------|
|
||||
| SUCCESS | 成功 | 当前所有推送记录均为此值 |
|
||||
| FAILED | 失败 | 预留(接真实通知网关后启用) |
|
||||
| PENDING | 待发 | 预留 |
|
||||
|
||||
### 6.2 推送渠道(channel)
|
||||
|
||||
单值,取自数据字典 `invoice_push_channel`(email / wechat / sms)。前端渠道文案可用该字典 dictLabel 映射。
|
||||
|
||||
> 说明:本期推送不对接真实邮件/微信/短信网关,仅登记推送动作,pushStatus 恒为 SUCCESS。
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581501 | INVOICE_NOT_FOUND | id 对应发票不存在 |
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——查推送明细
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/2070039024382193666/push-logs
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
响应(一次推 email+sms,得 2 条)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{"channel":"email","pushStatus":"SUCCESS","statusText":"成功","pushedBy":"admin","pushedAt":"2026-06-25 15:15:47","failReason":null},
|
||||
{"channel":"sms","pushStatus":"SUCCESS","statusText":"成功","pushedBy":"admin","pushedAt":"2026-06-25 15:15:47","failReason":null}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界——多次推送累积
|
||||
|
||||
先推 [email,sms] 再推 [wechat],push-logs 返回 3 条(wechat / email / sms,按时间倒序),历史不覆盖。
|
||||
|
||||
### 8.3 业务失败——发票不存在
|
||||
|
||||
请求
|
||||
```
|
||||
GET /v3/admin/order/invoice/9999999999/push-logs
|
||||
```
|
||||
响应
|
||||
```json
|
||||
{"code": 581501, "msg": "发票不存在", "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
- push-logs 返回该发票全部推送明细,按推送时间倒序,无分页(单发票推送次数有限)。
|
||||
- 一次推送多渠道 → 每渠道一条;多次推送累积,不覆盖。
|
||||
- 发票从未推送过 → 返回空数组。
|
||||
- 本期 pushStatus 恒 SUCCESS(不真发通知,仅登记动作)。
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
| 维度 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 推送记录 | 仅发票行覆盖记最后一次时间/渠道 | 独立明细,每渠道每次一条,累积历史 |
|
||||
| 操作人 | 不记录 | pushedBy 记录操作人真实姓名 |
|
||||
| 查询入口 | 无 | 新增 push-logs 接口 + 详情 pushLogs 数组 |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:否(纯新增接口 + 详情新增可选数组字段)
|
||||
|
||||
- 前端可在发票详情/推送弹窗展示推送历史(渠道/操作人/时间/状态)。
|
||||
- 发票详情原有字段不变,新增 pushLogs 数组,老前端忽略该字段不受影响。
|
||||
|
||||
回滚方案:回滚后端至本 PR 前版本,push-logs 接口下线、详情不再返回 pushLogs。`invoice_push_log` 表保留不影响其它功能。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. channel 是单值(一条记录一个渠道),一次多渠道推送对应多条记录。
|
||||
2. pushStatus 本期恒 SUCCESS,FAILED/PENDING 为预留状态(接真网关后启用)。
|
||||
3. 渠道文案前端用数据字典 `invoice_push_channel` 的 dictLabel 映射(email→邮件等)。
|
||||
4. push-logs 无分页,按推送时间倒序返回全部。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4395 发票推送明细日志表 invoice_push_log + 每渠道事件解耦](https://git.1814.love:8443/wx/HL/issues/4395)
|
||||
- **PR**:[#4397](https://git.1814.love:8443/wx/HL/pulls/4397)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,45 @@
|
||||
# 配房统一换酒店写口 — 去「换酒店」按钮 + 回显已配酒店 + 根治配房重提超卖
|
||||
|
||||
> 变更类型:♻️ 重构(后端,**房务管家前端需配合:去换酒店按钮 + 候选回显**)
|
||||
> 端类型:管理后台(房务管家·配房/换酒店)
|
||||
> 日期:2026-06-25 | 工单:#4401 | PR:#4407(在 #4402 基础上重写)| 服务:hl-order-service-v3(双实例已部署 health UP,**测试服 API+DB 实测全通过**)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
「配房」与「换酒店」本质同一功能(同城同日选酒店+房型)。原两个按钮/两条后端路径,且换酒店候选漏传 roomTypeId 导致提交报 `808403 新酒店库存扣减失败`。本单把换酒店**并入配房保存唯一写口**,去换酒店按钮,并根治「配房重提同天换酒店→新店不扣(超卖)/旧店不释放(泄漏)」潜伏 P0。
|
||||
|
||||
---
|
||||
|
||||
## 前端必改(房务管家,mmg)
|
||||
|
||||
### 1. 去掉「换酒店」按钮,改房统一走「配房」
|
||||
- **换酒店 3 个端点已删除**,前端停止调用:
|
||||
- `GET /v3/admin/order/swap-hotel/candidates`(删)
|
||||
- `POST /v3/admin/order/swap-hotel/preview`(删)
|
||||
- `POST /v3/admin/order/swap-hotel/commit`(删)
|
||||
- **改房统一走配房保存**:`POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`(§2.2,原配房保存接口)。给某天提交一个新的酒店/房型 item 即完成「换酒店」——后端自动识别:同酒店同房型同间数=幂等不动;酒店/房型/间数变=替换(释放旧店库存+扣新店+软删旧行+保留 swap 血缘+发换店通知)。**一个 item 一个 (天,酒店,房型),同天同三元组不要重复提交**(后端 808117 拒,请合并间数)。
|
||||
|
||||
### 2. 配房候选回显「本天当前已配酒店」(用户要求)
|
||||
`GET /v3/admin/hotel-candidates?orderId=&dayNumber=` 候选项 `candidates[]` **新增 2 字段**:
|
||||
- `isCurrentlyAssigned`(Boolean):该候选是否=本天当前已配酒店。前端据此**高亮/默认选中**原酒店。
|
||||
- `assignedRoomTypeId`(String 雪花):本天已配的房型 ID,供**预填原房型下拉**。
|
||||
- 已配酒店**保证出现在候选结果中**(即使排序靠后也强制保留,不被 limit 截断)。
|
||||
- 候选项本就含 `tags`(运营标签)、`roomTypes[]`(roomTypeId/房型/协议价/可用数)——换酒店选房直接复用配房候选这套,标签照常展示。
|
||||
|
||||
### 3. 订单详情 currentAssignment 补 roomTypeId
|
||||
`GET /admin/house/orders/{orderId}` → `itinerary[].currentAssignment` **新增 `roomTypeId`**(String 雪花),供详情侧精确回显/预填原房型。
|
||||
|
||||
---
|
||||
|
||||
## 后端行为(前端知悉即可)
|
||||
- 配房保存按天对账:某天换酒店时先释放旧酒店库存、再用隔离幂等键扣新酒店(根治同天多房型/换店复用键短路不扣的超卖),软删旧行后新行 `swap_from_id` 指向旧行。
|
||||
- 换酒店(hotelId 真变)仍发 3 条通知(旧店/新店/客户,走通知中心)+ 写 SWAP_SUCCESS 审计 + 自动归档 SWAP_HOTEL 待办。
|
||||
- 原 `808403` 不再复现(配房候选自带 roomTypeId)。
|
||||
|
||||
---
|
||||
|
||||
## 测试服实测(已通过)
|
||||
- 回显:候选 day2 正确标已配酒店 `isCurrentlyAssigned=true`+`assignedRoomTypeId`;详情 currentAssignment 返 roomTypeId。
|
||||
- 换酒店:配房保存换某天酒店 → 旧行软删+库存释放、新行 swap 血缘、每天仅 1 active、新旧幂等键隔离真扣。
|
||||
- 多房型同天:一次提交 2 房型 → 各自独立扣减(键不同、不短路)。
|
||||
@@ -0,0 +1,51 @@
|
||||
# 询房弹窗改造 — 酒店候选 list + 真实房型联动 + 联系人资源带出只读 + 正文预览 + 回填可改协议价/库存
|
||||
|
||||
> 变更类型:✨ 功能增强(后端,**房务管家前端需配合改「发起询房」+「回填」弹窗**)
|
||||
> 端类型:管理后台(房务管家·发起询房 / 回填询房)
|
||||
> 日期:2026-06-25 | 工单:#4404 + #4405 | PR:#4408 + #4409 | 服务:hl-order-service-v3 + hl-resource-service(双实例已部署 health UP)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
房务「发起询房」弹窗 + 「回填」共 5 处改造:酒店从下拉改候选 list(全部在售可选)、房型按所选酒店真实房型联动、联系人/微信从酒店资源自动带出且只读、询房正文支持发起前预览、回填时可直接改该房型当日协议价/库存并写回 resource 价格日历。
|
||||
|
||||
---
|
||||
|
||||
## 前端必改(房务管家,mmg)
|
||||
|
||||
### 1. 发起询房弹窗:酒店栏「下拉」改「候选 list」
|
||||
- 数据源改调 `GET /v3/admin/hotel-candidates`(传 orderId/requirementId/dayNumber/stayDate,CUSTOM 不传 city)→ 列**全部在售酒店**(池内/定制师点名带徽章排前),替换原「候选池摘要(2 家)」。
|
||||
- 候选项 `candidates[]` **本次新增 5 字段**:`contactPerson`(联系人)、`contactWechat`(联系人微信)、`settleType`(结算类型)、`paymentMode`(结算模式)、`hotelType`(住宿形态)。
|
||||
|
||||
### 2. 房型下拉:用候选项 roomTypes 联动
|
||||
- 选定酒店后,房型下拉用该候选项的 `roomTypes[]`(`roomTypeId`/`name`/协议价/可用数)生成 = 该酒店**真实房型**,替换原 `room_category` 字典固定枚举。
|
||||
|
||||
### 3. 联系人/微信:只读展示(资源带出)
|
||||
- 弹窗「联系人」「联系人微信」两栏改**只读**,值从选中候选项的 `contactPerson`/`contactWechat` 回显(resource 单源)。
|
||||
- 发起询房 `POST /v3/admin/order/inquiry/send` 的 `contactName`/`contactWechat` 入参**已废弃**(后端按 hotelId 从 resource 取,前端传了也忽略),无需再传。
|
||||
|
||||
### 4. 询房正文:发起前预览(新端点)
|
||||
- 新增 `POST /v3/admin/order/inquiry/preview`,body `{hotelId, stayDate, nights, roomCount, roomCategory, messageBody(选填)}`。
|
||||
- 返回 `{messageBody(渲染后最终文案), contactName, contactWechat}`——发起前预览将发出去的文案(messageBody 留空走系统模板),所见即所发;联系人同样 resource 带出只读。
|
||||
|
||||
### 5. 回填弹窗:可改协议价/库存写回酒店日历
|
||||
- `POST /v3/admin/order/inquiry/{inquiryId}/reply` **新增 4 个可选字段**:
|
||||
- `roomTypeId`:改哪个房型(从候选 roomTypes 带入)
|
||||
- `newProtocolPrice`:新协议价(选填)
|
||||
- `newStock`:新库存(选填,当日基准库存 stock)
|
||||
- `syncToCalendar`:是否写回酒店价格日历(默认 false)
|
||||
- 房务勾选「写回」(`syncToCalendar=true`)+ 填新协议价/新库存 → 后端写回 resource 价格日历(单源);**不勾或不填则只记本次回填、不碰酒店主数据**。
|
||||
|
||||
---
|
||||
|
||||
## 后端行为(前端知悉即可)
|
||||
- 联系人/微信单一真相是 resource 酒店主数据:候选 list、send、preview 都从这里取。测试库当前 wechat 列暂无数据 → contactWechat 多为空,**酒店资料维护了微信后即自动带出**。
|
||||
- 回填写回幂等(绝对值覆盖);写回失败只记日志、不阻断回填主流程;写回操作有审计日志(改动人/内容)。
|
||||
- preview / send 共用同一套渲染逻辑(所见即所发)。
|
||||
|
||||
---
|
||||
|
||||
## 测试服实测
|
||||
- 发起侧(#4404,已通过):候选 `contactPerson` 真值带出(嘉世豪前台/王经理/成悦前台等);preview 留空走系统模板+联系人插入、传入原样回显,核 DB 一致。
|
||||
- 回填侧(#4405,已通过):正例 `syncToCalendar=true` + newProtocolPrice=333/newStock=9 → resource 价格日历 protocol_price 320→**333**、stock 20→**9**(stock_used/status 不变);反例 `syncToCalendar=false`(即使带新值)→ 价/库存不变(开关生效)。核 DB 一致。
|
||||
- 提示:回填 `roomTypeId` 请务必从所选酒店候选的 `roomTypes` 取(须为该酒店真实房型),后端将校验房型归属该酒店。
|
||||
@@ -0,0 +1,67 @@
|
||||
# 房务「待办 + 日历」模块逻辑修复 + 不限库存读路径修复 — 前端需重新对接
|
||||
|
||||
> 变更类型:🐛 逻辑修复(后端,**房务管家前端需对照重新对接待办/日历/库存展示**)
|
||||
> 端类型:管理后台(房务管家·待办看板 / 日历视图 / 配房候选)
|
||||
> 日期:2026-06-25 | 工单:#4411 + #4413 | PR:#4412 + #4414 | 服务:hl-order-service-v3 + hl-resource-service(双实例已部署 health UP,**测试服 API+DB 实测通过**)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
34-agent 深度逻辑审计 + 17-agent 跨 PR 集成检验,确认并修复房务待办、日历、库存读路径一批逻辑 bug。前端需对照以下变更重新对接。
|
||||
|
||||
---
|
||||
|
||||
## 一、待办看板(GET /v3/admin/order/todos)
|
||||
|
||||
### 1. 排序已修正(后端自动,前端无需改但行为变)
|
||||
- 修复前:列表按 urgency 字符串降序,导致 **danger(最紧急:换酒店/退订/加急)排在最底**、warn 反而置顶(字典序 bug)。
|
||||
- 修复后:后端按权重 **danger > warn > normal**(同级按创建时间倒序)排序,最紧急的恒置顶。前端直接按返回顺序渲染即可。
|
||||
|
||||
### 2. stats 分类计数 key 改为枚举常量(**前端必改**)
|
||||
- stats 各类计数的 JSON key 由 camelCase(`swapHotel`)改为**枚举常量**(`SWAP_HOTEL`/`REFUND`/`INQUIRY_TIMEOUT`/`INQUIRY_ESCALATED`/`INVENTORY_CHECK_OVERDUE`),与 list 行的 `todoType`、筛选入参口径统一。前端读 stats 的 key 要改。
|
||||
|
||||
### 3. stats 随筛选收窄(行为变)
|
||||
- stats 现**跟随 scope(mine/others)与结构性筛选收窄**:切「我的」只数你负责的、切「同事」数同事的。(不随 Tab 选择/urgency/keyword 收窄,避免切 Tab 把其余红点打 0。)
|
||||
|
||||
### 4. urgency 三层智能升级(后端推导,前端展示颜色)
|
||||
- urgency 现由后端三层推导:基线 + **待办挂久自动升级**(挂满 2h→warn、4h→danger)+ **临近出行升红**(距出行 ≤24h→danger、≤7天→warn)。前端按返回的 urgency 渲染红/黄/灰即可。
|
||||
|
||||
### 5. UNDELIVERED「未送达」类型下线
|
||||
- 该类型无生产者、恒 0,已从 stats/异常集合移除。前端**别再展示「未送达」分类**。
|
||||
|
||||
---
|
||||
|
||||
## 二、日历视图(GET /admin/house/calendar?month=yyyy-MM)
|
||||
|
||||
### 6. 已配房按「每个入住晚」落点 + 下钻传 stayDate(**前端必改**)
|
||||
- 多晚订单现在月历上**每个入住晚都有一个点**(住 3 晚 → 3 天各一点)。
|
||||
- 点某天下钻列表时,**请带上该天的 `stayDate` 参数**(下钻接口 `GET /v3/admin/order/grab-pool/my-claims/hotel` 已支持 `stayDate` 可选过滤)——否则多晚订单点中间晚会跳转出空列表(这是本次修的 bug)。
|
||||
|
||||
### 7. 「询房中」桶补显示已抢未配的订单
|
||||
- 已抢单、还没配房、但已发了询房(PENDING)的订单,现在也会出现在日历「询房中」桶(之前完全不显示)。
|
||||
|
||||
### 8. 异常桶口径对齐
|
||||
- 日历异常桶现并入 `house_status=EXCEPTION`,与「我的接单」异常计数口径一致。
|
||||
|
||||
---
|
||||
|
||||
## 三、不限库存展示(配房候选 / 库存日历,**前端必改**)
|
||||
|
||||
### 9. 不限库存房型新增 `unlimited` 字段
|
||||
- 房务把某房型某日设为「不限库存」(stock=NULL)后,配房候选 `GET /v3/admin/hotel-candidates` 的 `roomTypes[]` 与库存日历,该房型现返回 **`unlimited=true`(此时 `available=null`、`inventoryStatus=AVAILABLE`)**。
|
||||
- 修复前:不限库存被错当成 `available=0/FULL`,房务在候选弹窗看到「满房/不可选」——明明永远可订却选不出来。现已修正。
|
||||
- **前端识别 `unlimited=true` 时展示「不限」**(而非数字 0 或 null)。
|
||||
- 酒店卡级 `matchedRoomTypeAvailable` 在 matched 房型不限时为占位值 `99`(仅排序用),前端展示**以 room 项的 `unlimited` 为准**。
|
||||
|
||||
---
|
||||
|
||||
## 四、后端行为(前端知悉即可,无需对接)
|
||||
- 待办 RESOLVE 闭环补全(退订/换酒店/异常处置完成/重发询房自动解,列表不再积压幽灵待办);转单/释放后待办归属自动迁移给新房务。
|
||||
- 配房「同步协议价」开关新增 roomTypeId 归属校验(防越权改别家酒店协议价);CONFIRMED/EXCEPTION 需求不再被 submit 误改/误翻态。
|
||||
|
||||
---
|
||||
|
||||
## 测试服实测(已通过)
|
||||
- 不限库存:嘉世豪豪华大床房 stock=NULL → `unlimited=true/available=null/AVAILABLE`,对照 stock=20 房型正常。
|
||||
- 越权:配房同步协议价传别家酒店房型 → 808112 拒。
|
||||
- 待办/日历接口 200 渲染正常;Flyway 迁移 success=1。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 酒店价格日历「设置价格」弹窗(① 协议价回显·前端 ② 库存可空=不限·前后端就位)
|
||||
|
||||
> 变更类型:① 📖 前端 bug(后端已返协议价)② 📖 前端 + ⚙️ 后端已就位(库存硬扣已开)
|
||||
> 端类型:管理后台(资源·酒店·房型价格日历「设置价格」弹窗)
|
||||
> 日期:2026-06-25 | 工单/PR:无(前端 UX/配置,走 changelog)| 服务:hl-resource-service
|
||||
> 关联:配房硬扣库存验收时发现。本日已开启 `hotel.stock.enabled=true`(测试服),配房真实扣减/释放已生效。
|
||||
|
||||
---
|
||||
|
||||
## ① 协议价没回显(前端 bug,后端已返)
|
||||
|
||||
**现象**:点已有价格的日期打开「设置价格」弹窗,协议价框是空的「请输入」,没带出该日已存在的协议价。
|
||||
|
||||
**根因(API 实证)**:`GET /admin/hotel/room-type/{roomTypeId}/prices?year&month` **已返回每日协议价**。实测 海拉尔海棠·精品标间 2026-06-26 返回 `protocolPrice = 330.00`、`stock=10`、`stockUsed=0`、`status=AVAILABLE`、顶层 `stockEnabled=true`。是前端没把已查到的值预填进弹窗。
|
||||
|
||||
**前端动作**:打开「设置价格」弹窗时,从已加载的日历数据 `prices[]` 里取该日期那条 `PriceCalendarDayVO`,用其 `protocolPrice` 预填「协议价」输入框(同样可预填 `stock`/`status`/`remark`)。
|
||||
|
||||
> ⚠️ **wx 2026-06-25 复测:协议价仍未回显**——后端再次确认 `getPriceCalendar` 返了 `protocolPrice`(实测海拉尔海棠 2026-06-26 返 330.00),请前端务必把弹窗打开时的预填补上。
|
||||
|
||||
## ② 库存可留空 = 不限制库存(后端已就位,前端放开必填 + null 展示)
|
||||
|
||||
**诉求**:库存字段允许留空(null),语义 = 该房型该日**不限库存**(不卡上限,永远可配)。
|
||||
|
||||
**后端现状(已就位,无需改)**:
|
||||
- 表 `room_price_calendar.stock` 本就 `DEFAULT NULL COMMENT 'NULL=unlimited'`;请求 DTO `PriceCalendarSetRequest.stock` 无 `@NotNull`(只 `@Min(0)`,允许 null)。
|
||||
- 扣减 SQL `(stock IS NULL OR stock - stock_used >= ?)` —— stock=null 时跳过上限校验只累加 `stock_used`;`isAvailable` 在 stock=null 时直接返可用。
|
||||
- 库存硬扣总开关 `hotel.stock.enabled` 已于本日设为 **true**(原默认 false=不扣),配房真实扣减/释放已生效(实测配 1 间 available 20→19、删配房 19→20 还原)。
|
||||
|
||||
**前端动作**:
|
||||
- 「库存」输入框设为**可选/可留空**(去掉必填)。
|
||||
- 留空提交时**不传 stock 字段或传 null**(= 该日不限库存)。
|
||||
- 回显时若 `stock=null`,显示为「不限制」或留空(别显示 0)。
|
||||
- 可用顶层 `stockEnabled` 决定是否展示库存相关交互(false 时库存管控关闭)。
|
||||
|
||||
**后端缺口已修并验证(工单 #4392)**:`batchSetPrices` 原对**已有记录**传 `stock=null` 是「保留原值」、改不回不限。后端已加 `unlimitedStock` Boolean——前端把已设库存的日期改回「不限」时传 `unlimitedStock=true`(后端即显式 `stock=null`);老逻辑不传该字段行为不变。**✅ 已部署测试服 + live 实测通过,最终契约见同目录 `26_价格日历_库存改回不限_写侧unlimitedStock最终契约_前端_管理后台.md`**。
|
||||
|
||||
## ③ 配房后库存「看着没变」——其实减的是占用/余量,不是总库存(前端补显示)
|
||||
|
||||
**现象(wx 复测)**:配了 1 间房后,价格日历「设置价格」弹窗里「库存」还是 10,以为没扣。
|
||||
|
||||
**真相(API + DB 实证)**:配房硬扣减的是**占用量**,不是总容量——
|
||||
- `stock`(总库存/容量)= 10:在弹窗里设的总量,**配房不改它**(酒店有 10 间,卖 1 间总数还是 10)。
|
||||
- `stockUsed`(已占用)配房后 **0 → 1**;`available`(可用余量)= `stock - stockUsed` = **10 → 9**。
|
||||
- 实证 海拉尔海棠·标准间 2026-06-30 配 1 间:DB `stock_used=1`、候选接口 `available=9`、扣减日志 `resource_success=1`。
|
||||
|
||||
**前端动作**:`GET /admin/hotel-candidates` 候选列表「余 N / 可用房」列已是 `available`(配房后实时,现显示余 9)——这是看占用的地方。价格日历「设置价格」弹窗只显示总量 `stock` 易误解,**建议弹窗里同时显示「已占用 `stockUsed` · 余 `stock-stockUsed`」**(`getPriceCalendar` 的 `PriceCalendarDayVO` 已返 `stock`+`stockUsed`,数据现成,无需后端改),省得每次误以为没扣。
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
- ① 后端无改动(GET 早返 `protocolPrice`),纯前端预填;**复测仍未生效,优先落地**。
|
||||
- ② 后端扣减/约束/schema 已就位 + 硬扣开关已开;前端放开库存必填 + null 展示「不限」;「已有记录切回不限」缺口后端 #4392 修复中(传 `unlimitedStock=true`)。
|
||||
- ③ 配房扣的是 `stockUsed`/`available`(非总 `stock`),前端弹窗补显占用/余即不再误解;后端 `getPriceCalendar` 已返这两个字段,无需改。
|
||||
@@ -0,0 +1,63 @@
|
||||
# 站内信会话化改造 · 前端待办总览(管理后台)
|
||||
|
||||
- **类型**:前端对接总览(**后端全部就绪**,本文汇总 mmg 一次接完)
|
||||
- **端类型**:管理后台(hl-ui)
|
||||
- **日期**:2026-06-25
|
||||
- **后端联系人**:王骁(wx)
|
||||
|
||||
> 站内信升级聊天后,wx 提了一连串改造(按会话聚合 / 团号 / 订单简介条 / 跳转 / 铃铛红点)。**后端侧已全部实现并测试服实测通过**,剩下的都是前端接入。本文是总索引,每项指向已写好的详细 spec。
|
||||
|
||||
---
|
||||
|
||||
## 待办清单(后端均已就绪,按此接入)
|
||||
|
||||
### 1. 「订单消息」改按会话聚合(一会话一行)
|
||||
- **做什么**:站内信「订单消息」tab 改为一会话一行(对方+最新消息+未读+时间),点开看整段对话。不要再逐条铺消息。
|
||||
- **后端接口(现成)**:`GET /admin/message/chat/conversations?bizModule=HOUSE&pageNo&pageSize`,返回每会话一行。
|
||||
- **为什么**:逐条混排只显示「你收到的那一半」+ 释放转单致归属变动,数据不全;按会话聚合才完整。
|
||||
- **详见**:`25_站内信订单消息改按会话聚合+团号+跳转_前端对接` §2/§3。
|
||||
|
||||
### 2. 标题加团号
|
||||
- **做什么**:每行标题 = 团号 `orderNo`(建议拼客户名 `customerName`),如 `HL20260625134754752 · 库凡巧`。
|
||||
- **字段**:会话 VO 的 `orderNo` / `customerName`(已有)。
|
||||
- **注意**:`orderNo` 为 null 是**订单被删/不存在**(软依赖降级),不是 bug;真实订单都有团号。
|
||||
|
||||
### 3. 订单简介条(打开对话顶部)
|
||||
- **做什么**:从会话列表打开对话时,对话顶部「订单简介条」也要显示 = **产品名 · 客户·人数 · 团号**(此前仅从订单页进才有)。
|
||||
- **字段(PR #4394 新增,已上线)**:`productName`(产品名)+ `customerName`(客户)+ `adultCount`/`childCount`(拼「2大1小」)+ `orderNo`(团号)——全在会话列表 VO 里,打开对话直接用,无需再查订单页。
|
||||
- **详见**:`25_站内信订单消息改按会话聚合+团号+跳转_前端对接` §6。
|
||||
|
||||
### 4. 每行跳转按钮(按你在该会话的身份)
|
||||
- **做什么**:每行加跳转按钮,**按 peerRole 反推你是哪一方**(不能用登录账号全局角色,超管同时是两边人):
|
||||
- 对方是房务/车务(`peerRole=HOUSE/FLEET`)→ 你是定制师 → **订单详情** `/order-v2/detail/{bizId}`
|
||||
- 对方是定制师(`peerRole=CUSTOMIZER`)+ `bizModule=HOUSE` → 你是房务 → **配房页** `/housekeeper/orders?orderId={bizId}&requirementId={requirementId}`(用 `requirementId` 自动开配房详情)
|
||||
- 对方是定制师 + `bizModule=FLEET` → 你是车务 → **配车页**(路由 best-effort,待车务模块核对)
|
||||
- **字段**:`peerRole` / `bizModule` / `bizId` / `requirementId`(PR #4394 新增,房务自动开详情用)——全在会话 VO。
|
||||
- **详见**:`25_站内信订单消息改按会话聚合+团号+跳转_前端对接` §3.3。
|
||||
|
||||
### 5. 顶部铃铛未读红点
|
||||
- **做什么**:收到消息时收件方铃铛要亮红点/数字。
|
||||
- 每个带铃铛的页面**加载时调** `GET /admin/message/unread-count` 取初值。
|
||||
- 订阅 SSE 流(`/ws/admin-msg/stream`),在 `im-chat`/`message`/`unread-count` 事件里读 `payload.unreadCount` 实时刷角标。`im-chat` 既驱动聊天 UI 也驱动铃铛——别只 append 聊天。
|
||||
- **后端已就绪**:未读已落库、聊天发送 afterCommit 已推带 `unreadCount` 的 SSE 信令、`unread-count` 接口返真值(实测 countUnread=1 但前端铃铛没亮 = 前端没接)。
|
||||
- **详见**:`25_站内信顶部铃铛未读角标接入_前端对接`。
|
||||
|
||||
### 6. tab 归属 / 全部 tab(前端 IA 自定)
|
||||
- 订单消息 = 会话列表(项1);普通消息 = `GET /admin/message/list?messageType=NORMAL`(仅系统通知);全部 = 前端决定(会话列表+系统通知合并 或 仅通知)。
|
||||
- **详见**:`25_站内信订单消息改按会话聚合+团号+跳转_前端对接` §4。
|
||||
|
||||
---
|
||||
|
||||
## 后端就绪情况一览(无需前端等后端)
|
||||
|
||||
| 能力 | 后端状态 | 接口/字段 |
|
||||
|------|---------|-----------|
|
||||
| 会话列表(按会话聚合) | ✅ 现成 | `/admin/message/chat/conversations` |
|
||||
| 团号/客户 | ✅ | `orderNo` / `customerName` |
|
||||
| 订单简介条(产品名/人数) | ✅ PR #4394 上线 | `productName` / `adultCount` / `childCount` |
|
||||
| 房务跳转自动开详情 | ✅ PR #4394 上线 | `requirementId` |
|
||||
| 跳转判向 | ✅ | `peerRole` / `bizModule` / `bizId` |
|
||||
| 铃铛未读(落库+信令+接口) | ✅ | `unread-count` + SSE `im-chat`(payload.unreadCount) |
|
||||
| 聊天线程/发送/已读 | ✅ 既有 | `/admin/message/chat/messages` 等 |
|
||||
|
||||
> 一句话:**后端全齐了,这几项卡的都是前端接入**。逐项 spec 见上面引用的 changelog。有疑问找王骁(wx)对字段/信令。
|
||||
@@ -0,0 +1,124 @@
|
||||
# 站内信「订单消息」改为按会话聚合 + 团号标题 + 跳转按钮(前端对接 spec · 管理后台)
|
||||
|
||||
- **类型**:前端改造对接说明(**后端零改动**,复用现成接口)
|
||||
- **端类型**:管理后台(hl-ui)
|
||||
- **日期**:2026-06-25
|
||||
- **后端联系人**:王骁(wx)
|
||||
- **关联**:站内信收件箱混排聊天(#4382 / #4386)的后续优化;INAPP-CHAT v1.0 §1.2 会话列表
|
||||
|
||||
> ⚠️ 本文是给前端(mmg)的对接 spec,不是后端变更。后端接口与字段**全部现成**,无需改后端。
|
||||
|
||||
---
|
||||
|
||||
## 1 背景 / 为什么改
|
||||
|
||||
#4382 把聊天混排进站内信「订单消息」是**逐条消息**模型,实测发现它注定数据不全:
|
||||
- 站内信收件箱本质是「一条消息只在收件方名下存一行」,所以只显示**你收到的那一半**(你自己发的不进你自己收件箱);
|
||||
- 释放/转单会把整段对话归属转移,导致旧参与方收件箱里看不到。
|
||||
|
||||
结论:聊天应按**会话**展示,不是按单条消息。故站内信「订单消息」改为 **一会话一行**(像聊天软件的会话列表),点开看整段对话。
|
||||
|
||||
---
|
||||
|
||||
## 2 用现成接口(后端零改动)
|
||||
|
||||
### 2.1 会话列表接口
|
||||
|
||||
```
|
||||
GET /admin/message/chat/conversations?bizModule=HOUSE&pageNo=1&pageSize=20
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
|
||||
- `bizModule`:`HOUSE`(配房会话)/ `FLEET`(配车会话,车务接入后);不传=当前员工全部会话。「订单消息」tab 取订单相关会话,可传 `HOUSE`(车务上线后再并 `FLEET`,或不传取全部业务会话)。
|
||||
- 分页:`pageNo`/`pageSize`;按 `lastMessageAt` 倒序。
|
||||
- 数据按收件人隔离(每人只看自己参与的会话),无需额外权限处理。
|
||||
|
||||
### 2.2 返回字段(ChatConversationRespVO,均已存在)
|
||||
|
||||
| 字段 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| `orderNo` | String | **团号(订单号)→ 放标题**。仅 HOUSE 会话有;order-v3 不可达/非 HOUSE 时为 null |
|
||||
| `bizId` | Long | **订单ID → 跳转用**(配房页/需求页都用它定位订单) |
|
||||
| `bizModule` | String | `HOUSE`/`FLEET`,判跳配房页还是配车页 |
|
||||
| `peerName` | String | 对方姓名(如「王骁」) |
|
||||
| `peerRole` | String | 对方角色码 `HOUSE`/`CUSTOMIZER`/`FLEET` |
|
||||
| `peerRoleLabel` | String | 对方角色中文(如「定制师」) |
|
||||
| `peerOnline` | Boolean | 对方在线态(绿点) |
|
||||
| `unreadCount` | Integer | 本会话未读数(红点) |
|
||||
| `lastMessagePreview` | String | 最后一条消息预览(放副标题/内容列) |
|
||||
| `lastMessageAt` | String | 最后活跃时间 |
|
||||
| `customerName` | String | 订单联系人(如「王完善」),可拼进标题 |
|
||||
| `destination`/`tripDays`/`bizStatusLabel`/`progressDesc` | — | 订单维度信息,按需展示 |
|
||||
|
||||
---
|
||||
|
||||
## 3 前端要做的事
|
||||
|
||||
### 3.1 标题加团号
|
||||
每行标题 = **团号 `orderNo`**(建议再拼客户名 `customerName`),例:`HL20260624092836856 · 王完善`。
|
||||
- `orderNo` 为 null(无真实订单/order-v3 不可达)时降级显示对方名或会话键,不要显示空白。
|
||||
|
||||
### 3.2 行内信息
|
||||
- 对方:`peerName` + `peerRoleLabel`(+ `peerOnline` 绿点)
|
||||
- 预览:`lastMessagePreview`
|
||||
- 未读红点:`unreadCount`(>0 显示)
|
||||
- 时间:`lastMessageAt`
|
||||
- 点击行 → 打开该会话线程(用 `conversationKey` 调线程接口 `GET /admin/message/chat/messages`,已有)
|
||||
|
||||
### 3.3 跳转按钮(按**你在该会话里的身份**定目标页 —— wx 2026-06-25 修正)
|
||||
|
||||
> ⚠️ 修正:**不能按登录账号的全局角色**(如「超级管理员」同时是房务也是定制师,没法定向)。要按 **你在这条会话里是哪一方** = `peerRole` 反推(对方是谁、你就是另一方)。
|
||||
|
||||
| 对方角色 `peerRole` | 你在会话里的身份 | 跳转目标 | 路由(按截图,前端核对实际路由) |
|
||||
|---------|---------|---------|------|
|
||||
| `HOUSE`(对方是房务) | 你是**定制师** | **订单详情/需求页** | `/order-v2/detail/{bizId}`(见 Image #5) |
|
||||
| `FLEET`(对方是车务) | 你是**定制师** | **订单详情/需求页** | `/order-v2/detail/{bizId}` |
|
||||
| `CUSTOMIZER`(对方是定制师)+ `bizModule=HOUSE` | 你是**房务** | **配房页** | `/housekeeper/orders`(带 `bizId`+`requirementId` 自动开配房详情,见 Image #4) |
|
||||
| `CUSTOMIZER`(对方是定制师)+ `bizModule=FLEET` | 你是**车务** | **配车页** | 车务配车页(带 `bizId`,路由前端定,best-effort) |
|
||||
|
||||
判定逻辑(伪码):
|
||||
```
|
||||
if peerRole == 'CUSTOMIZER': // 对方是定制师 → 我是业务方
|
||||
bizModule == 'HOUSE' ? 配房页 : 配车页
|
||||
else: // 对方是房务/车务 → 我是定制师
|
||||
订单详情 /order-v2/detail/{bizId}
|
||||
```
|
||||
|
||||
> 跳转只需 `bizId`+`peerRole`+`bizModule`(+ 房务侧 `requirementId` 自动开详情),均在会话列表 VO 里。后端不提供跳转 URL(避免耦合前端路由)。`requirementId` 见 §6 后端增强。
|
||||
|
||||
---
|
||||
|
||||
## 4 tab 归属建议(前端 IA 自定)
|
||||
|
||||
- **订单消息** tab → 本会话列表接口(按会话聚合,团号+跳转)。
|
||||
- **普通消息** tab → 系统通知:`GET /admin/message/list?messageType=NORMAL`(仅系统通知,不含聊天)。
|
||||
- **全部** tab → 由前端决定:可「会话列表 + 系统通知」两段合并展示,或仅系统通知。
|
||||
- 顶部铃铛未读总数仍走 `GET /admin/message/unread-count`(含聊天+通知全部未读)。
|
||||
|
||||
> 说明:#4382/#4386 给 `/admin/message/list` 做的「逐条混排聊天 + 派生标题」保留可用(如某处仍要平铺消息流),但「订单消息」tab 推荐改用本会话列表,体验更完整。
|
||||
|
||||
---
|
||||
|
||||
## 5 注意
|
||||
|
||||
1. `orderNo`/订单维度字段是软依赖 order-v3,测试环境用无真实订单的 bizId(如 88888)/订单已被删时为 null,属正常;真实存在的订单会有团号(实证:库凡巧 HL20260625134754752 团号/客户名都正常)。
|
||||
2. 跳转目标页路由以前端实际为准(本文路由摘自截图 Image #4 `/housekeeper/orders`、Image #5 `/order-v2/detail/{id}`)。
|
||||
3. 有疑问找后端(王骁/wx)对字段。
|
||||
|
||||
---
|
||||
|
||||
## 6 后端增强(会话列表 VO 新增字段,订单简介条 + 房务跳转,2026-06-25)
|
||||
|
||||
> 需求:① 从站内信会话列表打开对话时,对话顶部「订单简介条」(关于 **产品名** · 客户·人数 · 团号)也要有(此前仅从订单页进才有,见 Image #8);② 房务跳转要能自动打开配房详情(需 `requirementId`)。
|
||||
> 后端在 `/admin/message/chat/conversations`(及 internal `by-biz`)的会话 VO 上新增 4 个字段,均由现成 order-v3 enrich 补齐(order_main 已有产品名/人数,hotel_requirement 已有 requirementId),软依赖降级为 null。
|
||||
|
||||
| 新增字段 | 类型 | 来源 | 用途 |
|
||||
|---------|------|------|------|
|
||||
| `productName` | String | order_main | 订单简介条「产品名」(如 游牧的森林-短途版);仅 HOUSE 会话有 |
|
||||
| `adultCount` | Integer | order_main | 简介条人数「N 大」 |
|
||||
| `childCount` | Integer | order_main | 简介条人数「N 小」(前端按需拼「2大1小」) |
|
||||
| `requirementId` | Long | hotel_requirement | 房务跳转配房页时定位/自动打开该订单配房详情(无房务需求时 null) |
|
||||
|
||||
- 订单简介条 = `productName` + `customerName`(已有)+ `adultCount`/`childCount` + `orderNo`(已有,团号)。打开会话时前端用会话行这几个字段渲染顶部简介条,无需再查订单页。
|
||||
- 房务跳转:`/housekeeper/orders?orderId={bizId}&requirementId={requirementId}` 前端据此自动开配房详情(之前只 orderId 落到列表页)。
|
||||
- **✅ 已上线测试服(PR #4394,order-v3 + user-service 已部署)**。实测真实订单(库凡巧)会话返回 `productName=游牧的森林-短途版 / adultCount=2 / childCount=0 / requirementId=2070028178415337474 / orderNo=HL20260625134754752`,订单已删的会话这些字段为 null(软依赖降级)。前端可直接取用渲染订单简介条 + 房务跳转。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 站内信顶部铃铛未读角标接入(前端对接 spec · 管理后台)
|
||||
|
||||
- **类型**:前端对接说明(**后端零改动**,数据/信令均已就绪)
|
||||
- **端类型**:管理后台(hl-ui,各业务页顶部铃铛)
|
||||
- **日期**:2026-06-25
|
||||
- **后端联系人**:王骁(wx)
|
||||
|
||||
> 现象(wx 反馈):房务给定制师发了消息,定制师(在 order-v2 订单详情页)顶部铃铛 🔔 **没有红点**。
|
||||
> 排查结论:**后端完全正确**(收件方未读已落库、实时信令已推、未读数接口返真值),铃铛没红点是**前端没把未读数接到铃铛上**。
|
||||
|
||||
---
|
||||
|
||||
## 1 后端已就绪(实测确认)
|
||||
|
||||
某条消息发给收件方后,后端做了三件事:
|
||||
|
||||
1. **落库未读**:`admin_message`(收件方 adminId、`is_read=0`)+ 收件方 `member.unread_count+1`。实测房务发「4」后 `countUnread(定制师)=1`、`member.unread_count=1`。
|
||||
2. **推实时信令**(事务提交后 SSE 广播,跨实例):
|
||||
- 聊天消息 → 事件 `im-chat`(`type=CHAT`),payload 含 **`unreadCount`=收件方合并未读(NOTIFY+CHAT)**、`conversationKey`、`senderName`、`preview`、`priority`。
|
||||
- 系统通知 → 事件 `message`(`type=NOTIFY`),payload 含 `unreadCount`。
|
||||
- 标记已读后 → 事件 `unread-count`(`type=UNREAD`),payload 含最新 `unreadCount`(角标回落)。
|
||||
3. **未读数接口**:`GET /admin/message/unread-count` 返回当前登录员工合并未读总数(含聊天+通知)。
|
||||
|
||||
---
|
||||
|
||||
## 2 前端要做的(铃铛角标)
|
||||
|
||||
铃铛红点/数字 = 当前登录员工的合并未读数,前端需两路驱动:
|
||||
|
||||
1. **页面加载时**:调 `GET /admin/message/unread-count` 取初值,>0 显红点/数字。**每个有铃铛的页面都要调**(order-v2 订单详情、房务管家、各工作台…),不能只在某一个页面调。
|
||||
2. **实时更新**:订阅 SSE 流(`/ws/admin-msg/stream`,与聊天同一条连接),在以下事件里读 `payload.unreadCount` 刷新铃铛角标:
|
||||
- `im-chat`(收到聊天)→ 取 `unreadCount` 刷角标(红点点亮/数字+1)。
|
||||
- `message`(收到通知)→ 同上。
|
||||
- `unread-count`(自己标已读后)→ 取 `unreadCount` 刷角标(回落)。
|
||||
|
||||
> 关键:`im-chat` 事件**既驱动聊天 UI(live append)也驱动铃铛角标**——前端收到 `im-chat` 别只 append 聊天、要同时用 `payload.unreadCount` 刷铃铛。order-v2 订单详情页当前的铃铛大概率①没订阅这条 SSE 流,②或没在加载时调 unread-count,所以收了消息不亮红点。
|
||||
|
||||
---
|
||||
|
||||
## 3 注意
|
||||
|
||||
1. 铃铛角标是**合并未读**(聊天+通知),口径就是 `GET /admin/message/unread-count`,SSE 各事件 payload 的 `unreadCount` 与之一致,直接用即可,不用前端自己累加计数。
|
||||
2. SSE 连接要保证在用户登录后的各页面都建立(铃铛是全局组件),否则切到没订阅的页面就收不到实时点亮(刷新时靠 unread-count 兜底)。
|
||||
3. 有疑问找后端(王骁/wx)。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 配房前端 UX 三处(① 选酒店弹窗结算价改自由键入 ② 已选配房每行显示日期 ③ 订单详情·配房行程 DAY 卡片显示已配酒店结果)
|
||||
|
||||
> 变更类型:📖 纯前端 UX / 前端渲染 bug(后端无改动,所需数据后端均已返回并部署)
|
||||
> 端类型:管理后台(房务管家·配房「选择酒店」弹窗 + 订单详情「配房行程」Tab)
|
||||
> 日期:2026-06-25 | 工单/PR:无(前端 UX,走 changelog 通知)| 服务:hl-order-service-v3(无代码改动)
|
||||
> 关联:承接 #4373(定制师标记/分页/售价列)+ #4377(同步协议价开关);本篇列三处前端交互/渲染问题。
|
||||
|
||||
---
|
||||
|
||||
## ① 结算价:改成可自由键入的数字输入框(不要 −/+ 步进器)
|
||||
|
||||
**现象**:候选列表「结算价」列做成了 `−/+` 步进器,没法直接键入价格(如 288.88、330),房务反馈「没法输入结算价」。
|
||||
|
||||
**期望交互**:
|
||||
- 「结算价」改成**可自由键入的数字输入框**(允许两位小数),不要做成步进器。
|
||||
- **默认值 = 该酒店/房型协议价**:取候选接口的 `roomTypes[].protocolPrice`(选中具体房型时)或顶层 `protoPrice`(房型未细分时),房务可在此基础上手动改写。
|
||||
- 提交时仍把房务最终填的值放进 `AssignmentItemReqVO.sellPrice`(修改单条配房放 `AssignmentUpdateReqVO.sellPrice`)。
|
||||
|
||||
**后端状态**:零改动。`sellPrice` 字段后端早支持(`@NotNull` + `@DecimalMin(0.00)`,收任意合法两位小数)、已部署测试服、端到端实测过;协议价默认值来源 `protoPrice` / `roomTypes[].protocolPrice` 候选接口已返。
|
||||
|
||||
## ② 「已选配房」底部每行显示配房日期
|
||||
|
||||
**诉求**:弹窗底部「已选配房」每行现在是「酒店名 · 房型 · N间 · ¥价/间 · 同步协议价 · 移除」,**没带日期**;一天加多酒店/多房型、或跨天累计时分不清哪条是哪天。
|
||||
|
||||
**期望交互**:
|
||||
- 「已选配房」每行**补上该条的配房日期**(就是这次弹窗顶部那个日期 `2026-06-26`)。
|
||||
- 数据来源:候选接口顶层 `HotelCandidateRespVO.stayDate`(本次配房日期,弹窗顶部蓝标已在用);每条配房落库也带 `stayDate`,前端直接复用即可。
|
||||
|
||||
**后端状态**:零改动。`stayDate` 候选接口顶层早已返回(弹窗顶部已显示),纯前端把它也带进「已选配房」每行展示。
|
||||
|
||||
## ③ 订单详情·「配房行程」DAY 卡片:已配房后显示配置结果(后端已返 `currentAssignment`,前端未渲染)
|
||||
|
||||
**现象**:某 DAY 已配房(右侧状态徽章绿色「已完成/已确认」),但卡片正文仍显示占位「待指定酒店」,没把配好的酒店带出来。
|
||||
|
||||
**根因(真实接口实证,非推断)**:后端 `GET /admin/house/orders/{orderId}`(房务侧订单详情聚合)的 `itinerary[].currentAssignment` **已正确返回该天配房结果**,前端没渲染它、仍显示占位。
|
||||
实测 order `HL20260624092836856`(id 2069593503129636865)DAY1 实际返回:
|
||||
- `arrange = "confirmed"`、`arrangeLabel = "已确认"`
|
||||
- `currentAssignment = { hotelId, hotelName: "海拉尔海棠酒店", roomCategory, roomCount: 1, protoPrice: "520.00", sellPrice: "330.00", assignmentId }`
|
||||
|
||||
**前端动作**:DAY 卡片当 `currentAssignment != null` 时,把占位「待指定酒店」替换成**配房结果**——酒店名 `hotelName`、间数 `roomCount`、成交价 `sellPrice`(协议价 `protoPrice` 作参考),房型 `roomCategory`。仅 `currentAssignment == null` 时才显示「待指定酒店/待配房」。
|
||||
注:右侧绿色徽章(你截图里的「已完成」)与卡片正文是两件事——徽章是状态,正文该显示已配酒店。配房状态权威值取 `arrange`/`arrangeLabel`(`confirmed`=已确认 / `pending`=待配房)。
|
||||
|
||||
**后端状态**:零改动,`currentAssignment` 字段已返回并部署测试服。
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
- 三处均**后端零改动**,所需字段(`sellPrice` / `protoPrice` / `protocolPrice` / `stayDate` / `itinerary[].currentAssignment`)后端全部已返回并部署测试服,前端按上述调整即可。
|
||||
- ①② 与 #4373 ③「结算价输入挪到选中后填、默认协议价」一脉相承;③ 是订单详情行程卡渲染遗漏(后端早返 `currentAssignment`,实测见上)。
|
||||
@@ -0,0 +1,221 @@
|
||||
# 订单详情「终止行程」接口终态汇总(出行中订单)—— 管理后台
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(**终态汇总篇**,无新契约变化;合并 #3556 系列 3 篇 + #3965 金额 String 化)
|
||||
- 关联 Issue:#3556 PR:#3557(主改造)/ #3559(预览状态校验加固)/ #3965(金额 String 化)
|
||||
- 日期:2026-06-26
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
「终止行程」用于**出行中(TRAVELLING)订单**提前中止:定制师按"停在第几天 + 逐项资源是否已用"提交,后端按实际成交价权威核算退款额、写入核单返还,订单转为「已完成→待核单」。
|
||||
|
||||
此前该能力的 changelog 分散在 3 篇(接口改造 / 退款预览新增 / 旧路径下线),且金额格式在 #3965 后由数字改为字符串。**本篇为一处对接的终态汇总**,前端按此一份即可对接,旧 3 篇仅作历史留档。
|
||||
|
||||
> ⚠️ 旧路径 `POST /v3/admin/order/{id}/cancel/on-trip` **已删除**,调用返回 404,必须改调 `/terminate`。
|
||||
> ⚠️ 所有 Long ID 与金额均**字符串化**返回(防 JS 精度丢失),请求传金额相关 ID 也用字符串。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单(两个接口配套使用)
|
||||
|
||||
| # | 方法 | 路径 | 用途 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST | `/v3/admin/order/{id}/terminate/refund-preview` | 终止退款预览(拿全资源清单,前端本地算退款) |
|
||||
| 2 | POST | `/v3/admin/order/{id}/terminate` | 终止行程(提交执行,落核单返还) |
|
||||
|
||||
统一响应 `Result<T>`:`{ code, message, data, success }`,`code=200` 成功。认证:管理端 JWT(`Authorization` 头)。
|
||||
|
||||
**对接流程**:点「终止行程」→ 调①拿整趟资源清单 → 前端按所选结束天本地标已用/可退并算退款 → 调②只回传 `used` 判定 + `adjustAmount`(不传金额单价,后端权威重算防篡改)。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口 1:终止退款预览
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/terminate/refund-preview
|
||||
```
|
||||
- 入参:仅 path `id`(订单 ID),**无请求体**。一次性拿整趟全部资源,结束天选择/已用标记/退款计算全在前端本地完成,不需每选一天再调。
|
||||
- 幂等:是(只读,不落库)。
|
||||
|
||||
### 出参 `Result<OrderTerminateRefundPreviewRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| paidAmount | String(金额) | 客户已付金额(前端算退款基线用) |
|
||||
| tripDays | Integer | 行程总天数(画时间轴) |
|
||||
| departDate | String(date) | 出发日期 `yyyy-MM-dd`(第 N 天日期 = 出发日 + N−1) |
|
||||
| tripCurrentDay | Integer | 当前到第几天(时间轴默认选中点;出发前/未知=1) |
|
||||
| rooms | Array<Item> | 住宿清单(每晚×每组房一行) |
|
||||
| tickets | Array<Item> | 门票清单(每景点每天一行) |
|
||||
| vehicles | Array<Item> | 用车清单(**整车按天展开,每天一行**,同段车 refId 相同) |
|
||||
| insurance | Array<Item> | 保险清单(整单 1 行,locked=true 锁定不退) |
|
||||
|
||||
**Item(TerminateRefundItemVO,四类资源行通用)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refId | String | 资源配单记录 ID(提交终止时按此回传已用判定;用车多天共用同一 refId 靠 dayNumber 区分;保险可能为 null) |
|
||||
| name | String | 资源名称快照(酒店名/景点名/车型/保险产品名) |
|
||||
| dayNumber | Integer | 对应第几天(保险为 null) |
|
||||
| dealPrice | String(金额) | 成交单价(住宿=每间每晚 / 门票=每张 / 用车=每天每辆日租 / 保险=保费) |
|
||||
| quantity | Integer | 数量(住宿=间数 / 门票=张数 / 用车=车辆数 / 保险=1) |
|
||||
| totalAmount | String(金额) | 本行小计 = dealPrice × quantity |
|
||||
| locked | Boolean | 是否锁定不退(保险恒 true,其余 false) |
|
||||
| lockedReason | String | 锁定原因(如"保险已生效不退";未锁定为 null) |
|
||||
|
||||
> 响应**不含**"是否已用"标记与"退款合计"——由前端按所选结束天(`dayNumber ≤ 结束天` 默认已用,之后默认可退)实时计算。
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口 2:终止行程(执行)
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/terminate
|
||||
Content-Type: application/json
|
||||
```
|
||||
- 幂等:是(订单一旦终止变 COMPLETED,二次调用因状态非 TRAVELLING 返 581018)。
|
||||
|
||||
### 入参 Body(OrderTerminateTripReqVO)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||
|---|---|:--:|---|---|
|
||||
| cancelReason | String | ✅ | 终止原因(调整额说明并入此处,不单列字段) | 非空 |
|
||||
| endDayNumber | Integer | ✅ | 停在第几天(截断点,1-based) | ≥ 1 ≤ 行程总天数 |
|
||||
| rooms | Array<{refId, used}> | ✅ | 住宿各行已用判定(列表非 null,可空数组) | — |
|
||||
| tickets | Array<{refId, used}> | ✅ | 门票各行已用判定 | — |
|
||||
| vehicles | Array<{refId, dayNumber, used}> | ✅ | 用车各行已用判定(同车跨天靠 dayNumber 区分) | — |
|
||||
| adjustAmount | String(金额) | ❌ | 人工调整额(±,正=多退 负=少退),默认 0 | — |
|
||||
|
||||
**rooms / tickets 行(ResourceUsedItem)**:`refId`(String,必填) + `used`(Boolean,必填 true=已用不退/false=未用可退)
|
||||
**vehicles 行(VehicleUsedItem)**:`refId`(String,必填) + `dayNumber`(Integer,必填≥1) + `used`(Boolean,必填)
|
||||
|
||||
> `refId` 全部取自接口①返回值。入参**只传 `used` 判定 + `adjustAmount`,不传任何金额单价**——退款额由后端按预览同源成交价权威重算。保险不在入参,后端自动按锁定不退处理。
|
||||
|
||||
### 出参 `Result<OrderTerminateTripRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| terminateRefundId | String | 终止退款主表 ID |
|
||||
| baselineRefund | String(金额) | 系统建议基线 = max(0, paidAmount − usedAmount) |
|
||||
| adjustAmount | String(金额) | 人工调整额(回显入参,默认 0) |
|
||||
| finalRefund | String(金额) | 最终返还额 = max(0, baselineRefund + adjustAmount),最低 0 |
|
||||
| settlementRefundId | String | 核单返还记录 ID(核单模块写入后回填) |
|
||||
| newStatus | String | 终止后订单粗状态(恒 COMPLETED) |
|
||||
| newFlowStatus | String | 终止后流程细状态(恒 PENDING_REVIEW) |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 枚举 / 数据字典
|
||||
|
||||
- newStatus:`COMPLETED` 已完成(终止后进完成态转核单,**不再是旧的 CANCELLED**)
|
||||
- newFlowStatus:`PENDING_REVIEW` 待核单(等核单录入与结算复核)
|
||||
- 资源分类:通过 rooms/tickets/vehicles/insurance 四数组区分,无独立 resourceType 字段
|
||||
- locked:true=锁定不退(保险恒 true)/ false=可退
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| 581007 | 订单不存在 | id 无对应订单 |
|
||||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非 TRAVELLING(未出发/已终止/已完成,含二次终止) |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 示例
|
||||
|
||||
### 接口①响应(预览,节选)
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": {
|
||||
"paidAmount": "6000.00", "tripDays": 7, "departDate": "2026-05-12", "tripCurrentDay": 3,
|
||||
"rooms": [ {"refId":"96011","name":"拉萨瑞吉·大床房","dayNumber":1,"dealPrice":"1280.00","quantity":1,"totalAmount":"1280.00","locked":false,"lockedReason":null} ],
|
||||
"vehicles": [ {"refId":"98001","name":"丰田赛那 商务车","dayNumber":1,"dealPrice":"600.00","quantity":1,"totalAmount":"600.00","locked":false,"lockedReason":null} ],
|
||||
"insurance": [ {"refId":null,"name":"安联境内旅行险·尊享版","dayNumber":null,"dealPrice":"256.00","quantity":1,"totalAmount":"256.00","locked":true,"lockedReason":"保险已生效不退"} ]
|
||||
} }
|
||||
```
|
||||
|
||||
### 接口②请求(终止)
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户中途高反送医终止;林芝段酒店已付全款不可退,调整 -300",
|
||||
"endDayNumber": 3,
|
||||
"rooms": [ {"refId":"96011","used":true}, {"refId":"96014","used":false} ],
|
||||
"tickets": [ {"refId":"97001","used":true} ],
|
||||
"vehicles": [ {"refId":"98001","dayNumber":1,"used":true},{"refId":"98001","dayNumber":2,"used":true},{"refId":"98001","dayNumber":3,"used":true} ],
|
||||
"adjustAmount": "-300.00"
|
||||
}
|
||||
```
|
||||
### 接口②响应(终止)
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": {
|
||||
"terminateRefundId": "9610000001", "baselineRefund": "2264.00", "adjustAmount": "-300.00",
|
||||
"finalRefund": "1964.00", "settlementRefundId": "9620000001",
|
||||
"newStatus": "COMPLETED", "newFlowStatus": "PENDING_REVIEW" } }
|
||||
```
|
||||
### 边界(不填调整额,默认 0)
|
||||
```json
|
||||
请求: { "cancelReason":"客户主动终止","endDayNumber":5,"rooms":[],"tickets":[],"vehicles":[] }
|
||||
响应: { "code":200,"data":{ "baselineRefund":"0.00","adjustAmount":"0.00","finalRefund":"0.00","newStatus":"COMPLETED","newFlowStatus":"PENDING_REVIEW" } }
|
||||
```
|
||||
### 异常 —— 调旧路径(前端联调易踩)
|
||||
```
|
||||
POST /v3/admin/order/{id}/cancel/on-trip → HTTP 404 Not Found
|
||||
```
|
||||
旧路径后端无任何映射,不做 301/308 跳转,必须改调 `/terminate`。
|
||||
### 异常 —— 订单非出行中
|
||||
```json
|
||||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 业务边界
|
||||
|
||||
- ✅ 适用:订单当前粗状态 = TRAVELLING(出行中)
|
||||
- ❌ 不适用:未出发订单走「取消订单」流程(`cancel/pre-trip`);已终止/已完成二次调用返 581018
|
||||
- ⚠️ finalRefund 最低 0,不会负数;退款额进核单返还由财务复核放款;保险恒不退
|
||||
- ⚠️ 终止后订单是「已完成(待核单)」而非「已取消」,列表/详情状态展示需按新枚举对齐
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 修改前后对比(相对最早 cancel/on-trip 旧实现)
|
||||
|
||||
| 维度 | 改前 | 改后(终态) |
|
||||
|---|---|---|
|
||||
| 路径 | `POST /{id}/cancel/on-trip` | `POST /{id}/terminate`(旧路径 404) |
|
||||
| 退款预览 | 无独立预览 | `POST /{id}/terminate/refund-preview`(新增) |
|
||||
| 退款额来源 | 定制师手填一个数 | 后端按已用资源 + 成交价权威核算 |
|
||||
| 退款是否进核单 | 否(仅记录) | 是(写核单返还,财务复核放款) |
|
||||
| 入参 | returnAmount/returnRemark 手填 | endDayNumber + rooms/tickets/vehicles 已用判定 + adjustAmount |
|
||||
| 出参 | returnAmount 回显 | terminateRefundId/baselineRefund/adjustAmount/finalRefund/settlementRefundId/newFlowStatus |
|
||||
| 金额/ID 格式 | 数字 | **字符串(#3965 后)** |
|
||||
| 终止后状态 | CANCELLED 已取消 | COMPLETED 已完成 → PENDING_REVIEW 待核单 |
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 影响评估 / 回滚
|
||||
|
||||
- 接口已稳定上线(#3557/#3559/#3965 均已合并部署),本篇为终态汇总,不含新后端改动,无回滚项。
|
||||
- 前端「终止行程」页面对接:① 进入调①拿资源清单 → ② 选结束天 + 勾选已用 + 可选调整额 → ③ 调②提交 → ④ 终态按「已完成/待核单」展示并刷新详情。
|
||||
- 旧 3 篇(`06_3556_终止行程接口改造`、`06_3556_终止退款预览`、`11_3556_出行中取消路径下线`)保留历史留档,对接以本篇为准。
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 注意事项
|
||||
|
||||
- Long ID + 金额(terminateRefundId/baselineRefund/finalRefund/paidAmount/refId 等)均字符串化,请求/解析均按字符串。
|
||||
- 写操作需前端按钮防重复点击 + loading。
|
||||
- 必须先调预览拿 refId,再提交终止;提交不传金额单价(后端权威重算)。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/3556
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3557 ・ https://git.1814.love:8443/wx/HL/pulls/3559 ・ https://git.1814.love:8443/wx/HL/pulls/3965
|
||||
- 历史留档:`changelogs-v2/2026-06/06_3556_终止行程接口改造-修改接口-管理后台.md` 等 3 篇
|
||||
- 后端负责人:腰苏图
|
||||
- 接口已部署测试服,Knife4j 可见(`/v3/admin/order/{id}/terminate`、`/terminate/refund-preview`)。
|
||||
@@ -0,0 +1,216 @@
|
||||
# 标签库新增分页列表接口
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单标签库目前只有不分页的全量列表接口 `GET /v3/admin/tag-library`,供打标签弹窗下拉候选使用。
|
||||
新增 `GET /v3/admin/tag-library/page` 分页版,满足标签库管理页面场景(分页浏览、关键字搜索、仅看置顶)。
|
||||
两个接口并存,不影响现有不分页接口的使用。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| ✨ 新增接口 | `GET /v3/admin/tag-library/page` | 分页查询当前管理员可见的标签库列表 |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **方法 + 路径**:`GET /v3/admin/tag-library/page`
|
||||
- **接口名**:标签库分页列表
|
||||
- **描述**:分页查询当前管理员可见的标签库列表,数据范围为系统标签(SYSTEM)并集本人个人标签(PERSONAL),数据范围、过滤口径、排序规则与不分页接口 `GET /v3/admin/tag-library` 完全一致。
|
||||
- **认证**:管理后台登录态(JWT),`userId` 从请求头 `X-Admin-Id` 自动派生,前端无需手动传。
|
||||
- **幂等性**:GET 只读,天然幂等。
|
||||
- **限流**:无特殊限流配置,走网关全局限流。
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| page | int | 否 | 1 | 页码,从 1 开始 |
|
||||
| pageSize | int | 否 | 20 | 每页条数 |
|
||||
| keyword | string | 否 | - | 标签名模糊匹配(LIKE %keyword%),不传则不过滤 |
|
||||
| pinnedOnly | boolean | 否 | false | true=仅返回置顶记录(is_pinned=true),false=全部返回 |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无。
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
响应结构:`Result<PageResult<TagLibraryItemVO>>`
|
||||
|
||||
**PageResult 外层字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| records | array | 当页数据列表,元素见下表 |
|
||||
| total | int | 符合条件的总记录数 |
|
||||
| page | int | 当前页码 |
|
||||
| pageSize | int | 每页条数 |
|
||||
|
||||
**TagLibraryItemVO 每条字段**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string | 标签库行 ID(雪花 ID,序列化为字符串,防 JS 精度丢失) |
|
||||
| tagScope | string | 作用域枚举值:`SYSTEM` / `PERSONAL`(详见 §6) |
|
||||
| tagName | string | 标签名 |
|
||||
| tagColor | string | HEX 色值,如 `#5B8FF9` |
|
||||
| sortOrder | int | 排序值 |
|
||||
| isPinned | boolean | 是否置顶 |
|
||||
| usedCount | int | 使用次数 |
|
||||
| lastUsedAt | string(datetime) | 最近使用时间,格式 `yyyy-MM-dd HH:mm:ss`,可为 null |
|
||||
|
||||
**排序规则(后端固定,前端无需额外排序)**:`is_pinned DESC, sort_order ASC, last_used_at DESC`
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**tagScope — 标签作用域**
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
| SYSTEM | 系统标签,全员共享可见 |
|
||||
| PERSONAL | 个人标签,仅创建者本人可见、可维护 |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|---|---|---|---|
|
||||
| - | 401 | 未登录 | 请求未携带有效 JWT |
|
||||
| 581426 | 200 | 管理员会话丢失 | OperatorHolder ThreadLocal 为空(@Async 深度防御场景) |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/tag-library/page?page=1&pageSize=20
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "1938010234567890001",
|
||||
"tagScope": "SYSTEM",
|
||||
"tagName": "VIP客户",
|
||||
"tagColor": "#5B8FF9",
|
||||
"sortOrder": 1,
|
||||
"isPinned": true,
|
||||
"usedCount": 42,
|
||||
"lastUsedAt": "2026-06-25 14:32:00"
|
||||
},
|
||||
{
|
||||
"id": "1938010234567890002",
|
||||
"tagScope": "PERSONAL",
|
||||
"tagName": "高复购",
|
||||
"tagColor": "#FF6B6B",
|
||||
"sortOrder": 2,
|
||||
"isPinned": false,
|
||||
"usedCount": 7,
|
||||
"lastUsedAt": "2026-06-20 09:15:00"
|
||||
}
|
||||
],
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况
|
||||
|
||||
**标签库为空(或过滤条件无匹配)**:
|
||||
```
|
||||
GET /v3/admin/tag-library/page?keyword=不存在的关键字&pinnedOnly=true
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**lastUsedAt 为 null(从未使用过的新标签)**:
|
||||
```json
|
||||
{
|
||||
"id": "1938010234567890099",
|
||||
"tagScope": "PERSONAL",
|
||||
"tagName": "待观察",
|
||||
"tagColor": "#AAAAAA",
|
||||
"sortOrder": 99,
|
||||
"isPinned": false,
|
||||
"usedCount": 0,
|
||||
"lastUsedAt": null
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败
|
||||
|
||||
**未登录访问(401)**:
|
||||
```
|
||||
GET /v3/admin/tag-library/page
|
||||
(无 Authorization header)
|
||||
```
|
||||
|
||||
响应:HTTP 401,网关拦截,不返回业务 JSON。
|
||||
|
||||
**管理员会话丢失(错误码 581426)**:
|
||||
```json
|
||||
{
|
||||
"code": 581426,
|
||||
"msg": "管理员未登录或会话已过期",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
- 标签库管理页面分页展示
|
||||
- 需要关键字搜索标签的场景
|
||||
- 仅展示置顶标签的场景
|
||||
|
||||
**不适用**:
|
||||
- 打标签弹窗下拉候选列表(仍使用不分页接口 `GET /v3/admin/tag-library`,数据量小且需全量展示)
|
||||
|
||||
**特殊边界**:
|
||||
- 数据范围是「本人可见」:系统标签(全员共享)+ 本人创建的个人标签。不跨用户查看他人个人标签。
|
||||
- `pinnedOnly=false` 与不传 `pinnedOnly` 等效,均返回全部可见记录。
|
||||
- `keyword` 传空字符串等同于不过滤。
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
本次为纯新增接口,无修改,跳过此节。
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
本次为纯新增接口,无现有接口变更,无破坏兼容性,跳过此节。
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- `id` 字段为雪花 ID 字符串,前端请勿用 number 类型接收(会精度丢失),统一用 string。
|
||||
- 排序由后端固定(置顶优先 → sortOrder 升序 → 最近使用降序),前端无需在前端再做排序。
|
||||
- 不分页接口 `GET /v3/admin/tag-library` 保持不变,打标签弹窗继续调用原接口即可,无需迁移。
|
||||
- 零 DDL,纯接口新增,对数据库无变更。
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:https://git.1814.love:8443/wx/HL/issues/4415
|
||||
- **PR**:https://git.1814.love:8443/wx/HL/pulls/4416
|
||||
- **Commit**:https://git.1814.love:8443/wx/HL/commit/c596e3a76
|
||||
- **后端负责人**:yst
|
||||
@@ -0,0 +1,324 @@
|
||||
# 退款明细 Tab 懒加载——出参契约重构
|
||||
|
||||
- **接口**:`GET /v3/admin/order/{id}/refund`
|
||||
- **变更类型**:修改接口(出参字段调整)
|
||||
- **日期**:2026-06-26
|
||||
- **Issue**:[#4417](https://git.1814.love:8443/wx/HL/issues/4417)
|
||||
- **PR**:[#4418](https://git.1814.love:8443/wx/HL/pulls/4418)
|
||||
- **后端负责人**:yst
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单详情页「退款明细」Tab 采用懒加载模式,前端单独调用此接口获取退款数据。
|
||||
本次重构对出参 VO 结构进行语义修正:补充「退款原因」和「申请时间」两个展示字段,删除不展示的 `estimatedArriveDate`(预计到账日期)和 `items`(逐项明细)块,同时修正 `refundChannel` 字段的语义(此前误返回退款类型枚举值,现在统一返回真实渠道文案)。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 类型 | 字段 | 说明 |
|
||||
|------|------|------|
|
||||
| ✨ 新增 | `applications[].reason` | 退款原因文字 |
|
||||
| ✨ 新增 | `applications[].appliedAt` | 退款申请时间 |
|
||||
| ⚠️ 删除 | `applications[].estimatedArriveDate` | 预计到账日期,不再返回 |
|
||||
| ⚠️ 删除 | `applications[].items[]` | 退款逐项明细块,不再返回 |
|
||||
| 🔧 语义修正 | `applications[].refundChannel` | 固定返回 `"原路退回(微信)"` 字符串,不再返回退款类型枚举值 |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| **方法** | GET |
|
||||
| **路径** | `/v3/admin/order/{id}/refund` |
|
||||
| **描述** | 获取订单退款明细(Tab 懒加载) |
|
||||
| **认证** | 需要管理后台 JWT(Bearer Token) |
|
||||
| **幂等性** | 只读接口,天然幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | Long(字符串传输) | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
**顶层结构(RefundDetailVO)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `totalRefundAmount` | String(BigDecimal 序列化) | 合计退款金额,单位:元 |
|
||||
| `applications` | Array\<RefundApplicationVO\> | 退款申请列表(全部历史申请) |
|
||||
|
||||
> 当订单无任何退款申请时,整个 `refund` 节点返回 `null`,前端据此隐藏 Tab。
|
||||
|
||||
**applications[] 元素(RefundApplicationVO)——完整字段**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `applicationId` | Long(字符串序列化) | 退款申请 ID |
|
||||
| `status` | String | 退款进度状态码(见 §⑥ 枚举) |
|
||||
| `statusText` | String | 状态文案,如 `"待审批"` |
|
||||
| `refundAmount` | String(BigDecimal 序列化) | 退款金额(实退额为空时回退计算额),单位:元 |
|
||||
| `refundChannel` | String | 退款渠道,固定返回 `"原路退回(微信)"` |
|
||||
| `reason` | String | **【新增】** 退款原因,如 `"同行儿童突发感冒,不参与本次出行"` |
|
||||
| `appliedAt` | String(ISO 8601 LocalDateTime) | **【新增】** 申请时间,如 `"2026-04-25T11:30:00"` |
|
||||
| `approverName` | String | 审批人姓名,未审批时为 `null` |
|
||||
| `approvedAt` | String(ISO 8601 LocalDateTime) | 审批时间,未审批时为 `null` |
|
||||
| `actualArriveDate` | String(ISO 8601 LocalDate) | 实际到账日期,未到账时为 `null` |
|
||||
| `progress` | Array\<ProgressStepVO\> | 4 步时间线(APPLY / APPROVE / PAYOUT / ARRIVED) |
|
||||
|
||||
**progress[] 元素(ProgressStepVO)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `step` | String | 步骤码:`APPLY` / `APPROVE` / `PAYOUT` / `ARRIVED` |
|
||||
| `label` | String | 步骤中文标签,如 `"已申请"` |
|
||||
| `status` | String | 步骤状态:`DONE`(已完成)/ `ACTIVE`(进行中)/ `PENDING`(待进行) |
|
||||
| `occurredAt` | String(ISO 8601) | 该步骤发生时间,未到达时为 `null` |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
### refundChannel(退款渠道)
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `"原路退回(微信)"` | 系统当前唯一退款渠道,所有退款均原路退至微信支付 |
|
||||
|
||||
> 注意:此前误将退款类型枚举值(如 `PARTIAL` / `FULL`)回填到该字段,本次已修正为真实渠道文案。
|
||||
|
||||
### applications[].status(退款申请状态)
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `PENDING_APPROVE` | 待审批 |
|
||||
| `PENDING_PAYOUT` | 审批通过,待退款打出 |
|
||||
| `PENDING_ARRIVAL` | 退款已发出,待到账 |
|
||||
| `COMPLETED` | 已完成到账 |
|
||||
| `REJECTED` | 已驳回 |
|
||||
|
||||
### progress[].status(时间线步骤状态)
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `DONE` | 该步骤已完成 |
|
||||
| `ACTIVE` | 当前正在该步骤 |
|
||||
| `PENDING` | 尚未到达该步骤 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 错误码 | 说明 | 触发条件 |
|
||||
|--------|------|---------|
|
||||
| `404` / 业务错误 | 订单不存在 | `id` 对应订单不存在 |
|
||||
| `403` | 无权访问 | JWT 鉴权失败或无该订单访问权限 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功——订单含一笔退款申请(已完成)
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/1902345678901234567/refund
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"totalRefundAmount": "1280.00",
|
||||
"applications": [
|
||||
{
|
||||
"applicationId": "1902345678901234568",
|
||||
"status": "COMPLETED",
|
||||
"statusText": "已完成",
|
||||
"refundAmount": "1280.00",
|
||||
"refundChannel": "原路退回(微信)",
|
||||
"reason": "同行儿童突发感冒,不参与本次出行",
|
||||
"appliedAt": "2026-04-25T11:30:00",
|
||||
"approverName": "张三",
|
||||
"approvedAt": "2026-04-25T14:00:00",
|
||||
"actualArriveDate": "2026-04-27",
|
||||
"progress": [
|
||||
{
|
||||
"step": "APPLY",
|
||||
"label": "已申请",
|
||||
"status": "DONE",
|
||||
"occurredAt": "2026-04-25T11:30:00"
|
||||
},
|
||||
{
|
||||
"step": "APPROVE",
|
||||
"label": "已审批",
|
||||
"status": "DONE",
|
||||
"occurredAt": "2026-04-25T14:00:00"
|
||||
},
|
||||
{
|
||||
"step": "PAYOUT",
|
||||
"label": "退款已发出",
|
||||
"status": "DONE",
|
||||
"occurredAt": "2026-04-25T14:30:00"
|
||||
},
|
||||
{
|
||||
"step": "ARRIVED",
|
||||
"label": "已到账",
|
||||
"status": "DONE",
|
||||
"occurredAt": "2026-04-27T09:15:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 边界情况——无退款申请(refund 节点为 null)
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/1902345678901234999/refund
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
> 前端收到 `data` 为 `null` 时应隐藏「退款明细」Tab,不做渲染。
|
||||
|
||||
---
|
||||
|
||||
### 8.3 业务失败——订单不存在
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9999999999999999999/refund
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "订单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用场景**
|
||||
|
||||
- 仅用于「退款明细」Tab 懒加载,不作为退款管理工作台的数据源
|
||||
- 展示该订单全部退款申请历史(含已完成、已驳回的历史申请)
|
||||
|
||||
**不适用**
|
||||
|
||||
- 不返回退款政策/原因配置列表(有单独接口)
|
||||
- 不返回退款工作台列表数据
|
||||
|
||||
**特殊边界**
|
||||
|
||||
- 一笔订单采用**串行退款**模式:前一笔退款完成后才能发起下一笔,正常情况下不会同时存在多个进行中的申请
|
||||
- `refundAmount` 字段:实退额(`actualRefundAmount`)有值时返回实退额,否则返回前端计算额,前端无需特殊处理,直接展示即可
|
||||
- `approverName` / `approvedAt`:待审批状态下为 `null`,前端渲染时需判空
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| `applications[].reason` | **不存在** | ✨ 新增,退款原因文字 |
|
||||
| `applications[].appliedAt` | **不存在** | ✨ 新增,申请时间 ISO 8601 |
|
||||
| `applications[].estimatedArriveDate` | ⚠️ 存在,预计到账日期 | **已删除**,不再返回 |
|
||||
| `applications[].items[]` | ⚠️ 存在,逐项退款明细块 | **已删除**,不再返回 |
|
||||
| `applications[].refundChannel` | 误返回退款类型枚举值(如 `PARTIAL`) | 🔧 修正为渠道文案 `"原路退回(微信)"` |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|--------|--------|
|
||||
| 读取退款渠道 | 返回类型枚举 `PARTIAL`/`FULL`,前端当渠道用会显示错误 | 返回渠道文案,直接渲染即可 |
|
||||
| 退款原因展示 | 无此字段,需前端单独请求其他接口 | 内联在申请对象中,无需额外请求 |
|
||||
| 申请时间展示 | 无此字段 | 内联在申请对象中 |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
**破坏兼容性变更(前端必须同步处理)**
|
||||
|
||||
- `estimatedArriveDate` 已从出参中删除,前端如有读取此字段的代码,收到 `undefined` 不会报错,但需确认是否有展示逻辑需要移除
|
||||
- `items[]` 已从出参中删除,前端如有遍历 `items` 的渲染逻辑需要移除
|
||||
- `refundChannel` 语义变化:若前端曾将该字段作为退款类型判断(如 `PARTIAL` → 部分退款),需改为直接展示文案,不再做枚举判断
|
||||
|
||||
**前端同步上线建议**
|
||||
|
||||
1. 移除 `estimatedArriveDate` 渲染逻辑
|
||||
2. 移除 `items[]` 相关渲染块(如退款逐项明细表格)
|
||||
3. `refundChannel` 改为直接渲染字符串,不做枚举 switch
|
||||
4. 新增 `reason`(退款原因)和 `appliedAt`(申请时间)展示
|
||||
|
||||
**回滚方案**
|
||||
|
||||
- 若需回滚,回退 PR #4418 对应 commit 即可恢复旧出参结构
|
||||
- 回滚后 `reason` / `appliedAt` 消失,`estimatedArriveDate` / `items[]` 恢复,`refundChannel` 恢复为枚举值
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **`refundChannel` 不再是枚举值**:固定返回 `"原路退回(微信)"` 文案字符串,前端直接渲染,不要做枚举 switch 判断
|
||||
2. **`appliedAt` 格式为 ISO 8601 LocalDateTime**(如 `"2026-04-25T11:30:00"`,无时区后缀),前端渲染时自行格式化
|
||||
3. **`data` 为 null 时代表无退款**:不是接口异常,是正常业务状态,Tab 应隐藏
|
||||
4. **`actualArriveDate` 是 LocalDate**(仅日期 `"2026-04-27"`),与 `appliedAt` / `approvedAt`(LocalDateTime)格式不同,注意区分
|
||||
5. **金额字段均为字符串**(BigDecimal 序列化防 JS 精度丢失),不要用 `parseFloat` 直接计算
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
| 项目 | 链接 |
|
||||
|------|------|
|
||||
| Issue | [#4417 退款明细Tab懒加载出参重构](https://git.1814.love:8443/wx/HL/issues/4417) |
|
||||
| PR | [#4418 feat(order-v3/refund): 退款明细Tab懒加载出参重构](https://git.1814.love:8443/wx/HL/pulls/4418) |
|
||||
| 后端负责人 | yst |
|
||||
@@ -0,0 +1,46 @@
|
||||
# 房务月度对账 — 口径修复(已付/待付改已复核/待复核 + 订单数全局去重 + 数据源生命周期过滤)
|
||||
|
||||
> 变更类型:🐛 口径修复(后端,**对账数值口径与列头文案有变,前端需对照**)
|
||||
> 端类型:管理后台(房务·月度对账)
|
||||
> 日期:2026-06-26 | 工单:#4420 | PR:#4425 | 服务:hl-order-service-v3(已部署测试服,API 实测通过)
|
||||
> 接口:`GET /v3/admin/house/reconciliation/monthly` + `GET /v3/admin/house/reconciliation/monthly/export?format=xlsx|pdf`
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
对账模块(#4074)经审计确认一批口径/契约问题,已按口径决策修复。**接口路径、JSON 字段名均不变**,但部分数值口径与导出列头文案有调整,前端对照知悉即可(无需改字段对接,注意数值含义)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 「已付/待付」改称「已复核/待复核」(**列头文案变,JSON 字段名不变**)
|
||||
- 原「已付金额 / 待付金额」实际由订单级 `settlement_status=COMPLETED`(平台财务复核完成)驱动,**不等于「已付钱给酒店」**,易误读。现统一改称:
|
||||
- **已复核金额**(settlement_status=COMPLETED 部分)
|
||||
- **待复核金额**(应付 − 已复核)
|
||||
- **JSON 字段名保持 `paidAmount` / `unpaidAmount` 不变**(前端无需改取值);变化的是语义口径与 **Excel/PDF 导出的列头/合计行文案**(「已复核金额(元)」「已复核合计(元)」等)。
|
||||
- 前端若自渲染列头,请同步把「已付/待付」展示文案改为「已复核/待复核」。
|
||||
|
||||
## 2. 概览「订单数」改为全月全局去重(**数值口径变**)
|
||||
- 概览 `overview.orderCount` 原为各酒店去重订单数**相加** → 一个订单住 N 家酒店被重复计 N 次(违反字段「全月去重 orderId」约定)。
|
||||
- 现改为**全月全局去重 orderId**(一订单跨多酒店只计 1 次)。环比 `prevOrderCount`/`orderCountRatio` 同口径。
|
||||
- **影响**:跨酒店订单越多,新「订单数」会比旧值小(更准)。**列表行 `hotels[].orderCount`(按酒店)保持不变**。
|
||||
|
||||
## 3. 数据源加「生命周期过滤」(**totalAmount/待复核数值可能变小**)
|
||||
- 对账原按 stay_date + 现结(CASH_PAID) 全捞,不看订单结算阶段。现只纳入**已进入结算流程的有效订单**(settlement_status ∈ {PENDING, COMPLETED})。
|
||||
- **剔除**:未提交结算(NONE/空,即录了核单但从未 submit)+ 孤儿/已软删订单(回查不到)。
|
||||
- **影响**:`totalAmount`/`unpaidAmount` 会剔除这些虚增部分,比旧值更准(可能变小)。
|
||||
|
||||
## 4. 同物理酒店「分裂两行」已收敛(**hotelCount/列表行可能变少**)
|
||||
- 原同一家酒店可能裂成「有 hotelId 的行」+「name 兜底行」两条独立账单行,`hotelCount` 虚高。
|
||||
- 现聚合后做 name→id 二次收敛(同名唯一命中即合并;同名不同 id 的不合并)。
|
||||
- **影响**:`hotelCount` 与 `hotels[]` 行数更准(可能比旧值少),合并行的间夜/金额/订单数已正确累加。
|
||||
|
||||
## 5. 导出小修
|
||||
- PDF/Excel 环比行标签统一带「(较 YYYY-MM)」基准月后缀(原 Excel 缺、PDF 有,现对齐)。
|
||||
- 导出 `format` 参数行为不变(xlsx 默认 / pdf / 非法值报错)。
|
||||
|
||||
---
|
||||
|
||||
## 测试服实测(已通过)
|
||||
- `GET /monthly` 三月均 200、结构正确、内部一致性全 True(hotelCount==hotels长度、unpaidAmount==totalAmount−paidAmount、全局去重数 ≤ per-hotel 之和)。
|
||||
- Excel 导出解压确认列头为「已复核/待复核」、旧「已付/待付」已消除。
|
||||
- 注:测试库当前无现结(CASH_PAID)核单数据,数值为 0;行为正确性由单测(含跨酒店去重/收敛/生命周期过滤)覆盖。
|
||||
@@ -0,0 +1,267 @@
|
||||
# 已取消(CANCELLED)订单「应收总额 payableAmount」与「待收尾款 balanceAmount」归零修正(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(取值口径修正,字段签名不变;⚠️仅 CANCELLED 订单数值变化)
|
||||
- 关联 Issue:#4431(待收归零)、#4456(应收归零) PR:#4432、#4457
|
||||
- 日期:2026-06-26
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单取消(`orderStatus=CANCELLED`)是终态——合同终止、未来不再有任何应收。但此前 `payableAmount`(应收总额)和 `balanceAmount`(待收尾款)对取消单仍按正常公式派生,导致出现:
|
||||
|
||||
- **应收非 0**:取消单还显示「应收 3105 元」,财务误以为应该催款。
|
||||
- **待收异常大**:退款不回减已付金额(paidAmount 是毛累计),取消+退款单会被算出「待收 ≈ 整单额」,财务以为欠款待催。
|
||||
|
||||
两次修正:
|
||||
1. **PR #4432**(`balanceAmount` 归零):取消单待收尾款固定为 0,不再按公式派生。原因:合同终止后无待收,且旧公式在「取消+已退款」场景下把退款反算为欠款,严重误导财务。
|
||||
2. **PR #4457**(`payableAmount` 归零):取消单应收总额固定为 0。原因:取消=合同作废=应收关系消灭,无应收;同时补齐财务勾稽链,避免「应收非零、待收为零」自相矛盾(见下方勾稽链说明)。
|
||||
|
||||
**修正后取消单勾稽链(完全闭合)**:
|
||||
|
||||
```
|
||||
payableAmount(应收总额)= 0 ← 本次固定
|
||||
paidAmount(已付) = 历史值(不变,台账保留)
|
||||
refundAmount(已退) = 历史值(不变,台账保留)
|
||||
totalAmount(订单总价) = 历史值(不变,产品原价快照)
|
||||
balanceAmount(待收尾款) = 0 ← 本次固定
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/order/list`(别名 `/v3/admin/order`) | 列表项 OrderListItemRespVO:`payableAmount`、`balanceAmount` 对 CANCELLED 订单固定返回 `"0"` |
|
||||
| 2 | GET | `/v3/admin/order/{id}` | 出参 `data.main`(OrderMainVO):`payableAmount`、`balanceAmount` 对 CANCELLED 订单固定返回 `"0"` |
|
||||
| 3 | GET | `/v3/admin/order/{id}/finance` | 出参 FinanceVO:`payableAmount`、`balanceAmount` 对 CANCELLED 订单固定返回 `"0"` |
|
||||
|
||||
> 统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
>
|
||||
> **仅 `orderStatus=CANCELLED` 的订单受影响**;非取消单的任何字段均不变。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 订单列表 `GET /v3/admin/order/list`
|
||||
|
||||
- 认证:Bearer JWT(管理后台)
|
||||
- 幂等:是(纯查询)
|
||||
- 限流:无特殊限制
|
||||
- 使用场景:订单列表页每行展示「应收总额/待收尾款」财务列;取消单固定显示 0,不再误导催款。
|
||||
|
||||
### 2. 订单详情头部 `GET /v3/admin/order/{id}`
|
||||
|
||||
- 认证:Bearer JWT(管理后台)
|
||||
- 幂等:是(纯查询)
|
||||
- 限流:无特殊限制
|
||||
- 使用场景:订单详情页顶部财务摘要区;取消单显示「应收 0 / 待收 0」传达已结清语义。
|
||||
|
||||
### 3. 财务 Tab `GET /v3/admin/order/{id}/finance`
|
||||
|
||||
- 认证:Bearer JWT(管理后台)
|
||||
- 幂等:是(纯查询)
|
||||
- 限流:无特殊限制
|
||||
- 使用场景:订单详情财务 Tab 完整明细;取消单勾稽链展示为「应收 0 / 已付 X / 已退 X / 待收 0」。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
无变化(3 个接口入参均未改动)。
|
||||
|
||||
### 财务 Tab 路径参数(参考)
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | long(string) | 是 | 订单 ID(雪花 ID,路径参数)|
|
||||
|
||||
### 订单列表查询参数(节选,参考)
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| pageNo | integer | 是 | 页码(从 1 开始)|
|
||||
| pageSize | integer | 是 | 每页条数 |
|
||||
| orderStatus | string | 否 | 按状态筛选(传 `CANCELLED` 可只看取消单)|
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 口径修正字段(3 个接口统一)
|
||||
|
||||
| 字段 | 类型 | 本次变化 | 修正后语义 |
|
||||
|---|---|---|---|
|
||||
| payableAmount | string(decimal) | **取消单固定返回 `"0"`** | CANCELLED=0;其余=总价+增项−优惠(≥0)|
|
||||
| balanceAmount | string(decimal) | **取消单固定返回 `"0"`** | CANCELLED=0;其余=应收−已付+已退(≥0)|
|
||||
|
||||
### 不变字段(台账保留,历史值不清零)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| totalAmount | string(decimal) | 订单总价(产品原价快照)—— 取消单保留原价,不变 |
|
||||
| paidAmount | string(decimal) | 历史已付金额(毛累计)—— 取消单保留历史值,不变 |
|
||||
| refundAmount | string(decimal) | 历史已退金额(毛累计)—— 取消单保留历史值,不变 |
|
||||
| surchargeAmount | string(decimal) | 增加费用汇总(仅 FinanceVO)—— 不变 |
|
||||
| discountAmount | string(decimal) | 优惠费用汇总(仅 FinanceVO)—— 不变 |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
涉及订单状态枚举(参考,用于理解受影响范围):
|
||||
|
||||
| 枚举值 | 中文标签 | 受本次修正影响? |
|
||||
|---|---|---|
|
||||
| CANCELLED | 已取消 | **是**(payableAmount/balanceAmount 固定为 0)|
|
||||
| PENDING | 待支付 | 否 |
|
||||
| PAID | 已支付 | 否 |
|
||||
| IN_PROGRESS | 出行中 | 否 |
|
||||
| COMPLETED | 已完成 | 否 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
无新增(查询接口正常返回 code=200)。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型:取消单财务 Tab(应收/待收归零后)
|
||||
|
||||
订单已取消,原价 3105 / 优惠 0 / 从未支付:
|
||||
|
||||
请求 `GET /v3/admin/order/2068235251926114306/finance`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalAmount": "3105.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"discountAmount": "0.00",
|
||||
"payableAmount": "0",
|
||||
"paidAmount": "0.00",
|
||||
"refundAmount": "0.00",
|
||||
"balanceAmount": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
校验:取消单 payableAmount=0、balanceAmount=0;totalAmount=3105 保留历史原价。
|
||||
|
||||
### 8.2 边界:取消+已退款单(最能体现修正价值的场景)
|
||||
|
||||
订单已取消,原价 3105 / 优惠 0 / 已付定金 800 / 全退 800:
|
||||
|
||||
```json
|
||||
{
|
||||
"totalAmount": "3105.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"discountAmount": "0.00",
|
||||
"payableAmount": "0",
|
||||
"paidAmount": "800.00",
|
||||
"refundAmount": "800.00",
|
||||
"balanceAmount": "0"
|
||||
}
|
||||
```
|
||||
|
||||
修正前:balanceAmount 旧公式 = 3105 − 800 + 800 = 3105(误报"欠款 3105");修正后固定 0,财务不会再误判催款。
|
||||
|
||||
### 8.3 正常单对比(非 CANCELLED,不受影响)
|
||||
|
||||
订单状态 PENDING,原价 3105 / 优惠 150 / 未支付:
|
||||
|
||||
```json
|
||||
{
|
||||
"totalAmount": "3105.00",
|
||||
"surchargeAmount": "0.00",
|
||||
"discountAmount": "150.00",
|
||||
"payableAmount": "2955.00",
|
||||
"paidAmount": "0.00",
|
||||
"refundAmount": "0.00",
|
||||
"balanceAmount": "2955.00"
|
||||
}
|
||||
```
|
||||
|
||||
非 CANCELLED 订单的口径与此前完全一致,payableAmount/balanceAmount 按公式正常派生,**不受本次修正影响**。
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用(本次修正生效)**:
|
||||
- `orderStatus=CANCELLED` 的订单,全部接口的 payableAmount 和 balanceAmount 均固定返回 `"0"`。
|
||||
- 无论取消前是否有支付/退款记录,payableAmount 和 balanceAmount 均为 0。
|
||||
|
||||
**不适用(口径不变)**:
|
||||
- `orderStatus≠CANCELLED` 的全部订单——应收/待收仍按原有公式派生,不受本次改动影响。
|
||||
|
||||
**特殊边界**:
|
||||
- 历史台账字段(`totalAmount` / `paidAmount` / `refundAmount`)对取消单**保留历史值不清零**,供财务审计追溯。
|
||||
- 取消单的 `totalAmount` 仍是产品原价快照,不代表"应收",前端展示时注意语义区分(原价 vs 应收)。
|
||||
- 若取消后再发起退款(极少数场景),`refundAmount` 会更新历史值,但 `payableAmount` 和 `balanceAmount` 仍固定为 0。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 字段级对比(仅 CANCELLED 订单)
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| payableAmount | 按公式派生(总价+增项−优惠,可能非零)| **固定 `"0"`** |
|
||||
| balanceAmount | 按公式派生(应收−已付+已退,可能非零)| **固定 `"0"`** |
|
||||
| totalAmount | 不变 | 不变(历史原价保留)|
|
||||
| paidAmount | 不变 | 不变(历史已付保留)|
|
||||
| refundAmount | 不变 | 不变(历史已退保留)|
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 取消未付单 | payableAmount=3105(误,无应收关系)| payableAmount=0(正确)|
|
||||
| 取消+全退单 | balanceAmount=3105(误,误报欠款)| balanceAmount=0(正确)|
|
||||
| 财务勾稽链 | 取消单「应收非零+待收非零」自相矛盾 | 取消单「应收 0 / 待收 0」完全闭合 |
|
||||
| 非取消单 | — | 完全不变 |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
**破坏兼容性评估**:
|
||||
- 字段签名不变(字段名/类型均未变),结构兼容。
|
||||
- 但 **CANCELLED 订单的 `payableAmount` 和 `balanceAmount` 数值会从「旧算法值」变为 `"0"`**,属于语义修正,前端展示数字会变化。
|
||||
- 如果前端有针对取消单 `payableAmount`/`balanceAmount` 做非零判断(如「若待收>0 显示催款按钮」),取消单的该判断会自动消除——这是预期行为。
|
||||
|
||||
**前端同步上线**:无需前端改代码,接口数值自动修正,前端渲染字段取值即可。建议前端同步告知财务人员:「已取消订单的应收和待收均为 0,是修正后的正确值」。
|
||||
|
||||
**回滚方案**:
|
||||
- 后端回滚 PR #4432(balanceAmount 归零逻辑)+ PR #4457(payableAmount 归零逻辑)即可恢复旧算法。
|
||||
- 3 个接口均为无状态查询,回滚零风险,不涉及数据写入。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段均为**字符串(string/decimal)**,前端按字符串处理,勿做 JS number 精度运算。
|
||||
- 取消单的 `totalAmount`(订单总价/产品原价)仍是历史快照非零值,它代表「产品曾经值多少钱」,不代表「还需要收多少钱」——如需展示「原价」用 `totalAmount`,展示「待收」用 `balanceAmount`(取消单固定 0)。
|
||||
- 已取消订单在财务口径上:应收 0、待收 0,历史已付/已退保留台账,前端展示时注意区分「原价 vs 应收 vs 已付 vs 已退」四个语义。
|
||||
- 本次修正**仅影响 `orderStatus=CANCELLED`** 的订单,与其他状态的计算逻辑完全隔离。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue(待收归零):https://git.1814.love:8443/wx/HL/issues/4431
|
||||
- PR(待收归零):https://git.1814.love:8443/wx/HL/pulls/4432
|
||||
- Issue(应收归零):https://git.1814.love:8443/wx/HL/issues/4456
|
||||
- PR(应收归零):https://git.1814.love:8443/wx/HL/pulls/4457
|
||||
- 后端负责人:腰苏图
|
||||
- 已合并至 dev-v3,测试服部署后可用取消单实调验证(finance/详情头部/列表 三接口取消单 payableAmount=0、balanceAmount=0)。
|
||||
@@ -0,0 +1,309 @@
|
||||
# 订单预支管理(创建/审批/驳回/撤回/列表)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:新增接口
|
||||
- 关联:Issue #4429 / #4440 / #4452 | PR #4429 / #4442 / #4453
|
||||
- 服务:hl-order-service-v3
|
||||
- 日期:2026-06-26
|
||||
|
||||
> 本文为「订单预支借款」功能的最终态契约(合并 #4429 落地 + #4440 借款类型字典 + #4452 去草稿态),前端按本文对接即可,无需翻历史版本。
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
常规订单详情提供「预支借款」能力:定制师为订单的**某位人员**(借款对象,从本单人员下拉选定,默认主报账人,#4465)申请一笔预支款,财务审批通过/驳回。预支金额受订单待收尾款上限约束,借款类型走数据字典。
|
||||
|
||||
**流程(去草稿后,#4452)**:创建即进「待审批」→ 财务通过(APPROVED,触发后置事件)/ 驳回(REJECTED)。财务审批前申请人可撤回。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/order/{orderId}/advance/payee-candidates` | 借款对象候选下拉(全部人员,主报账人默认,#4465) |
|
||||
| 2 | POST | `/v3/admin/order/{orderId}/advance` | 创建预支(创建即待审批 SUBMITTED;#4465 入参加 payeeStaffId) |
|
||||
| 3 | PUT | `/v3/admin/order/advance/{advanceId}/approve` | 财务审批通过 |
|
||||
| 4 | PUT | `/v3/admin/order/advance/{advanceId}/reject` | 财务审批驳回 |
|
||||
| 5 | DELETE | `/v3/admin/order/advance/{advanceId}` | 撤回待审批预支(仅 SUBMITTED 可撤) |
|
||||
| 6 | GET | `/v3/admin/order/{orderId}/advances` | 分页查询本单预支列表 |
|
||||
|
||||
**⚠️ 已删除端点(#4452,旧路径调用返回 code=404「接口不存在」)**:
|
||||
|
||||
| 方法 | 旧路径 | 处理 |
|
||||
|---|---|---|
|
||||
| PUT | `/v3/admin/order/advance/{advanceId}/submit` | 已删除。去草稿后创建即待审批,无需单独提交动作 |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
统一响应包:`{ code, message, data, success }`,`code=200` 成功;业务异常 `code` 为对应错误码(见 ⑦),HTTP 状态恒 200。
|
||||
|
||||
### 0. 借款对象候选下拉 — GET `/v3/admin/order/{orderId}/advance/payee-candidates`(#4465)
|
||||
|
||||
返回本单全部人员(order 人员配置),供创建预支时选择借款对象。主报账人(reporter_rank=PRIMARY)排第一并标 `isDefault=true`,前端默认选中。候选项 `id` 即创建预支的 `payeeStaffId` 入参。返回 `PageResult`(单页全量,`records / total`)。
|
||||
|
||||
### 1. 创建预支 — POST `/v3/admin/order/{orderId}/advance`
|
||||
|
||||
入口门槛(不满足抛对应错误码):
|
||||
- 订单状态 ∈ {待出发 PENDING_DEPARTURE / 出行中 TRAVELLING},否则 585001(#4462 收紧:已完成 COMPLETED、已取消 CANCELLED、定制中、待付款均不可创建预支)
|
||||
- 订单结算状态 ≠ 已结算完成,否则 585001
|
||||
- 借款对象 `payeeStaffId` 须为本单人员(取候选下拉的 id),否则 585007(#4465)
|
||||
- 借款类型须为字典 `advance_type` 内的值,否则 585006
|
||||
- 预支金额 0 < amount ≤ 可用上限(可用上限 = 待收尾款 − 本单在途预支之和),否则 585003 / 585004
|
||||
|
||||
借款对象由前端从候选下拉选定(#4465,不再强制主报账人,去掉原 585002 门槛),后端校验属本单后快照该人员姓名/角色写入。创建成功状态直接为 `SUBMITTED`。
|
||||
|
||||
### 2. 审批通过 — PUT `/v3/admin/order/advance/{advanceId}/approve`
|
||||
|
||||
仅 `SUBMITTED` 可通过,否则 585005。写 `approvedBy`/`approvedAt`,状态置 `APPROVED`,触发后置事件(出账逻辑后续补充)。
|
||||
|
||||
### 3. 审批驳回 — PUT `/v3/admin/order/advance/{advanceId}/reject`
|
||||
|
||||
仅 `SUBMITTED` 可驳回,否则 585005。须填驳回原因,状态置 `REJECTED`。
|
||||
|
||||
### 4. 撤回待审批 — DELETE `/v3/admin/order/advance/{advanceId}`
|
||||
|
||||
仅 `SUBMITTED` 可撤回(软删),财务已审批(APPROVED/REJECTED)不可撤回,否则 585005。
|
||||
|
||||
### 5. 分页查询 — GET `/v3/admin/order/{orderId}/advances`
|
||||
|
||||
按 create_time 倒序返回本单预支列表,分页参数 `page`/`pageSize`。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 创建预支 请求体 CreateAdvanceReqVO
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| payeeStaffId | Long(字符串) | 是 | 借款对象,取候选下拉项的 id(#4465),须为本单人员,否则 585007 |
|
||||
| advanceType | String | 是 | 借款类型,取字典 `advance_type` 的 dictValue(见 ⑥) |
|
||||
| amount | BigDecimal | 是 | 预支金额,须 > 0(≥ 0.01) |
|
||||
| purpose | String | 否 | 用途说明 |
|
||||
| voucherUrl | String | 否 | 凭证文件 URL |
|
||||
|
||||
### 候选下拉 / 分页查询 path
|
||||
|
||||
候选下拉 `GET /v3/admin/order/{orderId}/advance/payee-candidates` 仅路径参数 `orderId`,无 query。
|
||||
|
||||
### 驳回 请求体 RejectAdvanceReqVO
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| reason | String | 是 | 驳回原因 |
|
||||
|
||||
### 分页查询 query
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| pageSize | int | 否 | 20 | 每页条数 |
|
||||
|
||||
approve / reject / revoke 仅路径参数 `advanceId`(approve/revoke 无请求体)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
创建/审批/驳回 返回 `data` 为单个 OrderAdvanceRespVO;分页查询 `data` 为 `PageResult<OrderAdvanceRespVO>`;候选下拉 `data` 为 `PageResult<AdvancePayeeCandidateVO>`。
|
||||
|
||||
### AdvancePayeeCandidateVO(借款对象候选,#4465)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String | 候选 ID(=创建预支 payeeStaffId,order 人员分配 ID) |
|
||||
| staffName | String | 员工姓名 |
|
||||
| staffRole | String | 员工角色代码(DRIVER/LEADER/PHOTOGRAPHER/OTHER 等) |
|
||||
| staffRoleText | String | 员工角色文案(司机/导游/摄影师/其他) |
|
||||
| reporterRank | String | 报账人等级(PRIMARY/SECONDARY/NONE) |
|
||||
| isDefault | boolean | 是否默认选中(主报账人为 true,前端默认选此项) |
|
||||
|
||||
### OrderAdvanceRespVO
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | String | 预支 ID(雪花,字符串) |
|
||||
| orderId | String | 订单 ID(字符串) |
|
||||
| payeeStaffId | String | 报账人 staff 分配 ID(快照) |
|
||||
| payeeName | String | 报账人姓名 |
|
||||
| payeeRole | String | 报账人角色代码(DRIVER/LEADER/PHOTOGRAPHER/OTHER 等) |
|
||||
| payeeRoleText | String | 报账人角色文案(DRIVER→司机/LEADER→导游/PHOTOGRAPHER→摄影师/OTHER→其他) |
|
||||
| advanceType | String | 借款类型代码(字典 advance_type dictValue) |
|
||||
| amount | BigDecimal | 预支金额 |
|
||||
| purpose | String | 用途说明 |
|
||||
| voucherUrl | String | 凭证文件 URL |
|
||||
| status | String | 状态代码(SUBMITTED/APPROVED/REJECTED) |
|
||||
| statusText | String | 状态文案(待审批/已通过/已驳回) |
|
||||
| rejectReason | String | 驳回原因(仅 REJECTED 有值) |
|
||||
| createdByName | String | 创建人姓名 |
|
||||
| createTime | DateTime | 创建时间 |
|
||||
| submittedAt | DateTime | 提交审批时间(创建即填充) |
|
||||
| approvedAt | DateTime | 审批时间(通过/驳回时填充) |
|
||||
| approvedBy | String | 审批人姓名 |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
### 状态 status(#4452 去 DRAFT,3 态)
|
||||
|
||||
| code | 文案 statusText | 说明 |
|
||||
|---|---|---|
|
||||
| SUBMITTED | 待审批 | 创建后初始态,财务未审批前可撤回 |
|
||||
| APPROVED | 已通过 | 财务审批通过(终态,触发后置事件) |
|
||||
| REJECTED | 已驳回 | 财务审批驳回,含 rejectReason(终态) |
|
||||
|
||||
> 旧 `DRAFT`(草稿)状态已删除,出参不再出现。
|
||||
|
||||
### 借款类型 advanceType(数据字典 `advance_type`,#4440)
|
||||
|
||||
| dictValue | dictLabel |
|
||||
|---|---|
|
||||
| ACCOMMODATION_DEPOSIT | 住宿押金 |
|
||||
| TICKET | 门票 |
|
||||
| CATERING | 餐饮 |
|
||||
| TRANSPORT | 交通用车 |
|
||||
|
||||
> 字典项可后台维护,前端建议通过字典接口 `GET /admin/dict/data/advance_type` 动态取值,不要硬编码。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| code | 含义 |
|
||||
|---|---|
|
||||
| 585000 | 预支记录不存在 |
|
||||
| 585001 | 订单当前状态不可创建预支 |
|
||||
| 585002 | ~~订单尚未配置主报账人~~(#4465 起借款对象可选任一人员,本码已废弃不再返回) |
|
||||
| 585003 | 预支金额必须大于 0 |
|
||||
| 585004 | 预支金额超过可用余额上限 |
|
||||
| 585005 | 预支当前状态不允许此操作(如撤回/审批一条非 SUBMITTED 的记录) |
|
||||
| 585006 | 借款类型非法(须为字典 advance_type 内的值) |
|
||||
| 585007 | 借款对象不属于本订单人员(payeeStaffId 非本单人员,#4465) |
|
||||
| 400 | 参数校验失败(如 payeeStaffId 未填「借款对象必填」、advanceType 未填「借款类型必填」、amount 为空/≤0、reason 未填) |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:借款对象候选下拉(#4465)
|
||||
|
||||
请求 `GET /v3/admin/order/2070038865875230722/advance/payee-candidates`
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true, "message": "成功",
|
||||
"data": {
|
||||
"total": 2, "page": 1, "pageSize": 2,
|
||||
"records": [
|
||||
{ "id": "9000000000000000001", "staffName": "刘领队", "staffRole": "LEADER", "staffRoleText": "导游", "reporterRank": "PRIMARY", "isDefault": true },
|
||||
{ "id": "9000000000000000002", "staffName": "孙师傅", "staffRole": "DRIVER", "staffRoleText": "司机", "reporterRank": "NONE", "isDefault": false }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 典型:创建预支(#4465 带 payeeStaffId)
|
||||
|
||||
请求 `POST /v3/admin/order/2070038865875230722/advance`
|
||||
```json
|
||||
{ "payeeStaffId": "9000000000000000002", "advanceType": "TICKET", "amount": 100, "purpose": "景区门票预付" }
|
||||
```
|
||||
响应
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true, "message": "成功",
|
||||
"data": {
|
||||
"id": "2070423136842539010", "orderId": "2070038865875230722",
|
||||
"payeeStaffId": "9000000000000000002", "payeeName": "孙师傅",
|
||||
"payeeRole": "DRIVER", "payeeRoleText": "司机",
|
||||
"advanceType": "TICKET", "amount": 100, "purpose": "景区门票预付",
|
||||
"status": "SUBMITTED", "statusText": "待审批",
|
||||
"createdByName": "定制师A", "submittedAt": "2026-06-26 16:25:17",
|
||||
"approvedAt": null, "approvedBy": null, "rejectReason": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 异常:借款对象不属于本单(#4465)
|
||||
|
||||
请求体 `{ "payeeStaffId": "999999", "advanceType": "TICKET", "amount": 100 }`
|
||||
```json
|
||||
{ "code": 585007, "success": false, "message": "借款对象不属于本订单人员", "data": null }
|
||||
```
|
||||
|
||||
### 边界:审批通过
|
||||
|
||||
请求 `PUT /v3/admin/order/advance/2070423411452010498/approve`
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "status": "APPROVED", "statusText": "已通过", "approvedBy": "admin", "approvedAt": "2026-06-26 16:25:40" } }
|
||||
```
|
||||
|
||||
### 异常:撤回一条已审批的预支
|
||||
|
||||
请求 `DELETE /v3/admin/order/advance/2070423411452010498`
|
||||
```json
|
||||
{ "code": 585005, "success": false, "message": "预支当前状态不允许此操作", "data": null }
|
||||
```
|
||||
|
||||
### 异常:调用已删除的 submit 端点
|
||||
|
||||
请求 `PUT /v3/admin/order/advance/{advanceId}/submit`
|
||||
```json
|
||||
{ "code": 404, "success": false, "message": "接口不存在: PUT /v3/admin/order/advance/{advanceId}/submit", "data": null }
|
||||
```
|
||||
|
||||
### 异常:借款类型非法
|
||||
|
||||
请求体 `{ "advanceType": "FOO", "amount": 100 }`
|
||||
```json
|
||||
{ "code": 585006, "success": false, "message": "借款类型非法(须为字典 advance_type 内的值)", "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 仅订单状态为「待出发 / 出行中」时可创建预支(#4462);已完成、已取消、定制中、待付款均返 585001。
|
||||
- 一个订单可有多笔预支;在途(SUBMITTED + APPROVED)金额之和不得超过订单待收尾款。
|
||||
- 借款对象(payee)由前端从本单人员候选下拉选定(#4465),创建时快照该人员姓名/角色,预支记录创建后不随人员变更而变化;候选默认选中主报账人。
|
||||
- 仅 SUBMITTED 可被 approve / reject / 撤回;APPROVED / REJECTED 为终态。
|
||||
- 撤回为软删,列表不再返回被撤回记录。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比(#4452 去草稿)
|
||||
|
||||
| 维度 | 旧(#4429 初版) | 新(#4452) |
|
||||
|---|---|---|
|
||||
| 流程 | 草稿 DRAFT → 提交 SUBMITTED → 审批 | 创建即 SUBMITTED → 审批 |
|
||||
| 创建后状态 | DRAFT(草稿) | SUBMITTED(待审批) |
|
||||
| 提交端点 | PUT `.../submit` | 已删除(404) |
|
||||
| DELETE 语义 | 删除草稿(仅 DRAFT) | 撤回待审批(仅 SUBMITTED) |
|
||||
| status 取值 | DRAFT/SUBMITTED/APPROVED/REJECTED | SUBMITTED/APPROVED/REJECTED |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- 前端:去掉「保存草稿 / 提交」两步交互,改为「创建即提交审批」;删除按钮语义改为「撤回」(仅待审批可点);状态枚举去掉草稿。
|
||||
- 后端:去草稿态随 PR #4453 上线,Flyway V20260626_005 已执行(status 默认值 SUBMITTED + 历史 DRAFT 行迁移)。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 所有 ID 字段(id/orderId/payeeStaffId)为字符串,避免 JS 精度丢失。
|
||||
- 借款类型务必从字典接口取值,字典可后台扩展。
|
||||
- 金额上限由后端依据订单待收尾款实时计算,前端提交后以后端校验为准。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4429 | /4440 | /4452 | /4462 | /4465
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4429 | /4442 | /4453 | /4463 | /4466
|
||||
- 负责人:yst(腰苏图)
|
||||
@@ -0,0 +1,405 @@
|
||||
# 订单时间线 11 事件类型扩展 + 转单入参正式生效
|
||||
|
||||
- **变更类型**:修改接口
|
||||
- **端类型**:管理后台
|
||||
- **日期**:2026-06-26
|
||||
- **服务**:hl-order-service-v3
|
||||
- **关联 PR**:#4422 #4426 #4434 #4449 #4454 #4464
|
||||
- **关联 Issue**:#4419 #4423 #4428 #4446 #4451 #4459
|
||||
- **后端负责人**:yst
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单时间线 Epic(6 PR 合并周期)带来两处接口变更:
|
||||
|
||||
1. **GET /v3/admin/order/{id}/status-log**:时间线出参 `eventType` 新增 11 个枚举值,覆盖增减项、合同、保险、退款、发票等业务动作,前端需按 eventType 渲染对应 label 与 content 文本。
|
||||
2. **PUT /v3/admin/order/{id}**(修改订单字段):原先 `targetConsultantId` + `transferReason` 两个字段静默忽略(后端收到但不执行),现在**正式生效**——传这两个字段即触发真实转单逻辑。⚠️ 若前端已在传这两个字段,行为将从「无效」变为「真改归属」。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 接口 | 变更类型 | 变更描述 |
|
||||
|---|---|---|
|
||||
| GET /v3/admin/order/{id}/status-log | ✨ 出参枚举扩展 | eventType 新增 11 个取值(详见 §⑥) |
|
||||
| GET /v3/admin/order/{id}/status-log | ✨ 出参新增触发场景 | INFO_ADJUST、REFUND_RECEIVED 新增触发场景(枚举值本身不变) |
|
||||
| PUT /v3/admin/order/{id} | ⚠️ 入参行为变更 | targetConsultantId + transferReason 从「静默忽略」变为「真转单」 |
|
||||
| PUT /v3/admin/order/{id} | ✨ 新增错误码 | 581042「目标定制师不存在或已停用」 |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 接口 A:GET /v3/admin/order/{id}/status-log
|
||||
|
||||
- **描述**:获取订单详情记录 Tab 的时间线列表
|
||||
- **认证**:需要管理员 JWT(Bearer Token)
|
||||
- **幂等性**:只读,天然幂等
|
||||
- **限流**:无特殊限流
|
||||
|
||||
### 接口 B:PUT /v3/admin/order/{id}
|
||||
|
||||
- **描述**:修改订单字段(含转单字段)
|
||||
- **认证**:需要管理员 JWT(Bearer Token)
|
||||
- **幂等性**:非幂等;同一 targetConsultantId 重复调用不报错但无实际变化(归属已是目标定制师则不重复写时间线)
|
||||
- **限流**:无特殊限流
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
### 接口 A(GET /v3/admin/order/{id}/status-log)
|
||||
|
||||
#### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Long | 是 | 订单 ID(雪花 ID,字符串形式传入) |
|
||||
|
||||
无请求体。
|
||||
|
||||
### 接口 B(PUT /v3/admin/order/{id})
|
||||
|
||||
#### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | Long | 是 | 订单 ID |
|
||||
|
||||
#### 4.2 请求体字段(仅列出本次变更相关字段,其余字段不变)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| targetConsultantId | Long | 否 | 转单目标定制师的 adminId。不传或与当前一致则不转单;传入且与当前不同则触发真实转单。 |
|
||||
| transferReason | String | 否 | 转单原因备注,配合 targetConsultantId 使用;targetConsultantId 未传时忽略 |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### 接口 A(GET /v3/admin/order/{id}/status-log)
|
||||
|
||||
出参结构未变,records 数组每项字段如下:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| records | Array | 时间线记录列表,按 changedAt 降序 |
|
||||
| records[].id | String | 记录 ID |
|
||||
| records[].eventType | String | 事件类型枚举(见 §⑥ 完整枚举表) |
|
||||
| records[].content | String | 事件描述文本(见 §⑥ content 样例,后端生成,直接渲染) |
|
||||
| records[].changedAt | String | 事件发生时间,ISO8601 格式 |
|
||||
| records[].operatorName | String | 操作人名称(系统触发时为「系统」) |
|
||||
| records[].fromStatus | String | 事件发生时订单粗状态快照 |
|
||||
| records[].toStatus | String | 事件发生后订单粗状态快照 |
|
||||
| records[].fromStatusName | String | fromStatus 中文名 |
|
||||
| records[].toStatusName | String | toStatus 中文名 |
|
||||
| records[].extra | Object | 附加结构化数据,按 eventType 不同而不同(可为 null) |
|
||||
|
||||
### 接口 B(PUT /v3/admin/order/{id})
|
||||
|
||||
出参结构不变,仍返回 `Result<Void>`(成功时 data 为 null,code=200)。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
### eventType 完整枚举表(含本次新增)
|
||||
|
||||
| eventType 值 | label | content 文本样例 | 是否本次新增 |
|
||||
|---|---|---|---|
|
||||
| ORDER_CREATED | 创建订单 | 创建订单 | 否 |
|
||||
| ORDER_CONFIRMED | 确认订单 | 确认订单 | 否 |
|
||||
| ORDER_CANCELLED | 订单取消 | 订单取消 | 否 |
|
||||
| ORDER_TRIP_STARTED | 出行开始 | 出行开始 | 否 |
|
||||
| ORDER_TRIP_TERMINATED | 终止行程 | 终止行程 | 否 |
|
||||
| ORDER_COMPLETED | 订单完成 | 订单完成 | 否 |
|
||||
| PAYMENT_RECEIVED | 收款记录 | 收款 ¥6000(微信支付) | 否 |
|
||||
| PAYMENT_DEPOSIT | 定金收款 | 定金收款 ¥2000 | 否 |
|
||||
| TRAVELER_CHANGED | 出行人变更 | 新增出行人:张三 / 移除出行人:李四 | 否 |
|
||||
| INFO_ADJUST | 订单信息调整 | 修改客户信息 | 否(新增触发场景:改客户姓名/电话/紧急联系人/备注) |
|
||||
| REFUND_RECEIVED | 退款到账 | ¥6000 已到账(手动完成) | 否(新增触发场景:退款手动完成) |
|
||||
| ADJUSTMENT_ADD | 新增增减项 | 新增优惠 -¥500:VIP优惠 / 新增附加费 +¥500:船票升舱 | **是** |
|
||||
| ADJUSTMENT_REVERSE | 撤销增减项 | 撤销优惠 ¥500:录错 / 撤销附加费 ¥300:录错 | **是** |
|
||||
| CONSULTANT_TRANSFER | 转单 | 转单:张三→李四 | **是** |
|
||||
| CONTRACT_CREATED | 创建合同 | 已创建合同:HT202601001 / 已报备合同:HT202601001 | **是** |
|
||||
| CONTRACT_INVALIDATED | 作废合同 | 已作废合同:HT202601001 | **是** |
|
||||
| INSURANCE_PURCHASED | 投保 | 已投保 保游金标准方案(3人) | **是** |
|
||||
| INSURANCE_CANCELLED | 退保 | 已退保:BY2026001234 | **是** |
|
||||
| REFUND_REVIEWED | 退款审核 | 退款审核通过 ¥6000 / 退款审核驳回:金额超出实付 | **是** |
|
||||
| REFUND_EXECUTING | 退款发起 | 退款发起 ¥6000 | **是** |
|
||||
| INVOICE_APPLIED | 申请开票 | 申请开票:呼籁旅行科技有限公司 | **是** |
|
||||
|
||||
> **eventType 分类说明**:
|
||||
> - ADJUSTMENT_ADD / ADJUSTMENT_REVERSE:DATA 类(订单内数据变更),fromStatus/toStatus 为当时订单粗状态快照,不代表状态发生了迁移。
|
||||
> - CONSULTANT_TRANSFER:DATA 类,归属变更不触发订单状态迁移。
|
||||
> - CONTRACT_CREATED / CONTRACT_INVALIDATED / INSURANCE_PURCHASED / INSURANCE_CANCELLED / REFUND_REVIEWED / REFUND_EXECUTING / INVOICE_APPLIED:DATA 类,均为业务动作记录。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
### 接口 B(PUT /v3/admin/order/{id})新增错误码
|
||||
|
||||
| 错误码 | message | 触发场景 |
|
||||
|---|---|---|
|
||||
| 581042 | 目标定制师不存在或已停用 | targetConsultantId 对应的 admin 用户不存在,或该用户账号已被停用 |
|
||||
|
||||
### 已有错误码(保持不变,供参考)
|
||||
|
||||
| 错误码 | message | 说明 |
|
||||
|---|---|---|
|
||||
| 581001 | 订单不存在 | id 对应订单不存在 |
|
||||
| 400 | 参数校验失败 | 请求体字段类型/格式错误 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功示例
|
||||
|
||||
#### 接口 A:获取时间线(含新枚举值)
|
||||
|
||||
请求:
|
||||
```
|
||||
GET /v3/admin/order/1234567890123456789/status-log
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": "9876543210",
|
||||
"eventType": "ADJUSTMENT_ADD",
|
||||
"content": "新增优惠 -¥500:VIP优惠",
|
||||
"changedAt": "2026-06-25T14:30:00+08:00",
|
||||
"operatorName": "李定制",
|
||||
"fromStatus": "CONFIRMED",
|
||||
"toStatus": "CONFIRMED",
|
||||
"fromStatusName": "已确认",
|
||||
"toStatusName": "已确认",
|
||||
"extra": null
|
||||
},
|
||||
{
|
||||
"id": "9876543211",
|
||||
"eventType": "CONSULTANT_TRANSFER",
|
||||
"content": "转单:张三→李四",
|
||||
"changedAt": "2026-06-25T10:00:00+08:00",
|
||||
"operatorName": "系统",
|
||||
"fromStatus": "CONFIRMED",
|
||||
"toStatus": "CONFIRMED",
|
||||
"fromStatusName": "已确认",
|
||||
"toStatusName": "已确认",
|
||||
"extra": null
|
||||
},
|
||||
{
|
||||
"id": "9876543212",
|
||||
"eventType": "INSURANCE_PURCHASED",
|
||||
"content": "已投保 保游金标准方案(3人)",
|
||||
"changedAt": "2026-06-24T16:20:00+08:00",
|
||||
"operatorName": "系统",
|
||||
"fromStatus": "CONFIRMED",
|
||||
"toStatus": "CONFIRMED",
|
||||
"fromStatusName": "已确认",
|
||||
"toStatusName": "已确认",
|
||||
"extra": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 接口 B:转单成功
|
||||
|
||||
请求:
|
||||
```
|
||||
PUT /v3/admin/order/1234567890123456789
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"targetConsultantId": 987654321,
|
||||
"transferReason": "原定制师休假,转交李四跟进"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 边界情况示例
|
||||
|
||||
#### 转单字段与当前归属相同(不触发转单,静默成功)
|
||||
|
||||
请求(当前归属已是 adminId=123456789):
|
||||
```json
|
||||
{
|
||||
"targetConsultantId": 123456789,
|
||||
"transferReason": "测试"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
不会写入 CONSULTANT_TRANSFER 时间线记录。
|
||||
|
||||
#### 不传转单字段(其他字段正常修改,归属不变)
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"customerRemark": "客户备注更新"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.3 业务失败示例
|
||||
|
||||
#### targetConsultantId 目标定制师不存在或已停用
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"targetConsultantId": 999999999,
|
||||
"transferReason": "转单测试"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581042,
|
||||
"msg": "目标定制师不存在或已停用",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
### 适用场景
|
||||
|
||||
- 接口 A:任何状态的订单均可查询时间线(只读)。
|
||||
- 接口 B 转单:订单状态为非终态(未取消、未完成)时可转单;已取消或已完成订单会被业务守卫拦截。
|
||||
|
||||
### 不适用场景
|
||||
|
||||
- 不可通过时间线接口触发业务动作,时间线为只读查询。
|
||||
- 转单不可将订单归属给已停用的管理员账号(报 581042)。
|
||||
- transferReason 在 targetConsultantId 未传时无意义,后端忽略。
|
||||
|
||||
### 特殊边界
|
||||
|
||||
- ADJUSTMENT_ADD / ADJUSTMENT_REVERSE 的 fromStatus/toStatus 是当时订单粗状态的快照,这两个事件本身**不改变订单状态**,前端时间线渲染不需要显示状态迁移箭头。
|
||||
- CONSULTANT_TRANSFER 同上,仅归属变更,不改变订单主状态。
|
||||
- 时间线记录不可删除,历史订单在本次上线前发生的对应操作无时间线记录属正常现象(该类事件在此次上线前未记录)。
|
||||
- targetConsultantId 传 null 或不传,后端视为「不转单」,与历史行为完全一致。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 接口 A:eventType 枚举值扩展
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| eventType 取值域 | 11 个基础值(ORDER_CREATED 等) | 在原有基础上新增 11 个值:ADJUSTMENT_ADD / ADJUSTMENT_REVERSE / CONSULTANT_TRANSFER / CONTRACT_CREATED / CONTRACT_INVALIDATED / INSURANCE_PURCHASED / INSURANCE_CANCELLED / REFUND_REVIEWED / REFUND_EXECUTING / INVOICE_APPLIED |
|
||||
| INFO_ADJUST 触发场景 | 仅原有场景 | 新增:改客户姓名/电话/紧急联系人/备注时写入 |
|
||||
| REFUND_RECEIVED 触发场景 | 仅原有场景 | 新增:退款手动标记完成时写入 |
|
||||
| 出参字段结构 | 不变 | 不变 |
|
||||
|
||||
### 接口 B:转单字段行为变更
|
||||
|
||||
| 维度 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| targetConsultantId 传非空值行为 | **静默忽略**(后端接收但不执行任何操作) | **正式生效**:触发转单逻辑,改变订单归属定制师 |
|
||||
| transferReason 传值行为 | **静默忽略** | 正式生效:记录转单备注,写入 CONSULTANT_TRANSFER 时间线 |
|
||||
| 转单时间线记录 | 无 | 自动写入 CONSULTANT_TRANSFER 事件 |
|
||||
| 错误码 581042 | 不存在 | **新增**,targetConsultantId 校验失败时返回 |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
### ⚠️ 破坏兼容风险(高优先级确认)
|
||||
|
||||
**接口 B 转单字段行为变更是破坏性变更。**
|
||||
|
||||
请前端确认:
|
||||
1. 现有「修改订单」表单是否已在传 `targetConsultantId` 字段?
|
||||
- 若**已在传**:上线后该字段会真实改变订单归属,需评估是否符合预期,或在前端侧暂时移除该字段传入,等转单 UI 正式上线再接入。
|
||||
- 若**未传**:无影响。
|
||||
2. `transferReason` 同上评估逻辑。
|
||||
|
||||
### 前端同步上线要求
|
||||
|
||||
- **接口 A 时间线**:新枚举值前端若不处理,时间线条目的 label/icon 会命中 fallback。建议按 §⑥ 枚举表逐一补充渲染逻辑(content 文本由后端返回,直接展示即可)。
|
||||
- **接口 B 转单**:确认现有表单是否已传转单字段,按需调整后再上线。
|
||||
|
||||
### 回滚方案
|
||||
|
||||
- 后端可通过 Nacos 开关关闭时间线新事件写入(不影响已有记录)。
|
||||
- 接口 B 转单生效逻辑如需紧急回滚,联系后端 yst 单独发布回滚版本。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **时间线 content 文本由后端生成**,前端直接渲染 records[].content 字符串,无需前端拼接或翻译枚举值。
|
||||
2. **eventType 枚举将随业务持续扩展**,前端建议用 map/switch 处理已知值,对未知 eventType 做 fallback 渲染(展示 content 文本而不崩溃)。
|
||||
3. **INFO_ADJUST 和 REFUND_RECEIVED 并非新枚举值**,只是新增了触发场景。历史订单在本次上线前对应操作无时间线记录,属预期行为。
|
||||
4. **接口 B 转单字段传 null 或不传**等同于「不转单」,与历史行为一致。
|
||||
5. **转单不改变订单主状态**,前端进度条/状态显示无需因转单刷新。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
### Issue
|
||||
|
||||
- [#4419](https://git.1814.love:8443/wx/HL/issues/4419)
|
||||
- [#4423](https://git.1814.love:8443/wx/HL/issues/4423)
|
||||
- [#4428](https://git.1814.love:8443/wx/HL/issues/4428)
|
||||
- [#4446](https://git.1814.love:8443/wx/HL/issues/4446)
|
||||
- [#4451](https://git.1814.love:8443/wx/HL/issues/4451)
|
||||
- [#4459](https://git.1814.love:8443/wx/HL/issues/4459)
|
||||
|
||||
### PR
|
||||
|
||||
- [#4422](https://git.1814.love:8443/wx/HL/pulls/4422)
|
||||
- [#4426](https://git.1814.love:8443/wx/HL/pulls/4426)
|
||||
- [#4434](https://git.1814.love:8443/wx/HL/pulls/4434)
|
||||
- [#4449](https://git.1814.love:8443/wx/HL/pulls/4449)
|
||||
- [#4454](https://git.1814.love:8443/wx/HL/pulls/4454)
|
||||
- [#4464](https://git.1814.love:8443/wx/HL/pulls/4464)
|
||||
|
||||
### 后端负责人
|
||||
|
||||
yst(腰苏图)
|
||||
@@ -0,0 +1,250 @@
|
||||
# 【修改接口·管理后台】订单详情「合同·保险」Tab:无合同/无保险时子对象返 null (#4460)
|
||||
|
||||
> **PR**: #4461 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-26
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单详情「合同·保险」Tab 懒加载接口,在订单**从未签约 / 从未投保**时,原本仍返回一个字段全为 `null` 的 `contract` / `insurance` 壳对象,前端要判断"这单到底有没有合同/保险"只能逐字段判空,体验差且容易误判。
|
||||
|
||||
本次改为:合同、保险**各自独立**判断是否存在,不存在则整个子对象直接返 `null`,前端一层判断即可。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 合同·保险 Tab | GET | `/v3/admin/order/{id}/contract-insurance` | 修改接口 | 无合同时 `contract` 返 null、无保险时 `insurance` 返 null(原为字段全 null 的壳对象) |
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 获取订单合同·保险 Tab 数据
|
||||
|
||||
- **使用场景**:管理后台订单详情页,点开「合同·保险」Tab 时懒加载
|
||||
- **认证**:需要 JWT(管理后台登录态)
|
||||
- **幂等性**:是(纯查询)
|
||||
- **限流**:无
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | String | ✅ | 订单 ID(雪花 ID,字符串传递) |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
无(GET 请求)。
|
||||
|
||||
## 5. 出参(响应)
|
||||
|
||||
### 5.1 顶层结构
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `contract` | Object \| null | 合同信息;该订单**从未签约**时为 `null` |
|
||||
| `insurance` | Object \| null | 保险信息;该订单**从未投保**时为 `null` |
|
||||
|
||||
> **判定口径**:
|
||||
> - `contract` 为 null:合同状态为空 **且** 无合同方案头 **且** 无合同事件
|
||||
> - `insurance` 为 null:保险聚合状态为空 **且** 无保单
|
||||
> 三者/两者任一非空即视为"存在",照常返回完整对象。
|
||||
|
||||
### 5.2 `contract` 对象字段(存在时)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `contractStatus` | String | 合同状态,见 §6.1 |
|
||||
| `contractSignedAt` | String | 签署时间(`yyyy-MM-dd HH:mm:ss`),未签为 null |
|
||||
| `contractFileUrl` | String | 合同文件地址,未生成为 null |
|
||||
| `contractSchemeName` | String | 合同方案名 |
|
||||
| `signerName` | String | 签署人 |
|
||||
| `signMethod` | String | 签署方式 |
|
||||
| `events` | Array | 合同流程事件(固定 5 步模板),见 §5.4 |
|
||||
|
||||
### 5.3 `insurance` 对象字段(存在时)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `insuranceStatus` | String | 保险聚合状态,见 §6.2 |
|
||||
| `insuranceStatusName` | String | 保险状态中文名 |
|
||||
| `totalPremium` | Number | 总保费 |
|
||||
| `policyCount` | Number | 保单数量 |
|
||||
| `policies` | Array | 逐张保单列表(存在时;可能为空数组) |
|
||||
|
||||
### 5.4 `events` / `policies[].events` 事件字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `eventType` / `stepCode` | String | 步骤码 |
|
||||
| `eventName` | String | 步骤名 |
|
||||
| `actorType` | String | 操作方类型,见 §6.3 |
|
||||
| `actorTypeName` | String | 操作方中文名 |
|
||||
| `actorLabel` | String | 操作方标签 |
|
||||
| `statusLabel` | String | 状态标签(如 已完成/进行中/待处理) |
|
||||
| `description` | String | 描述 |
|
||||
| `occurredAt` | String | 发生时间 |
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 contractStatus(合同状态)
|
||||
|
||||
**所属字段**:`contract.contractStatus` | **类型**:`String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SIGNED` | 已签约 | 合同已签署完成 |
|
||||
| 其他业务态 | — | 按合同流程取值(生成/报备/作废等) |
|
||||
|
||||
### 6.2 insuranceStatus(保险聚合状态)
|
||||
|
||||
**所属字段**:`insurance.insuranceStatus` | **类型**:`String`
|
||||
|
||||
| 值 | 中文(insuranceStatusName) | 说明 |
|
||||
|----|------|------|
|
||||
| `INSURED` | 已出单 | — |
|
||||
| `INSURING` | 出单中 | — |
|
||||
| `CANCELLED` | 已取消 | — |
|
||||
| `FAILED` | 出单失败 | — |
|
||||
|
||||
### 6.3 actorType(操作方类型)
|
||||
|
||||
**所属字段**:`events[].actorType` / `policies[].events[].actorType` | **类型**:`String`
|
||||
|
||||
| 值 | 中文(actorTypeName) | 说明 |
|
||||
|----|------|------|
|
||||
| `SYSTEM` | 系统 | — |
|
||||
| `THIRD_PARTY` | 第三方 | 如电子合同平台 / 保险公司 |
|
||||
| `CUSTOMER` | 客户 | — |
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|------|------|----------|
|
||||
| `581007` | 订单不存在 | 路径 `id` 对应订单不存在 |
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(无合同无保险订单)
|
||||
|
||||
**请求**:
|
||||
```
|
||||
GET /v3/admin/order/2070039058075017217/contract-insurance
|
||||
Authorization: Bearer <token>
|
||||
无请求体
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"contract": null,
|
||||
"insurance": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界(有合同·有保险订单)
|
||||
|
||||
**场景说明**:订单已签约(SIGNED)且已投保,两子对象照常返回完整结构。
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"contract": {
|
||||
"contractStatus": "SIGNED",
|
||||
"contractSignedAt": "2026-04-18 17:12:00",
|
||||
"contractFileUrl": "https://oss.example.com/contract/xxx.pdf",
|
||||
"contractSchemeName": "标准国内单团签约方案",
|
||||
"signerName": "张三",
|
||||
"signMethod": "电子签约",
|
||||
"events": [
|
||||
{ "stepCode": "GENERATE", "eventName": "生成合同", "actorType": "SYSTEM", "actorTypeName": "系统", "statusLabel": "已完成", "occurredAt": "2026-04-18 17:10:00" }
|
||||
]
|
||||
},
|
||||
"insurance": {
|
||||
"insuranceStatus": "INSURED",
|
||||
"insuranceStatusName": "已出单",
|
||||
"totalPremium": 88.00,
|
||||
"policyCount": 1,
|
||||
"policies": [
|
||||
{ "policyNo": "PICC-001", "status": "INSURED", "statusName": "已出单", "events": [] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
**请求**:
|
||||
```
|
||||
GET /v3/admin/order/88888888/contract-insurance
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ **返完整对象**:合同侧状态/方案头/事件任一存在 → `contract` 完整返回;保险侧聚合状态或保单任一存在 → `insurance` 完整返回
|
||||
- ⚠️ **返 null**:从未签约 → `contract` 整体为 `null`;从未投保 → `insurance` 整体为 `null`
|
||||
- 两者相互独立:可能出现「有合同无保险」(`contract` 非 null、`insurance` 为 null)或反之
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `data.contract`(无合同时) | 字段全为 null 的对象 `{contractStatus:null, ..., events:[]}` | `null` |
|
||||
| `data.insurance`(无保险时) | 字段全为 null 的对象 `{insuranceStatus:null, ..., policyCount:0, policies:[]}` | `null` |
|
||||
|
||||
### 10.2 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 前端判断有无合同/保险 | 逐字段判空(如 `contract.contractStatus == null && ...`) | 一层判断 `if (data.contract)` / `if (data.insurance)` |
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
### 11.1 影响评估
|
||||
|
||||
- **是否破坏向后兼容**:是(无合同/无保险订单的 `contract`/`insurance` 由对象变为 null,前端需调整判空逻辑)
|
||||
- **前端是否必须同步上线**:是(原逐字段判空逻辑需改为判子对象是否为 null)
|
||||
|
||||
### 11.2 回滚方案
|
||||
|
||||
- **回滚方式**:revert PR #4461
|
||||
- **回滚耗时**:预计 5 分钟(重新部署)
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
- 前端 workaround 清理点:原先「逐字段判空来识别是否有合同/保险」的逻辑可改为「判 `data.contract` / `data.insurance` 是否为 null」
|
||||
- 「有合同·有保险」「有合同·无保险」「无合同·有保险」「无合同·无保险」四种组合均需正确处理
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#4460](https://git.1814.love:8443/wx/HL/issues/4460)
|
||||
- **PR**: [#4461](https://git.1814.love:8443/wx/HL/pulls/4461)
|
||||
- **Merge commit**: [8ae5d58](https://git.1814.love:8443/wx/HL/commit/8ae5d5819268a01910a8ec93908489d728ad7005)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: —
|
||||
@@ -0,0 +1,61 @@
|
||||
# 酒店价格日历「设置价格」——把已设库存的日期改回「不限库存」(写侧最终契约·已部署测试服验证)
|
||||
|
||||
> 变更类型:⚙️ 后端已就位(写侧补口,最终契约)
|
||||
> 端类型:管理后台(资源·酒店·房型价格日历「设置价格」弹窗)
|
||||
> 日期:2026-06-26 | 工单/PR:#4392(#4396 加字段 + #4398 hotfix 真正落库)| 服务:hl-resource-service
|
||||
> 关联:承接 25_ 那条「② 库存可空=不限」里挂起的「待部署测试服验证后单独 changelog 通知最终契约」。**现已部署测试服并 live 实测通过**。
|
||||
|
||||
---
|
||||
|
||||
## 背景(前端先前反馈的缺口)
|
||||
|
||||
「已设库存的日期」想改回「不限库存」时,前端传 `stock=null`,后端原逻辑是**「保留原值不覆盖」**(防误清),所以改不回不限。需要后端提供一个「显式清空库存」的开关。
|
||||
|
||||
## 最终契约(已就位)
|
||||
|
||||
**端点**:`PUT /admin/hotel/room-type/{roomTypeId}/prices`(`batchSetPrices`,设置/更新某房型某日期区间价格)
|
||||
|
||||
**请求体新增字段**:`PriceCalendarSetRequest.unlimitedStock`(`Boolean`,可选)
|
||||
|
||||
| 传值 | 后端行为 |
|
||||
|------|----------|
|
||||
| `unlimitedStock = true` | **显式把该区间每日 `stock` 清成 `NULL`(= 不限库存)**,不管同时传不传 `stock`、传多少都忽略。新建记录时也直接 `stock=null`。 |
|
||||
| `unlimitedStock = false` 或**不传** | **维持原逻辑(行为完全不变)**:`stock` 非空才覆盖;`stock=null` 视为「不改」保留原值。老调用零影响。 |
|
||||
|
||||
**前端动作**:用户把一个「已设库存」的日期改回「不限」时,提交 `unlimitedStock=true`(`stock` 字段可不传或随意,会被忽略)。其余正常设库存的场景照旧(传 `stock` 数值、不传 `unlimitedStock`)。
|
||||
|
||||
**示例**
|
||||
|
||||
```jsonc
|
||||
// 改回不限库存
|
||||
PUT /admin/hotel/room-type/123/prices
|
||||
{ "startDate": "2026-06-30", "endDate": "2026-06-30", "protocolPrice": 330.00, "unlimitedStock": true }
|
||||
// → 该日 stock 变 NULL;GET 回读 stock=null(前端显示「不限制」)
|
||||
|
||||
// 正常设有限库存(老逻辑,不变)
|
||||
PUT /admin/hotel/room-type/123/prices
|
||||
{ "startDate": "2026-06-30", "endDate": "2026-06-30", "protocolPrice": 330.00, "stock": 15 }
|
||||
// → 该日 stock=15
|
||||
```
|
||||
|
||||
## 回读
|
||||
|
||||
`GET /admin/hotel/room-type/{roomTypeId}/prices?year&month` 的 `PriceCalendarDayVO.stock`:
|
||||
- `stock = null` → 显示「不限制」(**别显示 0**)。
|
||||
- `stock = 数值` → 显示该数值(有限库存)。
|
||||
|
||||
## 测试服验证(已 live 实测)
|
||||
|
||||
实测序列(同一房型同一日期,过网关 9443 + 真 admin token):
|
||||
1. `PUT stock=10` → 回读 `stock=10`
|
||||
2. `PUT {stock:7, unlimitedStock:true}` → 回读 **`stock=null`**(= 不限;同时传的 7 被忽略,证明 `unlimitedStock` 优先且真落库)
|
||||
3. `PUT stock=15` → 回读 `stock=15`(可从「不限」改回有限)
|
||||
4. `DELETE` 该日 → 无残留记录
|
||||
|
||||
> 实现备注(不影响前端契约):后端原 `setStock(null)+updateById` 因 MyBatis-Plus 默认跳过 null 字段而清不掉,#4398 改用 Mapper 显式 `set(stock,null)` 才真正落库——此坑由 API 实测发现,单测 mock 掩盖了它。
|
||||
|
||||
---
|
||||
|
||||
## 说明
|
||||
- 本条只覆盖 #4392「写侧把库存改回不限」最终契约;读侧(库存 null 展示「不限」、协议价回显、配房扣的是 `stockUsed`/`available` 非总 `stock`)见 25_ 那条。
|
||||
- 库存硬扣总开关 `hotel.stock.enabled` 已在测试服设为 `true`,扣减/释放已生效。
|
||||
@@ -0,0 +1,246 @@
|
||||
# 订单详情「合同·保险」Tab —— 创建合同 / 投保 接口对接(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:新增接口(前端首次对接;**后端接口早已存在**,本文为对接说明)
|
||||
- 关联:订单详情「合同·保险」Tab(懒加载 GET 仅回显,写操作走本文 4 接口)
|
||||
- 日期:2026-06-26
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单详情「合同·保险」Tab 的懒加载接口 `GET /v3/admin/order/{id}/contract-insurance` 是**只读回显**(返回合同/保险当前状态 + 时间线),**不含**写操作。Tab 页面上的两组操作按钮——「选择合同方案 + 创建合同」「选择保险方案 + 投保」——各由独立写接口支撑。本文给出这 4 个接口的完整契约供前端对接。
|
||||
|
||||
> 两个「按方案」接口都是**简化版**:前端只传 `orderId + schemeId`,后端自动从订单取出行人、行程日期、联系人,结合方案配置去创建合同 / 逐段投保。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 序 | 方法 | 路径 | 用途 |
|
||||
|---|---|---|---|
|
||||
| 1 | GET | `/v3/admin/contract/scheme/list` | 合同方案下拉(启用方案) |
|
||||
| 2 | POST | `/v3/admin/contract/create-by-scheme` | 创建合同(按方案) |
|
||||
| 3 | GET | `/v3/admin/insurance/scheme/list` | 保险方案下拉(启用方案,可按行程天数筛) |
|
||||
| 4 | POST | `/v3/admin/insurance/purchase-by-scheme` | 投保(按方案,逐段投保) |
|
||||
|
||||
统一响应 `Result<T>`:`{ code, message, data, success }`,`code=200` 成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 接口 1:合同方案下拉
|
||||
```
|
||||
GET /v3/admin/contract/scheme/list
|
||||
```
|
||||
无入参。返回当前**启用(ACTIVE)**的合同方案列表,供「选择合同方案」下拉。
|
||||
|
||||
### 接口 2:创建合同(按方案)
|
||||
```
|
||||
POST /v3/admin/contract/create-by-scheme
|
||||
Content-Type: application/json
|
||||
```
|
||||
点「创建合同」时调。后端按方案 + 订单数据创建合同;若方案为电子签约模式(STANDARD),会触发推送签署链接(短信/微信至客户预留手机号)。
|
||||
|
||||
### 接口 3:保险方案下拉
|
||||
```
|
||||
GET /v3/admin/insurance/scheme/list?totalDays={行程天数}
|
||||
```
|
||||
返回**启用(ACTIVE)**保险方案,供「选择保险方案」下拉。`totalDays` 可选:传则按行程天数筛匹配方案(全程方案 totalDays=-1 任意天数都命中),不传返回全部启用方案。
|
||||
|
||||
### 接口 4:投保(按方案)
|
||||
```
|
||||
POST /v3/admin/insurance/purchase-by-scheme
|
||||
Content-Type: application/json
|
||||
```
|
||||
点「投保」时调。后端按方案逐段投保(一个方案多段 → 每段建一张保单),自动按订单出行人投保。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口 1(合同方案下拉)
|
||||
无。
|
||||
|
||||
### 接口 2(创建合同)—— Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long(字符串) | 是 | 订单 ID |
|
||||
| schemeId | Long(字符串) | 是 | 合同方案 ID(取自接口 1 的 schemeId) |
|
||||
|
||||
### 接口 3(保险方案下拉)—— Query
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| totalDays | Integer | 否 | 行程天数,按天数筛方案;不传返回全部启用 |
|
||||
|
||||
### 接口 4(投保)—— Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long(字符串) | 是 | 订单 ID |
|
||||
| schemeId | Long(字符串) | 是 | 保险方案 ID(取自接口 3 的 schemeId) |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口 1:`Result<List<ContractSchemeVO>>`
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| schemeId | String | 方案 ID(创建合同时回传) |
|
||||
| name | String | 方案名称(下拉展示) |
|
||||
| description | String | 方案描述 |
|
||||
| contractPlatform | String | 合同平台:12301 / LOCAL / TENCENT_ESIGN |
|
||||
| vendorCode | String | 平台厂商编码:TENCENT_HULAI=呼籁自有腾讯账号;12301 平台为 NULL |
|
||||
| channel | String | 订单来源渠道:MP / ADMIN / CHANNEL;NULL=兜底方案 |
|
||||
| templateCode | String | 合同模板编码(contractTemplateCode 别名) |
|
||||
| contractTemplateName | String | 合同模板名称 |
|
||||
| contractMode | String | 签约模式:STANDARD(电子签约) / SYNC(线下报备) |
|
||||
| signatoryMode | Integer | 签署模式:1=短信签署 / 2=现场签署 / 3=线下签署 |
|
||||
| agencyCode / agencyName | String | 旅行社编码 / 名称 |
|
||||
| supplementaryClause | String | 补充条款 |
|
||||
| transactorName / transactorPhone | String | 经办人姓名 / 电话 |
|
||||
| sortOrder | Integer | 排序 |
|
||||
| status | String | 状态:ACTIVE / INACTIVE |
|
||||
| createTime | DateTime | 创建时间 |
|
||||
|
||||
### 接口 2:`Result<ContractDetailVO>`(创建成功返回合同详情)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| contractId | Long(字符串) | 合同 ID |
|
||||
| orderId / orderNo | Long(字符串) / String | 订单 ID / 订单编号 |
|
||||
| schemeId | Long(字符串) | 合同方案 ID |
|
||||
| templateCode / templateName | String | 模板编码 / 名称 |
|
||||
| contractNumber | String | 合同编号 |
|
||||
| platform | String | 签约平台 |
|
||||
| contractType / contractTypeLabel | String | 合同类型 TOUR-旅游合同 / INSURANCE-保险单 + 标签 |
|
||||
| mode / modeLabel | String | 签约模式 ONLINE-线上签署 / OFFLINE-线下签署 + 标签 |
|
||||
| status / statusLabel | String | 合同状态 + 标签(见⑥) |
|
||||
| signUrl / qrCodeUrl / fileUrl | String | 签署 URL / 二维码 URL / 合同文件 URL |
|
||||
| agencyCode / travelAgencyName | String | 旅行社编号 / 名称 |
|
||||
| destination | String | 目的地 |
|
||||
| departureDate / returnDate | Date | 出发 / 返回日期 |
|
||||
| totalAmount | BigDecimal(字符串) | 合同总金额 |
|
||||
| touristCount | Integer | 出行人数 |
|
||||
| contactName / contactPhone | String | 联系人姓名 / 电话(明文,脱敏前端处理) |
|
||||
| supplementaryClause | String | 补充约定内容 |
|
||||
| travelers | List | 出行人列表(travelerId/name/idCardType/idCardTypeLabel/idCardNo/phone/isSigner) |
|
||||
| statusLogs | List | 状态变更日志 |
|
||||
| createTime | DateTime | 创建时间 |
|
||||
|
||||
### 接口 3:`Result<List<InsuranceSchemeVO>>`
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| schemeId | Long(字符串) | 方案 ID(投保时回传) |
|
||||
| schemeName | String | 方案名称(下拉展示,name 为兼容别名同值) |
|
||||
| description | String | 方案描述 |
|
||||
| isOverseas | Boolean | 是否境外 |
|
||||
| totalDays | Integer | 适用行程天数(-1=全程方案,任意天数命中) |
|
||||
| sortOrder | Integer | 排序 |
|
||||
| status | String | 状态:ACTIVE-启用 / INACTIVE-停用 |
|
||||
| insuranceProductId / productName | Long(字符串) / String | 保险产品 ID / 名称 |
|
||||
| planId / planName | Long(字符串) / String | 保险计划 ID / 名称 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
| segmentCount | Integer | 段数量 |
|
||||
| segments | List | 适用行程段列表(见下) |
|
||||
| createTime / updateTime | DateTime | 创建 / 更新时间 |
|
||||
|
||||
`segments[]`(SegmentItem):segmentId / segmentName / dayOffsetStart / dayOffsetEnd(-1=最后一天) / minDays / maxDays / productId / productName / planId / planName / sortOrder。
|
||||
|
||||
### 接口 4:`Result<Void>`
|
||||
投保成功返回 `data=null`、`code=200`。投保结果(保单)通过懒加载 GET 接口的 `insurance.policies[]` 回显。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
- 合同方案 contractMode:STANDARD 电子签约 / SYNC 线下报备
|
||||
- 合同方案 contractPlatform:12301 / LOCAL / TENCENT_ESIGN(对应字典 contract_platform)
|
||||
- 合同方案 signatoryMode:1 短信签署 / 2 现场签署 / 3 线下签署
|
||||
- 合同 contractType:TOUR 旅游合同 / INSURANCE 保险单
|
||||
- 合同 mode:ONLINE 线上签署 / OFFLINE 线下签署
|
||||
- 合同 status:GENERATING 生成中 / GENERATED 已生成 / SIGNED 已签 / UPLOADED 已上传回执 / VOIDED 已作废 / RESIGNING 重签中
|
||||
- 保险方案 status:ACTIVE 启用 / INACTIVE 停用
|
||||
- 方案/产品/计划 status 通用:ACTIVE / INACTIVE
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
- 创建合同 / 投保失败抛 `BusinessException`,统一响应 `code≠200 + message`,前端按 message 提示。
|
||||
- 下拉查询接口正常返 200。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 接口 1 响应(合同方案下拉)
|
||||
```json
|
||||
{ "code": 200, "message": "操作成功", "success": true,
|
||||
"data": [
|
||||
{ "schemeId": "2054961027789152258", "name": "标准国内电子签约方案",
|
||||
"contractPlatform": "TENCENT_ESIGN", "contractMode": "STANDARD",
|
||||
"signatoryMode": 1, "status": "ACTIVE" }
|
||||
] }
|
||||
```
|
||||
|
||||
### 接口 2 请求(创建合同)
|
||||
```json
|
||||
{ "orderId": "2070039025715982337", "schemeId": "2054961027789152258" }
|
||||
```
|
||||
响应 `data` = ContractDetailVO(contractId/status=GENERATED/signUrl 等)。
|
||||
|
||||
### 接口 3 响应(保险方案下拉,传 totalDays=5)
|
||||
```json
|
||||
{ "code": 200, "success": true,
|
||||
"data": [
|
||||
{ "schemeId": "2067500963379245057", "schemeName": "境外旅行险·标准版",
|
||||
"totalDays": -1, "status": "ACTIVE", "segmentCount": 1,
|
||||
"segments": [ { "segmentId": "...", "segmentName": "全程", "dayOffsetStart": 0, "dayOffsetEnd": -1 } ] }
|
||||
] }
|
||||
```
|
||||
|
||||
### 接口 4 请求(投保)
|
||||
```json
|
||||
{ "orderId": "2070039025715982337", "schemeId": "2067500963379245057" }
|
||||
```
|
||||
响应 `{ "code": 200, "data": null, "success": true }`。
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 两个下拉只返回 **ACTIVE** 方案;INACTIVE 不出现。
|
||||
- 创建合同 / 投保只需 `orderId + schemeId`,其余数据后端从订单自动取。
|
||||
- 投保为**逐段投保**:一个方案多段 → 生成多张保单,懒加载 GET 的 `insurance.policies[]` 为数组。
|
||||
- 投保是异步出单:接口 4 返回成功表示**受理成功**,最终出单状态以懒加载 GET 的 policies status(INSURING→INSURED)为准。
|
||||
- 创建合同若方案为 STANDARD(电子签约),后端会推送签署链接到客户预留手机号。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
不适用(前端首次对接,无旧版本)。
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- 后端接口早已存在且稳定,前端对接不涉及后端改动,无回滚项。
|
||||
- 前端对接顺序建议:进 Tab → 调懒加载 GET 回显 → 无合同/保险时展示下拉 → 选方案 → 创建合同 / 投保 → 重新调懒加载 GET 刷新状态与时间线。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- Long ID(schemeId / orderId / contractId 等)与金额均**字符串化**返回(防 JS 精度丢失),请求时同样以字符串传 ID。
|
||||
- 创建合同 / 投保为写操作,前端需做按钮防重复点击 + loading。
|
||||
- 操作成功后刷新走懒加载 GET `/v3/admin/order/{id}/contract-insurance`(合同 5 步时间线 + 保险 policies 数组,见 2026-06-25 那份终态 changelog)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- 关联文档:`changelogs-v2/2026-06/25_4390_4403_订单详情合同保险Tab出参终态-修改接口-管理后台.md`(懒加载回显出参终态)
|
||||
- 后端负责人:腰苏图
|
||||
- 接口已部署测试服,Knife4j 可见(`/v3/admin/contract/**`、`/v3/admin/insurance/**`)。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 配房「更换 / 移除」酒店的接口接线说明(接 #4407 换酒店并入配房)
|
||||
|
||||
> 变更类型:📌 接口接线说明(无后端代码改动,**前端房务管家需按此接线「移除」**)
|
||||
> 端类型:管理后台(房务管家·配房弹窗「选择酒店」)
|
||||
> 日期:2026-06-26 | 关联:#4407(换酒店并入配房唯一写口)| 服务:hl-order-service-v3(端点均已在测试服实测)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
#4407 后「换酒店」并入「配房」:点某天「配房」→「选择酒店」弹窗 →「已选配房」回显当前已配酒店(`isCurrentlyAssigned`)。本说明明确弹窗里**「更换」和「移除」分别该调哪个后端接口**——后端能力齐全,关键是前端「移除」要接对端点。
|
||||
|
||||
---
|
||||
|
||||
## 1. 更换酒店 ✅ 走「确认配房」(§2.2 submit)
|
||||
- 在「已选配房」把当前酒店改成另一家(移除当前项 + 加入新酒店)→「确认配房」。
|
||||
- 调 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`,body `items` = **该天最终想要的配房集合**。
|
||||
- 后端按 (hotelId, roomTypeId) **整天对账**:同酒店同房型同间数=不动;换成另一家=自动「释放旧店库存 + 扣新店 + 替换那行」。前端无需关心换/没换,把最终状态提交即可。
|
||||
|
||||
## 2. 移除某天已配酒店 ⚠️ 必须走 §2.4 DELETE,**不能靠空提交**
|
||||
- **关键**:`POST …/assignments` 的 `items` 是 `@NotEmpty`,**空数组会被后端拒**(返 `items 不能为空`,测试服实测)。所以「已选配房」移除到 0 条再点「确认配房(0条)」**删不掉**(界面没了、刷新又回来)。
|
||||
- 真正移除某天已保存的配房,调 **`DELETE /v3/admin/order/assignments/{id}`**(`{id}` = 该天配房行 assignmentId)。后端:软删该行 + 级联删房间分配 + **释放库存**(测试服实测:删后该天 0 active 行、扣减 log `rollback_status=DONE` 库存已退)。
|
||||
- **前端「移除」链接的正确接线**:
|
||||
- 移除的是**已保存的配房行**(有 assignmentId,即回显出来的当前已配)→ 调 `DELETE /v3/admin/order/assignments/{id}`。
|
||||
- 移除的是**本次新加、尚未保存的项**(还没 assignmentId)→ 纯前端本地删即可,不调接口。
|
||||
|
||||
## 3. 业务提醒
|
||||
- 纯移除(该天清空、不换新酒店)会让该天无住宿 → **「最终确认」的「全晚配齐」校验过不了**。常规操作是「移除旧 + 加入新 = 更换」;纯移除仅用于「这天本不该配酒店」的纠错。
|
||||
|
||||
---
|
||||
|
||||
## 接口速查
|
||||
| 操作 | 接口 |
|
||||
|------|------|
|
||||
| 配房 / 更换酒店 / 加多房型 | `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`(items=该天最终集合,非空) |
|
||||
| 改协议价/结算价/备注(不换酒店) | `PUT /v3/admin/order/assignments/{id}` |
|
||||
| **移除某天已配酒店** | **`DELETE /v3/admin/order/assignments/{id}`** |
|
||||
| 回显当前已配 | 候选 `GET /v3/admin/hotel-candidates` 的 `isCurrentlyAssigned`/`assignedRoomTypeId`;详情 `currentAssignment.roomTypeId` |
|
||||
@@ -0,0 +1,76 @@
|
||||
# 配房「先询房选候选 → 单日确认定一家」流程 — 详情逐日补询房中态/候选清单 + 单日确认同步协议价
|
||||
|
||||
> 变更类型:✨ 新增字段 + 接口接线说明(后端已就绪,**房务管家前端需按此接线**)
|
||||
> 端类型:管理后台(房务管家·配房工作台)
|
||||
> 日期:2026-06-26 | 工单:#4427 | PR:#4433 | 服务:hl-order-service-v3(双实例已部署 health UP,**测试服 API+DB 实测通过**)
|
||||
|
||||
---
|
||||
|
||||
## 背景:房务配房两阶段流程
|
||||
1. **第一次选酒店/房型 = 选「询房候选」(可多家)** → 当天进入「询房中」 → 复制话术问酒店。
|
||||
2. **「单日确认」= 从候选挑一家定下来** → 当天配房完成(一天一家)。
|
||||
|
||||
后端能力本就齐:候选落 `house_inquiry_message`(一家一行);确认走询房回填转配房。本次补齐**详情把「询房中」显示出来 + 当天候选清单**,并让单日确认支持改结算价/同步协议价。
|
||||
|
||||
---
|
||||
|
||||
## 一、配房详情新增字段(`GET /admin/house/orders/{orderId}` → `itinerary[]`)
|
||||
|
||||
### 1. 逐日状态 `arrange` 新增「询房中」
|
||||
`itinerary[].arrange` 取值由原 `pending`/`confirmed` 扩为三态,`arrangeLabel` 为中文:
|
||||
- `pending` / **待配房**:当天无询房、无配房。
|
||||
- `waiting` / **询房中**(新):当天有进行中的询房(PENDING 待回复 / REPLIED 已回复)且尚未定稿配房。
|
||||
- `confirmed` / **已确认**:当天已有配房行(**优先级最高**——即便还有遗留询房,也显已确认)。
|
||||
|
||||
### 2. 逐日新增 `inquiries[]` 当天询房候选清单
|
||||
`itinerary[].inquiries`(数组,无询房则空数组)——供「单日确认」弹窗列候选选保留哪家。每项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `inquiryId` | String(雪花) | 询房记录 ID(回填/确认时用) |
|
||||
| `hotelId` | String(雪花) | 酒店 ID |
|
||||
| `hotelName` | String | 酒店名 |
|
||||
| `roomCategory` | String | 房型类别字典 code(询房只到类别粒度;**具体房型 ID 由前端单日确认时从该酒店房型列表现选**,故候选不带 roomTypeId) |
|
||||
| `roomCount` | Integer | 询问间数 |
|
||||
| `status` | String | 询房状态:PENDING/REPLIED/CONFIRMED/REJECTED/TIMEOUT |
|
||||
| `statusLabel` | String | 中文:待回复/已回复/已确认/已拒单/已超时 |
|
||||
| `replyPrice` | String(金额) | 酒店回复报价(未回则空) |
|
||||
| `replyAvailable` | Integer | 酒店回复可用房数(未回则空) |
|
||||
| `priority` | String | NORMAL/URGENT |
|
||||
| `timeoutAt` | DateTime | 询房超时刻(create + 4h) |
|
||||
|
||||
> 已取消(CANCELLED)的询房不进清单。
|
||||
|
||||
---
|
||||
|
||||
## 二、前端接线(房务管家,mmg 需改)
|
||||
|
||||
### 1. 第一次选房 → 走「发询房」(多候选),不要走「配房」
|
||||
- **第一次给某天选候选酒店/房型 → 逐个调 `POST /v3/admin/order/inquiry/send`**(一家一调,body 见下),不要调配房保存 `…/assignments`。
|
||||
- 原因:配房保存是「一天一家**定稿**」模型(按天归集,多家只留一家)——这就是「选多个酒店只存一个」的根因。多候选必须落询房表。
|
||||
- `send` body:`{requirementId, hotelId, dayNumber, stayDate, nights, roomCount, roomCategory, messageBody?}`;返回 `wechatTemplate`(话术,供复制)。
|
||||
- 复制话术:`POST /v3/admin/order/inquiry/preview`(所见即所发,不落库),或直接用 send 返回的 `wechatTemplate`。
|
||||
- 发起后该天 `arrange` 自动变 `waiting`/**询房中**,前端据此显示徽章。
|
||||
|
||||
### 2. 「单日确认」→ 弹窗列候选选一家 → 回填转配房
|
||||
- 弹窗数据源:详情 `itinerary[].inquiries`(或 `GET /v3/admin/order/inquiry?requirementId=&...` 历史)。
|
||||
- 选定一家 → `POST /v3/admin/order/inquiry/{inquiryId}/reply`,`convertToAssignment=true` → 落配房,该天变「已确认」。
|
||||
- **可在确认时改价**:
|
||||
- 改结算价:传 `replyPrice`(→ 落配房成交售价 sellPrice)。
|
||||
- **同步到协议价(新)**:传 `syncProtocolPrice=true` —— 把本次结算价(replyPrice)同步写回 resource 协议价(**与配房 `syncProtocolPrice` 同口径**,无需另填值)。需同时传 `roomTypeId`(前端从该酒店房型列表带入,写回价格日历定位用)。
|
||||
- 也可走原显式路径:`syncToCalendar=true` + `newProtocolPrice`/`newStock`(显式另填协议价/库存)。
|
||||
|
||||
---
|
||||
|
||||
## 接口速查
|
||||
| 操作 | 接口 |
|
||||
|------|------|
|
||||
| 发起询房(一家一调,多候选) | `POST /v3/admin/order/inquiry/send` |
|
||||
| 话术预览 | `POST /v3/admin/order/inquiry/preview` |
|
||||
| 询房历史/候选 | `GET /v3/admin/order/inquiry?requirementId=` |
|
||||
| 单日确认(选一家定稿,可改结算价/同步协议价) | `POST /v3/admin/order/inquiry/{inquiryId}/reply`(convertToAssignment=true,可带 replyPrice / syncProtocolPrice / roomTypeId) |
|
||||
| 详情逐日态+候选清单 | `GET /admin/house/orders/{orderId}` → `itinerary[].arrange`(+waiting/询房中) / `itinerary[].inquiries[]` |
|
||||
|
||||
## 测试服实测(已通过)
|
||||
- 详情逐日 `inquiries[]` 正确归集当天候选:发一条询房 → DAY 候选清单回 `status=PENDING/待回复`、中文 statusLabel、报价/可用房/优先级齐全;该天有配房时 `arrange` 保持 `confirmed`(已确认优先)。
|
||||
- 全量单测 4668 通过(含三态 + 候选归集 + 同步协议价写回 replyPrice 当协议价 UT)。
|
||||
@@ -0,0 +1,178 @@
|
||||
# 订单调整弹窗:出行人 tab + 改期 tab(管理后台)
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(出参/入参扩展 + **travelers 脱敏→明文,含破坏性**)
|
||||
- 关联 Issue:#4458(+ follow-up #4477) PR:#4468 / #4478
|
||||
- 日期:2026-06-27
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单调整弹窗的两个端点(`GET .../adjustment/snapshot` 预填 + `POST .../adjustment/submit` 统一提交)本期改造:
|
||||
1. 原「出行人数」tab(仅改 adultCount/childCount 整数)→「出行人」tab:管理真实出行人名单(增/删/改),人数由名单按类型自动派生,加减人触发算价。
|
||||
2. 新增「改期」tab:在"确认订单行程"之前允许修改出发日期;改期后订单退回「资源准备中」+ 配房配车重置。
|
||||
|
||||
两 tab 所有数据**一次 submit 一起提交**,零新端点、零 DDL。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 方法 | 路径 | 变更 |
|
||||
|---|---|---|
|
||||
| GET | `/v3/admin/order/{id}/adjustment/snapshot` | 出参新增 `schedule` 块;`travelers` 脱敏→明文;`editableTabLocksHint` 按状态门控 PEOPLE/SCHEDULE |
|
||||
| POST | `/v3/admin/order/{id}/adjustment/submit` | 入参 `updates.travelers` 启用(原空转)+ 新增 `updates.schedule`;出参新增改期字段 |
|
||||
|
||||
统一响应 `Result<T>`:`{ code, message, data, success }`,code=200 成功。
|
||||
|
||||
**破坏性提示**:`snapshot.travelers` 由**脱敏**视图改为**明文**视图(姓名/证件/手机为真实值,供 admin 编辑表单回填)。前端如有按脱敏展示逻辑需自行脱敏。
|
||||
|
||||
---
|
||||
|
||||
## ③ 可改窗口(两 tab 不同,前端按 editableTabLocksHint 控制)
|
||||
|
||||
| tab | 可改窗口 | 起锁点 |
|
||||
|---|---|---|
|
||||
| 出行人(PEOPLE) | flowStatus ≤ 待出行(PENDING_DEPARTURE) | 出行中 TRAVELLING 起锁 |
|
||||
| 改期(SCHEDULE) | flowStatus ≤ 待确认(PENDING_CONFIRM) | 待出行 PENDING_DEPARTURE 起锁 |
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### snapshot
|
||||
无变化(仅 path `id`)。
|
||||
|
||||
### submit —— `updates` 扩展
|
||||
```jsonc
|
||||
{
|
||||
"editReason": "客户加1人并改出发日", // ≥5字,强制审计
|
||||
"tabLocks": ["PEOPLE","SCHEDULE"], // 本次改了哪些 tab
|
||||
"confirmDiffHash": "...", // 防并发(拿 snapshot 时记录回送)
|
||||
"updates": {
|
||||
"travelers": { // 出行人 tab(原定义但未生效,本期启用)
|
||||
"add": [ { "name":"李四","travelerType":"ADULT","idCardType":"ID_CARD","idCardNo":"...","phone":"...","birthday":"1990-03-07" } ],
|
||||
"update": [ { "id":"92001","name":"张三","idCardNo":"..." } ],
|
||||
"remove": [ "92003" ] // 出行人 id
|
||||
},
|
||||
"schedule": { "departDate": "2026-07-15" } // 改期 tab(新增),仅传新出发日
|
||||
// 仍兼容 basic / itinerary / hotelRequirement / vehicleRequirement / feeChanges
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**travelers 行字段**:name / travelerType(ADULT/CHILD/YOUNG_CHILD/BABY)/ idCardType / idCardNo / phone / birthday;update 项带 `id`。
|
||||
**schedule**:`departDate`(`yyyy-MM-dd`,必填非空;returnDate 由后端按行程天数派生,不传)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### snapshot —— 新增 `schedule` 块
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| schedule.departDate | String(date) | 当前出发日 |
|
||||
| schedule.returnDate | String(date) | 当前返回日(派生) |
|
||||
| schedule.tripDays | Integer | 行程天数 |
|
||||
| schedule.canEditDate | Boolean | 能否改期(flowStatus ≤ 待确认 = true) |
|
||||
| schedule.lockReason | String | 不能改期时的原因(可改时 null) |
|
||||
|
||||
其余:`travelers`(现为明文:name/idCardNo/phone 真实值)、`basic`(含 adult/child/youngChild/baby 人数回显,编辑入口已移至出行人 tab)、`editableTabLocksHint`(含 PEOPLE/SCHEDULE 取决于状态)不变结构。
|
||||
|
||||
### submit —— 新增字段
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| changedDims | Array | 本次变更维度,新增 `DEPART_DATE`(改期);出行人变人数为 `HEADCOUNT` |
|
||||
| priceDelta | String(金额) | 价格差额(正=加费,负=优惠,0=无差额) |
|
||||
| newDepartDate | String(date) | 改期后新出发日(改期时返回) |
|
||||
| newReturnDate | String(date) | 改期后新返回日 |
|
||||
| newFlowStatus | String | 改期后流程状态(恒 `RESOURCE_PREPARING`) |
|
||||
| assignmentDeletedCount | Integer | 改期重置作废的配房配车 assignment 行数 |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
- travelerType:ADULT 成人 / CHILD 儿童(6-12) / YOUNG_CHILD 幼童(2-5,占座不占床) / BABY 婴儿(0-1,不占座不占床)
|
||||
- flowStatus(相关):RESOURCE_PREPARING 资源准备中 / PENDING_CONFIRM 待确认 / PENDING_DEPARTURE 待出行 / TRAVELLING 出行中
|
||||
- changedDims:HEADCOUNT 人数 / DEPART_DATE 出发日期 / TRIP_DAYS 行程天数 / ITINERARY / HOTEL / VEHICLE
|
||||
- editableTabLocksHint 取值:BASIC / PEOPLE / SCHEDULE / ITINERARY / HOTEL_REQ / VEHICLE_REQ / FEE
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| code | 含义 | 触发 |
|
||||
|---|---|---|
|
||||
| 587034 | 已出行,出行人不可调整 | flowStatus ≥ 出行中 时改出行人 |
|
||||
| 587035 | 行程已确认,出发日期不可修改 | flowStatus ≥ 待出行 时改期(请取消重下) |
|
||||
| 587036 | 团期子订单出行人调整暂不支持 | 团期子订单走出行人增删致人数变化(本期拦截) |
|
||||
| 587037 | 出发日期格式非法,请使用 yyyy-MM-dd 格式 | schedule.departDate 格式错 |
|
||||
| 587038 | 出发日期不能早于今天 | 改期到过去日期 |
|
||||
| 581041 | 所选出发日期不可售或未配置价格 | 改期到无价格日历的日期 |
|
||||
| 587002 | 订单已是终态不可调整 | COMPLETED/CANCELLED |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 改期(成功)
|
||||
请求:`{ "editReason":"客户改出发日","tabLocks":["SCHEDULE"],"updates":{"schedule":{"departDate":"2026-06-27"}} }`
|
||||
响应:`{ "code":200,"data":{ "changedDims":["DEPART_DATE","TRIP_DAYS"],"priceDelta":"0.00","newDepartDate":"2026-06-27","newReturnDate":"2026-06-29","newFlowStatus":"RESOURCE_PREPARING","assignmentDeletedCount":3 } }`
|
||||
|
||||
### 出行人新增(成功)
|
||||
请求:`{ "editReason":"客户加1人","tabLocks":["PEOPLE"],"updates":{"travelers":{"add":[{"name":"李四","travelerType":"ADULT","idCardType":"ID_CARD","idCardNo":"...","phone":"138...","birthday":"1990-03-07"}]}} }`
|
||||
响应:`{ "code":200,"data":{ "changedDims":["HEADCOUNT"],"priceDelta":"3105.00" } }`(人数派生回写 order_main,价差落加费)
|
||||
|
||||
### 异常
|
||||
- 改期到过去日期:`{ "code":587038,"message":"出发日期不能早于今天" }`
|
||||
- 出行中改出行人:`{ "code":587034,"message":"已出行,出行人不可调整" }`
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 出行人人数由名单按 travelerType 自动派生回写 order_main(4 档:成人/儿童/幼童/婴儿),不再手填。
|
||||
- 算价:加减人 / 改期(价格日历差)由后端调产品侧 fetchQuote 权威重算差额,**前端不传金额**;差额落 order_surcharge(正)/ order_discount(负)。多 tab 同时提交时单次合并算价(终态 vs 原态),无交叉误差。
|
||||
- 改期:改 departDate + returnDate 联动 + 全行程逐日 stayDate 位移 + 退回 RESOURCE_PREPARING + 配房配车作废重配;**不涉及合同保险**(合同保险在确认行程后才触发,改期发生在之前)。
|
||||
- 出行人变更(已确认/已存在合同保险时)经 TravelerChangedEvent 自动作废重签。
|
||||
- 团期子订单出行人增删致人数变化本期拦截(587036),团期容量联动后续专项。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 出行人数 tab | 仅改 adultCount/childCount 整数 | 管真实出行人名单,人数派生 |
|
||||
| snapshot.travelers | 脱敏 | 明文(admin 编辑回填) |
|
||||
| updates.travelers | 定义存在但 submit 空转不生效 | 启用(add/update/remove 落地) |
|
||||
| 改期 | 禁止(出发日不可改) | 确认行程前可改,带级联 |
|
||||
| submit 出参 | 无改期字段 | + newDepartDate/newReturnDate/newFlowStatus/assignmentDeletedCount + changedDims.DEPART_DATE |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:snapshot.travelers 脱敏→明文,前端展示需自行脱敏。其余为新增(向后兼容)。
|
||||
- 前端尚未对接本弹窗两 tab,按此一次对接即可。
|
||||
- 回滚:后端 revert PR #4468 #4478。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- Long ID + 金额均字符串化返回(防 JS 精度)。
|
||||
- 改期/出行人为写操作,前端需防重复点击 + loading。
|
||||
- 提交成功后刷新走 snapshot 重新拉取(改期后 flowStatus 已变 RESOURCE_PREPARING)。
|
||||
- 改期所选新出发日须为产品可售日期(有价格日历),否则 581041。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4458 ・ https://git.1814.love:8443/wx/HL/issues/4477
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4468 ・ https://git.1814.love:8443/wx/HL/pulls/4478
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(snapshot/改期/出行人三能力均生效)。
|
||||
@@ -0,0 +1,217 @@
|
||||
# 订单调整 snapshot / submit 契约重制——basic 承载订单信息+金额、行程 tab 定价化、submit 入出参大幅精简
|
||||
|
||||
> 端类型:管理后台
|
||||
> 变更类型:修改接口(**破坏性**,前端需同步改造)
|
||||
> 涉及接口:
|
||||
> - `GET /v3/admin/order/{id}/adjustment/snapshot`(调整弹窗预填快照)
|
||||
> - `POST /v3/admin/order/{id}/adjustment/submit`(调整统一提交)
|
||||
|
||||
## 背景
|
||||
|
||||
订单调整弹窗的预填快照与统一提交接口经一轮重制:snapshot 的 `basic` 改为每次必返并承载订单身份+金额、行程 tab 升级为带价展示、删除前端用不到的优惠/附加费列表;submit 入参删除若干无用/已不支持字段、行程改嵌套结构、出参精简为只剩成功标志。**两个接口的入出参结构均有破坏性变化,前端需按本文同步改造。**
|
||||
|
||||
---
|
||||
|
||||
## 一、`GET /v3/admin/order/{id}/adjustment/snapshot`(出参重制)
|
||||
|
||||
### 入参(不变)
|
||||
|
||||
| 位置 | 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|:---:|---|
|
||||
| path | `id` | Long | 是 | 订单 ID |
|
||||
| query | `scope` | String | 否 | 限定返回子域,逗号分隔,不传返全部。枚举:`BASIC`/`PEOPLE`/`SCHEDULE`/`ITINERARY`/`HOTEL_REQ`/`VEHICLE_REQ`(**`FEE` 已移除**) |
|
||||
|
||||
### 关键变更
|
||||
|
||||
1. **`basic` 每次必返**(不再受 `scope` 限制,传任意 scope 都返回 basic)。
|
||||
2. **`basic` 新增订单身份字段**:`orderNo`/`productName`/`teamNo`/`productType`(只读展示)。
|
||||
3. **订单金额从顶层移入 `basic`**:`orderAmount`/`surchargeAmount`/`discountAmount`/`receivableAmount`/`balanceAmount`。其中 `receivableAmount`(应收总额)为新增字段。
|
||||
4. **删除顶层字段**:`discounts[]`、`surcharges[]`、`orderAmount`、`surchargeAmount`、`discountAmount`、`balanceAmount`(金额已并入 basic;优惠/附加费列表下线)。
|
||||
5. **行程 `itinerary` 改嵌套带价**:原顶层 `nodes[]` 删除,节点改挂在 `days[].nodes[]` 下,并新增单价/数量/小计/本日合计。
|
||||
|
||||
### 出参 `basic`(OrderBasicVO)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `orderNo` | String | 订单号(只读)🆕 |
|
||||
| `productName` | String | 产品名(快照,只读)🆕 |
|
||||
| `teamNo` | String | 团号(订金支付后生成,未支付为 null,只读)🆕 |
|
||||
| `productType` | String | 订单类型 CORE/ROUTE/CUSTOM/GROUP(只读)🆕 |
|
||||
| `customerName` | String | 客户姓名(脱敏) |
|
||||
| `customerPhone` | String | 客户手机(脱敏) |
|
||||
| `emergencyContact` | String | 紧急联系人 |
|
||||
| `customerRemark` | String | 客户备注 |
|
||||
| `consultantRemark` | String | 顾问备注(内部) |
|
||||
| `tags` | String[] | 订单标签 |
|
||||
| `adultCount` / `childCount` / `youngChildCount` / `babyCount` | Integer | 成人/儿童/幼童/婴儿人数 |
|
||||
| `orderAmount` | String(金额) | 订单基价(由顶层迁入)🔁 |
|
||||
| `surchargeAmount` | String(金额) | 已有附加费合计(由顶层迁入)🔁 |
|
||||
| `discountAmount` | String(金额) | 已有优惠合计(由顶层迁入)🔁 |
|
||||
| `receivableAmount` | String(金额) | 应收总额 = 基价+附加费−优惠,≥0,取消单为 0 🆕 |
|
||||
| `balanceAmount` | String(金额) | 待付尾款 = 应收−净已付,≥0,取消单为 0(由顶层迁入)🔁 |
|
||||
|
||||
> 金额字段均序列化为**字符串**(防 JS 精度丢失)。
|
||||
|
||||
### 出参 `itinerary`(ItineraryEditVO,嵌套带价)
|
||||
|
||||
| 路径 | 字段 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| days[] | `id`/`dayNumber`/`dayDate`/`theme` | - | 行程天信息 |
|
||||
| days[] | `nodes[]` | 数组 | 当天节点(**已过滤 HOTEL 节点**)🆕 |
|
||||
| days[] | `dayTotal` | 金额 | 本日合计 = Σ节点 subtotal,后端派生只读 🆕 |
|
||||
| days[].nodes[] | `id`/`dayId`/`nodeType`/`title`/`startTime`/`sortOrder` | - | 节点信息 |
|
||||
| days[].nodes[] | `unitPrice` | 金额 | 单价(叙事展示;非 SCENIC 节点当前为 0)🆕 |
|
||||
| days[].nodes[] | `quantity` | Integer | 数量(默认 1)🆕 |
|
||||
| days[].nodes[] | `subtotal` | 金额 | 小计 = unitPrice×quantity,后端派生只读 🆕 |
|
||||
|
||||
> ⚠️ 原顶层 `itinerary.nodes[]` 已删除,全部迁入 `days[].nodes[]`。
|
||||
> 其余子域 `travelers` / `schedule` / `hotelRequirement` / `hotelDayDefaults` / `vehicleRequirement` / `lockedDays` / `editableTabLocksHint` 不变。
|
||||
|
||||
### snapshot 出参示例(节选)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"basic": {
|
||||
"orderNo": "HL202608010001",
|
||||
"productName": "长白山深度5日游",
|
||||
"teamNo": "GB202608001",
|
||||
"productType": "CORE",
|
||||
"customerName": "张*",
|
||||
"adultCount": 4,
|
||||
"orderAmount": "12000.00",
|
||||
"surchargeAmount": "800.00",
|
||||
"discountAmount": "200.00",
|
||||
"receivableAmount": "12600.00",
|
||||
"balanceAmount": "3000.00"
|
||||
},
|
||||
"itinerary": {
|
||||
"days": [
|
||||
{
|
||||
"id": "1001", "dayNumber": 1, "dayDate": "2026-08-01", "theme": "天池徒步",
|
||||
"nodes": [
|
||||
{ "id": "2001", "nodeType": "SCENIC", "title": "长白山天池门票", "unitPrice": "128.00", "quantity": 1, "subtotal": "128.00" },
|
||||
{ "id": "2002", "nodeType": "RESTAURANT", "title": "午餐", "unitPrice": "0", "quantity": 1, "subtotal": "0" }
|
||||
],
|
||||
"dayTotal": "128.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、`POST /v3/admin/order/{id}/adjustment/submit`(入参 + 出参重制)
|
||||
|
||||
### 入参 `AdjustmentSubmitReqVO`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `updates` | 对象 | 是 | 各子域改动(按需填,未改置 null,但不能全 null) |
|
||||
|
||||
> ⚠️ **删除入参字段**:`editReason`(原必填)、`tabLocks`、`confirmDiffHash`。这三个一律不再传。
|
||||
|
||||
### `updates`(AdjustmentUpdatesVO)—— 现仅 5 个子域
|
||||
|
||||
| 子域 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `travelers` | 对象 | 出行人增删改:`add[]` / `update[]`(含 id)/ `remove[]`(id 列表) |
|
||||
| `schedule` | 对象 | 改期:`departDate`(新出发日 yyyy-MM-dd,本子域存在时必填,不可同原值) |
|
||||
| `itinerary` | 对象 | 行程:`days[].nodes[]`(见下) |
|
||||
| `hotelRequirement` | 对象 | 房需求完整新版本:`totalRoomCount`/`roomTypeSummary`/`specialTags[]`/`remark`/`days[]` |
|
||||
| `vehicleRequirement` | 对象 | 车需求完整新版本:`vehicleType`/`requiredSeats`/`remark`/`fleet[]` |
|
||||
|
||||
> ⚠️ **删除子域**:`basic`(订单基础信息不再在调整弹窗修改)、`feeChanges`(手动加优惠/附加费下线)。
|
||||
|
||||
### `updates.itinerary` 行程入参
|
||||
|
||||
| 路径 | 字段 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| days[] | `id`/`dayNumber` | - | 定位天;`days` 数量比现有多/少 → 后端差量增/减天 |
|
||||
| days[].nodes[] | `id` | Long | 定位要改的节点;为 null 视为新建(本期不支持,忽略) |
|
||||
| days[].nodes[] | `unitPrice` / `quantity` | - | 传了即改单价/数量(patch,不传不动) |
|
||||
| days[].nodes[] | `deleted` | Boolean | true=删除该节点 |
|
||||
|
||||
### 出参 `AdjustmentSubmitRespVO`(精简)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `success` | Boolean | 提交是否成功 |
|
||||
|
||||
> ⚠️ 出参从原 15 字段精简为**仅 `success`**。删除:`appliedScopes`/`affectedRows`/`newHotelRequirementId`/`newHotelVersion`/`newVehicleRequirementId`/`newVehicleVersion`/`assignmentDeletedCount`/`statusLogId`/`changedDims`/`affectedDays`/`priceDelta`/`newDepartDate`/`newReturnDate`/`newFlowStatus`。
|
||||
> **失败时**统一走全局异常:HTTP 200 + `Result{code: 587xxx, message: "..."}`,前端按已有错误码机制读 `code`/`message`。
|
||||
|
||||
### submit 入参示例
|
||||
|
||||
```json
|
||||
{
|
||||
"updates": {
|
||||
"schedule": { "departDate": "2026-09-01" },
|
||||
"itinerary": {
|
||||
"days": [
|
||||
{ "id": "1001", "dayNumber": 1, "nodes": [
|
||||
{ "id": "2001", "unitPrice": "150.00", "quantity": 2 },
|
||||
{ "id": "2003", "deleted": true }
|
||||
]}
|
||||
]
|
||||
},
|
||||
"travelers": { "remove": [70001005] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### submit 成功出参示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "success", "data": { "success": true } }
|
||||
```
|
||||
|
||||
### submit 失败出参示例(如:减员致有效总额低于已付)
|
||||
|
||||
```json
|
||||
{ "code": 587032, "message": "调整后总额低于已支付金额,不允许本次调整", "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、错误码(submit 失败可能返回,前端按 code/message 展示)
|
||||
|
||||
| code | message | 含义 |
|
||||
|---|---|---|
|
||||
| 587002 | 订单已是终态,不可调整 | 订单 COMPLETED/CANCELLED 不可调整 |
|
||||
| 587012 | updates 所有子领域均为空,无实际修改 | 未提交任何子域 |
|
||||
| 587032 | 调整后总额低于已支付金额,不允许本次调整 | 防超付 |
|
||||
| 587034 | 已出行,出行人不可调整 | 出行中改出行人被拦 |
|
||||
| 587035 | 行程已确认,出发日期不可修改 | 改期窗口已关 |
|
||||
| 587036 | 团期子订单不支持通过调整弹窗增删出行人,请前往团期管理台操作 | 团期出行人增删拦截 |
|
||||
|
||||
> 注:上表为常见项,完整错误码以后端 IErrorCode 段位为准;前端只需读 `code`/`message`。
|
||||
|
||||
---
|
||||
|
||||
## 四、业务边界 / 注意
|
||||
|
||||
- **团期(GROUP)订单不能在调整弹窗改人数**:团期增删出行人被拦截,且基础信息子域已删,故团期人数变更不在本弹窗。非团期订单通过增减出行人(`travelers`)改人数。
|
||||
- **行程单价为叙事展示价**,不进结算、不影响订单应付总额(改单价不触发算价)。
|
||||
- **HOTEL 节点**不在 `itinerary.days[].nodes[]` 出现(配房默认值另走 `hotelDayDefaults`)。
|
||||
- 人数/天数/行程改动触发的**自动价差**仍会写入订单加费/优惠(后端处理,前端无需关心)。
|
||||
|
||||
---
|
||||
|
||||
## 五、影响评估 / 前端改造点
|
||||
|
||||
1. snapshot:金额与订单号/产品名/团号读取改从 `data.basic.*`(原顶层金额字段已无);不再渲染 `discounts`/`surcharges` 列表。
|
||||
2. snapshot:行程 tab 改读 `itinerary.days[].nodes[]`(含单价/数量/小计/本日合计),原顶层 `itinerary.nodes[]` 不存在。
|
||||
3. submit:请求体去掉 `editReason`/`tabLocks`/`confirmDiffHash`/`updates.basic`/`updates.feeChanges`;行程改提交 `days[].nodes[]`。
|
||||
4. submit:响应只读 `data.success`(其余字段已删);失败读 `code`/`message`。
|
||||
|
||||
---
|
||||
|
||||
## 六、关联
|
||||
|
||||
- Issue:#4488(行程 tab 定价化)/ #4498(应收总额)/ #4502(金额挪 basic + basic 恒返 + 身份字段)/ #4509(submit 删 editReason/tabLocks)/ #4516(submit 删 confirmDiffHash/basic/feeChanges + 出参精简)
|
||||
- PR:#4497 / #4500 / #4504 / #4521
|
||||
- 负责人:腰苏图(yst)
|
||||
@@ -0,0 +1,25 @@
|
||||
# 配房详情逐日支持「一天多家酒店/多房型」— `currentAssignment` 单数改 `currentAssignments` 列表
|
||||
|
||||
> 变更类型:✨ 新增字段(修既有折叠 bug,**房务管家前端需迁移读列表**)
|
||||
> 端类型:管理后台(房务管家·配房详情)
|
||||
> 日期:2026-06-26 | 工单:#4435 | PR:#4436 | 服务:hl-order-service-v3(双实例已部署 health UP,测试服实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 背景/根因
|
||||
配房**单日确认可选多家酒店、多个房型**,一天可有多行已配(数据层本就支持,现网已有一天 3 家的订单)。但详情 `GET /admin/house/orders/{orderId}` 原把一天的多行配房**折叠成只显示 1 家**(`currentAssignment` 是单数)。本次修复让详情逐日返回**全部**已配。
|
||||
|
||||
## 字段变更(`itinerary[]`)
|
||||
- **新增 `itinerary[].currentAssignments`(数组)**:当天**全部**已配房,每项结构与原 `currentAssignment` 完全一致(assignmentId / hotelId / roomTypeId / hotelName / roomCategory / roomCount / protoPrice / sellPrice,金额与雪花 ID 均字符串)。
|
||||
- **`itinerary[].currentAssignment`(单数)保留但已废弃**:= `currentAssignments[0]`(首个)。一天多家时只含首个,仅为向后兼容。
|
||||
|
||||
## 前端必改(mmg)
|
||||
- 配房详情逐日展示 **改读 `currentAssignments`(列表)** 渲染当天全部已配酒店/房型,不要再只读单数 `currentAssignment`(否则一天多家只显示第一家)。
|
||||
- 「单日确认」可勾选**多家候选**:对每个选中的候选逐个调 `POST /v3/admin/order/inquiry/{inquiryId}/reply` `convertToAssignment=true`(各落一行配房),该天 `currentAssignments` 即返回多家。
|
||||
- 「移除某家」仍走 `DELETE /v3/admin/order/assignments/{assignmentId}`(按 currentAssignments[].assignmentId 精确删某一家)。
|
||||
|
||||
## 测试服实测
|
||||
- 一天 3 家的订单详情逐日返回 `currentAssignments` 含全部 3 家(酒店名/房型/价格齐全),`currentAssignment`=首个;一天 1 家时列表 1 项、单数同值(不回归)。
|
||||
- 全量单测 4671 通过,ArchTest 全绿。
|
||||
|
||||
> 关联:#4427/#4433(询房候选流程 + 询房中天级态 + 单日确认同步协议价)。本单补齐「一天多家」展示。
|
||||
@@ -0,0 +1,256 @@
|
||||
# 订单调整记录——新增「调整记录」列表接口,按次展示每次 submit 改了什么
|
||||
|
||||
> 端类型:管理后台
|
||||
> 变更类型:新增接口
|
||||
> 涉及接口:
|
||||
> - `GET /v3/admin/order/{id}/adjustment-record`(查询订单调整记录列表)
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单调整(submit)原来只在时间线追加一条「订单调整」无明细说明。前端调整弹窗无法向操作人展示「本次调整改了哪些项」。
|
||||
|
||||
本期新建独立「调整记录」:每次 submit 成功后在 `order_adjustment_record` 表落一条记录(含每项变更的类型、文案、前值、后值、金额差),后台订单详情「调整记录」弹窗调用本接口逐条渲染。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 编号 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | ✨ 新增接口 | `GET /v3/admin/order/{id}/adjustment-record`,返回本订单全部调整记录(按时间倒序) |
|
||||
| 2 | ✨ 新增 VO | `AdjustmentRecordVO`(记录主体)/ `ChangeItemVO`(单项变更)/ `StatusNoteVO`(状态摘要) |
|
||||
| 3 | ✨ 新增枚举 | `AdjustChangeType`,定义 8 个变更类型(见§⑥) |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
| 项目 | 说明 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | `/v3/admin/order/{id}/adjustment-record` |
|
||||
| 接口名 | 查询调整记录列表 |
|
||||
| 认证 | 需要 JWT(管理后台 Token) |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
| 分页 | 不分页(全量返回;单订单调整次数预期 < 50 条) |
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `id` | Long(String) | 是 | 订单 ID(雪花 ID,JS 请当字符串处理) |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
无请求体。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### 顶层结构
|
||||
|
||||
```
|
||||
Result<List<AdjustmentRecordVO>>
|
||||
```
|
||||
|
||||
data 为 `AdjustmentRecordVO` 数组,按 `occurredAt` 降序(最新调整在前)。无调整记录时 `data: []`。
|
||||
|
||||
### AdjustmentRecordVO(调整记录主体)
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `id` | String | 否 | 调整记录 ID(雪花 ID 序列化为字符串,防 JS 精度丢失) |
|
||||
| `occurredAt` | String | 否 | 调整发生时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `operatorName` | String | 否 | 操作人姓名(定制师真名) |
|
||||
| `changeCount` | Integer | 否 | 本次变更项总数,渲「N 项变更」 |
|
||||
| `items` | List<ChangeItemVO> | 否 | 变更明细列表(至少 1 项) |
|
||||
| `statusNote` | StatusNoteVO | 是 | 本次调整引发的状态摘要;无状态变化时为 `null` |
|
||||
| `balanceBefore` | Number | 否 | 调整前待付尾款(BigDecimal,无精度丢失风险,保留两位小数) |
|
||||
| `balanceAfter` | Number | 否 | 调整后待付尾款(BigDecimal,保留两位小数) |
|
||||
|
||||
> `balanceBefore` / `balanceAfter` 是权威尾款,不要用 `Σ(items[].amountDelta)` 推算(两者不同轴)。
|
||||
|
||||
### ChangeItemVO(单项变更明细)
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `type` | String | 否 | 变更类型枚举值(见§⑥ AdjustChangeType) |
|
||||
| `label` | String | 否 | 前端直显文案,后端已拼好(含资源名/翻译),**前端零拼接** |
|
||||
| `before` | String | 是 | 变更前值(新增类为 null) |
|
||||
| `after` | String | 是 | 变更后值(删除类为 null) |
|
||||
| `amountDelta` | Number | 是 | 本项声明价差(正=加费/负=减费;仅行程节点类有值,其余 null) |
|
||||
|
||||
> `before` / `after` 都非空时渲「before → after」;`amountDelta` 非 null 时渲金额差(>0 渲 `+¥X`,<0 渲 `-¥X`,=0 渲 `+¥0`)。
|
||||
|
||||
### StatusNoteVO(状态变化摘要,可空)
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
|---|---|:---:|---|
|
||||
| `title` | String | 否 | 摘要标题,如「状态回滚」 |
|
||||
| `content` | String | 否 | 摘要内容,如「房型与车辆已退回准备中,等待房控/车控复核」 |
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
### AdjustChangeType(items[].type 取值)
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
| `HEADCOUNT` | 出行人数量变化(成人/儿童/小童/婴儿各档分别一条) |
|
||||
| `DEPART_DATE` | 出发日期调整 |
|
||||
| `TRIP_DAYS` | 行程天数变化(汇总一行,不逐天刷屏) |
|
||||
| `EDIT_NODE` | 编辑已有行程节点(单价/数量改价) |
|
||||
| `ADD_NODE` | 新增行程节点(增项) |
|
||||
| `REMOVE_NODE` | 删除行程节点(删项) |
|
||||
| `HOTEL_REQ` | 酒店需求已调整(粒度:本期只记「已调整」一句) |
|
||||
| `VEHICLE_REQ` | 车辆需求已调整(粒度:本期只记「已调整」一句) |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| code | message | 含义 | 前端处理建议 |
|
||||
|---|---|---|---|
|
||||
| 586001 | 订单不存在 | 订单 ID 无效或已删除 | toast 错误 |
|
||||
| 200 + data:[] | (正常) | 本订单暂无调整记录 | 渲空态「暂无调整记录」 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功——7 项变更(含状态回滚)
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/7910000000001/adjustment-record
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "7920000000001",
|
||||
"occurredAt": "2026-06-28 14:35:18",
|
||||
"operatorName": "李雯",
|
||||
"changeCount": 7,
|
||||
"items": [
|
||||
{ "type": "ADD_NODE", "label": "增项「敖鲁古雅驯鹿园」", "before": null, "after": null, "amountDelta": 200.00 },
|
||||
{ "type": "ADD_NODE", "label": "增项「莫尔道嘎国家森林公园」", "before": null, "after": null, "amountDelta": 150.00 },
|
||||
{ "type": "ADD_NODE", "label": "增项「室韦俄罗斯族民族乡」", "before": null, "after": null, "amountDelta": 0.00 },
|
||||
{ "type": "ADD_NODE", "label": "增项「额尔古纳湿地公园」", "before": null, "after": null, "amountDelta": 120.00 },
|
||||
{ "type": "ADD_NODE", "label": "增项「黑山头古城遗址」", "before": null, "after": null, "amountDelta": 0.00 },
|
||||
{ "type": "TRIP_DAYS", "label": "行程天数调整", "before": "7天6晚", "after": "8天7晚", "amountDelta": null },
|
||||
{ "type": "HEADCOUNT", "label": "成人人数调整", "before": "2", "after": "4", "amountDelta": null }
|
||||
],
|
||||
"statusNote": {
|
||||
"title": "状态回滚",
|
||||
"content": "请核对补充新增旅客信息·房型与车辆已退回准备中,等待房控/车控复核"
|
||||
},
|
||||
"balanceBefore": 0.00,
|
||||
"balanceAfter": 0.00
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——无调整记录
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/7910000000002/adjustment-record
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": []
|
||||
}
|
||||
```
|
||||
|
||||
> 前端渲空态「暂无调整记录」。
|
||||
|
||||
### 8.3 业务失败——订单不存在
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9999999999999/adjustment-record
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 586001,
|
||||
"message": "订单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**适用:**
|
||||
- 任意状态的订单均可调用(含已取消、已完成),用于查阅历史。
|
||||
- 每次调整弹窗 submit 成功后落一条记录;无 submit 历史则返空数组。
|
||||
|
||||
**不适用:**
|
||||
- 本接口不展示行程点编辑历史(行程节点单点增删改不走 adjustment-record,走时间线)。
|
||||
- 不展示状态变更日志(时间线接口覆盖)。
|
||||
|
||||
**特殊边界:**
|
||||
- `statusNote` 只在本次调整引发订单子流程状态回滚(如改期/增减天导致配房配车退回准备中)时有值,否则为 `null`,前端需做空值保护。
|
||||
- `amountDelta` 是每项操作的「声明价差」,与后端算价引擎算出的实际尾款差(`balanceAfter - balanceBefore`)可能不等,**尾款以 `balanceBefore` / `balanceAfter` 为准**。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
本次为新增接口,无修改前后对比。
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
本次为新增接口,不影响任何现有接口;回滚只需停止调用本端点。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. `id` 字段是雪花 ID 序列化为字符串,JS 请勿转 Number(精度丢失)。
|
||||
2. `occurredAt` 格式为 `yyyy-MM-dd HH:mm:ss`(后端 Jackson 序列化),非 ISO 8601。
|
||||
3. `items[].label` 已由后端拼好(含资源中文名),前端直接渲染,**不要尝试自己拼接**。
|
||||
4. `before` / `after` 双非空才渲箭头;`statusNote` 为 null 时整块不渲(无需占位符)。
|
||||
5. 本接口不分页,全量返回;若某订单调整次数异常多(> 50),前端可加折叠「展开更多」做 UX 保护(后端不限制)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:[#4540](https://git.1814.love:8443/wx/HL/issues/4540)
|
||||
- PR:[#4556](https://git.1814.love:8443/wx/HL/pulls/4556)
|
||||
- Commit:[34e921b9e](https://git.1814.love:8443/wx/HL/commit/34e921b9ef3b77f6707fc5b720187e876ec5581f)
|
||||
- 后端负责人:腰苏图(yst)
|
||||
@@ -0,0 +1,266 @@
|
||||
# 退役行程编辑历史接口——删除 edit-log/page 端点 + 7 个写接口响应删 editLogId 字段
|
||||
|
||||
> 端类型:管理后台
|
||||
> 变更类型:删除接口(⚠️ 破坏性,前端需清理依赖)
|
||||
> 涉及接口:
|
||||
> - `GET /v3/admin/order/{orderId}/itinerary/edit-log/page`(已下线,调用返 404)
|
||||
> - `POST /v3/admin/order/{orderId}/itinerary/days`(响应删 `editLogId`)
|
||||
> - `PUT /v3/admin/order/{orderId}/itinerary/days/{dayId}`(响应删 `editLogId`)
|
||||
> - `DELETE /v3/admin/order/{orderId}/itinerary/days/{dayId}`(响应删 `editLogId`)
|
||||
> - `POST /v3/admin/order/{orderId}/itinerary/nodes`(响应删 `editLogId`)
|
||||
> - `PUT /v3/admin/order/{orderId}/itinerary/nodes/{nodeId}`(响应删 `editLogId`)
|
||||
> - `DELETE /v3/admin/order/{orderId}/itinerary/nodes/{nodeId}`(响应删 `editLogId`)
|
||||
> - `POST /v3/admin/order/{orderId}/itinerary/save`(响应删 `editLogIds` 数组)
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
搭建期行程节点的单点编辑历史(`order_itinerary_edit_log` 表)已退役:搭建阶段的编辑操作不再留痕,后续调整记录改由「调整记录」(`order_adjustment_record`)承接(见 PR #4556)。本次退役操作包含:
|
||||
1. 下线「行程编辑历史分页」查询端点。
|
||||
2. 7 个行程写操作的响应 VO 删除 `editLogId` / `editLogIds` 字段(原用于记录 Tab 跳转定位,不再有意义)。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| 编号 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | ⚠️ 删除接口 | `GET /v3/admin/order/{orderId}/itinerary/edit-log/page` **已下线,调用返 404** |
|
||||
| 2 | ⚠️ 删除字段 | `ItineraryDayRespVO` 删除 `editLogId`(Long/String)字段 |
|
||||
| 3 | ⚠️ 删除字段 | `ItineraryNodeRespVO` 删除 `editLogId`(Long/String)字段 |
|
||||
| 4 | ⚠️ 删除字段 | `ItinerarySaveRespVO` 删除 `editLogIds`(Long 数组)字段 |
|
||||
| 5 | 保留入参 | `editReason` 入参字段**保留**(后端接受但不再审计落库,不影响前端调用) |
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 已下线接口:GET /v3/admin/order/{orderId}/itinerary/edit-log/page
|
||||
|
||||
⚠️ **本接口已永久下线。调用时网关/服务返回 404 Not Found,不再响应任何请求。**
|
||||
|
||||
| 项目 | 说明 |
|
||||
|---|---|
|
||||
| 方法 | GET(已下线) |
|
||||
| 路径 | `/v3/admin/order/{orderId}/itinerary/edit-log/page` |
|
||||
| 状态 | **已删除,请勿调用** |
|
||||
|
||||
### 保留接口(响应字段有变):行程写操作接口群
|
||||
|
||||
以下 7 个接口**路径和方法不变**,仅出参删字段:
|
||||
|
||||
| 方法 | 路径 | 接口名 | 删除字段 |
|
||||
|---|---|---|---|
|
||||
| POST | `/v3/admin/order/{orderId}/itinerary/days` | 新增行程天 | `editLogId` |
|
||||
| PUT | `/v3/admin/order/{orderId}/itinerary/days/{dayId}` | 修改行程天 | `editLogId` |
|
||||
| DELETE | `/v3/admin/order/{orderId}/itinerary/days/{dayId}` | 删除行程天 | `editLogId` |
|
||||
| POST | `/v3/admin/order/{orderId}/itinerary/nodes` | 新增行程节点 | `editLogId` |
|
||||
| PUT | `/v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` | 修改行程节点 | `editLogId` |
|
||||
| DELETE | `/v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` | 删除行程节点 | `editLogId` |
|
||||
| POST | `/v3/admin/order/{orderId}/itinerary/save` | 批量保存行程 | `editLogIds`(数组) |
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口入参
|
||||
|
||||
7 个保留接口的入参**无变化**。`editReason` 字段仍可传(接受但不落库)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参字段
|
||||
|
||||
### ItineraryDayRespVO(删 editLogId)
|
||||
|
||||
以下字段已从新增/修改/删除行程天的响应中移除:
|
||||
|
||||
| 字段 | 原类型 | 变更 |
|
||||
|---|---|---|
|
||||
| `editLogId` | String(Long 序列化) | ⚠️ **已删除** |
|
||||
|
||||
其余字段(`id` / `dayNumber` / `dayDate` / `theme` / `description` / `nodes[]` / `adjustmentId` / `createTime` 等)不变。
|
||||
|
||||
### ItineraryNodeRespVO(删 editLogId)
|
||||
|
||||
| 字段 | 原类型 | 变更 |
|
||||
|---|---|---|
|
||||
| `editLogId` | String(Long 序列化) | ⚠️ **已删除** |
|
||||
|
||||
其余字段不变。
|
||||
|
||||
### ItinerarySaveRespVO(删 editLogIds)
|
||||
|
||||
| 字段 | 原类型 | 变更 |
|
||||
|---|---|---|
|
||||
| `editLogIds` | List<Long> | ⚠️ **已删除** |
|
||||
|
||||
现有出参字段(`savedDayIds` / `savedNodeIds` / `deletedDayIds` / `deletedNodeIds`)不变。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
无新增枚举变更。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 接口 | code | message | 说明 |
|
||||
|---|---|---|---|
|
||||
| 已下线端点 | 404 | 接口不存在 | 调用旧路径返回此错误 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 8.1 典型成功——新增行程天响应(editLogId 已不存在)
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/7910000000001/itinerary/days
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"theme": "天池徒步",
|
||||
"description": "长白山天池观景台",
|
||||
"editReason": "补充行程细节"
|
||||
}
|
||||
```
|
||||
|
||||
**响应(editLogId 字段已删除)**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "8801234567890",
|
||||
"dayNumber": 3,
|
||||
"dayDate": "2026-08-03",
|
||||
"theme": "天池徒步",
|
||||
"description": "长白山天池观景台",
|
||||
"nodes": [],
|
||||
"adjustmentId": null,
|
||||
"createTime": "2026-06-28T15:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:响应中**不含 `editLogId` 字段**,原来前端读取的 `data.editLogId` 现在为 `undefined`,请清理相关依赖。
|
||||
|
||||
### 8.2 边界情况——批量保存响应(editLogIds 已不存在)
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/7910000000001/itinerary/save
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"days": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
**响应(editLogIds 数组已删除)**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"savedDayIds": ["8801234567890"],
|
||||
"savedNodeIds": ["8811234567890", "8811234567891"],
|
||||
"deletedDayIds": [],
|
||||
"deletedNodeIds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 业务失败——调用已下线的旧路径
|
||||
|
||||
**请求**
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/7910000000001/itinerary/edit-log/page
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应(404,旧路径已删)**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "接口不存在: GET /v3/admin/order/{id}/itinerary/edit-log/page",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
**不适用:**
|
||||
- 旧的「行程编辑历史」分页功能已永久下线,无法通过任何方式恢复调用。
|
||||
- 搭建期单点编辑操作不再审计留痕(若需调整记录,使用调整弹窗 submit,走「调整记录」接口 PR #4556)。
|
||||
|
||||
**特殊边界:**
|
||||
- `editReason` 入参字段可以继续传(后端不报错),但值被忽略,不再写入任何审计表。前端无需改传参代码,但继续传入对服务端无实际效果。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
### 接口变更
|
||||
|
||||
| 接口 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `GET .../itinerary/edit-log/page` | 正常返回 PageResult<EditLogVO> | **已下线,返回 404** |
|
||||
| 行程写操作 7 个接口 | 响应含 `editLogId` / `editLogIds` | **响应不含该字段** |
|
||||
|
||||
### 字段级对比
|
||||
|
||||
#### ItineraryDayRespVO / ItineraryNodeRespVO
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `editLogId` | String,本次写入的 edit_log 主键 | ⚠️ **已删除,不存在** |
|
||||
|
||||
#### ItinerarySaveRespVO
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `editLogIds` | List<Long>,本次产生的 edit_log id 列表 | ⚠️ **已删除,不存在** |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
**前端需做的改造:**
|
||||
1. 删除对 `GET .../itinerary/edit-log/page` 的调用及相关 UI(行程编辑历史列表)。
|
||||
2. 删除读取 `data.editLogId` 的代码(行程天/节点写操作响应)。
|
||||
3. 删除读取 `data.editLogIds` 的代码(批量保存行程响应)。
|
||||
|
||||
**回滚方案:**
|
||||
本次是纯删除,字段/端点已从代码移除。若需回滚,需后端重新发版恢复,前端无单独回滚方案。**建议前端同步本次上线部署后立即清理依赖,避免读 undefined 字段引发运行时错误。**
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
1. **旧路径 404**:`edit-log/page` 已彻底下线,务必在本次联调前删除调用代码,否则页面会出现 404 请求错误。
|
||||
2. **editLogId 变 undefined**:行程写操作响应不再包含 `editLogId`,若前端曾用此值做「记录 Tab 跳转定位」,该功能需同步下线或改用时间线接口。
|
||||
3. **editLogIds 变 undefined**:批量保存响应中 `editLogIds` 数组已删,若前端有读取逻辑需一并清理。
|
||||
4. **editReason 入参可保留**:入参字段 `editReason` 仍可传(后端不报错),不强制移除,但其值不再产生任何审计记录。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:[#4565](https://git.1814.love:8443/wx/HL/issues/4565)
|
||||
- PR:[#4569](https://git.1814.love:8443/wx/HL/pulls/4569)
|
||||
- Commit:[a5a321ebe](https://git.1814.love:8443/wx/HL/commit/a5a321ebecb58b0b656d2889b00612ee28ef3862)
|
||||
- 后端负责人:腰苏图(yst)
|
||||
@@ -0,0 +1,100 @@
|
||||
# 配房流程大改:天级确认(询房中→单日确认→整体确认→可改)+ 删记录式询房
|
||||
|
||||
> 变更类型:🔧 流程重构(**房务管家前端需较大迁移**)
|
||||
> 端类型:管理后台(房务管家·配房/详情/日历/待办)
|
||||
> 日期:2026-06-27 | 工单:#4469 #4474 | PR:#4470 #4475 | 服务:hl-order-service-v3(双实例已部署测试服,22 项全场景 E2E + DB 地面真相核对通过)
|
||||
|
||||
> ⚠️ 本单**取代**早前 `27_4427`(询房候选流程)的「询房候选」部分——询房中候选改由配房行 `confirm_status` 派生,不再有记录式询房消息。`28_4435` 的 `currentAssignments`(一天多家)保留但语义收紧为「仅已确认」。
|
||||
|
||||
---
|
||||
|
||||
## 一、配房流程改为「天级确认」(核心)
|
||||
|
||||
新流程:**抢单 → 单日行程点「配房」选酒店/房型/数量(可多选多家候选)→ 该天「询房中」→ list 每天单独「单日确认」→ 整体确认 → 整体确认后还能改房**。
|
||||
|
||||
「询房」= **只复制话术**(记录式发起询房/询房历史已删,见第四节)。
|
||||
|
||||
配房行新增确认态 `confirm_status`:
|
||||
- `INQUIRING`(询房中)= 配房时选的候选,**不占库存**;
|
||||
- `CONFIRMED`(已确认)= 单日确认保留的,**此刻才扣库存**。
|
||||
|
||||
---
|
||||
|
||||
## 二、新端点(2 个)
|
||||
|
||||
### 1. 单日确认
|
||||
`POST /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm`
|
||||
- Body(可整体省略):`{ "keepAssignmentIds": [Long...] }` —— 从该天「询房中」候选里**挑要保留的**(可多家);**省略/空数组 = 该天全部候选都保留**。
|
||||
- 行为:保留的行 → `CONFIRMED` + **扣 resource 库存**(库存不足在此拒,code `ROOM_STOCK_INSUFFICIENT`);落选的行软删。全部天都已确认时需求自动进「待最终确认」。
|
||||
- 出参 `Result<Void>`;幂等同 `(requirementId, dayNumber)` 3 秒。
|
||||
|
||||
### 2. 整体确认后解冻改房(reopen)
|
||||
`POST /v3/admin/order/hotel-requirements/{requirementId}/reopen`
|
||||
- 用途:整体确认(已完成)后房务还要改房时调用,把需求从「已完成」退回「配房中」再改。
|
||||
- 放行条件:订单 `flow_status ≤ 待确认(PENDING_CONFIRM)`(含待支付/待补全信息/资源准备/待确认);已进下游(待出行及以后)→ 拒 `808122`「订单已确认进入下游,如需改房请由定制师重新调整需求」。
|
||||
- 幂等:已是「配房中」重复调用安全。
|
||||
|
||||
> 配房保存 `POST .../assignments`、改价 `PUT .../assignments/{id}`、删配房 `DELETE .../assignments/{id}` 在「已完成」态仍被拦 `808121`,**必须先 reopen** 才能改。
|
||||
|
||||
---
|
||||
|
||||
## 三、详情 `GET /admin/house/orders/{orderId}` 字段变更(`itinerary[]` 每天)
|
||||
|
||||
| 字段 | 变更 |
|
||||
|---|---|
|
||||
| `arrange` / `arrangeLabel` | 三态语义改由 `confirm_status` 派生:`pending`(待配房,无配房行) / `waiting`(询房中,有 INQUIRING 候选无已确认) / `confirmed`(已确认,有 CONFIRMED 行) |
|
||||
| **`inquiringAssignments`(新增数组)** | 该天「询房中」候选行,结构同 `currentAssignment`(assignmentId/hotelId/roomTypeId/hotelName/roomCategory/roomCount/protoPrice/sellPrice,金额+雪花字符串)。**前端单日确认弹窗读它挑选**,拿 `assignmentId` 作 `keepAssignmentIds` |
|
||||
| `currentAssignments`(数组) | 语义收紧:**只含已确认(CONFIRMED)行**(之前含全部 active) |
|
||||
| `currentAssignment`(单数,已废弃) | = `currentAssignments[0]` |
|
||||
| ~~`inquiries` / `InquiryCandidate`~~ | **删除**(旧记录式询房候选,改用 `inquiringAssignments`) |
|
||||
| ~~`waitingDesc`~~ | **删除** |
|
||||
|
||||
---
|
||||
|
||||
## 四、删除 6 个记录式询房端点(询房=只复制话术)
|
||||
|
||||
**删**(调用返 body `code:404 接口不存在`):
|
||||
- `POST /v3/admin/order/inquiry/send`(发起询房)
|
||||
- `POST /v3/admin/order/inquiry/{id}/reply`(回填)
|
||||
- `GET /v3/admin/order/inquiry`(询房历史)
|
||||
- `GET /v3/admin/order/inquiry/timeouts`
|
||||
- `POST /v3/admin/order/inquiry/{id}/resend`(重发)
|
||||
- `POST /v3/admin/order/inquiry/{id}/escalate`(加急)
|
||||
|
||||
**保留**:`POST /v3/admin/order/inquiry/preview`(复制话术,房型已翻中文如「标间」)。
|
||||
|
||||
前端需:去掉「发起询房」「询房历史」入口,询房按钮接 `/inquiry/preview` 复制话术。
|
||||
|
||||
---
|
||||
|
||||
## 五、日历 `GET /admin/house/calendar`
|
||||
|
||||
- 「询房中」黄点桶现在 = 该天有 `confirm_status=INQUIRING` 的配房行(不再读记录式询房消息)。`summary.inquiry` 同义。
|
||||
- 4 桶不变(异常/询房中/待配房/进行中),其中「已抢未配」仍归进行中(#4448 不回退)。
|
||||
|
||||
---
|
||||
|
||||
## 六、待办 `GET /v3/admin/order/todos`
|
||||
|
||||
- **`stats` 新增两键**:`REQUIREMENT_ADJUSTED`(异常订单·需求变更,定制师改出发日期/行程天数)、`HOTEL_REPLY_TIMEOUT`(酒店超12h未回复)。
|
||||
- **删** `stats` 的 `INQUIRY_TIMEOUT` / `INQUIRY_ESCALATED` 键。
|
||||
- 新 `todoType=HOTEL_REPLY_TIMEOUT`(label「酒店超时未回复」):某天选了酒店发了话术但 12h 内没单日确认 → 待办提醒(每小时 job 产,房务单日确认/取消/标完成后自动消)。
|
||||
- 「我的接单」`list[]` 删 `waitingDesc`。
|
||||
|
||||
### 6b. 待办列表并入 ①刚抢单 + ④定制师未读消息(派生项,PR #4480)
|
||||
|
||||
`GET /v3/admin/order/todos` 的 `list[]` 新增 2 类**派生项**(实时算,非持久化):
|
||||
- **`todoType=PENDING_ARRANGE`「刚抢单待配房」**:当前房务 claimer 且 `house_status=CLAIMING` 的需求(一需求一条,带 `requirementId`)。
|
||||
- **`todoType=UNREAD_CHAT`「定制师消息未读」**:当前房务抢的订单中**有定制师未读消息**(unread>0)的订单(一订单一条,带 `unreadCount`)。**被动展示,不主动推送通知**。
|
||||
|
||||
派生项标识 `derived:true`、**无 `id`(不可走待办 RESOLVE 端点)**,源条件消失即自然消失(①配齐确认离开 CLAIMING / ④房务读消息 unread 归 0)。**派生项置顶、仅第 1 页出现、不计入 `total`**。`stats` 再加 `PENDING_ARRANGE`、`UNREAD_CHAT` 两全量计数键。
|
||||
|
||||
前端:待办列表渲染 5 类(②③⑤持久化可处理 + ①④派生只展示/点击跳转);派生项 `derived=true` 不显示「处理/解决」按钮。
|
||||
|
||||
---
|
||||
|
||||
## 七、测试服实测(22 项全场景 E2E,DB 地面真相核对)
|
||||
|
||||
抢单→配2候选(INQUIRING不扣库存)→询房中→单日确认(挑保留→CONFIRMED+落选软删+此刻扣库存)→逐天→整体确认(房控DONE)→已确认改房拦808121→reopen解冻→可改;一天保留多家→多家CONFIRMED各扣库存;取消已配房→异常+退订待办;preview话术翻中文;6询房端点已删;reopen下游拒808122;未付款单reopen放行;酒店12h待办单日确认自动消。全部通过。
|
||||
|
||||
> 后端已完成并实测,前端按本单迁移。
|
||||
@@ -0,0 +1,249 @@
|
||||
# 打印签单出参重制(自包含通知单 + 字段全中文化)
|
||||
|
||||
> 变更类型:**修改接口** | 端类型:**管理后台** | 接口:`GET /v3/admin/order/{id}/sign-voucher`
|
||||
> 合并自 3 个 PR:#4549(FROM 下沉 + 付款方式 + 经办人)、#4566(订单概要下沉 + 司机/导游)、#4584(房型/结算/付款方式中文化)
|
||||
> 原始接口见 #4505。本文给出**最终出参契约**,前端按此对接即可。
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
订单详情「打印签单」功能,后端组装「酒店预订通知单 + 景点门票预订通知单」(A4 横向传真单)数据。原型每张单独立打印,发给不同供应商。本次按验收反馈把出参重制为「每张通知单完全自包含」,并把所有字典 code 后端翻成中文。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
1. **删除共享 `header`**:原 `data.header`(订单概要 + FROM 抬头)整体删除,所有字段下沉到每个 `entries[]`,每条自成一张可独立打印的 A4 单。
|
||||
2. **订单概要下沉**:`teamNo` / `productName` / `dateRange` / `paxSummary` 移到每个 entry。
|
||||
3. **FROM 抬头下沉 + 新增经办人**:`fromCompany` / `taxId` 移到每个 entry,新增 `operatorName`(经办人/发件人=订单定制师)。
|
||||
4. **新增司机/导游**:每个 entry 加 `driverName` / `driverPhone` / `guideName` / `guidePhone`(电话为内网明文,供应商可联系;无则空串)。
|
||||
5. **新增付款方式**:每个 entry 加 `settleType`(结算方式)/ `paymentMode`(付款方式)。
|
||||
6. **字段全中文化**:`items[].name`(房型)、`settleType`、`paymentMode` 由原本返字典 code 改为**后端翻好的中文**,前端不再接触机器码。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 | GET |
|
||||
| 路径 | `/v3/admin/order/{id}/sign-voucher` |
|
||||
| 鉴权 | 管理后台登录态(Gateway JWT) |
|
||||
| 返回 | `Result<SignVoucherRespVO>` |
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `id` | path | Long | 是 | 订单 ID |
|
||||
| `showAmount` | query | Boolean | 否 | 是否展示金额,默认 `false`。`false`=脱敏(所有 unitPrice/amount/totalAmount 置 null,前端渲染 `***` 防成本价外泄给供应商);`true`=展示金额 |
|
||||
|
||||
> 入参无变化(与原始接口一致)。
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
**顶层**(注意:**已无 `header`**,只有 `entries`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `entries` | array | 预订通知单列表(每条完全自包含一张 A4 单) |
|
||||
|
||||
**`entries[]`(每张预订通知单)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `entryId` | string | 条目唯一 ID,酒店 `H{assignmentId}` / 景点 `S{assignmentId}`,前端勾选用 |
|
||||
| `type` | string | `HOTEL` / `SCENIC` |
|
||||
| `title` | string | "酒店预订通知单" / "景点门票预订通知单" |
|
||||
| `teamNo` | string | 团号(订单概要,下沉) |
|
||||
| `productName` | string | 产品名 |
|
||||
| `dateRange` | string | 出行日期范围 `depart→return`(任一为空返空串) |
|
||||
| `paxSummary` | string | 人数摘要,如 "2大1小"(成人=大,儿童+幼童=小) |
|
||||
| `driverName` | string | 司机姓名(无司机返空串 `""`) |
|
||||
| `driverPhone` | string | 司机电话(内网明文,无则空串) |
|
||||
| `guideName` | string | 导游姓名(无导游返空串) |
|
||||
| `guidePhone` | string | 导游电话(内网明文,无则空串) |
|
||||
| `fromCompany` | string | FROM 本社名(下沉,每条自带) |
|
||||
| `taxId` | string | 纳税人识别号(统一社会信用代码) |
|
||||
| `operatorName` | string | 经办人/发件人(订单定制师 consultant_name) |
|
||||
| `supplierName` | string | TO 供应商名(酒店名 / 景点名) |
|
||||
| `contactPerson` | string | 联系人/收件人(取不到返空串) |
|
||||
| `contactPhone` | string | 联系电话(取不到返空串) |
|
||||
| `fax` | string | 传真(本期恒 null) |
|
||||
| `settleType` | string | **结算方式中文**(酒店:现付/签单/公司付款;景点:恒空串) |
|
||||
| `paymentMode` | string | **付款方式中文**(酒店:预付款/现结/月结;景点:恒空串) |
|
||||
| `items` | array | 明细行 |
|
||||
| `totalAmount` | string(可空) | 合计金额(`showAmount=false` 时 null;BigDecimal 序列化为字符串防精度丢失) |
|
||||
|
||||
**`entries[].items[]`(明细行)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `name` | string | **中文名**:酒店=房型中文(大床房/标间…,未知房型回退「其他房型」);景点=固定"门票" |
|
||||
| `date` | string(date) | 日期,如 `2026-07-01` |
|
||||
| `qty` | int | 数量:酒店=房间数;景点=订单总人数(成人+儿童,婴幼儿不计) |
|
||||
| `unit` | string | "间夜" / "张" |
|
||||
| `unitPrice` | string(可空) | 单价(`showAmount=false` 时 null) |
|
||||
| `amount` | string(可空) | 金额(`showAmount=false` 时 null) |
|
||||
| `remark` | string | 备注(酒店取配房备注;景点恒 null) |
|
||||
|
||||
> 金额字段(unitPrice/amount/totalAmount)均为 BigDecimal,经 ToStringSerializer 序列化为**字符串**。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**`settleType`(结算方式,后端已翻中文,前端直接展示)**
|
||||
|
||||
| 后端返回(中文) | 含义 |
|
||||
|---|---|
|
||||
| 现付 | 现场付款 |
|
||||
| 签单 | 签单挂账 |
|
||||
| 公司付款 | 公司统一付款 |
|
||||
|
||||
**`paymentMode`(付款方式,后端已翻中文)**
|
||||
|
||||
| 后端返回(中文) | 含义 |
|
||||
|---|---|
|
||||
| 预付款 | 预付 |
|
||||
| 现结 | 现场结算 |
|
||||
| 月结 | 按月结算 |
|
||||
|
||||
**`items[].name`(房型,后端已翻中文)**:标间 / 单人间 / 双床房 / 大床房 / 豪华大床 / 套房 / 家庭房 / 蒙古包 / 特色房 / 亲子房 …;字典未命中回退「其他房型」。
|
||||
|
||||
> ⚠️ 前端**不再收到** `sign` / `prepay` / `BIG_BED` 等机器码,settleType/paymentMode/房型一律为中文。景点的 settleType/paymentMode 本期恒空串(景点资源未建结算字段)。
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码
|
||||
|
||||
| 场景 | code | 说明 |
|
||||
|---|---|---|
|
||||
| 订单不存在 | 581007 | ORDER_NOT_FOUND |
|
||||
| 未登录 | 401 | 网关鉴权 |
|
||||
|
||||
> 无新增业务错误码。
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
**典型(`showAmount=false`,1 酒店 + 1 景点)**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"entries": [
|
||||
{
|
||||
"entryId": "H2070758427717525506",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店预订通知单",
|
||||
"teamNo": "26-1590",
|
||||
"productName": "呼伦贝尔7日游",
|
||||
"dateRange": "2026-07-01→2026-07-07",
|
||||
"paxSummary": "2大1小",
|
||||
"driverName": "全广存",
|
||||
"driverPhone": "18614805444",
|
||||
"guideName": "",
|
||||
"guidePhone": "",
|
||||
"fromCompany": "内蒙古呼籁国际旅行社有限公司",
|
||||
"taxId": "91150702MACYFE851R",
|
||||
"operatorName": "张定制",
|
||||
"supplierName": "呼伦贝尔香格里拉大酒店",
|
||||
"contactPerson": "王经理",
|
||||
"contactPhone": "0470-8888888",
|
||||
"fax": null,
|
||||
"settleType": "签单",
|
||||
"paymentMode": "预付款",
|
||||
"items": [
|
||||
{ "name": "大床房", "date": "2026-07-01", "qty": 1, "unit": "间夜", "unitPrice": null, "amount": null, "remark": "含双早" }
|
||||
],
|
||||
"totalAmount": null
|
||||
},
|
||||
{
|
||||
"entryId": "S2071200000000000001",
|
||||
"type": "SCENIC",
|
||||
"title": "景点门票预订通知单",
|
||||
"teamNo": "26-1590",
|
||||
"productName": "呼伦贝尔7日游",
|
||||
"dateRange": "2026-07-01→2026-07-07",
|
||||
"paxSummary": "2大1小",
|
||||
"driverName": "全广存", "driverPhone": "18614805444",
|
||||
"guideName": "", "guidePhone": "",
|
||||
"fromCompany": "内蒙古呼籁国际旅行社有限公司",
|
||||
"taxId": "91150702MACYFE851R",
|
||||
"operatorName": "张定制",
|
||||
"supplierName": "呼和诺尔草原旅游区",
|
||||
"contactPerson": "", "contactPhone": "", "fax": null,
|
||||
"settleType": "", "paymentMode": "",
|
||||
"items": [
|
||||
{ "name": "门票", "date": "2026-07-03", "qty": 2, "unit": "张", "unitPrice": null, "amount": null, "remark": null }
|
||||
],
|
||||
"totalAmount": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**边界(无酒店无景点)**:`data.entries` = `[]`(空数组)。
|
||||
|
||||
**异常(订单不存在)**:
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 一个订单一个本社:所有 entry 的 `fromCompany`/`taxId`/`operatorName` 相同(同一定制师/本社)。
|
||||
- 司机/导游各取本单首条 DRIVER/GUIDE 角色人员;未配则四字段空串。
|
||||
- 酒店按 `hotelId` 分组,每酒店一张通知单(多晚合为一组多条 item);每条景点一张通知单。
|
||||
- 自定义景点(无 scenicId)联系人留空。
|
||||
- `showAmount=false`(默认)下所有金额 null,前端渲染 `***`。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| 维度 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 结构 | `data.header`(共享概要+FROM)+ `data.entries[]` | **只有 `data.entries[]`**,header 删除,所有字段下沉 |
|
||||
| FROM | header 一份 | 每个 entry 自带 fromCompany/taxId/**operatorName(新增)** |
|
||||
| 订单概要 | header 里 | 每个 entry 里 teamNo/productName/dateRange/paxSummary |
|
||||
| 司机/导游 | 无(header.driverName 恒 null) | 每个 entry driverName/driverPhone/guideName/guidePhone |
|
||||
| 付款方式 | 无 | 每个 entry settleType/paymentMode |
|
||||
| settleType/paymentMode/房型 | 返字典 code(sign/prepay/BIG_BED) | 返中文(签单/预付款/大床房) |
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **前端必改**:原读 `data.header.xxx` 的代码全部改读 `data.entries[i].xxx`(header 已删,读 header 会拿到 undefined)。
|
||||
- settleType/paymentMode/房型原本若前端自己做 code→中文映射,可删除该映射逻辑(后端已翻好)。
|
||||
- 回滚:接口为纯读组装,无 DDL、无状态写入,回滚仅需后端版本回退。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 司机/导游电话为**内网明文全号**(供应商需联系司机/导游,与酒店联系人电话同口径),前端按需展示。
|
||||
- 景点 `settleType`/`paymentMode` 本期恒空串(景点资源侧未建结算字段,待后续)。
|
||||
- 房型/结算/付款中文依赖数据字典(`room_category`/`hotel_settle_type`/`hotel_payment_mode`),字典缺值时:房型回退「其他房型」,结算/付款回退空串——前端做空值兜底即可。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:[#4549](https://git.1814.love:8443/wx/HL/issues/4549) / [#4566](https://git.1814.love:8443/wx/HL/issues/4566) / [#4582](https://git.1814.love:8443/wx/HL/issues/4582)(原始 [#4505](https://git.1814.love:8443/wx/HL/issues/4505))
|
||||
- PR:[#4550](https://git.1814.love:8443/wx/HL/pulls/4550) / [#4567](https://git.1814.love:8443/wx/HL/pulls/4567) / [#4584](https://git.1814.love:8443/wx/HL/pulls/4584)
|
||||
- 负责人:腰苏图(后端)
|
||||
@@ -0,0 +1,229 @@
|
||||
# 【修改接口·管理后台】订单调整记录:出参枚举新增 TRAVELER_EDIT(出行人资料修改)(#4614)
|
||||
|
||||
> **PR**: #4614 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-29
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
「订单调整记录」接口(`GET /v3/admin/order/{id}/adjustment-record`)返回本订单所有调整操作的逐项变更明细。每条记录含 `items[]` 数组,每个变更项有 `type` 字段表示该项变更的类型。
|
||||
|
||||
本次在 `type` 枚举中**新增 `TRAVELER_EDIT`(出行人资料修改)**。此前,定制师在「订单调整」中只修改出行人资料字段(证件类型 / 姓名 / 证件号 / 联系电话),不改人数时,调整记录里**不会产生任何变更项**,导致历史操作看不到「改了什么」。本次修复这一缺口,使每次出行人资料编辑都有迹可查。
|
||||
|
||||
同时修复了纯重复提交(出行人字段实际无变化)产生 `changeCount=0` 空记录的问题——现在若字段真的未变,不再产生多余的空记录。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 订单调整记录列表 | GET | `/v3/admin/order/{id}/adjustment-record` | 修改接口 | 出参 `items[].type` 枚举新增 `TRAVELER_EDIT` 取值 |
|
||||
|
||||
> 无新增字段、无删除字段、无路径变更,向后兼容。若前端对 `type` 做了穷举 switch / 枚举映射,需补 `TRAVELER_EDIT` 分支。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
- **路径**: `GET /v3/admin/order/{id}/adjustment-record`
|
||||
- **使用场景**: 管理后台订单详情页「调整记录」Tab,展示历次调整操作的变更明细。
|
||||
- **认证**: 需要管理后台 JWT。
|
||||
- **幂等性**: GET 只读,无副作用。
|
||||
- **限流**: 无特殊限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | Long | ✅ | 订单 ID |
|
||||
|
||||
无请求体。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**顶层结构**: `Result<List<AdjustmentRecordVO>>`
|
||||
|
||||
**AdjustmentRecordVO**(每条调整记录)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | String | 调整记录 ID |
|
||||
| `operatorName` | String | 操作人姓名 |
|
||||
| `adjustedAt` | String | 调整时间(ISO 8601) |
|
||||
| `changeCount` | Integer | 本次调整的变更项数量 |
|
||||
| `balanceBefore` | String | 调整前应付尾款(字符串,如 `"1000.00"`) |
|
||||
| `balanceAfter` | String | 调整后应付尾款(字符串) |
|
||||
| `items` | Array\<ChangeItemVO\> | 变更项明细列表 |
|
||||
|
||||
**ChangeItemVO**(单个变更项)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | String | 变更类型枚举(见第 6 节) |
|
||||
| `label` | String | 后端拼好的中文展示名(直接渲染,如 `出行人「张三」资料修改`) |
|
||||
| `before` | String | 变更前描述(如 `证件类型 身份证;证件号 110***1234`) |
|
||||
| `after` | String | 变更后描述(如 `证件类型 护照;证件号 E12***5678`) |
|
||||
| `amountDelta` | String / null | 本项价差(字符串);`TRAVELER_EDIT` 类型此字段恒为 `null`(资料编辑无价差) |
|
||||
|
||||
> `before` / `after` 中的证件号、手机号已由后端脱敏(首 3 末 4,中间全 `*`),前端直接展示即可,无需二次处理。
|
||||
> 证件类型显示中文名(身份证 / 护照 / 台胞证 / 回乡证…),由后端查数据字典翻译。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### AdjustChangeType(变更类型枚举)
|
||||
|
||||
| 枚举值 | 中文说明 | `amountDelta` | 本次状态 |
|
||||
|--------|----------|---------------|----------|
|
||||
| `HEADCOUNT` | 人数变更 | 可能有值 | 既有 |
|
||||
| `DEPART_DATE` | 出发日期调整 | 可能有值 | 既有 |
|
||||
| `TRIP_DAYS` | 行程天数调整 | 可能有值 | 既有 |
|
||||
| `EDIT_NODE` | 改项(修改行程节点) | 可能有值 | 既有 |
|
||||
| `ADD_NODE` | 增项(新增行程节点) | 可能有值 | 既有 |
|
||||
| `REMOVE_NODE` | 删项(删除行程节点) | 可能有值 | 既有 |
|
||||
| `HOTEL_REQ` | 酒店需求已调整 | `null` | 既有 |
|
||||
| `VEHICLE_REQ` | 车辆需求已调整 | `null` | 既有 |
|
||||
| **`TRAVELER_EDIT`** | **出行人资料修改** | **`null`** | **本次新增** |
|
||||
|
||||
> 若前端对 `type` 做了穷举渲染(switch / enum map),需补 `TRAVELER_EDIT` 分支,否则该类变更项在列表中可能不显示或落入 default 样式。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
本接口为只读查询接口,本次变更不引入新错误码。既有错误码(如订单不存在 `581001`)不变。
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——包含 TRAVELER_EDIT 变更项
|
||||
|
||||
**请求**:
|
||||
```
|
||||
GET /v3/admin/order/1234567890/adjustment-record
|
||||
Authorization: Bearer <管理后台 JWT>
|
||||
```
|
||||
|
||||
**响应**(200,含一条出行人资料修改记录):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "9876543210",
|
||||
"operatorName": "李顾问",
|
||||
"adjustedAt": "2026-06-29T10:30:00",
|
||||
"changeCount": 1,
|
||||
"balanceBefore": "6000.00",
|
||||
"balanceAfter": "6000.00",
|
||||
"items": [
|
||||
{
|
||||
"type": "TRAVELER_EDIT",
|
||||
"label": "出行人「张三」资料修改",
|
||||
"before": "证件类型 身份证;证件号 110***1234",
|
||||
"after": "证件类型 护照;证件号 E12***5678",
|
||||
"amountDelta": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况——同一次调整多字段同时改变
|
||||
|
||||
当出行人同时修改了证件类型、证件号、手机号三个字段,`before` / `after` 以分号拼串展示所有变化字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "TRAVELER_EDIT",
|
||||
"label": "出行人「李四」资料修改",
|
||||
"before": "证件类型 身份证;证件号 320***5678;手机号 138***0000",
|
||||
"after": "证件类型 台胞证;证件号 TE1***234;手机号 139***9999",
|
||||
"amountDelta": null
|
||||
}
|
||||
```
|
||||
|
||||
> 若出行人只改了一个字段,`before` / `after` 中只出现该字段。
|
||||
|
||||
### 8.3 业务失败——订单不存在
|
||||
|
||||
**请求**(id 不存在):
|
||||
```
|
||||
GET /v3/admin/order/9999999999/adjustment-record
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 581001,
|
||||
"message": "订单不存在",
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
- ✅ `TRAVELER_EDIT` 仅在「出行人资料字段有实际变化」时才产生变更项(证件类型 / 姓名 / 证件号 / 联系电话四字段任一有变化即记录)。
|
||||
- ❌ 出行人字段与原值完全相同(no-op 重复提交)→ 不产生 `TRAVELER_EDIT` 变更项,也不产生 `changeCount=0` 的空记录(本次同步修复)。
|
||||
- ❌ 出行人**增减人数**(HEADCOUNT 变更)→ 走 `HEADCOUNT` 类型,不走 `TRAVELER_EDIT`。
|
||||
- ⚠️ `TRAVELER_EDIT` 的 `amountDelta` 恒为 `null`(资料编辑不涉及价差),`balanceBefore` 与 `balanceAfter` 在该记录下通常相等。
|
||||
- ⚠️ `before` / `after` 已脱敏:前端直接渲染,无需再次脱敏。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 枚举值变化
|
||||
|
||||
| 类型 | 变更 |
|
||||
|------|------|
|
||||
| `TRAVELER_EDIT` | **新增**(此前不存在) |
|
||||
| 其余 8 个取值 | 不变 |
|
||||
|
||||
### 行为变化
|
||||
|
||||
| 场景 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 出行人只改资料字段、不改人数 | 调整记录无变更项(看不到操作记录) | 产生 `type=TRAVELER_EDIT` 变更项,可追溯 |
|
||||
| 出行人字段提交但无实际变化(no-op) | 产生 `changeCount=0` 的空记录 | 不产生空记录 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- **是否破坏向后兼容**: 否。纯新增枚举值 + 新增场景下的数据;不影响既有变更项。
|
||||
- **前端是否必须同步上线**: 非强制,但**建议尽快补 `TRAVELER_EDIT` 分支**。若前端对 `type` 做穷举渲染而未覆盖此值,出行人资料修改记录会不显示或以 default 样式渲染。
|
||||
- **回滚方案**: 本次变更在 Service 层追加枚举分支,后端回滚即恢复旧行为(不产生 `TRAVELER_EDIT` 记录);已写入 DB 的历史记录不会自动清除,但量极少(仅上线后的操作)。前端无需配合回滚。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 若前端调整记录组件对 `items[].type` 做 switch 渲染(如显示图标 / 颜色),需新增 `TRAVELER_EDIT` 分支,建议展示样式与 `HOTEL_REQ` / `VEHICLE_REQ`(无价差的信息类变更)一致。
|
||||
2. `before` / `after` 中的多字段拼接格式为「字段名 原值;字段名 原值」,前端按原文展示即可,无需解析内部结构。
|
||||
3. 证件号 / 手机号已由后端脱敏,前端**不可**用 `before` / `after` 做业务判断(脱敏后精度已损)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
### 13.1 链接
|
||||
|
||||
- **Issue**: [#4612](https://git.1814.love:8443/wx/HL/issues/4612)
|
||||
- **PR**: [#4614](https://git.1814.love:8443/wx/HL/pulls/4614)
|
||||
- **Merge commit**: [1744d31b4](https://git.1814.love:8443/wx/HL/commit/1744d31b4875752d5e74cd5c16f7d787c17eeb68)
|
||||
|
||||
### 13.2 联系人
|
||||
|
||||
- **后端负责人**: @yst
|
||||
- **前端对接(管理后台)**: 订单详情·调整记录 Tab
|
||||
@@ -0,0 +1,48 @@
|
||||
# 去房务抢单上限(30单) + 改期/改天数后补「需求变更重配」待办
|
||||
|
||||
> 变更类型:🔧 行为变更(房务管家·抢单/待办)
|
||||
> 端类型:管理后台(房务管家)
|
||||
> 日期:2026-06-27 | 工单:#4489 | PR:#4490 | 服务:hl-order-service-v3(双实例已部署测试服,API 实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 一、去除房务抢单上限(不再限单数)
|
||||
|
||||
房务抢单/转单**不再有「同时在跟 30 单」上限**。
|
||||
- 抢单 `POST /v3/admin/order/hotel-requirements/{requirementId}/claim`:**不再返 `808003`「你已达到同时在跟订单上限(30 单)」**。
|
||||
- 转单接收人:**不再返 `808015`「接收人已达上限」**。
|
||||
- 错误码 `808003` / `808015` **已删**,前端可去掉相关上限提示/拦截逻辑。
|
||||
- 转单候选选人页:所有房务**恒可选**(不再因「在跟 ≥30 单」灰掉);卡片仍展示「在跟订单数 activeCount」供参考。
|
||||
|
||||
前端需:移除抢单/转单的「30 单上限」错误提示与前置拦截。
|
||||
|
||||
---
|
||||
|
||||
## 二、改期/改天数/改酒店后,待办列表新增「需求变更重配」(修复待办⑤永空)
|
||||
|
||||
此前**定制师在调整订单改出发日期/行程天数/行程/酒店需求后,房务侧不产任何提醒**(待办类型 `REQUIREMENT_ADJUSTED` 一直为空)。现已修复:
|
||||
|
||||
`GET /v3/admin/order/todos` 的 `list[]` 现会出现 **`todoType=REQUIREMENT_ADJUSTED`「需求变更重配」** 待办:
|
||||
- 触发:定制师调整订单命中 **出发日期 / 行程天数 / 行程 / 酒店需求** 任一维度后,AFTER_COMMIT 自动产生。
|
||||
- 归属:**上个房务**(被作废旧版的 claimer,`ownerUserId`=上个房务);解析不到时广播(任一房务可见)。
|
||||
- 内容:`title`「客户调整需求 · 原配房失效, 请重新配房」,`urgency=DANGER`,`fromValue`=上个房务名。
|
||||
- 幂等:同一新需求版本只产 1 条(`dedupKey=REQADJ-{requirementId}`)。
|
||||
- `stats.REQUIREMENT_ADJUSTED` 计数同步生效。
|
||||
- 抢单池:该订单重版回池后标 **返工单 `isRework=true`** + 显示「上个房务:X」(依赖此待办存在)。
|
||||
|
||||
前端需:待办列表渲染 `REQUIREMENT_ADJUSTED`(持久化、可处理类,与 REFUND 同款交互);抢单池卡片读 `isRework`/上个房务名区分新单/返工单。
|
||||
|
||||
---
|
||||
|
||||
## 三、测试服实测
|
||||
|
||||
- 房务在跟 37 单时仍可抢单(去上限生效,原 808003 不再)。
|
||||
- 改期 06-28→06-30 后:`REQUIREMENT_ADJUSTED` 待办正确产出,`ownerUserId`=上个房务、`dedupKey=REQADJ-{新版需求ID}`。
|
||||
|
||||
> 后端已完成并实测,前端按本单接入。
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ 关联(订单侧/聊天侧另开工单交后端,前端先知会):
|
||||
> - **改行程天数(减天)** 目前订单侧不级联重置酒店配房 → 超范围晚库存暂未释放(#4491,订单侧修复中)。
|
||||
> - **④定制师未读消息** 待办在「房务先抢单、定制师后联系房务」顺序下暂不触发(#4492,聊天会话子系统修复中)。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 新增「房务组长」只读监督角色 + 组长↔房务基于订单的聊天
|
||||
|
||||
> 变更类型:✨ 新增角色 + 新增接口(房务管家·监督视图/聊天)
|
||||
> 端类型:管理后台(房务管家)
|
||||
> 日期:2026-06-27 | 工单:#4493 | PR:#4494 | 服务:hl-order-service-v3 + hl-user-service(双服务已部署测试服,house_keeper_lead 账号 API 实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 一、新增角色:房务组长(house_keeper_lead)
|
||||
|
||||
新增 sys_role `role_key=house_keeper_lead`(房务组长)。**只读监督角色**:
|
||||
- 看**全部房务**抢到的订单 + 全部房务待办(标明属哪个房务)。
|
||||
- **不可任何写操作**、**不能抢单**。
|
||||
- 可基于订单与对应房务在线聊天。
|
||||
|
||||
由超管在「管理员管理」给账号指派该角色。登录后 JWT `role=house_keeper_lead`。
|
||||
|
||||
---
|
||||
|
||||
## 二、新增:组长监督全部已抢订单
|
||||
|
||||
`GET /v3/admin/order/grab-pool/all-claims/hotel`(入参同「我的接单」`HouseMyOrderPageReqVO`:page/pageSize/houseStatus 等)
|
||||
- **权限**:仅 `house_keeper_lead` / `SUPER_ADMIN` 可调;普通房务调用返 `808091`(无权)。
|
||||
- **出参**:同「我的接单」`HouseMyOrderPageRespVO`(`total` + `stats` 全局计数 + `list[]`),**列表项新增 2 字段**:
|
||||
- `claimerId`(String,雪花)—— 该单所属房务 ID,**前端开「组长↔房务」会话时作 peerAdminId 传入**。
|
||||
- `claimerName`(String)—— 该单所属房务姓名,用于「属哪个房务」展示。
|
||||
|
||||
> 「我的接单」`GET .../my-claims/hotel`(房务看自己的)不变。
|
||||
|
||||
---
|
||||
|
||||
## 三、组长待办(复用现有端点)
|
||||
|
||||
`GET /v3/admin/order/todos?scope=all`:组长传 `scope=all` 即看全部房务待办;待办项 `ownerUserId` / `ownerName` 标明归属房务。**无需新接口、无字段变更**。
|
||||
|
||||
---
|
||||
|
||||
## 四、组长写操作一律被拒(只读铁律)
|
||||
|
||||
组长调用任何房务写端点(抢单 claim / 配房保存 / 单日确认 / 整体确认 / release / transfer / close / reopen / 删配房 / 改价 / 验房日志 / 水单回执 等)一律返:
|
||||
- `code: 808091`「房务组长为只读监督角色,无权执行该操作」。
|
||||
|
||||
前端:组长视图隐藏所有操作按钮(抢单/配房/确认/释放/转单等),仅保留查看 + 聊天入口。
|
||||
|
||||
---
|
||||
|
||||
## 五、新增:组长↔房务基于订单的聊天(方案 A 独立会话)
|
||||
|
||||
`POST /admin/message/chat/open-house-lead`
|
||||
- Body:`{ "orderId": Long, "peerAdminId": Long }` —— `peerAdminId` = 该订单房务(claimer),**前端从 all-claims 列表的 `claimerId` 取**。两者均必填。
|
||||
- 行为:幂等找回/新建会话,键 = **`HOUSE_LEAD:{orderId}`**(与定制师↔房务的 `HOUSE:{orderId}` **独立隔离**,互不污染)。转单后房务变更,再调自动对齐对端。
|
||||
- 出参:`ChatOpenRespVO`(`conversationKey=HOUSE_LEAD:{orderId}` + `isNew` + 对端/未读信息)。
|
||||
- **后续发消息/拉线程/标已读/会话列表/未读总数全部复用现有端点**(按 `conversationKey` 工作):
|
||||
- 发消息 `POST /admin/message/chat/{conversationKey}/messages`
|
||||
- 拉线程 `GET /admin/message/chat/{conversationKey}/messages`
|
||||
- 标已读 `POST /admin/message/chat/{conversationKey}/read`
|
||||
- 房务侧:该会话出现在房务现有聊天 Tab(对端角色 `HOUSE_LEAD`「房务组长」),可正常读取与回复。`senderRole` 自动正确(组长发=HOUSE_LEAD,房务回=HOUSE)。
|
||||
|
||||
前端需:聊天角色徽章新增 `HOUSE_LEAD`「房务组长」;组长在订单行点「联系房务」走 `/open-house-lead`(传 orderId + claimerId)。
|
||||
|
||||
---
|
||||
|
||||
## 六、测试服实测(house_keeper_lead 账号)
|
||||
|
||||
组长看 all-claims(带 claimerId/claimerName)✓;纯房务调 all-claims 拒 808091 ✓;组长抢单/配房/release/reopen 全拒 808091 ✓,普通房务写不被误拦 ✓;组长 open-house-lead→`HOUSE_LEAD:{orderId}`、发消息→房务成员行建出 unread+1、房务可读可回复、与 `HOUSE:{orderId}` 隔离 ✓。
|
||||
|
||||
> 后端已完成并实测,前端按本单接入。
|
||||
>
|
||||
> 补充(#4496 已落地):**普通房务调 all-claims 被拒的错误码为 `808092`「无权查看全部房务订单(仅房务组长或超管可查看)」**(与组长写操作被拒的 `808091`「房务组长为只读监督角色」语义区分)。前端:`808092` 出现在普通房务误调监督端点时,按普通无权提示即可。
|
||||
@@ -0,0 +1,630 @@
|
||||
# 打印行程单接口(出团行程单·Driver Copy)
|
||||
|
||||
**接口路径**:`GET /v3/admin/order/{id}/print-itinerary`
|
||||
**服务**:hl-order-service-v3
|
||||
**PR**:[#4650](https://git.1814.love:8443/wx/HL/pulls/4650) | **Issue**:[#4644](https://git.1814.love:8443/wx/HL/issues/4644) | **合并至**:dev-v3
|
||||
**变更类型**:✨ 新增接口
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
出团前,管理后台定制师(或运营人员)需打印一份完整的出团行程单(俗称"Driver Copy")交给司机/导游/领队,供出行全程参考。本次在订单详情页新增该打印端点,后端聚合 **11 个功能区块**(大交通、客人信息、费用明细、全程住宿、应急联系人、每日行程、注意事项、退费规则、交接确认、代收款回执、备注)一次性返回,前端按 A4 纵向布局渲染并提供打印/PDF 下载入口。
|
||||
|
||||
同源先例:`GET /v3/admin/order/{id}/sign-voucher`(签单 PDF)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| ✨ 新增 | `GET /v3/admin/order/{id}/print-itinerary` | 打印行程单 11 区块全量聚合接口 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| **方法 + 路径** | `GET /v3/admin/order/{id}/print-itinerary` |
|
||||
| **接口名** | 打印行程单(出团行程单·Driver Copy) |
|
||||
| **功能描述** | 聚合订单 11 个打印区块,供前端渲染 A4 行程单并打印 / 导出 PDF |
|
||||
| **认证** | 需携带管理后台 JWT(`Authorization: Bearer <token>`) |
|
||||
| **权限** | 已登录的管理后台用户,无额外角色限制 |
|
||||
| **幂等性** | 只读查询,天然幂等 |
|
||||
| **限流** | 无特殊限流(走网关通用限流) |
|
||||
| **响应格式** | `application/json; charset=utf-8` |
|
||||
| **包装类型** | `Result<PrintItineraryRespVO>` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `id` | Long(JSON 序列化为 String,防 JS 精度丢失) | 是 | 订单 ID(雪花 ID,前端应作为字符串透传,不要转 number) |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
本接口无请求体(GET 请求)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
响应结构:`Result<PrintItineraryRespVO>`,`code=200` 时 `data` 为完整行程单对象。
|
||||
|
||||
### 5.1 根字段(header 区块 + 顶层区块引用)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `agencyName` | String | 本社名称,如「呼籁旅行有限公司」 |
|
||||
| `printTime` | String | 打印时间,ISO-8601 格式,如 `2026-06-29T10:30:00` |
|
||||
| `teamNo` | String | 团号,如 `HLA2026060001` |
|
||||
| `productName` | String | 产品名 |
|
||||
| `dateRange` | String | 出行日期范围,格式 `YYYY-MM-DD→YYYY-MM-DD` |
|
||||
| `paxSummary` | String | 人数摘要,如 `2大1小` |
|
||||
| `driverName` | String | 司机姓名;未配置则为空串 |
|
||||
| `driverPhone` | String | 司机电话(内部明文,仅内网打印场景);未配置则为空串 |
|
||||
| `guideName` | String | 导游姓名;未配置则为空串 |
|
||||
| `guidePhone` | String | 导游电话;未配置则为空串 |
|
||||
| `leaderName` | String | 领队姓名;未配置则为空串 |
|
||||
| `leaderPhone` | String | 领队电话;未配置则为空串 |
|
||||
| `consultantName` | String | 定制师 / 经办人姓名 |
|
||||
| `transports` | `TransportVO[]` | 01 区块:大交通批次列表,按方向升序(ARRIVAL 在前) |
|
||||
| `customerOverview` | `CustomerOverviewVO` | 02 区块:客人信息速览 |
|
||||
| `feeDetail` | `FeeDetailVO` 或 `null` | 03 区块:费用明细;product-v2 Feign 失败时为 `null`,前端应降级展示「暂无」 |
|
||||
| `hotels` | `HotelVO[]` | 04 区块:全程住宿逐晚列表 |
|
||||
| `emergencyContacts` | `EmergencyContactVO[]` | 05 区块:应急兜底联系人(配房房务 claimer) |
|
||||
| `days` | `DayVO[]` | 06 区块:每日行程(来自固化行程表) |
|
||||
| `notices` | `NoticesVO` | 07 区块:注意事项(固定文案,见 §8 示例) |
|
||||
| `refundNotes` | `RefundNoteGroup[]` | 08 区块:退费规则(来自产品快照);无退费说明时为空数组 |
|
||||
| `handoverChecklist` | `String[]` | 09 区块:师傅领队交接确认 checklist(固定条目,前端在每项右侧留签字栏) |
|
||||
| `collectReceipt` | `CollectReceiptVO` | 10 区块:代收款签收回执 |
|
||||
| `remark` | String 或 `null` | 11 区块:备注(来自订单内部备注字段 `consultantRemark`) |
|
||||
|
||||
---
|
||||
|
||||
### 5.2 TransportVO(01 大交通批次)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `direction` | String | 方向枚举值:`ARRIVAL`(到达)/ `DEPARTURE`(出发) |
|
||||
| `transportType` | String | 交通方式枚举值:`FLIGHT`(航班)/ `TRAIN`(火车)/ `SELF_DRIVE`(自驾) |
|
||||
| `transportNo` | String | 航班号 / 车次号;`SELF_DRIVE` 时为空串 |
|
||||
| `carrier` | String | 航司 / 铁路公司名称 |
|
||||
| `departStation` | String | 出发站 / 机场全称 |
|
||||
| `arriveStation` | String | 到达站 / 机场全称 |
|
||||
| `departTime` | String(LocalDateTime) | 出发时间,格式 `YYYY-MM-DDTHH:mm:ss` |
|
||||
| `arriveTime` | String(LocalDateTime) | 到达时间,格式 `YYYY-MM-DDTHH:mm:ss` |
|
||||
| `selfDrivePeriod` | String 或 `null` | 自驾时段,仅 `SELF_DRIVE` 时有值:`MORNING` / `AFTERNOON` / `EVENING` |
|
||||
| `selfDriveEta` | String(LocalDateTime)或 `null` | 自驾预计到达时间,仅 `SELF_DRIVE` 时有值 |
|
||||
| `pickupRequired` | Boolean | 是否需要平台接送 |
|
||||
| `pickupRemark` | String 或 `null` | 接送备注 |
|
||||
| `remark` | String 或 `null` | 本段交通备注 |
|
||||
|
||||
---
|
||||
|
||||
### 5.3 CustomerOverviewVO(02 客人信息速览)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `customerName` | String | 主联系人姓名 |
|
||||
| `customerPhone` | String | 主联系人手机(已脱敏,格式 `138****5678`) |
|
||||
| `adultCount` | Integer | 成人数 |
|
||||
| `childCount` | Integer | 儿童数(6–12 岁) |
|
||||
| `youngChildCount` | Integer | 幼童数(2–6 岁) |
|
||||
| `babyCount` | Integer | 婴儿数(0–2 岁) |
|
||||
| `tripDays` | Integer | 行程天数 |
|
||||
| `customerType` | String 或 `null` | 客人类型(运营字段,当前后端恒为 `null`,前端按空值处理) |
|
||||
| `createSource` | String | 客源枚举值(见 §6 枚举表) |
|
||||
| `customerRemark` | String 或 `null` | 客户备注(特殊需求 / 忌口 / 纪念日等) |
|
||||
|
||||
---
|
||||
|
||||
### 5.4 FeeDetailVO(03 费用明细)
|
||||
|
||||
**整体可为 `null`**(product-v2 Feign 失败时降级),前端应展示「暂无」而不报错。
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `items` | `FeeLineItemVO[]` | 费用行项列表(仅含数量 > 0 的人群类型) |
|
||||
| `singleRoomSurcharge` | String(BigDecimal)或 `null` | 单房差加价;无单间需求时为 `null` |
|
||||
| `grandTotal` | String(BigDecimal) | 合计金额 |
|
||||
| `onsiteBalance` | String(BigDecimal)或 `null` | 代收款(现场实收尾款);未录入时为 `null` |
|
||||
|
||||
**FeeLineItemVO(费用行项)**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `name` | String | 行项名称,如 `成人包价` / `儿童包价` / `幼童包价` / `婴儿包价` |
|
||||
| `unitPrice` | String(BigDecimal) | 单价 |
|
||||
| `qty` | Integer | 数量 |
|
||||
| `subtotal` | String(BigDecimal) | 小计(= 单价 × 数量) |
|
||||
|
||||
> 注意:BigDecimal 字段均序列化为 **JSON String**(防 JS 精度丢失),前端渲染时直接使用,**不需要** `parseFloat()` 转换。
|
||||
|
||||
---
|
||||
|
||||
### 5.5 HotelVO(04 全程住宿,逐晚)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `dayNumber` | Integer | 天序号(1-based,第一晚 = 1) |
|
||||
| `stayDate` | String(LocalDate) | 入住日期,格式 `YYYY-MM-DD` |
|
||||
| `hotelName` | String | 酒店名称 |
|
||||
| `city` | String | 所在城市(通过 resource-service Feign 取;Feign 失败时为空串) |
|
||||
| `roomCategory` | String | 房型(中文,查字典 `room_category`;未命中则回退 `其他房型`) |
|
||||
| `roomCount` | Integer | 房间数 |
|
||||
| `contactPerson` | String | 酒店联系人(Feign 取;失败时为空串) |
|
||||
| `contactPhone` | String | 联系电话(Feign 取;失败时为空串) |
|
||||
| `settleType` | String | 结算方式(中文,查字典 `settle_type`;失败时为空串) |
|
||||
| `paymentMode` | String | 付款方式(中文,查字典 `payment_mode`;失败时为空串) |
|
||||
| `checkInTime` | String 或 `null` | 入住时间,格式 `HH:mm` |
|
||||
| `checkOutTime` | String 或 `null` | 退房时间,格式 `HH:mm` |
|
||||
|
||||
---
|
||||
|
||||
### 5.6 EmergencyContactVO(05 应急兜底联系人)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `contactName` | String | 姓名(来自配房 claimer_name 快照) |
|
||||
| `contactPhone` | String | 电话(通过 user-service Feign 取 admin_user.mobile;失败或未维护时为空串) |
|
||||
| `role` | String | 角色说明,固定为 `房务` |
|
||||
|
||||
---
|
||||
|
||||
### 5.7 DayVO(06 每日行程)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `dayNumber` | Integer | 天序号(1-based) |
|
||||
| `dayDate` | String(LocalDate) | 实际日期,格式 `YYYY-MM-DD` |
|
||||
| `dayTitle` | String | 当日标题 |
|
||||
| `description` | String 或 `null` | 当日描述 |
|
||||
| `breakfast` | String 或 `null` | 早餐安排枚举值(见 §6) |
|
||||
| `lunch` | String 或 `null` | 午餐安排枚举值 |
|
||||
| `dinner` | String 或 `null` | 晚餐安排枚举值 |
|
||||
| `diningRemark` | String 或 `null` | 餐饮备注 |
|
||||
| `gatherPlace` | String 或 `null` | 集合地点(POI JSON 字符串,前端按需解析展示名称) |
|
||||
| `dismissalPlace` | String 或 `null` | 解散地点(POI JSON 字符串) |
|
||||
| `dailyMileage` | String(BigDecimal)或 `null` | 当日里程(km) |
|
||||
| `nodes` | `NodeVO[]` | 当日点位列表,按 sort_order 升序 |
|
||||
|
||||
**NodeVO(06 子项:行程点位)**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `nodeName` | String | 点位名称(优先取 node 自定义名,回退资源名) |
|
||||
| `nodeType` | String | 节点类型枚举值(见 §6) |
|
||||
| `startTime` | String 或 `null` | 开始时间,格式 `HH:mm` |
|
||||
| `timePeriod` | String 或 `null` | 时段(`上午` / `下午` / `全天`) |
|
||||
| `supplierPhone` | String 或 `null` | 供应商电话;无则空串 |
|
||||
| `remark` | String 或 `null` | 操作备注(作司机 / 导游话术) |
|
||||
|
||||
---
|
||||
|
||||
### 5.8 NoticesVO(07 注意事项)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `forbidden` | `String[]` | 严禁项目(固定文案,见 §8 典型示例) |
|
||||
| `warning` | `String[]` | 警示项目(固定文案) |
|
||||
| `standard` | `String[]` | 流程标准项目(固定文案) |
|
||||
|
||||
> 三组均为后端硬编码固定文案,每次响应内容相同,前端直接渲染即可。
|
||||
|
||||
---
|
||||
|
||||
### 5.9 RefundNoteGroup(08 退费规则)
|
||||
|
||||
`refundNotes` 为 `RefundNoteGroup[]`,每个元素对应一个行程节点的退费说明。
|
||||
|
||||
**RefundNoteGroup**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `sourceName` | String | 来源点位名称,如 `呼伦湖景区` |
|
||||
| `intro` | String 或 `null` | 退费说明总体备注 |
|
||||
| `items` | `RefundItem[]` | 退费明细列表 |
|
||||
|
||||
**RefundItem**:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `title` | String | 展示标题,如 `成人未参加` |
|
||||
| `amount` | String(BigDecimal) | 退费金额(赠送项目为 `0`) |
|
||||
| `unitLabel` | String | 展示单位文案,如 `/人` / `/团` / `/辆` |
|
||||
| `settleScope` | String | 结算粒度枚举值(见 §6) |
|
||||
| `settleScopeLabel` | String | 结算粒度中文,如 `按人` |
|
||||
| `remark` | String 或 `null` | 备注 |
|
||||
| `effectiveFrom` | String(LocalDate)或 `null` | 规则生效起日;`null` 表示无限制 |
|
||||
| `effectiveTo` | String(LocalDate)或 `null` | 规则生效止日;`null` 表示无限制 |
|
||||
|
||||
---
|
||||
|
||||
### 5.10 handoverChecklist(09 师傅领队交接确认)
|
||||
|
||||
`String[]`,后端返回以下 5 条固定条目,前端在每项右侧留签字栏:
|
||||
|
||||
1. `出行群已建立并拉入全部出行人`
|
||||
2. `签单核对完毕(日期/人数/酒店/景点无误)`
|
||||
3. `车辆及座位安排已确认`
|
||||
4. `特殊需求已告知(忌口/纪念日/老幼残)`
|
||||
5. `代收款金额已当面清点确认`
|
||||
|
||||
---
|
||||
|
||||
### 5.11 CollectReceiptVO(10 代收款签收回执)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `collectAmount` | String(BigDecimal)或 `null` | 代收金额(来自 `order_main.onsiteBalance`);未录入时为 `null` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 direction(大交通方向)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `ARRIVAL` | 到达(进藏 / 进目的地) |
|
||||
| `DEPARTURE` | 出发(离开目的地返程) |
|
||||
|
||||
### 6.2 transportType(交通方式)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `FLIGHT` | 航班 |
|
||||
| `TRAIN` | 火车 |
|
||||
| `SELF_DRIVE` | 自驾 |
|
||||
|
||||
### 6.3 selfDrivePeriod(自驾时段,仅 SELF_DRIVE)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `MORNING` | 上午 |
|
||||
| `AFTERNOON` | 下午 |
|
||||
| `EVENING` | 晚上 |
|
||||
|
||||
### 6.4 createSource(客源渠道)
|
||||
|
||||
| 枚举值 | 常见含义(展示名由字典 order_create_source 翻译) |
|
||||
|--------|------------------------------------------------|
|
||||
| `OTA` | 马蜂窝 / 携程等平台 |
|
||||
| `CUSTOMER` | 老客户 / 回头客 |
|
||||
| `B2B` | 同业 |
|
||||
| `REFERRAL` | 转介绍 |
|
||||
| `MINI_PROGRAM` | 小程序直询 |
|
||||
|
||||
> 字典值以实际 order_create_source 字典表为准,可能随运营配置增减。
|
||||
|
||||
### 6.5 nodeType(行程点位类型)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `SCENIC` | 景区 |
|
||||
| `RESTAURANT` | 餐厅 |
|
||||
| `ACTIVITY` | 活动 |
|
||||
| `SERVICE` | 服务项 |
|
||||
| `CUSTOM` | 自定义 |
|
||||
|
||||
### 6.6 餐食安排(breakfast / lunch / dinner)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `HOTEL` | 酒店早餐 / 含在住宿中 |
|
||||
| `CAMP` | 营地餐 |
|
||||
| `SPECIAL` | 特色餐(安排饭店) |
|
||||
| `SELF` | 自理 |
|
||||
|
||||
### 6.7 settleScope(退费结算粒度)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `PER_PERSON` | 按人结算(`/人`) |
|
||||
| `PER_TEAM` | 按团结算(`/团`) |
|
||||
| `PER_VEHICLE` | 按车结算(`/辆`) |
|
||||
|
||||
### 6.8 字典翻译字段
|
||||
|
||||
以下字段值来自数据字典,前端**不需要**自己翻译枚举,后端已翻译为中文:
|
||||
|
||||
| 字段 | 字典表 key | 说明 |
|
||||
|------|-----------|------|
|
||||
| `hotels[].roomCategory` | `room_category` | 房型中文名;未命中回退「其他房型」 |
|
||||
| `hotels[].settleType` | `settle_type` | 结算方式中文 |
|
||||
| `hotels[].paymentMode` | `payment_mode` | 付款方式中文 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | message | 触发场景 |
|
||||
|--------|-----------|---------|----------|
|
||||
| `581007` | 200(Result 业务码) | 订单不存在 | 路径参数 `id` 对应订单不存在或已软删除 |
|
||||
| `401` | 401 | Unauthorized | JWT 未携带 / 已过期 |
|
||||
| `403` | 403 | Forbidden | 无访问权限 |
|
||||
|
||||
> 其余 Feign 软依赖(product-v2 费用 / resource 城市 / user 应急电话)失败时**不报错**,对应字段降级为 `null` / 空串,不影响接口正常返回。
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功(完整 2 大 1 小 7 日游订单)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/1914050000000001/print-itinerary
|
||||
Authorization: Bearer eyJhbGci...
|
||||
```
|
||||
|
||||
**响应**(节选关键区块,实际字段更多):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"agencyName": "呼籁旅行有限公司",
|
||||
"printTime": "2026-06-30T09:15:00",
|
||||
"teamNo": "HLA2026070001",
|
||||
"productName": "呼伦贝尔7日私家定制游",
|
||||
"dateRange": "2026-07-01→2026-07-07",
|
||||
"paxSummary": "2大1小",
|
||||
"driverName": "全广存",
|
||||
"driverPhone": "18614805444",
|
||||
"guideName": "",
|
||||
"guidePhone": "",
|
||||
"leaderName": "扎西",
|
||||
"leaderPhone": "13812345678",
|
||||
"consultantName": "张定制",
|
||||
"transports": [
|
||||
{
|
||||
"direction": "ARRIVAL",
|
||||
"transportType": "FLIGHT",
|
||||
"transportNo": "CA4167",
|
||||
"carrier": "中国国航",
|
||||
"departStation": "北京首都国际机场T3",
|
||||
"arriveStation": "海拉尔东山机场",
|
||||
"departTime": "2026-07-01T08:00:00",
|
||||
"arriveTime": "2026-07-01T11:30:00",
|
||||
"selfDrivePeriod": null,
|
||||
"selfDriveEta": null,
|
||||
"pickupRequired": true,
|
||||
"pickupRemark": "到达T2出口等候",
|
||||
"remark": null
|
||||
}
|
||||
],
|
||||
"customerOverview": {
|
||||
"customerName": "张三",
|
||||
"customerPhone": "138****5678",
|
||||
"adultCount": 2,
|
||||
"childCount": 1,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 0,
|
||||
"tripDays": 7,
|
||||
"customerType": null,
|
||||
"createSource": "CUSTOMER",
|
||||
"customerRemark": "小孩对海鲜过敏,全程忌口"
|
||||
},
|
||||
"feeDetail": {
|
||||
"items": [
|
||||
{"name": "成人包价", "unitPrice": "4500.00", "qty": 2, "subtotal": "9000.00"},
|
||||
{"name": "儿童包价", "unitPrice": "3000.00", "qty": 1, "subtotal": "3000.00"}
|
||||
],
|
||||
"singleRoomSurcharge": null,
|
||||
"grandTotal": "12000.00",
|
||||
"onsiteBalance": "5000.00"
|
||||
},
|
||||
"hotels": [{
|
||||
"dayNumber": 1,
|
||||
"stayDate": "2026-07-01",
|
||||
"hotelName": "伯爵大酒店",
|
||||
"city": "海拉尔",
|
||||
"roomCategory": "标准双床间",
|
||||
"roomCount": 2,
|
||||
"contactPerson": "张经理",
|
||||
"contactPhone": "0470-12345678",
|
||||
"settleType": "签单",
|
||||
"paymentMode": "月结",
|
||||
"checkInTime": "14:00",
|
||||
"checkOutTime": "12:00"
|
||||
}],
|
||||
"emergencyContacts": [{"contactName": "李房务", "contactPhone": "13900000001", "role": "房务"}],
|
||||
"days": [{
|
||||
"dayNumber": 1,
|
||||
"dayDate": "2026-07-01",
|
||||
"dayTitle": "抵达海拉尔·入住休整",
|
||||
"description": "抵达海拉尔后接机,送至酒店休整",
|
||||
"breakfast": null,
|
||||
"lunch": "SELF",
|
||||
"dinner": "SPECIAL",
|
||||
"diningRemark": "晚餐安排烤全羊",
|
||||
"gatherPlace": null,
|
||||
"dismissalPlace": null,
|
||||
"dailyMileage": "45.00",
|
||||
"nodes": [{
|
||||
"nodeName": "海拉尔东山机场",
|
||||
"nodeType": "SERVICE",
|
||||
"startTime": "11:30",
|
||||
"timePeriod": "上午",
|
||||
"supplierPhone": "0470-88880001",
|
||||
"remark": "接机后直接送酒店,行李搬运到房间"
|
||||
}]
|
||||
}],
|
||||
"notices": {
|
||||
"forbidden": ["严禁向客人推荐自费项目、带客进购物店;违者扣除本行程全部车费,且永不录用"],
|
||||
"warning": ["送站时间不得早于出发时间前 2 小时", "景区半价票差价须由领队自行承担,不得转嫁客人", "行程如有任何变更须经书面确认后方可执行"],
|
||||
"standard": ["出发前须核实全程住宿安排(联系酒店确认房态)", "在出行群内发出行提示及完整大交通信息", "妥善保管签单,行程结束后当日归还公司", "每次入住后须检查房间设施并拍照留存", "司机出发前主动向客人自我介绍并说明全程安排"]
|
||||
},
|
||||
"refundNotes": [{
|
||||
"sourceName": "呼伦湖景区",
|
||||
"intro": "苔藓步道为赠送项目,不退费",
|
||||
"items": [{"title": "成人未参加", "amount": "44.00", "unitLabel": "/人", "settleScope": "PER_PERSON", "settleScopeLabel": "按人", "remark": null, "effectiveFrom": null, "effectiveTo": null}]
|
||||
}],
|
||||
"handoverChecklist": ["出行群已建立并拉入全部出行人", "签单核对完毕(日期/人数/酒店/景点无误)", "车辆及座位安排已确认", "特殊需求已告知(忌口/纪念日/老幼残)", "代收款金额已当面清点确认"],
|
||||
"collectReceipt": {"collectAmount": "5000.00"},
|
||||
"remark": "客人全程需要轮椅通道,联系景区提前安排"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 边界情况(人员未配置 + feeDetail 降级 + 无退费规则)
|
||||
|
||||
模拟 product-v2 Feign 失败时 `feeDetail` 返回 `null`,司机/导游/领队均未配置,`refundNotes` 为空数组:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"agencyName": "呼籁旅行有限公司",
|
||||
"printTime": "2026-06-30T09:16:00",
|
||||
"teamNo": "HLA2026070002",
|
||||
"productName": "张家界3日游",
|
||||
"dateRange": "2026-07-10→2026-07-12",
|
||||
"paxSummary": "1大",
|
||||
"driverName": "",
|
||||
"driverPhone": "",
|
||||
"guideName": "",
|
||||
"guidePhone": "",
|
||||
"leaderName": "",
|
||||
"leaderPhone": "",
|
||||
"consultantName": "李运营",
|
||||
"transports": [],
|
||||
"customerOverview": {
|
||||
"customerName": "王五",
|
||||
"customerPhone": "139****0000",
|
||||
"adultCount": 1,
|
||||
"childCount": 0,
|
||||
"youngChildCount": 0,
|
||||
"babyCount": 0,
|
||||
"tripDays": 3,
|
||||
"customerType": null,
|
||||
"createSource": "OTA",
|
||||
"customerRemark": null
|
||||
},
|
||||
"feeDetail": null,
|
||||
"hotels": [],
|
||||
"emergencyContacts": [],
|
||||
"days": [],
|
||||
"notices": {
|
||||
"forbidden": ["严禁向客人推荐自费项目、带客进购物店;违者扣除本行程全部车费,且永不录用"],
|
||||
"warning": ["送站时间不得早于出发时间前 2 小时", "景区半价票差价须由领队自行承担,不得转嫁客人", "行程如有任何变更须经书面确认后方可执行"],
|
||||
"standard": ["出发前须核实全程住宿安排(联系酒店确认房态)", "在出行群内发出行提示及完整大交通信息", "妥善保管签单,行程结束后当日归还公司", "每次入住后须检查房间设施并拍照留存", "司机出发前主动向客人自我介绍并说明全程安排"]
|
||||
},
|
||||
"refundNotes": [],
|
||||
"handoverChecklist": ["出行群已建立并拉入全部出行人", "签单核对完毕(日期/人数/酒店/景点无误)", "车辆及座位安排已确认", "特殊需求已告知(忌口/纪念日/老幼残)", "代收款金额已当面清点确认"],
|
||||
"collectReceipt": {"collectAmount": null},
|
||||
"remark": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **前端处理建议**:
|
||||
> - `feeDetail === null` 时,整个费用区块显示「暂无(系统获取失败)」
|
||||
> - 人员字段(`driverName` 等)为空串时,对应展示行可隐藏或显示「-」
|
||||
> - `collectReceipt.collectAmount === null` 时,代收款回执区块显示「代收款金额待确认」
|
||||
|
||||
---
|
||||
|
||||
### 8.3 业务失败(订单不存在)
|
||||
|
||||
**请求**:
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/9999999999999/print-itinerary
|
||||
Authorization: Bearer eyJhbGci...
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581007,
|
||||
"message": "订单不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
### 适用场景
|
||||
|
||||
- 订单状态为任意状态均可查询(只读,不限状态)
|
||||
- 出团前 1–3 天由定制师打印交出行人员
|
||||
- 支持 A4 纵向排版,前端按 A4 规格渲染后提供打印 / PDF 导出
|
||||
|
||||
### 不适用场景
|
||||
|
||||
- 本接口**不替代**签单 PDF(`GET /v3/admin/order/{id}/sign-voucher`)
|
||||
- 本接口**不是**小程序端行程查看接口,只给管理后台打印用
|
||||
|
||||
### 特殊边界
|
||||
|
||||
| 情况 | 行为 |
|
||||
|------|------|
|
||||
| 订单行程未固化(`days` 为空) | 正常返回,`days` 为空数组 |
|
||||
| product-v2 Feign 超时 / 熔断 | `feeDetail` 降级为 `null`,接口正常 200 返回 |
|
||||
| resource-service Feign 失败 | `hotels[].city` / `contactPerson` / `contactPhone` 降级为空串 |
|
||||
| user-service Feign 失败 | `emergencyContacts[].contactPhone` 降级为空串 |
|
||||
| 费用快照与订单当前金额不一致 | 以产品快照(`feeDetail`)为准,非实时联动;若前端需要实时金额,读订单金额字段 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
本次为**新增接口**,无历史版本,跳过此节。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
本次为**新增接口**(纯加量),不影响任何已有接口。
|
||||
|
||||
- **前端是否需要同步上线**:是(仅新增打印入口,不强制)
|
||||
- **回滚方案**:后端可随时下架接口,前端调用报 404 时隐藏打印按钮即可
|
||||
- **破坏兼容性**:无
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **BigDecimal 均为 String**:`grandTotal` / `unitPrice` / `subtotal` / `singleRoomSurcharge` / `onsiteBalance` / `dailyMileage` / `collectAmount` / `amount` 均序列化为 JSON String,前端**直接展示**即可,不需要 `parseFloat` 转换。
|
||||
|
||||
2. **feeDetail 可为 null**:费用区块来自 product-v2 Feign,不保证一定有值,前端**必须**做 null 判断,展示兜底文案「暂无」。
|
||||
|
||||
3. **电话号码三级降级**:
|
||||
- 司机/导游/领队电话:来自订单人员分配,未分配时为空串(不为 null)
|
||||
- 酒店联系人/电话:来自 resource-service Feign,失败时为空串
|
||||
- 应急联系人电话:来自 user-service Feign 取 admin_user.mobile,失败时为空串
|
||||
- 三种情况均不报错,前端按空串处理即可(显示「-」或隐藏)
|
||||
|
||||
4. **主联系人手机已脱敏**:`customerOverview.customerPhone` 格式为 `138****5678`,用于打印展示,安全无需二次处理。
|
||||
|
||||
5. **注意事项固定文案**:`notices` 三组内容为后端常量(`PrintItineraryConstants`),不走数据库,修改文案需后端发版。
|
||||
|
||||
6. **费用与实际金额可能不同步**:`feeDetail` 来自产品快照,当产品价格调整但订单快照未更新时,打印金额可能与最新报价不一致,属预期行为(以签单时快照为准)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
| 项 | 链接 / 信息 |
|
||||
|----|------------|
|
||||
| **Issue** | [#4644 打印行程单接口(出团行程单·Driver Copy)](https://git.1814.love:8443/wx/HL/issues/4644) |
|
||||
| **PR** | [#4650 feat(order-v3): 打印行程单 11 区块聚合接口](https://git.1814.love:8443/wx/HL/pulls/4650) |
|
||||
| **Merge Commit** | [8e2b02f56](https://git.1814.love:8443/wx/HL/commit/8e2b02f56) |
|
||||
| **Feature Commit** | [187169daf](https://git.1814.love:8443/wx/HL/commit/187169daf) |
|
||||
| **后端负责人** | yaosutu |
|
||||
| **影响服务** | hl-order-service-v3(端口 8086) |
|
||||
@@ -0,0 +1,54 @@
|
||||
# 房务配房并发/事务加固——新增 3 个拒绝码(808119 取消/异常态、808113 需求作废态、808123 候选已更新)
|
||||
|
||||
> 变更类型:🔒 并发/事务加固(房务管家·配房写口)+ 新增错误码
|
||||
> 端类型:管理后台(房务管家)
|
||||
> 日期:2026-06-27 | 工单:#4506 | PR:#4507(批1) + #4514(批2) + #4523(韧性审计补) | 服务:hl-order-service-v3(双实例已部署测试服 + happy path 回归 + 新码端到端实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
外部审计核实出房务配房链路一批并发/事务一致性真问题(库存泄漏、状态陈旧覆盖、回池困死、孤儿配房行等),已分两批修复并部署验证。**绝大多数是后端内部硬化(库存扣减补偿、house_status CAS、统一写锁、事务内重检),前端无契约变更**;仅以下两点前端需知悉。
|
||||
|
||||
---
|
||||
|
||||
## 一、新增 3 个拒绝错误码(前端按已有错误码机制展示 msg 即可)
|
||||
|
||||
### 808119 `ASSIGNMENT_NOT_WRITABLE_IN_EXCEPTION`——订单取消/异常处置态不可配房
|
||||
- **触发**:对一个 **house_status=EXCEPTION**(订单已取消、或在“异常”桶处置中)的需求调用「提交配房」`POST .../assignments` 或「单日确认」`POST .../assignments/days/{day}/confirm`。
|
||||
- **返回**:`code 808119`,msg「订单已取消或异常处置中, 该需求不可新增或确认配房, 请在异常桶处理」。
|
||||
- **前端**:异常桶里的需求,应隐藏/禁用「提交配房」「单日确认」入口,仅保留删配房(释放)、释放回池、转单、标记完成等异常处置操作;若误触发,按 msg 提示。
|
||||
|
||||
### 808113 `REQUIREMENT_INACTIVE_FOR_ASSIGN`——需求已作废(定制师重调过)不可配房
|
||||
- **触发**:定制师改期/改行程天数会生成新版本需求并把**旧版本需求作废**(is_active=0)。若前端仍持旧需求 id 调用「提交配房」`POST .../assignments`,后端在落库前重检发现已作废 → 拒绝。
|
||||
- **返回**:`code 808113`,msg「需求已作废, 不可提交配房, 请由定制师重新调整需求」。
|
||||
- **前端**:收到 808113 说明手里的需求 id 已过期,应刷新订单/房务详情拿最新版本需求再操作(不要在旧需求上重试)。
|
||||
|
||||
### 808123 `KEEP_NOT_IN_DAY_CANDIDATE`——单日确认保留的配房行不属于该天候选(配房已更新)
|
||||
- **触发**:「单日确认」`POST .../assignments/days/{day}/confirm` 传的 `keepAssignmentIds` 中有 id **不属于该天「询房中」候选集**。最常见原因:配房被重新提交后该天候选行的 `assignmentId` 已轮换,前端仍用了缓存的旧 id;或误传了别天的 id。
|
||||
- **返回**:`code 808123`,msg「保留的配房行不属于该天询房中候选(可能配房已更新), 请刷新后重试」。
|
||||
- **前端**:收到 808123 应**刷新订单/房务详情**拿该天最新的 `inquiringAssignments[].assignmentId` 再确认,不要用缓存的旧 id。`keepAssignmentIds` 应始终取自当前详情返回的候选 id;省略/空表示「保留该天全部候选」(行为不变)。
|
||||
- **重要**:此前后端对全不命中的 keepAssignmentIds 会「静默把该天全部候选软删且零确认」(业务中断);现改为 fail-fast 拒绝、**不删任何候选**,前端刷新重试即可恢复。
|
||||
|
||||
> 另:转单后用过期持有人身份写配房会返既有 `808110`(非本人抢单),语义不变。
|
||||
|
||||
---
|
||||
|
||||
## 二、行为提示:confirmDay 锁粒度收紧(一般无感)
|
||||
|
||||
「单日确认」`POST .../assignments/days/{day}/confirm` 的并发锁由 **per-(需求,天)** 收紧为 **per-需求**:同一需求的多天确认操作改为串行排队(此前可并行)。
|
||||
|
||||
- 房务对一个订单本就逐天顺序操作,**正常无感**。
|
||||
- 仅当前端对**同一需求的多个天**几乎同时并发触发确认时,会短暂排队(30s 锁超时内依次完成),不会失败。
|
||||
- 提交配房/释放/转单/单日确认现统一互斥(同一需求同一时刻只允许一个写操作),杜绝了并发写导致的状态错乱。
|
||||
|
||||
---
|
||||
|
||||
## 三、纯后端内部硬化(前端无需改动,仅列出供知悉)
|
||||
|
||||
- 配房单日确认扣减:候选行扣减即绑定审计 log(消除并发删除时库存不释放的泄漏窗口);多候选部分扣减失败时自动反向释放已扣;resource 记账失败自动补偿避免库存白扣 + 幂等键可重试。
|
||||
- house_status 写改 CAS(防订单取消等并发把已迁移态硬覆盖回正常态,避免已取消需求在房务视图“复活”)。
|
||||
|
||||
---
|
||||
|
||||
> 后端已完成并测试服实测通过(部署 + 双实例 health + 配房主线/释放回池 happy path 回归 + 808119/808113 端到端实测 + DB 核对)。前端仅需对 808119/808113 两个新拒绝码做对应提示与刷新。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 房需求提交+回显统一收口进「订单调整」snapshot/submit(弃用旧 PUT /hotel-requirement)
|
||||
|
||||
> 变更类型:🔁 接口迁移 + 出参新增字段 + 入参字段清理 + 旧端点 Deprecated
|
||||
> 端类型:管理后台(定制师·房需求)
|
||||
> 日期:2026-06-27 | 工单:#4515 | PR:#4520 | 服务:hl-order-service-v3(已部署测试服 + 双实例 health UP + API round-trip 实测通过)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
房需求(逐晚 `days[].segments[].candidates[]` 段×候选结构)的**提交**与**回显**统一收口到「订单调整」的两个接口,前端不再使用独立的旧 PUT 端点:
|
||||
|
||||
- 提交:`POST /v3/admin/order/{id}/adjustment/submit`(`updates.hotelRequirement`)
|
||||
- 回显:`GET /v3/admin/order/{id}/adjustment/snapshot?scope=HOTEL_REQ`(`hotelRequirement.days`)
|
||||
|
||||
---
|
||||
|
||||
## 一、回显(GET snapshot):`hotelRequirement` 新增 `days` 嵌套结构
|
||||
|
||||
`GET /v3/admin/order/{id}/adjustment/snapshot?scope=HOTEL_REQ` 出参 `data.hotelRequirement` **新增 `days[].segments[].candidates[]`**,是当前 active 房需求的**无损回显**,结构与提交入参完全一致,可直接回填编辑表单、改完原样重交:
|
||||
|
||||
```
|
||||
hotelRequirement: {
|
||||
id, version, status, remark, specialTags, // specialTags 现已回填(此前恒 null)
|
||||
claimerId, claimerName, claimedAt,
|
||||
days: [ // ✅ 新增
|
||||
{ dayNumber, segments: [
|
||||
{ roomCount, roomCategory, budget, remark,
|
||||
candidates: [ { hotelId, hotelName, roomTypeId, roomTypeName, protocolPrice, remark } ] }
|
||||
] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **前端收益**:有 active 房需求时直接读 `hotelRequirement.days` 做回显,**不必再为房需求回显单独拉「行程安排 Tab」`GET /v3/admin/order/{id}/itinerary`**。
|
||||
- **删字段**:`hotelRequirement.totalRoomCount` / `roomTypeSummary` 已移除(此前恒为 null,无值)。
|
||||
- `hotelDayDefaults` 仍保留,定位调整为「仅某晚**无** active 房需求时的三级兜底预填建议(行程酒店节点/城市占位)」;有 active 需求时以 `hotelRequirement.days` 为准回显。
|
||||
|
||||
## 二、提交(POST submit):`updates.hotelRequirement` 入参清理 + 校验收紧
|
||||
|
||||
`POST /v3/admin/order/{id}/adjustment/submit` 的 `updates.hotelRequirement` 提交体:
|
||||
|
||||
- **结构不变**:仍是 `{ specialTags?, remark?, days[].segments[].candidates[] }`(与旧 PUT body 同源)。
|
||||
- **删字段**:不要再传 `totalRoomCount` / `roomTypeSummary`(后端从不读取,已从入参移除)。
|
||||
- **校验收紧(恢复与旧 PUT 等价的级联校验)**:`days` 必传且非空;逐项 `dayNumber` 必填、`segments` 非空、`roomCount` 必填、`candidates` 非空。违反返回 `code 400`(实测:空 days / `dayNumber=null` / 空 candidates 均被 400 拒)。此前经调整接口提交这些非法值会被静默落库,现已堵住。
|
||||
|
||||
## 三、旧端点 `PUT /v3/admin/order/{id}/hotel-requirement` 标记 Deprecated
|
||||
|
||||
- 该端点**仍可用**(保留兼容期),但已标记 `@Deprecated`,请前端迁移到 `POST .../adjustment/submit` 的 `updates.hotelRequirement`。迁移完成后端将下线该端点(届时另行通知)。
|
||||
- 同 Controller 的 dispatch / reject / assign / room / urgent 等端点**不受影响**。
|
||||
|
||||
## 四、错误码说明
|
||||
|
||||
- 房需求提交的历史错误码 `582011`~`582018`(days 长度 / dayNumber / roomCount / roomCategory 等)**当前不返回**(历史死码)。实际生效的校验为:`@Valid` 字段校验 `400` + `587xxx`(终态 587002 / 已过天 587030)+ 团期阶段闸。
|
||||
- `candidates[].hotelId` 自 #4204 起为**选填**(支持「只提房数、酒店留房控统筹」的无酒店候选);前端旧约定若视为必填,以后端选填为准。
|
||||
|
||||
---
|
||||
|
||||
> 后端已部署测试服 + 双实例 health UP;API round-trip 实测:snapshot 回显 `hotelRequirement.days` 段×候选 + 候选 protocolPrice + specialTags 与 DB 逐字段一致;非法提交(空 days / dayNumber=null / 空 candidates)均 400 拒绝、无副作用。前端按上述迁移到 snapshot/submit 即可,旧 PUT 兼容期内不会立即下线。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 房务房型展示口径统一——候选房型 bedType 后端翻中文 + 配房详情补 roomTypeName
|
||||
|
||||
> 端类型:管理后台(房务管家 / 配房)
|
||||
> 变更类型:修改接口(响应值/字段,**前端需配合**,非破坏性)
|
||||
> 涉及接口:
|
||||
> - `GET /v3/admin/hotel-candidates`(配房"选择酒店"候选列表)
|
||||
> - `GET /admin/house/orders/{orderId}`(房务订单详情聚合)
|
||||
> 关联工单:#4527 #4528(PR #4530,已合并部署测试服并实测)
|
||||
|
||||
## 背景
|
||||
房务 E2E 测试发现房型展示两处口径问题,后端已修:① 候选房型 `bedType` 透传 resource 英文 code(TWIN_BED/KING_BED)未翻中文,与已翻的"大床"中英混用;② 房务配房详情卡片只有房型类目(room_category→"标准间"),缺真实房型名,与定制师侧"精品标间"不一致。
|
||||
|
||||
---
|
||||
|
||||
## 一、`GET /v3/admin/hotel-candidates` — `candidates[].roomTypes[].bedType` 改返中文
|
||||
|
||||
- 后端已统一把 `bedType` 字典 code 翻成中文 label 返回:`TWIN_BED→双床`、`KING_BED→大床`、`DOUBLE_BED→双人床`、`SINGLE_BED→单人床`、`TATAMI→榻榻米`、`KANG→火炕`。
|
||||
- **前端改动:bedType 直接展示后端返回值即可,请勿再在前端做 code→中文 的二次翻译/映射**(以后端返回为准),避免双重翻译或映射缺项导致英文残留。
|
||||
- 降级说明:字典异常或 resource 存了非标准 code(如个别房型 `bed_type=KING` 而非 `KING_BED`)时,后端降级原样返回该值,前端按原样展示即可(此类属 resource 数据问题,另行治理)。
|
||||
|
||||
## 二、`GET /admin/house/orders/{orderId}` — `currentAssignments[]` 新增 `roomTypeName`
|
||||
|
||||
- 房务订单详情每条已配房(currentAssignment)**新增字段 `roomTypeName`**(真实房型名,与定制师侧、需求口径一致,如"精品标间"/"主题标间")。
|
||||
- **前端改动:配房卡片房型优先显示 `roomTypeName`;为 null 时回退显示 `roomCategoryLabel`(房型类目)。**
|
||||
- 降级说明:resource 取数异常/缺失时 `roomTypeName` 为 null(不阻断详情),故前端必须保留回退逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 实测确认(测试服)
|
||||
- 候选接口 `bedType`:精品标间→双床、大床房→大床、蒙古包→双人床等已返中文 ✓
|
||||
- 详情 `currentAssignments[].roomTypeName`:DAY1 精品标间 / DAY2 标准间 / DAY3 主题标间,与定制师侧一致 ✓
|
||||
@@ -0,0 +1,73 @@
|
||||
# 房务日历「我的 / 全部」切换契约(前端对接)
|
||||
|
||||
> 模块:房务管家 · 日历视图(管理后台)
|
||||
> 类型:**前端对接说明(后端无改动,能力早已具备)**
|
||||
> 日期:2026-06-28
|
||||
> 背景:房务日历需要能看到「全部订单」,当前默认只看本人(我的)。后端 `GET /admin/house/calendar` **早已支持 `scope=all` 返回全部、且无任何角色门控**,前端把「全部」Tab 接到 `scope=all` 即可,无需后端改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、端点与参数(不变)
|
||||
|
||||
`GET /admin/house/calendar`
|
||||
|
||||
| 参数 | 必填 | 取值 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `month` | 是 | `YYYY-MM`(如 `2026-06`) | 月份 |
|
||||
| `scope` | 否 | `mine`(**默认**)/ `all` | **「我的」=`mine`,「全部」=`all`** |
|
||||
| `status` | 否 | `inProgress,inquiry,pending,exception` 多选逗号分隔,不填=全部 | 状态点筛选 |
|
||||
|
||||
## 二、「我的 / 全部」怎么切换(核心)
|
||||
|
||||
**同一个端点,切换 Tab 只改 `scope` 参数值、重新请求即可**,其余参数(month/status)不变:
|
||||
|
||||
- 点「我的」→ 请求 `?month=2026-06&scope=mine`
|
||||
- 点「全部」→ 请求 `?month=2026-06&scope=all`
|
||||
|
||||
两个 scope 的差异(后端口径):
|
||||
|
||||
| | `scope=mine`(我的) | `scope=all`(全部) |
|
||||
|---|---|---|
|
||||
| 数据范围 | **仅当前登录房务作为认领人(claimer)的订单** | **所有订单**,不按认领人过滤 |
|
||||
| 角色门控 | 无 | **无**(普通房务 / admin / 组长都能看全部,不需要组长权限) |
|
||||
| 待配房桶(pending,无归属房务) | **恒为空**(待配房还没认领人,不属于"我的") | 正常返回本月全部待配房 |
|
||||
| summary 各计数 / days 各天团数 | 按"我的"口径 | 按"全部"口径 |
|
||||
|
||||
> 即:要让日历"看到全部订单",前端在「全部」Tab 下传 `scope=all` 即可。**建议日历默认进入时用 `scope=all`(一眼看全团),保留「我的」Tab 让房务切回 `scope=mine` 只看自己。** 是否默认全部由前端定,后端两种都支持。
|
||||
|
||||
## 三、出参结构(随 scope 变化,结构不变)
|
||||
|
||||
```
|
||||
{
|
||||
"month": "2026-06",
|
||||
"summary": { "totalNights": 64, "inProgress": 32, "pending": 0, "inquiry": 0, "exception": 0 },
|
||||
"days": [
|
||||
{
|
||||
"date": "2026-06-28",
|
||||
"isCurrentMonth": true,
|
||||
"isToday": true,
|
||||
"tourCount": 24, // 该日团数(DISTINCT order_id)
|
||||
"statusDots": [ // 右下角圆点
|
||||
{ "status": "inProgress", "label": "进行中", "count": 14, "color": "green" },
|
||||
{ "status": "inquiry", "label": "询房中", "count": 8, "color": "orange" },
|
||||
{ "status": "exception", "label": "异常", "count": 2, "color": "red" }
|
||||
]
|
||||
}
|
||||
// ... 完整周排版 28~42 天, isCurrentMonth=false 的为补位的上/下月日期(灰显)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `summary` 与 `days` 都随 `scope` 变;`scope=mine` 且当前房务名下无认领时全为 0(`totalNights=0`、days 无点)。
|
||||
- 状态点 `status` 取值:`inProgress`(进行中) / `inquiry`(询房中) / `pending`(待配房) / `exception`(异常)。
|
||||
|
||||
## 四、测试服实测(已通过,证明能力具备)
|
||||
|
||||
同一 admin 账号、同月 `2026-06`:
|
||||
|
||||
| scope | 实测结果 | 说明 |
|
||||
|-------|----------|------|
|
||||
| `mine` | `totalNights=0`、0 团 | 该账号名下无认领订单 → 我的为空 |
|
||||
| `all` | `totalNights=64`、`inProgress=32` 团 | **全部订单正常返回**,无门控 |
|
||||
|
||||
接口 HTTP 200,结构如上。**后端无需任何改动,前端按本说明把「全部」Tab 接到 `scope=all` 即可。**
|
||||
@@ -0,0 +1,40 @@
|
||||
# 出行人补全:身份证号自动回填「性别 / 出生日期」(前端对接)
|
||||
|
||||
> 模块:订单 · 补全出行信息弹窗(管理后台)
|
||||
> 类型:**前端对接说明(纯前端逻辑,后端无改动)**
|
||||
> 日期:2026-06-28
|
||||
> 背景:「补全出行信息」弹窗里,出行人填了身份证号后,性别、出生日期目前还要手动再填一遍。这两项本就编码在身份证号里,前端可在输入证件号后**自动解析回填**,减少手填和填错。证件号本身已正常入库,后端无需改动。
|
||||
|
||||
---
|
||||
|
||||
## 一、触发条件
|
||||
|
||||
- **仅当「证件类型 = 身份证」** 时启用自动回填;其它证件类型(护照 / 港澳台通行证等)无法从证件号推导,保持手填。
|
||||
- 在「证件号」输入变化 / 失焦时,证件号长度为 **18 位**(现行)或 **15 位**(旧版)且校验通过,则解析并回填「性别」「出生日期」。
|
||||
- 回填后两个字段**仍允许手动修改**(极个别证件号与实际不符时可人工覆盖)。
|
||||
|
||||
## 二、解析规则
|
||||
|
||||
### 18 位身份证(现行标准,绝大多数)
|
||||
- **出生日期** = 第 7~14 位(`YYYYMMDD`)→ 格式化为 `YYYY-MM-DD`
|
||||
- **性别** = 第 17 位:**奇数 = 男,偶数 = 女**
|
||||
|
||||
示例:`150782199502081450`
|
||||
- 第 7~14 位 = `19950208` → 出生日期 `1995-02-08`
|
||||
- 第 17 位 = `5`(奇数)→ 性别 `男`
|
||||
|
||||
### 15 位身份证(旧版,少量历史证件)
|
||||
- **出生日期** = 第 7~12 位(`YYMMDD`),年份前补 `19` → `19YY-MM-DD`
|
||||
- **性别** = 第 15 位(末位):**奇数 = 男,偶数 = 女**
|
||||
|
||||
## 三、建议的健壮性处理(前端自行把控)
|
||||
|
||||
- 长度不足 / 非 18·15 位 / 非身份证类型:不回填,不报错(用户继续手填)。
|
||||
- 建议顺带校验 18 位的**校验码(第 18 位)**与出生日期合法性(月 1~12、日 1~31、不晚于今天);校验不过则不自动回填,可给轻提示「身份证号格式有误」。
|
||||
- 第 18 位可能是 `X/x`,按 18 位身份证规则正常处理(不影响性别取第 17 位、出生日期取第 7~14 位)。
|
||||
- 回填策略:识别到合法身份证号即覆盖回填这两项;用户手改后以用户值为准。
|
||||
|
||||
## 四、范围
|
||||
|
||||
- 涉及页面:订单详情 →「补全出行信息」弹窗的每个出行人卡片。
|
||||
- 后端:**无改动**。性别 / 出生日期 / 证件号字段及保存接口均不变,前端回填后按原样提交即可。
|
||||
@@ -0,0 +1,31 @@
|
||||
# 改行程天数(减天)级联重置酒店配房 + 还原库存(前端对接)
|
||||
|
||||
> 模块:订单调整(adjustment)/ 房务配房(管理后台)
|
||||
> 类型:后端 bug 修复(PR #4491 已合 dev-v3 + 测试服实测生效)+ **前端需配合一项**
|
||||
> 日期:2026-06-28
|
||||
|
||||
## 背景 / 修了什么
|
||||
|
||||
定制师调整订单**减少行程天数**后,原先房务侧「超出新天数范围那几晚」的已配房 + 已扣 resource 库存**完全不被清理、永久泄漏**(2 天行程却挂着 3 晚酒店配房、第 3 晚库存永久占用)。
|
||||
|
||||
现已修复(与「改出发日期」同款级联):减行程天数提交后,后端自动
|
||||
- **软删超出新范围的酒店配房行** + **还原对应 resource 库存**;
|
||||
- 把该订单的酒店需求**重版回抢单池**(回到「待配房」),房务重新认领/配房;
|
||||
- 订单 room_control 回 PENDING、生成「需求变更(REQUIREMENT_ADJUSTED)」待办。
|
||||
|
||||
测试服实测:4 天 3 晚→配 3 晚确认(扣库存)→减到 2 天 → 第 3 晚 3 行配房全软删、`stock_used` 还原、需求回「待配房」。
|
||||
|
||||
## 前端需配合(1 项)
|
||||
|
||||
**减行程天数时,请同时下发裁剪后的酒店需求 `hotelRequirement.days`(只保留新天数范围内的晚)。**
|
||||
|
||||
- 后端库存泄漏已彻底修复(超范围配房软删 + 库存还原,与前端是否下发裁剪无关)。
|
||||
- 但若前端减天数时**只改 itinerary、不同步下发裁剪后的 `hotelRequirement.days`**,则重版后的新「待配房」需求的 days 模板仍含「那一晚」的天条目(不影响库存、不影响 active 配房=0,仅房务重新配房时会多看到一晚空档)。前端同步裁剪即完全对齐。
|
||||
|
||||
另:请确认前端「改行程天数」入口对 CORE(短途/定制)产品是否开放;本修复对任何减天数路径都生效。
|
||||
|
||||
## 影响接口
|
||||
- `POST /v3/admin/order/{orderId}/adjustment/submit`(itinerary 减天数分支,行为增强,入参结构不变)
|
||||
- 房务侧详情/日历/待办随之反映(需求回待配房、库存释放、REQUIREMENT_ADJUSTED 待办)
|
||||
|
||||
后端无需前端改即不再泄漏库存;前端配合下发裁剪 days 可让重配体验完全一致。
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户