比较提交
520
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
de960761df | ||
|
|
ee1c4a3167 | ||
|
|
7c5301db9f | ||
|
|
da2707f63b | ||
|
|
4a3ec16a3d | ||
|
|
ffd0983958 | ||
|
|
f52ef24dc1 | ||
|
|
12a331d7c7 | ||
|
|
86059ce5f3 | ||
|
|
272b23b251 | ||
|
|
54ee10aa8d | ||
|
|
b931167499 | ||
|
|
52d9b49daa | ||
|
|
3ffef86227 | ||
|
|
120aee2b40 | ||
|
|
e38807ccc8 | ||
|
|
bd7f5a5e19 | ||
|
|
85caef620c | ||
|
|
23ef052327 | ||
|
|
f8a161fdf6 | ||
|
|
0e95ebd933 | ||
|
|
07990e1578 | ||
|
|
6cab15bab7 | ||
|
|
cbabad9a44 | ||
|
|
8d5c6943a5 | ||
|
|
b883ce7aa8 | ||
|
|
236cf8ff19 | ||
|
|
e1f5c2b9a2 | ||
|
|
ec0ec9ede1 | ||
|
|
a7b750cede | ||
|
|
736a17f084 | ||
|
|
cdeb340e7c | ||
|
|
86d9356d72 | ||
|
|
c7c8a7299c | ||
|
|
bae6a58183 | ||
|
|
ed0a97326f | ||
|
|
fe3354cce2 | ||
|
|
8e0c18658f | ||
|
|
e64aa1a054 | ||
|
|
e66c76f96b | ||
|
|
3b4e095f26 | ||
|
|
e8fbd19fc5 | ||
|
|
1e2f39e76f | ||
|
|
4d7d0d5e5c | ||
|
|
93d013f1d4 | ||
|
|
9891fee76e | ||
|
|
7a3c0d70b7 | ||
|
|
4877596de1 | ||
|
|
a78b546cce | ||
|
|
f4ed055581 | ||
|
|
614cad68e6 | ||
|
|
12fa3b7489 | ||
|
|
e97a559737 | ||
|
|
1b0bbad5b4 | ||
|
|
7b66c073b9 | ||
|
|
00321aecdb | ||
|
|
00f6bfd4a5 | ||
|
|
2e6884262d | ||
|
|
1208626f69 | ||
|
|
55fe54afa0 | ||
|
|
e492376528 | ||
|
|
67bfaa3993 | ||
|
|
bb53e6f542 | ||
|
|
a808064c73 | ||
|
|
5e517a4503 | ||
|
|
6cbe22f40a | ||
|
|
402e6cb45b | ||
|
|
8493c75ab4 | ||
|
|
59470f7b4c | ||
|
|
246e999244 | ||
|
|
92093b7368 | ||
|
|
ba9b41bd9e | ||
|
|
fe4bdd1e2d | ||
|
|
40e28d2d4e | ||
|
|
92b53da54d | ||
|
|
4f93f147be | ||
|
|
cfc9c7b8db | ||
|
|
45f5dc4a06 | ||
|
|
ad33e901ab | ||
|
|
c76d5f72c7 | ||
|
|
e8b8eea8f7 | ||
|
|
33a34f1e3d | ||
|
|
4f94800a05 | ||
|
|
9b32c2e2e0 | ||
|
|
8737f63a54 | ||
|
|
acb7c040bc | ||
|
|
8288bfcc7f | ||
|
|
199dad384e | ||
|
|
171be62be5 | ||
|
|
6b29df3f63 | ||
|
|
e9a625d8fb | ||
|
|
710ceba840 | ||
|
|
8f6c7678dd | ||
|
|
b279ae7eeb | ||
|
|
3740020603 | ||
|
|
1e7c98f270 | ||
|
|
2e94a6387c | ||
|
|
f7ebc9c6f9 | ||
|
|
4eb1e9db52 | ||
|
|
8d6c2f3375 | ||
|
|
e5b0d7d7e7 | ||
|
|
c8557f50b5 | ||
|
|
27de6f8837 | ||
|
|
bdb6ac7e92 | ||
|
|
5163692a3f | ||
|
|
b5b18f01d7 | ||
|
|
c352c72060 | ||
|
|
fc2ebf84ac | ||
|
|
94e3aa4525 | ||
|
|
3847d19b20 | ||
|
|
3e710dee3c | ||
|
|
bf6e7636ac | ||
|
|
e1bc127abc | ||
|
|
8f08de40b6 | ||
|
|
ab510d7e62 | ||
|
|
c3a5b2cd99 | ||
|
|
033d60adc9 | ||
|
|
00365d3d26 | ||
|
|
6a8f6d870b | ||
|
|
df88c7256f | ||
|
|
37e951eb49 | ||
|
|
14f6349b96 | ||
|
|
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 | ||
|
|
0e9a44568f | ||
|
|
2c00668800 | ||
|
|
26ff684c82 | ||
|
|
b1d70a9020 | ||
|
|
77a1c4a780 | ||
|
|
a6cc4feb93 | ||
|
|
63e3b0c964 | ||
|
|
7c93c5b89d | ||
|
|
d5a7d8ac61 | ||
|
|
41822427f7 | ||
|
|
4436a5061e | ||
|
|
9e8f405d62 | ||
|
|
9f5731aabb | ||
|
|
0182ffff67 | ||
|
|
80088ae7d1 | ||
|
|
fc1cf246c3 | ||
|
|
e747c7f334 | ||
|
|
28deca3a56 | ||
|
|
40c4c956cb | ||
|
|
f95ebe79f9 | ||
|
|
7e80f1371d | ||
|
|
a3153e9684 | ||
|
|
a2866a89cf | ||
|
|
43d69d38d1 | ||
|
|
c674675b44 | ||
|
|
1a7ce36aea | ||
|
|
d720016808 | ||
|
|
c948be3f1c | ||
|
|
bd14ac0d07 | ||
|
|
d553718b36 |
@@ -0,0 +1,46 @@
|
||||
name: changelog-filename-gate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- edited
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Keep the gate self-contained: the test environment cannot reliably clone GitHub Actions repositories.
|
||||
- name: Checkout full history
|
||||
run: |
|
||||
git init -q .
|
||||
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --no-tags --prune origin \
|
||||
"+refs/heads/*:refs/remotes/origin/*" \
|
||||
"+refs/pull/*/head:refs/remotes/pull/*/head" \
|
||||
"+refs/pull/*/merge:refs/remotes/pull/*/merge"
|
||||
git checkout --detach "$GITHUB_SHA"
|
||||
|
||||
- name: Verify Node.js runtime
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
node -e "if (Number(process.versions.node.split('.')[0]) < 20) process.exit(1)"
|
||||
|
||||
- name: Run regression tests
|
||||
run: npm test
|
||||
|
||||
- name: Validate new changelog filenames
|
||||
run: npm run check:filenames -- --event "$GITHUB_EVENT_PATH"
|
||||
|
||||
- name: Validate changelog frontmatter
|
||||
run: npm run check:frontmatter -- --event "$GITHUB_EVENT_PATH"
|
||||
@@ -0,0 +1,75 @@
|
||||
# 后端 API Changelog 推送说明
|
||||
|
||||
接口发生新增、修改或删除时,在 `hl-api-changelog` 仓库提交一份 changelog。
|
||||
|
||||
## 1. 放在哪里
|
||||
|
||||
- 管理后台:`changelogs-v2/YYYY-MM/`
|
||||
- 小程序:`changelogs-v2-mp/YYYY-MM/`
|
||||
|
||||
文件名:
|
||||
|
||||
```text
|
||||
DD_issue_业务标题-{新增接口|修改接口|删除接口}-{管理后台|小程序端}.md
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
changelogs-v2/2026-07/24_5205_车务首页汇总状态补全-修改接口-管理后台.md
|
||||
```
|
||||
|
||||
日期使用提交时的上海日期;不要把“前端待处理”“已完成”等状态写进文件名。
|
||||
|
||||
## 2. 写什么
|
||||
|
||||
可以复制仓库根目录的 `CHANGELOG_TEMPLATE.md`,至少写清:
|
||||
|
||||
- 关联的 Issue 和后端 PR;
|
||||
- 接口路径和 HTTP 方法;
|
||||
- 新增、修改或删除的请求/响应字段;
|
||||
- 字段必填性、枚举、状态、空值、金额和兼容规则;
|
||||
- 前端需要做什么;
|
||||
- 后端测试、部署和网关验证结果。
|
||||
|
||||
元数据中:
|
||||
|
||||
```yaml
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
```
|
||||
|
||||
- 需要前端修改:`frontend_status: "pending"`
|
||||
- 不需要前端修改:`frontend_status: "not_required"`
|
||||
- 后端不要代替前端填写 `implemented`、`released` 或 `verified`
|
||||
|
||||
## 3. 校验
|
||||
|
||||
在 `hl-api-changelog` 仓库执行:
|
||||
|
||||
```powershell
|
||||
npm test
|
||||
npm run check:filenames -- --base origin/main --head HEAD
|
||||
npm run check:frontmatter -- --base origin/main --head HEAD
|
||||
```
|
||||
|
||||
确保正文没有 `TODO`、`待补充` 或模板占位符。
|
||||
|
||||
## 4. 提交和推送
|
||||
|
||||
只暂存本次 changelog 文件:
|
||||
|
||||
```powershell
|
||||
git status --short
|
||||
git add -- changelogs-v2/2026-07/24_5205_车务首页汇总状态补全-修改接口-管理后台.md
|
||||
git diff --cached --check
|
||||
git commit -m "docs: hand off API contract (#5205)"
|
||||
git push -u origin <任务分支>
|
||||
```
|
||||
|
||||
然后向 `main` 创建 PR。不要提交其他任务的 changelog、`.tmp-*` 文件或任何凭据。
|
||||
@@ -1,3 +1,21 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "{issue-no}"
|
||||
title: "{一句话概括变化}"
|
||||
consumer: "{admin|mp|internal|multiple}"
|
||||
change_type: "{新增接口|修改接口|删除接口}"
|
||||
backend_status: "pending"
|
||||
gateway_status: "pending"
|
||||
frontend_status: "{pending|not_required}"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: ""
|
||||
updated_at: "YYYY-MM-DD"
|
||||
base: "{dev|dev-v3}"
|
||||
---
|
||||
|
||||
# {模块名}: {一句话概括变化}
|
||||
|
||||
> **存放目录**:
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
# Changelog 贡献规则
|
||||
|
||||
## 二期文件名
|
||||
|
||||
`/v3/admin/*` 接口写入 `changelogs-v2/`:
|
||||
|
||||
```text
|
||||
changelogs-v2/YYYY-MM/DD_issue_业务标题-{新增接口|修改接口|删除接口}-管理后台.md
|
||||
```
|
||||
|
||||
`/v3/mp/*` 接口写入 `changelogs-v2-mp/`:
|
||||
|
||||
```text
|
||||
changelogs-v2-mp/YYYY-MM/DD_issue_业务标题-{新增接口|修改接口|删除接口}-小程序端.md
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `YYYY-MM` 和 `DD` 必须是校验运行时 `Asia/Shanghai` 的真实年月日,且均须补齐两位。跨越上海零点后仍未合并的 PR,需要把新文件重命名为当天日期。
|
||||
- `issue` 必须是不带 `#` 的十进制正整数,不允许 `0`、负数或前缀符号。
|
||||
- 业务标题不能为空。
|
||||
- 变更类型只能是 `新增接口`、`修改接口` 或 `删除接口`。
|
||||
- 端类型由目录唯一决定:`changelogs-v2/` 固定为 `管理后台`,`changelogs-v2-mp/` 固定为 `小程序端`。
|
||||
- 同一改动同时影响 `/v3/admin/*` 和 `/v3/mp/*` 时,应按目录拆成两份。
|
||||
|
||||
一期 `changelogs/` 沿用现行格式,不套用上述强制模板。
|
||||
|
||||
## 校验范围
|
||||
|
||||
检测器读取 `git diff --name-status -z --find-renames` 的结果,只校验本次 diff 新出现的目标路径:
|
||||
|
||||
- `A`(新增)、`C`(复制)和 `R`(重命名)的目标路径必须通过规则。
|
||||
- `M`(修改历史文件)和 `D`(删除)豁免,不会因存量错误命名阻断。
|
||||
- 重命名到受控目录时,新目标路径必须使用校验当天的上海日期。
|
||||
|
||||
本地校验:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run check:filenames -- --base origin/main --head HEAD
|
||||
```
|
||||
|
||||
生产 CLI 故意不提供 `--date` 或日期环境变量;测试只通过导出的纯函数注入 `Date`。规则失败返回退出码 `1`,Git/事件/参数等基础设施错误返回 `2`。
|
||||
|
||||
## 前端消费状态
|
||||
|
||||
新增二期 changelog 必须使用 `hl-changelog/v2` YAML Front Matter。状态只写在元数据中,不写入文件名:
|
||||
|
||||
```yaml
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
updated_at: "2026-07-24"
|
||||
```
|
||||
|
||||
前端状态正常流转为:
|
||||
|
||||
```text
|
||||
pending → claimed → implemented → released → verified
|
||||
```
|
||||
|
||||
不需要前端修改时使用 `not_required`。字段一致性、必填证据和新增文档 frontmatter 由 `check:frontmatter` 校验。
|
||||
|
||||
完整职责和命令见:
|
||||
|
||||
- `FRONTEND_CONSUMPTION_STATUS_GUIDE.md`
|
||||
- `BACKEND_CHANGELOG_DELIVERY_GUIDE.md`
|
||||
|
||||
本地校验:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run check:filenames -- --base origin/main --head HEAD
|
||||
npm run check:frontmatter -- --base origin/main --head HEAD
|
||||
```
|
||||
|
||||
## CI 与服务端阻断边界
|
||||
|
||||
Gitea Actions 会在指向 `main` 的 PR 和 `main` 的 push 上运行回归测试与文件名检测。该 workflow 是检测器:
|
||||
|
||||
- 在 `main` 未开启分支保护和 required status 时,失败状态不能硬性阻止合并。
|
||||
- `push` 事件发生在写入之后,只能检测/报警,不能撤销直推。
|
||||
- 只有管理员另行保护 `main`、关闭直推,并在 workflow 首次成功运行后,从 Gitea 最近上报的 status context 列表中选择实际值作为 required status,才能宣称服务端 hard gate 已激活。激活记录必须保存首次运行链接和 status API/分支保护回读证据;不得预设 job id/name `validate` 就是 Gitea 实际上报的 context。
|
||||
|
||||
因此,本仓库文件交付的准确表述是:**detector 已安装;在 `main` 未保护时,服务端 hard gate 尚未激活。**
|
||||
@@ -0,0 +1,109 @@
|
||||
# API Changelog 前端消费状态协作说明
|
||||
|
||||
## 可直接转发给前端的通知
|
||||
|
||||
API changelog 从 `hl-changelog/v2` 开始记录前端消费进度。后端交接时会填写:
|
||||
|
||||
```yaml
|
||||
backend_status: "deployed"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
```
|
||||
|
||||
请前端在领取、实现、发布和页面验证时更新对应状态,并填写可追溯的前端 PR、提交或发布版本。
|
||||
|
||||
这不会把前端工作纳入后端工单验收,也不要求在 `hl-ui` 创建配合工单。它只用于区分:
|
||||
|
||||
- 后端接口是否已经部署并验证;
|
||||
- 前端是否已经领取;
|
||||
- 前端代码是否已经实现;
|
||||
- 页面是否已经发布并验证。
|
||||
|
||||
只有 `frontend_status: "verified"` 才表示用户页面形成完整闭环。
|
||||
|
||||
## 状态流转
|
||||
|
||||
```text
|
||||
pending → claimed → implemented → released → verified
|
||||
```
|
||||
|
||||
不需要前端修改时:
|
||||
|
||||
```text
|
||||
not_required
|
||||
```
|
||||
|
||||
| 状态 | 含义 | 必填证据 |
|
||||
|---|---|---|
|
||||
| `not_required` | 不需要前端修改 | 不填写前端负责人、引用和版本 |
|
||||
| `pending` | 等待前端领取 | 无 |
|
||||
| `claimed` | 前端已领取 | `frontend_owner` |
|
||||
| `implemented` | 前端代码已实现 | `frontend_owner`、`frontend_ref` |
|
||||
| `released` | 已发布 | 再填写 `target_release` |
|
||||
| `verified` | 页面已验证 | 再填写 `verified_at` |
|
||||
|
||||
跨级迁移会被自动校验拒绝。状态回退或改为/取消 `not_required` 时必须填写原因。
|
||||
|
||||
## 更新命令
|
||||
|
||||
领取:
|
||||
|
||||
```powershell
|
||||
hl changelog transition 5205 D:/path/changelog.md claimed `
|
||||
--owner frontend-team --write
|
||||
```
|
||||
|
||||
实现:
|
||||
|
||||
```powershell
|
||||
hl changelog transition 5205 D:/path/changelog.md implemented `
|
||||
--owner frontend-team `
|
||||
--frontend-ref "mmg/hl-ui@abc1234" `
|
||||
--write
|
||||
```
|
||||
|
||||
发布:
|
||||
|
||||
```powershell
|
||||
hl changelog transition 5205 D:/path/changelog.md released `
|
||||
--target-release "test-2026.07.24" `
|
||||
--write
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```powershell
|
||||
hl changelog transition 5205 D:/path/changelog.md verified `
|
||||
--verified-at "2026-07-24" `
|
||||
--write
|
||||
```
|
||||
|
||||
命令默认只预览;只有 `--write` 才修改文件。写入命令会自动获取 `changelog` 单写租约。
|
||||
|
||||
## 职责边界
|
||||
|
||||
后端负责:
|
||||
|
||||
- 完成后端测试、部署和网关验证;
|
||||
- 初始化 v2 元数据;
|
||||
- 需要前端时设置 `pending`,不需要时设置 `not_required`;
|
||||
- 不替前端填写 `implemented`、`released` 或 `verified`。
|
||||
|
||||
前端负责:
|
||||
|
||||
- 领取时填写负责人;
|
||||
- 实现后填写前端引用;
|
||||
- 发布后填写目标版本或环境;
|
||||
- 页面验证后填写验证日期。
|
||||
|
||||
QA 或产品可以协助更新 `verified_at`,但必须基于实际页面验证,不能只根据接口成功或代码已合并标记完成。
|
||||
|
||||
## 存量文档
|
||||
|
||||
- 新 changelog 全部使用 `hl-changelog/v2`。
|
||||
- `hl-changelog/v1` 继续可读和索引,不强制一次性迁移。
|
||||
- 文件名带“前端待处理”不代表真实状态;需要继续流转时补充 v2 元数据。
|
||||
- 不通过重命名表达消费状态,避免破坏文件名校验和历史链接。
|
||||
@@ -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,44 @@
|
||||
# 【修改接口·小程序端】到达计划自驾时段 selfDrivePeriodLabel 文案口径含时间范围
|
||||
|
||||
> **PR**: #4061 | **服务**: hl-order-service-v3(经 mp-service 透传)| **更新时间**: 2026-06-19
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
大交通枚举改造(#4061)统一了自驾时段 `SelfDrivePeriod` 枚举的中文 label 口径,含时间范围。到达计划(mp)出参 `ArrivalPlanRespVO.selfDrivePeriodLabel` 随之变化。前端直接显示该 label,**无需改代码**,只是显示文案更详细了。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 接口 | 方法 | 路径 | 变更 |
|
||||
|---|---|---|---|
|
||||
| 到达计划查询 | GET | /v3/internal/mp/order/arrival/{orderId} | 出参 selfDrivePeriodLabel 文案口径变化 |
|
||||
|
||||
(同枚举也用于 mp 创建/更新/批量接口的回显出参)
|
||||
|
||||
## 3. 出参变化
|
||||
|
||||
| 字段 | 类型 | 变更 | 说明 |
|
||||
|---|---|---|---|
|
||||
| selfDrivePeriodLabel | String | 文案口径调整 | 自驾时段中文,由「上午/下午/晚上」改为含时间范围 |
|
||||
|
||||
## 4. 枚举 / 数据字典
|
||||
|
||||
| selfDrivePeriod 码值 | 改前 label | 改后 label |
|
||||
|---|---|---|
|
||||
| MORNING | 上午 | **上午(06:00-12:00)** |
|
||||
| AFTERNOON | 下午 | **下午(12:00-18:00)** |
|
||||
| EVENING | 晚上 | **晚上(18:00-24:00)** |
|
||||
|
||||
## 5. 影响评估
|
||||
|
||||
- **破坏向后兼容**:否。`selfDrivePeriod` 码值不变,仅 `selfDrivePeriodLabel` 展示文案更详细。
|
||||
- **前端是否需改**:否,前端直接展示 label 即可(显示内容自动变详细)。**例外**:若前端有按 label 文案做精确匹配/判断的逻辑(不推荐),需改为按 `selfDrivePeriod` 码值判断。
|
||||
|
||||
## 6. 注意事项
|
||||
|
||||
- 自驾时段判断逻辑请用 `selfDrivePeriod` 码值(MORNING/AFTERNOON/EVENING),不要依赖 `selfDrivePeriodLabel` 文案。
|
||||
|
||||
## 7. 关联 / 联系人
|
||||
|
||||
- **Issue**: [#4058](https://git.1814.love:8443/wx/HL/issues/4058)
|
||||
- **PR**: [#4061](https://git.1814.love:8443/wx/HL/pulls/4061)
|
||||
- **后端负责人**: @yaosutu
|
||||
@@ -0,0 +1,65 @@
|
||||
# 【修改接口·小程序端】客户出行人补全:订单确认后禁止 mp 端编辑(新增门禁 + 错误码 581145)(CR)
|
||||
|
||||
> **PR**: #4154 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-21
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序客户出行人补全接口(batch-edit)此前**未在后端强制限制可编辑的订单阶段**:客户端只要订单属于本人,任意阶段都能提交编辑。本次安全/一致性收紧:**仅订单确认前(待支付 / 定制中)允许经小程序编辑出行人;订单确认后(待出行起)及已完成 / 已取消一律拒绝**,返回新错误码 `581145`。
|
||||
|
||||
收紧原因:订单确认后修改出行人 PII(姓名 / 手机 / 生日等)会误触发后端「合同作废重签 + 退保重投保」链路,造成非预期的合同与保单churn。证件号此前已在签约后冻结,本次把整个编辑动作按订单阶段收口。
|
||||
|
||||
> 本次为行为收紧(破坏性变更面向「确认后仍调用」的旧逻辑)。按产品生命周期,出行人补全本就发生在「待补全信息」窗口(定制中阶段),正常流程不受影响。
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 客户出行人补全 | POST | /v3/internal/mp/order/{id}/traveler/batch-edit | 行为收紧 | 订单非「待支付/定制中」阶段调用直接拒绝(581145);入参 / 出参结构不变 |
|
||||
|
||||
## 3. 关键说明
|
||||
|
||||
- **放行阶段(白名单)**:`order_status ∈ {PENDING_PAY 待支付, CUSTOMIZING 定制中}`。
|
||||
- **拒绝阶段**:`PENDING_DEPARTURE 待出行(确认后)/ TRAVELLING 出行中 / COMPLETED 已完成 / CANCELLED 已取消`,以及任何其他状态(白名单 fail-closed)。
|
||||
- 拒绝时**整单不写入**(事务回滚),返回 `code=581145`。
|
||||
- 入参字段、出参 VO 结构**完全不变**,仅新增前置阶段校验。
|
||||
- 本门禁**只约束小程序(mp)路径**;定制师 / admin 后台路径不受限(如需确认后变更出行人,请走定制师后台)。
|
||||
|
||||
## 4. 入参 / 出参
|
||||
|
||||
入参(请求体 travelers[] + 路径 id)与响应 VO **均不变**,详见既有文档(如 #3976 出行人补全契约)。本次仅在订单阶段不满足时提前抛 581145。
|
||||
|
||||
## 5. 错误码
|
||||
|
||||
| code | 含义 | 触发场景 | 本次 |
|
||||
|------|------|----------|------|
|
||||
| **581145** | 订单已确认,出行人信息不可再经小程序修改,如需变更请联系定制师 | 订单 status 不在 {待支付, 定制中} 白名单(即确认后 / 已完成 / 已取消)时调用 batch-edit | **新增** |
|
||||
| 581102 | 订单不存在,无法编辑出行人 | orderId 无效 | 不变 |
|
||||
| 581122 | 订单不属于当前用户 | 当前登录 userId 与订单归属不一致(横向越权拦截) | 不变 |
|
||||
|
||||
## 6. 前端处理建议
|
||||
|
||||
- 出行人编辑入口建议按订单状态控制:仅在「待支付 / 定制中(待补全信息)」展示可编辑表单;订单确认后置为只读。
|
||||
- 若仍调用 batch-edit 命中 581145,按 message 文案提示用户「订单已确认,如需修改出行人请联系定制师」,不要静默失败。
|
||||
- 正常按生命周期编辑(确认前补全)的前端流程**无需改动**。
|
||||
|
||||
## 7. 业务边界
|
||||
|
||||
- 适用:订单 `order_status = PENDING_PAY` 或 `CUSTOMIZING`,且出行人属本人订单(581122 越权拦截照旧)。
|
||||
- 不适用:订单确认后(PENDING_DEPARTURE 起)/ 已完成 / 已取消 —— 一律 581145 拒绝。
|
||||
- 证件号(idNo)在合同已签(contract_status=SIGNED)时本就冻结(581111),本次门禁覆盖范围更靠前(按订单阶段整体拦截)。
|
||||
|
||||
## 8. 影响评估 / 回滚
|
||||
|
||||
- **向后兼容**:对「确认前编辑」的正常流程兼容;对「确认后仍调用 batch-edit」的旧行为为破坏性收紧(此前会误触发重签,属应修缺陷)。
|
||||
- **小程序是否必须同步上线**:建议同步——确认后隐藏 / 禁用编辑入口 + 处理 581145 文案;不同步也不会造成数据错误(后端已兜底拒绝)。
|
||||
- **回滚**:revert PR #4154 重新部署 hl-order-service-v3,恢复不限阶段编辑的旧行为。
|
||||
|
||||
## 9. 关联 / 联系人
|
||||
|
||||
### 9.1 链接
|
||||
|
||||
- **PR**: [#4154](https://git.1814.love:8443/wx/HL/pulls/4154)(CR 发现,无关联 Issue)
|
||||
|
||||
### 9.2 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
@@ -0,0 +1,241 @@
|
||||
# 发票补齐 - 小程序端申请 + 可见性过滤 + 签名下载(小程序端)
|
||||
|
||||
> Issue: [#4174](https://git.1814.love:8443/wx/HL/issues/4174) / [#4182](https://git.1814.love:8443/wx/HL/issues/4182)
|
||||
> PR: [#4181](https://git.1814.love:8443/wx/HL/pulls/4181)(mp 端申请+可见性)/ [#4184](https://git.1814.love:8443/wx/HL/pulls/4184)(签名下载)
|
||||
> 日期: 2026-06-21
|
||||
> 服务: hl-order-service-v3(invoice 域)
|
||||
> 端类型: 小程序端
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
小程序发票模块本次补齐三项改动:
|
||||
|
||||
- **申请门槛收紧**(PR4 / Issue #4174):原 collab 域 POST /v3/internal/mp/order/{orderId}/invoice/apply 路径不变,但实现从 collab 迁入 invoice 域,门槛由宽松(可叠加多张)改为严格(一单一票 + 订单必须 COMPLETED + 金额不超订单总价)。
|
||||
- **出参可见性过滤**(PR4):GET /v3/internal/mp/order/{orderId}/invoice/list 和 GET /v3/internal/mp/order/invoice/{invoiceId}/detail 出参中,fileUrl 字段现在仅在 ISSUED / PUSHED 状态时返回;REQUESTED 状态返回 null(处理中,前端勿展示 PDF 链接)。
|
||||
- **新增签名下载端点**(#4184 / Issue #4182):GET /v3/internal/mp/order/invoice/{invoiceId}/download,返回 1 小时有效期 OSS 签名 URL,供前端调 wx.openDocument 预览或下载发票 PDF。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | POST /v3/internal/mp/order/{orderId}/invoice/apply | 修改接口(行为收紧) | 申请门槛由宽松改严格:一单一票 + 订单 COMPLETED + 金额不超总价;resp status 字段值从 APPLIED 改为 REQUESTED |
|
||||
| 2 | GET /v3/internal/mp/order/{orderId}/invoice/list | 修改接口(字段行为变化) | fileUrl 字段:REQUESTED 状态由有值改为 null;ISSUED / PUSHED 保持有值 |
|
||||
| 3 | GET /v3/internal/mp/order/invoice/{invoiceId}/detail | 修改接口(字段行为变化) | fileUrl 字段同上:REQUESTED 返 null,ISSUED / PUSHED 有值 |
|
||||
| 4 | GET /v3/internal/mp/order/invoice/{invoiceId}/download | 新增接口 | 返回发票 PDF 的 OSS 签名下载 URL(1 小时有效期),含 IDOR 鉴权 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
认证:所有接口走小程序 JWT 认证,Header 携带 Authorization: Bearer token(小程序 openId → userId)。
|
||||
幂等性:apply 非幂等(一单一票,重复提交报 581511);download 幂等(每次生成新签名 URL)。
|
||||
限流:网关全局限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 接口 | 参数名 | 类型 | 必填 | 说明 |
|
||||
|------|--------|------|------|------|
|
||||
| POST /{orderId}/apply | orderId | Long | 是 | 路径参数,订单 ID |
|
||||
| GET /{orderId}/invoice/list | orderId | Long | 是 | 路径参数,订单 ID |
|
||||
| GET /invoice/{invoiceId}/detail | invoiceId | Long | 是 | 路径参数,发票 ID |
|
||||
| GET /invoice/{invoiceId}/download | invoiceId | Long | 是 | 路径参数,发票 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
**POST /v3/internal/mp/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| amount | String | 是 | 开票金额(字符串,单位元),不超过订单总价 |
|
||||
| invoiceType | String | 是 | 发票类型,枚举见 6 节 |
|
||||
| titleType | String | 是 | 抬头类型:PERSONAL / COMPANY |
|
||||
| title | String | 是 | 发票抬头(姓名或公司名) |
|
||||
| taxNo | String | 条件必填 | 税号(titleType=COMPANY 时必填) |
|
||||
| bankName | String | 条件必填 | 开户行(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| bankAccount | String | 条件必填 | 银行账号(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registAddress | String | 条件必填 | 注册地址(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registPhone | String | 条件必填 | 注册电话(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| email | String | 条件必填 | 收票邮箱(invoiceType=ELECTRONIC 时必填) |
|
||||
| remark | String | 否 | 备注 |
|
||||
|
||||
GET 接口无请求体。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**POST /v3/internal/mp/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 新建发票 ID(Long 序列化) |
|
||||
| status | String | 固定为 REQUESTED |
|
||||
|
||||
注意:status 值此前旧实现返回 APPLIED(collab 域),新实现返回 REQUESTED(invoice 域)。请前端对齐新值。
|
||||
|
||||
**GET /v3/internal/mp/order/{orderId}/invoice/list(单条字段,变化部分)**
|
||||
|
||||
| 字段 | 原来行为 | 现在行为 |
|
||||
|------|---------|---------|
|
||||
| fileUrl | REQUESTED / ISSUED / PUSHED 均有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 |
|
||||
|
||||
其余字段不变(id / orderId / status / statusName / amount / invoiceType / title / taxNo / createTime 等)。
|
||||
|
||||
**GET /v3/internal/mp/order/invoice/{invoiceId}/detail(变化部分)**
|
||||
|
||||
| 字段 | 原来行为 | 现在行为 |
|
||||
|------|---------|---------|
|
||||
| fileUrl | 同上,恒有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 |
|
||||
|
||||
**GET /v3/internal/mp/order/invoice/{invoiceId}/download(新增接口)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| fileUrl | String | OSS 签名下载 URL(1 小时有效期,格式为 HTTPS 带签名参数的 URL) |
|
||||
| pdfName | String | PDF 文件名(可选,用于 wx.openDocument 的 name 参数) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**InvoiceStatus - 发票状态(影响 fileUrl 可见性)**
|
||||
|
||||
| 枚举值 | 中文名 | fileUrl 是否可见 |
|
||||
|--------|--------|----------------|
|
||||
| REQUESTED | 待开票 | null(处理中,前端勿展示 PDF 链接) |
|
||||
| ISSUED | 已开票 | 有值 |
|
||||
| PUSHED | 已推送 | 有值 |
|
||||
| VOIDED | 已作废 | null |
|
||||
|
||||
**InvoiceType - 发票类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
| ELECTRONIC | 电子发票 |
|
||||
|
||||
**TitleType - 抬头类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| PERSONAL | 个人 |
|
||||
| COMPANY | 公司 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581510 | INVOICE_ORDER_NOT_COMPLETED | 订单状态非 COMPLETED,不可申请开票 |
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581512 | INVOICE_AMOUNT_EXCEED | 开票金额超过订单总价 |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头 / 专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行 / 银行账号 / 注册地址 / 注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 电子发票时邮箱为必填 |
|
||||
| 581517 | INVOICE_NOT_DOWNLOADABLE | 发票尚未开具(REQUESTED / VOIDED 状态),暂时无法下载 |
|
||||
| 581518 | INVOICE_FORBIDDEN | 无权访问该发票(IDOR 防护,含发票不存在场景) |
|
||||
| 581519 | INVOICE_SIGNED_URL_FAILED | 获取签名下载 URL 失败,请稍后重试(hl-user-service 暂时不可用) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:申请开票
|
||||
|
||||
POST /v3/internal/mp/order/1234567890123456789/invoice/apply
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
### 8.2 边界情况:列表中 REQUESTED 状态 fileUrl 为 null
|
||||
|
||||
GET /v3/internal/mp/order/1234567890123456789/invoice/list 响应示例(某张待开票发票):
|
||||
|
||||
|
||||
### 8.3 业务失败:REQUESTED 状态发票不可下载
|
||||
|
||||
GET /v3/internal/mp/order/invoice/111/download
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- 申请:订单状态必须为 COMPLETED;无有效发票(一单一票);金额不超订单总价
|
||||
- 下载:发票状态为 ISSUED 或 PUSHED;且该发票属于当前登录用户的订单
|
||||
|
||||
**不适用:**
|
||||
- 订单 COMPLETED 之前不可申请
|
||||
- 已有有效发票,不可重复申请
|
||||
- REQUESTED / VOIDED 状态发票不可下载(报 581517)
|
||||
- 访问他人订单的发票报 581518(IDOR 防护,不返回 404 以避免泄露存在性)
|
||||
|
||||
**特殊边界:**
|
||||
- 签名 URL 有效期 1 小时。前端如果缓存了 fileUrl,每次打开下载前须重新调 download 接口获取最新签名 URL,不可长期复用。
|
||||
- wx.openDocument 下载后端直接返回签名 URL,前端无需中转。
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### POST apply 申请门槛变化
|
||||
|
||||
| 项目 | 变更前(旧 collab 实现) | 变更后(invoice 域) |
|
||||
|------|----------------------|-------------------|
|
||||
| 一单一票限制 | 无(可叠加多张) | 有(existsActiveByOrderId 拦截) |
|
||||
| 金额校验 | 已开+本次 <= 总价 | 本次金额 <= 总价(一单一票前提下等价) |
|
||||
| 响应 status 值 | APPLIED | REQUESTED |
|
||||
|
||||
### GET list / detail fileUrl 可见性变化
|
||||
|
||||
| 发票状态 | 变更前 fileUrl | 变更后 fileUrl |
|
||||
|---------|--------------|--------------|
|
||||
| REQUESTED | 有值(OSS 公链) | null(前端显示处理中) |
|
||||
| ISSUED | 有值 | 有值(不变) |
|
||||
| PUSHED | 有值 | 有值(不变) |
|
||||
| VOIDED | 有值 | null |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- 破坏兼容性(重要):fileUrl 字段在 REQUESTED 状态下由有值变为 null。前端如果之前有展示 REQUESTED 状态下 PDF 链接的逻辑(点击打开 OSS 文件),现在会读到 null,需要做 null 判断,展示「处理中」状态文案。
|
||||
- apply status 值变化:从 APPLIED 改为 REQUESTED。前端如果有对 status 值做 hardcode 判断(if status == APPLIED),需要更新。
|
||||
- 前端同步上线建议:fileUrl null 判断 + apply 响应 status 对齐必须在本次版本同步更新。
|
||||
- 回滚方案:回滚旧版本,fileUrl 可见性恢复,download 端点返回 404。apply 的 status 回退为 APPLIED(如旧版本有此值)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. fileUrl null 判断:list / detail 接口读到 fileUrl 为 null 时,前端应显示「处理中」或「待开票」而不是空链接。
|
||||
2. 签名 URL 勿缓存超过 1 小时:download 返回的签名 URL 有效期 1 小时,前端每次用前须重新调接口,不可全局缓存复用。
|
||||
3. apply 响应 status 值变更:旧值 APPLIED 已废弃,现在返回 REQUESTED。如前端有 switch/if 判断该值,须对齐。
|
||||
4. download IDOR 防护:发票不存在或不属于当前用户均返回 581518(INVOICE_FORBIDDEN),不返回 404,避免存在性枚举攻击。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue(小程序端 mp): https://git.1814.love:8443/wx/HL/issues/4174
|
||||
- Issue(签名下载): https://git.1814.love:8443/wx/HL/issues/4182
|
||||
- PR(mp 端 #4181): https://git.1814.love:8443/wx/HL/pulls/4181
|
||||
- PR(签名下载 #4184): https://git.1814.love:8443/wx/HL/pulls/4184
|
||||
- 后端负责人: yaosutu
|
||||
@@ -0,0 +1,340 @@
|
||||
# 售后申诉 — 小程序端新增接口
|
||||
|
||||
- **端类型**:小程序端
|
||||
- **变更类型**:新增接口
|
||||
- **日期**:2026-06-22
|
||||
- **关联 Issue**:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- **关联 PR**:#4185(小程序端接口)/ #4162 #4165 #4168 #4187(基础能力)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
v3 全新售后服务中心(com.hulalv.aftersale)上线。小程序端用户可发起投诉或申诉、查看自己的工单列表与详情、以及在工单未处置前随时撤回。
|
||||
|
||||
新增 4 个接口(含 1 个原有但归入本域):发起工单、我的工单列表、工单详情、撤回工单。
|
||||
|
||||
本次为全新域,不影响任何已有接口。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 编号 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | POST | /v3/mp/aftersale/ticket | 发起投诉/申诉工单 |
|
||||
| 2 | GET | /v3/mp/aftersale/ticket/my | 我的工单列表(按订单分页) |
|
||||
| 3 | GET | /v3/mp/aftersale/ticket/{id} | 工单详情 |
|
||||
| 4 | POST | /v3/mp/aftersale/ticket/{id}/withdraw | 撤回工单 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| 认证方式 | JWT Bearer Token(C 端 Token,Gateway 解析后注入 userId) |
|
||||
| userId 传递 | Gateway 解析 JWT 后注入到请求属性,后端从 HttpServletRequest 取;前端无需传 userId 字段 |
|
||||
| 限流 | Gateway 默认限流策略 |
|
||||
| 路径前缀 | /v3/mp/aftersale/ticket |
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 POST /v3/mp/aftersale/ticket — 发起工单
|
||||
|
||||
请求体(JSON):
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| orderId | Long | 是 | — | 订单 ID |
|
||||
| category | String | 是 | COMPLAINT / APPEAL | 大类:投诉/申诉 |
|
||||
| type | String | 是 | 见 §六枚举 | 细分类型,必须与 category 匹配 |
|
||||
| title | String | 是 | 最多 100 字 | 工单标题 |
|
||||
| description | String | 是 | 最多 2000 字 | 诉求描述 |
|
||||
| targetRefId | Long | 条件必填 | — | 申诉时必填:关联退款申请 ID;投诉时可不传(不传则用 orderId) |
|
||||
| claimType | String | 否 | REFUND / RECTIFY / EXPLANATION | 用户诉求类型,仅供参考 |
|
||||
| claimAmount | Number | 否 | — | 用户期望金额,仅供参考,无约束力 |
|
||||
| attachments | Array<String> | 否 | 最多 10 个 | 附件 URL 列表(图片/视频) |
|
||||
|
||||
### 4.2 GET /v3/mp/aftersale/ticket/my — 我的工单列表
|
||||
|
||||
Query 参数:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| orderId | Long | 是 | — | 订单 ID,只查该订单下的工单 |
|
||||
| pageNo | Integer | 否 | >= 1 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 1-50 | 每页条数,默认 20 |
|
||||
|
||||
### 4.3 GET /v3/mp/aftersale/ticket/{id} — 工单详情
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
注:后端校验工单归属,非本人工单返回 586014。
|
||||
|
||||
### 4.4 POST /v3/mp/aftersale/ticket/{id}/withdraw — 撤回工单
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
无请求体。后端校验:1. 工单归属(非本人返回 586014);2. 状态(只有 SUBMITTED / PROCESSING 可撤回,否则返回 586013)。
|
||||
|
||||
---
|
||||
|
||||
## 五、出参字段
|
||||
|
||||
### 5.1 发起工单 — 返回工单 ID
|
||||
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000001"}
|
||||
```
|
||||
|
||||
data 为 String(Long),即新建工单的 ID。
|
||||
|
||||
### 5.2 我的工单列表 — PageResult 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 5,
|
||||
"pageNo": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 工单详情及列表行 — TicketMpRespVO 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 工单 ID |
|
||||
| orderId | String(Long) | 关联订单 ID |
|
||||
| category | String | 大类枚举值:COMPLAINT / APPEAL |
|
||||
| categoryLabel | String | 大类中文名:投诉 / 申诉 |
|
||||
| type | String | 细分类型枚举值,见 §六 |
|
||||
| typeLabel | String | 细分类型中文名 |
|
||||
| title | String | 工单标题 |
|
||||
| description | String | 诉求描述 |
|
||||
| claimType | String | 用户诉求类型(可选) |
|
||||
| claimAmount | Number | 用户期望金额(可选) |
|
||||
| attachments | Array<String> | 附件 URL 列表 |
|
||||
| status | String | 工单状态枚举值,见 §六 |
|
||||
| statusLabel | String | 工单状态中文名 |
|
||||
| resolutionRemark | String | 处置说明(RESOLVED / CLOSED 后可见,用于展示给用户) |
|
||||
| canWithdraw | Boolean | 是否可撤回(SUBMITTED / PROCESSING 且非终态时为 true) |
|
||||
| createdAt | String(ISO 8601) | 创建时间 |
|
||||
| updatedAt | String(ISO 8601) | 更新时间 |
|
||||
|
||||
注:C 端视图隐藏处置细节(decision / resolutionType / resolutionAmount / approvalNo / linkedRefundId),仅展示 status 和 resolutionRemark。
|
||||
|
||||
### 5.4 撤回工单
|
||||
|
||||
```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### AftersaleCategory — 工单大类
|
||||
|
||||
| 枚举值 | 中文名 | 场景 |
|
||||
|--------|--------|------|
|
||||
| COMPLAINT | 投诉 | 对已发生的服务质量不满 |
|
||||
| APPEAL | 申诉 | 对某个处理结果不服,求复核 |
|
||||
|
||||
### AftersaleTicketType — 细分类型(type 字段)
|
||||
|
||||
| 枚举值 | 中文名 | 归属大类 |
|
||||
|--------|--------|----------|
|
||||
| ITINERARY | 行程 | COMPLAINT |
|
||||
| HOTEL | 酒店 | COMPLAINT |
|
||||
| VEHICLE | 车 | COMPLAINT |
|
||||
| GUIDE | 导游 | COMPLAINT |
|
||||
| OTHER | 其他 | COMPLAINT |
|
||||
| REFUND_REJECTED | 退款被拒 | APPEAL |
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议 | APPEAL |
|
||||
|
||||
注:type 必须与 category 匹配,否则返回 586012。
|
||||
|
||||
### AftersaleTicketStatus — 工单状态
|
||||
|
||||
| 枚举值 | 中文名 | 终态 | 说明 |
|
||||
|--------|--------|------|------|
|
||||
| SUBMITTED | 已提交 | 否 | 等待受理 |
|
||||
| PROCESSING | 处理中 | 否 | 客服受理中 |
|
||||
| RESOLVED | 已处置 | 否 | 处置方案落定(退款类等待到账) |
|
||||
| CLOSED | 已关闭 | 是 | 流程终结 |
|
||||
| WITHDRAWN | 已撤回 | 是 | 用户主动撤回 |
|
||||
|
||||
canWithdraw = true 的条件:status 为 SUBMITTED 或 PROCESSING。
|
||||
|
||||
### claimType — 用户诉求类型(参考值,无强制约束)
|
||||
|
||||
| 常见值 | 中文名 |
|
||||
|--------|--------|
|
||||
| REFUND | 退款 |
|
||||
| RECTIFY | 整改 |
|
||||
| EXPLANATION | 要个说法 |
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 586009 | 工单不存在 | 传入 id 找不到工单 |
|
||||
| 586011 | 同一对象已存在活跃工单 | 防重复提交(同订单下同 target 已有进行中工单) |
|
||||
| 586012 | category 与 type 不匹配 | 如 COMPLAINT 传了 REFUND_REJECTED |
|
||||
| 586013 | 撤回时状态非法 | 终态或 RESOLVED 工单不可撤回 |
|
||||
| 586014 | 非本人工单 | 查看/撤回他人工单 |
|
||||
| 586015 | 申诉必须关联退款申请 | APPEAL 类工单未传 targetRefId |
|
||||
| 586016 | 申诉关联退款状态不合法 | 关联的退款申请状态不允许申诉 |
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功 — 发起投诉工单
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer {mp_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1800000000000099,
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级投诉",
|
||||
"description": "订单挂四星酒店实际入住三星,要求退差价",
|
||||
"claimType": "REFUND",
|
||||
"claimAmount": 600.00,
|
||||
"attachments": ["https://oss.example.com/img1.jpg"]
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000001"}
|
||||
```
|
||||
|
||||
### 8.2 典型成功 — 发起申诉工单(退款被拒)
|
||||
|
||||
```
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer {mp_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1800000000000099,
|
||||
"category": "APPEAL",
|
||||
"type": "REFUND_REJECTED",
|
||||
"targetRefId": 9876543210,
|
||||
"title": "退款申请被拒申诉",
|
||||
"description": "退款申请被拒,但供应商确实未提供服务,要求重新审核"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 200, "data": "1895000000000002"}
|
||||
```
|
||||
|
||||
### 8.3 边界情况 — 查询工单详情(已处置,展示处置说明)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": "1895000000000001",
|
||||
"orderId": "1800000000000099",
|
||||
"category": "COMPLAINT",
|
||||
"categoryLabel": "投诉",
|
||||
"type": "HOTEL",
|
||||
"typeLabel": "酒店",
|
||||
"title": "酒店降级投诉",
|
||||
"description": "订单挂四星酒店实际入住三星,要求退差价",
|
||||
"claimType": "REFUND",
|
||||
"claimAmount": 600.00,
|
||||
"attachments": ["https://oss.example.com/img1.jpg"],
|
||||
"status": "CLOSED",
|
||||
"statusLabel": "已关闭",
|
||||
"resolutionRemark": "经核实酒店确实降级,已退差价 500 元",
|
||||
"canWithdraw": false,
|
||||
"createdAt": "2026-06-22T09:00:00",
|
||||
"updatedAt": "2026-06-22T15:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 业务失败 — 申诉未传 targetRefId
|
||||
|
||||
```json
|
||||
{"code": 586015, "msg": "申诉必须关联一笔有效的退款申请"}
|
||||
```
|
||||
|
||||
### 8.5 业务失败 — 撤回已 CLOSED 工单
|
||||
|
||||
```json
|
||||
{"code": 586013, "msg": "撤回时工单状态非法(只有 SUBMITTED / PROCESSING 可撤回)"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
适用:
|
||||
- 同一订单下同一 targetRef 只能有一个活跃工单(SUBMITTED / PROCESSING / RESOLVED),否则返回 586011
|
||||
- 申诉(APPEAL)类工单必须关联一笔有效的退款申请(targetRefId 必填)
|
||||
- SUBMITTED / PROCESSING 状态的工单可撤回,canWithdraw=true
|
||||
|
||||
不适用:
|
||||
- 终态工单(CLOSED / WITHDRAWN)不可撤回
|
||||
- 非本人工单不可查看/撤回
|
||||
|
||||
特殊边界:
|
||||
- 工单从 PROCESSING 撤回后,若该工单已提交企微 OA,后端会自动撤回 OA 审批(异步,不影响前端响应)
|
||||
- attachments 最多 10 个,超出返回参数校验错误
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
本次为全新域新增接口,无旧接口对比。
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
本次为全新域,不影响任何已有小程序接口,可独立上线。
|
||||
|
||||
回滚方案:回滚后端服务版本即可。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
1. 发起工单返回值是 String(Long):data 字段为新建工单的 ID,是字符串而非数字。
|
||||
2. C 端视图隐藏处置细节:resolutionAmount / decision / approvalNo 等字段在 C 端不返回,不要在小程序页面展示。resolutionRemark 是给用户看的处置说明,可展示。
|
||||
3. canWithdraw 字段已由后端计算:前端直接用该字段判断是否显示撤回按钮,无需自行判断状态。
|
||||
4. 所有 ID 为字符串:id / orderId 均序列化为字符串,前端不要转 Number。
|
||||
5. category=APPEAL 时:targetRefId 必填(关联退款申请 ID),targetRefType 后端自动设为 REFUND_APPLICATION。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- PR #4185(小程序端接口):https://git.1814.love:8443/wx/HL/pulls/4185
|
||||
- PR #4162(售后基建):https://git.1814.love:8443/wx/HL/pulls/4162
|
||||
- PR #4165(受理/处置 + OA 接入):https://git.1814.love:8443/wx/HL/pulls/4165
|
||||
- PR #4168(OA 回调内部接口):https://git.1814.love:8443/wx/HL/pulls/4168
|
||||
- PR #4187(退款驱动联动):https://git.1814.love:8443/wx/HL/pulls/4187
|
||||
- 后端负责人:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,341 @@
|
||||
# 售后工单接入 wx 内容安全机审 — 修改接口(小程序端)
|
||||
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-22
|
||||
- **Issue**:[#4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[#4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[8882eb156](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **后端负责人**:yaosutu
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
用户在小程序发起售后工单(投诉/申诉)时,若附带图片或视频,前端需先通过微信内容安全 `msgSecCheck` 接口对媒体文件做内容审核,得到微信返回的 `traceId` 后,连同创建工单请求一并透传给后端。后端不直接调用微信安全接口,只存储 traceId,等待微信异步回调后更新审核状态。无图/无视频的工单跳过机审,直接置为 `APPROVED`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 变更类型 | 接口 | 字段 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ⚠️ 入参新增 | `POST /v3/mp/aftersale/ticket` | `mediaTraceIds` | 新增可选字段,机审 traceId 数组,最多 12 个 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/{id}` | `auditStatus` | 工单 wx 内容安全机审状态 |
|
||||
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/list` | `auditStatus` | 工单列表每条记录新增机审状态 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 发起售后工单
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `POST /v3/mp/aftersale/ticket` |
|
||||
| 接口描述 | 用户发起售后工单(投诉/申诉),支持附件 + 机审 traceId 透传 |
|
||||
| 认证 | 需要用户 JWT(小程序登录 token) |
|
||||
| 幂等性 | 非幂等,每次调用创建一条新工单 |
|
||||
| 限流 | 无独立限流(受网关全局限流) |
|
||||
|
||||
### 3.2 工单详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/{id}` |
|
||||
| 接口描述 | 获取单条售后工单详情 |
|
||||
| 认证 | 需要用户 JWT,仅可查自己的工单 |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
### 3.3 工单列表
|
||||
|
||||
| 属性 | 值 |
|
||||
|-----|-----|
|
||||
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/list` |
|
||||
| 接口描述 | 查询当前用户的售后工单列表(分页) |
|
||||
| 认证 | 需要用户 JWT |
|
||||
| 限流 | 无独立限流 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
`GET /v3/mp/aftersale/ticket/{id}`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-------|------|------|------|
|
||||
| id | Long | 是 | 工单 ID |
|
||||
|
||||
### 4.2 请求体字段(POST /v3/mp/aftersale/ticket)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 校验 | 说明 |
|
||||
|-------|------|------|------|------|
|
||||
| orderId | Long | 是 | 不能为空 | 关联订单 ID |
|
||||
| category | String | 是 | 枚举:COMPLAINT / APPEAL | 工单分类:投诉 / 申诉 |
|
||||
| type | String | 是 | 枚举,见 §6 | 具体类型(如 HOTEL / GUIDE / PRICE) |
|
||||
| title | String | 是 | max=100 | 工单标题 |
|
||||
| description | String | 是 | max=2000 | 问题描述 |
|
||||
| attachments | List\<String\> | 否 | max=10 | 附件 URL 列表(图片/视频) |
|
||||
| **mediaTraceIds** | **List\<String\>** | **否** | **max=12** | **微信内容安全 traceId 数组(前端透传,后端只存)** |
|
||||
|
||||
> `mediaTraceIds` 与 `attachments` 一一对应关系由前端维护。后端不校验对应关系,仅存储用于异步机审回流。无附件时可不传或传空数组。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### TicketMpRespVO(工单详情/列表单条)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| id | Long | 工单 ID |
|
||||
| orderId | Long | 关联订单 ID |
|
||||
| orderNo | String | 订单编号 |
|
||||
| category | String | 工单分类枚举:COMPLAINT / APPEAL |
|
||||
| type | String | 工单类型 |
|
||||
| title | String | 标题 |
|
||||
| description | String | 描述 |
|
||||
| status | String | 工单状态:SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN |
|
||||
| statusName | String | 工单状态中文名 |
|
||||
| canWithdraw | Boolean | 是否可撤回 |
|
||||
| **auditStatus** | **String** | **wx 内容安全机审状态,取值见 §6** |
|
||||
| attachments | List\<String\> | 附件 URL |
|
||||
| createdAt | String | 创建时间(ISO 8601) |
|
||||
| updatedAt | String | 更新时间(ISO 8601) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### auditStatus — wx 内容安全机审状态
|
||||
|
||||
| 值 | 含义 | 触发条件 |
|
||||
|----|------|---------|
|
||||
| `PENDING` | 机审中 | 创建工单时有 mediaTraceIds(异步等待微信回调) |
|
||||
| `APPROVED` | 已通过 | 无 mediaTraceIds 的工单直接置此(免审);微信回调结果合规也置此 |
|
||||
| `MANUAL_REVIEW` | 人工复审 | 微信机审认为需人工介入 |
|
||||
| `REJECTED` | 已驳回 | 微信机审判定内容违规 |
|
||||
|
||||
> **重要**:`PENDING` 状态为异步,工单创建成功后微信回调可能在数秒到数分钟内到达。前端建议在工单详情页轮询或基于页面刷新展示最新状态,不要把 `PENDING` 当作错误。
|
||||
|
||||
### category — 工单分类
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `COMPLAINT` | 投诉 |
|
||||
| `APPEAL` | 申诉 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|
||||
|-------|---------|------|---------|
|
||||
| 581001 | 400 | 工单关联订单不存在 | orderId 无效 |
|
||||
| 581002 | 400 | 工单已存在,不可重复提交 | 同一订单同分类已有活跃工单 |
|
||||
| 581003 | 400 | 订单状态不允许发起售后 | 订单未处于可售后状态 |
|
||||
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
|
||||
| 200-002 | 403 | 无权操作 | 工单不属于当前用户 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——带图片机审
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"mediaTraceIds": [
|
||||
"trace_abc123def456",
|
||||
"trace_xyz789uvw012"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654321,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "COMPLAINT",
|
||||
"type": "HOTEL",
|
||||
"title": "酒店降级安排",
|
||||
"description": "预订了四星酒店,实际安排了三星,差价未退。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "PENDING",
|
||||
"attachments": [
|
||||
"https://oss.example.com/media/hotel_photo_1.jpg",
|
||||
"https://oss.example.com/media/hotel_photo_2.jpg"
|
||||
],
|
||||
"createdAt": "2026-06-22T10:30:00",
|
||||
"updatedAt": "2026-06-22T10:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `auditStatus` 初始为 `PENDING`,等待微信异步回调后更新为 `APPROVED` / `MANUAL_REVIEW` / `REJECTED`。
|
||||
|
||||
### 8.2 边界情况——无附件工单(直接 APPROVED)
|
||||
|
||||
**请求**
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"attachments": [],
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 987654322,
|
||||
"orderId": 1234567890,
|
||||
"orderNo": "HL20260622001",
|
||||
"category": "APPEAL",
|
||||
"type": "PRICE",
|
||||
"title": "价格异议",
|
||||
"description": "行程中临时要求加收费用,合同未约定。",
|
||||
"status": "SUBMITTED",
|
||||
"statusName": "已提交",
|
||||
"canWithdraw": true,
|
||||
"auditStatus": "APPROVED",
|
||||
"attachments": [],
|
||||
"createdAt": "2026-06-22T10:35:00",
|
||||
"updatedAt": "2026-06-22T10:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 无 mediaTraceIds 或传空数组时,`auditStatus` 直接为 `APPROVED`,无需等待回调。
|
||||
|
||||
### 8.3 业务失败——订单已有活跃工单
|
||||
|
||||
**请求**(同一订单同分类已存在未关闭工单)
|
||||
```http
|
||||
POST /v3/mp/aftersale/ticket
|
||||
Authorization: Bearer <user-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": 1234567890,
|
||||
"category": "COMPLAINT",
|
||||
"type": "GUIDE",
|
||||
"title": "导游服务差",
|
||||
"description": "导游态度恶劣。",
|
||||
"mediaTraceIds": []
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```json
|
||||
{
|
||||
"code": 581002,
|
||||
"msg": "工单已存在,不可重复提交",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用场景**:
|
||||
- 订单处于可售后状态(已出行、出行中、已完成等,具体由后端校验)
|
||||
- 有图片/视频附件时前端必须先完成 `wx.msgSecCheck` 得到 traceId 再调此接口
|
||||
- 无附件或不需要机审时,`mediaTraceIds` 可省略或传空数组
|
||||
|
||||
**不适用场景**:
|
||||
- 已取消订单不可发起售后
|
||||
- 同一订单同分类已有活跃(非 CLOSED/WITHDRAWN)工单时,不可重复发起
|
||||
|
||||
**特殊边界**:
|
||||
- `mediaTraceIds` 最多 12 个,超出返回参数校验错误
|
||||
- `auditStatus` 为 `PENDING` 时工单照常流转(客服可正常处理),机审结果不阻塞工单流程
|
||||
- `auditStatus` 为 `REJECTED` 时,仅作展示标记,工单处理流程由客服决定是否关闭
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
**CreateTicketReqVO(入参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| mediaTraceIds | 不存在 | 新增,List\<String\>,可选,max=12 |
|
||||
|
||||
**TicketMpRespVO(出参)**
|
||||
|
||||
| 字段 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| auditStatus | 不存在 | 新增,String,wx 机审状态 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 场景 | 变更前 | 变更后 |
|
||||
|------|-------|-------|
|
||||
| 带图片发起工单 | 无机审,无审核状态 | 存 traceId,初始 auditStatus=PENDING,微信异步回调更新 |
|
||||
| 无图片发起工单 | 无机审状态字段 | auditStatus 直接置 APPROVED |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| 破坏兼容性 | 否(入参新增可选字段,旧版不传兼容;出参新增字段,旧版忽略) |
|
||||
| 前端同步上线 | 建议同步:入参传 mediaTraceIds 发挥机审能力;出参展示 auditStatus |
|
||||
| 回滚方案 | 后端回滚 PR #4203 即可,无 DDL 数据损失风险(DDL 只加列不删) |
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. `mediaTraceIds` 由前端在调用微信 `wx.msgSecCheck` 后获得,后端不直接调微信安全接口,只做存储和转发。
|
||||
2. `auditStatus=PENDING` 是异步状态,不代表工单异常,客服后台可正常处理。
|
||||
3. 历史工单(此 PR 上线前创建的)`auditStatus` 字段为 `null`,前端展示时建议视 `null` 等同于 `APPROVED` 处理。
|
||||
4. 微信机审结果 `REJECTED` 不自动关闭工单,仅打标记,由客服决定后续处理。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**:[https://git.1814.love:8443/wx/HL/issues/4196](https://git.1814.love:8443/wx/HL/issues/4196)
|
||||
- **PR**:[https://git.1814.love:8443/wx/HL/pulls/4203](https://git.1814.love:8443/wx/HL/pulls/4203)
|
||||
- **Commit**:[https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
|
||||
- **关联 Issue**:[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
|
||||
- **后端负责人**:yaosutu
|
||||
@@ -0,0 +1,31 @@
|
||||
# 【修正·小程序】售后工单 mp 端接口加归属校验(防横向越权)
|
||||
|
||||
> 模块:order-v3 售后工单(aftersale)小程序端
|
||||
> 类型:安全加固(每日审查驱动,新模块上线前修正)
|
||||
> 影响端点:`/v3/mp/aftersale/ticket/**`(我的工单列表 / 详情 / 撤回)
|
||||
> 关联:售后工单中心 PR1-5(#4162/#4165/#4168/#4173/#4185)的每日审查修正
|
||||
> 测试服已部署验证(9443 实测端点 200)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
售后工单 mp 端三个用户入口此前缺少归属校验,任意登录用户传他人 `ticketId`/`orderId` 即可读取或撤回他人工单(横向越权 IDOR)。本次加固:**经订单归属校验当前用户是否为下单人**,非本人操作返回错误码 **586014(TICKET_NOT_OWN)**。
|
||||
|
||||
正常业务流程下,小程序用户只操作自己订单的售后工单,**不会触发**该错误;前端无需改动,仅需对 586014 做兜底提示(如「无权访问该工单」)。
|
||||
|
||||
## 1. 受影响端点与行为
|
||||
|
||||
| 端点 | 修正 |
|
||||
|------|------|
|
||||
| `GET /v3/mp/aftersale/ticket/my`(我的工单列表) | 新增按当前用户校验 `orderId` 归属;端点入参不变,仅服务端加校验 |
|
||||
| `GET /v3/mp/aftersale/ticket/{id}`(工单详情) | 校验工单所属订单归属当前用户,非本人抛 586014 |
|
||||
| `POST /v3/mp/aftersale/ticket/{id}/withdraw`(撤回) | 同上归属校验,非本人不可撤回(且不再触发他人 OA 审批撤销) |
|
||||
|
||||
## 2. 附带修正:撤回工单不再消失
|
||||
|
||||
撤回(WITHDRAWN)此前被误当软删处理,导致撤回后的工单在「我的工单」列表/详情中**永久不可见**。本次修正后,**撤回是可见终态**,撤回的工单仍可在列表与详情中查看(状态显示「已撤回」)。
|
||||
|
||||
## 3. 错误码
|
||||
|
||||
| code | 含义 |
|
||||
|------|------|
|
||||
| 586014 | 无权操作该售后工单(非本人订单)|
|
||||
@@ -0,0 +1,222 @@
|
||||
# 退款申诉(新增)+ 统一售后工单接口下线(小程序端)
|
||||
|
||||
- 端类型:小程序端
|
||||
- 变更类型:新增接口(4)+ 删除接口(统一售后工单 mp 端)
|
||||
- 关联 Issue:#4250 PR:#4251 #4254
|
||||
- 日期:2026-06-23
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
售后体系重构:原"投诉 + 申诉统一售后工单"方向作废,拆为**投诉**与**申诉**两套独立能力。
|
||||
- **申诉**:用户对"退款被拒"或"退款金额"不服时发起,**小程序入口仍是「退款申诉」**。申诉记录单独存(不再混入退款申请表)。
|
||||
- **关键变化**:申诉**复用既有退款审批**——发起申诉后系统自动建一笔 PENDING 退款申请,由管理员在退款审批里复核(通过则退款 / 驳回则结束);申诉本身不再有独立审批。
|
||||
- 原统一售后工单接口(`/v3/mp/aftersale/ticket*`)**全部下线**。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 类型 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST | `/v3/mp/refund/appeal` | 🆕 新增(发起申诉)|
|
||||
| 2 | GET | `/v3/mp/refund/appeal/my` | 🆕 新增(我的申诉,分页)|
|
||||
| 3 | GET | `/v3/mp/refund/appeal/{id}` | 🆕 新增(申诉详情)|
|
||||
| 4 | POST | `/v3/mp/refund/appeal/{id}/withdraw` | 🆕 新增(撤回申诉)|
|
||||
| 5 | ALL | `/v3/mp/aftersale/ticket*` | ❌ 删除(统一售后工单下线,调用返回 404)|
|
||||
|
||||
统一响应包装 `Result<T>`:`{ code, message, data, success }`,`code=200` 为成功。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口详情
|
||||
|
||||
### 1. 发起申诉 `POST /v3/mp/refund/appeal`
|
||||
用户对一笔退款申请发起申诉。两种类型:`REFUND_REJECTED`(退款被拒,原退款 status=REJECTED 才可申诉)/ `REFUND_AMOUNT_DISPUTE`(退款金额异议,原退款 status=REFUNDED 求补差)。
|
||||
> 发起成功后:① 落一条申诉记录(PENDING)② **自动建一笔 PENDING 退款申请**走退款审批 ③ 订单进入「售后中」。
|
||||
|
||||
### 2. 我的申诉 `GET /v3/mp/refund/appeal/my`
|
||||
当前登录用户的申诉分页列表。
|
||||
|
||||
### 3. 申诉详情 `GET /v3/mp/refund/appeal/{id}`
|
||||
单条申诉详情,含关联的两笔退款(来源退款 + 申诉触发的退款)。
|
||||
|
||||
### 4. 撤回申诉 `POST /v3/mp/refund/appeal/{id}/withdraw`
|
||||
仅 `PENDING`(审批中)状态可撤回;撤回会一并取消申诉触发的那笔 PENDING 退款申请。
|
||||
|
||||
---
|
||||
|
||||
## ④ 入参
|
||||
|
||||
### 接口1 create(`@RequestBody`,登录态 userId 由网关注入)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| refundApplicationId | long | 是 | 来源退款申请 ID(被申诉的那笔退款)|
|
||||
| appealType | string | 是 | 申诉类型:`REFUND_REJECTED` / `REFUND_AMOUNT_DISPUTE` |
|
||||
| appealReason | string | 是 | 申诉理由(≤2000 字)|
|
||||
| appealAmount | string(decimal) | 否 | 期望退款金额(金额异议时填)|
|
||||
| mediaTraceIds | string[] | 否 | 凭证图片机审 traceId 列表(前端上传后透传,≤12)|
|
||||
|
||||
### 接口2 my
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| pageNo | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||||
|
||||
### 接口3 detail / 接口4 withdraw
|
||||
| 参数 | 位置 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | path | long | 申诉 ID |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 出参
|
||||
|
||||
### 接口1 create `Result<Long>`
|
||||
`data` = 新建申诉 ID(字符串化 Long)。
|
||||
|
||||
### 接口2 my `Result<PageResult<AppealMpRespVO>>`;接口3 detail `Result<AppealMpRespVO>`
|
||||
`PageResult`:`{ records[], total, page, pageSize }`。`AppealMpRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string(Long) | 申诉 ID |
|
||||
| orderId | string(Long) | 订单 ID |
|
||||
| refundApplicationId | string(Long) | 来源退款申请 ID |
|
||||
| appealType | string | 申诉类型(枚举值,见⑥)|
|
||||
| appealTypeLabel | string | 申诉类型中文名 |
|
||||
| appealReason | string | 申诉理由 |
|
||||
| appealAmount | string(decimal) | 申诉退款金额 |
|
||||
| appealStatus | string | 申诉状态(枚举值,见⑥)|
|
||||
| appealStatusLabel | string | 申诉状态中文名 |
|
||||
| applicantName | string | 申请人姓名 |
|
||||
| reviewedAt | datetime | 审批完成时间(可空)|
|
||||
| createTime | datetime | 创建时间 |
|
||||
| sourceRefund | object | 来源退款信息(被申诉的原退款),见下 |
|
||||
| triggeredRefund | object | 申诉触发的退款信息(申诉建单后自动生成的退款),见下 |
|
||||
|
||||
`sourceRefund` / `triggeredRefund` 结构(关联退款信息):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refundApplicationId | string(Long) | 退款申请 ID |
|
||||
| status | string | 退款状态(PENDING/APPROVED/REJECTED/REFUNDING/REFUNDED/CANCELLED/ABNORMAL)|
|
||||
| actualAmount | string(decimal) | 实际退款金额(可空)|
|
||||
|
||||
### 接口4 withdraw `Result<Void>`
|
||||
`data` = null。
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 枚举 / 数据字典
|
||||
|
||||
**申诉类型 appealType**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| REFUND_REJECTED | 退款被拒(原退款被驳回后申诉)|
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议(已退款但金额有异议,求补差)|
|
||||
|
||||
**申诉状态 appealStatus**:
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| PENDING | 审批中(申诉触发的退款申请待退款审批)|
|
||||
| REJECTED | 已驳回(退款审批驳回)|
|
||||
| REFUNDED | 退款到账(申诉成功,退款已退)|
|
||||
| WITHDRAWN | 已撤回 |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 错误码(段位 530600-530699)
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 530601 | 申诉必须关联一笔退款申请 |
|
||||
| 530602 | 关联的退款申请不存在 |
|
||||
| 530603 | 退款申请当前状态不满足申诉条件 |
|
||||
| 530604 | 该退款申请已有处理中的申诉,请勿重复提交 |
|
||||
| 530605 | 申诉退款金额超出可退余额 |
|
||||
| 530606 | 申诉记录不存在 |
|
||||
| 530607 | 无权操作此申诉 |
|
||||
| 530608 | 当前申诉状态不允许撤回,仅待审核状态可撤回 |
|
||||
| 530609 | 申诉类型无效 |
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 示例
|
||||
|
||||
### 典型:发起申诉(退款被拒)
|
||||
请求 `POST /v3/mp/refund/appeal`:
|
||||
```json
|
||||
{ "refundApplicationId": 20001, "appealType": "REFUND_REJECTED",
|
||||
"appealReason": "退款审核不合理,申请复核", "appealAmount": "300.00",
|
||||
"mediaTraceIds": [] }
|
||||
```
|
||||
响应:`{ "code":200, "message":"成功", "data":"40001", "success":true }`
|
||||
|
||||
### 典型:我的申诉
|
||||
请求 `GET /v3/mp/refund/appeal/my?pageNo=1&pageSize=10`,响应(节选一项):
|
||||
```json
|
||||
{ "code":200, "success":true, "data": { "total":1, "records":[
|
||||
{ "id":"40001", "orderId":"10001", "appealType":"REFUND_REJECTED", "appealTypeLabel":"退款被拒",
|
||||
"appealStatus":"PENDING", "appealStatusLabel":"审批中", "appealAmount":"300.00",
|
||||
"sourceRefund": { "refundApplicationId":"20001", "status":"REJECTED", "actualAmount":null },
|
||||
"triggeredRefund": { "refundApplicationId":"20009", "status":"PENDING", "actualAmount":null } }
|
||||
] } }
|
||||
```
|
||||
|
||||
### 异常:重复申诉
|
||||
对同一退款申请已有处理中申诉时再发起:
|
||||
```json
|
||||
{ "code":530604, "message":"该退款申请已有处理中的申诉,请勿重复提交", "success":false }
|
||||
```
|
||||
|
||||
### 异常:调用已下线的工单接口
|
||||
请求 `POST /v3/mp/aftersale/ticket`(旧统一售后工单接口):
|
||||
```json
|
||||
{ "code":404, "message":"Not Found" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 业务边界
|
||||
|
||||
- 申诉**复用退款审批**:发起后自动建 PENDING 退款申请,管理员在退款审批里复核——**前端不要再调用任何"申诉审批"接口**(已无独立申诉审批)。
|
||||
- 申诉状态跟随其触发的退款申请:退款到账→REFUNDED,退款审批驳回→REJECTED。
|
||||
- 撤回仅 PENDING 可撤,且会取消触发的 PENDING 退款。
|
||||
- 申诉发起即把订单标记「售后中」,终结(REFUNDED/REJECTED/WITHDRAWN 且无其他活跃售后)后回切。
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 修改前后对比
|
||||
|
||||
| | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 申诉入口 | 统一售后工单 `POST /v3/mp/aftersale/ticket`(category=APPEAL)| `POST /v3/mp/refund/appeal` |
|
||||
| 我的申诉 | `/v3/mp/aftersale/ticket/my` | `/v3/mp/refund/appeal/my` |
|
||||
| 详情/撤回 | `/v3/mp/aftersale/ticket/{id}` `/{id}/withdraw` | `/v3/mp/refund/appeal/{id}` `/{id}/withdraw` |
|
||||
| 审批 | 申诉独立 OA 审批 | 无(复用退款审批)|
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 影响评估 / 回滚
|
||||
|
||||
- **破坏性**:`/v3/mp/aftersale/ticket*` 已删,调用返回 404,前端涉及"退款申诉"的页面**必须切到新申诉接口**。
|
||||
- 投诉接口不在此列(投诉走 C 端经 BFF 的现有通道,无变化)。
|
||||
- 回滚:后端回滚 PR #4251 #4254。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 注意事项
|
||||
|
||||
- 金额字段(appealAmount / actualAmount)均为**字符串**,前端按字符串处理防精度丢失。
|
||||
- Long 型 ID 均字符串化返回(防 JS 精度)。
|
||||
- 上传凭证图片仍走既有 wx 内容安全机审通道(mediaTraceIds 透传)。
|
||||
|
||||
---
|
||||
|
||||
## ⑬ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4250
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/4251 ・ https://git.1814.love:8443/wx/HL/pulls/4254
|
||||
- 后端负责人:腰苏图
|
||||
- 已部署测试服并网关实调验证通过(申诉接口 200 / 工单接口 404)。
|
||||
@@ -0,0 +1,336 @@
|
||||
# 发票申请票种修正(最终契约)— 小程序端
|
||||
|
||||
> 变更类型:修改接口(破坏性)
|
||||
> 端类型:小程序端
|
||||
> 日期:2026-06-23 | Issue:#4296 | PR:#4292 / #4302 | 服务:hl-order-service-v3
|
||||
|
||||
> ⚠️ **破坏性变更**:`invoiceType` 删除 `ELECTRONIC`、`email` 改为始终必填 + 格式校验、专票限公司抬头 + 必填五项、删除 `mailAddress` 字段。小程序开票页与详情页均需同步改动,上线时前后端须同步发布。
|
||||
|
||||
> **勘误(截至 PR #4311 收口)**:抬头字段统一 `titleName`、金额 `amount` 改 JSON number(单位元,非分/x100)、票种仅 2 值(`VAT_NORMAL`/`VAT_SPECIAL`)、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。
|
||||
|
||||
---
|
||||
|
||||
## 接口背景
|
||||
|
||||
发票模型经 PR #4292 + #4302 多轮修正后定稿:删除「电子发票」票种(业务决策统一走普票),专票收严字段校验(限公司抬头 + 必填银行注册信息),email 改为始终必填(全系统电子交付),删除纸质邮寄字段 `mailAddress`。
|
||||
|
||||
本次涉及小程序两个接口:初次申请 `POST /v3/internal/mp/order/{orderId}/invoice/apply` 和重新开票 `POST /v3/internal/mp/invoice/{id}/reissue`,入参字段矩阵相同,出参亦做同步修正(删 `mailAddress`,invoiceType 去 ELECTRONIC)。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 变更类型 | 说明 |
|
||||
|---|---------|------|
|
||||
| 1 | ⚠️ 申请入参删枚举值 | `invoiceType` 删除 `ELECTRONIC`,只保留 `VAT_NORMAL` / `VAT_SPECIAL` |
|
||||
| 2 | ⚠️ 申请入参删字段 | 删除 `mailAddress`(纸质邮寄地址) |
|
||||
| 3 | ⚠️ 申请入参校验收严 | `email` 改为始终必填 + 邮箱格式校验 |
|
||||
| 4 | ⚠️ 申请入参校验新增 | `taxNo`:单位抬头或专票时必填 |
|
||||
| 5 | ⚠️ 申请入参校验新增 | 专票:`bankName` / `bankAccount` / `registAddress` / `registPhone` 四项必填 |
|
||||
| 6 | ⚠️ 申请入参新增错误码 | `581523` 专票只能开给单位 |
|
||||
| 7 | ⚠️ 详情出参删字段 | `GET /v3/internal/mp/order/{orderId}/invoice/detail` 出参删除 `mailAddress` |
|
||||
| 8 | ⚠️ 列表出参枚举收窄 | `GET /v3/internal/mp/order/{orderId}/invoice/list` 出参 invoiceType 不再出现 ELECTRONIC |
|
||||
| 9 | 重开接口同步 | `POST /v3/internal/mp/invoice/{id}/reissue` 入参字段矩阵与申请接口保持一致 |
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 小程序申请接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/order/{orderId}/invoice/apply` |
|
||||
| **接口名** | 小程序客户自主申请发票 |
|
||||
| **描述** | 客户在订单完成后自主提交开票申请,支持普票/专票,全电子交付 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
### 小程序重开接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|------|
|
||||
| **方法 + 路径** | `POST /v3/internal/mp/invoice/{id}/reissue` |
|
||||
| **接口名** | 小程序重新开票 |
|
||||
| **描述** | 旧发票作废后,客户重新提交开票申请;入参字段矩阵与 apply 接口相同 |
|
||||
| **认证** | 小程序 JWT(Bearer Token,C 端用户) |
|
||||
| **幂等性** | 非幂等 |
|
||||
| **限流** | 无特殊限流 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 路径参数
|
||||
|
||||
**apply 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `orderId` | string(Long 雪花 ID) | 是 | 订单 ID |
|
||||
|
||||
**reissue 接口:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `id` | string(Long 雪花 ID) | 是 | 被重开的旧发票 ID |
|
||||
|
||||
### 请求体字段(两接口共用)
|
||||
|
||||
| 字段名 | 类型 | 必填条件 | 说明 |
|
||||
|--------|------|---------|------|
|
||||
| `invoiceType` | string | 始终必填 | 发票类型:`VAT_NORMAL`(普票)/ `VAT_SPECIAL`(专票);**不含 ELECTRONIC** |
|
||||
| `titleType` | string | 始终必填 | 抬头类型:`COMPANY`(单位)/ `PERSONAL`(个人);**VAT_SPECIAL 只能 COMPANY** |
|
||||
| `titleName` | string | 始终必填 | 发票抬头 |
|
||||
| `taxNo` | string | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时必填 | 纳税人识别号 |
|
||||
| `bankName` | string | `invoiceType=VAT_SPECIAL` 时必填 | 开户银行 |
|
||||
| `bankAccount` | string | `invoiceType=VAT_SPECIAL` 时必填 | 银行账号 |
|
||||
| `registAddress` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册地址 |
|
||||
| `registPhone` | string | `invoiceType=VAT_SPECIAL` 时必填 | 注册电话 |
|
||||
| `amount` | number | 始终必填 | 开票金额,JSON number,单位**元**(如 `986.00`),须 > 0 且不超过订单总金额 |
|
||||
| `email` | string | 始终必填 | 收件邮箱,须通过邮箱格式校验 |
|
||||
| `remark` | string | 选填 | 备注 |
|
||||
|
||||
> ⚠️ `mailAddress` 字段**已删除**,不再接收。
|
||||
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
### apply / reissue 接口出参
|
||||
|
||||
返回结构:`Result<Long>`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `data` | string | 新建/重开发票 ID,字符串雪花 ID |
|
||||
|
||||
### 发票详情出参变化(GET /v3/internal/mp/order/{orderId}/invoice/detail)
|
||||
|
||||
| 字段名 | 变化 | 说明 |
|
||||
|--------|------|------|
|
||||
| `mailAddress` | **已删除** | 原出参中此字段已移除,前端不应再渲染邮寄地址 |
|
||||
| `invoiceType` | 值范围收窄 | 只会出现 `VAT_NORMAL` / `VAT_SPECIAL`,不再出现 `ELECTRONIC` |
|
||||
| `email` | 始终有值 | 原可能为 null,现始终有值 |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### 发票类型(invoiceType)
|
||||
|
||||
| 枚举值 | 中文名 | 适用抬头 | 专票字段要求 |
|
||||
|--------|--------|---------|------------|
|
||||
| `VAT_NORMAL` | 增值税普通发票 | COMPANY / PERSONAL | 专票四项均不需要 |
|
||||
| `VAT_SPECIAL` | 增值税专用发票 | **仅 COMPANY** | taxNo + bankName + bankAccount + registAddress + registPhone 全必填 |
|
||||
|
||||
> ~~`ELECTRONIC`~~ 已删除,不得传入。
|
||||
|
||||
### 抬头类型(titleType)
|
||||
|
||||
| 枚举值 | 中文名 | 可选票种 |
|
||||
|--------|--------|---------|
|
||||
| `COMPANY` | 单位 | VAT_NORMAL / VAT_SPECIAL |
|
||||
| `PERSONAL` | 个人 | 只能 VAT_NORMAL,选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|--------|------|---------|
|
||||
| `581510` | 订单未完成,不可开票 | 订单状态不是 COMPLETED |
|
||||
| `581511` | 一单一票,已有有效发票 | 已有 REQUESTED / ISSUED / PUSHED 状态发票 |
|
||||
| `581512` | 开票金额超订单总额 | `amount` > 订单 `orderAmount` |
|
||||
| `581513` | 发票类型非法 | `invoiceType` 不是 VAT_NORMAL 或 VAT_SPECIAL |
|
||||
| `581514` | 公司抬头或专票须填税号 | 单位抬头或专票时 `taxNo` 为空 |
|
||||
| `581515` | 专票须填银行及注册信息 | 专票时任一四项为空 |
|
||||
| `581516` | 收件邮箱不能为空 | HTTP 400,入参层 @NotBlank 校验;格式错误返回 400「邮箱格式不正确」 |
|
||||
| `581523` | 专票只能开给单位 | `invoiceType=VAT_SPECIAL` 且 `titleType=PERSONAL` |
|
||||
| `401` | 未授权 | 未携带有效 JWT |
|
||||
| `403` | 无权限 / 越权 | 非订单归属用户操作 |
|
||||
|
||||
---
|
||||
|
||||
## 示例
|
||||
|
||||
### 典型成功(增值税普通发票,个人抬头)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000001/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_NORMAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "张三",
|
||||
"taxNo": null,
|
||||
"amount": 986.00,
|
||||
"email": "zhangsan@qq.com",
|
||||
"remark": "旅游报销"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456789"
|
||||
}
|
||||
```
|
||||
|
||||
### 典型成功(增值税专用发票,公司抬头,全字段)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000002/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "COMPANY",
|
||||
"titleName": "某某科技有限公司",
|
||||
"taxNo": "91310000XXXXXXXXXX",
|
||||
"bankName": "招商银行上海支行",
|
||||
"bankAccount": "1234567890123456",
|
||||
"registAddress": "上海市浦东新区XX路XX号",
|
||||
"registPhone": "021-88888888",
|
||||
"amount": 2980.00,
|
||||
"email": "finance@company.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": "1934567890123456790"
|
||||
}
|
||||
```
|
||||
|
||||
### 业务失败(专票个人抬头,错误码 581523)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/internal/mp/order/1920000000000000003/invoice/apply
|
||||
Authorization: Bearer <mp-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"invoiceType": "VAT_SPECIAL",
|
||||
"titleType": "PERSONAL",
|
||||
"titleName": "李四",
|
||||
"taxNo": null,
|
||||
"amount": 500.00,
|
||||
"email": "lisi@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 581523,
|
||||
"msg": "增值税专用发票只能开给单位,个人抬头不可选专票",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**
|
||||
- 客户在小程序「我的订单 - 订单详情」中点「申请发票」,订单状态为 COMPLETED 时可用
|
||||
- 旧发票被作废后,客户在小程序发起重新申请(reissue 接口)
|
||||
|
||||
**不适用场景**
|
||||
- 订单未完成(非 COMPLETED 状态),返回 581510
|
||||
- 已有有效发票,须先由财务作废后才能 reissue,不能再次 apply
|
||||
- 管理后台代客申请走 admin 专属接口
|
||||
|
||||
**特殊边界**
|
||||
- 金额单位:`amount` 为 JSON number,单位**元**(如 `986.00`),前端输入框以元为单位直接提交,无需 x100
|
||||
- 一单一票:同一订单至多一张有效发票
|
||||
- 专票抬头联动:选专票后,抬头类型需强制为单位,个人选项需禁用或隐藏
|
||||
- 详情回显:`mailAddress` 字段已不存在,前端不应渲染邮寄地址块
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
### 发票类型枚举
|
||||
|
||||
| 旧值 | 新值 |
|
||||
|------|------|
|
||||
| VAT_NORMAL(保留) | VAT_NORMAL(保留) |
|
||||
| VAT_SPECIAL(保留) | VAT_SPECIAL(保留) |
|
||||
| ELECTRONIC(电子发票) | **已删除** |
|
||||
|
||||
### email 字段校验
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 条件必填(ELECTRONIC 时才必填) | **始终必填 + 邮箱格式校验** |
|
||||
|
||||
### mailAddress 字段
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| 申请入参可选填;详情/列表出参包含此字段 | **申请入参已删除;详情出参已删除** |
|
||||
|
||||
### 专票限制
|
||||
|
||||
| 旧行为 | 新行为 |
|
||||
|--------|--------|
|
||||
| titleType 无限制 | 专票只能 COMPANY(个人返回 581523) |
|
||||
| bankName 等无强制要求 | 专票 taxNo + 银行四项全部必填(返回 581515) |
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
### 小程序开票页必须改动
|
||||
|
||||
| 模块 | 必须改动 |
|
||||
|------|---------|
|
||||
| 发票类型选择 | 去掉「电子发票」,只保留「增值税普通发票」/「增值税专用发票」 |
|
||||
| 专票表单区块 | 新增开户行、银行账号、注册地址、注册电话 4 个必填项,仅选专票时显示 |
|
||||
| 抬头类型联动 | 选专票时隐藏/禁用「个人」选项 |
|
||||
| email 输入框 | 改为必填(加红星),增加邮箱格式校验 |
|
||||
| mailAddress 输入框 | 移除,不再渲染邮寄地址块 |
|
||||
| 详情页 | 移除邮寄地址展示区,invoiceType 显示只有普票/专票两种文案 |
|
||||
|
||||
### 回滚方案
|
||||
|
||||
后端回滚至 PR #4292 前,ELECTRONIC 重新有效,email 恢复条件必填,mailAddress 重新可用。小程序需同步回滚开票页逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `ELECTRONIC` 已从枚举删除,小程序开票选项只有两项,禁止出现「电子发票」
|
||||
2. 重开接口(reissue)入参字段矩阵与 apply 完全相同,改动同步适用
|
||||
3. 专票五项(taxNo + 开户行 + 账号 + 注册地址 + 注册电话)在专票场景下全必填,缺一返回 581515
|
||||
4. 详情页 `mailAddress` 字段已消失,老版本小程序读到 undefined 需做好空值守卫(不报错)
|
||||
5. `amount` 为 JSON number,单位**元**(如 `986.00`),小程序输入框直接传元值,无需 x100
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| **Issue(功能)** | [#4290 发票申请票种修正](https://git.1814.love:8443/wx/HL/issues/4290) |
|
||||
| **Issue(小程序)** | [#4296 小程序发票申请同步修正](https://git.1814.love:8443/wx/HL/issues/4296) |
|
||||
| **PR(票种删 ELECTRONIC)** | [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) |
|
||||
| **PR(专票限公司/email 必填/删 mailAddress)** | [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) |
|
||||
| **后端负责人** | yst |
|
||||
@@ -0,0 +1,217 @@
|
||||
# 发票申请删 amount · 门槛放宽(小程序端)
|
||||
|
||||
- **接口**:POST /v3/internal/mp/order/{orderId}/invoice/apply
|
||||
- **变更类型**:修改接口,破坏性变更(申请入参删 amount;申请门槛扩展;新增错误码 581524)
|
||||
- **端类型**:小程序端
|
||||
- **日期**:2026-06-25
|
||||
- **Issue**:[#4353](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
|
||||
---
|
||||
|
||||
## 1 接口背景
|
||||
|
||||
小程序端发票申请接口(POST /v3/internal/mp/order/{orderId}/invoice/apply)本次同步两项改动:
|
||||
|
||||
1. **申请入参删 amount**:开票金额不再前端传入,开票时后端按规则自动计算(正常单=应收总额;取消单=净实收)。
|
||||
2. **申请门槛放宽**:原门槛仅允许 COMPLETED(已完成)订单申请;放宽为定制中及以后均可申请,同时支持已取消但净实收 > 0 的订单申请。
|
||||
|
||||
---
|
||||
|
||||
## 2 变更清单
|
||||
|
||||
| # | 变更项 | 变更前 | 变更后 |
|
||||
|---|--------|--------|--------|
|
||||
| 1 | 申请入参 amount | 必填字段,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 2 | 申请门槛(正常订单) | 订单状态必须 COMPLETED | 状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请 |
|
||||
| 3 | 申请门槛(取消订单) | 不允许 | CANCELLED 且净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 4 | 不满足门槛的错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED | ✨ 新增 581524 INVOICE_ORDER_NOT_APPLICABLE(更精确语义) |
|
||||
|
||||
---
|
||||
|
||||
## 3 接口详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|----|
|
||||
| 方法 | POST |
|
||||
| 路径 | /v3/internal/mp/order/{orderId}/invoice/apply |
|
||||
| 描述 | 客户自主申请开票(申请阶段不填金额,开票时后端自动算) |
|
||||
| 认证 | Bearer JWT(小程序用户) |
|
||||
| 幂等性 | 非幂等,重复提交触发 581511(一单一票) |
|
||||
| 限流 | 无特殊限制 |
|
||||
|
||||
---
|
||||
|
||||
## 4 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| orderId | string(Long 雪花) | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段(MpInvoiceApplyReqVO,⚠️ amount 已删)
|
||||
|
||||
当前完整字段列表(无 amount):
|
||||
|
||||
| 字段 | 类型 | 必填条件 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| invoiceType | string | 始终必填 | 发票类型,只有 VAT_NORMAL / VAT_SPECIAL 两值 |
|
||||
| titleType | string | 始终必填 | 抬头类型:COMPANY(单位)/ PERSONAL(个人);VAT_SPECIAL 只能 COMPANY |
|
||||
| titleName | string | 始终必填 | 发票抬头(企业全称或个人姓名) |
|
||||
| taxNo | string | titleType=COMPANY 或 invoiceType=VAT_SPECIAL 时必填 | 纳税人识别号 |
|
||||
| bankName | string | invoiceType=VAT_SPECIAL 时必填 | 开户银行名称 |
|
||||
| bankAccount | string | invoiceType=VAT_SPECIAL 时必填 | 银行账号 |
|
||||
| registAddress | string | invoiceType=VAT_SPECIAL 时必填 | 注册地址 |
|
||||
| registPhone | string | invoiceType=VAT_SPECIAL 时必填 | 注册电话 |
|
||||
| email | string | 始终必填 | 收件邮箱,需通过邮箱格式校验 |
|
||||
| remark | string | 选填 | 申请备注 |
|
||||
|
||||
---
|
||||
|
||||
## 5 出参字段
|
||||
|
||||
响应:成功返回新建发票 ID(字符串,雪花 ID)。
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6 枚举 / 数据字典
|
||||
|
||||
### 6.1 发票类型(invoiceType)
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
|
||||
### 6.2 抬头类型(titleType)
|
||||
|
||||
| code | 说明 | 限制 |
|
||||
|------|------|------|
|
||||
| COMPANY | 单位 | 普票/专票均可 |
|
||||
| PERSONAL | 个人 | 只能选 VAT_NORMAL;选 VAT_SPECIAL 返回 581523 |
|
||||
|
||||
### 6.3 订单状态与可申请关系
|
||||
|
||||
| 订单状态(code) | 中文 | 可申请? | 说明 |
|
||||
|----------------|------|---------|------|
|
||||
| CUSTOMIZING | 定制中 | 是 | 放宽新增 |
|
||||
| PENDING_DEPARTURE | 待出行 | 是 | 放宽新增 |
|
||||
| TRAVELLING | 出行中 | 是 | 放宽新增 |
|
||||
| COMPLETED | 已完成 | 是 | 原有 |
|
||||
| CANCELLED | 已取消 | 条件是 | 净实收(已付 - 已退)> 0 时可申请 |
|
||||
| 其他状态 | - | 否 | 报 581524 |
|
||||
|
||||
### 6.4 开票时金额自动计算规则
|
||||
|
||||
| 订单类型 | 金额计算公式 |
|
||||
|----------|-------------|
|
||||
| 正常单(非 CANCELLED) | 应收总额 = 订单总价 + 增项 - 优惠 |
|
||||
| 取消单(CANCELLED) | 净实收 = 已付金额 - 已退金额 |
|
||||
|
||||
---
|
||||
|
||||
## 7 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头/专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行/银行账号/注册地址/注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 收件邮箱为必填或格式错误(HTTP 400) |
|
||||
| 581518 | INVOICE_FORBIDDEN | 无权访问该订单(IDOR 防护) |
|
||||
| 581523 | INVOICE_VAT_SPECIAL_PERSONAL_FORBIDDEN | 专票只能开给单位 |
|
||||
| 581524 | INVOICE_ORDER_NOT_APPLICABLE | ✨ 新增:订单状态不在可申请集合内,或取消单净实收为 0 |
|
||||
|
||||
---
|
||||
|
||||
## 8 示例
|
||||
|
||||
### 8.1 典型成功——普票申请(无 amount 字段)
|
||||
|
||||
请求
|
||||
|
||||
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.2 边界情况——订单处于定制中提前申请
|
||||
|
||||
说明:订单状态 CUSTOMIZING(定制中),本次放宽后允许申请。开票金额将在财务执行开票操作时按当时应收总额计算。
|
||||
|
||||
请求(结构同 8.1,orderId 为定制中订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
### 8.3 业务失败——取消订单净实收为 0 被拒
|
||||
|
||||
说明:订单已取消,实付 1000 元且已全额退款,净实收 = 0,无法申请开票。
|
||||
|
||||
请求(结构同 8.1,orderId 为该取消订单 ID)
|
||||
|
||||
响应
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9 业务边界
|
||||
|
||||
适用:
|
||||
- 订单状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请
|
||||
- CANCELLED 且净实收(已付 - 已退)> 0 时可申请
|
||||
|
||||
不适用:
|
||||
- PAID(已支付但未进入定制阶段)等其他状态报 581524
|
||||
- CANCELLED 且净实收 = 0 报 581524
|
||||
- 订单不属于当前登录用户,报 581518(IDOR 防护)
|
||||
|
||||
特殊边界:
|
||||
- 一单一票:同一订单只允许一张有效发票(REQUESTED/ISSUED/PUSHED 状态),重复申请报 581511
|
||||
- 开票金额由后端在财务执行 issue 操作时自动计算,小程序端申请时不确定最终金额
|
||||
|
||||
---
|
||||
|
||||
## 10 修改前后对比
|
||||
|
||||
| 字段 / 规则 | 变更前 | 变更后 |
|
||||
|-------------|--------|--------|
|
||||
| 申请入参 amount | 必填,客户填写开票金额 | ⚠️ 已删除,后端开票时自动算 |
|
||||
| 正常订单申请门槛 | 仅 COMPLETED | CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED |
|
||||
| 取消订单申请 | 不允许 | 净实收 > 0 时可申请 |
|
||||
| 不满足门槛错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED(已废弃) | 581524 INVOICE_ORDER_NOT_APPLICABLE(新增) |
|
||||
|
||||
---
|
||||
|
||||
## 11 影响评估 / 回滚
|
||||
|
||||
破坏兼容性:是
|
||||
|
||||
- 小程序申请发票表单必须删除金额输入框,不再传 amount 字段
|
||||
- 若现有流程中有基于 amount 的前端金额展示或校验逻辑,须同步移除
|
||||
- 不满足门槛的错误提示由旧 581510 改为 581524,前端若 hardcode 了 581510 的处理逻辑需同步更新
|
||||
|
||||
前端同步上线:申请表单删 amount 必须与后端同期上线。
|
||||
|
||||
回滚方案:回滚后端至 #4360 前版本,amount 字段恢复必填,申请门槛收回到仅 COMPLETED,581524 不再存在。
|
||||
|
||||
---
|
||||
|
||||
## 12 注意事项
|
||||
|
||||
1. **amount 字段已删**:小程序申请发票表单必须移除金额输入框,不传 amount,传入后端会忽略。
|
||||
2. **申请门槛放宽的注意点**:定制中等状态提前申请,开票时金额由财务执行 issue 操作时后端按当时应收总额计算,小程序无法在申请时预知最终开票金额。
|
||||
3. **581524 vs 旧 581510**:新错误码 581524 取代了旧的 581510,含义更精确(包含取消单净实收为 0 的场景)。若前端有 581510 的 hardcode 判断需更新为 581524。
|
||||
4. **开票金额由财务操作确定**:小程序端只负责申请,金额由后端在财务开票(PUT issue)时写入,前端无法在申请时展示最终金额。
|
||||
|
||||
---
|
||||
|
||||
## 13 关联 / 联系人
|
||||
|
||||
- **Issue**:[#4353 发票模块门槛放宽与开票规则优化](https://git.1814.love:8443/wx/HL/issues/4353)
|
||||
- **PR**:[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,73 @@
|
||||
# 房务原型对齐 — 配房候选项酒店运营标签 + 价格日历底价字段 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:✨ 响应字段新增(候选项 tags)+ 🔧 字段占位预留(价格日历 floorPrice)
|
||||
> 端类型:管理后台(房务管家 · 配房选酒店 / 酒店管理 · 价格日历)
|
||||
> 日期:2026-06-19
|
||||
> 服务:hl-order-service-v3 + hl-resource-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4081 (Closes #4070)、https://git.1814.love:8443/wx/HL/pulls/4078 (Closes #4071)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端,2 处)
|
||||
|
||||
### 1. 配房候选项返回酒店「运营标签」(#4070)
|
||||
|
||||
配房选酒店候选接口 `GET /v3/admin/hotel-candidates` 的每个候选项**新增 `tags` 字段**(`List<String>`,酒店运营标签名列表,如「草原景观」「网红」「性价比高」)。
|
||||
|
||||
- 标签数据来自**现有酒店标签库**——资源管理 · 酒店管理本就有的打标签能力(`/admin/hotel` 系列标签接口),不是新机制。
|
||||
- 前端在配房候选项渲染标签 chip;**无标签的酒店返空数组 `[]`(不返 null)**。
|
||||
- 给酒店打/改标签仍走现有 `PUT /admin/hotel/item/{hotelId}/tags`、`/admin/hotel/tags` 等接口;本次只是把酒店**已打的标签**透传到配房候选项展示。
|
||||
|
||||
### 2. 价格日历新增「底价」字段(#4071)
|
||||
|
||||
房型价格日历 `GET / PUT /admin/hotel/room-type/{roomTypeId}/prices` 每天**新增 `floorPrice`(底价)字段**(字符串金额,如 `"260.00"`)。
|
||||
|
||||
- **占位预留,当前不接任何业务逻辑**——可读可写可存,但暂不参与任何计算/校验。
|
||||
- 前端**当前不用**(后续真正接入时再单独通知);GET 默认返 `null`,PUT 可不传。
|
||||
|
||||
---
|
||||
|
||||
## 1. 配房候选项 tags(#4070,响应结构新增)
|
||||
|
||||
**`GET /v3/admin/hotel-candidates`**(路径/入参不变,仅响应每个候选项新增字段)
|
||||
|
||||
| 新增字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| tags | List<String> | 酒店运营标签名列表(来自酒店标签库;无标签返 `[]`) |
|
||||
|
||||
候选项其余字段(protoPrice 协议价、isPoolMatch 池内标记、todayAvailable 可用房等)不变。
|
||||
|
||||
---
|
||||
|
||||
## 2. 价格日历 floorPrice(#4071,字段占位)
|
||||
|
||||
**`GET / PUT /admin/hotel/room-type/{roomTypeId}/prices`**(路径/结构不变,每天日历项新增字段)
|
||||
|
||||
| 新增字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| floorPrice | String(金额,ToString 序列化) | 底价(占位预留,暂不用);GET 返出、PUT 可填入库 |
|
||||
|
||||
```bash
|
||||
# GET 月度价格日历(每天含 floorPrice,默认 null)
|
||||
curl -k "https://api.test.1814.love:9443/admin/hotel/room-type/{roomTypeId}/prices?year=2026&month=6" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
响应片段(已测试服实测):
|
||||
|
||||
```json
|
||||
{ "date": "2026-06-15", "protocolPrice": "320.00", "floorPrice": "260.00",
|
||||
"status": "AVAILABLE", "stock": 5, "stockUsed": 0, "remark": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
- **#4071** 已在测试服 9443 实测:GET 每天返 `floorPrice` 字段(默认 null)、PUT 写 `260.00` 回读为字符串 `"260.00"`(金额 String 序列化正确)、DELETE 还原。Flyway `V20260619_002` 已应用。
|
||||
- **#4070** 候选端点已部署可达;`tags` 透传由单测覆盖(resource 37 / order-v3 候选 40 全绿)+ 同一取标签方法 `getTagsByHotelIds` 在 admin 酒店列表实证返真标签。
|
||||
|
||||
## 备注
|
||||
|
||||
- 网关无需改(`/v3/admin/hotel-candidates`、`/admin/hotel/**` 路由已覆盖)。
|
||||
- via 房务管家 v2.0 原型对齐 batch(预付款先不做;月度对账/酒店富表单/配房改价 另立工单 #4072-#4074)。
|
||||
@@ -0,0 +1,61 @@
|
||||
# 【接口下线·管理后台+小程序】资源「车型管理」整套下线 + 产品编排不再绑车型
|
||||
|
||||
> 服务:hl-resource-service / hl-product-service-v2 / hl-order-service-v3 / hl-order-service-v2 / hl-user-service / hl-gateway | 分支:dev-v3 | PR:#4083 | 工单:#4062
|
||||
> 已部署测试服 6 服务双实例 health UP,9443 真 token + DB 地面真相实测通过(2026-06-20)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **车型统一归车务(fleet)**:创建产品不再关联资源车型,资源管理下的「车型管理」整套**物理删除**(代码 + 数据表 + 接口)。前端需移除相关入口/字段。
|
||||
2. **影响面**:管理后台(车型管理菜单 + 产品编排绑车型)、小程序(产品详情用车展示)、物流仪表盘(车型/车辆统计)三处。
|
||||
3. **用车不受影响**:订单实际用车一直是 fleet 订单派单(配房/配车在 HOUSE 域),本次只是砍掉「产品维度的车型目录」这条已不展示的链路。
|
||||
|
||||
## 1. 接口下线(管理后台)
|
||||
|
||||
资源管理「车型管理」页面对应的全部接口已删除,调用一律返 `code:404「接口不存在」`:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET/POST/PUT/DELETE | `/admin/vehicle/model`、`/admin/vehicle/models`、`/admin/vehicle/models/all`、`/admin/vehicle/model/{id}` 等全部 | 车型 CRUD/审批/状态 |
|
||||
| (内部) | `/internal/vehicle/**`、`/internal/resource/vehicle/stats` | 车型 Feign / 车型统计 |
|
||||
|
||||
```bash
|
||||
# 实测:车型管理接口已 404
|
||||
curl -k -H "Authorization: Bearer <token>" "https://api.test.1814.love:9443/admin/vehicle/models"
|
||||
# → {"code":404,"message":"接口不存在: /admin/vehicle/models","success":false}
|
||||
```
|
||||
|
||||
前端动作:**移除「资源管理 > 车型管理」整个菜单与页面**。
|
||||
|
||||
## 2. 产品编排去车型(管理后台)
|
||||
|
||||
产品编排不再有「绑定车型」入口,以下字段从接口契约移除:
|
||||
|
||||
| VO / 接口 | 移除字段 |
|
||||
|---|---|
|
||||
| `ProductSupplementSaveReqVO`(编排补充配置保存) | `vehicleModelIds`(展示车型) |
|
||||
| 产品详情 `ProductDetailRespVO` | `routeInfo.vehicleModelId`(成本车型)、`supplement.vehicleModelIds` |
|
||||
| 小程序产品详情 `MpProductDetailInternalVO.PricingItem` | `vehicleModelIds` |
|
||||
|
||||
前端动作:**产品编排表单移除「车型」选择控件**;详情/小程序不再读这些字段。注:`supplement.vehicleRuleText`(用车规则说明文案)**保留不变**。
|
||||
|
||||
## 3. 小程序产品详情「用车」展示
|
||||
|
||||
产品详情快照不再下发车型规格列表(`vehicleModels`,原聚合产品绑定车型的名称/座位/分类)。产品维度的「用车车型」展示口径取消,用车信息以订单侧 fleet 派单为准。
|
||||
|
||||
前端动作:小程序产品详情页若有「用车车型」展示块,按字段缺失/隐藏处理。
|
||||
|
||||
## 4. 物流仪表盘去车型/车辆统计
|
||||
|
||||
`LogisticsDashboardVO` 移除两字段:`totalVehicles`(车辆总数)、`totalVehicleModels`(车型总数)。仪表盘其余项(待配车 `pendingArrangeVehicle`、即将出行 `upcomingTrips`)不变。
|
||||
|
||||
## 5. 行为变更(后端,前端知悉即可)
|
||||
|
||||
- 新建订单 `needsVehicle` 默认 `false`(原由产品绑定车型派生);配车需求由 HOUSE/requirement 域运营驱动,不再由产品自动派生。fleet 配车/派单/结算/用车需求全套不变。
|
||||
- order-v2(一期)订单详情用车弹窗规格 + 房车升级差价:车型成本不再可得,优雅降级(差价含车型部分恒 0,规格显示占位)。
|
||||
|
||||
## 6. 数据库变更(已在测试服执行)
|
||||
|
||||
| 库 | 变更 |
|
||||
|---|---|
|
||||
| hl_resource_service | DROP 表 `vehicle_model` / `vehicle_tag` / `vehicle_tag_rel` / `vehicle_price_calendar` |
|
||||
| hl_product_service | DROP 列 `product_route_info.vehicle_model_id`、`product_supplement.vehicle_model_ids` |
|
||||
@@ -0,0 +1,74 @@
|
||||
# 房务原型对齐(二)— 配房改协议价同步 + 酒店富表单/OCR + 月度对账 — 管理后台
|
||||
|
||||
> 变更类型:✨ 新功能(月度对账/OCR)+ 字段新增(协议价/酒店富字段)
|
||||
> 端类型:管理后台(房务管家 · 配房/对账 / 资源管理 · 酒店)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3 + hl-resource-service
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4098 (Closes #4072)、https://git.1814.love:8443/wx/HL/pulls/4099 (Closes #4073)、https://git.1814.love:8443/wx/HL/pulls/4100 (Closes #4074)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端,3 处)
|
||||
|
||||
### 1. 配房编辑可改协议价(#4072)
|
||||
|
||||
房务配房编辑 **`PUT /v3/admin/order/assignments/{id}`** 新增可选入参 **`protoPrice`(协议价)**。房务改协议价后,后端 afterCommit 自动同步写回 resource 价格日历(协议价单源真相)。
|
||||
|
||||
- 前端配房编辑表单可加「协议价」输入项(与 sellPrice 售价并列,均可选传)。
|
||||
- **定制师改结算价不在此**——那个在「确认订单」环节,另行处理。
|
||||
|
||||
### 2. 酒店档案富表单字段 + 营业执照 OCR(#4073)
|
||||
|
||||
资源管理 · 酒店新增/编辑(`/admin/hotel/item`)出入参新增一批字段,配合原型 4-5 步引导表单:
|
||||
|
||||
| 分组 | 字段 | 类型 |
|
||||
|---|---|---|
|
||||
| 营业执照资质 | licenseUnitName 单位名称 / socialCreditCode 信用代码 / legalPerson 法人 / registeredCapital 注册资本(字符串含单位) / registeredAddress 注册地址 / establishDate 成立日期 / businessTermEnd 营业期限到期日 / businessTermLongValid 长期有效 / businessScope 经营范围 / qualificationRemindDays 资质到期提前提醒天数(默认30) / qualificationRemindChannels 提醒渠道(逗号串) / licenseMaterialId 营业执照图片 | 扁平列 |
|
||||
| 接待政策 | receptionPolicies | **JSON 数组** |
|
||||
| 设施富子字段 | facilityDetails | **JSON 数组**(现有 facilities 逗号串保留) |
|
||||
|
||||
JSON 结构示例:
|
||||
|
||||
```json
|
||||
"receptionPolicies": [
|
||||
{"policyType":"pet","value":"need_confirm","deposit":200.00,"remark":"仅限小型犬"},
|
||||
{"policyType":"extra_bed","value":"allowed","fee":100.00}
|
||||
],
|
||||
"facilityDetails": [
|
||||
{"facilityType":"breakfast","openTime":"07:00","closeTime":"10:00","fee":68.00,"breakfastType":"自助餐"},
|
||||
{"facilityType":"parking","parkingSpaces":50,"free":false,"fee":15.00}
|
||||
]
|
||||
```
|
||||
|
||||
**营业执照 OCR**:**`POST /admin/hotel/ocr/business-license`**(multipart 上传营业执照图片 → 识别返回单位名称/信用代码/法人/注册资本/注册地址/成立日期/营业期限/经营范围,供前端回填)。
|
||||
|
||||
> ⚠️ OCR 依赖测试服 Nacos 配 `aliyun.ocr.enabled=true` + AK/SK 才真生效(与车务/出行人 OCR 同源),未配返「服务未就绪」310416;字段录入不受影响。
|
||||
|
||||
### 3. 房务月度对账 + Excel 导出(#4074)
|
||||
|
||||
新增 **`GET /v3/admin/house/reconciliation/monthly?month=YYYY-MM`**(现结酒店账单按 酒店×月 聚合 + 概览)+ **`GET .../monthly/export?month=YYYY-MM`**(Excel 下载)。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| overview | 概览:hotelCount/orderCount/roomNights/totalAmount 现结合计/paidAmount 已付/unpaidAmount 待付 |
|
||||
| hotels[] | 每酒店:hotelId/hotelName/orderCount 订单数/roomNights 间夜/totalAmount 应付/avgPrice 均价/paidAmount 已付/unpaidAmount 待付 |
|
||||
|
||||
- 仅统计**现结**(settlement paymentMethod=CASH_PAID);待付/已付按订单 settlement_status 判。
|
||||
- 预付余额核销因「预付款先不做」暂缓。空月返空列表 + 全 0 概览。
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/v3/admin/house/reconciliation/monthly?month=2026-05" -H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试(测试服 9443 实测)
|
||||
|
||||
- **#4072**:配房编辑端点已部署(dummy id 返 808120),protoPrice 入参被接受;协议价写回 resource 内部链路单测覆盖(HouseAssignmentServiceTest 37 绿)。
|
||||
- **#4073**:酒店详情 9 个新字段全返出(Flyway V20260620_001 已应用,qualificationRemindDays 默认 30);OCR 端点已部署(multipart 契约)。
|
||||
- **#4074**:GET monthly 返 200 + 正确空月聚合(totalAmount 字符串序列化);Excel 导出返真 .xlsx(PK 魔数 3898 字节);month 格式错返 808170。
|
||||
|
||||
## 备注
|
||||
|
||||
- 网关无需改(`/v3/admin/**`、`/admin/hotel/**` 路由已覆盖)。
|
||||
- via 房务管家 v2.0 原型对齐 batch(预付款先不做)。
|
||||
@@ -0,0 +1,339 @@
|
||||
# 早鸟优惠计划管理 CRUD 接口(管理后台)
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **变更类型**:新增接口
|
||||
- **日期**:2026-06-20
|
||||
- **PR**:[#4076](https://git.1814.love:8443/wx/HL/pulls/4076)
|
||||
- **负责人**:yst(腰苏图)
|
||||
|
||||
---
|
||||
|
||||
## 接口背景
|
||||
|
||||
早鸟优惠(Early Bird Discount)是订单 v3 新增的优惠机制,允许管理员为指定产品配置日期区间 x 人数区间 x 折扣方式三维优惠计划。下单时系统自动匹配最优方案并应用优惠。
|
||||
|
||||
本次(PR #4076)新增管理后台侧 CRUD 6 个接口,供管理员对早鸟计划进行增删改查、启停操作。
|
||||
|
||||
---
|
||||
|
||||
## 变更清单
|
||||
|
||||
| # | 方法 | 路径 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | GET | /v3/admin/order/early-bird/list | 早鸟计划分页列表 |
|
||||
| 2 | GET | /v3/admin/order/early-bird/{planId} | 早鸟计划详情 |
|
||||
| 3 | POST | /v3/admin/order/early-bird | 创建早鸟计划 |
|
||||
| 4 | PUT | /v3/admin/order/early-bird/{planId} | 修改早鸟计划(不影响已下单订单) |
|
||||
| 5 | DELETE | /v3/admin/order/early-bird/{planId} | 删除早鸟计划 |
|
||||
| 6 | PUT | /v3/admin/order/early-bird/{planId}/toggle | 启用 / 禁用早鸟计划 |
|
||||
|
||||
全部为新增接口,无前版存量接口被替换。
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **认证** | 需要管理后台 JWT Token(Authorization: Bearer token) |
|
||||
| **Base URL** | /v3/admin/order/early-bird |
|
||||
| **幂等性** | POST 创建非幂等(同参数可重复创建多个计划);PUT 修改按 planId 幂等覆盖 |
|
||||
| **限流** | 无独立限流规则,走全局网关限流 |
|
||||
| **内容类型** | 请求体 Content-Type: application/json;GET 请求均为 Query 参数 |
|
||||
|
||||
---
|
||||
|
||||
## 接口入参
|
||||
|
||||
### 4.1 GET /v3/admin/order/early-bird/list — 分页列表
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| pageNo | Integer | 是 | 页码,从 1 开始 |
|
||||
| pageSize | Integer | 是 | 每页条数,建议 10 / 20 |
|
||||
|
||||
### 4.2 GET /v3/admin/order/early-bird/{planId} — 详情
|
||||
|
||||
路径参数 planId(Long):早鸟计划 ID。
|
||||
|
||||
### 4.3 POST /v3/admin/order/early-bird — 创建
|
||||
|
||||
请求体(EarlyBirdPlanSaveReqVO):
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 | 说明 |
|
||||
|------|------|------|----------|------|
|
||||
| planName | String | 是 | NotBlank | 计划名称 |
|
||||
| remark | String | 否 | 无 | 优惠描述 |
|
||||
| discountType | String | 否 | 枚举三选一 | 折扣方式,默认 AMOUNT_TOTAL(见枚举节) |
|
||||
| discountAmount | BigDecimal | 条件必填 | AMOUNT_PER_PERSON 或 AMOUNT_TOTAL 时必填 | 优惠金额(元) |
|
||||
| discountPercent | BigDecimal | 条件必填 | PERCENT 时必填,值区间 (0, 100) 开区间 | 折扣率,80=8折(非立减百分比) |
|
||||
| priority | Integer | 否 | >=0,默认 0 | 优先级,同区间多命中时大者优先 |
|
||||
| minPeople | Integer | 是 | >=1 | 最低适用人数(含) |
|
||||
| maxPeople | Integer | 否 | >=1 且 >=minPeople | 最高适用人数(含),不填=不限 |
|
||||
| startDate | LocalDate | 是 | yyyy-MM-dd | 生效开始日期 |
|
||||
| endDate | LocalDate | 是 | yyyy-MM-dd,需 >=startDate | 生效结束日期 |
|
||||
| productIds | List<Long> | 否 | 无 | 关联产品 ID 列表,空=不限产品 |
|
||||
| applicableTravelerTypes | List<String> | 否 | 枚举值见下 | 适用人群 code 列表,空=后端自动填充成人+儿童+小童(排婴儿) |
|
||||
|
||||
### 4.4 PUT /v3/admin/order/early-bird/{planId} — 修改
|
||||
|
||||
路径参数 planId(Long)。请求体与 4.3 完全一致,全字段覆盖更新。修改不影响已下单订单中已匹配的早鸟优惠。
|
||||
|
||||
### 4.5 DELETE /v3/admin/order/early-bird/{planId} — 删除
|
||||
|
||||
路径参数 planId(Long)。无请求体。
|
||||
|
||||
### 4.6 PUT /v3/admin/order/early-bird/{planId}/toggle — 启用 / 禁用
|
||||
|
||||
| 参数 | 类型 | 位置 | 说明 |
|
||||
|------|------|------|------|
|
||||
| planId | Long | Path | 早鸟计划 ID |
|
||||
| enabled | Boolean | Query | true=启用;false=禁用 |
|
||||
|
||||
禁用后该计划不再参与下单时自动匹配。
|
||||
---
|
||||
|
||||
## 出参字段
|
||||
|
||||
所有接口返回 Result<T> 包装,成功 code=200。列表接口返回 Result<PageResult<EarlyBirdPlanVO>>,创建/详情返回 Result<EarlyBirdPlanVO>,修改/删除/toggle 返回 Result<Void>。
|
||||
|
||||
### EarlyBirdPlanVO 字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| planId | Long(字符串序列化) | 计划 ID,雪花 ID,前端用字符串接收防精度丢失 |
|
||||
| planName | String | 计划名称 |
|
||||
| remark | String | 优惠描述,可为 null |
|
||||
| discountType | String | 折扣方式枚举 code |
|
||||
| discountAmount | BigDecimal | 优惠金额,PERCENT 类型时为 null |
|
||||
| discountPercent | BigDecimal | 折扣率(0~100),非 PERCENT 时为 null;80=8折 |
|
||||
| priority | Integer | 优先级 |
|
||||
| minPeople | Integer | 最低适用人数(含) |
|
||||
| maxPeople | Integer | 最高适用人数(含),null=不限 |
|
||||
| peopleRangeText | String | 后端拼好的人数区间文案,如 3-5 人 / 3 人及以上 |
|
||||
| discountText | String | 后端拼好的折扣说明,如 每人立减 100 元 / 整单立减 500 元 / 8 折优惠 |
|
||||
| startDate | LocalDate(yyyy-MM-dd) | 生效开始日期 |
|
||||
| endDate | LocalDate(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}> | 关联产品结构化列表 |
|
||||
| createTime | LocalDateTime | 创建时间 |
|
||||
| updateTime | LocalDateTime | 最后更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 枚举 / 数据字典
|
||||
|
||||
### discountType — 折扣方式
|
||||
|
||||
| code | 中文 | 计算说明 |
|
||||
|------|------|----------|
|
||||
| AMOUNT_PER_PERSON | 每人立减 | 优惠金额 = discountAmount x 计费人数 |
|
||||
| AMOUNT_TOTAL | 整单立减(默认) | 优惠金额 = discountAmount |
|
||||
| PERCENT | 按比例打折 | 优惠金额 = 总价 x (100 - discountPercent) / 100 |
|
||||
|
||||
discountPercent 语义:值为折扣率,80 表示 8折,不是打 8 折的优惠百分比。立减额 = 总价 x (100 - 80) / 100 = 总价 x 0.2。
|
||||
|
||||
### applicableTravelerTypes — 适用人群
|
||||
|
||||
| code | 中文 | 人数匹配规则 |
|
||||
|------|------|------------|
|
||||
| ADULT | 成人 | 计入人数 |
|
||||
| CHILD | 儿童 | 计入人数 |
|
||||
| YOUNG_CHILD | 小童 | 计入人数 |
|
||||
| BABY | 婴儿 | 默认不计入人数,需显式配置才计入 |
|
||||
|
||||
人数匹配口径:命中方案的 applicableTravelerTypes 中包含哪几类,就统计哪几类的人数判断是否满足 minPeople/maxPeople 区间。默认配置排除婴儿。
|
||||
|
||||
---
|
||||
|
||||
## 错误码
|
||||
|
||||
早鸟模块错误码段位:581600 – 581699
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581600 | 早鸟计划不存在 | planId 查不到对应记录 |
|
||||
| 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.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",
|
||||
"remark": "7月出发整单立减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-20T10:00:00",
|
||||
"updateTime": "2026-06-20T10:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
### 8.2 边界情况 — 折扣率类计划(不限人数、不限产品)
|
||||
|
||||
maxPeople 不传=不限;productIds 不传=不限产品;applicableTravelerTypes 不传=后端回填默认值。
|
||||
|
||||
请求体:
|
||||
|
||||
{
|
||||
"planName": "年底全产品9折",
|
||||
"discountType": "PERCENT",
|
||||
"discountPercent": 90,
|
||||
"minPeople": 1,
|
||||
"startDate": "2026-12-01",
|
||||
"endDate": "2026-12-31"
|
||||
}
|
||||
|
||||
响应:
|
||||
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"planId": "1920000000001235",
|
||||
"planName": "年底全产品9折",
|
||||
"remark": null,
|
||||
"discountType": "PERCENT",
|
||||
"discountAmount": null,
|
||||
"discountPercent": 90.00,
|
||||
"priority": 0,
|
||||
"minPeople": 1,
|
||||
"maxPeople": null,
|
||||
"peopleRangeText": "1 人及以上",
|
||||
"discountText": "9 折优惠",
|
||||
"startDate": "2026-12-01",
|
||||
"endDate": "2026-12-31",
|
||||
"enabled": true,
|
||||
"applicableTravelerTypes": ["ADULT", "CHILD", "YOUNG_CHILD"],
|
||||
"applicableTravelerTypesText": "成人/儿童/小童",
|
||||
"productIds": [],
|
||||
"productNames": [],
|
||||
"products": [],
|
||||
"createTime": "2026-06-20T11:00:00",
|
||||
"updateTime": "2026-06-20T11:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
### 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": "产品已存在生效时间与人数区间重叠的早鸟计划,不能重复创建或启用",
|
||||
"data": null
|
||||
}
|
||||
---
|
||||
|
||||
## 业务边界
|
||||
|
||||
**适用场景**:管理员新建/修改/删除早鸟优惠配置;对已有计划快速启停;查询当前所有早鸟计划状态。
|
||||
|
||||
**不适用场景**:小程序用户侧查看早鸟优惠(走 internal Feign 接口);下单时自动匹配优惠(订单服务内部逻辑,前端无需干预);修改不追溯已下单订单中已应用的优惠金额。
|
||||
|
||||
**特殊边界**:
|
||||
|
||||
- 同一产品在同一时间段+人数区间内只允许存在一个启用状态的计划(581610);禁用状态不参与冲突检测
|
||||
- discountPercent=80 表示 8折,不是优惠 80%;前端展示需转换为 X折格式
|
||||
- planId 为雪花 ID,JSON 已序列化为字符串,前端用字符串类型接收
|
||||
- applicableTravelerTypes 传空或不传,后端自动填充默认值 [ADULT, CHILD, YOUNG_CHILD] 并持久化
|
||||
|
||||
---
|
||||
|
||||
## 修改前后对比
|
||||
|
||||
新增接口,无前版对比。
|
||||
|
||||
---
|
||||
|
||||
## 影响评估 / 回滚
|
||||
|
||||
新增接口,不影响存量功能,无需回滚策略。如需下线,在网关层屏蔽 /v3/admin/order/early-bird 前缀即可。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. discountPercent 语义陷阱:80=8折,不是立减 80%。前端展示请转换为 X折,勿直接显示 80%。
|
||||
2. planId 雪花 ID 序列化:响应中已序列化为字符串;调用修改/删除/toggle 时路径参数直接传字符串即可。
|
||||
3. applicableTravelerTypes 回填:创建时不传或传空,后端自动填充默认值并持久化;详情/列表返回实际存储值(不再是空数组)。
|
||||
4. toggle 启用触发重叠校验(581610):将已禁用计划重新启用时,若同时间+人数区间已有其他启用计划,会拒绝启用。
|
||||
5. PUT 修改是全字段覆盖,不支持 PATCH 语义,未传的选填字段会被重置为 null/默认值。
|
||||
6. productNames/products 降级:关联产品已删除时,productNames 对应位置返回 产品[ID]已删除,前端按需降级处理。
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| PR | [#4076 早鸟优惠管理后台 CRUD](https://git.1814.love:8443/wx/HL/pulls/4076) |
|
||||
| 后端负责人 | yst(腰苏图) |
|
||||
| 变更服务 | hl-order-service-v3 |
|
||||
@@ -0,0 +1,41 @@
|
||||
# 【行为变更·管理后台】早鸟优惠——同一产品禁止两个"会对同一订单同时生效"的早鸟计划(人数阶梯保留)
|
||||
|
||||
> 服务:hl-order-service-v2(8094) + hl-order-service-v3(8086) | 分支:dev-v3 | PR:#4097 | 工单:#4094
|
||||
> 已部署测试服并经网关 9443 真 admin token 端到端实测通过(2026-06-20):冲突 create 返 585010;人数不重叠的阶梯档 create 正常 200。
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **早鸟新增"防同订单冲突"校验**:一个产品仍可挂多个早鸟计划当**人数阶梯**(满5减100/满10减300,下单按人数命中最优一档,此能力**保留不变**)。新增的限制是:**禁止同一产品出现两个"会对同一笔订单同时生效"的早鸟**——即两个计划都启用、且**生效日期窗重叠、且人数区间重叠**时拒绝。
|
||||
2. **触发入口**:新建(create)、编辑(update,本计划处启用态时)、启用(toggle 启用)三处。命中冲突时**保存/启用失败**,返回业务码:
|
||||
- order-v2(线上购买/报价端):**`code=585010`**「产品(ID=X)已存在生效时间与人数区间重叠的早鸟计划(planId=Y),不能重复创建或启用」
|
||||
- order-v3:**`code=581610`**(同义消息)
|
||||
3. **前端需留意**:早鸟计划配置页保存/启用**不再恒成功**,命中冲突会返上面的业务码(HTTP 仍 200,`success=false`)。请按 `success===true` 判定成功,失败时把后端 `message` 直接 toast 出来(消息已是可读中文,无需前端拼文案)。
|
||||
4. **不算冲突、照旧放过的情况**:① 人数区间不重叠(如满1-4 与 满5-9,这就是阶梯);② 生效日期窗不重叠(不同档期);③ 对方计划已禁用;④ 编辑时与自己不算冲突。
|
||||
|
||||
## 1. 各场景行为对照
|
||||
|
||||
| 场景(同一产品) | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 新建早鸟,产品已有启用且日期+人数都重叠的计划 | 允许,运行时被动按 priority 选一个(有按错价风险) | **拒绝**,返 585010/581610 |
|
||||
| 新建早鸟,人数区间不重叠(阶梯档) | 允许 | 允许(阶梯能力保留) |
|
||||
| 新建早鸟,日期窗不重叠(不同档期) | 允许 | 允许 |
|
||||
| 编辑/启用计划致与他计划"同订单同时生效" | 允许 | **拒绝** |
|
||||
| 禁用计划 | 允许 | 允许(不校验,禁用只会减少生效计划) |
|
||||
|
||||
## 2. 端到端实测记录(测试服,网关 9443 真 admin token)
|
||||
|
||||
```text
|
||||
TEST1 冲突:产品 2056938821550747650 已有启用计划(min1+/2026-05-01~06-30)
|
||||
POST /admin/order/early-bird {minPeople:2,maxPeople:10,startDate:2026-06-01,endDate:2026-06-15,productIds:[...]}
|
||||
→ code=585010 success=false ✅ 按预期拒绝
|
||||
TEST2 阶梯:产品 2047268852147875842 已有启用计划(min6+)
|
||||
POST /admin/order/early-bird {minPeople:1,maxPeople:5,startDate:2026-07-01,endDate:2026-07-31,productIds:[...]}
|
||||
→ code=200 success=true ✅ 人数不重叠的阶梯档照旧放过(实测后已删除临时数据)
|
||||
```
|
||||
|
||||
## 3. 后端内部变更(备查)
|
||||
|
||||
1. `EarlyBirdPlanService`(v2/v3)新增 `validateNoActiveConflict`,在 create/update/toggle 三入口接线。冲突判定 = 同产品(排除自身)的另一启用计划,日期窗重叠 且 人数区间重叠(minPeople 空按 1、maxPeople 空按无上限)。
|
||||
2. `EarlyBirdPlanMapper`(v2/v3)新增 `selectEnabledByIds`(LambdaQueryWrapperX)。
|
||||
3. 新错误码:v2 `OrderEarlyBirdErrorCode.PRODUCT_HAS_CONFLICTING_PLAN=585010`、v3 `EarlyBirdErrorCode.PRODUCT_HAS_CONFLICTING_PLAN=581610`。
|
||||
4. 未动数据库/匹配算法(`doFindBestMatch`)/网关路由;纯配置端 Java 校验。
|
||||
@@ -0,0 +1,46 @@
|
||||
# 房务列表补 consultantId(站内信接入房务·列表卡片开会话)— 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:✨ 响应字段新增(房务列表 VO 加 consultantId)
|
||||
> 端类型:管理后台(房务管家 · 我的接单 / 抢单池 → 聊天)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4102 (Closes #4101)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端)
|
||||
|
||||
站内信(聊天)接入房务收尾。后端聊天接入已就位(`open` 支持 bizModule=HOUSE、未读红点、会话列表 enrich、配不出走聊天),本次补**房务列表卡片直接开「与定制师会话」**所需的 `consultantId`。
|
||||
|
||||
- **我的接单** `GET /v3/admin/order/grab-pool/my-claims/hotel`、**抢单池** `GET /v3/admin/order/grab-pool/hotel-requirements` 的列表项**新增 `consultantId`**(定制师 adminId,字符串雪花)。
|
||||
- 之前列表只返 `consultantName`,开会话需要的 `consultantId` 只能进详情才拿得到;现列表卡片即可拿。
|
||||
- 前端从列表卡片开会话:
|
||||
|
||||
```
|
||||
POST /admin/message/chat/open
|
||||
{ "bizModule": "HOUSE", "bizId": <orderId>, "peerAdminId": <consultantId> }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 字段
|
||||
|
||||
| 端点 | 新增字段 | 类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `/v3/admin/order/grab-pool/my-claims/hotel`(我的接单)| consultantId | String(雪花)| 定制师 adminId(聊天对端 peerAdminId)|
|
||||
| `/v3/admin/order/grab-pool/hotel-requirements`(抢单池)| consultantId | String(雪花)| 同上 |
|
||||
|
||||
取自 `order_main.consultant_id`(与既有 `consultantName` 同源同行);列表其余字段不变。
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
- 已部署测试服 order-v3 双实例 success;9443 实测两端点 200。
|
||||
- `consultantId` 透传由单测覆盖(HouseGrab*Test 47 绿,含 consultantId 断言)+ 与既有 consultantName 同一 JOIN 投影同源。
|
||||
- 注:测试环境当前抢单池/我的接单为空,未在真实行上观测到 consultantId 取值;字段为纯增、同源 consultantName 已生产工作。
|
||||
|
||||
## 备注
|
||||
|
||||
- 网关无需改(`/v3/admin/**` 路由已覆盖)。
|
||||
- via 站内信接入房务。
|
||||
@@ -0,0 +1,80 @@
|
||||
# 提房需求「每家酒店备注 + 每晚备注 + 分住明细」完整转发房务详情 — 修改接口 — 管理后台
|
||||
|
||||
> 变更类型:✨ 请求字段新增(每家酒店备注)+ 响应字段新增(房务详情转发结构化数据)
|
||||
> 端类型:管理后台(定制师 · 调整订单/提房需求 → 房务 · 订单详情)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4116
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端)
|
||||
|
||||
定制师「调整订单 / 提房需求」填的结构化数据,要在房务抢单后的订单详情里**完整看到**。本次补齐:每家选的酒店可写备注、每晚可写备注,连同同晚分住的多家酒店全部透传房务。
|
||||
|
||||
### 1. 提房需求表单(定制师端,写入)
|
||||
|
||||
`PUT /v3/admin/order/{id}/hotel-requirement`,body 里:
|
||||
|
||||
| 备注类型 | 字段 | 状态 |
|
||||
|---|---|---|
|
||||
| **整单自定义备注** | 顶层 `remark`(≤500) | **早已存在**,前端把那个自定义备注输入框接到它即可(后端无改动) |
|
||||
| **每晚备注** | `days[].remark`(≤200) | 早已存在 |
|
||||
| **每家酒店备注** | `days[].hotels[].remark`(≤200) | **本次新增**,每家选的酒店可单独写备注(如「靠湖一侧/含双早/可加床」) |
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
PUT /v3/admin/order/60001/hotel-requirement
|
||||
{
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"remark": "当晚整体安排说明",
|
||||
"hotels": [
|
||||
{"hotelId": 80001, "hotelName": "湖景酒店", "roomCategory": "KING", "roomCount": 2, "budget": 900.00, "remark": "靠湖一侧"},
|
||||
{"hotelId": 80002, "hotelName": "山景酒店", "roomCategory": "TWIN", "roomCount": 1, "remark": "含双早(同一晚分住)"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"specialTags": ["高楼层", "景观房"],
|
||||
"remark": "整单自定义备注:客人怕吵"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 房务订单详情(房务端,读取 → 抢单后能看到全部)
|
||||
|
||||
`GET /admin/house/orders/{orderId}` 的 `requirement.current`(即 `RequirementSnapshot`):
|
||||
|
||||
| 字段 | 说明 | 状态 |
|
||||
|---|---|---|
|
||||
| `consultantRemark` | **整单自定义备注**(= 提房的顶层 `remark`) | 早已转发 |
|
||||
| `specialTags` / `specialTagLabels` | 特殊诉求标签 | 早已转发 |
|
||||
| `days[].remark` | **当晚备注** | **本次新增转发** |
|
||||
| `days[].hotels[]` | **当晚定制师选的全部酒店明细**(含同晚分住多家,不再只给 1 家) | **本次新增** |
|
||||
|
||||
`days[].hotels[]` 每项(`HotelChoice`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| hotelId | String(雪花) | 定制师指定酒店 ID |
|
||||
| hotelName | String | 酒店名快照 |
|
||||
| roomCategory | String | 房型字典 code |
|
||||
| roomCount | Integer | 房间数 |
|
||||
| budget | String/null | 预算/晚 |
|
||||
| remark | String | **每家酒店备注** |
|
||||
|
||||
> 兼容:`days[]` 原有的扁平字段(`roomCategory/roomCount/budget/preferredHotelId`,取自当晚第 1 家)**保持不变**,老前端不受影响;本次只是**新增** `days[].remark` 与 `days[].hotels[]` 两项,房务详情其余结构零变化。
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
- `HouseDetailAggregatorTest` 21/21 绿(新增 1 例:同晚分住 2 家 + 每家 hotelName/remark + 当晚 remark 完整转发,含既有扁平字段防回归断言);`RequirementServiceTest` 99/0(VO 加字段无序列化回归)。
|
||||
- 已部署测试服 order-v3 双实例 success;9443 实测:房务详情端点无回归、提房 VO 接受新 `days[].hotels[].remark`/`days[].remark` 字段。
|
||||
- 注:测试环境当前无可抢房务单,未在真实订单上观测到转发取值;映射逻辑由单测覆盖。
|
||||
|
||||
## 备注
|
||||
|
||||
- 网关无需改。
|
||||
- 定制师端「自定义备注输入框」「每家酒店备注输入框」UI 由前端实现,后端字段已就位。
|
||||
@@ -0,0 +1,34 @@
|
||||
# 房务酒店详情金额字段序列化修复(泄成数字 → 字符串)— 修复 — 管理后台
|
||||
|
||||
> 变更类型:🐛 Bug 修复(金额字段序列化,数字 → 字符串)
|
||||
> 端类型:管理后台(房务管家 · 酒店详情 4Tab)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4117
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端)
|
||||
|
||||
`GET /v3/admin/house/hotels/{hotelId}`(房务酒店详情)的以下金额字段,此前**漏成 JSON 数字**,现统一修为**字符串**(与平台「金额=字符串」契约一致):
|
||||
|
||||
| 字段 | 位置 | 修复前 | 修复后 |
|
||||
|---|---|---|---|
|
||||
| `roomTypes[].protoPrice` | Tab2 房型协议价 | `380.00`(数字) | `"380.00"`(字符串) |
|
||||
| `priceCalendar30d[].price` | Tab3 价格日历当日价 | `380.00`(数字) | `"380.00"`(字符串) |
|
||||
| `basic.protoPrice` | Tab1 协议价兜底 | `480.00`(数字) | `"480.00"`(字符串) |
|
||||
|
||||
前端按字符串接收即可(同详情里其它金额、以及房务各列表/对账金额一致)。
|
||||
|
||||
## 根因
|
||||
|
||||
平台全局 Jackson 只把**超 JS 安全范围的 Long 雪花**转字符串,**不转 BigDecimal**——所以 BigDecimal 金额漏标 `@JsonSerialize(ToStringSerializer)` 就**永远**序列化为数字。这 3 个字段所在的 feign VO 直接作为房务详情响应体序列化给前端,其金额字段漏标(同 VO 的房型/酒店 ID 早已标)。之前审计因测试酒店多数无房型/价格日历,未观测到。
|
||||
|
||||
## 测试(测试服 9443 实测)
|
||||
|
||||
取 3 家真实有房型 + 价格日历的酒店(房型 1-2 个、价格日历 11-30 天)实测:`roomTypes[].protoPrice`、`priceCalendar30d[].price`、`basic.protoPrice` 全部返字符串(如 `"1080.00"`/`"280.00"`),无一泄成数字。编译 BUILD SUCCESS、部署 order-v3 双实例 success。
|
||||
|
||||
## 备注
|
||||
|
||||
- 经纬度 `basic.longitude/latitude` 是坐标,保持数字(不属金额)。
|
||||
- 网关无需改。
|
||||
@@ -0,0 +1,34 @@
|
||||
# 早鸟优惠管理「优惠金额」序列化修复(泄成数字 → 字符串)— 修复 — 管理后台
|
||||
|
||||
> 变更类型:🐛 Bug 修复(金额字段序列化,数字 → 字符串)
|
||||
> 端类型:管理后台(营销 · 早鸟优惠管理)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4118
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端)
|
||||
|
||||
早鸟优惠管理 `GET /v3/admin/order/early-bird/list`(列表)与 `GET /v3/admin/order/early-bird/{planId}`(详情)返回的 `EarlyBirdPlanVO.discountAmount`(优惠金额),此前漏成 JSON 数字,现修为**字符串**(与平台「金额=字符串」契约一致):
|
||||
|
||||
| 字段 | 修复前 | 修复后 |
|
||||
|---|---|---|
|
||||
| `discountAmount` | `200.00`(数字) | `"200.00"`(字符串) |
|
||||
|
||||
- `discountPercent`(折扣率 0~100,如 `90.0`)**仍为数字**——它是比例不是金额,前端按数字用(`90` → 9 折)。
|
||||
- `planId` 等雪花 ID 仍为字符串。
|
||||
|
||||
## 根因
|
||||
|
||||
平台全局 Jackson 只把超 JS 范围的 Long 雪花转字符串、不转 BigDecimal,故金额 BigDecimal 漏标 `@JsonSerialize(ToStringSerializer)` 就泄成数字。`AdminEarlyBirdController`(前端管理页)直接返回 `EarlyBirdPlanVO`,其 `discountAmount` 漏标。
|
||||
|
||||
## 测试(测试服 9443 实测)
|
||||
|
||||
早鸟列表/详情实测:`discountAmount` 返字符串(`"200.00"`/`"80.00"`/`"150.00"`),`discountPercent` 仍数字、`planId` 字符串。编译 BUILD SUCCESS、部署 order-v3 双实例 success。
|
||||
|
||||
## 备注
|
||||
|
||||
- 列表端点是 `/v3/admin/order/early-bird/list`(不是裸 `/v3/admin/order/early-bird`,后者会被 `/{id}` 路由捕获)。
|
||||
- 全 order-v3 金额序列化已横扫收口:除本次与房务详情 3 feign VO(#4117)外,其余漏标的金额都在请求 VO / `/internal/` 响应 / feign 入站 DTO(Java 消费方无损),非前端可达、不受影响。
|
||||
- 网关无需改。
|
||||
@@ -0,0 +1,372 @@
|
||||
# 房务模块 + 定制师提房需求 + 站内信聊天 — 前端对接总览(管理后台)
|
||||
|
||||
> 变更类型:📘 对接总览(三域接口契约汇总,供前端排期对接)
|
||||
> 端类型:管理后台(房务管家工作台 / 订单·定制师提房需求 / 站内信聊天)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3(房务 + 定制师提房需求)、hl-user-service(站内信聊天)
|
||||
> 关联工单:#2728 房务工作台、#4013 房务侧 6 态、#4029/#4039/#4047/#4052/#4066/#4070~#4074/#4101 等
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(务必先读)
|
||||
|
||||
1. **统一网关 + 鉴权**:所有 admin 接口走网关 `https://api.test.1814.love:9443`,请求头 `Authorization: Bearer <token>`。登录 `POST /admin/auth/login {username,password}` → `data.token`。`X-Admin-Id` 由网关从 token 注入,**前端不要自己设**("当前操作人/归属/未读"全部据此判定)。
|
||||
2. **雪花 ID 一律是字符串**:所有 `id/orderId/requirementId/assignmentId/hotelId/consultantId/...` 等雪花 Long 在 JSON 里都是**字符串**(全局 `ToStringSerializer`)。前端按 string 接收、原样回传,**禁止 `parseInt`/`Number()`** 否则丢精度。
|
||||
3. **金额一律是字符串**:所有 `BigDecimal` 金额(`totalAmount/sellPrice/protoPrice/budget/replyPrice/...`)在 JSON 里是**字符串**(如 `"600.00"`)。前端按 string 展示/计算(用 decimal 库),不要当 number。
|
||||
4. **统一响应包**:`{ "code": 200, "msg": "success", "data": {...} }`。HTTP 始终 200,业务错误用 `code` + 6 位错误码区分(见各域错误码表)。分页统一 `PageResult{ list/total }`(部分接口为 `{list,total,stats}`)。
|
||||
5. **错误码段位**:房务 order-v3 = `808xxx`;定制师提房需求 = `582xxx`;站内信 user-service = `281xxx`。
|
||||
6. **GET 查询的日期参数格式**(务必遵守,否则报 500 转换异常):`LocalDate` 传 `yyyy-MM-dd`(如 `2026-06-20`);`LocalDateTime`(各种 `*From/*To`、`createTimeFrom`、`claimedAtFrom`、`repliedAtFrom` 等)**必须带 `T` 和时分秒** `yyyy-MM-dd'T'HH:mm:ss`(如 `2026-06-20T00:00:00`),不能只传日期。POST 请求体(`@RequestBody`)里的日期不受此限,传 `yyyy-MM-dd` 即可。详见同日《房务 GET 查询日期参数修复》changelog。
|
||||
7. **本总览=导航 + 核心契约**:高频建屏接口给到字段级 + 示例;其余给端点 + 用途 + 关键参数,**完整字段以 Knife4j/Swagger 在线文档为准**(每个 VO 都有 `@ApiModelProperty` 中文)。
|
||||
|
||||
---
|
||||
|
||||
# 模块一 · 定制师提房需求(hl-order-service-v3)
|
||||
|
||||
定制师在订单里提交/修改住宿需求 → 生成 `order_hotel_requirement`(INSERT-only 版本化)→ 进抢单池给房务抢。控制器 `HotelRequirementAdminController`,全部 `/v3/admin/order/**`(网关已覆盖)。
|
||||
|
||||
## 1.1 端点总览
|
||||
|
||||
| 方法 | 路径 | 用途 | 调用角色 |
|
||||
|---|---|---|---|
|
||||
| **PUT** | `/v3/admin/order/{id}/hotel-requirement` | **提交/修改/调整房型需求(核心入口·三分支自动判断)** | 定制师 |
|
||||
| GET | `/v3/admin/order/{id}/room` | 查询房间分配列表(家庭维度) | 定制师 |
|
||||
| POST | `/v3/admin/order/{id}/room` | 新增房间分配(家庭维度) | 定制师 |
|
||||
| PUT | `/v3/admin/order/{id}/room/{roomAssignmentId}` | 编辑房间分配 | 定制师 |
|
||||
| POST | `/v3/admin/order/{id}/hotel-requirement/dispatch` | 团期管理员提交房务(PENDING_REVIEW→PENDING) | 团期管理员 |
|
||||
| POST | `/v3/admin/order/{id}/hotel-requirement/reject` | 团期管理员打回定制师 | 团期管理员 |
|
||||
|
||||
> 另有 `POST /v3/admin/order/hotel-requirement/{requirementId}/assign`(房控老式单次配房)属房务侧,建议房务工作台统一走【模块二 · 2.4 批量配房】。
|
||||
|
||||
## 1.2 核心接口:提交/修改/调整房型需求
|
||||
|
||||
**`PUT /v3/admin/order/{id}/hotel-requirement`** —— 定制师提房需求唯一入口。后端按订单当前是否有 active 需求 + 状态**自动判断三分支**,前端无需区分:
|
||||
|
||||
- 无 active 行 → `INIT_SUBMIT`(首次提交)
|
||||
- active 且 `PENDING` → `PENDING_EDIT`(编辑草稿,不增版本号)
|
||||
- active 且 `DONE` → `DONE_ADJUST`(已配房后调整,自动回 PENDING/PENDING_REVIEW 并软删旧配房)
|
||||
|
||||
**请求体**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| days | `List<DayHotelReq>` | 是 | 每晚用房安排,**长度必须 = 订单 tripNights** |
|
||||
| specialTags | `List<String>` | 否 | 特殊诉求标签(高楼层/景观房/无烟房…,前端自定义多选,后端不做枚举校验) |
|
||||
| remark | String(≤500) | 否 | 需求备注 |
|
||||
|
||||
`DayHotelReq`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| dayNumber | Integer | 是 | 第几晚(1..tripNights,不重复) |
|
||||
| hotels | `List<HotelDetailReq>` | 是 | 同晚酒店列表(≥1) |
|
||||
| remark | String(≤200) | 否 | 当晚备注 |
|
||||
|
||||
`HotelDetailReq`:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| hotelId | Long | 是 | 资源域酒店 ID(须存在且已发布) |
|
||||
| roomCategory | String | 是 | 房型分类字典 `room_category`:STANDARD/SINGLE/TWIN/QUEEN/KING/SUITE/FAMILY/YURT/SPECIAL |
|
||||
| roomCount | Integer | 是 | 房间数(>0) |
|
||||
| budget | BigDecimal | 条件 | 预算/晚。**自由行必填、跟团传 null**。⚠️ **工单 #4047:后端按协议价独算覆盖、定制师不可改(预算锁)**,前端可展示协议价做建议值但提交后端会以协议价为准 |
|
||||
| hotelName | String | 否 | 酒店名快照(前端传,供回显,后端不自动查资源) |
|
||||
| source | String | 否 | 来源标记,默认 MANUAL |
|
||||
|
||||
> `stayDate`(每晚入住日)**前端不传**,后端按 `departDate + (dayNumber-1)` 反推。
|
||||
|
||||
**响应体(HotelRequirementRespVO)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| requirementId | Long(字符串) | 需求行 ID |
|
||||
| version | Integer | 版本号(INSERT-only 单调递增;PENDING 反复改不增) |
|
||||
| isActive | Boolean | 是否当前生效版本 |
|
||||
| status | String | 需求状态(见 1.3 状态机) |
|
||||
| branchTaken | String | 实际走的分支:INIT_SUBMIT / PENDING_EDIT / DONE_ADJUST |
|
||||
| submittedAt | LocalDateTime | 首提时间(PENDING 改不变,保抢单公平) |
|
||||
| previousVersion | Integer | DONE_ADJUST 分支:上一版本号 |
|
||||
| assignmentDeletedCount | Integer | DONE_ADJUST 分支:软删旧配房行数 |
|
||||
| claimerId/claimerName/claimedAt | — | 抢单房务信息(已被抢时有值) |
|
||||
|
||||
**请求示例**
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://api.test.1814.love:9443/v3/admin/order/60001/hotel-requirement" \
|
||||
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{
|
||||
"days": [
|
||||
{"dayNumber":1,"remark":"靠近景点","hotels":[
|
||||
{"hotelId":80001001,"roomCategory":"TWIN","roomCount":2,"budget":600.00,"hotelName":"拉萨西藏宾馆"}
|
||||
]}
|
||||
],
|
||||
"specialTags": ["高楼层","景观房"],
|
||||
"remark": "夫妻房,要求安静"
|
||||
}'
|
||||
```
|
||||
|
||||
## 1.3 需求状态机(RequirementStatus)
|
||||
|
||||
| 枚举 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| PENDING | 待房务配 | 已提交进抢单池(核心订单提交后直达此态) |
|
||||
| PROCESSING | 配房中 | 已被抢,处理中 |
|
||||
| DONE | 配房完成 | 配房完成 |
|
||||
| PENDING_REVIEW | 待审核 | **仅团期**:定制师提交后等团期管理员审核 |
|
||||
| REJECTED_TO_CONSULTANT | 已打回定制师 | 房务/管理员打回,定制师改后重提 |
|
||||
| REJECTED_TO_ADMIN | 已打回团期管理员 | **仅团期**:房务打回回到管理员重审 |
|
||||
|
||||
- **核心订单**:提交 → PENDING →(DONE / 打回 REJECTED_TO_CONSULTANT)。
|
||||
- **团期订单**:提交 → PENDING_REVIEW →(管理员 dispatch → PENDING / 管理员 reject → REJECTED_TO_CONSULTANT)。
|
||||
|
||||
## 1.4 错误码(582xxx)
|
||||
|
||||
| 码 | 说明 |
|
||||
|---|---|
|
||||
| 582001 | 订单不存在 |
|
||||
| 582011 | days 长度与订单 tripNights 不一致 |
|
||||
| 582012 | dayNumber 越界或重复 |
|
||||
| 582013 | hotelId 不存在或已下架 |
|
||||
| 582014 | 跟团模式 budget 必须为空 |
|
||||
| 582015 | 自由行模式 budget 必填 |
|
||||
| 582016 | roomCount 必须 > 0 |
|
||||
| 582017 | 订单状态不允许提交需求(已结算/已取消) |
|
||||
| 582018 | roomCategory 不在字典中 |
|
||||
| 582030 | 当前需求状态不可修改(PROCESSING) |
|
||||
| 582042 | 已签合同需走调整订单工作流 |
|
||||
|
||||
---
|
||||
|
||||
# 模块二 · 房务管家工作台(hl-order-service-v3)
|
||||
|
||||
房务工作台全功能。`/v3/admin/order/**`、`/v3/admin/house/**`、`/admin/house/**`、`/v3/admin/hotel-candidates` 网关均已覆盖。
|
||||
|
||||
## 2.1 端点总览
|
||||
|
||||
| 分组 | 方法 | 路径 | 用途 |
|
||||
|---|---|---|---|
|
||||
| **抢单池** | GET | `/v3/admin/order/grab-pool/hotel-requirements` | 抢单池列表(分页+多维筛选) |
|
||||
| | POST | `/v3/admin/order/hotel-requirements/{requirementId}/claim` | 抢单(乐观锁先到先得) |
|
||||
| | POST | `/v3/admin/order/hotel-requirements/{requirementId}/transfer` | 转单 / 超管强制指派 |
|
||||
| | POST | `/v3/admin/order/hotel-requirements/{requirementId}/release` | 释放回抢单池 |
|
||||
| | GET | `/v3/admin/order/grab-pool/my-claims/hotel` | 我的接单(分页+stats) |
|
||||
| | GET | `/v3/admin/house/staff` | 转单可选房务员工 |
|
||||
| **候选酒店** | GET | `/v3/admin/hotel-candidates` | 候选酒店统一查询(4 场景) |
|
||||
| **配房** | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | **批量提交配房方案** |
|
||||
| | PUT | `/v3/admin/order/assignments/{id}` | 修改单条配房(含**改协议价 #4072**) |
|
||||
| | DELETE | `/v3/admin/order/assignments/{id}` | 删除配房(软删) |
|
||||
| | GET | `/v3/admin/order/orders/{orderId}/rooms` | 房间分配查询(按家庭分组) |
|
||||
| | POST | `/v3/admin/order/assignments/{assignmentId}/rooms` | 家庭维度房间分配(整批覆盖) |
|
||||
| **订单详情** | GET | `/admin/house/orders/{orderId}` | 房务侧订单详情聚合 |
|
||||
| | GET | `/admin/house/orders/{orderId}/requirement-history` | 需求历史(含 diff/退回) |
|
||||
| **最终确认** | POST | `/admin/house/assignments/requirements/{requirementId}/finalize` | 房务点最终确认 |
|
||||
| | POST | `/admin/house/assignments/requirements/{requirementId}/receipts` | 上传确认回执(multipart) |
|
||||
| | GET | `/admin/house/assignments/requirements/{requirementId}/receipts` | 查询回执列表 |
|
||||
| **询房** | POST | `/v3/admin/order/inquiry/send` | 发起询房 |
|
||||
| | POST | `/v3/admin/order/inquiry/{inquiryId}/reply` | 回填酒店回复(可同步建配房) |
|
||||
| | GET | `/v3/admin/order/inquiry` | 询房历史(分页) |
|
||||
| | POST | `/v3/admin/order/inquiry/{originalInquiryId}/resend` | 重发询房 |
|
||||
| | POST | `/v3/admin/order/inquiry/{inquiryId}/escalate` | 加急/催办 |
|
||||
| **换酒店** | GET | `/v3/admin/order/swap-hotel/candidates` | 换酒店候选(同城可用) |
|
||||
| | POST | `/v3/admin/order/swap-hotel/preview` | 换酒店预览差价 |
|
||||
| | POST | `/v3/admin/order/swap-hotel/commit` | 换酒店提交(触发联动) |
|
||||
| **待办** | GET | `/v3/admin/order/todos` | 房务待办列表(多筛选+分类stats) |
|
||||
| **日历** | GET | `/admin/house/calendar` | 房务日历(月历+状态点) |
|
||||
| **酒店视图** | GET | `/v3/admin/house/hotels` | 房务酒店列表 |
|
||||
| | GET | `/v3/admin/house/hotels/{hotelId}` | 酒店详情 4Tab 聚合 |
|
||||
| | POST | `/v3/admin/house/hotels/{hotelId}/check-log` | 新增核房记录 |
|
||||
| | GET | `/v3/admin/house/hotels/{hotelId}/check-log` | 核房历史 |
|
||||
| | GET | `/v3/admin/house/hotels/{hotelId}/operation-log` | 酒店维度操作日志 |
|
||||
| | GET | `/v3/admin/house/orders/{orderId}/operation-log` | 订单维度操作日志 |
|
||||
| **月度对账** | GET | `/v3/admin/house/reconciliation/monthly` | 月度对账(按酒店现结汇总) |
|
||||
| | GET | `/v3/admin/house/reconciliation/monthly/export` | 月度对账 Excel 导出 |
|
||||
|
||||
## 2.2 抢单池列表 `GET /v3/admin/order/grab-pool/hotel-requirements`
|
||||
|
||||
**Query**:`keyword`(≤32) / `productType`(CORE/GROUP/CUSTOM) / `productName` / `consultantId` / `guestName` / `departDateFrom` / `departDateTo` / `page`(默认1) / `pageSize`(1-100,默认20) / `sortBy`(默认 createTime,desc,可 departDate,asc)。
|
||||
|
||||
**响应 `PageResult<HouseGrabPageItemRespVO>`**(行卡片,挑关键):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | Long(串) | 房型需求 ID(抢单用) |
|
||||
| orderId / orderNo | Long(串)/String | 订单 ID/订单号 |
|
||||
| guestName / personsDesc | String | 客人姓名 / 人数描述(如 2大1小) |
|
||||
| productType / productName / productNo | String | 产品类型/名/编号 |
|
||||
| route / departDate / nights | String/Date/Int | 档位·夜数 / 出行日 / 夜数 |
|
||||
| cities | `List<String>` | 行程城市(去重中文) |
|
||||
| totalAmount | BigDecimal(串) | 订单总额 |
|
||||
| consultantName / **consultantId** | String/Long(串) | 定制师姓名 / **adminId(#4101 新增,开聊天用,见模块三)** |
|
||||
| requirementNote / special | String/`List<String>` | 需求备注 / 特殊诉求标签 |
|
||||
| requirementVersion | Integer | 需求版本(>1 时 UI 标"已修订") |
|
||||
| createTime | LocalDateTime | 创建时间 |
|
||||
|
||||
> **我的接单** `GET .../my-claims/hotel` 响应为 `{list, total, stats}`,`stats={inProgress,pendingConfirm,confirmed,exception}`;行 VO 同样含 `totalAmount`(#4123 新增,String,与抢单池同口径)+ `consultantId` + `unreadMessageCount`(聊天未读红点,见模块三)。
|
||||
> **stats 空态说明**:`pendingConfirm`(待确认=PENDING_FINALIZE) / `exception`(异常=EXCEPTION) 计数随真实数据出,库里这两态 0 行时即为 0(空态,非 bug,**勿隐藏 tab**),订单走到配房全确认 / 拒单超时即出数。
|
||||
> **productType 筛选与徽标**:`productType` 跨服务字段不下推 SQL(当页内存精筛),故筛选时 `total` 仍是池总数(= 待抢徽标语义=池内总数),筛 productType 时前端弱化该徽标即可。
|
||||
|
||||
**转单 `POST /v3/admin/order/hotel-requirements/{requirementId}/transfer`** 请求体 `HouseTransferReqVO`:
|
||||
|
||||
| Key | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| toUserId | Long(串) | ✅ | 接收人房务 ID(admin_id) |
|
||||
| reason | String | ✅ | 原因(普通房务 ≥1 字 / 超管 ≥10 字,1~200) |
|
||||
| skipUpperLimit | Boolean | 否 | 跳过 30 单上限(仅超管有效,默认 false) |
|
||||
|
||||
## 2.3 候选酒店 `GET /v3/admin/hotel-candidates`
|
||||
|
||||
**Query**:`orderId`(必填) / `dayNumber` / `stayDate` / `city` / `keyword`(非空时跨城搜) / `limit`(默认10上限50) / `roomCategory` / `roomCount` / `preferredHotelId` / `requirementId`。
|
||||
|
||||
**响应 HotelCandidateRespVO**:`stayDate/city/productType/candidates[]`,每个候选含酒店信息、协议价/售价、可用房、运营标签 `tags`(**#4070 复用酒店标签库**:响应快/常合作等)、推荐理由等。
|
||||
|
||||
## 2.4 批量提交配房 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`
|
||||
|
||||
幂等:同 requirementId 3 秒窗口去重 + 30 秒分布式锁。
|
||||
|
||||
**请求 `{ items: List<AssignmentItemReqVO> }`**,每项:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| dayNumber | Integer | 是 | 第几天(≥1) |
|
||||
| familyIndex | Integer | 否 | 家庭序号(多家庭团区分,默认1) |
|
||||
| hotelId / roomTypeId | Long | 是 | 酒店/房型 ID(resource) |
|
||||
| roomCategory | String | 是 | 房型字典 code |
|
||||
| roomCount | Integer | 是 | 间数(≥1) |
|
||||
| sellPrice | BigDecimal | 是 | 售价(元/间·晚,≥0) |
|
||||
| remark | String | 否 | 备注 |
|
||||
|
||||
**响应 `{successCount,failCount,items[]}`**,item:`dayNumber/familyIndex/assignmentId(串)/arrange(pending/waiting/confirmed/problem)`。
|
||||
|
||||
## 2.5 修改配房 `PUT /v3/admin/order/assignments/{id}`(含改协议价 #4072)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| **protoPrice** | BigDecimal | 否 | **新协议价(#4072 房务改协议价)。后端 afterCommit 自动写回 resource 价格日历(协议价单源真相)** |
|
||||
| sellPrice | BigDecimal | 否 | 新售价 |
|
||||
| remark | String | 否 | 备注 |
|
||||
| syncHotelSnapshot | Boolean | 否 | 重新拉酒店主数据覆盖快照 |
|
||||
|
||||
> **改价口径**:房务侧只改**协议价**(本接口 `protoPrice`);**定制师改结算价不在房务、也不在"提需求"那步**,另在"确认订单"环节处理,前端房务工作台无需提供改结算价入口。
|
||||
|
||||
## 2.6 询房(核心两个)
|
||||
|
||||
**发起 `POST /v3/admin/order/inquiry/send`**:`requirementId/hotelId/dayNumber/stayDate/nights(1-30)/roomCount(1-99)/roomCategory?/messageBody?(≤1000,留空用模板)/isPriority?/timeoutOverride?(默认240min)/contactName?/contactWechat?`。响应含 `id/status(PENDING)/timeoutAt/wechatTemplate(微信复制粘贴模板)`。
|
||||
|
||||
**回填 `POST /v3/admin/order/inquiry/{inquiryId}/reply`**:`replyBody(必填1-2000)/replyPrice?/replyAvailable?(REJECTED 时必须 0)/replyStatus(REPLIED/REJECTED/CONFIRMED)/convertToAssignment?(true 同步建配房)/isPartialReply?`。`convertToAssignment=true` 时响应回 `convertedAssignmentId`。
|
||||
|
||||
> 询房=线下微信沟通的**记录/状态机**载体,不是即时通讯;**房务↔定制师的实时沟通走站内信聊天(模块三)**,两者不同。
|
||||
|
||||
## 2.7 换酒店(三步)
|
||||
|
||||
`GET /swap-hotel/candidates?assignmentId=&level=` → `POST /swap-hotel/preview`(算差价 autoDiff/appliedDiff/totalDiff/diffDirection 升级·降级·持平)→ `POST /swap-hotel/commit`(`assignmentId/newHotelId/newRoomTypeId?/newRoomCategory?/newSellPrice/nights/diffOverride?/swapReason(4-256)`,响应回 `newAssignmentId/totalDiff/diffDirection`,正数=需补差)。
|
||||
|
||||
## 2.8 日历 `GET /admin/house/calendar`
|
||||
|
||||
**Query**:`month`(YYYY-MM 必填) / `scope`(mine默认/all) / `status`(inProgress/inquiry/pending/exception,逗号多选)。响应 `{month,summary,days[]}`,days 为完整周排版(28-42 天,含上下月补位 `isCurrentMonth=false`),每天 `tourCount + statusDots[]`(status/label/count/color)。
|
||||
|
||||
## 2.9 月度对账 `GET /v3/admin/house/reconciliation/monthly?month=YYYY-MM`(#4074)
|
||||
|
||||
仅统计**现结**(settlement paymentMethod=CASH_PAID)。响应 `{month,overview,hotels[]}`:
|
||||
|
||||
- `overview`:`hotelCount/orderCount/roomNights/totalAmount/paidAmount/unpaidAmount`(金额均字符串)。
|
||||
- `hotels[]`:`hotelId(串)/hotelName/orderCount/roomNights/totalAmount/avgPrice/paidAmount/unpaidAmount`,应付降序。
|
||||
|
||||
导出 `GET .../monthly/export?month=YYYY-MM` → `.xlsx` 文件流(`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,文件名 `house-reconciliation-{month}.xlsx`)。空月返空列表 + 全 0 概览。
|
||||
|
||||
## 2.10 房务错误码(808xxx)
|
||||
|
||||
| 段 | 码 → 说明(节选高频) |
|
||||
|---|---|
|
||||
| 抢单 0080xx | 808001 已被他人抢 / 808003 达30单上限 / 808010 非本人需求 / 808013 转单达3次 / 808021 已有confirmed无法释放 / 808090 未登录或非房务 |
|
||||
| 配房 0081xx | 808100 需求不存在 / 808101 需求未抢 / 808102 dayNumber越界 / 808110 非本人需求 / 808120 配房不存在 / 808130 房间数超配 / 808131 床型不在字典 / 808140 订单不存在 / 808141 订单非房务可见 / 808150 month格式错 / 808170 对账month格式错 / 808180 未全部配房 / 808181 有未完结询房 / 808182 状态不允许最终确认 / 808186 状态不允许上传回执 |
|
||||
| 询房 0082xx | 808200 非本人需求 / 808201 酒店ID无效 / 808202 该酒店该晚已有PENDING询房 / 808210 询问不存在 / 808211 询问已结束 / 808220 orderId与requirementId至少传一 |
|
||||
| 换酒店 0084xx | 808400 原配房不存在 / 808401 新酒店今日无可用房 / 808402 无权操作 / 808403 库存冲突 / 808404 新旧酒店相同 / 808405 配房已被换过 |
|
||||
| 酒店视图 0085xx | 808500 酒店不存在 / 808501 totalAvailable与byRoomType不一致 / 808502 该日已有核房记录 / 808503 byRoomType为空 |
|
||||
|
||||
---
|
||||
|
||||
# 模块三 · 站内信聊天(hl-user-service)
|
||||
|
||||
站内信已升级为**双向 1:1 会话 + SSE 实时 + 敏感词过滤**。房务↔定制师围绕订单的实时沟通走这里(区别于询房的线下记录)。控制器 `ChatMessageController`,`/admin/message/chat/**`。**只做聊天,不推业务通知**。
|
||||
|
||||
## 3.1 端点总览
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|---|---|---|
|
||||
| POST | `/admin/message/chat/open` | 打开/找回 1:1 会话(幂等) |
|
||||
| GET | `/admin/message/chat/conversations` | 我的会话列表(含订单摘要/待回复 tab) |
|
||||
| GET | `/admin/message/chat/{conversationKey}/messages` | 拉线程消息(游标分页) |
|
||||
| POST | `/admin/message/chat/{conversationKey}/messages` | 发一条消息 |
|
||||
| POST | `/admin/message/chat/{conversationKey}/read` | 标记已读到最新 |
|
||||
| GET | `/admin/message/chat/unread-total` | 我的聊天未读总数(聊天 Tab 专用) |
|
||||
| GET | `/ws/admin-msg/stream` | **SSE 实时推送长连接** |
|
||||
| GET | `/admin/message/unread-count` | 顶部合并角标(聊天+通知,已有,不在本次范围但前端会用) |
|
||||
|
||||
## 3.2 打开会话 `POST /admin/message/chat/open`
|
||||
|
||||
**请求**:`bizModule`(HOUSE/FLEET/DIRECT) / `bizId`(HOUSE=orderId;DIRECT 留空) / `peerAdminId`(对方 adminId)。
|
||||
**响应**:`conversationKey / peerAdminId / peerName / peerRole / unreadCount / isNew`。
|
||||
|
||||
> **房务从订单卡片开会话**:`{bizModule:"HOUSE", bizId:<orderId>, peerAdminId:<consultantId>}`,`consultantId` 取自抢单池/我的接单列表行(#4101 已补到列表 VO,无需进详情)。同业务对象同两人重复 open 返 `isNew=false`,前端可缓存 conversationKey 避免重复调。
|
||||
|
||||
## 3.3 会话列表 `GET /admin/message/chat/conversations`
|
||||
|
||||
**Query**:`bizModule?` / `bizId?`(配合实现"围绕某订单的会话")/ `onlyPending?`(true=只看待回复) / `pageNo`(默认1) / `pageSize`(1-50,默认20)。
|
||||
|
||||
**响应 `PageResult<ChatConversationRespVO>`**:`conversationKey / bizModule / bizId(串) / peerAdminId(串) / peerName / peerRole / unreadCount / lastMessagePreview / lastMessageAt / status(ACTIVE/ARCHIVED) / peerAvatar` + **HOUSE 会话专属**:`orderNo / customerName / destination / tripDays / bizStatusLabel(中文如"待配房") / progressDesc(如"已配 2/4 间") / pendingReply`。按最后活跃倒序。
|
||||
|
||||
## 3.4 拉消息 / 发消息 / 已读
|
||||
|
||||
- **拉 `GET /{conversationKey}/messages`**:`beforeId?`(游标,首屏不传=拉最新一页) / `pageSize`(1-50)。响应 `{conversationKey,hasMore,nextCursor,list[]}`,向上翻页用 `nextCursor` 当下次 `beforeId`。消息项:`messageId(串)/senderAdminId(串)/senderName/senderRole/msgType/priority/content/isMine/sentAt`。
|
||||
- **发 `POST /{conversationKey}/messages`**:`content`(必填,≤1000) / `priority?`(NORMAL默认/URGENT) / `msgType?`(TEXT默认/IMAGE/SYSTEM)。响应 `{messageId,conversationKey,sentAt}`。**限流 20 条/60 秒/会话**。
|
||||
- **已读 `POST /{conversationKey}/read`**:把会话标记已读到最新,`unreadCount` 归 0。响应 `{conversationKey,unreadCount:0,lastReadMessageId}`。**建议发消息后/进入会话时自动调一次**。
|
||||
- **未读总数 `GET /admin/message/chat/unread-total`** → `{chatUnreadTotal}`(仅 CHAT,聊天 Tab 红点)。
|
||||
|
||||
## 3.5 SSE 实时推送 `GET /ws/admin-msg/stream`
|
||||
|
||||
前端用 `EventSource` 连接,token 走 query:`/ws/admin-msg/stream?token=<token>`(网关已放行 /ws/ 前缀、注入 X-Admin-Id;SSE 路由 response-timeout 已加大到 1h 防掐断)。事件:
|
||||
|
||||
| 事件名 | 触发 | 载荷关键字段 |
|
||||
|---|---|---|
|
||||
| `connected` | 建连即发 | "ok" |
|
||||
| `im-chat` | 新聊天消息(type=CHAT) | adminId / unreadCount / conversationKey / senderName / preview / priority |
|
||||
| `message` | 通知类(type=NOTIFY 或老信令) | adminId / unreadCount / title |
|
||||
| `ping` | 每 25s 心跳(注释行,不触发 onmessage) | — |
|
||||
|
||||
> 前端按**事件名**分发:`im-chat` 刷新聊天会话/红点,`message` 走原通知逻辑。
|
||||
|
||||
## 3.6 会话 key 规则 + 枚举
|
||||
|
||||
- **会话 key**:`{bizModule}:{bizId}:{minAdminId}:{maxAdminId}`,两人 adminId 强制小者在前,保证双向命中同一会话不分叉。DIRECT 纯私聊 bizId 用 `0` 占位。示例 `HOUSE:70123:101:205`。
|
||||
- **bizModule**:HOUSE(房务·bizId=orderId) / FLEET(车务·bizId=派车单) / DIRECT(纯私聊·bizId=0) / SYSTEM(系统通知专用)。
|
||||
- **msgType**:TEXT(默认) / IMAGE(图片URL) / SYSTEM(系统提示,senderAdminId 空)。
|
||||
- **敏感词**:后端发送时本地 DFA 命中替换为等长 `*`、**不报错**。前端**无需自己过滤**,直接展示返回的 `content`。
|
||||
|
||||
## 3.7 站内信错误码(281xxx)
|
||||
|
||||
| 码 | HTTP语义 | 说明 |
|
||||
|---|---|---|
|
||||
| 281001 | 404 | 会话不存在(对未 open 的 key 操作) |
|
||||
| 281002 | 403 | 无权访问该会话(非成员) |
|
||||
| 281003 | 409 | 会话正在创建,请稍后重试(open 并发) |
|
||||
| 281004 | 429 | 发送过于频繁(限流 20/60s) |
|
||||
| 281005 | 400 | 不能与自己创建会话 |
|
||||
| 281010 | 400 | 目标员工不存在或已离职 |
|
||||
| 281011 | 400 | 消息内容不合法(空/超 1000 字) |
|
||||
|
||||
---
|
||||
|
||||
# 前端联调 Checklist
|
||||
|
||||
1. 登录拿 token → 所有请求带 `Authorization: Bearer`,不要手设 `X-Admin-Id`。
|
||||
2. **所有 id/金额按字符串处理**(雪花精度 + 金额精度)。
|
||||
3. 定制师提房:`PUT /v3/admin/order/{id}/hotel-requirement`,days 长度=tripNights;budget 自由行必填/跟团 null,**别在前端锁死预算值**(后端按协议价覆盖)。
|
||||
4. 房务工作台:抢单池列表 → claim → 候选 `/v3/admin/hotel-candidates` → 批量配房 `/assignments` → (改协议价走 `PUT /assignments/{id}` 的 `protoPrice`)→ finalize + 上传回执。
|
||||
5. 聊天:列表卡片 `consultantId` → `open(HOUSE,orderId,consultantId)` → messages/send/read;并发起一条 `EventSource('/ws/admin-msg/stream?token=')` 监听 `im-chat`/`message`。
|
||||
6. 完整字段以测试服 Knife4j/Swagger 为准(每 VO 有中文 `@ApiModelProperty`),本总览覆盖端点+核心契约+错误码+枚举。
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
- 本总览三域接口均已部署测试服并通过 9443 实测;网关路由全覆盖,无需新增。
|
||||
- 站内信"接入房务"= 房务↔定制师 1:1 聊天(已完备);**不做房务事件→站内信通知**(配房完成/待配房等 NOTIFY 推送暂不做)。
|
||||
- 月度对账/酒店富表单等后端 schema+API 已就位,对应**表单/列表 UI 由前端实现**。
|
||||
@@ -0,0 +1,82 @@
|
||||
# 房务 / 候选 GET 查询「日期参数」绑定修复(stayDate 等 500)— 管理后台
|
||||
|
||||
> 变更类型:🐛 Bug 修复(GET 查询日期参数无法绑定,报 500)
|
||||
> 端类型:管理后台(房务管家 · 候选酒店/抢单池/我的接单/待办/询房历史/核房历史/操作日志)
|
||||
> 日期:2026-06-20
|
||||
> 服务:hl-order-service-v3
|
||||
> PR:https://git.1814.love:8443/wx/HL/pulls/4109
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(给前端)
|
||||
|
||||
前端联调「选择酒店」弹窗时,`GET /v3/admin/hotel-candidates?orderId=...&stayDate=2026-06-20` 报:
|
||||
|
||||
```
|
||||
Failed to convert property value of type 'java.lang.String' to required type
|
||||
'java.time.LocalDate' for property 'stayDate' ... Parse attempt failed for value [2026-06-20]
|
||||
```
|
||||
|
||||
**原因**:这是后端 bug —— GET 查询参数的日期字段缺少格式声明,已修复并部署测试服。前端**无需改 URL 写法**,只要按下面的**格式约定**传值即可正常工作。
|
||||
|
||||
## 日期参数格式约定(重要)
|
||||
|
||||
房务/候选这批 GET 接口的日期筛选参数,分两类,**格式不同**:
|
||||
|
||||
| 类型 | 接受格式 | 示例 | 涉及参数 |
|
||||
|---|---|---|---|
|
||||
| **日期** LocalDate | `yyyy-MM-dd` | `2026-06-20` | stayDate、departDateFrom/To、lastCheckedBefore、核房历史 startDate/endDate |
|
||||
| **日期时间** LocalDateTime | `yyyy-MM-dd'T'HH:mm:ss`(**必须带 `T` 和时分秒**) | `2026-06-20T00:00:00` | claimedAtFrom/To、待办 createTimeFrom/To、询房 createTimeFrom/To·repliedAtFrom/To、操作日志 startDate/endDate |
|
||||
|
||||
> ⚠️ **日期时间类参数不能只传 `2026-06-20`**,要补全成 `2026-06-20T00:00:00`(起始)/ `2026-06-20T23:59:59`(结束)。日期选择器若只取到日,请前端拼上 `T00:00:00` / `T23:59:59` 再传。
|
||||
|
||||
## 受影响接口(已全部修复,9443 实测 200)
|
||||
|
||||
- `GET /v3/admin/hotel-candidates`(候选酒店 · stayDate)
|
||||
- `GET /v3/admin/order/grab-pool/hotel-requirements`(抢单池 · departDateFrom/To)
|
||||
- `GET /v3/admin/order/grab-pool/my-claims/hotel`(我的接单 · departDateFrom/To、claimedAtFrom/To)
|
||||
- `GET /v3/admin/house/hotels`(房务酒店列表 · lastCheckedBefore)
|
||||
- `GET /v3/admin/order/todos`(待办 · createTimeFrom/To)
|
||||
- `GET /v3/admin/order/inquiry`(询房历史 · createTimeFrom/To、repliedAtFrom/To)
|
||||
- `GET /v3/admin/house/hotels/{hotelId}/check-log`(核房历史 · startDate/endDate)
|
||||
- `GET /v3/admin/house/hotels/{hotelId}/operation-log` 与 `GET /admin/house/orders/{orderId}/operation-log`(操作日志 · startDate/endDate)
|
||||
|
||||
> 注:POST 请求体(`@RequestBody`,如发起询房 `inquiry/send`、新增核房 `check-log`)走 JSON 解析,日期照常传 `2026-06-20` 即可,本次不受影响、未改动。
|
||||
|
||||
## 测试(测试服 9443 实测)
|
||||
|
||||
```bash
|
||||
# 前端原报错的请求,现返回 200(不再 500)
|
||||
curl -k "https://api.test.1814.love:9443/v3/admin/hotel-candidates?orderId=2067532338723504130&stayDate=2026-06-20" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
- 候选酒店 + stayDate=2026-06-20:**200**(修复前 500 转换异常)。
|
||||
- 上述 8 类日期/日期时间筛选端点逐一实测:全部 **200**,无转换异常、无 500。
|
||||
- `mvn -pl hl-order-service-v3 compile` BUILD SUCCESS;测试服 order-v3 双实例部署 success。
|
||||
|
||||
## 同类隐患连带修复(PR #4112)
|
||||
|
||||
对三域对接总览 50+ 端点逐个真 token 实测(49 端点全绿、零框架级报错)后,又静态排查 order-v3 全部 GET 查询日期 VO,发现房务模块外另有 2 处同病,一并补 `@DateTimeFormat`:
|
||||
|
||||
| 端点 | 参数 |
|
||||
|---|---|
|
||||
| `GET /v3/admin/complaint/page`(投诉列表) | dateFrom / dateTo |
|
||||
| `GET /v3/admin/order/vehicle-requirement/{id}/candidates`(配车候选) | availableFrom / availableTo |
|
||||
|
||||
均已部署 + 9443 实测按日期筛选返 200,不再 500。
|
||||
|
||||
## 参数格式错误消息已转干净中文(PR #4115)
|
||||
|
||||
合法日期已能正常解析(上文);针对**非法/格式不对的日期**(如日期范围控件给 LocalDateTime 字段发了纯日期 `2026-06-20`,或脏值 `2026-13-45`),后端**不再向前端吐 Spring 原始的 `Failed to convert ... ConversionFailedException` 长串**,改为干净中文:
|
||||
|
||||
```
|
||||
参数【stayDate】格式不正确(日期请用 yyyy-MM-dd,日期时间请用 yyyy-MM-dd'T'HH:mm:ss)
|
||||
```
|
||||
|
||||
`code` 仍为 400。前端可直接展示该 `message`。(仅 order-v3 生效;合法值不受影响、行为不变。)
|
||||
|
||||
## 备注
|
||||
|
||||
- 配套《房务+定制师提房需求+站内信聊天 前端对接总览》(同日发布)的日期参数,按本文格式约定传值。
|
||||
- 网关无需改。
|
||||
@@ -0,0 +1,127 @@
|
||||
# 【答复·管理后台】房务前端对接 4 个问题逐项答复(转单字段 / productType 计数 / 待确认·异常空态 / 我的接单补金额)
|
||||
|
||||
> 类型:接口契约确认 + 1 处字段补充(PR #4123)
|
||||
> 端:管理后台(hl-ui)
|
||||
> 服务:hl-order-service-v3
|
||||
> 日期:2026-06-20
|
||||
> 关联:前端对接反馈(口头),无工单(前端反馈走 changelog)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
前端对接房务时反馈 4 个问题,逐项核查代码 + 测试服 DB 地面真相后答复如下。**结论:1 个真问题已修(item 4 我的接单卡片补金额,PR #4123),另 3 个是契约确认 / 设计取舍 / 空态,均无需前端改动方向,照下方说明对接即可。**
|
||||
|
||||
| # | 前端反馈 | 核查结论 | 处置 |
|
||||
|---|---------|---------|------|
|
||||
| 1 | 转单 body 字段名两份契约都没列 | `{toUserId, reason}` 前端猜的**完全正确**,另有可选 `skipUpperLimit` | 契约已确认,无需改 |
|
||||
| 2 | productType 筛选时「N 单待抢」徽标计数虚高 | 设计取舍(productType 跨服务字段不能下推 SQL):徽标 total = **池总数**,productType 是当页视图过滤 | 徽标语义说明,无需改 |
|
||||
| 3 | 我的接单「待确认/异常」两 tab stats 恒 0 | **不是「都映射 PROCESSING」**——映射 100% 正确、状态流转已接通;恒 0 是**当前测试库这两态 0 行**(空态) | 不要隐藏 tab,渲染空态即可 |
|
||||
| 4 | 列表卡片未渲染金额 | 抢单池卡片本就有 `totalAmount`;**我的接单卡片原缺**,已补齐对齐 | **PR #4123 已修** |
|
||||
|
||||
---
|
||||
|
||||
## 1. 转单接口字段名(item 1)—— 契约确认,前端猜对了
|
||||
|
||||
**端点**:`POST /v3/admin/order/hotel-requirements/{requirementId}/transfer`
|
||||
**请求体 VO**:`HouseTransferReqVO`
|
||||
|
||||
| JSON Key | 类型 | 必填 | 说明 |
|
||||
|----------|------|------|------|
|
||||
| `toUserId` | Long(字符串传) | ✅ 必填 | 接收人房务 ID(= `admin_user.admin_id`) |
|
||||
| `reason` | String | ✅ 必填 | 转单/指派原因(普通房务 ≥1 字,超管 ≥10 字;长度 1~200) |
|
||||
| `skipUpperLimit` | Boolean | 否(默认 false) | 跳过 30 单上限(**仅超管有效**,普通房务传 true 也忽略) |
|
||||
|
||||
> 前端猜的 `{toUserId, reason}` **一字不差正确**,补一个可选 `skipUpperLimit` 即可。`toUserId` 是接收房务的 `admin_id`(即「我的接单」「抢单池」卡片里 `consultantId` 同源的 admin 主键体系)。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
curl -X POST "https://api.test.1814.love:9443/v3/admin/order/hotel-requirements/70123/transfer" \
|
||||
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{"toUserId": 1003, "reason": "我今天临时请假,转给小图接手"}'
|
||||
```
|
||||
|
||||
失败(如接收人不存在 / 超 30 单上限)走业务错误码 toast,不崩页面。上线转单前可在 Knife4j 自测一次确认。
|
||||
|
||||
---
|
||||
|
||||
## 2. productType 筛选时「N 单待抢」徽标计数(item 2)—— 设计取舍,徽标 = 池总数
|
||||
|
||||
**现象属实**:抢单池列表按 productType 筛选时,返回的 `total` 是池总数(不随 productType 收窄),故 `total >> 当前 list 长度`。
|
||||
|
||||
**根因(非 bug,是架构约束)**:
|
||||
- `productType` 是 **product 域字段**,`order_main` 表里没有这列,需跨服务 Feign(`ProductFeignClient`)按 productId 现查补上。
|
||||
- 抢单池为小数据集(5~8 人小团队的待抢需求,单页装得下),故设计为:SQL 出准 total + list → 当页 Feign enrich 补 productType → **当页内存按 productType 精筛**。
|
||||
- 受 CODE_RULES §9「事务内禁同步 Feign」约束,productType 无法下推到 Mapper 的 SQL WHERE,total 只能是 DB 端未含 productType 的池总数。
|
||||
|
||||
**前端对接建议**:
|
||||
- 「N 单待抢」徽标语义 = **抢单池待抢总数**(productType 是当页视图过滤,不改变池里有多少单待抢)。
|
||||
- 按前端自己的建议,**筛选 productType 时弱化/隐藏该徽标**,或标注为「池内共 N 单」,避免与筛选后列表长度产生「数字对不上」的观感即可。
|
||||
- 当前测试库抢单池为 0 行(24 条需求全部已确认),该现象暂不会触发。
|
||||
|
||||
> 备注:若后续产品要求 productType 筛选下徽标精确计数,需做「拉全池 enrich 后过滤再分页」的小改(小数据集可行),届时另立。当前不改。
|
||||
|
||||
---
|
||||
|
||||
## 3. 我的接单「待确认 / 异常」两 tab stats 恒 0(item 3)—— 空态,不是 bug,请勿隐藏 tab
|
||||
|
||||
**前端假设「这俩 tab 都映射到 PROCESSING」—— 经核查不成立。** 映射完全正确、各自独立:
|
||||
|
||||
| 前端 tab | 后端 status key | 房务 6 态(house_status) |
|
||||
|---------|----------------|------------------------|
|
||||
| 进行中 | `inProgress` | CLAIMING / IN_INQUIRY |
|
||||
| **待确认** | `pendingConfirm` | **PENDING_FINALIZE** |
|
||||
| 已确认 | `confirmed` | CONFIRMED |
|
||||
| **异常** | `exception` | **EXCEPTION** |
|
||||
|
||||
- stats 按**细分** house_status 分别统计(非按粗状态归并),代码逻辑正确。
|
||||
- 状态机流转**已接通**:`CLAIMING → PENDING_FINALIZE`(配房全部确认 ASSIGNMENT_CONFIRM_FULL)、`PENDING_FINALIZE → CONFIRMED`、`IN_INQUIRY → EXCEPTION`(拒单/超时无回复)。
|
||||
|
||||
**恒 0 的真因 = 测试库当前这两态 0 行**(地面真相):
|
||||
|
||||
```
|
||||
house_status 全量分布(order_hotel_requirement):CONFIRMED = 24(仅此一态)
|
||||
PENDING_FINALIZE / EXCEPTION:全表 0 行
|
||||
```
|
||||
|
||||
当前测试数据里 24 条需求全部停在 CONFIRMED,没有任何单子走到「待最终确认」或「异常」,所以这两个 tab 计数为 0 是**如实反映数据的空态**,不是计数错误。
|
||||
|
||||
**前端对接建议**:**不要禁用/隐藏这两个 tab**——它们是真实状态,等订单走到配房全确认(→待确认)/ 拒单超时(→异常)时会自动出数。前端把 0 渲染成空态/灰显即可。要现场验证,可造一条单子走完配房确认流程看「待确认」出数。
|
||||
|
||||
---
|
||||
|
||||
## 4. 列表卡片金额渲染(item 4)—— 我的接单卡片已补 `totalAmount`(PR #4123)
|
||||
|
||||
**核查发现两张卡片不对称**:
|
||||
- **抢单池**列表项 `HouseGrabPageItemRespVO` —— **本就有** `totalAmount`(String「6840.00」,对应 mock 的 ¥6,840),前端直接渲染即可,后端无需改。
|
||||
- **我的接单**列表项 `HouseMyOrderItemRespVO` —— **原缺任何金额字段**,已补齐(镜像抢单池同口径,JOIN `order_main.order_amount`)。
|
||||
|
||||
**变更(PR #4123,已合并部署测试服)**:我的接单列表项新增字段
|
||||
|
||||
| 字段 | 类型 | 序列化 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `totalAmount` | BigDecimal | **String(防精度)** | 订单总额(元),取 `order_main.order_amount`,与抢单池卡片同口径 |
|
||||
|
||||
```jsonc
|
||||
// GET /v3/admin/order/grab-pool/my-claims/hotel → data.list[]
|
||||
{
|
||||
"id": "70123",
|
||||
"orderNo": "26-0501",
|
||||
"guestName": "张先生一家",
|
||||
"totalAmount": "6840.00", // ★ 新增,String
|
||||
"houseStatus": "已确认",
|
||||
"consultantId": "1002"
|
||||
// ...其余字段不变
|
||||
}
|
||||
```
|
||||
|
||||
> 非破坏性加字段,前端按需渲染(是否在卡片展示金额按原型定;后端两张卡片现已都提供)。金额统一 String 传输防精度,前端展示加 ¥ 前缀即可。
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
- `HouseGrabServiceImplTest` 40 + `*ArchTest`(MapperBoundary 22 + RedLine 9 + LocalCacheVetting 1 = 32)全绿。
|
||||
- 部署测试服 order-v3 双实例(8086/8186)成功;带新 JOIN 列的 my-claims 查询在生产库实跑返 200。
|
||||
- item 4 字段来源核对:我的接单关联 24 单 `order_amount` 全非空(3105.00 ~ 22000.00)。
|
||||
@@ -0,0 +1,81 @@
|
||||
# 【审计修复·管理后台】房务模块4类同类逻辑问题系统排查修复(9 PR · 前端/下游影响)
|
||||
|
||||
> 类型:审计驱动批量修复(无单个工单,以 #4124/#4125 为样板的同类排查)
|
||||
> 端:管理后台(hl-ui)+ 下游(mp 行程/合同)
|
||||
> 服务:hl-order-service-v3
|
||||
> 日期:2026-06-20
|
||||
> 关联 PR:#4133 #4137 #4138 #4139 #4140 #4143 #4144 #4146 #4147
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
以已实现的 #4124/#4125 为样板,派 4 个并行 agent 把房务模块的逻辑问题抽象成 **4 类**(A 上游数据被映射丢弃 / B 硬编码该走枚举字典 / C 冗余死代码 / D mock 占位非真实)全量排查,出 ~25 项,9 个 PR 全部修复。**多数是后端重构(死代码清理 / 枚举收口),无 API 契约变更;下面只列前端/下游能感知到的变更。**
|
||||
|
||||
| 变更 | 端 | 影响 |
|
||||
|------|----|----|
|
||||
| 1. 抢单池/详情/我的接单卡片新增 `dispatchRemark` | 管理后台 | 加字段,展示「提交备注(给房务看)」 |
|
||||
| 2. 我的接单 `exceptionCount`/`todoCount` 由恒 0 改真实计数 | 管理后台 | 行为变更,排序/筛选恢复有效 |
|
||||
| 3. 房务待办列表 8 个展示字段由占位改真实 | 管理后台 | 行为变更,字段不再为 null/写死 |
|
||||
| 4. 车控「我的接单」由假数据改真实查询 | 管理后台 | 行为变更,不再返 mock 假单 |
|
||||
| 5. 候选推荐语去掉假的「历史命中 50%」 | 管理后台 | 文案变更 |
|
||||
| 6. 配车需求 `specialTags` 标注字典绑定 | 管理后台 | 字典选项(`vehicle_special_demand`) |
|
||||
| 7. 配置聚合 bundle 已配酒店由空改真实 | 下游(mp/合同) | 行为变更,行程/合同含酒店 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 新增 dispatchRemark(提交备注)字段(PR #4138)
|
||||
|
||||
团期管理员提交需求时填的「提交备注(给房务/车队看)」原写入表却从未透传到任何列表/详情,工作台看不到。现已加字段透传:
|
||||
|
||||
| 接口 | 新增字段 | 说明 |
|
||||
|------|---------|------|
|
||||
| `GET /v3/admin/order/grab-pool/hotel-requirements`(抢单池) | `dispatchRemark` | String,提交房务备注 |
|
||||
| 房务详情快照 `requirement.current` | `dispatchRemark` | 同上 |
|
||||
| `GET /v3/internal/order/vehicle-requirements`(车务池,下游) | `dispatchRemark` | 提交车务备注 |
|
||||
|
||||
非破坏性加字段,前端按需展示。
|
||||
|
||||
## 2. 我的接单异常/待办计数接真值(PR #4139)
|
||||
|
||||
`GET /v3/admin/order/grab-pool/my-claims/hotel` 列表项:
|
||||
|
||||
| 字段 | 旧 | 新 |
|
||||
|------|----|----|
|
||||
| `exceptionCount` | 恒 0 | 该订单 OPEN 异常待办真实数 |
|
||||
| `todoCount` | 恒 0 | 该订单未关闭待办真实数 |
|
||||
|
||||
> 之前因恒 0,`sortBy=exceptionCount,desc` 排序与「仅看有异常的单」过滤全失效;现已恢复有效。
|
||||
|
||||
## 3. 房务待办列表展示字段接真值(PR #4139)
|
||||
|
||||
`GET /v3/admin/order/todos` 列表项:`orderNo`/`guestName`/`personsDesc`/`ownerName`/`requirementSummary`/`requirementVersion`/`departDate` 由恒 null/写死 1 改为联表订单/需求真实值(批量取数,跨域降级)。
|
||||
|
||||
## 4. 车控「我的接单」去 Mock 改真实查询(PR #4146)
|
||||
|
||||
`GET /v3/admin/order/grab-pool/my-claims/vehicle` 原返硬编码假数据(「长白山自由行5日」等),现改真实查询(按当前车控 claimer_id + status 分页查 order_vehicle_requirement,装配真实数据)。列表项补 `consultantId`(开与定制师会话用)。
|
||||
|
||||
> 说明:车务是 fleet 派单(非人工抢单),`claimer_id` 写入路径暂未建,故当前返**空列表(真实)**,而非假数据。
|
||||
|
||||
## 5. 候选推荐语去掉假「历史命中」(PR #4133)
|
||||
|
||||
`GET /v3/admin/hotel-candidates` 候选 `recommendation` 文案:历史命中算法尚未接入(stub 恒 0.5),原代码却把它拼成「· 历史命中 50%」展示给定制师(假指标)。现 stub 模式下不再拼该片段(`historyScoreStub=true` 标志位保留)。
|
||||
|
||||
## 6. 配车需求 specialTags 字典化标注(PR #4146)
|
||||
|
||||
`VehicleRequirementReqVO.specialTags` 标注绑定字典 `vehicle_special_demand`(镜像房务侧 `house_special_demand`),前端可走字典多选 + 自定义。
|
||||
|
||||
## 7. 配置聚合 bundle 已配酒店接真(PR #4147,下游)
|
||||
|
||||
internal `getAssignmentBundle`(供 mp 行程聚合/合同签约)的 `hotelAssignments` 原恒空(已配酒店数据迁到 house 域后未接),现经中立契约 `HouseAssignmentReadContract` 读真实已配酒店填充。下游 mp 行程/合同模板现能拿到酒店明细。
|
||||
|
||||
---
|
||||
|
||||
## 纯后端重构(无 API 变更,前端无需改动,仅记录)
|
||||
|
||||
- **死代码清理**(#4133/#4137):删 order.assignment 3 个死候选 VO、RequirementService hotel 抢单旧死方法链 + 死 VO、RequirementSnapshot.lastReturnInfo 死字段、GrabPoolJoinItem.tripDays 死投影、死请求字段、Ranker 镜像方法上提。
|
||||
- **枚举收口**(#4140/#4143/#4144):productType/staffRole/resourceType 复用现成枚举;徽章/recommendSource/returnTarget/branchTaken/dailyFeeSource/urgency 建枚举;HouseTodoType label 内聚。**行为零变更**(字面量→枚举,输出/比较结果完全一致)。
|
||||
|
||||
## 验证
|
||||
|
||||
各 PR 均 test-compile + 相关单测 + `*ArchTest`(32)全绿;部署测试服 order-v3 成功,9443 实测车控我的接单去 Mock 生效(返空非假数据)、各端点 200 在服。
|
||||
@@ -0,0 +1,142 @@
|
||||
# 【修改·管理后台】配房/选酒店流程评审 11 点收口(候选房型富化 + 段×候选 + 池内快照 + 特殊诉求字典)
|
||||
|
||||
> 涉及 PR:#4126 / #4127 / #4135 / #4136 / #4148 / #4149(hl-order-service-v3,区县另涉 hl-resource-service)
|
||||
> 影响页面:**订单详情 → 调整订单 → 酒店安排**(配房需求表单)+ 其中的**「选择酒店」弹窗**
|
||||
> 状态:已合并 dev-v3、已部署测试服双实例、候选接口已网关 API 实测通过
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键说明(先读)
|
||||
|
||||
1. 这是定制师「提交配房需求」表单 +「选择酒店」弹窗的一轮产品评审收口,共 11 个点,前后端都要改。
|
||||
2. 两处契约变化,前端必改:
|
||||
- **配房需求提交体** `HotelRequirementReqVO.days` 结构升级为「**段 × 候选**」(见 §三-2)。
|
||||
- **选酒店候选接口** `GET /v3/admin/hotel-candidates` 出参**富化**(真实房型/城市/区县/池内,见 §三-1)。
|
||||
3. **房型下拉不再用通用字典**,改由「所选酒店的真实房型」驱动;**不选酒店不能选房型**。
|
||||
4. 金额字段(协议价/标价/预算)均为 **String**(后端 @JsonSerialize),前端按字符串收。
|
||||
|
||||
---
|
||||
|
||||
## 一、配房需求表单改动
|
||||
|
||||
### 点1 删「当晚备注」
|
||||
- 每晚的「当晚备注(当晚整体安排说明)」**整条去掉**(输入框删除)。
|
||||
- 后端 `DayHotelReq.remark` 字段已移除;行程 Tab 的 `requirementDays[].remark` 也随之废弃(新单恒空,前端别再展示当晚备注)。
|
||||
- **保留**「该酒店备注」(迁为每个候选酒店的备注,见点8 `CandidateHotel.remark`)。
|
||||
|
||||
### 点2 删「城市/区域」输入(纯前端)
|
||||
- 每晚那个「城市/区域(如 拉萨市区)」输入框**删掉**——它本就没进后端,且与「选择酒店」弹窗自带的搜索框重复。
|
||||
|
||||
### 点3 特殊诉求改字典(多选 + 允许自定义)
|
||||
- 「特殊诉求」从自由文本标签改为**绑定字典** `house_special_demand`,前端做**多选**,**仍允许回车加自定义**。
|
||||
- 字典加载:`GET /admin/dict/data/house_special_demand` → `[{dictValue, dictLabel}]`(当前值:安静楼层/高楼层/景观房/无烟房/含早餐/含双早/可加床/可拆分双床/相邻房间/远离电梯,运营可在字典管理增删)。
|
||||
- 提交字段不变,仍是 `specialTags: List<String>`(存字典 value,自定义则存原文)。
|
||||
|
||||
### 点8 酒店支持「段 × 候选」(提交体结构升级,重点)
|
||||
两个**独立**维度(产品确认都要):
|
||||
- **分住**:同一晚住两家(房数相加)—— 一晚有多个 **段(segment)**。
|
||||
- **候选**:定制师对某段提多家心仪酒店,**房控后台择一**回配 —— 一段有多个 **候选(candidate)**。
|
||||
|
||||
UI:
|
||||
- 每晚可「+ 加一段不同酒店(同一晚分住)」= 加 segment(每段独立填 房数/房型/预算/段备注)。
|
||||
- 每段的「酒店」框支持**多选**候选酒店(房控择一);每个候选可填该酒店备注。
|
||||
- 候选酒店会自动进候选接口的「定制师点名」高亮(`isConsultantRecommended`)。
|
||||
|
||||
---
|
||||
|
||||
## 二、选择酒店弹窗 + 房型
|
||||
|
||||
### 点4 协议价按真实房型 / 点6 可用房按真实房型 / 点7 房型按真实酒店
|
||||
- 候选接口现返回每家酒店的**真实房型列表** `roomTypes[]`(来自 resource 价格日历/库存)。
|
||||
- 前端:
|
||||
- **房型下拉**改由所选酒店的 `roomTypes[]` 驱动(`name`/`bedType`/`maxOccupancy`),**不选酒店则房型不可选**;不再用通用 `room_category` 字典。
|
||||
- **协议价**显示所选房型的 `roomTypes[].protocolPrice`(不是酒店级起步价 `protoPrice`)。
|
||||
- **可用房**显示所选房型的 `roomTypes[].available`(+ `inventoryStatus` AVAILABLE/FULL/CLOSED)。
|
||||
- 注:测试酒店需 resource 侧配了价格日历+库存才有 `roomTypes` 数据(无则该酒店 roomTypes 为空/价 null)。
|
||||
|
||||
### 点5 城市 / 点10 区县
|
||||
- 候选项新增 `city`(城市,如 呼伦贝尔市)+ `district`(区/县,如 满洲里市)。
|
||||
- 选酒店列表/候选展示加「城市」「区/县」两列。
|
||||
|
||||
### 点9+11 池内徽章:按行程天数 + 从订单产品快照取
|
||||
- 「池内」徽章 `isPoolMatch` 语义修正为:**该酒店在「订单产品快照」里当天当档(order.tierSeq)的产品酒店池中**。
|
||||
- 按**行程天数**:第1天只标第1天产品行程的酒店,第2天标第2天的。
|
||||
- 从**订单产品快照冻结值**取(非 live 产品配置)——产品下单后被改不影响已下单订单的池内判定。
|
||||
- 前端无需改调用方式,照常按 `dayNumber` 查候选,`isPoolMatch`/`poolMatchBadge` 直接用。
|
||||
|
||||
---
|
||||
|
||||
## 三、接口契约
|
||||
|
||||
### 1. 候选接口出参(新增字段)
|
||||
`GET /v3/admin/hotel-candidates?orderId=&dayNumber=&roomCategory=&roomCount=&keyword=&limit=`
|
||||
出参 `data.candidates[]`(`HotelCandidateRespVO.Candidate`)**新增**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `city` | String | 酒店所在城市(如 呼伦贝尔市) |
|
||||
| `district` | String | 酒店所在区/县(如 满洲里市) |
|
||||
| `roomTypes` | List | 该酒店当日真实房型列表(见下) |
|
||||
|
||||
`roomTypes[]`(`RoomTypeOption`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `roomTypeId` | String | 房型 ID |
|
||||
| `name` | String | 房型名称(如 普通标间) |
|
||||
| `bedType` | String | 床型(如 TWIN_BED) |
|
||||
| `maxOccupancy` | Integer | 最大入住人数 |
|
||||
| `available` | Integer | 今日可用房数 |
|
||||
| `stock` | Integer | 总库存 |
|
||||
| `protocolPrice` | String | 协议价(金额字符串) |
|
||||
| `basePrice` | String | 标价 |
|
||||
| `inventoryStatus` | String | AVAILABLE / FULL / CLOSED |
|
||||
|
||||
`isPoolMatch`(既有字段,语义改为按天+快照)、`protoPrice`/`matchedRoomType*`(既有,向后兼容保留)不变。
|
||||
|
||||
### 2. 配房需求提交体(结构升级)
|
||||
`HotelRequirementReqVO`(提交配房需求 / 调整订单 hotelRequirement.days 同构):
|
||||
```
|
||||
{
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"segments": [ // ← 原 hotels[] 改为 segments[](分住段)
|
||||
{
|
||||
"roomCount": 2,
|
||||
"roomCategory": "TWIN",
|
||||
"budget": "600.00", // 可空(后端反查协议价覆盖)
|
||||
"remark": "段备注",
|
||||
"candidates": [ // ← 候选酒店列表(房控择一)
|
||||
{ "hotelId": 80001, "hotelName": "拉萨西藏宾馆", "remark": "靠湖一侧" },
|
||||
{ "hotelId": 80002, "hotelName": "山景酒店", "remark": "含双早" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"specialTags": ["安静楼层", "含早餐"], // 字典 house_special_demand 的 value(允许自定义)
|
||||
"remark": "整单备注"
|
||||
}
|
||||
```
|
||||
- **去掉**:`days[].remark`(当晚备注)、`days[].hotels[].source`(死字段)。
|
||||
- 后端兼容旧 `hotels[]` 结构读取(存量订单不报错),但**新提交请用 `segments[].candidates[]`**。
|
||||
|
||||
### 3. 特殊诉求字典
|
||||
`GET /admin/dict/data/house_special_demand` → `data: [{dictValue, dictLabel, ...}]`。
|
||||
|
||||
---
|
||||
|
||||
## 四、curl 实测(候选接口,2026-06-20 测试服)
|
||||
```
|
||||
GET /v3/admin/hotel-candidates?orderId=2068235380959682562&dayNumber=1&limit=3
|
||||
→ 200
|
||||
data.candidates[0]:
|
||||
hotelName=呼伦贝尔香格里拉大酒店 city=呼伦贝尔市 district=满洲里市 isPoolMatch=true
|
||||
roomTypes=[{name:普通标间, bedType:TWIN_BED, available:20, protocolPrice:"280.00", inventoryStatus:AVAILABLE}, ...]
|
||||
data.candidates[1]: 海拉尔嘉世豪酒店 district=海拉尔区 isPoolMatch=false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
如对契约/字段有疑问随时找后端(王骁)。
|
||||
@@ -0,0 +1,314 @@
|
||||
# 发票补齐 - 财务开票闭环 + 定制师代申请(管理后台)
|
||||
|
||||
> Issue: [#4172](https://git.1814.love:8443/wx/HL/issues/4172) / [#4173](https://git.1814.love:8443/wx/HL/issues/4173)
|
||||
> PR: [#4179](https://git.1814.love:8443/wx/HL/pulls/4179) / [#4180](https://git.1814.love:8443/wx/HL/pulls/4180)
|
||||
> 日期: 2026-06-21
|
||||
> 服务: hl-order-service-v3(invoice 域)
|
||||
> 端类型: 管理后台
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
order-v3 发票模块此前缺少财务侧开票操作和定制师代客申请入口。本次补齐:
|
||||
|
||||
- 财务开票闭环(PR2 / Issue #4172):财务人员上传发票 PDF 至 OSS,完成开票(REQUESTED 变 ISSUED),支持重新上传覆盖;以及发票管理列表(分页 + Tab 统计 + 当月金额统计 + 关键词搜索)。
|
||||
- 定制师代客申请(PR3 / Issue #4173):定制师代用户提交开票申请,门槛与小程序端统一(订单 COMPLETED + 一单一票 + 申请金额不超订单总价)。
|
||||
- 已有接口字段补全:GET /v3/admin/order/{id}/invoices 出参补充 8 个原来恒为 null 的字段。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 接口 | 变更类型 | 说明 |
|
||||
|---|------|----------|------|
|
||||
| 1 | POST /v3/admin/order/invoice/upload | 新增接口 | 财务上传发票文件至 OSS,返回 fileUrl(不绑定发票记录) |
|
||||
| 2 | PUT /v3/admin/order/invoice/{id}/issue | 新增接口 | 完成开票,状态 REQUESTED 变 ISSUED |
|
||||
| 3 | PUT /v3/admin/order/invoice/{id}/reupload | 新增接口 | 重新上传覆盖文件(ISSUED / PUSHED 均可) |
|
||||
| 4 | GET /v3/admin/order/invoice/page | 新增接口 | 发票管理列表(分页 + Tab 计数 + 当月统计 + 搜索) |
|
||||
| 5 | POST /v3/admin/order/{orderId}/invoice/apply | 新增接口 | 定制师代客申请开票 |
|
||||
| 6 | GET /v3/admin/order/{id}/invoices(已有) | 修改接口(字段补全) | 出参新增:fileUrl / pdfName / pdfSize / invoiceNo / issuedAt / issuedBy / requestedAt / requestedBy |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
认证:所有接口走管理后台 JWT,Header 携带 Authorization: Bearer token。upload 使用 multipart/form-data,其余 application/json。
|
||||
幂等性:upload 每次生成新 OSS 文件,无幂等。issue / reupload 依赖发票状态守卫,状态不符报业务错误。
|
||||
限流:网关全局限流,无接口级特殊限流。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数 / Query 参数
|
||||
|
||||
| 接口 | 参数名 | 类型 | 必填 | 说明 |
|
||||
|------|--------|------|------|------|
|
||||
| PUT /issue/{id} | id | Long | 是 | 发票记录 ID |
|
||||
| PUT /reupload/{id} | id | Long | 是 | 发票记录 ID |
|
||||
| GET /page | page | Integer | 否 | 页码,默认 1 |
|
||||
| GET /page | pageSize | Integer | 否 | 每页条数,默认 20 |
|
||||
| GET /page | tab | String | 否 | Tab 过滤,见 6 节,默认 ALL |
|
||||
| GET /page | keyword | String | 否 | 关键词搜索(发票号 / 申请人) |
|
||||
| GET /page | startDate | String | 否 | 申请开始日期 yyyy-MM-dd |
|
||||
| GET /page | endDate | String | 否 | 申请结束日期 yyyy-MM-dd |
|
||||
| POST /{orderId}/apply | orderId | Long | 是 | 路径参数,订单 ID |
|
||||
|
||||
### 4.2 请求体字段
|
||||
|
||||
**POST /v3/admin/order/invoice/upload**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| file | MultipartFile | 是 | 发票 PDF 文件(multipart/form-data) |
|
||||
|
||||
**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 文件大小(字节) |
|
||||
|
||||
**POST /v3/admin/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| amount | String | 是 | 开票金额(字符串,单位元),不超过订单总价 |
|
||||
| invoiceType | String | 是 | 发票类型,枚举见 6 节 |
|
||||
| titleType | String | 是 | 抬头类型:PERSONAL / COMPANY |
|
||||
| title | String | 是 | 发票抬头(姓名或公司名) |
|
||||
| taxNo | String | 条件必填 | 税号(titleType=COMPANY 时必填) |
|
||||
| bankName | String | 条件必填 | 开户行(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| bankAccount | String | 条件必填 | 银行账号(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registAddress | String | 条件必填 | 注册地址(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| registPhone | String | 条件必填 | 注册电话(invoiceType=VAT_SPECIAL 时必填) |
|
||||
| email | String | 条件必填 | 收票邮箱(invoiceType=ELECTRONIC 时必填) |
|
||||
| remark | String | 否 | 备注 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
**POST /v3/admin/order/invoice/upload**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | String | 上传成功后的 OSS 文件 URL,用于后续 issue / reupload 接口 |
|
||||
|
||||
**PUT issue 和 reupload:** 操作成功返回 HTTP 200,data 为 null。
|
||||
|
||||
**GET /v3/admin/order/invoice/page**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| records | Array | 发票列表(见下表) |
|
||||
| total | Long | 总记录数 |
|
||||
| page | Integer | 当前页码 |
|
||||
| pageSize | Integer | 每页条数 |
|
||||
| tabCounts | Object | 各 Tab 计数(requestedCount / issuedCount / pushedCount) |
|
||||
| stats | Object | 当月统计(currentMonthIssuedCount / currentMonthIssuedAmount) |
|
||||
|
||||
records 单条字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 发票 ID(Long 序列化) |
|
||||
| orderId | String | 所属订单 ID(Long 序列化) |
|
||||
| status | String | 发票状态枚举,见 6 节 |
|
||||
| statusName | String | 状态中文名 |
|
||||
| amount | String | 开票金额(字符串,单位元) |
|
||||
| invoiceType | String | 发票类型枚举 |
|
||||
| invoiceTypeName | String | 发票类型中文名 |
|
||||
| titleType | String | 抬头类型 |
|
||||
| title | String | 发票抬头 |
|
||||
| taxNo | String | 税号 |
|
||||
| fileUrl | String | 发票 PDF 地址(ISSUED / PUSHED 有值,其余 null) |
|
||||
| pdfName | String | PDF 文件名 |
|
||||
| pdfSize | Long | PDF 文件大小(字节) |
|
||||
| invoiceNo | String | 发票号码 |
|
||||
| issuedAt | String | 开票时间(ISO 8601) |
|
||||
| issuedBy | String | 开票人姓名(财务真实姓名) |
|
||||
| requestedAt | String | 申请时间(ISO 8601) |
|
||||
| requestedBy | String | 申请人(定制师姓名;mp 端为 userId 字符串) |
|
||||
| createTime | String | 记录创建时间 |
|
||||
|
||||
**POST /v3/admin/order/{orderId}/invoice/apply**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String | 新建发票 ID(Long 序列化) |
|
||||
| status | String | 固定为 REQUESTED(申请后待开票) |
|
||||
|
||||
**GET /v3/admin/order/{id}/invoices(已有接口,字段补全)**
|
||||
|
||||
以下字段由恒 null 变为有值:fileUrl / pdfName / pdfSize / invoiceNo / issuedAt / issuedBy / requestedAt / requestedBy。字段含义同 /page 出参表。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
**InvoiceStatus - 发票状态**
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| REQUESTED | 待开票 | 用户 / 定制师已申请,等待财务处理 |
|
||||
| ISSUED | 已开票 | 财务已完成开票并上传 PDF |
|
||||
| PUSHED | 已推送 | 发票已推送给客户 |
|
||||
| VOIDED | 已作废 | 发票已作废 |
|
||||
|
||||
**Tab 过滤值(GET /page 入参 tab 字段)**
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| ALL | 全部(默认) |
|
||||
| REQUESTED | 仅待开票 |
|
||||
| ISSUED | 仅已开票 |
|
||||
| PUSHED | 仅已推送 |
|
||||
|
||||
**InvoiceType - 发票类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| VAT_NORMAL | 增值税普通发票 |
|
||||
| VAT_SPECIAL | 增值税专用发票 |
|
||||
| ELECTRONIC | 电子发票 |
|
||||
|
||||
**TitleType - 抬头类型**
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| PERSONAL | 个人 |
|
||||
| COMPANY | 公司 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 常量 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 581502 | INVOICE_CANNOT_ISSUE | 发票状态非 REQUESTED,不允许开票 |
|
||||
| 581503 | INVOICE_CANNOT_REUPLOAD | 发票状态非 ISSUED / PUSHED,不允许重新上传 |
|
||||
| 581504 | INVOICE_FILE_UPLOAD_FAILED | 文件上传失败(OSS 或 hl-user-service 不可用) |
|
||||
| 581510 | INVOICE_ORDER_NOT_COMPLETED | 订单状态非 COMPLETED,不可申请开票 |
|
||||
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) |
|
||||
| 581512 | INVOICE_AMOUNT_EXCEED | 开票金额超过订单总价 |
|
||||
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
|
||||
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头 / 专票时税号为必填 |
|
||||
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行 / 银行账号 / 注册地址 / 注册电话为必填 |
|
||||
| 581516 | INVOICE_EMAIL_REQUIRED | 电子发票时邮箱为必填 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:财务两步完成开票
|
||||
|
||||
Step 1 上传文件:
|
||||
|
||||
POST /v3/admin/order/invoice/upload,Content-Type: multipart/form-data,file 字段传 PDF 文件。
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
Step 2 完成开票:
|
||||
|
||||
PUT /v3/admin/order/invoice/1234567890123456789/issue,请求体:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.2 边界情况:发票列表 tab=PUSHED 无记录
|
||||
|
||||
GET /v3/admin/order/invoice/page?tab=PUSHED&page=1&pageSize=20
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.3 业务失败:订单未完成,定制师代申请被拒
|
||||
|
||||
POST /v3/admin/order/9876543210987654321/invoice/apply,请求体:
|
||||
|
||||
|
||||
|
||||
订单状态为 CUSTOMIZING(定制中)时,响应:
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用:**
|
||||
- 财务开票:发票状态必须为 REQUESTED
|
||||
- 重新上传:发票状态为 ISSUED 或 PUSHED
|
||||
- 定制师代申请:订单状态必须为 COMPLETED(出行结束后)
|
||||
|
||||
**不适用:**
|
||||
- 订单处于 PAID、CUSTOMIZING、TRAVELLING 等非 COMPLETED 状态
|
||||
- 已存在有效发票(一单一票),不可重复申请
|
||||
- 开票金额超过订单 orderAmount(产品原售价)
|
||||
|
||||
**特殊边界:**
|
||||
- PUSHED 状态重新上传,状态自动降级为 ISSUED,需重新推送给客户
|
||||
- 发票列表 NONE Tab(未申请的 COMPLETED 订单)当前未实现,传 tab=NONE 返回空集合
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
**GET /v3/admin/order/{id}/invoices 出参字段补全:**
|
||||
|
||||
| 字段名 | 变更前 | 变更后 |
|
||||
|--------|--------|--------|
|
||||
| fileUrl | 恒 null | ISSUED / PUSHED 时有值(OSS URL) |
|
||||
| pdfName | 恒 null | 有值 |
|
||||
| pdfSize | 恒 null | 有值(字节数) |
|
||||
| invoiceNo | 恒 null | 有值(发票号码) |
|
||||
| issuedAt | 恒 null | 有值(ISO 8601 时间戳) |
|
||||
| issuedBy | 恒 null | 有值(开票人姓名) |
|
||||
| requestedAt | 恒 null | 有值(申请时间) |
|
||||
| requestedBy | 恒 null | 有值(申请人姓名) |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
- 破坏兼容性:GET /{id}/invoices 字段由 null 变为有值,前端对 null 的防护代码可安全保留。新增 5 个接口对旧版本无影响。
|
||||
- 前端同步上线:无强制要求,新接口按需接线,/invoices 字段补全向后兼容。
|
||||
- 回滚方案:回滚旧版本,新接口返回 404,/invoices 出参回退为 null,已写入 DB 的开票信息保留不丢失。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. upload 接口仅上传,不绑定发票:返回的 fileUrl 须在调用 issue / reupload 时传入,否则 OSS 文件闲置。
|
||||
2. PUSHED 状态重新上传后降级为 ISSUED,前端应提示操作者需要重新推送给客户。
|
||||
3. 专票五项(taxNo / bankName / bankAccount / registAddress / registPhone)缺任一即报 581515。
|
||||
4. requestedBy 记录的是登录管理员真实姓名(来自网关注入的 X-Admin-RealName),不是用户姓名。
|
||||
5. amount、currentMonthIssuedAmount 均为 String 类型,前端须按字符串接收,避免 JS 大数精度丢失。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- Issue(财务开票): https://git.1814.love:8443/wx/HL/issues/4172
|
||||
- Issue(定制师代申请): https://git.1814.love:8443/wx/HL/issues/4173
|
||||
- PR(财务开票 #4179): https://git.1814.love:8443/wx/HL/pulls/4179
|
||||
- PR(定制师代申请 #4180): https://git.1814.love:8443/wx/HL/pulls/4180
|
||||
- 后端负责人: yaosutu
|
||||
@@ -0,0 +1,101 @@
|
||||
# 【接口新增·管理后台】车管「微信通知模板」5 接口上线(模板 CRUD + 渲染预览)
|
||||
|
||||
> 服务:hl-fleet-service / hl-order-service-v3 / hl-common-core | 分支:dev-v3 | PR:#4157 #4158 | 工单:#4156
|
||||
> 已部署测试服 + 9443 真 token API 实测全通过(含真实订单跨服务渲染回填,2026-06-21)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **用途**:车管控制台「派单 Step3 弹窗」用的可复用微信通知文案模板 —— 模板增删改查 + 渲染预览。
|
||||
2. **本期不含自动发送**:只做模板管理 + 把模板渲染成文本,**车管渲染后手动复制粘贴**到微信发给司机。真实自动发送(企微/通知中心)留后续「派单 Step3」,本期不做。
|
||||
3. 路径前缀 `/admin/fleet/message-templates`,需 admin 登录(网关 `/admin/fleet/**` 已覆盖,无新增路由)。
|
||||
4. 模板类型 `templateType` 4 值:`hold_notify`(排车待确认)/ `trip_pack`(行程包)/ `cancel_notify`(取消通知)/ `change_notify`(变更通知);**同一类型至多 1 条默认模板**(置默认自动清同类型旧默认)。
|
||||
|
||||
## 1. 模板列表
|
||||
|
||||
`GET /admin/fleet/message-templates`
|
||||
|
||||
| 参数 | 位置 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| templateType | query | 否 | 按类型筛(不传返全部) |
|
||||
|
||||
出参(按 sortOrder 升序):
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","data":[{
|
||||
"id":"2068551732970831873","templateName":"排车待确认-标准","templateType":"hold_notify",
|
||||
"bodyTemplate":"您好{{driver.name}},订单{{order.no}}…","variablesHelp":null,
|
||||
"isDefault":true,"sortOrder":1,"createTime":"2026-06-21 12:28:59"}]}
|
||||
```
|
||||
|
||||
## 2. 新增模板
|
||||
|
||||
`POST /admin/fleet/message-templates`
|
||||
|
||||
入参:
|
||||
|
||||
```json
|
||||
{"templateName":"排车待确认-标准","templateType":"hold_notify",
|
||||
"bodyTemplate":"您好{{driver.name}},订单{{order.no}}已派车{{vehicle.plate}},今天{{system.today}}。",
|
||||
"variablesHelp":null,"isDefault":true,"sortOrder":1}
|
||||
```
|
||||
|
||||
出参:`{"code":200,"data":{"id":"…","templateName":"…","createTime":"…"}}`(返完整对象,前端取 `data.id`)。`isDefault=true` 时自动把同类型其它模板默认标记清 0。
|
||||
|
||||
## 3. 编辑模板
|
||||
|
||||
`PUT /admin/fleet/message-templates/{templateId}`,入参同新增。出参 `{"code":200,"message":"成功"}`。模板不存在返 **600801**。
|
||||
|
||||
## 4. 渲染预览 ★(派单 Step3 弹窗用)
|
||||
|
||||
`POST /admin/fleet/message-templates/{templateId}/render`
|
||||
|
||||
入参(orderId/vehicleId/driverId 均雪花、均可缺失):
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://api.test.1814.love:9443/admin/fleet/message-templates/{id}/render" \
|
||||
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{"orderId":2068240585604407298,"vehicleId":12345,"driverId":67890}'
|
||||
```
|
||||
|
||||
出参(真实订单实测):
|
||||
|
||||
```json
|
||||
{"code":200,"data":{
|
||||
"renderedBody":"订单HL20260620155236293客户张伟人数1出发2026-06-24天数3",
|
||||
"variablesUsed":["order.no","order.customer","order.headcount","order.startDate","order.days"]}}
|
||||
```
|
||||
|
||||
- `renderedBody`:模板正文里 `{{xxx}}` 占位符替换后的最终文本(车管复制此文本去微信发)。
|
||||
- `variablesUsed`:本次实际引用到的白名单变量 key 列表。
|
||||
- 白名单内变量值缺失(车/司机/订单查不到)→ 替换空串不报错;**白名单外占位符 → 600800**。
|
||||
- `order.*` 取真实订单(经 order-v3),`driver.*`/`vehicle.*` 取 fleet 档案,`system.today` 取服务端当天。
|
||||
|
||||
## 5. 删除模板(软删)
|
||||
|
||||
`DELETE /admin/fleet/message-templates/{templateId}`。软删(不影响已发记录)。**默认模板禁删 → 600803**(须先把另一条同类型设为默认再删本条)。
|
||||
|
||||
## 错误码
|
||||
|
||||
| code | message |
|
||||
|---|---|
|
||||
| 600800 | 模板变量缺失(模板引用了白名单外的变量) |
|
||||
| 600801 | 模板不存在 |
|
||||
| 600803 | 默认模板不可删除 |
|
||||
|
||||
## 17 变量白名单(render 可用)
|
||||
|
||||
| 分组 | 变量 key |
|
||||
|---|---|
|
||||
| driver | `driver.name`、`driver.phone`(脱敏) |
|
||||
| vehicle | `vehicle.plate`、`vehicle.model`、`vehicle.seats` |
|
||||
| order | `order.no`、`order.customer`、`order.headcount`、`order.startDate`、`order.endDate`、`order.days`、`order.pickupAt`、`order.dropoffAt`、`order.tripTheme` |
|
||||
| itinerary | `itinerary.url`、`itinerary.expireAt` |
|
||||
| system | `system.today` |
|
||||
|
||||
> 注:`order.pickupAt` / `order.dropoffAt` / `order.tripTheme` / `itinerary.*` 本期数据源未就绪,render 暂返空串(待行程包/接送站数据补齐)。
|
||||
|
||||
## 前端动作(车管控制台)
|
||||
|
||||
1. 「模板管理」页对接列表/新增/编辑/删除 4 接口;模板编辑器「插入变量」菜单数据源 = 出参 `variablesHelp` 字段(JSON,缺省可回退上方白名单)。
|
||||
2. 派单 Step3 弹窗:选模板 → 调 render 预览 → 展示 `renderedBody` → 车管复制粘贴到微信发司机(本期无后端自动发送)。
|
||||
3. 删除默认模板会被 600803 拦,前端提示「请先把其它同类型模板设为默认再删」。
|
||||
@@ -0,0 +1,87 @@
|
||||
# 【接口新增·管理后台】车管「证件到期看板」上线(GET /admin/fleet/board/expiry)
|
||||
|
||||
> 服务:hl-fleet-service | 分支:dev-v3 | PR:#4160 | 工单:#4159
|
||||
> 已部署测试服 + 9443 真 token API 实测全通过(基础调用 + kinds/buckets/keyword 筛选 + counts 全量不随筛选变,含真实数据,2026-06-21)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
1. **用途**:车管控制台「证件到期 / 到期提醒中心」聚合页(原型 `page_expiry`)数据源——一次查询聚合**四类证件到期**,按剩余天数分桶,供 KPI 卡 + 列表渲染。**纯查询、零写、无业务错误码。**
|
||||
2. 路径 `GET /admin/fleet/board/expiry`,需 admin 登录(网关 `/admin/fleet/**` 已覆盖,无新增路由)。
|
||||
3. **四类到期 kind**:`inspect`(车辆年检)/ `vehInsure`(车辆保险)/ `license`(司机驾照)/ `driverInsure`(司机年度保险,仅年保司机有)。
|
||||
4. **司机赛季 gate**:司机两类(license / driverInsure)仅纳入在岗司机(season=active/pending),已离队 / 黑名单司机排除;车辆两类无此限制(软删车自然排除)。
|
||||
|
||||
## 1. 入参(query · 均可选)
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| kinds | string[] | 类型多选 `inspect`/`vehInsure`/`license`/`driverInsure`,任一命中即返;空=四类全返 |
|
||||
| buckets | string[] | 桶多选 `expired`/`urgent`/`soon`/`watch`/`ok`,任一命中即返;空=全部桶。**建议默认取 expired+urgent+soon+watch(不含 ok),需正常项再传 ok** |
|
||||
| keyword | string | 模糊搜车牌 / 司机姓名(匹配 title 或 sub,大小写不敏感)|
|
||||
|
||||
> 多选参数传法:`?kinds=license&kinds=driverInsure` 或 `?buckets=expired&buckets=urgent`。
|
||||
|
||||
## 2. 出参
|
||||
|
||||
- `data.counts`:各桶计数 `{expired, urgent, soon, watch, ok, total}`,**全量·不随 kinds/buckets/keyword 筛选变化**(供 KPI 卡固定展示,total=各桶之和=四类到期项总数)。
|
||||
- `data.list`:筛选后明细数组,**排序固定**=桶优先级 `expired→urgent→soon→watch→ok` 升序、组内 `daysLeft` 升序(最紧急 / 过期最久在最前)。无数据 counts 全 0、list 返空数组。
|
||||
|
||||
`list` 项字段:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| id | 列表 key,`{targetId}::{kind}`(如 `蒙A-20221::vehInsure`)|
|
||||
| kind | 到期类型(见上 4 值)|
|
||||
| target | 实体域:`vehicle` / `driver` |
|
||||
| targetId | 车=车牌 plate / 司机=driverId |
|
||||
| title | 车=车牌 / 司机=姓名 |
|
||||
| sub | 车=`车型 · 车队`;司机驾照=`脱敏手机 · 准驾车型 驾照`;司机年险=`脱敏手机 · ¥年保费/年`(手机脱敏)|
|
||||
| expiryField | 到期字段名(inspect_due / insure_due / license_expire / insurance_annual_end)|
|
||||
| expiryDate | 到期日 `YYYY-MM-DD` |
|
||||
| daysLeft | 剩余天数(负=已过期 N 天)|
|
||||
| bucket | 所属桶(见下分桶口径)|
|
||||
|
||||
## 3. 分桶口径
|
||||
|
||||
`daysLeft = 到期日 − 今天`:`expired`(<0 已过期) / `urgent`(0-30) / `soon`(31-60) / `watch`(61-90) / `ok`(>90)。颜色 / 桶标签由前端渲染,后端只给 `bucket` + `daysLeft`。
|
||||
|
||||
## 4. 真实请求 / 响应(测试服实测节选)
|
||||
|
||||
```bash
|
||||
curl -k "https://api.test.1814.love:9443/admin/fleet/board/expiry" \
|
||||
-H "Authorization: Bearer <admin token>"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"counts": {"expired": 3, "urgent": 4, "soon": 1, "watch": 1, "ok": 48, "total": 57},
|
||||
"list": [
|
||||
{"id":"2067084362829979650::license","kind":"license","target":"driver",
|
||||
"targetId":"2067084362829979650","title":"王信","sub":"185****2756 · C1 驾照",
|
||||
"expiryField":"license_expire","expiryDate":"2022-02-05","daysLeft":-1597,"bucket":"expired"},
|
||||
{"id":"2065272148229865473::driverInsure","kind":"driverInsure","target":"driver",
|
||||
"targetId":"2065272148229865473","title":"恩克","sub":"135****5010 · ¥2/年",
|
||||
"expiryField":"insurance_annual_end","expiryDate":"2026-06-20","daysLeft":-1,"bucket":"expired"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
筛选示例(counts 恒为全量 57,仅 list 变化):
|
||||
|
||||
```bash
|
||||
# 仅驾照类
|
||||
curl -k ".../admin/fleet/board/expiry?kinds=license" -H "Authorization: Bearer <token>"
|
||||
# 仅已过期桶(list=3,=counts.expired)
|
||||
curl -k ".../admin/fleet/board/expiry?buckets=expired" -H "Authorization: Bearer <token>"
|
||||
# 关键词搜司机姓名(中文需 URL 编码)
|
||||
curl -k ".../admin/fleet/board/expiry?keyword=%E7%8E%8B" -H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
## 前端动作(车管控制台)
|
||||
|
||||
1. 「证件到期 / 到期提醒中心」页对接本接口:KPI 卡读 `data.counts`(5 桶固定计数),列表读 `data.list`(已排好序,前端按 bucket 上色即可)。
|
||||
2. 顶部筛选:类型多选传 `kinds`、紧急度多选传 `buckets`、搜索框传 `keyword`;**`counts` 是全量固定值不随筛选变,别用 list 长度反推 KPI**。
|
||||
3. 列表项颜色 / 「已过期 N 天」「N 天后到期」文案前端按 `bucket` + `daysLeft` 渲染。
|
||||
@@ -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,369 @@
|
||||
# 售后申诉中心 — 管理后台新增接口
|
||||
|
||||
- **端类型**:管理后台
|
||||
- **变更类型**:新增接口
|
||||
- **日期**:2026-06-22
|
||||
- **关联 Issue**:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- **关联 PR**:#4162 / #4165 / #4168 / #4185 / #4187
|
||||
- **后端负责人**:腰苏图(yaosutu)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口背景
|
||||
|
||||
v3 全新售后服务中心(com.hulalv.aftersale)上线,支持投诉与申诉两大类工单的完整生命周期管理。
|
||||
|
||||
管理后台侧新增 4 个接口:工单分页列表、工单详情、受理工单、处置工单。客服在管理后台完成工单全流程操作,其中退款类处置会触发企微 OA 审批,OA 通过后由系统自动驱动退款。
|
||||
|
||||
本次为全新域,不影响任何已有接口。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更清单
|
||||
|
||||
| 编号 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | GET | /v3/admin/aftersale/ticket/page | 售后工单分页列表 |
|
||||
| 2 | GET | /v3/admin/aftersale/ticket/{id} | 售后工单详情 |
|
||||
| 3 | POST | /v3/admin/aftersale/ticket/{id}/process | 受理工单(SUBMITTED → PROCESSING) |
|
||||
| 4 | POST | /v3/admin/aftersale/ticket/{id}/resolve | 处置工单(PROCESSING → RESOLVED/CLOSED) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| 认证方式 | JWT Bearer Token(管理端 Token,Gateway 校验) |
|
||||
| 幂等性 | GET 天然幂等;POST 接口状态守卫保证重复提交不会二次流转 |
|
||||
| 限流 | Gateway 默认限流策略 |
|
||||
| 路径前缀 | /v3/admin/aftersale/ticket |
|
||||
|
||||
---
|
||||
|
||||
## 四、接口入参
|
||||
|
||||
### 4.1 GET /v3/admin/aftersale/ticket/page — 工单分页列表
|
||||
|
||||
Query 参数(均可选):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| pageNo | Integer | 否 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 10 |
|
||||
| status | String | 否 | 工单状态过滤,见 §六枚举 |
|
||||
| category | String | 否 | 大类过滤:COMPLAINT / APPEAL |
|
||||
| orderId | String(Long) | 否 | 关联订单 ID 精确匹配 |
|
||||
|
||||
### 4.2 GET /v3/admin/aftersale/ticket/{id} — 工单详情
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
### 4.3 POST /v3/admin/aftersale/ticket/{id}/process — 受理工单
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
请求体(JSON,Body 整体可不传,效果等同传空对象):
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| assigneeId | Long | 否 | 指定处理客服 ID;不传则当前登录管理员自动接单 |
|
||||
| remark | String | 否 | 受理备注 |
|
||||
|
||||
### 4.4 POST /v3/admin/aftersale/ticket/{id}/resolve — 处置工单
|
||||
|
||||
路径参数:id(String/Long)必填,工单 ID。
|
||||
|
||||
请求体(JSON):
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| decision | String | 是 | APPROVE / REJECT | 处置决定 |
|
||||
| resolutionType | String | 条件必填 | REFUND / RECTIFY / REJECT | decision=APPROVE 时必填;COMPENSATION 首期不实现,传入返回 586020 |
|
||||
| resolutionAmount | Number | 条件必填 | >= 0 | decision=APPROVE 且 resolutionType=REFUND 时必填 |
|
||||
| remark | String | 否 | — | 处置说明,建议填写便于通知用户 |
|
||||
|
||||
---
|
||||
|
||||
## 五、出参字段
|
||||
|
||||
### 5.1 分页接口 — PageResult 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 42,
|
||||
"pageNo": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 详情及列表行 — TicketAdminRespVO 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | String(Long) | 工单 ID |
|
||||
| orderId | String(Long) | 关联订单 ID |
|
||||
| category | String | 大类枚举值:COMPLAINT / APPEAL |
|
||||
| categoryLabel | String | 大类中文名:投诉 / 申诉 |
|
||||
| type | String | 细分类型枚举值,见 §六 |
|
||||
| typeLabel | String | 细分类型中文名 |
|
||||
| targetRefType | String | 关联对象类型:ORDER / REFUND_APPLICATION |
|
||||
| targetRefId | String(Long) | 关联对象主键 |
|
||||
| sourceChannel | String | 来源渠道:MP / ADMIN |
|
||||
| title | String | 工单标题 |
|
||||
| description | String | 诉求描述 |
|
||||
| claimType | String | 用户诉求类型(可选) |
|
||||
| claimAmount | Number | 用户期望金额(可选,仅供参考,无约束力) |
|
||||
| attachments | Array<String> | 附件 URL 列表 |
|
||||
| status | String | 工单状态枚举值,见 §六 |
|
||||
| statusLabel | String | 工单状态中文名 |
|
||||
| assigneeId | String(Long) | 处理客服 ID,未受理时为 null |
|
||||
| decision | String | 处置决定:APPROVE / REJECT,未处置时为 null |
|
||||
| resolutionType | String | 处置类型,未处置时为 null |
|
||||
| resolutionAmount | Number | 后台核定金额,非退款类为 null |
|
||||
| resolutionRemark | String | 处置说明 |
|
||||
| approvalNo | String | 企微 OA 审批单号,非 REFUND 处置时为 null |
|
||||
| linkedRefundId | String(Long) | 关联退款单 ID(OA 通过触发退款后回填),null 表示未触发 |
|
||||
| createdAt | String(ISO 8601) | 创建时间,如 2026-06-22T10:30:00 |
|
||||
| updatedAt | String(ISO 8601) | 更新时间 |
|
||||
|
||||
### 5.3 受理 / 处置接口
|
||||
|
||||
```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、枚举 / 数据字典
|
||||
|
||||
### AftersaleCategory — 工单大类
|
||||
|
||||
| 枚举值 | 中文名 | 场景 |
|
||||
|--------|--------|------|
|
||||
| COMPLAINT | 投诉 | 对已发生的服务质量不满 |
|
||||
| APPEAL | 申诉 | 对某个处理结果不服,求复核 |
|
||||
|
||||
### AftersaleTicketType — 细分类型(type 字段)
|
||||
|
||||
| 枚举值 | 中文名 | 归属大类 |
|
||||
|--------|--------|----------|
|
||||
| ITINERARY | 行程 | COMPLAINT |
|
||||
| HOTEL | 酒店 | COMPLAINT |
|
||||
| VEHICLE | 车 | COMPLAINT |
|
||||
| GUIDE | 导游 | COMPLAINT |
|
||||
| OTHER | 其他 | COMPLAINT |
|
||||
| REFUND_REJECTED | 退款被拒 | APPEAL |
|
||||
| REFUND_AMOUNT_DISPUTE | 退款金额异议 | APPEAL |
|
||||
|
||||
注:type 必须与 category 匹配,否则返回 586012。
|
||||
|
||||
### AftersaleTicketStatus — 工单状态
|
||||
|
||||
| 枚举值 | 中文名 | 终态 | 说明 |
|
||||
|--------|--------|------|------|
|
||||
| SUBMITTED | 已提交 | 否 | 等待受理 |
|
||||
| PROCESSING | 处理中 | 否 | 客服受理中 / OA 审批进行中 |
|
||||
| RESOLVED | 已处置 | 否 | 处置方案落定,等待退款到账后关闭 |
|
||||
| CLOSED | 已关闭 | 是 | 流程终结 |
|
||||
| WITHDRAWN | 已撤回 | 是 | 用户主动撤回 |
|
||||
|
||||
状态流转:
|
||||
|
||||
```
|
||||
SUBMITTED -[受理]-> PROCESSING -[APPROVE+REFUND]-> RESOLVED -[退款到账]-> CLOSED
|
||||
|
|
||||
+-[APPROVE+RECTIFY]-> CLOSED
|
||||
+-[decision=REJECT]-> CLOSED
|
||||
+-[用户撤回]-> WITHDRAWN
|
||||
SUBMITTED -[用户撤回]-> WITHDRAWN
|
||||
```
|
||||
|
||||
### AftersaleDecision — 处置决定
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| APPROVE | 通过 | 诉求成立,按 resolutionType 执行 |
|
||||
| REJECT | 驳回 | 诉求不成立,直接关单 |
|
||||
|
||||
### AftersaleResolutionType — 处置类型
|
||||
|
||||
| 枚举值 | 中文名 | 走 OA | 说明 |
|
||||
|--------|--------|------|------|
|
||||
| REFUND | 退款 | 是 | 提交企微 OA 审批,通过后自动触发退款 |
|
||||
| RECTIFY | 整改 | 否 | 道歉/整改/解释,直接关单 |
|
||||
| REJECT | 驳回 | 否 | 诉求不成立,通知用户 |
|
||||
| COMPENSATION | 补偿(预留) | — | 首期不实现,传入返回 586020 |
|
||||
|
||||
### AftersaleTargetRefType — 关联对象类型
|
||||
|
||||
| 枚举值 | 中文名 | 场景 |
|
||||
|--------|--------|------|
|
||||
| ORDER | 订单 | 投诉场景 |
|
||||
| REFUND_APPLICATION | 退款申请 | 申诉场景 |
|
||||
|
||||
### AftersaleSourceChannel — 来源渠道
|
||||
|
||||
| 枚举值 | 中文名 |
|
||||
|--------|--------|
|
||||
| MP | 小程序 |
|
||||
| ADMIN | 管理后台 |
|
||||
|
||||
### claimType — 用户诉求类型(可选字段,无强制枚举约束)
|
||||
|
||||
| 常见值 | 中文名 |
|
||||
|--------|--------|
|
||||
| REFUND | 退款 |
|
||||
| RECTIFY | 整改 |
|
||||
| EXPLANATION | 要个说法 |
|
||||
|
||||
---
|
||||
|
||||
## 七、错误码
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 586009 | 工单不存在 | 传入 id 找不到工单 |
|
||||
| 586010 | 工单状态不允许该操作 | 通用状态守卫 |
|
||||
| 586011 | 同一对象已存在活跃工单 | 防重复提交 |
|
||||
| 586012 | category 与 type 不匹配 | 如 COMPLAINT 传了 REFUND_REJECTED |
|
||||
| 586017 | 受理时状态非法 | 只有 SUBMITTED 可受理 |
|
||||
| 586018 | 处置时状态非法 | 只有 PROCESSING 可处置 |
|
||||
| 586019 | 核定金额非法 | REFUND 处置时 resolutionAmount 为空或负数 |
|
||||
| 586020 | COMPENSATION 处置首期未实现 | resolutionType=COMPENSATION |
|
||||
|
||||
---
|
||||
|
||||
## 八、示例
|
||||
|
||||
### 8.1 典型成功 — 受理工单(指定客服)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/admin/aftersale/ticket/1895000000000001/process
|
||||
Authorization: Bearer {admin_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{"assigneeId": 42, "remark": "已联系供应商核实中"}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
### 8.2 典型成功 — 处置工单(REFUND 类,触发企微 OA)
|
||||
|
||||
请求:
|
||||
```
|
||||
POST /v3/admin/aftersale/ticket/1895000000000001/resolve
|
||||
Authorization: Bearer {admin_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"decision": "APPROVE",
|
||||
"resolutionType": "REFUND",
|
||||
"resolutionAmount": 500.00,
|
||||
"remark": "经核实酒店确实降级,同意退差价 500 元"
|
||||
}
|
||||
```
|
||||
|
||||
响应:```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
注:响应返回后工单仍为 PROCESSING,等待企微 OA 审批通过后系统自动推进,无需前端轮询。
|
||||
|
||||
### 8.3 边界情况 — 受理不传 Body(当前管理员自动接单)
|
||||
|
||||
```
|
||||
POST /v3/admin/aftersale/ticket/1895000000000001/process
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
响应:```json
|
||||
{"code": 200, "data": null}
|
||||
```
|
||||
|
||||
### 8.4 边界情况 — 处置驳回(decision=REJECT,无需 resolutionType 和金额)
|
||||
|
||||
```json
|
||||
{"decision": "REJECT", "remark": "经核实酒店等级属实,不予退款"}
|
||||
```
|
||||
|
||||
响应:```json
|
||||
{"code": 200, "data": null}
|
||||
``` 工单直接变 CLOSED。
|
||||
|
||||
### 8.5 业务失败 — REFUND 处置未填金额
|
||||
|
||||
```json
|
||||
{"decision": "APPROVE", "resolutionType": "REFUND"}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"code": 586019, "msg": "处置涉钱(REFUND)时 resolutionAmount 不能为空或负数"}
|
||||
```
|
||||
|
||||
### 8.6 业务失败 — 受理已 PROCESSING 工单
|
||||
|
||||
```json
|
||||
{"code": 586017, "msg": "受理时工单状态非法(只有 SUBMITTED 可受理)"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、业务边界
|
||||
|
||||
适用:
|
||||
- 工单处于正确状态时方可操作(受理须 SUBMITTED,处置须 PROCESSING)
|
||||
- assigneeId 不传时当前登录管理员自动接单
|
||||
- decision=REJECT 时 resolutionType / resolutionAmount 可不传,工单直接 CLOSED
|
||||
|
||||
不适用:
|
||||
- 终态工单(CLOSED / WITHDRAWN)不可再受理或处置
|
||||
- COMPENSATION 处置类型首期未实现,传入直接 586020
|
||||
|
||||
特殊边界:
|
||||
- REFUND 处置后工单不立即关闭,停在 PROCESSING 等企微 OA 审批,OA 通过后系统自动推进
|
||||
- resolutionAmount(后台核定)与 claimAmount(用户期望)完全独立,前端展示时请区分两者语义
|
||||
|
||||
---
|
||||
|
||||
## 十、修改前后对比
|
||||
|
||||
本次为全新域新增接口,无旧接口对比。
|
||||
|
||||
---
|
||||
|
||||
## 十一、影响评估 / 回滚
|
||||
|
||||
本次为全新域,不影响任何已有接口,可独立上线。
|
||||
|
||||
回滚方案:回滚后端服务版本即可;Flyway DDL 由后端管理,前端无感知。
|
||||
|
||||
---
|
||||
|
||||
## 十二、注意事项
|
||||
|
||||
1. 双金额区分:claimAmount(用户期望,无约束力)和 resolutionAmount(后台核定,唯一退款依据)是两个独立字段,请在列表和详情页分别展示,不要混用。
|
||||
2. REFUND 处置的异步性:调用 resolve 后工单状态仍为 PROCESSING;可根据 decision=APPROVE + resolutionType=REFUND + approvalNo 非空,展示等待 OA 审批的状态。
|
||||
3. 所有 ID 字段为字符串:id / orderId / assigneeId / targetRefId / linkedRefundId 均序列化为字符串(雪花 ID 防 JS 精度丢失),前端不要转 Number。
|
||||
4. attachments 为 URL 数组:直接用于展示或下载,无需额外解析。
|
||||
|
||||
---
|
||||
|
||||
## 十三、关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/4161
|
||||
- PR #4162(售后基建 + 分页/详情):https://git.1814.love:8443/wx/HL/pulls/4162
|
||||
- PR #4165(受理/处置接口 + 企微 OA 接入):https://git.1814.love:8443/wx/HL/pulls/4165
|
||||
- PR #4168(OA 回调内部接口):https://git.1814.love:8443/wx/HL/pulls/4168
|
||||
- PR #4185(小程序端接口):https://git.1814.love:8443/wx/HL/pulls/4185
|
||||
- PR #4187(退款驱动联动):https://git.1814.love:8443/wx/HL/pulls/4187
|
||||
- 后端负责人:腰苏图(yaosutu)
|
||||
@@ -0,0 +1,43 @@
|
||||
# 【修改接口·管理后台】配房/房间分配移除「多家庭团」familyIndex(家庭序号)字段 (#4193)
|
||||
|
||||
> **PR**: #4193 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-22
|
||||
|
||||
## 1. 背景
|
||||
|
||||
家庭概念已废弃。「多家庭团」对应字段 `familyIndex`(家庭序号)原为「一个团/订单拆成多个家庭分别配房」设计(仅团期 GROUP 多家庭场景使用,团期配房已暂缓,散客单家庭恒为 1)。本次**彻底移除** `familyIndex`:相关请求/响应 VO 不再包含该字段,DB 列已 DROP。
|
||||
|
||||
> 前端如有「家庭序号」相关入参/展示/排序逻辑,请一并清理。
|
||||
|
||||
## 2. 变更清单(familyIndex 字段移除)
|
||||
|
||||
| # | 场景 | 方向 | 受影响字段 |
|
||||
|---|------|------|-----------|
|
||||
| 1 | 配房批量提交项 | 请求 | `AssignmentItemReqVO.familyIndex` 移除 |
|
||||
| 2 | 配房提交结果项 | 响应 | `AssignmentSubmitRespVO.familyIndex` 移除 |
|
||||
| 3 | 房间分配新增 | 请求 | `RoomAssignReqVO.familyIndex` 移除(含原「familyIndex 连续性校验」一并取消) |
|
||||
| 4 | 房间分配视图 | 响应 | `RoomAssignmentVO.familyIndex` / `HotelAssignmentVO.familyIndex` 移除 |
|
||||
| 5 | 订单详情·行程配房项 | 响应 | 订单详情 `itinerary` 内配房项 `familyIndex` 移除 |
|
||||
| 6 | 房控扣库存(内部) | 请求 | `HouseDeductRoomStockReqVO.familyIndex` 移除 |
|
||||
|
||||
## 3. 行为变化
|
||||
|
||||
- **入参**:上述请求 VO 传入 `familyIndex` 将被忽略(字段已不存在,多传无副作用,但建议前端移除)。
|
||||
- **出参**:上述响应 VO 不再返回 `familyIndex` 字段。
|
||||
- **排序**:配房记录列表原「按 `dayNumber`、`familyIndex` 升序」→ 现「按 `dayNumber` 升序」(同日多行按写入顺序稳定)。展示层若依赖 familyIndex 分组/排序,改为仅按 dayNumber。
|
||||
- **DB**:`order_room_assignment` / `house_hotel_assignment` 的 `family_index` 列已 DROP(`order_hotel_assignment` 此前已移除)。
|
||||
|
||||
## 4. 前端处理建议
|
||||
|
||||
- 移除配房表单/提交体中的「家庭序号」输入与 `familyIndex` 入参。
|
||||
- 移除配房列表/订单详情中对 `familyIndex` 的读取、分组、展示。
|
||||
- 多家庭分组 UI(如有)整体下线。
|
||||
|
||||
## 5. 影响评估 / 回滚
|
||||
|
||||
- **兼容性**:响应去字段属破坏性变更(前端读 `familyIndex` 将得 undefined);入参去字段向后兼容(多传被忽略)。
|
||||
- **回滚**:revert PR #4193 重新部署 hl-order-service-v3(注:DB 列已 DROP,回滚需配套补迁移,建议前滚不回滚)。
|
||||
|
||||
## 6. 关联
|
||||
|
||||
- **PR**: [#4193](https://git.1814.love:8443/wx/HL/pulls/4193)(前端反馈驱动,无关联 Issue)
|
||||
- **后端负责人**: @wx
|
||||
@@ -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,89 @@
|
||||
# 【修改接口·管理后台】房务月度对账 新增月度环比 + PDF 导出(mmg ledger 缺口答复) (#4202)
|
||||
|
||||
> **PR**: #4202 | **工单**: #4201 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-22
|
||||
|
||||
## ⚠️ 关键说明(先读)
|
||||
|
||||
针对 mmg 反馈的「ledger 月度对账页原型有 4 块 UI 当前后端无端点」缺口,逐项答复 + 落地:
|
||||
|
||||
- ✅ **环比**:已加,`GET /monthly` 的 `overview.monthOverMonth`(见 §2)。
|
||||
- ✅ **PDF 导出**:已加,`GET /monthly/export?format=pdf`(见 §3)。
|
||||
- 🟢 **间夜**:后端**一直就有** `overview.roomNights`,前端直接恢复「本月间夜」卡即可(无需后端改动)。
|
||||
- ⏸️ **预付核销 / 预付余额**:维持移除(与既定「预付先不做」一致)。
|
||||
- ⏸️ **毛利**:**本轮不做**,原型「毛利」卡请暂不恢复,原因见 §4(数据真相)。
|
||||
|
||||
金额 / 环比比率字段均为 **String**(后端 `@JsonSerialize`),前端按字符串收。
|
||||
|
||||
---
|
||||
|
||||
## 1. 月度环比(新增 `overview.monthOverMonth`)
|
||||
|
||||
`GET /v3/admin/house/reconciliation/monthly?month=YYYY-MM` 的 `data.overview` **新增**一个嵌套对象 `monthOverMonth`(与上月同口径对比):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `prevMonth` | String | 上月(YYYY-MM,如 "2026-05") |
|
||||
| `prevRoomNights` | Integer | 上月间夜合计 |
|
||||
| `roomNightsRatio` | String | 间夜环比 %(本月较上月);**上月为 0/无数据时为 `null`** |
|
||||
| `prevTotalAmount` | String | 上月应付合计(元) |
|
||||
| `totalAmountRatio` | String | 应付环比 %;上月为 0 时 `null` |
|
||||
| `prevOrderCount` | Integer | 上月订单数 |
|
||||
| `orderCountRatio` | String | 订单数环比 %;上月为 0 时 `null` |
|
||||
|
||||
- 环比口径:`环比% = (本月 − 上月) / 上月 × 100`,保留 2 位小数,符号保留(下降为负,如 `"-12.50"`)。
|
||||
- **`ratio` 为 `null` 时前端显示「—」**(上月基数为 0 无法定义增长率,后端不返 0% 或 ∞)。
|
||||
- `/monthly/export?format=xlsx` 的概览块也追加了 3 行环比(间夜环比 / 应付环比 / 订单数环比)。
|
||||
|
||||
## 2. PDF 导出(`/monthly/export` 加 `format` 参)
|
||||
|
||||
复用既有导出端点,**新增可选查询参 `format`**:
|
||||
|
||||
`GET /v3/admin/house/reconciliation/monthly/export?month=YYYY-MM&format=pdf`
|
||||
|
||||
| format | 行为 | Content-Type |
|
||||
|--------|------|-------------|
|
||||
| 不传 / `xlsx` | 原 Excel 导出(**向后兼容,行为不变**) | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
||||
| `pdf` | PDF 导出(概览块 + 环比 + 按酒店账单表,A4) | `application/pdf` |
|
||||
| 其它值 | 业务错误(HTTP 200 + `code:808171`) | `application/json` |
|
||||
|
||||
- PDF 文件名 `house-reconciliation-{month}.pdf`;中文正常(服务端 Chromium 渲染 HTML)。
|
||||
- 前端「生成 PDF」按钮直接 `window.open` / 下载该 URL 即可(带 `format=pdf`)。
|
||||
|
||||
## 3. 三个「不恢复」项的明确答复
|
||||
|
||||
- **间夜卡**:`overview.roomNights`(本月)+ 每酒店 `hotels[].roomNights` 一直都有,前端恢复卡片即可。
|
||||
- **预付核销 tab / 预付余额卡**:维持移除。月度对账当前只覆盖现结(CASH_PAID),预付维度属「先不做」范围。
|
||||
- **毛利卡**:本轮不做,**请暂不恢复**。原因:月度对账是「按酒店应付」视角,住宿域数据只有**成本**(实际成本 / 协议价 / 实际成交价),**全域没有「客户为住宿付了多少」的按酒店收入**(定制住宿收入捆在订单产品总价里、未按酒店/晚拆分)。原型「毛利 = 收入 − 成本」缺收入这一半,真售卖毛利需产品/财务先定义「订单住宿收入如何归集到酒店」的口径后才能算,届时单独立项再通知。
|
||||
|
||||
## 4. curl 实测(2026-06-22 测试服,已通过网关)
|
||||
|
||||
```bash
|
||||
# 1) 月度对账(含环比)
|
||||
curl 'https://api.test.1814.love:9443/v3/admin/house/reconciliation/monthly?month=2026-06' \
|
||||
-H 'Authorization: Bearer <admin-token>'
|
||||
# → data.overview.monthOverMonth = {"prevMonth":"2026-05","prevRoomNights":0,"roomNightsRatio":null,
|
||||
# "prevTotalAmount":"0","totalAmountRatio":null,"prevOrderCount":0,"orderCountRatio":null}
|
||||
# (该月无现结数据故各 ratio=null;有数据时如 "20.00")
|
||||
|
||||
# 2) PDF 导出
|
||||
curl 'https://api.test.1814.love:9443/v3/admin/house/reconciliation/monthly/export?month=2026-06&format=pdf' \
|
||||
-H 'Authorization: Bearer <admin-token>' -o recon.pdf
|
||||
# → 200 application/pdf, %PDF- 开头(实测 141KB)
|
||||
|
||||
# 3) 默认仍是 xlsx(向后兼容)
|
||||
curl '.../monthly/export?month=2026-06' -H 'Authorization: Bearer <token>' -o recon.xlsx # PK zip
|
||||
# 4) 非法 format
|
||||
curl '.../monthly/export?month=2026-06&format=csv' ... # → 200 {"code":808171,"message":"导出格式非法,仅支持 xlsx/pdf"}
|
||||
```
|
||||
|
||||
## 5. 前端处理建议
|
||||
|
||||
- 「本月间夜」卡:恢复,读 `overview.roomNights`。
|
||||
- 「环比 %」:读 `overview.monthOverMonth.*Ratio`,为 `null` 显示「—」,否则显示 `值 + "%"`(注意带符号)。
|
||||
- 「生成 PDF」按钮:下载 `/monthly/export?month=...&format=pdf`。
|
||||
- 「毛利」卡 / 「预付」相关 UI:保持移除(待产品决议)。
|
||||
|
||||
## 6. 影响评估 / 回滚
|
||||
|
||||
- **兼容性**:`overview` 加字段、导出加 `format` 参,**均向后兼容**(旧前端不读新字段 / 不传 format 行为不变)。
|
||||
- **回滚**:revert PR #4202 重新部署 hl-order-service-v3(无 DB 迁移,纯代码)。
|
||||
@@ -0,0 +1,313 @@
|
||||
# 订单增减项改造 — 新增统一端点 + 出参增 itemCode/itemName
|
||||
|
||||
- **日期**: 2026-06-22
|
||||
- **端类型**: 管理后台
|
||||
- **服务**: hl-order-service-v3(端口 8086)
|
||||
- **接口路径前缀**: /v3/admin/order
|
||||
- **PR**: [#4207](https://git.1814.love:8443/wx/HL/pulls/4207)
|
||||
- **Issue**: [#4205](https://git.1814.love:8443/wx/HL/issues/4205)
|
||||
- **Commit**: [3a3577aa9](https://git.1814.love:8443/wx/HL/commit/3a3577aa9de39857d66c3e1811420ce175956f0a)
|
||||
- **后端负责人**: 腰苏图(yaosutu)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
订单详情页原「添加优惠」按鈕,升级为「订单增减项」:**既能加优惠(减项)又能加费用(增项)**。改造前项目名是自由文本输入;改造后改为**数据字典选项**,前端渲染 chip 单选。
|
||||
|
||||
本次变更要点:
|
||||
|
||||
- **新增** 统一端点 POST /v3/admin/order/{orderId}/adjustment,direction 字段区分减项/增项,前端只维护一套提交逻辑
|
||||
- **新建** 两个数据字典 type(order_discount_item / order_surcharge_item),前端动态拉取渲染
|
||||
- **增强** 优惠/附加费清单接口出参,discounts[]/surcharges[] 各新增 itemCode + itemName 字段
|
||||
- 旧端点 POST /v3/admin/order/{orderId}/discounts 和 /surcharges 已标为 @Deprecated,前端改调 /adjustment;**撤销端点不变**
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| # | 变更类型 | 接口 / 字段 | 影响 |
|
||||
|---|----------|------------|------|
|
||||
| 1 | ✨ 新增接口 | POST /v3/admin/order/{orderId}/adjustment | 统一增减项写入,替代旧两端点 |
|
||||
| 2 | ✨ 新增字典 | order_discount_item(8 项) | 减项/优惠选项,前端动态拉取 |
|
||||
| 3 | ✨ 新增字典 | order_surcharge_item(6 项) | 增项/费用选项,前端动态拉取 |
|
||||
| 4 | ✨ 出参新增字段 | discounts[].itemCode / discounts[].itemName | 优惠/附加费清单接口 |
|
||||
| 5 | ✨ 出参新增字段 | surcharges[].itemCode / surcharges[].itemName | 同上 |
|
||||
| 6 | 🔧 旧端点废弃 | POST /v3/admin/order/{orderId}/discounts | @Deprecated,Swagger 隐藏,前端不再调用 |
|
||||
| 7 | 🔧 旧端点废弃 | POST /v3/admin/order/{orderId}/surcharges | @Deprecated,Swagger 隐藏,前端不再调用 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情
|
||||
|
||||
### 3.1 新增统一增减项端点
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 | POST |
|
||||
| 路径 | /v3/admin/order/{orderId}/adjustment |
|
||||
| 接口名 | 订单增减项(统一端点,按 direction 分流) |
|
||||
| 认证 | Bearer JWT(管理后台 token) |
|
||||
| 幂等性 | 有(@Idempotent,同用户同订单重复提交返回首次结果) |
|
||||
| 并发锁 | 有(@Lock4j,同订单并发请求串行执行) |
|
||||
| Content-Type | application/json |
|
||||
|
||||
### 3.2 字典查询接口(走 user-service,路径与现有字典接口相同)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | /admin/dict/data/order_discount_item | 获取减项选项列表 |
|
||||
| GET | /admin/dict/data/order_surcharge_item | 获取增项选项列表 |
|
||||
|
||||
返回格式示例:
|
||||
|
||||
前端渲染 chip 单选,提交时传 value(即 itemCode)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| orderId | Long | 是 | 订单 ID |
|
||||
|
||||
### 4.2 请求体字段(OrderAdjustmentReqVO)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
||||
|---|---|---|---|---|
|
||||
| direction | String | 是 | 增减方向:REDUCE=减项/优惠,ADD=增项/附加费 | "REDUCE" |
|
||||
| itemCode | String | 是 | 项目代码:REDUCE 时取 order_discount_item 的 value,ADD 时取 order_surcharge_item 的 value | "OLD_CUSTOMER" |
|
||||
| amount | String | 是 | 金额(元),必须 > 0,传字符串防 JS 精度丢失 | "200.00" |
|
||||
| remark | String | 否 | 备注,最多 500 字 | "春节活动老客户优惠" |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 统一端点出参
|
||||
|
||||
direction=REDUCE 时返回优惠记录(OrderDiscountRespVO),direction=ADD 时返回附加费记录(OrderSurchargeRespVO)。
|
||||
|
||||
**direction=REDUCE 出参(优惠记录)**
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|---|---|---|---|
|
||||
| id | String | 优惠记录主键 | "8800001234567" |
|
||||
| orderId | String | 订单 ID | "12345678901234" |
|
||||
| discountType | String | 优惠类型(统一端点写入时存 itemCode 值) | "OLD_CUSTOMER" |
|
||||
| itemCode | String | **新增** 优惠项目 itemCode(字典 order_discount_item value) | "OLD_CUSTOMER" |
|
||||
| itemName | String | **新增** 优惠项目中文名(字典 label 快照) | "老客户回访" |
|
||||
| discountName | String | 优惠描述(与 itemName 相同,为字典中文名快照) | "老客户回访" |
|
||||
| discountAmount | String | 优惠金额(元) | "200.00" |
|
||||
| sourceType | String | 来源类型(统一端点固定 MANUAL) | "MANUAL" |
|
||||
| sourceRefId | String/null | 来源关联 ID | null |
|
||||
| status | String | 记录状态(写入后固定 ACTIVE) | "ACTIVE" |
|
||||
| operatorId | String | 操作人 ID(JWT 派生) | "30001" |
|
||||
| operatorName | String | 操作人姓名快照 | "李定制师" |
|
||||
| createdAt | String | 写入时间(ISO 8601) | "2026-06-22T16:08:32" |
|
||||
| orderDiscountAmountAfter | String | 写入后订单优惠总额镜像(本订单所有 ACTIVE 优惠合计) | "200.00" |
|
||||
|
||||
**direction=ADD 出参(附加费记录)**
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|---|---|---|---|
|
||||
| id | String | 附加费记录主键 | "8800009876543" |
|
||||
| orderId | String | 订单 ID | "12345678901234" |
|
||||
| itemCode | String | **新增** 增项项目 itemCode(字典 order_surcharge_item value) | "ROOM_UPGRADE" |
|
||||
| itemName | String | **新增** 增项项目中文名(字典 label 快照) | "升级房型" |
|
||||
| surchargeName | String | 附加费描述(与 itemName 相同) | "升级房型" |
|
||||
| surchargeAmount | String | 附加费金额(元) | "300.00" |
|
||||
| sourceType | String | 来源类型(统一端点固定 MANUAL) | "MANUAL" |
|
||||
| sourceRefId | String/null | 来源关联 ID | null |
|
||||
| status | String | 记录状态(写入后固定 ACTIVE) | "ACTIVE" |
|
||||
| operatorId | String | 操作人 ID | "30001" |
|
||||
| operatorName | String | 操作人姓名快照 | "李定制师" |
|
||||
| createdAt | String | 写入时间(ISO 8601) | "2026-06-22T16:12:00" |
|
||||
| orderSurchargeAmountAfter | String | 写入后订单附加费总额镜像(本订单所有 ACTIVE 附加费合计) | "300.00" |
|
||||
|
||||
### 5.2 优惠/附加费清单接口新增字段
|
||||
|
||||
接口:GET /v3/admin/order/{orderId}/discounts-surcharges(路径不变)
|
||||
|
||||
**discounts[] 新增字段**
|
||||
|
||||
| 字段 | 类型 | 说明 | 历史数据 |
|
||||
|---|---|---|---|
|
||||
| itemCode | String/null | 优惠项目 itemCode | null(早鸟、行程编辑等历史写入行) |
|
||||
| itemName | String/null | 优惠项目中文名 | null(历史行兼容) |
|
||||
|
||||
**surcharges[] 新增字段**
|
||||
|
||||
| 字段 | 类型 | 说明 | 历史数据 |
|
||||
|---|---|---|---|
|
||||
| itemCode | String/null | 增项项目 itemCode | null(历史行兼容) |
|
||||
| itemName | String/null | 增项项目中文名 | null(历史行兼容) |
|
||||
|
||||
历史行(早鸟自动写入、行程编辑产生的记录)itemCode/itemName 返回 null,前端渲染时降级显示 discountName/surchargeName 字段。
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 AdjustmentDirection 枚举(入参 direction 字段)
|
||||
|
||||
| 值 | 含义 | 落库位置 |
|
||||
|---|---|---|
|
||||
| REDUCE | 减项/优惠,订单总额减少 | order_discount 表 |
|
||||
| ADD | 增项/附加费,订单总额增加 | order_surcharge 表 |
|
||||
|
||||
### 6.2 order_discount_item 字典(dictType=order_discount_item)
|
||||
|
||||
减项/优惠选项,前端调 GET /admin/dict/data/order_discount_item 动态渲染。
|
||||
|
||||
| value(itemCode,提交时传此值) | label(展示中文) | sort |
|
||||
|---|---|---|
|
||||
| OLD_CUSTOMER | 老客户回访 | 1 |
|
||||
| DISTRIBUTION | 分销返点 | 2 |
|
||||
| PEER_PRICE | 同行价 | 3 |
|
||||
| FAMILY_PRICE | 亲友价 | 4 |
|
||||
| COMPLAINT_COMP | 投诉补偿 | 5 |
|
||||
| FESTIVAL | 生日节日 | 6 |
|
||||
| GROUPON | 团购优惠 | 7 |
|
||||
| OTHER | 其他 | 8 |
|
||||
|
||||
### 6.3 order_surcharge_item 字典(dictType=order_surcharge_item)
|
||||
|
||||
增项/费用选项,前端调 GET /admin/dict/data/order_surcharge_item 动态渲染。
|
||||
|
||||
| value(itemCode,提交时传此值) | label(展示中文) | sort |
|
||||
|---|---|---|
|
||||
| ROOM_UPGRADE | 升级房型 | 1 |
|
||||
| ADD_ENTERTAINMENT | 加购娱乐项目 | 2 |
|
||||
| ADD_VEHICLE | 增加用车 | 3 |
|
||||
| EXTRA_PERSON | 超员补位 | 4 |
|
||||
| ADD_ITINERARY | 行程加点 | 5 |
|
||||
| OTHER | 其他 | 6 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| 200 | 成功 | — |
|
||||
| 581306 | ADJUSTMENT_ITEM_INVALID:itemCode 不在对应字典范围内 | direction=REDUCE 时 itemCode 不在 order_discount_item;direction=ADD 时 itemCode 不在 order_surcharge_item;或字典服务不可用 |
|
||||
| 400 | 参数校验失败 | direction/itemCode 为空,amount 为空或 ≤ 0,remark 超 500 字 |
|
||||
| 404(内部业务码) | 订单不存在 | orderId 无对应订单 |
|
||||
|
||||
> 字典服务(user-service)不可用时,后端降级返回空选项集,任何 itemCode 均触发 581306 拒绝(保守策略)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功——减项(老客户优惠 200 元)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.2 典型成功——增项(升级房型 300 元)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.3 业务失败——itemCode 不合法(错误码 581306)
|
||||
|
||||
请求:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
> "VIP" 是旧端点历史枚举值,不在 order_discount_item 字典,统一端点拒绝。
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
**适用**:
|
||||
- 订单处于任何状态均可操作(无订单状态限制)
|
||||
- 一笔订单可叠加多条优惠/附加费记录,每条独立成行
|
||||
|
||||
**不适用**:
|
||||
- 旧端点(/discounts、/surcharges)已废弃,前端不应再调用
|
||||
|
||||
**特殊边界**:
|
||||
- 减项/增项金额均无上限校验,业务人员自行把握
|
||||
- 历史行(早鸟写入、行程编辑产生)itemCode/itemName 返回 null,前端渲染时降级显示 discountName/surchargeName
|
||||
- 同一请求遭并发时(@Lock4j),后到的排队等待,不会重复写入
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
### 10.1 写入端点
|
||||
|
||||
| 场景 | 改造前 | 改造后 |
|
||||
|---|---|---|
|
||||
| 加优惠 | POST /{orderId}/discounts,discountName 自由文本 | POST /{orderId}/adjustment,direction=REDUCE + itemCode 字典选项 |
|
||||
| 加附加费 | POST /{orderId}/surcharges,surchargeName 自由文本 | POST /{orderId}/adjustment,direction=ADD + itemCode 字典选项 |
|
||||
|
||||
### 10.2 出参字段变化(清单接口)
|
||||
|
||||
| 位置 | 改造前 | 改造后 |
|
||||
|---|---|---|
|
||||
| discounts[n] | 无 itemCode / itemName 字段 | 新增 itemCode、itemName(统一端点写入时有值,历史行 null) |
|
||||
| surcharges[n] | 无 itemCode / itemName 字段 | 新增 itemCode、itemName(同上) |
|
||||
|
||||
### 10.3 项目名来源变化
|
||||
|
||||
| 改造前 | 改造后 |
|
||||
|---|---|
|
||||
| discountName/surchargeName 由用户自由输入文本 | 由 itemCode 查字典 label 后端回填,存入 discountName/surchargeName 快照列 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
**前端需同步操作**:
|
||||
|
||||
1. 订单增减项弹窗的原因/项目 chips 改为动态调字典接口渲染(/admin/dict/data/order_discount_item 和 order_surcharge_item),不再硬编码
|
||||
2. 提交时调 /adjustment,传 direction + itemCode + amount + remark
|
||||
3. 清单接口(discounts/surcharges 列表)新增 itemCode/itemName 字段,建议优先显示 itemName,null 时降级显示 discountName/surchargeName
|
||||
|
||||
**破坏兼容性**:旧端点仍保留可调(非破坏性),但 Swagger 已隐藏,前端代码应切换。
|
||||
|
||||
**上线顺序建议**:user-service 须先于或同时于 order-v3 部署(字典须先存在);建议先上线后端,再上线前端。
|
||||
|
||||
**回滚方案**:
|
||||
- 后端:重新部署旧版 hl-order-service-v3 + hl-user-service(旧端点仍在)
|
||||
- 前端:切回调旧两端点即可
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. **user-service 先行**:order-v3 /adjustment 端点运行时调 user-service 拉字典,若字典不存在,任何 itemCode 均触发 581306 拒绝。两服务须同批或 user-service 先上。
|
||||
2. **itemCode 大小写敏感**:传 "OLD_CUSTOMER" 不能传 "old_customer"。
|
||||
3. **amount 传字符串**:防 JS 数值精度丢失,传 "200.00" 而非数字 200.00。
|
||||
4. **字典 value 不可随意修改**:value 已写入历史快照字段,运营改 value 会导致历史数据 itemCode 无法回查中文名(itemName 返回 null,discountName 仍有值可降级)。
|
||||
5. **多条叠加**:多次调 /adjustment 每次写一条新记录,清单接口可见全部明细,镜像值为当前全部 ACTIVE 之和。
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **Issue**: https://git.1814.love:8443/wx/HL/issues/4205
|
||||
- **PR**: https://git.1814.love:8443/wx/HL/pulls/4207
|
||||
- **Commit**: https://git.1814.love:8443/wx/HL/commit/3a3577aa9de39857d66c3e1811420ce175956f0a
|
||||
- **后端负责人**: 腰苏图(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,495 @@
|
||||
# 退款申请管理接口说明(管理后台)
|
||||
|
||||
- **日期**: 2026-06-22
|
||||
- **端类型**: 管理后台
|
||||
- **接口路径前缀**: /v3/admin/refund
|
||||
- **服务**: hl-order-service-v3(端口 8086,网关前缀 /v3)
|
||||
- **说明**: 本文档为现有已上线接口的对接说明,非新版本改动
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
退款申请管理模块提供客服/管理员全生命周期操作退款申请的能力,包括代客户发起退款、分页查询、审核(支持多级审核,当前配置为单级)、触发实退、手动完成退款(测试/线下场景)、查退款执行记录、查状态流水。
|
||||
|
||||
存在两套退款通道:
|
||||
|
||||
- 申请单通道(/v3/admin/refund/application/*):标准退款流程,含政策计算、多级审核、自动实退
|
||||
- 直接退款通道(/v3/admin/refund/orders/{orderId} 和 /{refundId} 和 /order/{orderId}/list):绕过申请单直接调微信退款 API,需 SUPER_ADMIN/ADMIN/FINANCE 角色,用于紧急场景
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| 接口说明 | POST /v3/admin/refund/application | 客服代下退款申请 |
|
||||
| 接口说明 | GET /v3/admin/refund/application/page | 退款申请分页查询(14 个筛选维度) |
|
||||
| 接口说明 | GET /v3/admin/refund/application/{id} | 退款申请详情(含审核/执行记录) |
|
||||
| 接口说明 | POST /v3/admin/refund/review | 提交审核(APPROVED/REJECTED/PARTIAL) |
|
||||
| 接口说明 | GET /v3/admin/refund/review/list/{appId} | 查审核历史 |
|
||||
| 接口说明 | POST /v3/admin/refund/execute/{appId} | 触发实退(APPROVED->REFUNDING) |
|
||||
| 接口说明 | PUT /v3/admin/refund/application/{applicationId}/complete | 手动完成退款(不调微信) |
|
||||
| 接口说明 | GET /v3/admin/refund/application/{id}/status-log | 查退款状态流水 |
|
||||
| 接口说明 | POST /v3/admin/refund/orders/{orderId} | 直接退款(绕申请单,需 FINANCE 角色) |
|
||||
| 接口说明 | GET /v3/admin/refund/{refundId} | 退款记录详情 |
|
||||
| 接口说明 | GET /v3/admin/refund/order/{orderId}/list | 按订单查询退款记录列表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情(通用)
|
||||
|
||||
- **认证**: JWT,Header Authorization: Bearer token,需管理员身份
|
||||
- **直接退款通道**(POST /orders/{orderId})额外需要角色:SUPER_ADMIN / ADMIN / FINANCE(由 token 中 role 字段判断)
|
||||
- **幂等性**: 客服代下退款申请使用 DB 唯一索引防重(同一订单仅允许一条活跃申请,状态 PENDING/APPROVED/REFUNDING 为活跃);触发实退支持 CAS 防重复触发;直接退款使用 @Idempotent 基于 orderId 的时间窗去重
|
||||
- **Content-Type**: application/json
|
||||
- **响应格式**: 统一 { "code": 200, "data": ..., "msg": "success" }
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 客服代下退款申请
|
||||
|
||||
POST /v3/admin/refund/application
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| orderId | Long(String) | 是 | 订单 ID |
|
||||
| refundType | String | 否 | 退款类型,默认 FULL。可选值见第 6 节 RefundType |
|
||||
| reasonId | Long | 否 | 退款原因 ID(从退款原因列表选择) |
|
||||
| reasonText | String | 否 | 退款原因文本(选了 reasonId 时自动填充,也可手填) |
|
||||
| reasonDetail | String | 否 | 原因详情/备注,自由文本 |
|
||||
| policyId | Long | 否 | 指定退款政策 ID;不传则按订单绑定政策计算,政策为空时全额退款 |
|
||||
| departureDate | String | 否 | 出发日期(yyyy-MM-dd),按政策计算时必填,用于计算距出发天数 |
|
||||
| paidAmount | BigDecimal | 是 | 已付金额快照,用于政策计算基数,需 > 0 |
|
||||
| requestedAmount | BigDecimal | 否 | 申请退款金额,PARTIAL 退款类型时必填 |
|
||||
| mediaTraceIds | Array[String] | 否 | 媒体内容 traceId 列表(微信内容安全机审使用) |
|
||||
|
||||
### 4.2 退款申请分页查询
|
||||
|
||||
GET /v3/admin/refund/application/page(Query 参数)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| pageNo | Integer | 否 | 页码,默认 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认 10 |
|
||||
| orderId | Long | 否 | 订单 ID 精确匹配 |
|
||||
| orderNo | String | 否 | 订单号模糊匹配 |
|
||||
| applicantName | String | 否 | 申请人姓名模糊匹配 |
|
||||
| applicantId | Long | 否 | 申请人 ID 精确匹配 |
|
||||
| status | Array[String] | 否 | 退款状态多选,如 status=PENDING&status=APPROVED |
|
||||
| auditStatus | String | 否 | 机审状态过滤,如 MANUAL_REVIEW |
|
||||
| createTimeFrom | String | 否 | 创建时间起,格式 yyyy-MM-ddTHH:mm:ss,如 2026-04-01T00:00:00 |
|
||||
| createTimeTo | String | 否 | 创建时间止 |
|
||||
| refundedAtFrom | String | 否 | 退款完成时间起 |
|
||||
| refundedAtTo | String | 否 | 退款完成时间止 |
|
||||
| sortField | String | 否 | 排序字段:createTime/refundedAt/actualAmount,默认 createTime |
|
||||
| sortOrder | String | 否 | 排序方向:ASC/DESC,默认 DESC |
|
||||
|
||||
### 4.3 退款申请详情
|
||||
|
||||
GET /v3/admin/refund/application/{id}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| id | Path | Long(String) | 是 | 退款申请 ID |
|
||||
|
||||
### 4.4 提交审核
|
||||
|
||||
POST /v3/admin/refund/review
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| applicationId | Long(String) | 是 | 退款申请 ID |
|
||||
| decision | String | 是 | 决议:APPROVED(全额通过)/ REJECTED(拒绝)/ PARTIAL(部分通过) |
|
||||
| approvedAmount | BigDecimal | PARTIAL 时必填 | 同意退款金额;PARTIAL 时必填,APPROVED 时可选(不填按政策计算金额全退) |
|
||||
| remark | String | REJECTED 时必填 | 审核备注/拒绝原因,合规要求 |
|
||||
|
||||
### 4.5 查审核历史
|
||||
|
||||
GET /v3/admin/refund/review/list/{appId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| appId | Path | Long(String) | 是 | 退款申请 ID |
|
||||
|
||||
### 4.6 触发实退
|
||||
|
||||
POST /v3/admin/refund/execute/{appId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| appId | Path | Long(String) | 是 | 退款申请 ID,必须为 APPROVED 状态 |
|
||||
|
||||
### 4.7 手动完成退款
|
||||
|
||||
PUT /v3/admin/refund/application/{applicationId}/complete
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| applicationId | Path | Long(String) | 是 | 退款申请 ID,必须为 REFUNDING 状态 |
|
||||
|
||||
### 4.8 查退款状态流水
|
||||
|
||||
GET /v3/admin/refund/application/{id}/status-log
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| id | Path | Long(String) | 是 | 退款申请 ID |
|
||||
|
||||
### 4.9 直接退款(绕申请单)
|
||||
|
||||
POST /v3/admin/refund/orders/{orderId},需角色 SUPER_ADMIN / ADMIN / FINANCE。
|
||||
|
||||
| 参数/字段 | 位置 | 类型 | 必填 | 说明 |
|
||||
|-----------|------|------|------|------|
|
||||
| orderId | Path | Long(String) | 是 | 订单 ID |
|
||||
| refundAmount | Body BigDecimal | 是 | 退款金额(元) |
|
||||
| reason | Body String | 否 | 退款原因说明 |
|
||||
|
||||
### 4.10 退款记录详情
|
||||
|
||||
GET /v3/admin/refund/{refundId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| refundId | Path | Long(String) | 是 | 退款记录 ID |
|
||||
|
||||
### 4.11 按订单查询退款记录列表
|
||||
|
||||
GET /v3/admin/refund/order/{orderId}/list
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| orderId | Path | Long(String) | 是 | 订单 ID |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 RefundApplicationDetailRespVO(申请详情,管理端完整版)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| applicationId | String | 退款申请 ID(Long->String) |
|
||||
| orderId | String | 订单 ID(Long->String) |
|
||||
| refundType | String | 退款类型:FULL/PARTIAL/DEPOSIT/BALANCE(见第 6 节) |
|
||||
| reasonId | String | 退款原因 ID |
|
||||
| reasonText | String | 退款原因文本(快照) |
|
||||
| reasonDetail | String | 退款原因详情 |
|
||||
| paidAmount | String | 已付金额(String,前端用 BigNumber.js 处理) |
|
||||
| calculatedAmount | String | 按政策计算退款金额 |
|
||||
| actualAmount | String | 实际退款金额(最终决议) |
|
||||
| policyId | String | 退款政策 ID |
|
||||
| policyName | String | 退款政策名称(快照) |
|
||||
| departureDate | String | 出发日期(yyyy-MM-dd) |
|
||||
| daysBeforeDept | Integer | 距出发天数 |
|
||||
| refundRatio | Integer | 退款比例(百分比) |
|
||||
| applicantType | String | 申请人类型:USER/ADMIN/SYSTEM |
|
||||
| applicantId | String | 申请人 ID |
|
||||
| applicantName | String | 申请人姓名 |
|
||||
| status | String | 申请状态枚举值(见第 6 节) |
|
||||
| statusText | String | 状态中文展示,如「待审核」 |
|
||||
| refundedAt | String | 退款完成时间 ISO |
|
||||
| auditStatus | String | 机审状态:PENDING/APPROVED/MANUAL_REVIEW/REJECTED |
|
||||
| auditStatusText | String | 机审状态中文 |
|
||||
| mediaTraceIds | Array[String] | 媒体 traceId 列表 |
|
||||
| createBy | String | 创建人 |
|
||||
| updateBy | String | 更新人 |
|
||||
| createTime | String | 创建时间 |
|
||||
| updateTime | String | 更新时间 |
|
||||
| orderInfo | Object | 订单概要快照(见下) |
|
||||
| reviews | Array | 审核记录列表(按 reviewedAt ASC,见下) |
|
||||
| records | Array | 退款执行记录列表(按 createTime ASC,见下) |
|
||||
|
||||
orderInfo(订单概要快照):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| orderNo | String | 订单号 |
|
||||
| productName | String | 产品名称 |
|
||||
| departureDate | String | 出发日期 |
|
||||
| contactName | String | 联系人姓名 |
|
||||
| contactPhone | String | 联系电话(管理端明文) |
|
||||
|
||||
reviews[i](审核记录项):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| reviewId | String | 审核 ID |
|
||||
| reviewLevel | Integer | 审核级别:1/2/3(当前单级=1) |
|
||||
| reviewerId | String | 审核人 ID |
|
||||
| reviewerName | String | 审核人姓名 |
|
||||
| reviewerRole | String | 审核人角色:CS/SUPERVISOR/EXEC |
|
||||
| decision | String | 决议:APPROVED/REJECTED/PARTIAL |
|
||||
| decisionText | String | 决议中文,如「已通过」 |
|
||||
| approvedAmount | String | 同意退款金额(PARTIAL 时有值) |
|
||||
| remark | String | 审核备注 |
|
||||
| reviewedAt | String | 审核时间 |
|
||||
| createTime | String | 记录创建时间 |
|
||||
|
||||
records[i](退款执行记录项):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| refundId | String | 退款记录 ID |
|
||||
| applicationId | String | 退款申请 ID |
|
||||
| transactionId | String | 关联支付交易 ID |
|
||||
| orderNo | String | 订单号 |
|
||||
| paymentNo | String | 关联支付单号 |
|
||||
| refundChannel | String | 退款渠道:WECHAT/OFFLINE/BALANCE(见第 6 节) |
|
||||
| refundChannelText | String | 退款渠道中文,如「微信退款」 |
|
||||
| mchId | String | 商户号 |
|
||||
| outRefundNo | String | 商户退款单号 |
|
||||
| refundIdWx | String | 微信退款单号 |
|
||||
| refundAmount | String | 本次退款金额(元,String) |
|
||||
| totalAmount | String | 该交易总金额(元,String) |
|
||||
| status | String | 退款记录状态:PENDING/SUCCESS/FAILED/ABNORMAL/CLOSED(见第 6 节) |
|
||||
| statusText | String | 状态中文,如「已成功」 |
|
||||
| failReason | String | 失败原因(status=FAILED 时有值) |
|
||||
| refundedAt | String | 退款完成时间 |
|
||||
| createTime | String | 创建时间 |
|
||||
|
||||
### 5.2 RefundApplicationPageItemRespVO(分页列表项,瘦身版)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| applicationId | String | 退款申请 ID |
|
||||
| orderId | String | 订单 ID |
|
||||
| orderNo | String | 订单号 |
|
||||
| refundType | String | 退款类型 |
|
||||
| reasonText | String | 退款原因文本 |
|
||||
| paidAmount | String | 已付金额 |
|
||||
| calculatedAmount | String | 政策计算金额 |
|
||||
| actualAmount | String | 实际退款金额 |
|
||||
| applicantType | String | 申请人类型 |
|
||||
| applicantId | String | 申请人 ID |
|
||||
| applicantName | String | 申请人姓名 |
|
||||
| status | String | 申请状态枚举 |
|
||||
| statusText | String | 状态中文 |
|
||||
| auditStatus | String | 机审状态 |
|
||||
| reviewerNameLast | String | 最后审核人姓名(摘要) |
|
||||
| refundRecordCount | Integer | 退款执行记录数量 |
|
||||
| refundedAt | String | 退款完成时间 |
|
||||
| createTime | String | 创建时间 |
|
||||
| updateTime | String | 更新时间 |
|
||||
|
||||
分页响应外层结构:{ "code": 200, "data": { "list": [...], "total": 100, "pageNo": 1, "pageSize": 10 } }
|
||||
|
||||
### 5.3 RefundStatusLogRespVO(状态流水)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| total | Integer | 总记录数 |
|
||||
| records | Array | 流水列表(按 changedAt ASC) |
|
||||
| records[i].logId | String | 日志 ID |
|
||||
| records[i].applicationId | String | 退款申请 ID |
|
||||
| records[i].fromStatus | String | 变更前状态枚举 |
|
||||
| records[i].fromStatusText | String | 变更前状态中文 |
|
||||
| records[i].toStatus | String | 变更后状态枚举 |
|
||||
| records[i].toStatusText | String | 变更后状态中文 |
|
||||
| records[i].eventType | String | 事件类型(见第 6 节 RefundLogEventType) |
|
||||
| records[i].eventTypeText | String | 事件类型中文 |
|
||||
| records[i].operatorType | String | 操作人类型:USER/ADMIN/SYSTEM/OA |
|
||||
| records[i].operatorId | String | 操作人 ID |
|
||||
| records[i].operatorName | String | 操作人姓名 |
|
||||
| records[i].reason | String | 理由/备注 |
|
||||
| records[i].extra | Object | 附加快照(按 eventType 路由,见第 6 节 extra 约定) |
|
||||
| records[i].changedAt | String | 变更时间 |
|
||||
|
||||
### 5.4 RefundRecordVO(直接退款记录)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| refundId | Long | 退款 ID(此处为 Long,前端接收需注意精度,建议当 String 处理) |
|
||||
| transactionId | Long | 关联交易 ID |
|
||||
| orderId | Long | 订单 ID |
|
||||
| orderNo | String | 订单号 |
|
||||
| mchId | String | 商户号 |
|
||||
| outRefundNo | String | 商户退款单号 |
|
||||
| refundIdWx | String | 微信退款单号 |
|
||||
| refundAmount | BigDecimal | 退款金额(元) |
|
||||
| totalAmount | BigDecimal | 订单总金额(元) |
|
||||
| reason | String | 退款原因 |
|
||||
| status | String | 退款状态:PENDING/SUCCESS/FAILED/ABNORMAL |
|
||||
| successTime | String | 退款成功时间 |
|
||||
| createTime | String | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 RefundApplicationStatus(退款申请状态)
|
||||
|
||||
| 枚举值 | 中文 | 说明 |
|
||||
|--------|------|------|
|
||||
| PENDING | 待审核 | 申请已提交,等待审核 |
|
||||
| APPROVED | 已通过 | 审核通过,可触发实退 |
|
||||
| REJECTED | 已拒绝 | 审核拒绝,终态 |
|
||||
| REFUNDING | 退款中 | 实退已触发,等待微信回调 |
|
||||
| REFUNDED | 已退款 | 退款完成,终态 |
|
||||
| CANCELLED | 已取消 | 申请被撤回,终态 |
|
||||
| ABNORMAL | 退款异常 | 实退异常(微信接口失败等),需人工处理 |
|
||||
|
||||
活跃状态:PENDING + APPROVED + REFUNDING。同一订单只允许存在一条活跃申请(DB 唯一索引保证),重复提交返回 530004。
|
||||
|
||||
### 6.2 RefundType(退款类型)
|
||||
|
||||
| 枚举值 | 中文 | 说明 |
|
||||
|--------|------|------|
|
||||
| FULL | 全额退款 | 退全部已付金额 |
|
||||
| PARTIAL | 部分退款 | 部分退款,需指定金额 |
|
||||
| DEPOSIT | 订金退款 | 仅退定金部分 |
|
||||
| BALANCE | 尾款退款 | 仅退尾款部分 |
|
||||
|
||||
### 6.3 RefundApplicantType(申请人类型)
|
||||
|
||||
| 枚举值 | 中文 | 说明 |
|
||||
|--------|------|------|
|
||||
| USER | 用户 | C 端用户自主申请 |
|
||||
| ADMIN | 管理员 | 客服代下 |
|
||||
| SYSTEM | 系统 | 订单取消时系统自动发起 |
|
||||
|
||||
### 6.4 退款渠道(refundChannel)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| WECHAT | 微信退款 | 原路退回微信支付 |
|
||||
| OFFLINE | 线下退款 | 线下转账退款 |
|
||||
| BALANCE | 余额退款 | 退到用户平台余额 |
|
||||
|
||||
### 6.5 退款记录状态(refund_record.status)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| PENDING | 处理中 | 退款请求已发送,等待微信回调 |
|
||||
| SUCCESS | 已成功 | 微信回调确认退款成功 |
|
||||
| FAILED | 已失败 | 微信退款失败 |
|
||||
| ABNORMAL | 异常 | 异常状态,等待对账 Job 补偿 |
|
||||
| CLOSED | 已关闭 | 微信退款单被关闭,资金原路退回,终态 |
|
||||
|
||||
### 6.6 RefundLogEventType(状态流水事件类型)及 extra 约定
|
||||
|
||||
| 枚举值 | 中文 | extra 字段内容 |
|
||||
|--------|------|----------------|
|
||||
| APPLY | 发起申请 | { "reasonId": "xxx", "calculatedAmount": "800.00", "refundRatio": 80, "mediaTraceIds": [] } |
|
||||
| REVIEW | 审核 | { "reviewId": "xxx", "level": 1, "decision": "APPROVED", "approvedAmount": "800.00", "remark": "" } |
|
||||
| OA_CALLBACK | OA 回调 | { "approvalNo": "xxx", "spStatus": 2, "approvedAmount": "800.00", "approverName": "张三" } |
|
||||
| REFUND_EXECUTE | 退款执行 | { "recordIds": ["111", "222"], "totalAmount": "800.00" } |
|
||||
| AUDIT_UPDATE | 机审状态更新 | { "newAuditStatus": "APPROVED", "traceIds": [] } |
|
||||
|
||||
### 6.7 审核决议(decision)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| APPROVED | 已通过 | 全额通过(按政策计算金额) |
|
||||
| REJECTED | 已拒绝 | 拒绝,需填 remark |
|
||||
| PARTIAL | 部分通过 | 同意部分金额,需填 approvedAmount |
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 530001 | 无效的退款类型 | refundType 传了非枚举值 |
|
||||
| 530002 | 该订单暂无可退款金额 | 订单已付金额为 0 |
|
||||
| 530003 | 无权操作此订单 | 管理员无权限操作该订单 |
|
||||
| 530004 | 该订单已有退款申请处理中,请勿重复提交 | 同订单存在 PENDING/APPROVED/REFUNDING 状态申请 |
|
||||
| 530005 | 无权操作此退款申请 | 越权操作 |
|
||||
| 530101 | 无权查看此退款申请 | 越权查询 |
|
||||
| 530201 | 当前状态不允许审批,仅待审核状态可审批 | 非 PENDING 状态时尝试审核 |
|
||||
| 530202 | 实际退款金额不能超过已付金额 | approvedAmount 超出 paidAmount |
|
||||
| 530203 | 累计退款金额超过已付金额 | 多次退款累计超出已付 |
|
||||
| 530204 | 仅退款中状态可手动完成退款 | 非 REFUNDING 状态时调 complete 接口 |
|
||||
| 530401 | 退款申请当前状态不允许审核,仅 PENDING 状态可审核 | 重复审核或状态不符 |
|
||||
| 530402 | 审核决议必填 | decision 为空 |
|
||||
| 530403 | PARTIAL 决议必须填写同意退款金额 | PARTIAL 时未填 approvedAmount |
|
||||
| 530404 | 审核同意金额超出可退额度 | approvedAmount > 可退余额 |
|
||||
| 530410 | 当前退款申请状态不允许触发实退,仅已通过的申请可触发 | 非 APPROVED 状态触发 execute |
|
||||
| 530411 | 退款已触发,请勿重复操作 | CAS 防重,execute 接口重复点击 |
|
||||
| 530412 | 退款状态转换非法 | 状态机约束,非法状态流转 |
|
||||
| 530417 | 模拟退款通道已禁用 | 生产环境不允许调模拟退款 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:客服代下退款申请
|
||||
|
||||
请求:POST /v3/admin/refund/application,Header Authorization: Bearer admin-token
|
||||
|
||||
请求体:
|
||||
|
||||
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.2 边界情况:分页查询,筛选待审核+机审需人工
|
||||
|
||||
请求:GET /v3/admin/refund/application/page?pageNo=1&pageSize=20&status=PENDING&auditStatus=MANUAL_REVIEW&sortField=createTime&sortOrder=DESC
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
### 8.3 业务失败:重复提交退款申请
|
||||
|
||||
请求:POST /v3/admin/refund/application,orderId 已有活跃申请
|
||||
|
||||
响应:
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
适用场景:
|
||||
- 客服在管理后台代客户发起退款,系统自动按政策计算可退金额
|
||||
- 管理员在退款列表页审核(单级,当前无需多级流程)
|
||||
- 金融人员/管理员使用直接退款通道处理紧急退款场景
|
||||
- 手动完成退款仅用于测试订单或线下退款,正常流程不应使用
|
||||
|
||||
不适用场景:
|
||||
- 小程序 C 端退款走 /v3/mp/refund/application(独立接口,强 IDOR 校验)
|
||||
- 退款详情接口(/application/{id})禁止直接转发给小程序(含联系电话等管理端明文信息)
|
||||
|
||||
特殊边界:
|
||||
- 审核通过(APPROVED)后,系统自动异步触发实退(调微信退款 API),通常不需手动点「触发实退」按钮;手动触发作为补充入口
|
||||
- PARTIAL 审核通过后实退金额以 approvedAmount 为准,不是 calculatedAmount
|
||||
- 同一订单仅允许一条活跃申请(PENDING/APPROVED/REFUNDING),已完结(REFUNDED/REJECTED/CANCELLED)的申请不占用,可再次申请
|
||||
- 审核通过后发起的退款按支付交易拆分多条 refund_record,records 列表可能有多条
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
本文档为对接说明文档,无改动历史,跳过本节。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
本文档为对接说明文档,跳过本节。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. 金额字段均为 String 类型(申请单通道):paidAmount / calculatedAmount / actualAmount / refundAmount / approvedAmount 在 JSON 中均为 String,请用 BigNumber.js 处理,不要用 JS 原生 Number
|
||||
2. 直接退款通道(/orders/{orderId})的 RefundRecordVO 中 refundId / transactionId / orderId 为 Long 类型,如值超过 JS 安全整数范围需前端注意
|
||||
3. 状态流水 extra 字段:各 eventType 对应不同结构(见第 6.6 节),前端按 eventType 路由渲染对应展示逻辑
|
||||
4. 触发实退是异步的:POST /execute/{appId} 返回成功表示已触发,实际退款完成通过微信回调异步通知,状态会从 REFUNDING 变为 REFUNDED;前端列表页需刷新查看最终状态
|
||||
5. 测试环境模拟退款:测试服开启了 hulai.refund.simulate-enabled=true,退款不真实调微信;生产环境关闭,调用真实微信退款 API
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **服务**: hl-order-service-v3(端口 8086)
|
||||
- **后端负责人**: 腰苏图
|
||||
- **Gitea 项目**: https://git.1814.love:8443/wx/HL
|
||||
@@ -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,309 @@
|
||||
# 退款政策与退款原因管理接口说明(管理后台)
|
||||
|
||||
- **日期**: 2026-06-22
|
||||
- **端类型**: 管理后台
|
||||
- **接口路径前缀**: /v3/admin/order/refund-policy
|
||||
- **服务**: hl-order-service-v3(端口 8086,网关前缀 /v3)
|
||||
- **说明**: 本文档为现有已上线接口的对接说明,非新版本改动
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口背景
|
||||
|
||||
退款政策模块支持按「距出发天数」设置分阶梯退款比例,产品设计时可绑定指定政策;退款原因模块维护 C 端用户申请退款时可选择的原因选项。两个模块均由管理后台维护,不开放给小程序。
|
||||
|
||||
---
|
||||
|
||||
## 2. 变更清单
|
||||
|
||||
| 类型 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| 接口说明 | GET /v3/admin/order/refund-policy/list | 退款政策全量列表(含禁用) |
|
||||
| 接口说明 | GET /v3/admin/order/refund-policy/enabled | 启用政策列表(含 payType 过滤) |
|
||||
| 接口说明 | GET /v3/admin/order/refund-policy/{policyId} | 退款政策详情(含阶梯规则) |
|
||||
| 接口说明 | POST /v3/admin/order/refund-policy | 创建退款政策 |
|
||||
| 接口说明 | PUT /v3/admin/order/refund-policy/{policyId} | 修改退款政策 |
|
||||
| 接口说明 | DELETE /v3/admin/order/refund-policy/{policyId} | 删除退款政策(软删) |
|
||||
| 接口说明 | PUT /v3/admin/order/refund-policy/{policyId}/toggle | 启用/禁用退款政策 |
|
||||
| 接口说明 | POST /v3/admin/order/refund-policy/reason | 创建退款原因 |
|
||||
| 接口说明 | PUT /v3/admin/order/refund-policy/reason/{reasonId} | 修改退款原因 |
|
||||
| 接口说明 | DELETE /v3/admin/order/refund-policy/reason/{reasonId} | 删除退款原因(软删) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口详情(通用)
|
||||
|
||||
- **认证**: JWT,Header Authorization: Bearer token,需管理员身份(adminId 从 token 解析)
|
||||
- **幂等性**: 创建/修改无幂等 header,前端需防止重复点击
|
||||
- **Content-Type**: application/json
|
||||
- **响应格式**: 统一 { "code": 200, "data": ..., "msg": "success" }
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口入参
|
||||
|
||||
### 4.1 政策列表
|
||||
|
||||
#### GET /v3/admin/order/refund-policy/list
|
||||
|
||||
无入参,返回全量政策列表(含禁用),按创建时间倒序。
|
||||
|
||||
#### GET /v3/admin/order/refund-policy/enabled
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| payType | Query | String | 否 | 按支付类型过滤。传 FULL 返回 FULL+BOTH 的政策;传 DEPOSIT 返回 DEPOSIT+BOTH 的政策;不传返回全部启用政策 |
|
||||
|
||||
#### GET /v3/admin/order/refund-policy/{policyId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyId | Path | Long(String) | 是 | 退款政策 ID |
|
||||
|
||||
### 4.2 创建/修改退款政策(请求体)
|
||||
|
||||
POST /v3/admin/order/refund-policy 和 PUT /v3/admin/order/refund-policy/{policyId} 共用同一请求体结构:
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyName | String | 是 | 最大 100 字 | 政策名称,如「国内游退款政策」 |
|
||||
| remark | String | 否 | 最大 500 字 | 备注说明 |
|
||||
| applyPayType | String | 否 | FULL/DEPOSIT/BOTH | 适用支付类型;不传默认 BOTH |
|
||||
| rules | Array | 是 | 不能为空 | 退款规则阶梯 |
|
||||
| rules[].minDays | Integer | 是 | >= 0 | 距出发最少天数(含当天)。例如 minDays=7 表示出发前 7 天及以上适用此规则 |
|
||||
| rules[].refundRatio | Integer | 是 | 0~100 | 退款比例(百分比)。0=不可退;100=全退 |
|
||||
|
||||
PUT 时额外路径参数:
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyId | Path | Long(String) | 是 | 退款政策 ID |
|
||||
|
||||
### 4.3 删除退款政策
|
||||
|
||||
DELETE /v3/admin/order/refund-policy/{policyId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyId | Path | Long(String) | 是 | 退款政策 ID |
|
||||
|
||||
### 4.4 启用/禁用退款政策
|
||||
|
||||
PUT /v3/admin/order/refund-policy/{policyId}/toggle
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| policyId | Path | Long(String) | 是 | 退款政策 ID |
|
||||
| enabled | Query | Boolean | 是 | true=启用;false=禁用 |
|
||||
|
||||
### 4.5 创建/修改退款原因(请求体)
|
||||
|
||||
POST /v3/admin/order/refund-policy/reason 和 PUT /v3/admin/order/refund-policy/reason/{reasonId} 共用:
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| reasonText | String | 是 | 最大 200 字 | 退款原因文本,展示给 C 端用户,如「行程有变」 |
|
||||
| reasonCode | String | 否 | 最大 64 字 | 机器可读编码,如 TIME_CONFLICT / OTHER |
|
||||
| category | String | 否 | 最大 50 字 | 分类,走数据字典 refund_reason_category(见第 6 节) |
|
||||
| sortOrder | Integer | 否 | - | 排序号,数字越小越靠前 |
|
||||
|
||||
### 4.6 删除退款原因
|
||||
|
||||
DELETE /v3/admin/order/refund-policy/reason/{reasonId}
|
||||
|
||||
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| reasonId | Path | Long(String) | 是 | 退款原因 ID |
|
||||
|
||||
---
|
||||
|
||||
## 5. 出参字段
|
||||
|
||||
### 5.1 RefundPolicyVO(退款政策)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| policyId | String | 政策 ID(Long 序列化为 String,防 JS 精度丢失) |
|
||||
| policyName | String | 政策名称 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
| applyPayType | String | 适用支付类型:FULL/DEPOSIT/BOTH(见第 6 节) |
|
||||
| remark | String | 备注 |
|
||||
| createdBy | String | 创建人 ID |
|
||||
| createTime | String | 创建时间 ISO 格式,如 2026-06-01T10:00:00 |
|
||||
| updateTime | String | 更新时间 |
|
||||
| rules | Array | 退款规则阶梯列表 |
|
||||
| rules[].ruleId | String | 规则 ID |
|
||||
| rules[].minDays | Integer | 距出发最少天数 |
|
||||
| rules[].refundRatio | Integer | 退款比例(百分比) |
|
||||
|
||||
阶梯算法说明:规则按 minDays 降序排列,距出发天数 >= 当前规则 minDays 时命中。calculatedAmount = paidAmount x refundRatio / 100,向下取整保留 2 位小数。
|
||||
|
||||
### 5.2 RefundReasonVO(退款原因)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| reasonId | String | 原因 ID |
|
||||
| reasonText | String | 原因描述,展示给 C 端 |
|
||||
| reasonCode | String | 机器可读编码 |
|
||||
| category | String | 分类(字典 refund_reason_category 的 key) |
|
||||
| sortOrder | Integer | 排序号 |
|
||||
| enabled | Boolean | 是否启用 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 枚举 / 数据字典
|
||||
|
||||
### 6.1 applyPayType(适用支付类型)—— 字典 payment_type
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| FULL | 全款支付 | 仅适用于全款支付的订单 |
|
||||
| DEPOSIT | 定金支付 | 仅适用于定金+尾款的订单 |
|
||||
| BOTH | 两者均可 | 全款和定金订单均适用(不传默认值) |
|
||||
|
||||
### 6.2 退款原因分类 —— 字典 refund_reason_category
|
||||
|
||||
category 字段存储字典 key,前端下拉展示时从数据字典接口获取标签。常见 key 示例(以实际字典表为准):
|
||||
|
||||
| key 示例 | 说明 |
|
||||
|----------|------|
|
||||
| USER_REASON | 用户原因(行程变化/临时改变等) |
|
||||
| SERVICE_REASON | 服务原因(产品质量问题等) |
|
||||
| OTHER | 其他 |
|
||||
|
||||
字典实际值以管理后台数据字典模块配置为准,前端需从字典接口动态拉取。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
| 错误码 | 说明 | 触发场景 |
|
||||
|--------|------|----------|
|
||||
| 530209 | 退款政策阶梯天数重复或重叠 | 创建/修改政策时 rules 中有 minDays 重叠的规则 |
|
||||
| 530210 | 退款政策正在被使用,无法删除 | 删除政策时有退款申请正在引用该政策 |
|
||||
| 400 | 参数校验失败 | policyName 为空、rules 为空、refundRatio 超范围等 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例
|
||||
|
||||
### 8.1 典型成功:创建退款政策
|
||||
|
||||
请求:POST /v3/admin/order/refund-policy,请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"policyName": "国内游标准退款政策",
|
||||
"applyPayType": "FULL",
|
||||
"remark": "适用于所有国内游产品",
|
||||
"rules": [
|
||||
{ "minDays": 30, "refundRatio": 90 },
|
||||
{ "minDays": 15, "refundRatio": 70 },
|
||||
{ "minDays": 7, "refundRatio": 50 },
|
||||
{ "minDays": 0, "refundRatio": 0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"policyId": "1234567890123456789",
|
||||
"policyName": "国内游标准退款政策",
|
||||
"enabled": true,
|
||||
"applyPayType": "FULL",
|
||||
"remark": "适用于所有国内游产品",
|
||||
"createTime": "2026-06-22T10:00:00",
|
||||
"rules": [
|
||||
{ "ruleId": "111", "minDays": 30, "refundRatio": 90 },
|
||||
{ "ruleId": "112", "minDays": 15, "refundRatio": 70 },
|
||||
{ "ruleId": "113", "minDays": 7, "refundRatio": 50 },
|
||||
{ "ruleId": "114", "minDays": 0, "refundRatio": 0 }
|
||||
]
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 边界情况:不可退政策(仅一条 minDays=0, refundRatio=0)
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"policyName": "不可退政策",
|
||||
"applyPayType": "BOTH",
|
||||
"rules": [ { "minDays": 0, "refundRatio": 0 } ]
|
||||
}
|
||||
```
|
||||
|
||||
响应结构同 8.1,rules 只有一条。
|
||||
|
||||
### 8.3 业务失败:阶梯天数重叠
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"policyName": "冲突政策",
|
||||
"rules": [
|
||||
{ "minDays": 7, "refundRatio": 80 },
|
||||
{ "minDays": 7, "refundRatio": 50 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{ "code": 530209, "data": null, "msg": "退款政策阶梯天数重复或重叠:7" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 业务边界
|
||||
|
||||
适用场景:
|
||||
- 管理员在「退款设置」模块维护全局退款政策库
|
||||
- 产品编排时从 GET /enabled 拉取可选政策列表绑定到产品;payType 过滤:全款产品传 FULL,定金产品传 DEPOSIT
|
||||
|
||||
不适用场景:
|
||||
- 接口不开放给小程序 C 端(C 端只能查已绑定政策的规则预览)
|
||||
- 删除或禁用政策后,已绑定该政策的产品申请退款时降级使用默认政策(全退),前端不需要特殊处理
|
||||
|
||||
特殊边界:
|
||||
- 修改政策规则时,替换整个 rules 列表(不做增量合并),需传完整阶梯
|
||||
- 历史退款申请已快照政策信息(policyName/refundRatio),修改政策不影响已有退款申请
|
||||
- 退款原因删除为软删,C 端不再展示,但已使用该原因的历史申请原因文本仍保留
|
||||
|
||||
---
|
||||
|
||||
## 10. 修改前后对比
|
||||
|
||||
本文档为对接说明文档,无改动历史,跳过本节。
|
||||
|
||||
---
|
||||
|
||||
## 11. 影响评估 / 回滚
|
||||
|
||||
本文档为对接说明文档,跳过本节。
|
||||
|
||||
---
|
||||
|
||||
## 12. 注意事项
|
||||
|
||||
1. ID 字段均为 String 类型:policyId / ruleId / reasonId 在 JSON 中返回 String(Long 转 String 防 JS 精度丢失),前端接收和传参均用 String,不要转 Number
|
||||
2. rules 阶梯天数语义:minDays 是「距出发最少天数(含)」,无 maxDays 字段,阶梯间隔由两条相邻规则的 minDays 差值隐式决定
|
||||
3. enabled 接口 payType 过滤逻辑:传 FULL 返回 FULL+BOTH,传 DEPOSIT 返回 DEPOSIT+BOTH,因为 BOTH 类政策两种支付类型都适用
|
||||
4. 修改政策 rules 为全量替换,传入 rules 列表会替换原有所有规则,不支持单条追加
|
||||
|
||||
---
|
||||
|
||||
## 13. 关联 / 联系人
|
||||
|
||||
- **服务**: hl-order-service-v3(端口 8086)
|
||||
- **后端负责人**: 腰苏图
|
||||
- **Gitea 项目**: https://git.1814.love:8443/wx/HL
|
||||
@@ -0,0 +1,50 @@
|
||||
# 【修正·管理后台】发票管理接口金额字段改 String 序列化(对齐平台金额约定)
|
||||
|
||||
> 模块:order-v3 发票(invoice)管理后台
|
||||
> 类型:契约修正(每日审查驱动,非新增端点)
|
||||
> 影响端点:`/v3/admin/order/invoice/**`(发票申请/开票/重传/分页列表)
|
||||
> 关联:发票补齐 PR1-4(#4171/#4172/#4173/#4174)的每日审查修正
|
||||
> 测试服已部署验证(9443 真 token 实测 200,金额字段确认为字符串)
|
||||
|
||||
## ⚠️ 关键说明
|
||||
|
||||
发票管理后台部分响应 VO 的金额字段(`BigDecimal`)此前漏标 `@JsonSerialize(ToStringSerializer)`,会被全局 Jackson 序列化成 **JSON 数字**(如 `0`),与平台「所有金额一律 String」约定不一致。本次修正后统一返回**字符串**(如 `"0"`、`"1280.00"`)。
|
||||
|
||||
前端按平台惯例**以字符串解析金额**即可,无需改动;若此前已按数字解析这几个字段,请改回字符串。
|
||||
|
||||
## 1. 受影响字段
|
||||
|
||||
| VO | 字段 | 修正前 | 修正后 |
|
||||
|----|------|--------|--------|
|
||||
| InvoicePageStatsVO(分页统计) | requestedAmount / issuedAmount / pushedAmount / currentMonthIssuedAmount | 数字 `0` | 字符串 `"0"` |
|
||||
| InvoicePageItemRespVO(分页行) | amount | 数字 | 字符串 |
|
||||
| AdminInvoiceApplyRespVO(代客申请响应) | amount | 数字 | 字符串 |
|
||||
|
||||
## 2. 实测样例
|
||||
|
||||
请求:`GET /v3/admin/order/invoice/page?pageNo=1&pageSize=5`(Bearer admin token)
|
||||
|
||||
响应(节选,金额均为字符串):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"stats": {
|
||||
"requestedCount": 0,
|
||||
"requestedAmount": "0",
|
||||
"issuedCount": 0,
|
||||
"issuedAmount": "0",
|
||||
"pushedCount": 0,
|
||||
"pushedAmount": "0",
|
||||
"currentMonthIssuedCount": 0,
|
||||
"currentMonthIssuedAmount": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
count 类计数字段仍为数字(非金额,不受影响)。
|
||||
@@ -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。
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户