比较提交
787 次代码提交
docs/grass
...
main
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
0a1b497397 | ||
|
|
5b2de4a2a0 | ||
|
|
f5cf9b2f53 | ||
|
|
03d38eca14 | ||
|
|
747510fe08 | ||
|
|
a2626f6590 | ||
|
|
ae331af398 | ||
|
|
621ec3c9f1 | ||
|
|
70fefbcf45 | ||
|
|
257b72ff59 | ||
|
|
36d2f83ead | ||
|
|
b4c44254d3 | ||
|
|
c2092c8932 | ||
|
|
dfbd18506c | ||
|
|
e1e155577e | ||
|
|
d461dbe895 | ||
|
|
50b22e6b6b | ||
|
|
57bf865c3b | ||
|
|
1083e3ba5e | ||
|
|
d98db3e76e | ||
|
|
0d2122d4b6 | ||
|
|
0329ca28ac | ||
|
|
5c285a39c0 | ||
|
|
950381cfcc | ||
|
|
320e48ac61 | ||
|
|
0dfcfd1fc9 | ||
|
|
56e89ee029 | ||
|
|
7c5eafada3 | ||
|
|
de40c74d73 | ||
|
|
1aeb52b8cd | ||
|
|
42184e66cb | ||
|
|
b3c2f52ca7 | ||
|
|
03f4599f91 | ||
|
|
d7d2083276 | ||
|
|
c8ec9e440b | ||
|
|
e7410b4d95 | ||
|
|
d1e98fa028 | ||
|
|
360d32fbea | ||
|
|
c1e421867a | ||
|
|
d50b369c8f | ||
|
|
0266bd0c37 | ||
|
|
5644bbf4e0 | ||
|
|
de43d84f12 | ||
|
|
3da4d00882 | ||
| a4fb47b2e3 | |||
|
|
45ea603be0 | ||
|
|
738e239a07 | ||
|
|
2490021161 | ||
|
|
84286cc925 | ||
|
|
627c4c8721 | ||
|
|
c2ec420f36 | ||
|
|
d362c3acb1 | ||
|
|
57c7dcd2aa | ||
|
|
5236c14599 | ||
|
|
5fd8827cfe | ||
|
|
738560e3af | ||
|
|
05f0f548d2 | ||
|
|
231f2e0411 | ||
| f3712c9162 | |||
|
|
1eaff7273f | ||
|
|
0ab42ab0d4 | ||
|
|
f407b0765b | ||
|
|
690046d0b2 | ||
|
|
23dfa84f64 | ||
|
|
4e381e13d0 | ||
|
|
c5996d43ce | ||
|
|
5cedd5fb44 | ||
|
|
63fd34d7fd | ||
|
|
514a2e167a | ||
|
|
76c446312b | ||
|
|
1c022e7943 | ||
|
|
02e8d75fd3 | ||
|
|
698a64d1a1 | ||
|
|
e4fe3ccbf1 | ||
|
|
baaf9a0e2e | ||
|
|
3094d055b6 | ||
|
|
036f1558a1 | ||
|
|
b0cabd9436 | ||
|
|
de72a4f9e6 | ||
|
|
28e3bc46cb | ||
|
|
44e1599741 | ||
|
|
5755a954a8 | ||
|
|
4ab1d52c8c | ||
|
|
71a0544914 | ||
|
|
7921162dfa | ||
|
|
31ed8a0e31 | ||
|
|
5f50581ba1 | ||
|
|
fabb787f81 | ||
|
|
fd6638a1b0 | ||
|
|
c550f00901 | ||
|
|
95368c0303 | ||
|
|
cb60e2cf67 | ||
|
|
a86e136b47 | ||
|
|
4c782a118b | ||
|
|
af1756768f | ||
|
|
ec43d39803 | ||
|
|
926336c5aa | ||
|
|
ad04e0b450 | ||
|
|
b8596041d7 | ||
|
|
0e911a98f3 | ||
|
|
65ddaae00f | ||
|
|
469ae00f98 | ||
|
|
0959c16268 | ||
|
|
872ba16ea3 | ||
|
|
595b1a6d2f | ||
|
|
5a071d8fe2 | ||
|
|
f108466c7a | ||
|
|
672942369e | ||
|
|
ff7e119073 | ||
|
|
a133aa7a50 | ||
|
|
c7bd95e01f | ||
|
|
c76ef049cc | ||
|
|
fc9ca84692 | ||
|
|
793e81fab7 | ||
|
|
8e1da77703 | ||
|
|
6cfed6863a | ||
|
|
719031462f | ||
|
|
a4e5ed7dc5 | ||
|
|
9f29dae489 | ||
|
|
d38c24b1d2 | ||
|
|
28340e8013 | ||
|
|
5363980fe5 | ||
|
|
a664ab410a | ||
|
|
b3a2cf07e7 | ||
|
|
579a64e543 | ||
|
|
8c7ebe19c1 | ||
|
|
289fa89a70 | ||
|
|
93428611ea | ||
|
|
10b9ea32f0 | ||
|
|
3b1d6f3767 | ||
|
|
8225112e9f | ||
|
|
44f016b99d | ||
|
|
6aed45f380 | ||
|
|
86927d4039 | ||
|
|
360981f42f | ||
|
|
214915b1ea | ||
|
|
c70ca66473 | ||
|
|
3953ec74c2 | ||
|
|
a24f7cc724 | ||
|
|
5158b4ef7e | ||
|
|
0fbd9f321b | ||
|
|
f438c9bd63 | ||
|
|
443ffdfab5 | ||
|
|
7939795d8f | ||
|
|
8b53273713 | ||
|
|
5e53e06f35 | ||
|
|
82fe6c6059 | ||
|
|
96da306c1d | ||
|
|
737e3fcb4c | ||
|
|
1a2a1bdb65 | ||
|
|
2f613a6ea9 | ||
|
|
fe3fe0e248 | ||
|
|
4110192fd4 | ||
|
|
186231976e | ||
|
|
b0fee56955 | ||
|
|
b2ac18ff52 | ||
|
|
1554534753 | ||
|
|
2a612a98cc | ||
|
|
1f5b1eea47 | ||
|
|
1d88fab99d | ||
|
|
d50c0d1804 | ||
|
|
1ecc2b91d5 | ||
|
|
a09466557f | ||
|
|
5293bb84cf | ||
|
|
5349d91d27 | ||
|
|
8af58c6c26 | ||
|
|
46730e7a73 | ||
|
|
96db84a917 | ||
|
|
3b86274217 | ||
|
|
154338d76d | ||
|
|
2b069a2d37 | ||
|
|
f802d92994 | ||
|
|
d023480172 | ||
|
|
8e1ec2ff65 | ||
|
|
4efa7e3a17 | ||
|
|
a5e9bcda50 | ||
|
|
3928b35212 | ||
|
|
a0b43444db | ||
|
|
09a155a1e7 | ||
|
|
b955b244df | ||
|
|
f6ca749fe3 | ||
|
|
40e47b1b6a | ||
|
|
2792bf31fa | ||
|
|
da23030b62 | ||
|
|
957622305b | ||
|
|
988058474c | ||
|
|
ed376947fc | ||
|
|
16c743d37e | ||
|
|
ffdfd6057a | ||
|
|
080fe9528d | ||
|
|
0a64dd8ccf | ||
|
|
4ac2465003 | ||
|
|
fee1e9a5aa | ||
|
|
e69a5b9550 | ||
|
|
7ca2565338 | ||
|
|
6daaf40faa | ||
|
|
76301f2c02 | ||
|
|
aa3a32080c | ||
|
|
07f4db337d | ||
|
|
4fb4821b14 | ||
|
|
b14f70292d | ||
|
|
fda2f2ea49 | ||
|
|
2e26fd4a8b | ||
|
|
f1254e37b5 | ||
|
|
dc8a3ef559 | ||
|
|
b3c427bf23 | ||
|
|
869d75c988 | ||
|
|
df0463e589 | ||
|
|
61bf4a46f4 | ||
|
|
6399b0aba9 | ||
|
|
15a82f9d45 | ||
|
|
f60ce32c10 | ||
|
|
f3a25fb629 | ||
|
|
85dcb1a129 | ||
|
|
2531b4a711 | ||
|
|
afeb26cf31 | ||
|
|
8af962ba55 | ||
|
|
827f7301e9 | ||
|
|
506f9dd715 | ||
|
|
c6924d4c3d | ||
|
|
14b83d5801 | ||
|
|
b58a9afe59 | ||
|
|
5fd2ef9bb8 | ||
|
|
3c2e093ef6 | ||
|
|
20255492d0 | ||
|
|
3fa393d0d7 | ||
|
|
ad9b2a5c52 | ||
|
|
54e1cb6f28 | ||
|
|
7eb2308d20 | ||
|
|
ce9da1a372 | ||
|
|
3041884f3c | ||
|
|
0a7781ae00 | ||
|
|
cf2861ee02 | ||
|
|
07271c7ede | ||
|
|
0fa10a8846 | ||
|
|
81e6d3aa44 | ||
|
|
bbd6d7b864 | ||
|
|
d178008c4f | ||
|
|
b9f4c54df5 | ||
|
|
ced26c2548 | ||
|
|
f909409c16 | ||
|
|
a6475f4efa | ||
|
|
d65e40a936 | ||
|
|
04f5a4c5e9 | ||
|
|
5de8b3497f | ||
|
|
70c648a70f | ||
|
|
7dc8033c95 | ||
|
|
88d1e4843e | ||
|
|
987d468cac | ||
|
|
c810f5be2d | ||
|
|
ef246da67c | ||
|
|
46eb07b972 | ||
|
|
a031db038b | ||
|
|
daa80e084d | ||
|
|
306c31943d | ||
|
|
465547976e | ||
|
|
b382c6c40b | ||
|
|
f215878bab | ||
|
|
a38e197c1c | ||
|
|
6791f74cc3 | ||
|
|
c3d7f4b48f | ||
|
|
37355a753e | ||
|
|
1042d7ad62 | ||
|
|
8fc0df51b5 | ||
|
|
64637bb344 | ||
|
|
d5f8f64bcb | ||
|
|
faaefbe8de | ||
|
|
22e0b40614 | ||
|
|
f32db894e4 | ||
|
|
372db232c9 | ||
|
|
2e7ea21ef1 | ||
|
|
da21377d9d | ||
|
|
6c8ccae1d2 | ||
|
|
9c9d2ddbae | ||
|
|
62253a2646 | ||
|
|
4dddd378c7 | ||
|
|
1662671ff6 | ||
|
|
be67501c86 | ||
|
|
b6181f2c0a | ||
|
|
20e55c2d2a | ||
|
|
557bbf4ad1 | ||
|
|
c6c031e6f3 | ||
|
|
79906d0320 | ||
|
|
f6fb5e16d8 | ||
|
|
82191f0931 | ||
|
|
6207073ea7 | ||
|
|
c1f336acd9 | ||
|
|
1a04b55e8c | ||
|
|
cfe34b9439 | ||
|
|
5aa3c55dc2 | ||
|
|
b85c4bd5aa | ||
|
|
d893c56d8a | ||
|
|
b6b796f06f | ||
|
|
6dea6462ad | ||
|
|
d450378994 | ||
|
|
1a17cf5e55 | ||
|
|
a76c7a80b9 | ||
|
|
ce4ce083c1 | ||
|
|
2981268e8f | ||
|
|
bb13ec8089 | ||
|
|
da4c53221e | ||
|
|
d1571345d6 | ||
|
|
ff25eb178d | ||
|
|
ca81deaa8d | ||
|
|
264e05e604 | ||
|
|
a0fec0eef4 | ||
|
|
e255fcf0d9 | ||
|
|
b9f0184499 | ||
|
|
55cdfbef70 | ||
|
|
2e01157b2c | ||
|
|
fd0b4d683a | ||
|
|
090b25a484 | ||
|
|
776d869ba4 | ||
|
|
d2f16b9e96 | ||
|
|
21a013dedb | ||
|
|
85a0cf29bd | ||
|
|
aba61fd5fb | ||
|
|
16e592a497 | ||
|
|
58adddcf30 | ||
|
|
eb07619159 | ||
|
|
e11d5482c6 | ||
|
|
8757c2dc06 | ||
|
|
c310c462fd | ||
|
|
0cb9a139a1 | ||
|
|
b99b00940d | ||
|
|
aba0dbf019 | ||
|
|
17cfdacf0c | ||
|
|
20d3da2fe4 | ||
|
|
5c5991a13b | ||
|
|
96c73b5d08 | ||
|
|
9ef3ad6bab | ||
|
|
8d5e8014b0 | ||
|
|
89bbf236be | ||
|
|
ed25da9e0b | ||
|
|
8315af15eb | ||
|
|
d808b57efc | ||
|
|
a475527ff8 | ||
|
|
d2225c72ec | ||
|
|
cd846d5894 | ||
|
|
fc8456c3ea | ||
|
|
d3d21efceb | ||
|
|
95b2b5a155 | ||
|
|
4deff2accf | ||
|
|
47831d2226 | ||
|
|
64f0bdb60e | ||
|
|
5ac7c3452c | ||
|
|
ccb4de62ee | ||
|
|
dfcffb1d3c | ||
|
|
d830838e90 | ||
|
|
7b8cf6c15e | ||
|
|
6ef4eb60c6 | ||
|
|
deb92b41af | ||
|
|
2982d27323 | ||
|
|
a4ab64254b | ||
|
|
8e2af4e81c | ||
|
|
407494fa43 | ||
|
|
a094aeb9f0 | ||
|
|
5545cff81c | ||
|
|
9a51716e91 | ||
|
|
3f72b09769 | ||
|
|
2f365dda19 | ||
|
|
b3e977c66b | ||
|
|
2544dc7289 | ||
|
|
a76ac60052 | ||
|
|
f2a369aad1 | ||
|
|
1465075091 | ||
|
|
01b39a30df | ||
|
|
1a03185395 | ||
|
|
7a54feb25d | ||
|
|
0e4e4474f9 | ||
|
|
d7272a93ac | ||
|
|
1a030a08d6 | ||
|
|
97e13394a6 | ||
|
|
1b5719fc1a | ||
|
|
9b9ca15d63 | ||
|
|
5a034b07c2 | ||
|
|
550edc1406 | ||
|
|
68dec09317 | ||
|
|
372225e522 | ||
|
|
ce61e09027 | ||
|
|
2f11bd1928 | ||
|
|
bc65aeb305 | ||
|
|
bf7e85dfc3 | ||
|
|
059b0e037c | ||
|
|
cdf37e39f8 | ||
|
|
3b47e02a39 | ||
|
|
093a9913b4 | ||
|
|
87961e2a29 | ||
|
|
b94a49f0e1 | ||
|
|
0cd4aedf1a | ||
|
|
76f18dcbec | ||
|
|
2dbb01ed3d | ||
|
|
725a8a79bf | ||
|
|
2638269e71 | ||
|
|
f24e32d24e | ||
|
|
e040b18f5d | ||
|
|
f93d9a9906 | ||
|
|
6f530766c5 | ||
|
|
a778bea7b7 | ||
|
|
36ce8d8013 | ||
|
|
bc155953a0 | ||
|
|
dcff2890f3 | ||
|
|
3177d81542 | ||
|
|
9aef56dfe0 | ||
|
|
92eb93ea7b | ||
|
|
1903100da2 | ||
|
|
fbf900b365 | ||
|
|
318a453db5 | ||
|
|
9d31a43808 | ||
|
|
2c0c2367db | ||
|
|
5c24db01c5 | ||
|
|
f699aeac22 | ||
|
|
5f1969aef4 | ||
|
|
73be1b61d3 | ||
|
|
52e1aa07a3 | ||
|
|
bf331240eb | ||
|
|
7f31a6b9cc | ||
|
|
c101715232 | ||
|
|
ab0067c309 | ||
|
|
1331f9f7d0 | ||
|
|
2876779c0e | ||
|
|
c095a9d2fa | ||
|
|
118877e367 | ||
|
|
b14ababc3d | ||
|
|
69327dce00 | ||
|
|
8f7be9c0bf | ||
|
|
4603598116 | ||
|
|
bfb0a3ce66 | ||
|
|
ae24c1dfce | ||
| 4affc1a21f | |||
|
|
b2044226d0 | ||
| b184a350ea | |||
|
|
050f7700e2 | ||
|
|
138f99da4b | ||
|
|
6b86db55b1 | ||
|
|
5ffab24ee3 | ||
|
|
bb19816d03 | ||
| 26c23c5b7e | |||
| 96a73275ca | |||
| 1d5e0efb43 | |||
| dce6e0f77b | |||
|
|
a2fc4f3f42 | ||
|
|
faaad27dd2 | ||
| 46ab701a44 | |||
|
|
8ff70b4efd | ||
|
|
480949ec45 | ||
|
|
8ec297c7b9 | ||
| 9a6968de9e | |||
|
|
28a1d43254 | ||
|
|
ab57a99de4 | ||
|
|
9254671fbe | ||
|
|
93e1274542 | ||
|
|
d0a9d794cd | ||
|
|
91d12c216e | ||
|
|
46b937135c | ||
|
|
e1a5255ab3 | ||
|
|
91260cefa1 | ||
|
|
9e8dfc6360 | ||
|
|
3851666575 | ||
|
|
770ce9ad3a | ||
|
|
d65d0e22bb | ||
|
|
057837ebc3 | ||
|
|
cf97a39075 | ||
|
|
2ce66df423 | ||
|
|
73c4daab04 | ||
|
|
601e272457 | ||
|
|
4ad3a92f66 | ||
|
|
3a7032eba2 | ||
|
|
cab228c639 | ||
|
|
a2ab2f20e7 | ||
|
|
dbef2f3397 | ||
|
|
c8bb504b8d | ||
| 70b18d43f8 | |||
|
|
e75821b469 | ||
|
|
deb8b64d15 | ||
|
|
5285b242b9 | ||
|
|
bb0cdc6e80 | ||
|
|
a3ee62d00d | ||
|
|
cb602f86e8 | ||
|
|
ade3f99db7 | ||
|
|
3c099dca2f | ||
|
|
2ae5886d43 | ||
|
|
ac06376f48 | ||
|
|
b140269132 | ||
|
|
0d1872c50a | ||
|
|
9fec9cd3da | ||
| 7917826ea3 | |||
| a72b685243 | |||
| 77725caf28 | |||
|
|
11670fc4f6 | ||
| 3fe95b157f | |||
|
|
67b27b8435 | ||
| 31f00892a8 | |||
|
|
3173f4f567 | ||
|
|
f017ea044d | ||
|
|
dacf76047a | ||
|
|
eb1fc06e4e | ||
|
|
c624775c5c | ||
|
|
cd6dce1d74 | ||
| 6b7c9d9110 | |||
|
|
3493363276 | ||
| 8864373064 | |||
| 68d34e7e7f | |||
| 1faefa4d08 | |||
|
|
2f4e5ad842 | ||
|
|
096d02ccc3 | ||
| b5d1f0add8 | |||
|
|
86e4be1cf0 | ||
|
|
31b640c655 | ||
|
|
e1d4d85e08 | ||
| 58fc5437e6 | |||
|
|
1f53bdd6cb | ||
|
|
a817262898 | ||
| 31573fcdf5 | |||
| bc682e1a78 | |||
|
|
991bdd62d9 | ||
| 9333180fb7 | |||
| a4a559e3c5 | |||
|
|
fb7804dc6a | ||
|
|
52b1d25ebd | ||
| ea83186570 | |||
|
|
170d3b6208 | ||
|
|
3b94480241 | ||
| 2e0e840e24 | |||
|
|
01b67cbeac | ||
|
|
71b931226c | ||
|
|
52b50b2a38 | ||
|
|
57322565b2 | ||
|
|
f7a0b9b539 | ||
|
|
f40d83efc5 | ||
| 6257bf2df9 | |||
|
|
3b7d1bdc9e | ||
| 679b455450 | |||
|
|
69953f5aec | ||
|
|
1e0685a4fe | ||
|
|
450c9a6dfc | ||
|
|
991d9bb641 | ||
|
|
042ee2f396 | ||
| ed20130660 | |||
| 1cd35d4c8f | |||
|
|
2590cc25eb | ||
|
|
de05bab5f2 | ||
|
|
7d76ee0cde | ||
| 89248fea03 | |||
|
|
8083139dc6 | ||
|
|
e816810210 | ||
|
|
446721c542 | ||
|
|
b4879f96c9 | ||
|
|
e49ef47ce9 | ||
| f4f851bb74 | |||
|
|
017cc5cadc | ||
| 211cd7952d | |||
|
|
2e6dac729f | ||
| 3cb62cf417 | |||
|
|
5c7fa5dbe6 | ||
|
|
e35ff35c58 | ||
|
|
c021d338b7 | ||
| ec3986cd7e | |||
|
|
c3e6cd38c2 | ||
|
|
248493b0f5 | ||
| 228b573705 | |||
|
|
6e40fa96de | ||
|
|
73d6f843ef | ||
|
|
ba8a01befb | ||
|
|
a4f468a732 | ||
| 699af1531f | |||
|
|
f4161862c2 | ||
| cb52c7918e | |||
|
|
14482b6a22 | ||
| 6c23a7367f | |||
|
|
de960761df | ||
|
|
ee1c4a3167 | ||
|
|
d0d0d87fec | ||
|
|
551d0564d0 | ||
| 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 |
2
.gitattributes
vendored
普通文件
2
.gitattributes
vendored
普通文件
@ -0,0 +1,2 @@
|
|||||||
|
.githooks/* text eol=lf
|
||||||
|
scripts/*.mjs text eol=lf
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
- name: Validate changelog path aliases
|
||||||
|
run: npm run check:path-aliases
|
||||||
25
.githooks/pre-push
普通文件
25
.githooks/pre-push
普通文件
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# changelog 发布门禁(推送前强制校验)
|
||||||
|
# 启用(每台机一次): git config core.hooksPath .githooks
|
||||||
|
# 拦截目标: 接口类 changelog 未部署测试服(backend_status != deployed)就推送给前端,
|
||||||
|
# 以及文件名/frontmatter 结构违规。规则实现见 scripts/validate-changelog-*.mjs。
|
||||||
|
zero=0000000000000000000000000000000000000000
|
||||||
|
status=0
|
||||||
|
while read local_ref local_sha remote_ref remote_sha; do
|
||||||
|
# 删除远端分支的推送没有本地内容可校验
|
||||||
|
[ "$local_sha" = "$zero" ] && continue
|
||||||
|
if [ "$remote_sha" = "$zero" ]; then
|
||||||
|
base=$(git rev-parse --verify origin/main 2>/dev/null) || continue
|
||||||
|
else
|
||||||
|
base=$remote_sha
|
||||||
|
fi
|
||||||
|
[ "$base" = "$local_sha" ] && continue
|
||||||
|
node scripts/validate-changelog-filenames.mjs --base "$base" --head "$local_sha" || status=1
|
||||||
|
node scripts/validate-changelog-frontmatter.mjs --base "$base" --head "$local_sha" || status=1
|
||||||
|
done
|
||||||
|
if [ "$status" -ne 0 ]; then
|
||||||
|
echo "" >&2
|
||||||
|
echo "推送被 changelog 发布门禁拦截:接口类条目必须测试服已部署+实测(backend_status=deployed)后才能推送给前端。" >&2
|
||||||
|
echo "修正文件后重试;规则详见 BACKEND_CHANGELOG_DELIVERY_GUIDE.md §2.1。" >&2
|
||||||
|
fi
|
||||||
|
exit $status
|
||||||
1
.gitignore
vendored
普通文件
1
.gitignore
vendored
普通文件
@ -0,0 +1 @@
|
|||||||
|
.tmp-user-*
|
||||||
132
BACKEND_CHANGELOG_DELIVERY_GUIDE.md
普通文件
132
BACKEND_CHANGELOG_DELIVERY_GUIDE.md
普通文件
@ -0,0 +1,132 @@
|
|||||||
|
# 后端 API Changelog 推送说明
|
||||||
|
|
||||||
|
接口发生新增、修改或删除时,在 `hl-api-changelog` 仓库提交一份 changelog。
|
||||||
|
|
||||||
|
## 1. 放在哪里
|
||||||
|
|
||||||
|
- 管理后台:`changelogs-v2/YYYY-MM/`
|
||||||
|
- 小程序:`changelogs-v2-mp/YYYY-MM/`
|
||||||
|
|
||||||
|
文件名:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DD_issue_业务标题-{新增接口|修改接口|删除接口|修复|前端缺陷|前端优化|前端修复}-{管理后台|小程序端}.md
|
||||||
|
```
|
||||||
|
|
||||||
|
纯前端条目(无后端工单)issue 段写字面量 `frontend`,如 `10_frontend_标题-前端缺陷-管理后台.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`
|
||||||
|
|
||||||
|
## 2.1 发布门禁(硬规则,2026-08-10 wx 定)
|
||||||
|
|
||||||
|
**给前端推送的 changelog,内容必须是测试环境已经存在、可实测到的。**
|
||||||
|
|
||||||
|
- 接口类条目(新增接口/修改接口/删除接口):推送前必须走完「PR 合并 → 部署测试服 → 测试服真实 API 验证」,frontmatter 必须 `backend_status: "deployed"`,并在正文「验证证据」章节贴实测结果。
|
||||||
|
- `backend_status` 为 `merged` / `pending` / `implemented` 等未部署状态的条目**禁止 push**(校验规则 E_BACKEND_PENDING 会拦)。「先给前端契约、部署随后」的预告式推送一律禁止——前端拿到 changelog 会立刻联调,接口不在等于空耗与误判。
|
||||||
|
- 纯前端条目(前端缺陷/前端优化/前端修复):`backend_status: "not_required"`,change_type 用对应前端类型;`frontend_status: "not_required"` 时不得残留 frontend_owner / frontend_ref / target_release / verified_at。
|
||||||
|
- 背景:2026-08-06~08-07 三条未部署即推送的条目(#5599/#5567/#5633)导致前端在测试环境验不到字段(2026-08-10 投诉属实);当时仓库 CI 因校验规则假阳性长期常红被忽略,规则已于 2026-08-10 修正(前端条目类型合法化、`{orderId}` 路径参数不再误判为占位符),此后 **CI 红 = 真违规,必须当场修复回填**。
|
||||||
|
|
||||||
|
**推送校验(强制)**:
|
||||||
|
|
||||||
|
- 推荐一次性启用本地钩子,之后 push 自动拦截:`git config core.hooksPath .githooks`
|
||||||
|
- 未启用钩子则每次 push 前手动跑 §3 的两条校验命令,红了不许推。
|
||||||
|
- 仓库 CI(changelog-filename-gate)对每次 push 复检;push 后请回看 Gitea Actions 状态,红 X 必须当场处理。
|
||||||
|
|
||||||
|
## 2.5 写作方法论(对齐 yst 团队 changelog-conventions SKILL,2026-08-04 起执行)
|
||||||
|
|
||||||
|
**受众优先**:触达 `/admin/*` `/mp/*` `/v3/admin/*` `/v3/mp/*` 等对外前缀的改动**一律**写前端 changelog,哪怕"前端代码零改动"(前端 AI 可能有 workaround 需清理信号)。`/v3/internal/*` Feign 接口**必须拆出去**单独走后端 changelog,不许和 admin/mp 接口塞同一份(反例:# traveler 11 接口事故)。
|
||||||
|
|
||||||
|
**自包含**:禁止"详见 Knife4j / Swagger / 同目录 xx.md"。所有请求参数表、响应字段表、枚举值(值+中文+说明)、错误码、完整 JSON 示例必须内联——消费方 AI 没有内部文档权限。
|
||||||
|
|
||||||
|
**消费方语言**:写"下拉框去掉草稿选项",不写"status 字段 ApiModelProperty 注解更新";值变了用 `原来 → 现在` 表格,不写散文。
|
||||||
|
|
||||||
|
**示例要求**:每个接口至少 1 组「典型成功」示例(请求+响应完整 JSON);修改类接口建议补「边界」「异常」共 3 组。GET 示例也要写全 URL + Authorization 头 + 注明"无请求体"。
|
||||||
|
|
||||||
|
**不写后端实现**:禁止出现 DB 表/字段名、雪花 ID 序列化细节、Nacos 配置拼接、端口/重启/回滚耗时等后端实现与运维内容(后端运维信息写后端 changelog)。"任何一行拿掉后接口契约仍成立,就该删"。
|
||||||
|
|
||||||
|
**emoji 分类(标题用)**:⚠️ 破坏性变更 / ✨ 新增 / 🔧 行为变更 / 📝 仅文档。
|
||||||
|
|
||||||
|
**commit message 用中文**:`新增退款政策字段(产品详情接口)`,不用英文。
|
||||||
|
|
||||||
|
**多接口 changelog(≥3 接口)**:按接口分小节,每个接口自含「使用场景/入参/出参/错误码/业务边界/示例」,不把多接口入参混到一张大表。
|
||||||
|
|
||||||
|
## 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 文件,**直接 commit main**(不建分支/PR):
|
||||||
|
|
||||||
|
```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 origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
不要提交其他任务的 changelog、`.tmp-*` 文件或任何凭据。
|
||||||
|
|
||||||
|
## 5. author 字段(必填)
|
||||||
|
|
||||||
|
所有 changelog frontmatter 必须包含 `author` 字段,格式为推送者登录名 + `(GIT)` 后缀:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
author: "wx(GIT)" # wx 推送写 wx(GIT);yst 推送写 yst(GIT);以此类推
|
||||||
|
```
|
||||||
|
|
||||||
|
谁 push 到 main 就写谁,多会话并行时用于追溯该条 changelog 的推送人。新写文件必须带;修改旧文件时顺手补上。
|
||||||
|
|
||||||
|
**联系人章节(模仿 yst 格式,2026-08-04 wx 定)**:除 frontmatter `author` 字段外,正文末尾"关联 / 联系人"章节必须标注后端负责人,格式与 yst 的 changelog 一致:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
|
```
|
||||||
|
|
||||||
|
与 frontmatter `author` 字段同源:wx 负责写 `@wx`,yst 负责写 `@yst`。不要在正文开头加"作者"行(已废弃)。模板已含此章节(见 CHANGELOG_TEMPLATE.md)。修改他人 changelog 时不要改联系人。
|
||||||
@ -1,3 +1,22 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "{issue-no}"
|
||||||
|
title: "{一句话概括变化}"
|
||||||
|
consumer: "{admin|mp|internal|multiple}"
|
||||||
|
author: "{推送者登录名}(GIT)" # 如 wx(GIT)/yst(GIT),谁 push 到 main 就写谁
|
||||||
|
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}"
|
||||||
|
---
|
||||||
|
|
||||||
# {模块名}: {一句话概括变化}
|
# {模块名}: {一句话概括变化}
|
||||||
|
|
||||||
> **存放目录**:
|
> **存放目录**:
|
||||||
@ -135,6 +154,36 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||||
|
|
||||||
|
每个枚举单独一个子节,不混表。字段+枚举类对应关系写在子节开头。
|
||||||
|
|
||||||
|
### {字段名}({枚举类全限定名})
|
||||||
|
|
||||||
|
**所属字段**: `{ReqVO/RespVO 字段名}` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `VALUE_A` | 中文名 | 触发条件/含义 |
|
||||||
|
|
||||||
|
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
|
||||||
|
|
||||||
|
### 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
|
||||||
|
### 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
|
||||||
|
## 六.7、影响评估(修改/删除类必写)
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 是 / 否
|
||||||
|
- **前端是否必须同步上线**: 是 / 否
|
||||||
|
- **前端 workaround 清理点**: {如"老前端按比例硬编码算定金的逻辑可撤",无则写"无"}
|
||||||
|
|
||||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||||
|
|
||||||
- **仅影响**: 管理后台 X 表单
|
- **仅影响**: 管理后台 X 表单
|
||||||
@ -175,3 +224,15 @@ POST /mp/product/{id}/quote → 200 + 报价成功 ✓
|
|||||||
- 关联 Issue: [wx/HL#{issue}](https://git.1814.love:8443/wx/HL/issues/{issue})
|
- 关联 Issue: [wx/HL#{issue}](https://git.1814.love:8443/wx/HL/issues/{issue})
|
||||||
- 关联 PR: [wx/HL#{pr}](https://git.1814.love:8443/wx/HL/pulls/{pr})
|
- 关联 PR: [wx/HL#{pr}](https://git.1814.love:8443/wx/HL/pulls/{pr})
|
||||||
- 后续计划: 见 `{另一条 changelog 路径}`
|
- 后续计划: 见 `{另一条 changelog 路径}`
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#{issue-no}](https://git.1814.love:8443/wx/HL/issues/{issue-no})
|
||||||
|
- **PR**: [#{pr-no}](https://git.1814.love:8443/wx/HL/pulls/{pr-no})
|
||||||
|
- **Merge commit**: [{merge-sha}](https://git.1814.love:8443/wx/HL/commit/{merge-sha})(合并后回填)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @{推送者登录名}
|
||||||
|
|||||||
102
CONTRIBUTING.md
普通文件
102
CONTRIBUTING.md
普通文件
@ -0,0 +1,102 @@
|
|||||||
|
# Changelog 贡献规则
|
||||||
|
|
||||||
|
## 二期文件名
|
||||||
|
|
||||||
|
`/v3/admin/*` 接口写入 `changelogs-v2/`:
|
||||||
|
|
||||||
|
项目例外:`/admin/fleet/*` 虽无 `/v3` 前缀,但由二期车管管理后台消费,同样写入 `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`(删除)和 `R`(重命名)默认以 `E_PATH_STABILITY` 阻断。确需迁移时,必须在
|
||||||
|
`changelog-path-aliases.json` 登记旧路径到 canonical 的精确关系,并保留可读取的兼容入口。
|
||||||
|
- 重命名到受控目录时,新目标路径必须使用校验当天的上海日期。
|
||||||
|
|
||||||
|
本地校验:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
npm run check:filenames -- --base origin/main --head HEAD
|
||||||
|
npm run check:path-aliases
|
||||||
|
```
|
||||||
|
|
||||||
|
生产 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`
|
||||||
|
|
||||||
|
已下发路径是消费契约的一部分,不通过重命名表达状态。历史路径已发生迁移时:
|
||||||
|
|
||||||
|
- `changelog-path-aliases.json` 是机器可识别的唯一映射源;
|
||||||
|
- alias 文件必须保留完整 `hl-changelog/v2` frontmatter,并用 `canonical_path` 指向 canonical;
|
||||||
|
- 前端状态更新使用 `npm run changelog:transition -- <path> <status> ... --write`,命令会同时更新
|
||||||
|
canonical 与全部 alias;对同一状态和证据重复执行不会产生文件变更。
|
||||||
|
|
||||||
|
本地校验:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
npm run check:filenames -- --base origin/main --head HEAD
|
||||||
|
npm run check:frontmatter -- --base origin/main --head HEAD
|
||||||
|
npm run check:path-aliases
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 尚未激活。**
|
||||||
134
FRONTEND_CONSUMPTION_STATUS_GUIDE.md
普通文件
134
FRONTEND_CONSUMPTION_STATUS_GUIDE.md
普通文件
@ -0,0 +1,134 @@
|
|||||||
|
# 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` 时必须填写原因。
|
||||||
|
|
||||||
|
## 更新命令
|
||||||
|
|
||||||
|
### 存在历史路径 alias 的文档
|
||||||
|
|
||||||
|
消费线程已经记录的路径不得因文件改名失效。先解析路径:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run changelog:resolve -- "changelogs-v2/2026-07/旧路径.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
状态回写统一使用 alias-aware 命令;传旧路径或 canonical 均会同时更新整组文件:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run changelog:transition -- `
|
||||||
|
"changelogs-v2/2026-07/旧路径.md" implemented `
|
||||||
|
--owner frontend-team `
|
||||||
|
--frontend-ref "mmg/hl-ui@abc1234" `
|
||||||
|
--write
|
||||||
|
```
|
||||||
|
|
||||||
|
相同状态和证据可以重复执行,第二次不会产生文件变更。alias 关系集中记录在
|
||||||
|
`changelog-path-aliases.json`,并由 `npm run check:path-aliases` 校验文件存在性、ticket、
|
||||||
|
canonical 指向及前端状态一致性。
|
||||||
|
|
||||||
|
### 无 alias 的文档
|
||||||
|
|
||||||
|
领取:
|
||||||
|
|
||||||
|
```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 元数据。
|
||||||
|
- 不通过重命名表达消费状态,避免破坏文件名校验和历史链接。
|
||||||
|
- 已被消费的路径如确需规范化,必须先登记 alias、保留兼容入口,并使用 alias-aware 命令同步状态。
|
||||||
4
changelog-path-aliases.json
普通文件
4
changelog-path-aliases.json
普通文件
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schema": "hl-changelog-path-aliases/v1",
|
||||||
|
"aliases": []
|
||||||
|
}
|
||||||
@ -44,3 +44,4 @@ body:`{"orderId": 2071784830965620737}`(必填;可选 `peer`)
|
|||||||
- 车务发消息→定制师收到(unread+1);车务读→团队共享水位推进(任一车务读全体清零)。
|
- 车务发消息→定制师收到(unread+1);车务读→团队共享水位推进(任一车务读全体清零)。
|
||||||
- 单测 user-service 3099 绿(房务基线零回归)。
|
- 单测 user-service 3099 绿(房务基线零回归)。
|
||||||
- **看板列表红点(PR #4698)实测**:`/admin/fleet/board/orders` 74 单每行含 `orderId`(真数字雪花)+ `unreadMessageCount`;定制师向某单广播一条→重查该行 `unreadMessageCount=1`(团队未读联动看板);user-service 软降级路径不阻断列表。fleet-service 全量 1307 测试绿、ArchTest 11/11。
|
- **看板列表红点(PR #4698)实测**:`/admin/fleet/board/orders` 74 单每行含 `orderId`(真数字雪花)+ `unreadMessageCount`;定制师向某单广播一条→重查该行 `unreadMessageCount=1`(团队未读联动看板);user-service 软降级路径不阻断列表。fleet-service 全量 1307 测试绿、ArchTest 11/11。
|
||||||
|
|
||||||
|
|||||||
@ -197,16 +197,7 @@ GET /v3/admin/order/2075415307597357058/payment/manual-receipt/options
|
|||||||
"channel": "BANK_TRANSFER",
|
"channel": "BANK_TRANSFER",
|
||||||
"channelText": "银行转账",
|
"channelText": "银行转账",
|
||||||
"allowedPayTypes": ["DEPOSIT", "FULL"],
|
"allowedPayTypes": ["DEPOSIT", "FULL"],
|
||||||
"collectors": [
|
"collectors": []
|
||||||
{
|
|
||||||
"collectorType": "COMPANY_ACCOUNT",
|
|
||||||
"collectorId": null,
|
|
||||||
"collectorName": "公司账户",
|
|
||||||
"collectorRole": "COMPANY_ACCOUNT",
|
|
||||||
"collectorRoleText": "公司账户",
|
|
||||||
"defaultSelected": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"channel": "DRIVER_CASH",
|
"channel": "DRIVER_CASH",
|
||||||
|
|||||||
@ -0,0 +1,322 @@
|
|||||||
|
# 【修改接口·管理后台】费用明细已收款拆分 (#5022)
|
||||||
|
|
||||||
|
> **PR**: #5025 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-18 00:00
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
管理后台订单费用明细需要区分展示已收订金、已收尾款、已收全款。原接口只返回 `paidAmount` 已收总额,无法直接区分不同收款类型。本次在订单费用接口响应 `data` 内新增 3 个拆分金额字段,`paidAmount` 仍表示已收总额。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 订单费用信息 | GET | `/v3/admin/order/{id}/finance` | 修改接口 | 响应 `data` 新增 `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 订单费用信息
|
||||||
|
|
||||||
|
- **使用场景**: 查询单个订单的费用汇总、优惠、加价、退款、线上支付交易明细。
|
||||||
|
- **认证**: 需要管理后台登录态 JWT。
|
||||||
|
- **幂等性**: 查询接口,幂等。
|
||||||
|
- **限流**: 无新增限流规则。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `id` | String | 是 | 订单 ID,路径参数。示例:`2077233785174179841` |
|
||||||
|
|
||||||
|
### 4.2 请求体字段
|
||||||
|
|
||||||
|
GET 请求,无请求体。
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
### 5.1 顶层响应字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务状态码,`200` 表示成功 |
|
||||||
|
| `message` | String | 响应消息 |
|
||||||
|
| `data` | Object | 订单费用信息 |
|
||||||
|
| `traceId` | String / null | 链路追踪 ID,可能为 `null` |
|
||||||
|
| `success` | Boolean | 请求是否成功 |
|
||||||
|
|
||||||
|
### 5.2 data 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `totalAmount` | String | 订单总金额,金额字符串,单位元 |
|
||||||
|
| `payableAmount` | String | 应付金额,金额字符串,单位元 |
|
||||||
|
| `paidAmount` | String | 已收总额,包含成功线上收款和未撤销线下收款 |
|
||||||
|
| `depositPaidAmount` | String | 新增。实际已收订金金额,成功线上订金 + 未撤销线下订金 |
|
||||||
|
| `balancePaidAmount` | String | 新增。实际已收尾款金额,成功线上尾款 + 未撤销线下尾款 |
|
||||||
|
| `fullPaidAmount` | String | 新增。实际已收全款金额,成功线上全款 + 未撤销线下全款 |
|
||||||
|
| `balanceAmount` | String | 待收余额,金额字符串,单位元 |
|
||||||
|
| `discountAmount` | String | 优惠总额,金额字符串,单位元 |
|
||||||
|
| `surchargeAmount` | String | 加价总额,金额字符串,单位元 |
|
||||||
|
| `refundAmount` | String | 已退金额,金额字符串,单位元 |
|
||||||
|
| `payments` | Array | 线上支付交易明细;仍只表示线上交易,不包含线下收款明细 |
|
||||||
|
| `discounts` | Array | 优惠明细 |
|
||||||
|
| `surcharges` | Array | 加价明细 |
|
||||||
|
|
||||||
|
### 5.3 payments 字段项
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String | 支付交易 ID |
|
||||||
|
| `paymentNo` | String | 支付流水号 |
|
||||||
|
| `amount` | String | 支付金额,单位元 |
|
||||||
|
| `paymentType` | String | 支付类型 |
|
||||||
|
| `status` | String | 支付状态 |
|
||||||
|
| `paidAt` | String / null | 支付成功时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
|
||||||
|
> 本次未改变 `payments` 语义:它仍只表示线上支付交易明细。线下收款明细仍通过 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 获取;线下金额已聚合进 `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 和 `paidAmount`。
|
||||||
|
|
||||||
|
### 5.4 discounts 字段项
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String | 优惠明细 ID |
|
||||||
|
| `name` | String | 优惠名称 |
|
||||||
|
| `amount` | String | 优惠金额,单位元 |
|
||||||
|
| `type` | String | 优惠类型 |
|
||||||
|
| `source` | String | 优惠来源 |
|
||||||
|
| `createdAt` | String | 创建时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
|
||||||
|
### 5.5 surcharges 字段项
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String | 加价明细 ID |
|
||||||
|
| `name` | String | 加价名称 |
|
||||||
|
| `amount` | String | 加价金额,单位元 |
|
||||||
|
| `type` | String | 加价类型 |
|
||||||
|
| `createdAt` | String | 创建时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 paymentType
|
||||||
|
|
||||||
|
**所属字段**: `payments[].paymentType` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `DEPOSIT` | 订金 | 订金支付 |
|
||||||
|
| `BALANCE` | 尾款 | 尾款支付 |
|
||||||
|
| `FULL` | 全款 | 全款支付 |
|
||||||
|
|
||||||
|
### 6.2 status
|
||||||
|
|
||||||
|
**所属字段**: `payments[].status` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SUCCESS` | 支付成功 | 计入对应已收金额 |
|
||||||
|
| `PENDING` | 待支付 | 不计入对应已收金额 |
|
||||||
|
| `CLOSED` | 已关闭 | 不计入对应已收金额 |
|
||||||
|
| `FAILED` | 支付失败 | 不计入对应已收金额 |
|
||||||
|
|
||||||
|
### 6.3 type
|
||||||
|
|
||||||
|
**所属字段**: `discounts[].type`、`surcharges[].type` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `EARLY_BIRD` | 早鸟优惠 | 早鸟规则产生的优惠 |
|
||||||
|
| `MANUAL` | 手工调整 | 人工录入的优惠或加价 |
|
||||||
|
| `OTHER` | 其他 | 其他类型 |
|
||||||
|
|
||||||
|
### 6.4 source
|
||||||
|
|
||||||
|
**所属字段**: `discounts[].source` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `EARLY_BIRD_PLAN` | 早鸟方案 | 来源于早鸟优惠方案 |
|
||||||
|
| `MANUAL` | 手工录入 | 来源于人工录入 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 订单费用信息查询成功 |
|
||||||
|
| `401` | 未登录或登录失效 | 未携带有效管理后台 JWT |
|
||||||
|
| `403` | 无权限 | 当前账号无权访问该订单费用信息 |
|
||||||
|
| `404` | 订单不存在 | 路径参数 `id` 对应订单不存在 |
|
||||||
|
| `500` | 系统异常 | 服务端处理异常 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233785174179841/finance HTTP/1.1
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"totalAmount": "3105.00",
|
||||||
|
"payableAmount": "2955.00",
|
||||||
|
"paidAmount": "2500.00",
|
||||||
|
"depositPaidAmount": "2000.00",
|
||||||
|
"balancePaidAmount": "500.00",
|
||||||
|
"fullPaidAmount": "0",
|
||||||
|
"balanceAmount": "455.00",
|
||||||
|
"discountAmount": "150.00",
|
||||||
|
"surchargeAmount": "0.00",
|
||||||
|
"refundAmount": "0.00",
|
||||||
|
"payments": [],
|
||||||
|
"discounts": [
|
||||||
|
{
|
||||||
|
"id": "2077233785199345666",
|
||||||
|
"name": "早鸟优惠:早鸟-小团减150(适用人群:成人/儿童/小童)",
|
||||||
|
"amount": "150.00",
|
||||||
|
"type": "EARLY_BIRD",
|
||||||
|
"source": "EARLY_BIRD_PLAN",
|
||||||
|
"createdAt": "2026-07-15 11:28:22"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"surcharges": []
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况
|
||||||
|
|
||||||
|
**场景说明**: 订单暂无任何成功线上收款和未撤销线下收款时,所有已收拆分金额均返回 0 金额;明细数组可为空数组。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233785174179841/finance HTTP/1.1
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"totalAmount": "3105.00",
|
||||||
|
"payableAmount": "2955.00",
|
||||||
|
"paidAmount": "0",
|
||||||
|
"depositPaidAmount": "0",
|
||||||
|
"balancePaidAmount": "0",
|
||||||
|
"fullPaidAmount": "0",
|
||||||
|
"balanceAmount": "2955.00",
|
||||||
|
"discountAmount": "150.00",
|
||||||
|
"surchargeAmount": "0.00",
|
||||||
|
"refundAmount": "0.00",
|
||||||
|
"payments": [],
|
||||||
|
"discounts": [],
|
||||||
|
"surcharges": []
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败
|
||||||
|
|
||||||
|
**场景说明**: 订单 ID 不存在。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/9999999999999999999/finance HTTP/1.1
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"message": "订单不存在",
|
||||||
|
"data": null,
|
||||||
|
"traceId": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**: 管理后台查询订单费用明细时调用,订单存在且当前账号有访问权限。
|
||||||
|
- **不适用场景**: 用该接口获取线下收款明细列表;线下收款明细仍由 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 返回。
|
||||||
|
- **特殊边界**: `payments` 为空不代表订单没有已收金额;可能存在未撤销线下收款,已聚合到本次新增的拆分金额和 `paidAmount`。
|
||||||
|
- **金额口径**: `paidAmount` 为已收总额;`depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 为按收款类型拆分后的已收金额。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.depositPaidAmount` | 不返回 | 返回实际已收订金金额 |
|
||||||
|
| `data.balancePaidAmount` | 不返回 | 返回实际已收尾款金额 |
|
||||||
|
| `data.fullPaidAmount` | 不返回 | 返回实际已收全款金额 |
|
||||||
|
| `data.paidAmount` | 返回已收总额 | 继续返回已收总额,语义不变 |
|
||||||
|
| `data.payments` | 返回线上支付交易明细 | 继续只返回线上支付交易明细,语义不变 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 已收金额拆分 | 只能读取 `paidAmount` 总额 | 可读取订金、尾款、全款 3 类已收金额 |
|
||||||
|
| 线下收款聚合 | `paidAmount` 中包含线下收款,无法按类型拆分 | 线下收款按类型聚合进新增拆分字段和 `paidAmount` |
|
||||||
|
| 线上支付明细 | `payments` 表示线上支付交易明细 | 保持不变,仍不包含线下收款明细 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否。本次只新增响应字段,未删除或改名已有字段。
|
||||||
|
- **前端是否必须同步上线**: 否。旧前端继续读取 `paidAmount` 不受影响;需要区分已收订金、已收尾款、已收全款时读取新增字段。
|
||||||
|
- **影响已有数据**: 否。历史订单按成功线上收款和未撤销线下收款聚合返回。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚方式**: 回滚 PR #5025 后,接口不再返回 3 个新增字段。
|
||||||
|
- **回滚后兼容**: 只依赖 `paidAmount` 的旧逻辑不受影响;依赖新增字段的消费方需要兼容字段缺失。
|
||||||
|
- **回滚后清理**: 无需清理前端侧数据。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- `depositPaidAmount`、`balancePaidAmount`、`fullPaidAmount` 是金额字符串,单位元。
|
||||||
|
- 金额为 0 时可能返回 `"0"` 或 `"0.00"`,消费方不要依赖固定小数位判断金额语义。
|
||||||
|
- `payments` 为空时仍可能存在已收金额,因为线下收款不进入 `payments`。
|
||||||
|
- 线下收款明细列表仍由 `GET /v3/admin/order/{orderId}/payment/manual-receipt` 获取。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5022](https://git.1814.love:8443/wx/HL/issues/5022)
|
||||||
|
- **PR**: [#5025](https://git.1814.love:8443/wx/HL/pulls/5025)
|
||||||
|
- **Merge commit**: [aeb2d6d24](https://git.1814.love:8443/wx/HL/commit/aeb2d6d248fcc0fe49a540bfb3b864f06bc00123)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: 腰苏图
|
||||||
@ -0,0 +1,261 @@
|
|||||||
|
# 【新增接口·管理后台】核单操作日志查询 (#5038)
|
||||||
|
|
||||||
|
> **PR**: [#5042](https://git.1814.love:8443/wx/HL/pulls/5042) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-18 15:30
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单页新增独立操作日志页签,用于查看当前订单在核单流程中的关键写入动作,包括住宿核单、门票/活动核单、人员费用、补助、返还记录、主报账对账、提交核单和财务确认。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 查询核单操作日志 | GET | `/v3/admin/order/{orderId}/settlement/logs` | 新增接口 | 按订单分页返回核单专用操作日志 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询核单操作日志
|
||||||
|
|
||||||
|
- **使用场景**: 订单详情核单页签内展示核单操作历史。
|
||||||
|
- **认证**: 需要管理后台 JWT。
|
||||||
|
- **幂等性**: 是。GET 查询不产生写入。
|
||||||
|
- **排序**: 按 `operatedAt` 倒序;同一时间按 `id` 倒序。
|
||||||
|
- **请求体**: 无。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `orderId` | string | 是 | 订单 ID。后端按 64 位整数处理,前端按字符串保存和传递。 |
|
||||||
|
|
||||||
|
### 4.2 Query 参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 默认值 | 校验规则 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `page` | number | 否 | `1` | 最小 `1` | 当前页码。 |
|
||||||
|
| `pageSize` | number | 否 | `20` | `1` 到 `100` | 每页条数。 |
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
接口返回 `Result<PageResult<RecordVO>>`。
|
||||||
|
|
||||||
|
### 5.1 顶层响应字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `code` | number | 状态码,成功为 `200`。 |
|
||||||
|
| `message` | string | 响应消息,成功为 `成功`。 |
|
||||||
|
| `data` | object | 分页数据。 |
|
||||||
|
| `traceId` | string \| null | 链路追踪 ID。 |
|
||||||
|
| `success` | boolean | 是否成功。 |
|
||||||
|
|
||||||
|
### 5.2 `data` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `records` | array | 操作日志记录列表。无日志时为空数组。 |
|
||||||
|
| `total` | number | 总记录数。 |
|
||||||
|
| `page` | number | 当前页码。 |
|
||||||
|
| `pageSize` | number | 每页条数。 |
|
||||||
|
|
||||||
|
### 5.3 `records[]` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | string | 日志 ID。 |
|
||||||
|
| `orderId` | string | 订单 ID。 |
|
||||||
|
| `operationType` | string | 操作类型编码,见第 6 节。 |
|
||||||
|
| `operationTypeName` | string | 操作类型中文名。 |
|
||||||
|
| `operationObject` | string | 操作对象编码,见第 6 节。 |
|
||||||
|
| `operationObjectName` | string | 操作对象中文名。 |
|
||||||
|
| `content` | string | 操作内容。 |
|
||||||
|
| `operatorType` | string | 操作人类型,见第 6 节。 |
|
||||||
|
| `operatorId` | string \| null | 操作人 ID。系统自动操作时可为 `null`。 |
|
||||||
|
| `operatorName` | string | 操作人名称。 |
|
||||||
|
| `operatedAt` | string | 操作时间,格式示例 `2026-07-18 15:15:12`。 |
|
||||||
|
| `beforeSnapshot` | object \| array \| null | 改动前快照。结构随操作对象变化。 |
|
||||||
|
| `afterSnapshot` | object \| array \| null | 改动后快照。结构随操作对象变化。 |
|
||||||
|
| `changeItems` | array | 改动项列表。无差异时为空数组。 |
|
||||||
|
|
||||||
|
### 5.4 `changeItems[]` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `field` | string | 改动字段。非对象快照整体变化时为 `snapshot`。 |
|
||||||
|
| `fieldName` | string | 改动字段展示名。当前与 `field` 同值;整体变化时为 `整体快照`。 |
|
||||||
|
| `beforeValue` | any | 改动前值。 |
|
||||||
|
| `afterValue` | any | 改动后值。 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `operationType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `SAVE_STEP1` | 保存住宿核单 | 保存 Step1 住宿核单明细时生成。 |
|
||||||
|
| `SAVE_STEP2` | 保存门票/活动核单 | 保存 Step2 门票/活动核单明细时生成。 |
|
||||||
|
| `SAVE_STEP3` | 保存人员费用 | 保存 Step3 人员费用核单时生成。 |
|
||||||
|
| `SAVE_STEP4` | 保存补助 | 保存 Step4 补助时生成。 |
|
||||||
|
| `ADD_REFUND` | 新增返还记录 | 新增 Step5 返还记录时生成;终止行程自动生成返还记录也使用该类型。 |
|
||||||
|
| `DELETE_REFUND` | 删除返还记录 | 删除 Step5 返还记录时生成。 |
|
||||||
|
| `SAVE_RECON` | 保存主报账对账 | 保存主报账对账信息时生成。 |
|
||||||
|
| `SUBMIT` | 提交核单 | Step6 提交核单时生成。 |
|
||||||
|
| `CONFIRM` | 财务确认 | 财务确认结算时生成。 |
|
||||||
|
|
||||||
|
### 6.2 `operationObject`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `HOTEL` | 住宿核单 | 住宿核单相关操作对象。 |
|
||||||
|
| `TICKET` | 门票/活动核单 | 门票或活动核单相关操作对象。 |
|
||||||
|
| `STAFF_FEES` | 人员费用 | 司机、导游、摄影或其他人员费用。 |
|
||||||
|
| `SUBSIDY` | 补助 | 补助核单相关操作对象。 |
|
||||||
|
| `REFUND` | 返还记录 | 返还记录相关操作对象。 |
|
||||||
|
| `RECON` | 主报账对账 | 主报账对账相关操作对象。 |
|
||||||
|
| `SETTLEMENT` | 核单结算 | 提交核单或财务确认等整体结算操作对象。 |
|
||||||
|
|
||||||
|
### 6.3 `operatorType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ADMIN` | 管理后台用户 | 管理后台人工操作。 |
|
||||||
|
| `SYSTEM` | 系统自动 | 定时任务或内部流程自动触发。 |
|
||||||
|
| `USER` | C 端用户 | 当前核单日志一般不使用,保留统一操作人类型。 |
|
||||||
|
| `MQ` | 消息回调 | 当前核单日志一般不使用,保留统一操作人类型。 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `200` | 成功 | 查询成功,含无日志空列表。 |
|
||||||
|
| `400` | 参数校验失败 | `page < 1`、`pageSize < 1`、`pageSize > 100`,或 `orderId` 无法解析为整数。 |
|
||||||
|
| `401` | 未认证 | 未携带有效管理后台 JWT。 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233855281971202/settlement/logs?page=1&pageSize=20
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": "2078378034477375490",
|
||||||
|
"orderId": "2077233855281971202",
|
||||||
|
"operationType": "SAVE_STEP4",
|
||||||
|
"operationTypeName": "保存补助",
|
||||||
|
"operationObject": "SUBSIDY",
|
||||||
|
"operationObjectName": "补助",
|
||||||
|
"content": "保存补助",
|
||||||
|
"operatorType": "ADMIN",
|
||||||
|
"operatorId": "1001",
|
||||||
|
"operatorName": "admin",
|
||||||
|
"operatedAt": "2026-07-18 15:15:12",
|
||||||
|
"beforeSnapshot": [],
|
||||||
|
"afterSnapshot": [],
|
||||||
|
"changeItems": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界:暂无日志
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2076236236812345346/settlement/logs?page=1&pageSize=20
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [],
|
||||||
|
"total": 0,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 异常:未登录
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233855281971202/settlement/logs?page=1&pageSize=20
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 401,
|
||||||
|
"message": "缺少有效 Authorization 头",
|
||||||
|
"data": null,
|
||||||
|
"traceId": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- 该接口只查询核单专用操作日志,不返回订单状态日志、支付流水、调整订单记录或房车操作日志。
|
||||||
|
- 无核单日志时返回空分页,不视为异常。
|
||||||
|
- `beforeSnapshot`、`afterSnapshot` 的内部字段随操作对象变化,前端应把它们作为 JSON 快照展示或按对象类型做兼容解析。
|
||||||
|
- `changeItems` 只表达快照层面的差异;数组类快照整体变化时可能只返回一条 `field=snapshot` 的整体改动项。
|
||||||
|
- 查询接口本身不会生成日志;日志由对应核单写入动作生成。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
新增接口,无历史接口可对比。
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否,新增接口。
|
||||||
|
- **前端是否必须同步上线**: 否。不接入该接口时只是不展示核单操作日志页签。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端展示长整型 ID 时按字符串处理,避免精度丢失。
|
||||||
|
- 前端不要根据 `operationTypeName` 或 `operationObjectName` 反推状态;需要判断类型时使用编码字段。
|
||||||
|
- 空列表是合法状态,适用于未开始核单或日志功能上线前的历史订单。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5038](https://git.1814.love:8443/wx/HL/issues/5038)
|
||||||
|
- **PR**: [#5042](https://git.1814.love:8443/wx/HL/pulls/5042)
|
||||||
|
- **Merge commit**: [176405d](https://git.1814.love:8443/wx/HL/commit/176405d489df22a184e848df88a64fe104a6672f)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yaosutu
|
||||||
|
- **前端对接**: 管理后台前端
|
||||||
@ -0,0 +1,319 @@
|
|||||||
|
# 【新增接口·管理后台】预支审批列表接口 (#5041)
|
||||||
|
|
||||||
|
> **PR**: #5044 / #5046 | **服务**: hl-order-service-v3 + hl-user-service | **更新时间**: 2026-07-18 15:40
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
管理后台需要在财务菜单下独立查看待审批、已通过、已驳回的订单预支记录。此前预支记录只能从订单详情上下文查看,财务人员缺少全局审批列表入口。
|
||||||
|
|
||||||
|
本次新增全局分页查询接口,并新增菜单入口:
|
||||||
|
|
||||||
|
- 菜单目录:`财务管理`
|
||||||
|
- 菜单名称:`预支审批`
|
||||||
|
- 菜单路由:`advance-approvals`
|
||||||
|
- 前端组件:`finance/AdvanceApprovalList`
|
||||||
|
- 列表权限:`order:advance-approval:page`
|
||||||
|
- 通过按钮权限:`order:advance-approval:approve`
|
||||||
|
- 驳回按钮权限:`order:advance-approval:reject`
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 分页查询预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 新增接口 | 按审批状态分页查询预支记录,并返回订单摘要字段 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 分页查询预支审批列表
|
||||||
|
|
||||||
|
- **使用场景**:财务人员进入“财务管理 / 预支审批”页面时查询预支审批列表。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **权限点**:`order:advance-approval:page`。
|
||||||
|
- **幂等性**:是。该接口只读,不修改数据。
|
||||||
|
- **排序**:按 `submittedAt` 倒序,其次按 `createTime` 倒序,再按 `id` 倒序。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 Query 参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 默认值 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|--------|------|----------|
|
||||||
|
| `page` | Integer | 否 | `1` | 页码 | 最小值 `1` |
|
||||||
|
| `pageSize` | Integer | 否 | `20` | 每页条数 | 最小值 `1`,最大值 `100` |
|
||||||
|
| `status` | String | 否 | `SUBMITTED` | 审批状态 | 可传 `SUBMITTED` / `APPROVED` / `REJECTED`;大小写不敏感,后端会转大写 |
|
||||||
|
| `orderId` | String | 否 | 无 | 订单 ID 精确筛选 | 雪花 ID,前端按字符串处理 |
|
||||||
|
| `keyword` | String | 否 | 无 | 订单关键字 | 模糊匹配订单号、团号、产品名 |
|
||||||
|
| `payeeName` | String | 否 | 无 | 收款人姓名 | 模糊匹配 |
|
||||||
|
| `createdByName` | String | 否 | 无 | 申请人姓名 | 模糊匹配 |
|
||||||
|
| `submittedAtFrom` | String | 否 | 无 | 提交时间开始 | 格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||||
|
| `submittedAtTo` | String | 否 | 无 | 提交时间结束 | 格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||||
|
|
||||||
|
### 4.2 请求体
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
### 5.1 响应结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [],
|
||||||
|
"total": 0,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"traceId": "可选链路追踪ID",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 `data` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `records` | Array | 当前页记录列表 |
|
||||||
|
| `total` | Integer | 总记录数 |
|
||||||
|
| `page` | Integer | 当前页码 |
|
||||||
|
| `pageSize` | Integer | 每页条数 |
|
||||||
|
|
||||||
|
### 5.3 `records[]` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String | 预支 ID |
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `payeeStaffId` | String | 收款人对应的订单人员分配 ID |
|
||||||
|
| `payeeName` | String | 收款人姓名 |
|
||||||
|
| `payeeRole` | String | 收款人角色编码 |
|
||||||
|
| `payeeRoleText` | String | 收款人角色中文 |
|
||||||
|
| `advanceType` | String | 预支类型 |
|
||||||
|
| `amount` | Number | 预支金额 |
|
||||||
|
| `purpose` | String | 用途说明 |
|
||||||
|
| `voucherUrl` | String | 凭证 URL,可为空 |
|
||||||
|
| `status` | String | 预支审批状态编码 |
|
||||||
|
| `statusText` | String | 预支审批状态中文 |
|
||||||
|
| `rejectReason` | String | 驳回原因,仅驳回记录通常有值 |
|
||||||
|
| `createdByName` | String | 申请人姓名 |
|
||||||
|
| `createTime` | String | 创建时间,格式为 ISO 日期时间 |
|
||||||
|
| `submittedAt` | String | 提交审批时间,格式为 ISO 日期时间 |
|
||||||
|
| `approvedAt` | String | 审批时间,通过或驳回后有值 |
|
||||||
|
| `approvedBy` | String | 审批人姓名 |
|
||||||
|
| `orderNo` | String | 订单号 |
|
||||||
|
| `teamNo` | String | 团号 |
|
||||||
|
| `productName` | String | 产品名称 |
|
||||||
|
| `departDate` | String | 出发日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `returnDate` | String | 返程日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `consultantName` | String | 定制师姓名 |
|
||||||
|
| `orderStatus` | String | 订单状态编码 |
|
||||||
|
| `orderStatusName` | String | 订单状态中文 |
|
||||||
|
| `flowStatus` | String | 流程状态编码 |
|
||||||
|
| `flowStatusName` | String | 流程状态中文 |
|
||||||
|
| `payStatus` | String | 支付状态编码 |
|
||||||
|
| `payStatusName` | String | 支付状态中文 |
|
||||||
|
| `settlementStatus` | String | 结算状态编码 |
|
||||||
|
| `settlementStatusName` | String | 结算状态中文 |
|
||||||
|
| `orderAmount` | String | 订单应收金额 |
|
||||||
|
| `paidAmount` | String | 订单已收金额 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `status` / `records[].status`:预支审批状态
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SUBMITTED` | 待审批 | 创建预支后进入待审批状态;默认查询此状态 |
|
||||||
|
| `APPROVED` | 已通过 | 财务审批通过 |
|
||||||
|
| `REJECTED` | 已驳回 | 财务审批驳回,通常带 `rejectReason` |
|
||||||
|
|
||||||
|
### 6.2 `records[].payeeRole`:收款人角色
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `DRIVER` | 司机 | 司机人员 |
|
||||||
|
| `LEADER` | 导游 | 导游人员 |
|
||||||
|
| `PHOTOGRAPHER` | 摄影师 | 摄影人员 |
|
||||||
|
| `OTHER` | 其他 | 其他人员 |
|
||||||
|
|
||||||
|
### 6.3 订单状态类字段
|
||||||
|
|
||||||
|
`orderStatus`、`flowStatus`、`payStatus`、`settlementStatus` 返回系统内已有状态编码;对应中文展示优先使用同记录里的 `orderStatusName`、`flowStatusName`、`payStatusName`、`settlementStatusName`,前端不需要硬编码中文。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 查询成功 |
|
||||||
|
| `585005` | 预支当前状态不允许此操作 | `status` 传入值不在 `SUBMITTED` / `APPROVED` / `REJECTED` 内 |
|
||||||
|
| `400` | 参数校验失败 | `page < 1`、`pageSize < 1`、`pageSize > 100` 或日期格式不符合要求 |
|
||||||
|
| `401` | 未认证 | 未携带有效管理后台 JWT |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:查询待审批列表
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=SUBMITTED HTTP/1.1
|
||||||
|
Authorization: Bearer {adminToken}
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": "2077000000000000001",
|
||||||
|
"orderId": "2076000000000000001",
|
||||||
|
"payeeStaffId": "2076000000000000101",
|
||||||
|
"payeeName": "张三",
|
||||||
|
"payeeRole": "DRIVER",
|
||||||
|
"payeeRoleText": "司机",
|
||||||
|
"advanceType": "ACCOMMODATION_DEPOSIT",
|
||||||
|
"amount": 500.00,
|
||||||
|
"purpose": "住宿押金",
|
||||||
|
"voucherUrl": "https://example.test/voucher/advance-001.jpg",
|
||||||
|
"status": "SUBMITTED",
|
||||||
|
"statusText": "待审批",
|
||||||
|
"rejectReason": null,
|
||||||
|
"createdByName": "腰苏图",
|
||||||
|
"createTime": "2026-07-18T10:20:30",
|
||||||
|
"submittedAt": "2026-07-18T10:20:30",
|
||||||
|
"approvedAt": null,
|
||||||
|
"approvedBy": null,
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "T202607180001",
|
||||||
|
"productName": "草原亲子 3 日游",
|
||||||
|
"departDate": "2026-07-21",
|
||||||
|
"returnDate": "2026-07-23",
|
||||||
|
"consultantName": "腰苏图",
|
||||||
|
"orderStatus": "PENDING_DEPARTURE",
|
||||||
|
"orderStatusName": "待出行",
|
||||||
|
"flowStatus": "PENDING_DEPARTURE",
|
||||||
|
"flowStatusName": "待出行",
|
||||||
|
"payStatus": "PAID",
|
||||||
|
"payStatusName": "已支付",
|
||||||
|
"settlementStatus": "NONE",
|
||||||
|
"settlementStatusName": "未核单",
|
||||||
|
"orderAmount": "3600.00",
|
||||||
|
"paidAmount": "3600.00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况:无记录
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=20&status=APPROVED&keyword=NO_MATCH_KEYWORD HTTP/1.1
|
||||||
|
Authorization: Bearer {adminToken}
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [],
|
||||||
|
"total": 0,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 异常情况:非法审批状态
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=BAD_STATUS HTTP/1.1
|
||||||
|
Authorization: Bearer {adminToken}
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 585005,
|
||||||
|
"message": "预支当前状态不允许此操作",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- `status` 不传时默认查 `SUBMITTED`。
|
||||||
|
- `status` 支持小写或混合大小写,后端统一转大写后校验。
|
||||||
|
- `keyword` 只匹配订单号、团号、产品名。
|
||||||
|
- `payeeName` 只匹配收款人姓名。
|
||||||
|
- `createdByName` 只匹配申请人姓名。
|
||||||
|
- `submittedAtFrom` 与 `submittedAtTo` 都是闭区间过滤条件。
|
||||||
|
- 金额字段中,预支金额 `amount` 为数值;订单金额 `orderAmount`、`paidAmount` 为字符串,前端按字符串展示或转高精度数值处理。
|
||||||
|
- ID 类字段均按字符串处理,避免 JS 数字精度问题。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
新增接口,无旧接口对比。
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
新增接口和新增菜单入口,不破坏已有接口契约。
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。
|
||||||
|
- **前端是否必须同步上线**:否;未接入该页面时不影响原订单详情预支能力。
|
||||||
|
- **回滚影响**:回滚后“财务管理 / 预支审批”菜单和列表接口不可用。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端页面应挂到 `财务管理 / 预支审批`。
|
||||||
|
- 查询接口只负责列表展示,不执行审批动作。
|
||||||
|
- 审批通过、驳回按钮权限已经随菜单一起下发,按钮可以按权限点控制展示或禁用。
|
||||||
|
- 当前菜单按钮节点 `visible=false`,用于权限控制,不作为侧边栏可见菜单展示。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5041](https://git.1814.love:8443/wx/HL/issues/5041)
|
||||||
|
- **PR**: [#5044](https://git.1814.love:8443/wx/HL/pulls/5044)
|
||||||
|
- **部署修复 Issue**: [#5045](https://git.1814.love:8443/wx/HL/issues/5045)
|
||||||
|
- **部署修复 PR**: [#5046](https://git.1814.love:8443/wx/HL/pulls/5046)
|
||||||
|
- **Merge commit**: [b1ac11c03](https://git.1814.love:8443/wx/HL/commit/b1ac11c030a56a94fd8623f44b5a630fb41f2da9)
|
||||||
|
|
||||||
|
### 13.2 验证记录
|
||||||
|
|
||||||
|
- 测试服 `hl-user-service` 8081/8181 双实例部署成功。
|
||||||
|
- 测试服 `hl-order-service-v3` 8086/8186 双实例部署成功。
|
||||||
|
- 网关实调 `GET /v3/admin/order/advance-approvals/page?page=1&pageSize=10&status=SUBMITTED` 返回 `code=200`、`records=1`。
|
||||||
|
- 网关实调非法 `status=BAD_STATUS` 返回 `code=585005`。
|
||||||
|
- 网关实调 `/admin/menu/my` 已返回“预支审批”菜单和通过/驳回按钮权限。
|
||||||
|
|
||||||
|
### 13.3 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,397 @@
|
|||||||
|
# 【修改接口·管理后台】核单 Step1 住宿成本字段 (#5043)
|
||||||
|
|
||||||
|
> **PR**: #5047 / #5162 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-23 09:53
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单 Step1 住宿成本明细需要对齐原型里的酒店资源、房型资源、核算单价、来源和确认状态展示。现有接口保留原路径,在 `GET/PUT /v3/admin/order/{orderId}/settlement/step1` 上做兼容增强。
|
||||||
|
|
||||||
|
### 1.1 2026-07-23 前端对接补充(数据来源提交约定)
|
||||||
|
|
||||||
|
`sourceType` 只用于区分住宿明细的数据来源,不决定行是否可编辑。行的可编辑性继续由 `settlementConfirmStatus` 控制,本次不新增 `deletable` 或 `sourceFieldsEditable` 字段。
|
||||||
|
|
||||||
|
| 数据来源 | GET 返回 / PUT 回传的 `sourceType` | PUT 回传的 `sourceId` | PUT 回传的 `hotelAssignmentId` |
|
||||||
|
|----------|------------------------------------------|----------------------------|---------------------------------------|
|
||||||
|
| 后台配房自动行 | `HOUSE_ASSIGNMENT` | 原样回传 GET 返回的配房记录 ID | 原样回传 GET 返回的配房记录 ID |
|
||||||
|
| 前端手动新增行 | `MANUAL` | `null` | `null` |
|
||||||
|
|
||||||
|
`sourceType` 当前仍为非必填字段:未传时,后端可根据 `hotelAssignmentId` 推断数据来源。为了稳定保留来源信息,前端保存时应按上表显式回传。
|
||||||
|
|
||||||
|
**后台配房自动行 PUT 关键字段**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sourceType": "HOUSE_ASSIGNMENT",
|
||||||
|
"sourceId": "2077233886282088401",
|
||||||
|
"hotelAssignmentId": "2077233886282088401",
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**前端手动新增行 PUT 关键字段**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sourceType": "MANUAL",
|
||||||
|
"sourceId": null,
|
||||||
|
"hotelAssignmentId": null,
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询住宿核算明细 | GET | `/v3/admin/order/{orderId}/settlement/step1` | 修改接口 | `HotelItemVO` 出参新增 10 个字段 |
|
||||||
|
| 2 | 保存住宿核算明细 | PUT | `/v3/admin/order/{orderId}/settlement/step1` | 修改接口 | `HotelItemVO` 入参支持保存酒店/房型资源、单价、来源和确认状态字段 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询住宿核算明细
|
||||||
|
|
||||||
|
- **使用场景**:进入核单 Step1 住宿页签时查询住宿成本明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:幂等,只读查询。
|
||||||
|
- **响应结构**:`data` 为 `HotelItemVO[]` 数组。
|
||||||
|
|
||||||
|
### 3.2 保存住宿核算明细
|
||||||
|
|
||||||
|
- **使用场景**:保存核单 Step1 住宿成本明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:全量替换保存;同一份 `items` 重复提交后,以最后一次提交为准。
|
||||||
|
- **请求体兼容**:推荐使用 `{ "items": [...] }`;历史数组 body `[...]` 仍兼容。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | string | 是 | 订单 ID,长整型字符串 |
|
||||||
|
|
||||||
|
### 4.2 PUT 请求体字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `items` | array | 是 | 住宿成本明细行数组,全量替换保存 | 不允许为 `null` |
|
||||||
|
| `items[].id` | string | 否 | 已存在行 ID;全量替换保存时可不传 | 长整型字符串 |
|
||||||
|
| `items[].hotelAssignmentId` | string/null | 否 | 配房 assignment ID;后台配房自动行应原样回传,手动行传 `null` | 长整型字符串或 `null` |
|
||||||
|
| `items[].hotelId` | string/null | 否 | 酒店资源 ID;本次新增 | 长整型字符串或 `null` |
|
||||||
|
| `items[].roomTypeId` | string/null | 否 | 房型资源 ID;本次新增 | 长整型字符串或 `null` |
|
||||||
|
| `items[].stayDate` | string | 是 | 入住日期 | `yyyy-MM-dd`,不能早于订单出发日 |
|
||||||
|
| `items[].hotelName` | string | 是 | 酒店名称 | 1-200 字符 |
|
||||||
|
| `items[].roomType` | string/null | 否 | 房型分类或旧展示字段 | 最大 64 字符 |
|
||||||
|
| `items[].roomTypeName` | string/null | 否 | 房型/规格名称;本次新增 | 最大 64 字符 |
|
||||||
|
| `items[].roomCount` | integer | 是 | 总间数 | 正整数 |
|
||||||
|
| `items[].unitPrice` | number/null | 否 | 核算单价,单位元/间夜;本次新增;不传时按 `actualCost / roomCount` 降级计算 | `>= 0` |
|
||||||
|
| `items[].plannedCost` | number | 是 | 计划成本,单位元 | `>= 0` |
|
||||||
|
| `items[].actualCost` | number | 是 | 实际成本,单位元 | `>= 0` |
|
||||||
|
| `items[].paymentMethod` | string | 否 | 付款方式;与 `settleType` 二选一 | `SIGNED` / `COMPANY_PAID` / `CASH_PAID` |
|
||||||
|
| `items[].paymentMethodName` | string | 否 | 付款方式中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||||
|
| `items[].settleType` | string | 否 | 配房结算类型;与 `paymentMethod` 二选一 | `cash` / `sign` / `company` |
|
||||||
|
| `items[].sourceType` | string | 否 | 仅标识数据来源;后台配房行回传 `HOUSE_ASSIGNMENT`,手动行传 `MANUAL`;不传时后端按 `hotelAssignmentId` 推断 | `HOUSE_ASSIGNMENT` / `MANUAL` / `TEMPLATE` |
|
||||||
|
| `items[].sourceTypeName` | string | 否 | 来源类型中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||||
|
| `items[].sourceId` | string/null | 否 | 来源业务 ID;后台配房自动行应原样回传配房记录 ID,手动行传 `null` | 长整型字符串或 `null` |
|
||||||
|
| `items[].settlementConfirmStatus` | string | 否 | 核单确认状态;本次新增;不传默认 `CONFIRMED` | `UNCONFIRMED` / `CONFIRMED` |
|
||||||
|
| `items[].settlementConfirmStatusName` | string | 否 | 核单确认状态中文名,仅展示字段;保存时可不传;本次新增 | 最大 32 字符 |
|
||||||
|
| `items[].remark` | string/null | 否 | 备注 | 最大 500 字符 |
|
||||||
|
| `items[].voucherUrls` | array | 否 | 凭证图片 URL 数组 | 字符串数组 |
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
### 5.1 GET 响应字段:`HotelItemVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | string/null | 核单住宿明细行 ID;首次派生未保存的行可为 `null` |
|
||||||
|
| `hotelAssignmentId` | string/null | 配房 assignment ID;手工行可为 `null` |
|
||||||
|
| `hotelId` | string/null | 酒店资源 ID;本次新增 |
|
||||||
|
| `roomTypeId` | string/null | 房型资源 ID;本次新增 |
|
||||||
|
| `stayDate` | string | 入住日期,`yyyy-MM-dd` |
|
||||||
|
| `hotelName` | string | 酒店名称 |
|
||||||
|
| `roomType` | string/null | 房型分类或旧展示字段 |
|
||||||
|
| `roomTypeName` | string/null | 房型/规格名称;本次新增 |
|
||||||
|
| `roomCount` | integer | 总间数 |
|
||||||
|
| `unitPrice` | number/null | 核算单价,单位元/间夜;本次新增 |
|
||||||
|
| `plannedCost` | number | 计划成本,单位元 |
|
||||||
|
| `actualCost` | number | 实际成本,单位元 |
|
||||||
|
| `paymentMethod` | string | 付款方式 |
|
||||||
|
| `paymentMethodName` | string/null | 付款方式中文名;本次新增 |
|
||||||
|
| `settleType` | string/null | 配房结算类型;保存草稿后可能为空 |
|
||||||
|
| `sourceType` | string | 来源类型;本次新增 |
|
||||||
|
| `sourceTypeName` | string/null | 来源类型中文名;本次新增 |
|
||||||
|
| `sourceId` | string/null | 来源业务 ID;本次新增 |
|
||||||
|
| `settlementConfirmStatus` | string | 核单确认状态;本次新增 |
|
||||||
|
| `settlementConfirmStatusName` | string/null | 核单确认状态中文名;本次新增 |
|
||||||
|
| `remark` | string/null | 备注 |
|
||||||
|
| `voucherUrls` | array | 凭证图片 URL 数组 |
|
||||||
|
|
||||||
|
### 5.2 PUT 响应字段:`SettlementHotelSaveRespVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `addedIds` | string[] | 本次保存新增的核单住宿明细行 ID 列表 |
|
||||||
|
| `updatedIds` | string[] | 本次保存更新的核单住宿明细行 ID 列表;当前全量替换语义下通常为空数组 |
|
||||||
|
| `deletedIds` | string[] | 本次保存删除的核单住宿明细行 ID 列表;当前返回通常为空数组 |
|
||||||
|
| `totalActualCost` | string | 保存后 Step1 实际成本合计,单位元 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `paymentMethod`
|
||||||
|
|
||||||
|
**所属字段**:`items[].paymentMethod`、`data[].paymentMethod` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SIGNED` | 签单 | 现场签单 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 公司统一付款 |
|
||||||
|
| `CASH_PAID` | 现付 | 现场现金或线下现付 |
|
||||||
|
|
||||||
|
### 6.2 `settleType`
|
||||||
|
|
||||||
|
**所属字段**:`items[].settleType`、`data[].settleType` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 映射后的 `paymentMethod` |
|
||||||
|
|----|------|--------------------------|
|
||||||
|
| `cash` | 现付 | `CASH_PAID` |
|
||||||
|
| `sign` | 签单 | `SIGNED` |
|
||||||
|
| `company` | 公司付款 | `COMPANY_PAID` |
|
||||||
|
|
||||||
|
### 6.3 `sourceType`
|
||||||
|
|
||||||
|
**所属字段**:`items[].sourceType`、`data[].sourceType` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `HOUSE_ASSIGNMENT` | 配房结果 | 来自房务配房结果 |
|
||||||
|
| `MANUAL` | 手工 | 核单手工补充住宿行 |
|
||||||
|
| `TEMPLATE` | 模板 | 模板来源住宿行,当前预留 |
|
||||||
|
|
||||||
|
### 6.4 `settlementConfirmStatus`
|
||||||
|
|
||||||
|
**所属字段**:`items[].settlementConfirmStatus`、`data[].settlementConfirmStatus` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `UNCONFIRMED` | 未确认 | 核单住宿明细未确认 |
|
||||||
|
| `CONFIRMED` | 已确认 | 核单住宿明细已确认;不传时默认该值 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `584002` | 当前核单状态不允许录住宿核单 | PUT 保存时,订单不是「待核单」或「核单中」 |
|
||||||
|
| `584008` | 订单缺出发日期,无法派生 dayNumber | PUT 保存时订单出发日期为空 |
|
||||||
|
| `584009` | `stayDate` 早于订单出发日期 | PUT 保存时日期越界 |
|
||||||
|
| `584062` | 临时行必须指定付款方式 | `paymentMethod` 和 `settleType` 都为空 |
|
||||||
|
| `584064` | 配房记录 `settleType` 字典值非法 | `settleType` 不是 `cash/sign/company` |
|
||||||
|
| `100001` | 参数非法 | 字段格式不符合校验,例如枚举值不在允许范围内、金额小于 0 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:GET 查询
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233855281971202/settlement/step1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "2077328317421187001",
|
||||||
|
"hotelAssignmentId": "2077233886282088401",
|
||||||
|
"hotelId": "50001",
|
||||||
|
"roomTypeId": "51001",
|
||||||
|
"stayDate": "2026-07-18",
|
||||||
|
"hotelName": "海拉尔海棠酒店",
|
||||||
|
"roomType": "STANDARD",
|
||||||
|
"roomTypeName": "精品标间",
|
||||||
|
"roomCount": 2,
|
||||||
|
"unitPrice": 440.00,
|
||||||
|
"plannedCost": 880.00,
|
||||||
|
"actualCost": 880.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"paymentMethodName": "现付",
|
||||||
|
"settleType": "cash",
|
||||||
|
"sourceType": "HOUSE_ASSIGNMENT",
|
||||||
|
"sourceTypeName": "配房结果",
|
||||||
|
"sourceId": "2077233886282088401",
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": "已核对",
|
||||||
|
"voucherUrls": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界成功:PUT 保存手工住宿行
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2077233855281971202/settlement/step1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"hotelAssignmentId": null,
|
||||||
|
"hotelId": null,
|
||||||
|
"roomTypeId": null,
|
||||||
|
"stayDate": "2026-07-18",
|
||||||
|
"hotelName": "临时补充酒店",
|
||||||
|
"roomType": "STANDARD",
|
||||||
|
"roomTypeName": "标准间",
|
||||||
|
"roomCount": 1,
|
||||||
|
"unitPrice": 300.00,
|
||||||
|
"plannedCost": 300.00,
|
||||||
|
"actualCost": 300.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"sourceType": "MANUAL",
|
||||||
|
"sourceId": null,
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "核单临时补充"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"addedIds": ["2078398065684692001"],
|
||||||
|
"updatedIds": [],
|
||||||
|
"deletedIds": [],
|
||||||
|
"totalActualCost": "300.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:缺付款方式
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2077233855281971202/settlement/step1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"hotelAssignmentId": null,
|
||||||
|
"stayDate": "2026-07-18",
|
||||||
|
"hotelName": "临时补充酒店",
|
||||||
|
"roomType": "STANDARD",
|
||||||
|
"roomCount": 1,
|
||||||
|
"plannedCost": 300.00,
|
||||||
|
"actualCost": 300.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584062,
|
||||||
|
"message": "临时行(无配房关联)必须指定 paymentMethod",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- `PUT` 是全量替换保存;前端保存时应提交页面当前完整明细列表。
|
||||||
|
- GET 返回的后台配房自动行,PUT 保存时应原样回传 `sourceType=HOUSE_ASSIGNMENT`、`sourceId` 和 `hotelAssignmentId`,两个 ID 都是对应配房记录 ID。
|
||||||
|
- 前端手动新增行,PUT 保存时传 `sourceType=MANUAL`、`sourceId=null`、`hotelAssignmentId=null`。
|
||||||
|
- `sourceType` 不传时,`hotelAssignmentId` 有值默认 `HOUSE_ASSIGNMENT`,否则默认 `MANUAL`。
|
||||||
|
- `sourceType` 只区分数据来源,不决定可编辑性;可编辑性继续由 `settlementConfirmStatus` 控制。
|
||||||
|
- `sourceId` 不传且 `sourceType=HOUSE_ASSIGNMENT` 时,默认使用 `hotelAssignmentId`;其他来源可为 `null`。
|
||||||
|
- `unitPrice` 不传且 `roomCount > 0`、`actualCost` 有值时,返回时会按 `actualCost / roomCount` 保留 2 位小数。
|
||||||
|
- `settlementConfirmStatus` 不传时默认 `CONFIRMED`。
|
||||||
|
- `paymentMethod` 与 `settleType` 二选一;`paymentMethod` 优先,`settleType` 会映射成 `paymentMethod`。
|
||||||
|
- 订单核单状态必须是「待核单」或「核单中」才允许保存 Step1。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `hotelId` | 无 | 新增,酒店资源 ID |
|
||||||
|
| `roomTypeId` | 无 | 新增,房型资源 ID |
|
||||||
|
| `roomTypeName` | 无 | 新增,房型/规格名称 |
|
||||||
|
| `unitPrice` | 无 | 新增,核算单价 |
|
||||||
|
| `paymentMethodName` | 无 | 新增,付款方式中文名 |
|
||||||
|
| `sourceType` | 无 | 新增,来源类型 |
|
||||||
|
| `sourceTypeName` | 无 | 新增,来源类型中文名 |
|
||||||
|
| `sourceId` | 无 | 新增,来源业务 ID |
|
||||||
|
| `settlementConfirmStatus` | 无 | 新增,核单确认状态 |
|
||||||
|
| `settlementConfirmStatusName` | 无 | 新增,核单确认状态中文名 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 住宿来源展示 | 只能通过 `hotelAssignmentId` 粗略判断 | 返回 `sourceType/sourceTypeName/sourceId` |
|
||||||
|
| 单价展示 | 前端只能根据总价和间数自行推算 | 返回 `unitPrice`,缺失时后端按实际成本和间数降级计算 |
|
||||||
|
| 确认状态展示 | 无独立字段 | 返回 `settlementConfirmStatus/settlementConfirmStatusName` |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。新增字段为兼容性新增,旧字段保留。
|
||||||
|
- **前端是否必须同步上线**:否。旧页面可继续按原字段展示;需要原型新增列时读取新增字段。
|
||||||
|
- **影响已有数据**:历史行新增字段可能为 `null`,前端需要保留空值展示逻辑。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- 回滚接口代码后,前端不要再依赖本次新增字段。
|
||||||
|
- 如果页面已使用新增列,回滚期间新增列需要降级为空态展示。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- `paymentMethodName`、`sourceTypeName`、`settlementConfirmStatusName` 都是展示字段,保存时可不传。
|
||||||
|
- `roomType` 是旧字段,`roomTypeName` 是本次新增的房型/规格名称;两者可能同时存在。
|
||||||
|
- `settlementConfirmStatus` 是核单明细确认状态,与房务配房确认状态不是同一个字段。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5043](https://git.1814.love:8443/wx/HL/issues/5043)
|
||||||
|
- **PR**: [#5047](https://git.1814.love:8443/wx/HL/pulls/5047)
|
||||||
|
- **Merge commit**: [0a9e83b39](https://git.1814.love:8443/wx/HL/commit/0a9e83b390d135c05b43bc18afe1b190329bf85c)
|
||||||
|
- **对接补充 Issue**: [#5157](https://git.1814.love:8443/wx/HL/issues/5157)
|
||||||
|
- **对接补充 PR**: [#5162](https://git.1814.love:8443/wx/HL/pulls/5162)
|
||||||
|
- **对接补充 Merge commit**: [bc5669fd5](https://git.1814.love:8443/wx/HL/commit/bc5669fd5)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yaosutu
|
||||||
@ -0,0 +1,306 @@
|
|||||||
|
# 【修改接口·管理后台】核单 Step2 景区游玩项目字段 (#5049)
|
||||||
|
|
||||||
|
> **PR**: #5051 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-18 16:40
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单 Step2 的景区/游玩项目核算明细需要展示来源、行程天数、规格/票型、销售单价、销售小计和付款方式中文名。现有接口保留原路径,在原 `GET/PUT /v3/admin/order/{orderId}/settlement/step2` 上做兼容增强。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询门票/游玩项目核算明细 | GET | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `TicketItemVO` 出参新增 6 个字段 |
|
||||||
|
| 2 | 保存门票/游玩项目核算明细 | PUT | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `sourceType` 新增 `CUSTOM_ASSIGNMENT`,入参支持保存规格、销售单价、销售小计 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询门票/游玩项目核算明细
|
||||||
|
|
||||||
|
- **使用场景**:进入核单 Step2 景区/游玩项目页签时查询明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:幂等,只读查询。
|
||||||
|
- **响应结构**:`data` 为 `TicketItemVO[]` 数组。
|
||||||
|
|
||||||
|
### 3.2 保存门票/游玩项目核算明细
|
||||||
|
|
||||||
|
- **使用场景**:保存核单 Step2 景区/游玩项目核算明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:全量替换保存;同一份 `items` 重复提交后,以最后一次提交为准。
|
||||||
|
- **请求体兼容**:推荐使用 `{ "items": [...] }`;历史数组 body `[...]` 仍兼容。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | string | 是 | 订单 ID,长整型字符串 |
|
||||||
|
|
||||||
|
### 4.2 PUT 请求体字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `items` | array | 是 | 门票/游玩项目明细行数组,全量替换保存 | 不允许为 `null` |
|
||||||
|
| `items[].id` | string | 否 | 已存在行 ID;全量替换保存时可不传 | 长整型字符串 |
|
||||||
|
| `items[].sourceType` | string | 是 | 来源类型 | `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `CUSTOM_ASSIGNMENT` |
|
||||||
|
| `items[].sourceTypeName` | string | 否 | 来源类型中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||||
|
| `items[].scenicAssignmentId` | string | 否 | 来源 assignment ID;手工项目传 `null` | 长整型字符串或 `null` |
|
||||||
|
| `items[].dayNumber` | integer | 否 | 行程第几天;保存时以后端根据 `dayDate` 计算后的值为准 | 从 1 开始 |
|
||||||
|
| `items[].dayDate` | string | 是 | 行程日期 | `yyyy-MM-dd`,不能早于订单出发日 |
|
||||||
|
| `items[].scenicName` | string | 是 | 景区/游玩项目名称 | 1-200 字符 |
|
||||||
|
| `items[].specName` | string | 否 | 规格/票型名称 | 最大 128 字符 |
|
||||||
|
| `items[].ticketCount` | integer | 是 | 实际购票数量 | 建议非负整数 |
|
||||||
|
| `items[].ticketUnitPrice` | number | 否 | 参考成本单价,单位元 | 小数 |
|
||||||
|
| `items[].sellPrice` | number | 否 | 客户成交单价,单位元 | `>= 0` |
|
||||||
|
| `items[].totalAmount` | number | 否 | 客户成交小计,单位元;为空且有 `sellPrice` 时后端按 `sellPrice * ticketCount` 降级计算 | `>= 0` |
|
||||||
|
| `items[].plannedCost` | number | 是 | 计划成本,单位元 | `>= 0` |
|
||||||
|
| `items[].actualCost` | number | 是 | 实际成本,单位元 | `>= 0` |
|
||||||
|
| `items[].paymentMethod` | string | 否 | 付款方式;为空时默认 `COMPANY_PAID` | `SIGNED` / `COMPANY_PAID` / `CASH_PAID` |
|
||||||
|
| `items[].paymentMethodName` | string | 否 | 付款方式中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||||
|
| `items[].voucherUrls` | array | 否 | 凭证图片 URL 数组 | 字符串数组 |
|
||||||
|
| `items[].remark` | string | 否 | 备注 | 最大 500 字符 |
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
### 5.1 GET 响应字段:`TicketItemVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | string | 核单明细行 ID |
|
||||||
|
| `sourceType` | string | 来源类型 |
|
||||||
|
| `sourceTypeName` | string | 来源类型中文名;本次新增 |
|
||||||
|
| `scenicAssignmentId` | string/null | 来源 assignment ID;手工项目为 `null` |
|
||||||
|
| `dayNumber` | integer/null | 行程第几天;本次新增 |
|
||||||
|
| `dayDate` | string | 行程日期,`yyyy-MM-dd` |
|
||||||
|
| `scenicName` | string | 景区/游玩项目名称 |
|
||||||
|
| `specName` | string/null | 规格/票型名称;本次新增 |
|
||||||
|
| `ticketCount` | integer | 实际购票数量 |
|
||||||
|
| `ticketUnitPrice` | number/null | 参考成本单价 |
|
||||||
|
| `sellPrice` | number/null | 客户成交单价;本次新增 |
|
||||||
|
| `totalAmount` | number/null | 客户成交小计;本次新增 |
|
||||||
|
| `plannedCost` | number | 计划成本 |
|
||||||
|
| `actualCost` | number | 实际成本 |
|
||||||
|
| `paymentMethod` | string | 付款方式 |
|
||||||
|
| `paymentMethodName` | string/null | 付款方式中文名;本次新增 |
|
||||||
|
| `voucherUrls` | array | 凭证图片 URL 数组 |
|
||||||
|
| `remark` | string/null | 备注 |
|
||||||
|
|
||||||
|
### 5.2 PUT 响应字段:`SettlementTicketSaveRespVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `addedIds` | string[] | 本次保存新增的核单明细行 ID 列表 |
|
||||||
|
| `updatedIds` | string[] | 本次保存更新的核单明细行 ID 列表;当前全量替换语义下通常为空数组 |
|
||||||
|
| `deletedIds` | string[] | 本次保存删除的核单明细行 ID 列表;当前返回通常为空数组 |
|
||||||
|
| `totalActualCost` | string | 保存后 Step2 实际成本合计,单位元 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `sourceType`
|
||||||
|
|
||||||
|
**所属字段**:`items[].sourceType`、`data[].sourceType` | **类型**:String | **必填**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SCENIC_ASSIGNMENT` | 景区 | 景区来源行 |
|
||||||
|
| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 游玩项目来源行 |
|
||||||
|
| `CUSTOM_ASSIGNMENT` | 手工项目 | 本次新增;核单手工补充行,`scenicAssignmentId` 可为 `null` |
|
||||||
|
|
||||||
|
### 6.2 `paymentMethod`
|
||||||
|
|
||||||
|
**所属字段**:`items[].paymentMethod`、`data[].paymentMethod` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SIGNED` | 签单 | 现场签单 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 公司统一付款;未传 `paymentMethod` 时默认该值 |
|
||||||
|
| `CASH_PAID` | 现付 | 现场现金/线下现付 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `584011` | 当前核单状态不允许录门票核单 | PUT 保存时,订单不是「待核单」或「核单中」 |
|
||||||
|
| `100001` | 参数非法 | 字段格式不符合校验,例如 `sourceType` 不在允许枚举内、金额小于 0 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:GET 查询
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233855281971202/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "2077328317421187073",
|
||||||
|
"sourceType": "SCENIC_ASSIGNMENT",
|
||||||
|
"sourceTypeName": "景区",
|
||||||
|
"scenicAssignmentId": "2077233886282088450",
|
||||||
|
"dayNumber": 5,
|
||||||
|
"dayDate": "2026-07-18",
|
||||||
|
"scenicName": "呼和诺尔草原旅游区",
|
||||||
|
"specName": null,
|
||||||
|
"ticketCount": 1,
|
||||||
|
"ticketUnitPrice": 59.00,
|
||||||
|
"sellPrice": null,
|
||||||
|
"totalAmount": null,
|
||||||
|
"plannedCost": 59.00,
|
||||||
|
"actualCost": 59.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界成功:PUT 保存手工项目
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2077233855281971202/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"sourceType": "CUSTOM_ASSIGNMENT",
|
||||||
|
"scenicAssignmentId": null,
|
||||||
|
"dayDate": "2026-07-18",
|
||||||
|
"scenicName": "临时补充游玩项目",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": 12.34,
|
||||||
|
"sellPrice": 56.78,
|
||||||
|
"totalAmount": 113.56,
|
||||||
|
"plannedCost": 24.68,
|
||||||
|
"actualCost": 24.68,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "核单临时补充"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"addedIds": ["2078398065684692994"],
|
||||||
|
"updatedIds": [],
|
||||||
|
"deletedIds": [],
|
||||||
|
"totalActualCost": "24.68"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:已核单订单禁止保存
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2077233886248534018/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584011,
|
||||||
|
"message": "当前核单状态为「已核单」,不允许录门票核单,必须为「待核单」或「核单中」",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- `PUT` 是全量替换保存;前端保存时应提交页面当前完整明细列表。
|
||||||
|
- `CUSTOM_ASSIGNMENT` 表示核单手工补充项目,`scenicAssignmentId` 可以为 `null`。
|
||||||
|
- `dayNumber` 保存时以后端根据 `dayDate` 和订单出发日计算的结果为准。
|
||||||
|
- `totalAmount` 为空且 `sellPrice` 有值时,后端会按 `sellPrice * ticketCount` 降级计算。
|
||||||
|
- 未传 `paymentMethod` 时,后端默认使用 `COMPANY_PAID`。
|
||||||
|
- 订单核单状态必须是「待核单」或「核单中」才允许保存 Step2。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `sourceType` | `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` | 新增 `CUSTOM_ASSIGNMENT` |
|
||||||
|
| `sourceTypeName` | 无 | 新增,返回来源中文名 |
|
||||||
|
| `dayNumber` | 无 | 新增,返回行程第几天 |
|
||||||
|
| `specName` | 无 | 新增,返回/保存规格或票型名称 |
|
||||||
|
| `sellPrice` | 无 | 新增,返回/保存客户成交单价 |
|
||||||
|
| `totalAmount` | 无 | 新增,返回/保存客户成交小计 |
|
||||||
|
| `paymentMethodName` | 无 | 新增,返回付款方式中文名 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 手工补充项目来源 | 只能用既有来源类型兜底表达 | 可明确传 `CUSTOM_ASSIGNMENT` |
|
||||||
|
| 手工项目 assignment ID | 前端容易误以为必须有来源 ID | `CUSTOM_ASSIGNMENT` 下 `scenicAssignmentId` 可为 `null` |
|
||||||
|
| 销售金额展示 | 只能展示成本字段 | 可展示 `sellPrice` / `totalAmount` |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。新增字段为兼容性新增;旧字段继续保留。
|
||||||
|
- **前端是否必须同步上线**:否。旧页面可继续按原字段展示;需要原型新增列时再读取新字段。
|
||||||
|
- **影响已有数据**:不需要前端做数据迁移;历史行新字段可能为 `null`。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- 回滚接口代码后,前端不要再依赖 `CUSTOM_ASSIGNMENT` 和新增字段。
|
||||||
|
- 如已保存手工项目,回滚前应确认旧版本是否能识别该来源类型。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端不要把 `sourceTypeName`、`paymentMethodName` 当作提交必填项;它们是展示字段。
|
||||||
|
- 前端保存时建议保留并回传用户编辑后的 `specName`、`sellPrice`、`totalAmount`。
|
||||||
|
- 已核单订单保存 Step2 会返回 `584011`,这不是接口异常。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5049](https://git.1814.love:8443/wx/HL/issues/5049)
|
||||||
|
- **PR**: [#5051](https://git.1814.love:8443/wx/HL/pulls/5051)
|
||||||
|
- **Merge commit**: [d60324fde](https://git.1814.love:8443/wx/HL/commit/d60324fde228b50c7ba70d1f39a641e851dc351d)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,752 @@
|
|||||||
|
# 【新增/修改接口·管理后台】核团核算列表与详情聚合 (#5055)
|
||||||
|
|
||||||
|
> **PR**: [#5058](https://git.1814.love:8443/wx/HL/pulls/5058) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-18 18:20
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核团核算页面需要一个常规产品订单入口列表,并且详情页需要一次性拿到订单信息、出行人、司机车辆、应收构成、线上支付和线下收款记录。此前详情接口字段不完整,前端需要自行合并多个接口;本次把核团入口和详情聚合契约收敛到订单服务接口。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 核团核算任务列表 | GET | `/v3/admin/order-settlement/tasks` | 新增接口 | 查询常规 CORE 产品、无团期批次、已完成订单的核团任务列表 |
|
||||||
|
| 2 | 查询核团详情 | GET | `/v3/admin/order/{orderId}/settlement/return-detail` | 修改接口 | 扩展订单信息、出行人中文枚举、司机车辆集合、应收汇总/明细、支付+线下收款合并记录 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 核团核算任务列表
|
||||||
|
|
||||||
|
- **使用场景**: 核团核算页的常规产品列表。
|
||||||
|
- **认证**: 需要管理后台 JWT。
|
||||||
|
- **幂等性**: 是,只读查询。
|
||||||
|
- **请求体**: 无。
|
||||||
|
- **响应结构**: `Result<PageResult<SettlementTaskRespVO>>`。
|
||||||
|
|
||||||
|
#### 3.1.1 Query 入参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 默认值 | 校验规则 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `page` | number | 否 | `1` | 最小 `1` | 当前页码 |
|
||||||
|
| `pageSize` | number | 否 | `20` | `1` 到 `100` | 每页条数 |
|
||||||
|
| `keyword` | string | 否 | - | - | 关键词,按订单号、团号、产品名模糊查询 |
|
||||||
|
| `departureDateFrom` | string | 否 | - | `yyyy-MM-dd` | 出发日期开始 |
|
||||||
|
| `departureDateTo` | string | 否 | - | `yyyy-MM-dd` | 出发日期结束 |
|
||||||
|
| `settlementStatus` | string | 否 | - | `NONE` / `PENDING` / `COMPLETED` | 核算状态筛选 |
|
||||||
|
|
||||||
|
#### 3.1.2 响应字段
|
||||||
|
|
||||||
|
顶层统一响应:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `code` | number | 成功为 `200` |
|
||||||
|
| `message` | string | 成功为 `成功` |
|
||||||
|
| `data` | object | 分页数据 |
|
||||||
|
| `traceId` | string/null | 链路追踪 ID |
|
||||||
|
| `success` | boolean | 是否成功 |
|
||||||
|
|
||||||
|
`data` 分页字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `records` | array | 核团任务行列表,空结果返回 `[]` |
|
||||||
|
| `total` | number | 总记录数 |
|
||||||
|
| `page` | number | 当前页码 |
|
||||||
|
| `pageSize` | number | 每页条数 |
|
||||||
|
|
||||||
|
`records[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderId` | string | 订单 ID |
|
||||||
|
| `orderNo` | string | 订单号 |
|
||||||
|
| `teamNo` | string/null | 团号 |
|
||||||
|
| `productName` | string | 产品名称 |
|
||||||
|
| `departureDate` | string/null | 出发日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `returnDate` | string/null | 返团日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `peopleCount` | number | 出行人总数 |
|
||||||
|
| `peopleSummary` | string | 人数文案,如 `2成人2儿童`、`2成人1婴儿`、`0人` |
|
||||||
|
| `systemBalanceAmount` | number | 系统计算待收尾款 |
|
||||||
|
| `settlementStatus` | string | 核算状态 |
|
||||||
|
| `settlementStatusName` | string | 核算状态中文名 |
|
||||||
|
|
||||||
|
列表不返回 `routeName`、`driverName`、`vehiclePlateNo`。
|
||||||
|
|
||||||
|
#### 3.1.3 示例
|
||||||
|
|
||||||
|
典型成功:
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&settlementStatus=COMPLETED
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"orderId": "2077233886248534018",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "T20260718001",
|
||||||
|
"productName": "呼伦贝尔草原 5 日游",
|
||||||
|
"departureDate": "2026-07-20",
|
||||||
|
"returnDate": "2026-07-24",
|
||||||
|
"peopleCount": 3,
|
||||||
|
"peopleSummary": "2成人1婴儿",
|
||||||
|
"systemBalanceAmount": 0.00,
|
||||||
|
"settlementStatus": "COMPLETED",
|
||||||
|
"settlementStatusName": "已结算"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 12,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
空结果:
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&keyword=不存在的订单
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [],
|
||||||
|
"total": 0,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 查询核团详情
|
||||||
|
|
||||||
|
- **使用场景**: 点击核团任务后进入返团核算详情页。
|
||||||
|
- **认证**: 需要管理后台 JWT。
|
||||||
|
- **幂等性**: 是,只读查询。
|
||||||
|
- **请求体**: 无。
|
||||||
|
- **响应结构**: `Result<SettlementReturnDetailRespVO>`。
|
||||||
|
|
||||||
|
#### 3.2.1 路径入参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 校验规则 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `orderId` | string | 是 | 长整型字符串,必须大于 `0` | 订单 ID |
|
||||||
|
|
||||||
|
#### 3.2.2 响应字段
|
||||||
|
|
||||||
|
`data` 顶层字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderInfo` | object/null | 订单信息;取消订单可能为空 |
|
||||||
|
| `travelers` | array | 出行人列表,敏感字段已脱敏 |
|
||||||
|
| `driverVehicles` | array | 当前有效司机车辆连续服务区间;无有效派车返回 `[]` |
|
||||||
|
| `receivableSummary` | object/null | 应收汇总 |
|
||||||
|
| `receivableItems` | array | 应收计算明细 |
|
||||||
|
| `collectionSummary` | object/null | 收款汇总 |
|
||||||
|
| `collectionRecords` | array | 线上支付和线下收款合并记录 |
|
||||||
|
|
||||||
|
`orderInfo` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderId` | string | 订单 ID |
|
||||||
|
| `orderNo` | string | 订单号 |
|
||||||
|
| `teamNo` | string/null | 团号 |
|
||||||
|
| `productName` | string | 产品名称 |
|
||||||
|
| `departureDate` | string/null | 出发日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `returnDate` | string/null | 返团日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `peopleCount` | number | 出行人总数 |
|
||||||
|
| `adultCount` | number | 成人数 |
|
||||||
|
| `childCount` | number | 儿童数 |
|
||||||
|
| `youngChildCount` | number | 幼童数 |
|
||||||
|
| `babyCount` | number | 婴儿数 |
|
||||||
|
| `peopleSummary` | string | 人数文案 |
|
||||||
|
| `consultantId` | string/null | 定制师 ID |
|
||||||
|
| `consultantName` | string/null | 定制师姓名 |
|
||||||
|
| `houseStaffId` | string/null | 房务人员 ID |
|
||||||
|
| `houseStaffName` | string/null | 房务人员姓名 |
|
||||||
|
| `fleetStaffId` | string/null | 车务人员 ID |
|
||||||
|
| `fleetStaffName` | string/null | 车务人员姓名 |
|
||||||
|
| `settlementStatus` | string | 核算状态 |
|
||||||
|
| `settlementStatusName` | string | 核算状态中文名 |
|
||||||
|
|
||||||
|
`travelers[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `travelerId` | string | 出行人 ID |
|
||||||
|
| `travelerName` | string | 出行人姓名 |
|
||||||
|
| `travelerType` | string | 出行人类型 |
|
||||||
|
| `travelerTypeName` | string | 出行人类型中文名 |
|
||||||
|
| `idType` | string/null | 证件类型 |
|
||||||
|
| `idTypeName` | string/null | 证件类型中文名 |
|
||||||
|
| `phone` | string/null | 脱敏手机号 |
|
||||||
|
| `idCardNo` | string/null | 脱敏证件号 |
|
||||||
|
|
||||||
|
`driverVehicles[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `driverId` | string/null | 司机 ID |
|
||||||
|
| `driverName` | string/null | 司机姓名 |
|
||||||
|
| `driverPhone` | string/null | 脱敏司机手机号 |
|
||||||
|
| `vehicleId` | string/null | 车辆 ID |
|
||||||
|
| `vehiclePlateNo` | string/null | 车牌号 |
|
||||||
|
| `vehicleModelName` | string/null | 车型名称 |
|
||||||
|
| `seatCount` | number/null | 座位数 |
|
||||||
|
| `startDate` | string | 连续服务开始日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `endDate` | string | 连续服务结束日期,格式 `yyyy-MM-dd` |
|
||||||
|
|
||||||
|
`receivableSummary` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderAmount` | number | 订单基础金额 |
|
||||||
|
| `surchargeAmount` | number | 附加费金额 |
|
||||||
|
| `discountAmount` | number | 优惠金额 |
|
||||||
|
| `payableAmount` | number | 应收总额 |
|
||||||
|
| `formulaText` | string | 应收总额公式文案,固定为 `订单金额 + 附加费 - 优惠 = 应收总额` |
|
||||||
|
|
||||||
|
`receivableItems[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `sourceRecordId` | string/null | 来源记录 ID |
|
||||||
|
| `itemType` | string | 应收项类型 |
|
||||||
|
| `itemTypeName` | string | 应收项类型中文名 |
|
||||||
|
| `itemCode` | string/null | 应收项编码 |
|
||||||
|
| `itemName` | string/null | 应收项名称 |
|
||||||
|
| `direction` | string | 方向,`ADD` 增加应收,`DEDUCT` 减少应收 |
|
||||||
|
| `amount` | number | 金额 |
|
||||||
|
|
||||||
|
`collectionSummary` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `payableAmount` | number | 应收总额 |
|
||||||
|
| `paidAmount` | number | 累计已收 |
|
||||||
|
| `refundedAmount` | number | 累计已退 |
|
||||||
|
| `netPaidAmount` | number | 净已收,等于已收减已退 |
|
||||||
|
| `balanceAmount` | number | 待收尾款 |
|
||||||
|
| `depositPaidAmount` | number | 已收订金 |
|
||||||
|
| `balancePaidAmount` | number | 已收尾款 |
|
||||||
|
| `fullPaidAmount` | number | 已收全款 |
|
||||||
|
|
||||||
|
`collectionRecords[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `recordId` | string | 收款记录 ID |
|
||||||
|
| `recordType` | string | 记录类型,线上支付或线下收款 |
|
||||||
|
| `recordTypeName` | string | 记录类型中文名 |
|
||||||
|
| `payType` | string/null | 款项类型 |
|
||||||
|
| `payTypeName` | string/null | 款项类型中文名 |
|
||||||
|
| `channel` | string/null | 支付或收款渠道 |
|
||||||
|
| `channelName` | string/null | 支付或收款渠道中文名 |
|
||||||
|
| `receiptMethod` | string/null | 线下收款方式;在线支付和对公转账可为空 |
|
||||||
|
| `receiptMethodName` | string/null | 线下收款方式中文名 |
|
||||||
|
| `amount` | number | 金额 |
|
||||||
|
| `collectedAt` | string/null | 收款时间,格式 `yyyy-MM-dd HH:mm:ss` 或 ISO 时间字符串 |
|
||||||
|
| `status` | string/null | 收款记录状态 |
|
||||||
|
| `statusName` | string/null | 收款记录状态中文名 |
|
||||||
|
| `collectorName` | string/null | 代收人姓名 |
|
||||||
|
| `operatorName` | string/null | 登记人姓名 |
|
||||||
|
| `thirdPartyNoMasked` | string/null | 脱敏第三方交易号 |
|
||||||
|
| `transferRef` | string/null | 转账流水号 |
|
||||||
|
| `voucherUrls` | string[]/null | 凭证图片 URL |
|
||||||
|
| `remark` | string/null | 备注 |
|
||||||
|
| `includedInPaidAmount` | boolean | 是否计入已收金额 |
|
||||||
|
|
||||||
|
详情接口不返回 `needsVehicle`、`vehicleControlStatus`、`vehicleControlStatusName`。
|
||||||
|
|
||||||
|
#### 3.2.3 示例
|
||||||
|
|
||||||
|
典型成功:
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233886248534018/settlement/return-detail
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"orderInfo": {
|
||||||
|
"orderId": "2077233886248534018",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "T20260718001",
|
||||||
|
"productName": "呼伦贝尔草原 5 日游",
|
||||||
|
"departureDate": "2026-07-20",
|
||||||
|
"returnDate": "2026-07-24",
|
||||||
|
"peopleCount": 3,
|
||||||
|
"adultCount": 2,
|
||||||
|
"childCount": 0,
|
||||||
|
"youngChildCount": 0,
|
||||||
|
"babyCount": 1,
|
||||||
|
"peopleSummary": "2成人1婴儿",
|
||||||
|
"consultantId": "1001",
|
||||||
|
"consultantName": "admin",
|
||||||
|
"houseStaffId": "20001",
|
||||||
|
"houseStaffName": "房务A",
|
||||||
|
"fleetStaffId": "30001",
|
||||||
|
"fleetStaffName": "车务A",
|
||||||
|
"settlementStatus": "COMPLETED",
|
||||||
|
"settlementStatusName": "已结算"
|
||||||
|
},
|
||||||
|
"travelers": [
|
||||||
|
{
|
||||||
|
"travelerId": "2077233886248535001",
|
||||||
|
"travelerName": "张三",
|
||||||
|
"travelerType": "ADULT",
|
||||||
|
"travelerTypeName": "成人",
|
||||||
|
"idType": "ID_CARD",
|
||||||
|
"idTypeName": "身份证",
|
||||||
|
"phone": "138****0000",
|
||||||
|
"idCardNo": "150***********1234"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"driverVehicles": [
|
||||||
|
{
|
||||||
|
"driverId": "2078304714008522754",
|
||||||
|
"driverName": "李师傅",
|
||||||
|
"driverPhone": "176****3787",
|
||||||
|
"vehicleId": "2065329514644152321",
|
||||||
|
"vehiclePlateNo": "蒙C01E01",
|
||||||
|
"vehicleModelName": "丰田埃尔法",
|
||||||
|
"seatCount": 7,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-24"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"receivableSummary": {
|
||||||
|
"orderAmount": 6000.00,
|
||||||
|
"surchargeAmount": 200.00,
|
||||||
|
"discountAmount": 90.00,
|
||||||
|
"payableAmount": 6110.00,
|
||||||
|
"formulaText": "订单金额 + 附加费 - 优惠 = 应收总额"
|
||||||
|
},
|
||||||
|
"receivableItems": [
|
||||||
|
{
|
||||||
|
"sourceRecordId": "2077233886248534018",
|
||||||
|
"itemType": "BASE_ORDER",
|
||||||
|
"itemTypeName": "订单基础应收",
|
||||||
|
"itemCode": "ORDER_AMOUNT",
|
||||||
|
"itemName": "呼伦贝尔草原 5 日游",
|
||||||
|
"direction": "ADD",
|
||||||
|
"amount": 6000.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceRecordId": "2077233886248536001",
|
||||||
|
"itemType": "SURCHARGE",
|
||||||
|
"itemTypeName": "附加费",
|
||||||
|
"itemCode": "SINGLE_ROOM",
|
||||||
|
"itemName": "单房差",
|
||||||
|
"direction": "ADD",
|
||||||
|
"amount": 200.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceRecordId": "2077233886248537001",
|
||||||
|
"itemType": "DISCOUNT",
|
||||||
|
"itemTypeName": "优惠",
|
||||||
|
"itemCode": "PROMOTION",
|
||||||
|
"itemName": "活动优惠",
|
||||||
|
"direction": "DEDUCT",
|
||||||
|
"amount": 90.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collectionSummary": {
|
||||||
|
"payableAmount": 6110.00,
|
||||||
|
"paidAmount": 6110.00,
|
||||||
|
"refundedAmount": 0.00,
|
||||||
|
"netPaidAmount": 6110.00,
|
||||||
|
"balanceAmount": 0.00,
|
||||||
|
"depositPaidAmount": 1000.00,
|
||||||
|
"balancePaidAmount": 5110.00,
|
||||||
|
"fullPaidAmount": 0.00
|
||||||
|
},
|
||||||
|
"collectionRecords": [
|
||||||
|
{
|
||||||
|
"recordId": "2077233886248538001",
|
||||||
|
"recordType": "ONLINE_PAYMENT",
|
||||||
|
"recordTypeName": "在线支付",
|
||||||
|
"payType": "DEPOSIT",
|
||||||
|
"payTypeName": "订金",
|
||||||
|
"channel": "WECHAT",
|
||||||
|
"channelName": "微信支付",
|
||||||
|
"receiptMethod": null,
|
||||||
|
"receiptMethodName": null,
|
||||||
|
"amount": 1000.00,
|
||||||
|
"collectedAt": "2026-07-10 10:30:00",
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"statusName": "支付成功",
|
||||||
|
"collectorName": null,
|
||||||
|
"operatorName": null,
|
||||||
|
"thirdPartyNoMasked": "4200****0001",
|
||||||
|
"transferRef": null,
|
||||||
|
"voucherUrls": null,
|
||||||
|
"remark": null,
|
||||||
|
"includedInPaidAmount": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recordId": "2077233886248539001",
|
||||||
|
"recordType": "MANUAL_RECEIPT",
|
||||||
|
"recordTypeName": "线下收款",
|
||||||
|
"payType": "BALANCE",
|
||||||
|
"payTypeName": "尾款",
|
||||||
|
"channel": "DRIVER_CASH",
|
||||||
|
"channelName": "报账人收款",
|
||||||
|
"receiptMethod": "WECHAT_TRANSFER",
|
||||||
|
"receiptMethodName": "微信转账",
|
||||||
|
"amount": 5110.00,
|
||||||
|
"collectedAt": "2026-07-20 18:30:00",
|
||||||
|
"status": "CONFIRMED",
|
||||||
|
"statusName": "已确认",
|
||||||
|
"collectorName": "李师傅",
|
||||||
|
"operatorName": "admin",
|
||||||
|
"thirdPartyNoMasked": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"voucherUrls": [
|
||||||
|
"https://oss.example.com/receipt/a.jpg"
|
||||||
|
],
|
||||||
|
"remark": "现场收尾款",
|
||||||
|
"includedInPaidAmount": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
边界成功:无司机车辆、无收款记录。
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2077233886248534019/settlement/return-detail
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"orderInfo": {
|
||||||
|
"orderId": "2077233886248534019",
|
||||||
|
"orderNo": "HL202607180002",
|
||||||
|
"teamNo": null,
|
||||||
|
"productName": "呼伦贝尔草原 5 日游",
|
||||||
|
"departureDate": "2026-07-20",
|
||||||
|
"returnDate": "2026-07-24",
|
||||||
|
"peopleCount": 1,
|
||||||
|
"adultCount": 1,
|
||||||
|
"childCount": 0,
|
||||||
|
"youngChildCount": 0,
|
||||||
|
"babyCount": 0,
|
||||||
|
"peopleSummary": "1成人",
|
||||||
|
"consultantId": "1001",
|
||||||
|
"consultantName": "admin",
|
||||||
|
"houseStaffId": null,
|
||||||
|
"houseStaffName": null,
|
||||||
|
"fleetStaffId": null,
|
||||||
|
"fleetStaffName": null,
|
||||||
|
"settlementStatus": "NONE",
|
||||||
|
"settlementStatusName": "未结算"
|
||||||
|
},
|
||||||
|
"travelers": [],
|
||||||
|
"driverVehicles": [],
|
||||||
|
"receivableSummary": {
|
||||||
|
"orderAmount": 3000.00,
|
||||||
|
"surchargeAmount": 0.00,
|
||||||
|
"discountAmount": 0.00,
|
||||||
|
"payableAmount": 3000.00,
|
||||||
|
"formulaText": "订单金额 + 附加费 - 优惠 = 应收总额"
|
||||||
|
},
|
||||||
|
"receivableItems": [
|
||||||
|
{
|
||||||
|
"sourceRecordId": "2077233886248534019",
|
||||||
|
"itemType": "BASE_ORDER",
|
||||||
|
"itemTypeName": "订单基础应收",
|
||||||
|
"itemCode": "ORDER_AMOUNT",
|
||||||
|
"itemName": "呼伦贝尔草原 5 日游",
|
||||||
|
"direction": "ADD",
|
||||||
|
"amount": 3000.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collectionSummary": {
|
||||||
|
"payableAmount": 3000.00,
|
||||||
|
"paidAmount": 0.00,
|
||||||
|
"refundedAmount": 0.00,
|
||||||
|
"netPaidAmount": 0.00,
|
||||||
|
"balanceAmount": 3000.00,
|
||||||
|
"depositPaidAmount": 0.00,
|
||||||
|
"balancePaidAmount": 0.00,
|
||||||
|
"fullPaidAmount": 0.00
|
||||||
|
},
|
||||||
|
"collectionRecords": []
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
业务失败:订单不存在。
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/999999999999999999/settlement/return-detail
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581007,
|
||||||
|
"message": "订单不存在",
|
||||||
|
"data": null,
|
||||||
|
"traceId": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 入参汇总
|
||||||
|
|
||||||
|
| 接口 | 入参位置 | 字段 |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET /v3/admin/order-settlement/tasks` | Query | `page`、`pageSize`、`keyword`、`departureDateFrom`、`departureDateTo`、`settlementStatus` |
|
||||||
|
| `GET /v3/admin/order/{orderId}/settlement/return-detail` | Path | `orderId` |
|
||||||
|
|
||||||
|
## 5. 出参汇总
|
||||||
|
|
||||||
|
| 接口 | 出参根结构 | 主要字段 |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET /v3/admin/order-settlement/tasks` | `PageResult<SettlementTaskRespVO>` | `records[]`、`total`、`page`、`pageSize` |
|
||||||
|
| `GET /v3/admin/order/{orderId}/settlement/return-detail` | `SettlementReturnDetailRespVO` | `orderInfo`、`travelers`、`driverVehicles`、`receivableSummary`、`receivableItems`、`collectionSummary`、`collectionRecords` |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `settlementStatus`
|
||||||
|
|
||||||
|
**所属字段**: `settlementStatus`、`records[].settlementStatus`、`orderInfo.settlementStatus` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `NONE` | 未结算 | 初始态,尚未提交核单 |
|
||||||
|
| `PENDING` | 待财务复核 | 已提交核单,等待财务复核 |
|
||||||
|
| `COMPLETED` | 已结算 | 财务复核已完成 |
|
||||||
|
|
||||||
|
### 6.2 `travelerType`
|
||||||
|
|
||||||
|
**所属字段**: `travelers[].travelerType` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ADULT` | 成人 | 成人出行人 |
|
||||||
|
| `CHILD` | 儿童 | 儿童出行人 |
|
||||||
|
| `YOUNG_CHILD` | 幼童 | 幼童出行人 |
|
||||||
|
| `BABY` | 婴儿 | 婴儿出行人 |
|
||||||
|
|
||||||
|
### 6.3 `idType`
|
||||||
|
|
||||||
|
**所属字段**: `travelers[].idType` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ID_CARD` | 身份证 | 居民身份证 |
|
||||||
|
| `PASSPORT` | 护照 | 护照 |
|
||||||
|
| `BIRTH_CERT` | 出生证明 | 出生医学证明 |
|
||||||
|
|
||||||
|
### 6.4 `itemType`
|
||||||
|
|
||||||
|
**所属字段**: `receivableItems[].itemType` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `BASE_ORDER` | 订单基础应收 | 订单基础金额 |
|
||||||
|
| `SURCHARGE` | 附加费 | 附加费用,增加应收 |
|
||||||
|
| `DISCOUNT` | 优惠 | 优惠项目,减少应收 |
|
||||||
|
|
||||||
|
### 6.5 `direction`
|
||||||
|
|
||||||
|
**所属字段**: `receivableItems[].direction` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ADD` | 增加 | 计入应收增加项 |
|
||||||
|
| `DEDUCT` | 扣减 | 计入应收扣减项 |
|
||||||
|
|
||||||
|
### 6.6 `recordType`
|
||||||
|
|
||||||
|
**所属字段**: `collectionRecords[].recordType` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `ONLINE_PAYMENT` | 在线支付 | 线上支付流水 |
|
||||||
|
| `MANUAL_RECEIPT` | 线下收款 | 管理后台登记的线下收款 |
|
||||||
|
|
||||||
|
### 6.7 `payType`
|
||||||
|
|
||||||
|
**所属字段**: `collectionRecords[].payType` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEPOSIT` | 订金 | 订金 |
|
||||||
|
| `FULL` | 全款 | 全款 |
|
||||||
|
| `BALANCE` | 尾款 | 尾款 |
|
||||||
|
|
||||||
|
### 6.8 `channel`
|
||||||
|
|
||||||
|
**所属字段**: `collectionRecords[].channel` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `WECHAT` | 微信支付 | 在线微信支付 |
|
||||||
|
| `ALIPAY` | 支付宝 | 在线支付宝支付 |
|
||||||
|
| `OFFLINE_TRANSFER` | 线下转账 | 线下转账渠道 |
|
||||||
|
| `DRIVER_CASH` | 报账人收款 | 报账人代收 |
|
||||||
|
| `BANK_TRANSFER` | 对公转账 | 对公银行转账 |
|
||||||
|
| `CONSULTANT_COLLECTION` | 定制师代收 | 定制师代收 |
|
||||||
|
|
||||||
|
### 6.9 `receiptMethod`
|
||||||
|
|
||||||
|
**所属字段**: `collectionRecords[].receiptMethod` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `WECHAT_TRANSFER` | 微信转账 | 线下微信转账 |
|
||||||
|
| `CASH` | 现金收款 | 现金收款 |
|
||||||
|
|
||||||
|
### 6.10 `collectionRecords[].status`
|
||||||
|
|
||||||
|
**所属字段**: `collectionRecords[].status` | **类型**: `String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `SUCCEEDED` | 支付成功 | 成功在线支付,计入已收 |
|
||||||
|
| `PENDING` | 待支付 | 在线支付待支付,不计入已收 |
|
||||||
|
| `CLOSED` | 已关闭 | 在线支付已关闭,不计入已收 |
|
||||||
|
| `REFUNDED` | 已退款 | 在线支付已退款 |
|
||||||
|
| `CONFIRMED` | 已确认 | 线下收款已确认,计入已收 |
|
||||||
|
| `VOIDED` | 已撤销 | 线下收款已撤销,不计入已收 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `200` | 成功 | 查询成功 |
|
||||||
|
| `400` | 参数校验失败 | `page < 1`、`pageSize > 100`、`orderId <= 0`、日期格式非法、`settlementStatus` 非法 |
|
||||||
|
| `401` | 未认证 | 未携带有效管理后台 JWT |
|
||||||
|
| `581007` | 订单不存在 | 详情接口查询不存在的订单 |
|
||||||
|
| `581045` | 房务角色无权查看订单详情,房务仅可配房 | 房务管理员或房务组长访问列表或详情 |
|
||||||
|
| `584072` | 车务司机车辆信息暂时不可用,请稍后重试 | 详情接口读取司机车辆信息不可用 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
示例已按接口放在 `3.1.3` 和 `3.2.3`:列表接口包含典型成功和空结果;详情接口包含典型成功、无司机车辆/无收款记录边界成功、订单不存在业务失败。
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- 列表只包含常规 CORE 产品、无团期批次、已完成订单;团期、小蒙马、导游/摄影团队独立列表不在本接口范围。
|
||||||
|
- 列表按返团日期倒序、订单 ID 倒序返回。
|
||||||
|
- 详情接口取消订单返回成功响应,但 `orderInfo`、汇总对象可为空,数组字段为空数组。
|
||||||
|
- 详情中的手机号、身份证号、第三方交易号、司机手机号均为脱敏值。
|
||||||
|
- `collectionRecords` 已合并在线支付和线下收款,前端不需要再把支付记录接口与线下收款接口自行合并。
|
||||||
|
- `receivableSummary.payableAmount` 是应收总额,计算项通过 `receivableItems` 返回。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 列表接口
|
||||||
|
|
||||||
|
| 项 | 修改前 | 修改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| 常规产品核团列表 | 无专用接口 | 新增 `GET /v3/admin/order-settlement/tasks` |
|
||||||
|
| 人数文案 | 无 | 返回 `peopleSummary` |
|
||||||
|
| 核算状态中文 | 无 | 返回 `settlementStatusName` |
|
||||||
|
| 司机/车牌 | 不适用 | 列表不返回司机和车牌字段 |
|
||||||
|
|
||||||
|
### 10.2 详情接口
|
||||||
|
|
||||||
|
| 字段/结构 | 修改前 | 修改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderInfo.peopleSummary` | 无 | 新增 |
|
||||||
|
| `orderInfo.adultCount/childCount/youngChildCount/babyCount` | 无 | 新增 |
|
||||||
|
| `orderInfo.consultantId/consultantName` | 无 | 新增 |
|
||||||
|
| `orderInfo.houseStaffId/houseStaffName` | 无 | 新增 |
|
||||||
|
| `orderInfo.fleetStaffId/fleetStaffName` | 无 | 新增 |
|
||||||
|
| `orderInfo.settlementStatusName` | 无 | 新增 |
|
||||||
|
| `travelers[].travelerTypeName` | 无 | 新增 |
|
||||||
|
| `travelers[].idTypeName` | 无 | 新增 |
|
||||||
|
| `driverVehicles` | 无 | 新增司机车辆集合 |
|
||||||
|
| `receivableSummary` | 不完整 | 新增订单金额、附加费、优惠、应收总额、公式文案 |
|
||||||
|
| `receivableItems` | 无 | 新增应收计算明细 |
|
||||||
|
| `collectionSummary` | 不完整 | 新增已收/已退/净已收/待收/订金/尾款/全款汇总 |
|
||||||
|
| `collectionRecords` | 无 | 新增在线支付+线下收款合并记录 |
|
||||||
|
| `needsVehicle/vehicleControlStatus/vehicleControlStatusName` | 可能需要前端关注 | 本接口不返回 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否。列表为新增接口;详情为新增出参字段和新增集合结构。
|
||||||
|
- **前端是否必须同步上线**: 建议同步。核团列表页应改用新增列表接口;详情页可直接使用新增聚合字段,减少前端合并接口逻辑。
|
||||||
|
- **影响已有数据**: 不需要数据迁移。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚方式**: 回滚 PR #5058。
|
||||||
|
- **回滚后前端影响**: 新增列表接口不可用,详情新增字段消失;前端需要回退到原有多接口合并方案或旧页面逻辑。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- `orderId`、`travelerId`、`driverId`、`vehicleId` 等长整型 ID 均按字符串处理,避免 JS 精度丢失。
|
||||||
|
- 列表不要展示司机和车牌;司机车辆只在详情的 `driverVehicles` 中展示。
|
||||||
|
- 详情里的线下收款和在线支付已经按统一记录结构返回,`recordType` 用于区分来源。
|
||||||
|
- 金额字段单位均为元。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5055](https://git.1814.love:8443/wx/HL/issues/5055)
|
||||||
|
- **PR**: [#5058](https://git.1814.love:8443/wx/HL/pulls/5058)
|
||||||
|
- **Merge commit**: [d916901f8](https://git.1814.love:8443/wx/HL/commit/d916901f8)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
|
- **需求确认**: @yaosutu
|
||||||
@ -0,0 +1,255 @@
|
|||||||
|
# 🔧【消费方式纠正·管理后台】调整订单尾款显示纠正(#5116)
|
||||||
|
|
||||||
|
> **接口**:`GET /v3/admin/order/{id}/adjustment/snapshot`
|
||||||
|
> **服务**:`hl-order-service-v3`
|
||||||
|
> **更新时间**:2026-07-21
|
||||||
|
> **重要说明**:**后端接口契约、字段和金额计算均未变;本通知仅要求管理后台纠正字段取值,前端必须同步处理。**
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
管理后台订单详情与“调整订单”弹窗对同一订单展示了不同的待收尾款。订单存在 150.00 元优惠时:
|
||||||
|
|
||||||
|
- 订单详情展示待收尾款 `14350.00`;
|
||||||
|
- 调整快照实际返回 `data.basic.balanceAmount = "14350.00"`;
|
||||||
|
- 调整弹窗却展示 `14500.00`。
|
||||||
|
|
||||||
|
错误值恰好等于 `16000.00 - 1500.00 = 14500.00`,说明弹窗使用订单基价减已付金额自行计算,遗漏了 `150.00` 优惠。后端快照已经返回包含优惠、附加费、实付及退款口径的最终尾款,前端不应再次计算。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 调整订单预填快照查询 | GET | `/v3/admin/order/{id}/adjustment/snapshot` | 前端消费方式纠正 | 弹窗尾款直接读取 `data.basic.balanceAmount`;后端接口无变更 |
|
||||||
|
|
||||||
|
本次没有新增、删除或重命名任何请求字段、响应字段、枚举值或错误码。
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 调整订单预填快照查询
|
||||||
|
|
||||||
|
- **使用场景**:打开管理后台“调整订单”弹窗时,获取当前订单的基础金额及所选子领域快照。
|
||||||
|
- **认证**:管理后台登录态。
|
||||||
|
- **幂等性**:是;只读查询。
|
||||||
|
- **限流**:无本接口专属限流约定。
|
||||||
|
- **尾款取值**:直接读取 `data.basic.balanceAmount`。
|
||||||
|
- **禁止用法**:不要使用 `orderAmount - paidAmount`、`订单总额 - 已付订金`等公式自行计算尾款。
|
||||||
|
|
||||||
|
`snapshot` 响应中没有供前端重算尾款使用的 `paidAmount` 字段;`balanceAmount` 已是后端统一金额口径下的最终结果。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 位置 | 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|:---:|------|----------|
|
||||||
|
| Path | `id` | Long | 是 | 订单 ID | 必须是存在且当前账号可访问的订单 ID;前端按字符串传递,避免 JavaScript 大整数精度丢失 |
|
||||||
|
| Query | `scope` | String | 否 | 限定返回子领域;多个值用英文逗号分隔 | 不传返回全部子领域;合法值见第 6 节 |
|
||||||
|
|
||||||
|
### 4.2 请求体
|
||||||
|
|
||||||
|
GET 请求无请求体。本次请求参数没有变化。
|
||||||
|
|
||||||
|
## 5. 出参(响应)
|
||||||
|
|
||||||
|
### 5.1 响应包装
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务状态码;`200` 表示成功 |
|
||||||
|
| `message` | String | 结果说明;失败时为错误信息 |
|
||||||
|
| `data` | Object / null | 成功时为调整快照;失败时为 `null` |
|
||||||
|
| `data.basic` | Object | 订单基础信息;无论 `scope` 取何合法值均返回 |
|
||||||
|
|
||||||
|
### 5.2 `data.basic` 本问题涉及的金额字段
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 语义 | 示例值 |
|
||||||
|
|------|-----------|------|--------|
|
||||||
|
| `orderAmount` | String | 订单基价,不等同于优惠后的应收金额 | `"16000.00"` |
|
||||||
|
| `surchargeAmount` | String | 已有附加费合计 | `"0.00"` |
|
||||||
|
| `discountAmount` | String | 已有优惠合计 | `"150.00"` |
|
||||||
|
| `receivableAmount` | String | 应收总额,口径为 `max(0, orderAmount + surchargeAmount - discountAmount)` | `"15850.00"` |
|
||||||
|
| `balanceAmount` | String | 待收尾款;已综合应收、净已付和退款口径,前端直接展示 | `"14350.00"` |
|
||||||
|
|
||||||
|
金额字段均为十进制金额字符串。前端可按金额组件的统一规则格式化显示,但不得从其他字段重新推导 `balanceAmount`。
|
||||||
|
|
||||||
|
本问题订单的金额核对:
|
||||||
|
|
||||||
|
```text
|
||||||
|
应收金额 = 16000.00 + 0.00 - 150.00 = 15850.00
|
||||||
|
待收尾款 = 15850.00 - 1500.00 = 14350.00
|
||||||
|
错误展示 = 16000.00 - 1500.00 = 14500.00(漏减优惠 150.00)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `scope`(调整快照子领域)
|
||||||
|
|
||||||
|
**所属字段**:Query 参数 `scope`|**类型**:String|**必填**:否|**本次变化**:无
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `BASIC` | 基础信息 | 仅请求基础视图;`basic` 本身始终返回 |
|
||||||
|
| `PEOPLE` | 出行人 | 返回出行人子领域,同时返回 `basic` |
|
||||||
|
| `SCHEDULE` | 改期 | 返回日期/天数子领域,同时返回 `basic` |
|
||||||
|
| `ITINERARY` | 行程 | 返回行程子领域,同时返回 `basic` |
|
||||||
|
| `HOTEL_REQ` | 住宿需求 | 返回住宿需求子领域,同时返回 `basic` |
|
||||||
|
| `VEHICLE_REQ` | 用车需求 | 返回用车需求子领域,同时返回 `basic` |
|
||||||
|
| `FEE` | 费用兼容值 | 不返回独立费用列表;金额统一读取 `basic` |
|
||||||
|
|
||||||
|
多个子领域可用英文逗号连接,例如 `PEOPLE,SCHEDULE`。尾款展示只依赖始终返回的 `basic.balanceAmount`,无需为了尾款额外指定 `scope`。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 快照查询成功 |
|
||||||
|
| `581007` | 订单不存在 | `id` 对应订单不存在 |
|
||||||
|
| `587003` | scope 枚举值非法 | `scope` 中任一值不在第 6 节合法值范围内 |
|
||||||
|
|
||||||
|
本次未新增或修改错误码。登录失效、无访问权限等通用网关错误沿用管理后台现有统一处理。
|
||||||
|
|
||||||
|
## 8. 示例(典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
### 8.1 典型成功:订单存在优惠
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=BASIC
|
||||||
|
Authorization: Bearer <管理后台登录凭证>
|
||||||
|
|
||||||
|
无请求体
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"basic": {
|
||||||
|
"orderAmount": "16000.00",
|
||||||
|
"surchargeAmount": "0.00",
|
||||||
|
"discountAmount": "150.00",
|
||||||
|
"receivableAmount": "15850.00",
|
||||||
|
"balanceAmount": "14350.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端底部“尾款”应展示 `14350.00`,取值路径为 `data.basic.balanceAmount`。
|
||||||
|
|
||||||
|
### 8.2 边界情况:无优惠、无附加费
|
||||||
|
|
||||||
|
**场景说明**:优惠和附加费均为 0 时,错误公式可能碰巧得到相同结果,仍必须读取 `balanceAmount`,不可据此保留自行计算逻辑。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=BASIC
|
||||||
|
Authorization: Bearer <管理后台登录凭证>
|
||||||
|
|
||||||
|
无请求体
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"basic": {
|
||||||
|
"orderAmount": "16000.00",
|
||||||
|
"surchargeAmount": "0.00",
|
||||||
|
"discountAmount": "0.00",
|
||||||
|
"receivableAmount": "16000.00",
|
||||||
|
"balanceAmount": "14500.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:非法 scope
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/adjustment/snapshot?scope=UNKNOWN
|
||||||
|
Authorization: Bearer <管理后台登录凭证>
|
||||||
|
|
||||||
|
无请求体
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 587003,
|
||||||
|
"message": "scope 枚举值非法",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- ✅ `basic` 对所有合法 `scope` 始终返回,尾款统一读取 `data.basic.balanceAmount`。
|
||||||
|
- ✅ 优惠、附加费、实付和退款等金额因素由后端统一计入口径;前端无需也不应复算。
|
||||||
|
- ✅ `discountAmount = "0.00"` 时仍按同一路径读取尾款,避免代码按“有无优惠”产生两个分支。
|
||||||
|
- ✅ 取消订单的 `receivableAmount` 和 `balanceAmount` 为 `"0.00"`,前端按返回值展示。
|
||||||
|
- ⚠️ 金额是字符串;不得先转为 JavaScript `Number` 后自行进行财务运算。
|
||||||
|
- ❌ 不要把 `orderAmount` 当成应收金额或待收尾款。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 项目 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 后端请求字段 | 现有契约 | **不变** |
|
||||||
|
| 后端响应字段 | 已返回 `basic.balanceAmount` | **不变** |
|
||||||
|
| 后端枚举 / 错误码 | 现有契约 | **不变** |
|
||||||
|
| 前端尾款取值 | 疑似用 `orderAmount - paidAmount` 自行计算 | 直接读取 `data.basic.balanceAmount` |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 场景 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 存在 150.00 元优惠 | 弹窗显示 `14500.00`,比正确金额多 150.00 | 弹窗显示后端返回的 `14350.00` |
|
||||||
|
| 无优惠 | 可能因错误公式碰巧显示正确 | 始终按统一字段展示 |
|
||||||
|
| 存在附加费或退款口径 | 自行计算可能继续出现偏差 | 由后端统一口径的 `balanceAmount` 保证一致 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否;后端契约无变化。
|
||||||
|
- **前端是否必须同步上线**:是;当前调整弹窗已展示错误尾款。
|
||||||
|
- **影响范围**:管理后台“调整订单”弹窗底部尾款展示;订单详情页无需调整。
|
||||||
|
|
||||||
|
### 11.2 回滚说明
|
||||||
|
|
||||||
|
- 本通知没有后端变更,不涉及后端回滚。
|
||||||
|
- 前端若回滚本次取值纠正,会恢复错误展示,因此不建议回滚;需要紧急处理时应暂时隐藏尾款展示,不应恢复自行计算。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 删除或停用弹窗内“订单总额减已付金额”的尾款计算逻辑。
|
||||||
|
- 尾款唯一取值路径为 `snapshot.data.basic.balanceAmount`;若前端请求封装已解包 `data`,则取 `snapshot.basic.balanceAmount`。
|
||||||
|
- 不要使用 `orderAmount`、`receivableAmount` 与其他页面缓存的已付金额拼接计算尾款。
|
||||||
|
- 建议增加至少两条前端回归用例:存在优惠时尾款一致;存在附加费时尾款一致。
|
||||||
|
- **后端契约未变、后端无需修改;本通知是现存前端消费问题的纠正通知。**
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**:[#5116](https://git.1814.love:8443/wx/HL/issues/5116)
|
||||||
|
- **后端 PR**:无(后端无需改动)
|
||||||
|
- **后端 commit**:无(后端无需改动)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **负责人**:@yst
|
||||||
@ -0,0 +1,411 @@
|
|||||||
|
# 📝【契约纠正·管理后台】对公转账不需要代收人 (#5120)
|
||||||
|
|
||||||
|
> **变更性质**:现有接口契约澄清 + 历史文档示例纠错|**端类型**:管理后台|**更新日期**:2026-07-21
|
||||||
|
>
|
||||||
|
> 本次没有发布新的后端字段、枚举或行为变更;下文说明接口已有的稳定契约。
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
管理后台在“登记线下收款”中选择“对公转账”后仍显示“代收人”,与当前接口契约不一致。对公转账不由某位员工代收,只需填写转账流水号;代收人仅在“报账人收款”渠道下需要选择。
|
||||||
|
|
||||||
|
2026-07-10 的历史通知曾在示例中给 `BANK_TRANSFER.collectors` 放入“公司账户”对象,该示例与实际响应不符,本次一并纠正为空数组。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 方法 | 路径 | 通知类型 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | GET | `/v3/admin/order/{orderId}/payment/manual-receipt/options` | 契约澄清 | `BANK_TRANSFER.collectors` 始终为 `[]`;各渠道使用各自的候选项 |
|
||||||
|
| 2 | POST | `/v3/admin/order/{orderId}/payment/manual-receipt` | 契约澄清 | `collectorStaffId` 仅对 `DRIVER_CASH` 条件必填;`BANK_TRANSFER` 条件必填 `transferRef` |
|
||||||
|
| 3 | 文档 | `2026-07/10_4884_线下收款代收人-修改接口-管理后台.md` | 示例纠错 | 将对公转账的错误 `collectors` 对象改为 `[]` |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询线下收款选项
|
||||||
|
|
||||||
|
- **方法与路径**:`GET /v3/admin/order/{orderId}/payment/manual-receipt/options`
|
||||||
|
- **使用场景**:打开登记线下收款表单时,查询当前订单可用的渠道、款项类型、代收人和收款方式。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:幂等,只读查询。
|
||||||
|
- **限流**:无接口专属限流规则。
|
||||||
|
|
||||||
|
### 3.2 登记线下收款
|
||||||
|
|
||||||
|
- **方法与路径**:`POST /v3/admin/order/{orderId}/payment/manual-receipt`
|
||||||
|
- **使用场景**:按 options 当前返回的可用渠道和款项类型登记一笔线下收款。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:非幂等,每次成功请求会新增一条收款记录。
|
||||||
|
- **限流**:无接口专属限流规则。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数(两个接口通用)
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `orderId` | Path | String(Long) | 是 | 订单 ID,按字符串处理 |
|
||||||
|
|
||||||
|
GET 接口无 Query 参数、无请求体。
|
||||||
|
|
||||||
|
### 4.2 POST 请求体
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `channel` | String | 是 | 收款渠道 | `DRIVER_CASH` / `BANK_TRANSFER` / `CONSULTANT_COLLECTION` |
|
||||||
|
| `payType` | String | 是 | 款项类型 | 必须取 options 中当前渠道的 `allowedPayTypes` |
|
||||||
|
| `amount` | Decimal | 是 | 收款金额 | 最小 `0.01`,不能超过当前可收余额 |
|
||||||
|
| `receivedAt` | String(LocalDateTime) | 否 | 收款时间 | `yyyy-MM-dd'T'HH:mm:ss`;不传默认当前时间 |
|
||||||
|
| `transferRef` | String | 条件必填 | 对公转账流水号 | `BANK_TRANSFER` 必填,其他渠道不使用 |
|
||||||
|
| `receiptMethod` | String | 否 | 收款方式 | 取当前渠道 `receiptMethods[].value`;`BANK_TRANSFER` 为空 |
|
||||||
|
| `collectorStaffId` | String(Long) | 条件必填 | 代收人 assignmentId | **仅 `DRIVER_CASH` 必填**,且必须取当前渠道 `collectors[].collectorId` |
|
||||||
|
| `collectorType` | String | 否 | 实际代收人类型 | 不传时按 `channel` 推导;如传入,必须与渠道匹配 |
|
||||||
|
| `voucherUrls` | Array<String> | 否 | 凭证图片 URL 列表 | 可为空数组或不传 |
|
||||||
|
| `remark` | String | 否 | 备注 | 最长 500 字 |
|
||||||
|
|
||||||
|
### 4.3 渠道联动必填矩阵
|
||||||
|
|
||||||
|
| `channel` | `collectorStaffId` | `collectorType` | `transferRef` | 代收人规则 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `BANK_TRANSFER` | 不需要;误传也不作为员工代收人处理 | 可不传;如传只能为 `COMPANY_ACCOUNT` | **必填** | 不选择任何员工,公司账户是收款归属而非代收人候选项 |
|
||||||
|
| `CONSULTANT_COLLECTION` | 不需要 | 可不传;如传只能为 `CONSULTANT` | 不需要 | 使用订单定制师,不使用员工选择器 |
|
||||||
|
| `DRIVER_CASH` | **必填** | 可不传;如传只能为 `ORDER_STAFF` | 不需要 | 仅能选当前订单 options 返回的有效报账人 |
|
||||||
|
|
||||||
|
## 5. 出参(响应)
|
||||||
|
|
||||||
|
### 5.1 通用响应包装
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `code` | Integer | `200` 表示成功,其他值为业务错误码 |
|
||||||
|
| `message` | String | 结果或错误说明 |
|
||||||
|
| `data` | Object/null | 业务数据;失败时通常为 `null` |
|
||||||
|
| `success` | Boolean | 是否成功 |
|
||||||
|
|
||||||
|
### 5.2 GET options 的 `data`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `channels` | Array<ChannelOption> | 当前订单的线下收款渠道列表 |
|
||||||
|
| `channels[].channel` | String | 渠道枚举值 |
|
||||||
|
| `channels[].channelText` | String | 渠道展示文案 |
|
||||||
|
| `channels[].allowedPayTypes` | Array<String> | 当前订单状态下该渠道允许的款项类型;以本次响应为准 |
|
||||||
|
| `channels[].disabled` | Boolean | `true` 表示当前不可提交该渠道 |
|
||||||
|
| `channels[].disabledReason` | String/null | 禁用原因;可用时为 `null` |
|
||||||
|
| `channels[].collectors` | Array<CollectorOption> | **该渠道自己的代收人候选列表**;`BANK_TRANSFER` 为 `[]` |
|
||||||
|
| `channels[].receiptMethods` | Array<OptionItem> | 该渠道可选收款方式;`BANK_TRANSFER` 为 `[]` |
|
||||||
|
| `collectors[].collectorType` | String | 代收人类型 |
|
||||||
|
| `collectors[].collectorId` | String(Long) | `ORDER_STAFF` 为 assignmentId,`CONSULTANT` 为管理员 ID |
|
||||||
|
| `collectors[].collectorName` | String | 代收人姓名 |
|
||||||
|
| `collectors[].collectorRole` | String | 代收人角色值 |
|
||||||
|
| `collectors[].collectorRoleText` | String | 代收人角色文案 |
|
||||||
|
| `collectors[].defaultSelected` | Boolean | 是否默认选中 |
|
||||||
|
| `receiptMethods[].value` | String | 收款方式值 |
|
||||||
|
| `receiptMethods[].label` | String | 收款方式文案 |
|
||||||
|
| `receiptMethods[].defaultSelected` | Boolean | 是否默认选中 |
|
||||||
|
|
||||||
|
### 5.3 POST 的 `data`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | String(Long) | 收款凭据 ID |
|
||||||
|
| `orderId` | String(Long) | 订单 ID |
|
||||||
|
| `channel` / `channelLabel` | String | 收款渠道值 / 文案 |
|
||||||
|
| `payType` / `payTypeLabel` | String | 款项类型值 / 文案 |
|
||||||
|
| `amount` | Decimal | 本次收款金额 |
|
||||||
|
| `receivedAt` | String(LocalDateTime) | 收款时间 |
|
||||||
|
| `collectorStaffId` / `collectorStaffName` | String(Long)/String/null | 仅 `DRIVER_CASH` 有值 |
|
||||||
|
| `collectorType` | String | 实际代收人类型 |
|
||||||
|
| `collectorAdminId` | String(Long)/null | `CONSULTANT_COLLECTION` 为定制师管理员 ID |
|
||||||
|
| `collectorName` / `collectorRole` | String | 代收归属快照名称 / 角色 |
|
||||||
|
| `transferRef` | String/null | 对公转账流水号,仅 `BANK_TRANSFER` 有值 |
|
||||||
|
| `receiptMethod` / `receiptMethodLabel` | String/null | 收款方式值 / 文案;`BANK_TRANSFER` 为空 |
|
||||||
|
| `voucherUrls` | Array<String> | 凭证图片 URL 列表 |
|
||||||
|
| `remark` | String/null | 备注 |
|
||||||
|
| `operatorName` | String | 登记人姓名 |
|
||||||
|
| `createTime` | String(LocalDateTime) | 登记时间 |
|
||||||
|
| `voided` | Boolean | 是否已撤销;新登记为 `false` |
|
||||||
|
| `voidedByName` / `voidedAt` / `voidReason` | String/null | 撤销信息;新登记时为 `null` |
|
||||||
|
| `paidAmountAfter` | Decimal | 登记后订单累计已付金额 |
|
||||||
|
| `payStatusAfter` | String | 登记后订单支付状态 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `channel`
|
||||||
|
|
||||||
|
**所属字段**:`channel` / `channels[].channel`|**类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `BANK_TRANSFER` | 对公转账 | 无员工代收人,必须填 `transferRef` |
|
||||||
|
| `CONSULTANT_COLLECTION` | 定制师代收 | 使用订单定制师,不传 `collectorStaffId` |
|
||||||
|
| `DRIVER_CASH` | 报账人收款 | 仅允许尾款,必须从本渠道 `collectors` 选择代收人 |
|
||||||
|
|
||||||
|
### 6.2 `payType`
|
||||||
|
|
||||||
|
**所属字段**:`payType` / `channels[].allowedPayTypes[]`|**类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEPOSIT` | 订金 | 是否可登记以 options 当前返回为准 |
|
||||||
|
| `FULL` | 全款 | 是否可登记以 options 当前返回为准 |
|
||||||
|
| `BALANCE` | 尾款 | 是否可登记以 options 当前返回为准;`DRIVER_CASH` 只允许此值 |
|
||||||
|
|
||||||
|
> `BANK_TRANSFER` 的通用契约可支持 `DEPOSIT` / `FULL` / `BALANCE`,但具体订单当次能提交哪些值,必须以 options 的 `allowedPayTypes` 为准,不要将某个实例的 `BALANCE` 硬编码为全局规则。
|
||||||
|
|
||||||
|
### 6.3 `collectorType`
|
||||||
|
|
||||||
|
**所属字段**:`collectorType` / `collectors[].collectorType`|**类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 匹配渠道 |
|
||||||
|
|---|---|---|
|
||||||
|
| `COMPANY_ACCOUNT` | 公司账户 | `BANK_TRANSFER` |
|
||||||
|
| `CONSULTANT` | 定制师 | `CONSULTANT_COLLECTION` |
|
||||||
|
| `ORDER_STAFF` | 订单工作人员 | `DRIVER_CASH` |
|
||||||
|
|
||||||
|
### 6.4 `receiptMethod`
|
||||||
|
|
||||||
|
**所属字段**:`receiptMethod` / `receiptMethods[].value`|**类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `WECHAT_TRANSFER` | 微信转账 | 人员代收渠道的当前默认字典值 |
|
||||||
|
| `CASH` | 现金收款 | 人员代收渠道的当前默认字典值 |
|
||||||
|
|
||||||
|
`BANK_TRANSFER.receiptMethods=[]`;该字典可扩展,实际可选值以 options 当次返回为准。
|
||||||
|
|
||||||
|
### 6.5 `payStatusAfter`
|
||||||
|
|
||||||
|
**所属字段**:POST 响应 `payStatusAfter`|**类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `UNPAID` | 未付款 | 尚未完成有效收款 |
|
||||||
|
| `DEPOSIT_PAID` | 已付订金 | 订金已收 |
|
||||||
|
| `FULLY_PAID` | 已付全款 | 应收金额已收齐 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | message / 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `520011` | 支付类型无效或与订单状态不匹配 | `payType` 不在当前 options 允许范围内 |
|
||||||
|
| `520401` | 收款渠道非法 | `channel` 不在三个渠道枚举中 |
|
||||||
|
| `520402` | 对公转账渠道必须填写转账流水号 | `BANK_TRANSFER` 未传 `transferRef` |
|
||||||
|
| `520403` | 报账人收款渠道必须指定代收人 | `DRIVER_CASH` 未传 `collectorStaffId` |
|
||||||
|
| `520404` | 代收人不属于本订单人员 | `collectorStaffId` 不是本订单有效人员 |
|
||||||
|
| `520407` | 订单已取消,不允许登记线下收款 | 已取消订单提交 POST |
|
||||||
|
| `520408` | 收款金额必须大于 0 | `amount < 0.01` |
|
||||||
|
| `520409` | 线下收款代收人类型非法 | `collectorType` 与 `channel` 不匹配 |
|
||||||
|
| `520410` | 报账人收款只能登记尾款 | `DRIVER_CASH` 提交 `DEPOSIT` 或 `FULL` |
|
||||||
|
| `520411` | 报账人收款必须选择本订单报账人 | 选中的订单人员不是报账人 |
|
||||||
|
| `520412` | 订单没有可用定制师,不能登记定制师代收 | `CONSULTANT_COLLECTION` 无可用定制师 |
|
||||||
|
| `520413` | 本次收款金额超过当前可收余额 | `amount` 大于当前可收金额 |
|
||||||
|
|
||||||
|
## 8. 示例(典型 + 边界 + 异常)
|
||||||
|
|
||||||
|
### 8.1 典型:查询选项,对公转账无代收人
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/payment/manual-receipt/options
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
|
||||||
|
无请求体
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"channel": "CONSULTANT_COLLECTION",
|
||||||
|
"channelText": "定制师代收",
|
||||||
|
"allowedPayTypes": ["BALANCE"],
|
||||||
|
"disabled": false,
|
||||||
|
"disabledReason": null,
|
||||||
|
"collectors": [
|
||||||
|
{
|
||||||
|
"collectorType": "CONSULTANT",
|
||||||
|
"collectorId": "2037350531801993218",
|
||||||
|
"collectorName": "张三",
|
||||||
|
"collectorRole": "CONSULTANT",
|
||||||
|
"collectorRoleText": "定制师",
|
||||||
|
"defaultSelected": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"receiptMethods": [
|
||||||
|
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
|
||||||
|
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel": "BANK_TRANSFER",
|
||||||
|
"channelText": "对公转账",
|
||||||
|
"allowedPayTypes": ["BALANCE"],
|
||||||
|
"disabled": false,
|
||||||
|
"disabledReason": null,
|
||||||
|
"collectors": [],
|
||||||
|
"receiptMethods": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel": "DRIVER_CASH",
|
||||||
|
"channelText": "报账人收款",
|
||||||
|
"allowedPayTypes": [],
|
||||||
|
"disabled": true,
|
||||||
|
"disabledReason": "本订单暂无可代收报账人",
|
||||||
|
"collectors": [],
|
||||||
|
"receiptMethods": [
|
||||||
|
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
|
||||||
|
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界:对公转账不传代收人
|
||||||
|
|
||||||
|
**场景说明**:`collectorStaffId` 和 `collectorType` 都不传;仅提交 options 当前允许的款项类型与对公转账流水号。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"channel": "BANK_TRANSFER",
|
||||||
|
"payType": "BALANCE",
|
||||||
|
"amount": 100.00,
|
||||||
|
"transferRef": "BANK202607210001",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "客户对公转账"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": "2079600000000000001",
|
||||||
|
"orderId": "2079454953641836546",
|
||||||
|
"channel": "BANK_TRANSFER",
|
||||||
|
"channelLabel": "对公转账",
|
||||||
|
"payType": "BALANCE",
|
||||||
|
"payTypeLabel": "尾款",
|
||||||
|
"amount": 100.00,
|
||||||
|
"receivedAt": "2026-07-21T15:30:00",
|
||||||
|
"collectorStaffId": null,
|
||||||
|
"collectorStaffName": null,
|
||||||
|
"collectorType": "COMPANY_ACCOUNT",
|
||||||
|
"collectorAdminId": null,
|
||||||
|
"collectorName": "公司账户",
|
||||||
|
"collectorRole": "COMPANY_ACCOUNT",
|
||||||
|
"transferRef": "BANK202607210001",
|
||||||
|
"receiptMethod": null,
|
||||||
|
"receiptMethodLabel": null,
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "客户对公转账",
|
||||||
|
"operatorName": "管理员",
|
||||||
|
"createTime": "2026-07-21T15:30:00",
|
||||||
|
"voided": false,
|
||||||
|
"voidedByName": null,
|
||||||
|
"voidedAt": null,
|
||||||
|
"voidReason": null,
|
||||||
|
"paidAmountAfter": 1600.00,
|
||||||
|
"payStatusAfter": "DEPOSIT_PAID"
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 异常:报账人收款未选择代收人
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"channel": "DRIVER_CASH",
|
||||||
|
"payType": "BALANCE",
|
||||||
|
"amount": 100.00,
|
||||||
|
"receiptMethod": "CASH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 520403,
|
||||||
|
"message": "报账人收款渠道必须指定代收人",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- `collectors` 是每个 channel 自己的候选列表,不是所有渠道共用的必选列表。
|
||||||
|
- `BANK_TRANSFER`:`collectors=[]`,不传 `collectorStaffId`,必须传 `transferRef`;即使误传 `collectorStaffId`,响应中员工代收人 ID 仍为空。
|
||||||
|
- `CONSULTANT_COLLECTION`:不要提交 `collectorStaffId`;当订单没有可用定制师时,渠道禁用。
|
||||||
|
- `DRIVER_CASH`:仅允许 `BALANCE`,必须传当前订单有效报账人的 assignmentId。
|
||||||
|
- options 的 `allowedPayTypes` 随订单状态和可收余额变化。待支付订单中,对公转账/定制师代收可返回 `DEPOSIT`、`FULL`;非待支付且仍有可收余额时可返回 `BALANCE`。
|
||||||
|
- 渠道 `disabled=true` 或 `allowedPayTypes=[]` 时,当前不可提交该渠道。
|
||||||
|
- 已取消订单、无可收余额的订单不能登记线下收款。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
> 本节对比的是“错误理解 / 错误文档示例”与“正确的现有契约”,不表示后端今日发布了新的接口变更。
|
||||||
|
|
||||||
|
| 项目 | 错误理解 / 历史错误示例 | 正确契约 |
|
||||||
|
|---|---|---|
|
||||||
|
| 对公转账的代收人候选 | `BANK_TRANSFER.collectors` 含“公司账户”对象 | `BANK_TRANSFER.collectors=[]` |
|
||||||
|
| `collectorStaffId` 字段 | 所有渠道都要选代收人,或该字段已从后端删除 | 字段仍保留,**仅 `DRIVER_CASH` 条件必填** |
|
||||||
|
| 对公转账必填项 | 代收人 | `transferRef` 转账流水号 |
|
||||||
|
| 定制师代收 | 复用员工代收人选择器并传 `collectorStaffId` | 不要传 `collectorStaffId`,使用订单定制师 |
|
||||||
|
| 对公转账款项类型 | 固定只能是某一种款项 | 以 options 当前返回的 `allowedPayTypes` 为准 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。后端字段、枚举和行为没有变更。
|
||||||
|
- **前端是否必须同步上线**:是。已有页面在 `BANK_TRANSFER` 下显示代收人,需要按正确契约纠正。
|
||||||
|
|
||||||
|
### 11.2 回滚说明
|
||||||
|
|
||||||
|
- 本次仅修正通知文档,不涉及后端接口回滚。
|
||||||
|
- 若前端回滚渠道联动修正,对公转账将再次错误显示代收人。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 选中 `BANK_TRANSFER` 时,隐藏代收人选择器,并清空从其他渠道切换前残留的 `collectorStaffId`。
|
||||||
|
- 选中 `BANK_TRANSFER` 时,显示并校验 `transferRef`,不要根据统一响应结构中“存在 `collectors` 字段”就认定代收人必选。
|
||||||
|
- 仅 `DRIVER_CASH` 把 `collectorStaffId` 设为必填,候选项取当前 channel 的 `collectors`。
|
||||||
|
- `CONSULTANT_COLLECTION` 不要复用 `DRIVER_CASH` 的员工代收人校验。
|
||||||
|
- 不要把测试订单中 `BANK_TRANSFER.allowedPayTypes=["BALANCE"]` 固化为全局规则;每次均以 options 返回为准。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 关联
|
||||||
|
|
||||||
|
- **Issue**:[#5120](https://git.1814.love:8443/wx/HL/issues/5120)
|
||||||
|
- **后端 PR**:无(本次无后端代码变更)
|
||||||
|
- **后端 commit**:无(本次无后端代码变更)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**:腰苏图
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5158"
|
||||||
|
title: "派车按行程日标记车费日期"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "implemented"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@5eb8a46bee9a5a101371313e2088decd9ea843f2"
|
||||||
|
updated_at: "2026-07-25T03:03:38.490Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-22T18:00:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】派车按行程日标记车费日期
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5166](https://git.1814.love:8443/wx/HL/pulls/5166)、[wx/HL#5167](https://git.1814.love:8443/wx/HL/pulls/5167)
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5158](https://git.1814.love:8443/wx/HL/issues/5158)
|
||||||
|
>
|
||||||
|
> **影响范围**: 管理后台派单创建、修改派单、派单详情及待司机确认通知预览
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
派单的“服务日”和“收取车费日”现在是两个不同概念。未勾选车费的日期仍然是正常派车服务日,继续占用司机和车辆并按原规则处理保险,只把当天车费记为 `0`;不要把未勾选日期当成取消派车。
|
||||||
|
|
||||||
|
历史派单及未传新字段的调用均按“全部服务日收取车费”处理,不改变旧数据金额。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| POST | `/admin/fleet/assignments` | 请求新增字段 | 创建排车/直接派车时冻结计费服务日 |
|
||||||
|
| POST | `/admin/fleet/assignments/{assignmentId}/change` | 请求能力扩展 | 支持不换车、不换司机,仅修改已确认派单的计费日 |
|
||||||
|
| GET | `/admin/fleet/board/orders/{orderId}` | 响应新增字段 | 当前有效派车组返回计费日、免费服务日及说明 |
|
||||||
|
| POST | `/admin/fleet/message-templates/{templateId}/render` | 请求与模板变量新增 | 预览计费安排;默认待确认模板已增加车费安排 |
|
||||||
|
|
||||||
|
## 创建派单
|
||||||
|
|
||||||
|
`POST /admin/fleet/assignments` 的原字段不变,新增:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `chargeableServiceDates` | `LocalDate[]/null` | 否 | 收取车费的服务日期;不传或 `null` 表示全部服务日,空数组表示全部免费 |
|
||||||
|
| `vehicleFeeWaiverReason` | `String/null` | 条件必填 | 免费服务日说明,最长 256 字;全部免费时必填 |
|
||||||
|
| `confirmAllServiceDatesFree` | `Boolean/null` | 条件必填 | `chargeableServiceDates=[]` 时必须显式传 `true` |
|
||||||
|
|
||||||
|
部分日期计费示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2046400000000000001",
|
||||||
|
"startDate": "2026-07-29",
|
||||||
|
"endDate": "2026-07-31",
|
||||||
|
"vehicleId": "2046400000000000101",
|
||||||
|
"driverId": "2046400000000000201",
|
||||||
|
"holdMode": 1,
|
||||||
|
"chargeableServiceDates": ["2026-07-30"],
|
||||||
|
"vehicleFeeWaiverReason": "首尾接送已包含在团费中",
|
||||||
|
"requestId": "dispatch-5158-example"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
全部免费示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chargeableServiceDates": [],
|
||||||
|
"vehicleFeeWaiverReason": "本团车费由合作方统一结算",
|
||||||
|
"confirmAllServiceDatesFree": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
校验规则:
|
||||||
|
|
||||||
|
- 所有日期必须属于本次派车组的服务日期,否则返回参数错误。
|
||||||
|
- 空数组但未二次确认,返回“全部服务日免费时必须二次确认”。
|
||||||
|
- 空数组但未填写说明,返回“全部服务日免费时必须填写原因”。
|
||||||
|
- `null` 与不传保持兼容,默认所有服务日计费。
|
||||||
|
|
||||||
|
## 修改已确认派单的计费日
|
||||||
|
|
||||||
|
`POST /admin/fleet/assignments/{assignmentId}/change` 复用同名三个字段。仅调整车费时可以不传 `newVehicleId/newDriverId`,但必须:
|
||||||
|
|
||||||
|
- `holdMode=0`;
|
||||||
|
- `effectiveDate` 指定修改生效日;
|
||||||
|
- `reason` 填写本次修改原因;
|
||||||
|
- `chargeableServiceDates` 表示从 `effectiveDate` 起目标切片中仍收车费的日期,不能包含生效日前日期。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"effectiveDate": "2026-07-29",
|
||||||
|
"holdMode": 0,
|
||||||
|
"chargeableServiceDates": ["2026-07-30"],
|
||||||
|
"vehicleFeeWaiverReason": "首尾接送已包含在团费中",
|
||||||
|
"reason": "按实际结算范围调整",
|
||||||
|
"requestId": "change-fee-5158-example"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
已确认派单会原地更新计费标记并写操作审计,不取消/重建派单,不改变司机、车辆、占用和保险。对应月份已经关账时返回 `605600`,前端应提示先由有权限人员重开账期。
|
||||||
|
|
||||||
|
## 派单详情新增字段
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders/{orderId}` 的 `data.currentAssignment` 与 `data.activeAssignments[]` 新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chargeableServiceDates": ["2026-07-30"],
|
||||||
|
"freeServiceDates": ["2026-07-29", "2026-07-31"],
|
||||||
|
"vehicleFeeWaiverReason": "首尾接送已包含在团费中"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `chargeableServiceDates` | `LocalDate[]` | 收取车费的服务日,按日期升序 |
|
||||||
|
| `freeServiceDates` | `LocalDate[]` | 仍提供车辆服务但车费为 0 的日期,按日期升序 |
|
||||||
|
| `vehicleFeeWaiverReason` | `String/null` | 免费服务日说明 |
|
||||||
|
|
||||||
|
## 待确认通知与模板预览
|
||||||
|
|
||||||
|
`POST /admin/fleet/message-templates/{templateId}/render` 请求新增:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `serviceDates` | `LocalDate[]/null` | 本次派车的实际服务日期;不传时按订单起止日生成 |
|
||||||
|
| `chargeableServiceDates` | `LocalDate[]/null` | 预览中的计费日期;不传表示全部计费 |
|
||||||
|
| `vehicleFeeWaiverReason` | `String/null` | 免费服务日说明 |
|
||||||
|
|
||||||
|
新增模板变量:
|
||||||
|
|
||||||
|
| 变量 | 示例 |
|
||||||
|
| --- | --- |
|
||||||
|
| `{{assignment.vehicleFeeSummary}}` | `收取车费:7月30日;免费服务日:7月29日、7月31日` |
|
||||||
|
| `{{assignment.chargeableServiceDates}}` | `7月30日` |
|
||||||
|
| `{{assignment.freeServiceDates}}` | `7月29日、7月31日` |
|
||||||
|
| `{{assignment.vehicleFeeWaiverReason}}` | `首尾接送已包含在团费中` |
|
||||||
|
|
||||||
|
全部免费时摘要固定为“本团服务日均不计车费”,全部计费时为“全部服务日收取车费”。派单待确认通知会读取落库后的逐日切片并冻结正文;运营修改模板后不会追改已生成的通知。
|
||||||
|
|
||||||
|
系统默认“排车待确认”模板已增加“车费安排”和“免车费说明”。运营自行修改过的默认模板不会被数据库迁移覆盖,如需显示新内容,应在车管模板页面自行加入上述变量。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 派单页用订单实际行程服务日期生成多选项,默认全选,并明确标题为“收取车费日期”。
|
||||||
|
- [ ] 未选日期标为“免费服务日(仍派车、仍占用、仍按规则投保)”,不得触发取消派车逻辑。
|
||||||
|
- [ ] 全部取消勾选时显示二次确认并要求填写免费原因,提交 `confirmAllServiceDatesFree=true`。
|
||||||
|
- [ ] 修改已确认派单时调用既有 `/change` 接口,不直接复用创建接口;提交 `holdMode=0`、`reason` 和完整目标日期集合。
|
||||||
|
- [ ] 派单详情读取 `chargeableServiceDates/freeServiceDates` 回显,不根据车费金额反推。
|
||||||
|
- [ ] 待确认短信预览把同一组 `serviceDates/chargeableServiceDates/vehicleFeeWaiverReason` 传给模板渲染接口。
|
||||||
|
- [ ] 雪花 ID 继续按字符串处理,日期继续使用 `yyyy-MM-dd`。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 创建派单默认全计费、部分计费及全部免费二次确认均有单元测试。
|
||||||
|
- 免费服务日仅车费归零,保险成本字段与派车占用保持不变。
|
||||||
|
- 已确认派单变更写可靠 Outbox,失败可重试;已关账期在变更落库前拦截。
|
||||||
|
- 待司机确认通知从实际逐日派车切片生成并冻结计费摘要。
|
||||||
|
- `FleetServiceApplicationTest` 以 H2 真 Flyway 执行 65 条迁移通过;`spotless:check` 与 fleet `verify` 通过。
|
||||||
|
- 测试环境部署任务 `784326bb` 成功,`hl-fleet-service` 8087/8187 双实例健康。
|
||||||
|
- 以 `admin` 车务身份经测试网关实测模板列表、部分计费预览、全部免费预览、看板列表与详情,HTTP/业务码均为 200。
|
||||||
|
- 部分计费正文实际包含“收取车费:7月30日;免费服务日:7月29日、7月31日”;全部免费正文实际包含“本团服务日均不计车费”和免费原因。
|
||||||
|
- 看板详情 `currentAssignment` 已实际返回 `chargeableServiceDates/freeServiceDates/vehicleFeeWaiverReason` 契约字段。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`;前端按“前端处理清单”接入即可。
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@b309f1672f4587d11aa6b8e86d0dd4ba043274d1"
|
||||||
|
updated_at: "2026-07-25T03:11:01.515Z"
|
||||||
|
---
|
||||||
|
# 【前端待处理·管理后台】#5160 一名司机可绑定多辆常驻车
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`
|
||||||
|
> **Issue**: [wx/HL#5160](https://git.1814.love:8443/wx/HL/issues/5160)
|
||||||
|
> **PR**: [wx/HL#5164](https://git.1814.love:8443/wx/HL/pulls/5164)
|
||||||
|
> **日期**: 2026-07-22
|
||||||
|
> **影响范围**: 管理后台司机档案、车辆档案与车务派单候选
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
- 常驻关系调整为“一辆车至多一名常驻司机,一名司机可常驻多辆车”。常驻关系仍以 `fleet_vehicle.primary_driver_id` 为权威,与实际派单关系相互独立。
|
||||||
|
- 新增司机侧常驻车辆全集替换接口;空数组表示全部解绑。接口会先校验全部目标车辆,任一车辆已被其他司机占用时整单失败,不产生部分写入。
|
||||||
|
- 旧单车接口和旧单车响应字段继续保留。旧接口等价于把全集替换为单元素或空集合;旧响应字段固定取按车辆 ID 升序后的第一辆。
|
||||||
|
- `onlyResidentUnbound` 司机筛选参数兼容保留但不再生效,因为司机不再存在“已被一辆车占用”的状态。车辆侧“仅无常驻司机车辆”筛选仍有效。
|
||||||
|
|
||||||
|
## 接口清单
|
||||||
|
|
||||||
|
| # | 方法 | 路径 | 变更 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `PUT` | `/admin/fleet/drivers/{driverId}/resident-vehicles` | 新增:全量替换司机常驻车辆集合 |
|
||||||
|
| 2 | `PUT` | `/admin/fleet/drivers/{driverId}/resident-vehicle` | 保留:旧单车契约,内部按全集替换执行 |
|
||||||
|
| 3 | `GET` | `/admin/fleet/drivers/{driverId}` | 新增 `residentVehicles[]` |
|
||||||
|
| 4 | `GET` | `/admin/fleet/drivers` | 列表项新增 `residentVehicles[]`;`onlyResidentUnbound` 废弃 |
|
||||||
|
| 5 | `POST` | `/admin/fleet/assignments/candidates` | 司机候选与已选司机回显新增完整常驻车辆集合 |
|
||||||
|
| 6 | 车辆新增/编辑/导入 | 既有车辆档案接口 | 同一司机已常驻其他车辆时不再返回 `605022` |
|
||||||
|
|
||||||
|
## 1. 全量替换常驻车辆
|
||||||
|
|
||||||
|
`PUT /admin/fleet/drivers/{driverId}/resident-vehicles`
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vehicleIds": [
|
||||||
|
"2079857985374363650",
|
||||||
|
"2079857985697308674",
|
||||||
|
"2079857985848320002"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `vehicleIds` | `string[]` | ✅ | 最多 100 个 | 目标全集;`[]` 表示全部解绑;雪花 ID 必须按字符串处理 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 200, "message": "成功", "data": null, "success": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
错误行为:
|
||||||
|
|
||||||
|
- 司机不存在:`600205`
|
||||||
|
- 任一车辆不存在:`600110`
|
||||||
|
- 任一目标车辆属于其他常驻司机:`605023`
|
||||||
|
- 缺少 `vehicleIds` 或超过 100 个:`400`
|
||||||
|
|
||||||
|
## 2. 司机详情与列表
|
||||||
|
|
||||||
|
详情和分页列表项新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"residentVehiclePlate": "蒙A-T1557",
|
||||||
|
"residentVehicles": [
|
||||||
|
{
|
||||||
|
"vehicleId": "2079857985374363650",
|
||||||
|
"plate": "蒙A-T1557",
|
||||||
|
"modelName": "丰田普拉多",
|
||||||
|
"vehicleTypeId": "..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vehicleId": "2079857985697308674",
|
||||||
|
"plate": "蒙A-U1557",
|
||||||
|
"modelName": "丰田汉兰达",
|
||||||
|
"vehicleTypeId": "..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`residentVehicles` 恒按 `vehicleId` 升序;无常驻车辆时为 `[]`。兼容字段 `residentVehiclePlate` 取第一项车牌,无数据时为 `null`。
|
||||||
|
|
||||||
|
## 3. 派单候选
|
||||||
|
|
||||||
|
`POST /admin/fleet/assignments/candidates`:
|
||||||
|
|
||||||
|
- `data.drivers.records[].residentVehicles[]` 新增全部 `{ vehicleId, plate }`;旧 `residentVehicleId` / `residentVehiclePlate` 取第一项。
|
||||||
|
- `data.selectedDriverResidentVehicles[]` 新增已选司机的全部车辆候选快照;旧 `selectedDriverResidentVehicle` 取第一项。
|
||||||
|
- 所选车辆命中司机常驻集合中的任意一辆,均视为常驻匹配,不触发跨常驻确认。
|
||||||
|
- `driverKeyword` 可命中该司机任意常驻车牌。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 每辆车仍只有一个 `primaryDriverId`,车辆侧选择常驻司机仍是单选。
|
||||||
|
- 实际订单派车不修改常驻关系;跨常驻车辆派单规则继续有效。
|
||||||
|
- 旧接口、旧单车字段与 H5 单车续签契约继续可用。
|
||||||
|
- 本次只提供后端契约,不直接修改 `hl-ui`。
|
||||||
|
|
||||||
|
## 验收清单
|
||||||
|
|
||||||
|
- [ ] 司机编辑可提交多辆车辆的 `vehicleIds` 全集,保存后详情回显相同集合。
|
||||||
|
- [ ] 空数组可全部解绑;重复提交相同集合幂等成功。
|
||||||
|
- [ ] 目标车辆被其他司机占用时整单失败,原绑定保持不变。
|
||||||
|
- [ ] 车辆档案可把同一司机设为多辆车的常驻司机。
|
||||||
|
- [ ] 派单选择司机的任一常驻车辆都显示常驻匹配,其他车辆仍按跨常驻规则提示。
|
||||||
|
- [ ] 所有雪花 ID 保持字符串处理。
|
||||||
|
|
||||||
|
## 测试环境验证
|
||||||
|
|
||||||
|
2026-07-22 经测试网关 `https://api.test.1814.love:9443` 验证:
|
||||||
|
|
||||||
|
- `PUT /admin/fleet/drivers/2079857983403024385/resident-vehicles` 提交 3 个车辆 ID → `code=200`。
|
||||||
|
- `GET /admin/fleet/drivers/2079857983403024385` → `residentVehicles` 按 ID 升序返回 3 项,兼容字段 `residentVehiclePlate=蒙A-T1557`。
|
||||||
|
- 分别读取 3 辆车辆详情,`primaryDriverId` 均为 `2079857983403024385`,`primaryDriverName=朝鲁门`:
|
||||||
|
- `蒙A-T1557` / 丰田普拉多
|
||||||
|
- `蒙A-U1557` / 丰田汉兰达
|
||||||
|
- `蒙A-V1557` / 丰田兰德酷路泽
|
||||||
|
- 测试环境部署任务:`1485774a`,`hl-fleet-service` 从 `dev-v3` 部署成功。
|
||||||
|
- 后端验证:定向 527 个测试通过;`spotless:check` 通过;最新 `dev-v3` 基线执行 `mvn -pl hl-fleet-service -am verify` 通过。
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# 车务订单聊天前端入口与未读提醒
|
||||||
|
|
||||||
|
## 前端交接(截图反馈)
|
||||||
|
|
||||||
|
- 派单看板订单卡接入 `unreadMessageCount`:大于 0 显示消息红点,超过 9 显示 `9+`。
|
||||||
|
- 派单看板订单卡增加「联系定制师」按钮,使用该行 `orderId` 调用 `POST /admin/message/chat/open-fleet`,不要使用展示号 `id`。
|
||||||
|
- 订单详情弹窗增加「联系定制师」按钮,复用房务聊天抽屉和同一 `open-fleet` 接口;打开会话后自动标记已读。
|
||||||
|
- 右上角消息提醒沿用 `/admin/message/unread-count` 初始拉取 + SSE 刷新,和房务保持一致。
|
||||||
|
|
||||||
|
详细接口契约见同目录 `02_4689_车务订单聊天_定制师车务团队_新接口_管理后台.md`。
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@28a4a78888777a50b11c37a69d4bb42d43d0e552"
|
||||||
|
updated_at: "2026-07-25T03:14:52.995Z"
|
||||||
|
---
|
||||||
|
# 房务配房价格模型收口为协议价与结算价
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3
|
||||||
|
> **Issue**: [wx/HL#5176](https://git.1814.love:8443/wx/HL/issues/5176)
|
||||||
|
> **PR**: [wx/HL#5179](https://git.1814.love:8443/wx/HL/pulls/5179)
|
||||||
|
> **日期**: 2026-07-23
|
||||||
|
> **影响范围**: 管理后台 · 房务配房提交/修改/详情、订单行程住宿回配展示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
房务配房价格只保留两个权威字段:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
|------|------|
|
||||||
|
| `protoPrice` | 协议价快照,元/间·晚 |
|
||||||
|
| `settlementPrice` | 结算价快照,元/间·晚 |
|
||||||
|
|
||||||
|
历史 `sellPrice` 已从房务请求、响应、持久化、快照和内部契约中删除。订单行程住宿回配对象同时删除为兼容历史展示而重复返回的 `unitPrice`、`plannedCost`、`protocolPrice`;前端不得继续提交、读取或回退这些字段。
|
||||||
|
|
||||||
|
本变更仅清理房务配房链路,不影响行程节点、结算票等其他业务域中的同名价格字段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 变更 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 提交配房方案 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | 入参项删除 `sellPrice` |
|
||||||
|
| 修改单条配房 | PUT | `/v3/admin/order/assignments/{assignmentId}` | 入参删除 `sellPrice` |
|
||||||
|
| 房务订单详情 | GET | `/admin/house/orders/{orderId}` | `itinerary[].assignments[]` 删除 `sellPrice` |
|
||||||
|
| 订单行程详情 | GET | `/v3/admin/order/{orderId}/itinerary` | `hotelGroup.assignments[]` 删除重复价格字段,改为双价 |
|
||||||
|
|
||||||
|
### 配房提交/修改入参
|
||||||
|
|
||||||
|
只提交:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protoPrice": 588.00,
|
||||||
|
"settlementPrice": 688.00
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
不要再提交:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sellPrice": 688.00
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 房务详情配房项
|
||||||
|
|
||||||
|
`data.itinerary[].assignments[]` 当前价格字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protoPrice": "588.00",
|
||||||
|
"settlementPrice": "688.00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
已删除:`sellPrice`。
|
||||||
|
|
||||||
|
### 订单行程住宿回配项
|
||||||
|
|
||||||
|
`data.hotelGroup.assignments[]` 当前价格字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protoPrice": "588.00",
|
||||||
|
"settlementPrice": "688.00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
已删除:
|
||||||
|
|
||||||
|
- `sellPrice`
|
||||||
|
- `unitPrice`
|
||||||
|
- `plannedCost`
|
||||||
|
- `protocolPrice`
|
||||||
|
|
||||||
|
前端价格列直接读取 `settlementPrice`;需要同时展示成本参考时读取 `protoPrice`。不要为兼容旧页面在本地重新合成 `unitPrice`、`plannedCost` 或 `sellPrice`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 金额口径
|
||||||
|
|
||||||
|
- 房务配房、房务详情和订单行程详情均直接返回落库快照,不按当前资源价格日历重算。
|
||||||
|
- 终止退款住宿单价改为从房务只读契约读取 `settlementPrice`;对外退款明细字段名仍为 `dealPrice`。
|
||||||
|
- 签单住宿成本仍读取 `protoPrice`。
|
||||||
|
- 核单住宿计划/实际成本继续按 `settlementPrice × roomCount` 派生,对外核单字段不在本次删除范围。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
1. 删除所有房务配房请求中的 `sellPrice`。
|
||||||
|
2. 房务详情和订单行程住宿价格统一展示 `settlementPrice`。
|
||||||
|
3. 协议价展示读取 `protoPrice`。
|
||||||
|
4. 删除对 `sellPrice`、`unitPrice`、`plannedCost`、`protocolPrice` 的兼容读取与回退逻辑。
|
||||||
|
5. 若接口返回的 `protoPrice` 或 `settlementPrice` 为 `null`,按统一空值样式展示,不用另一字段伪造。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- 房务提交、修改、详情、历史快照、内部聚合、订单详情、退款和核单定向测试通过。
|
||||||
|
- 订单详情集成测试确认仅返回 `protoPrice`、`settlementPrice`,旧价格字段不存在。
|
||||||
|
- Flyway 新增迁移,精确删除 `house_hotel_assignment.sell_price` 与 `house_requirement_assignment_snapshot.sell_price`;其他业务域同名列不受影响。
|
||||||
|
- `D:/work2/hl-ui` 未修改,前端适配由管理后台项目单独处理。
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5178"
|
||||||
|
title: "用车手动加急与派车看板状态颜色"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "implemented"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@d07506cd3aa8a23cb2aa90f891eb853e1b7dd13f"
|
||||||
|
updated_at: "2026-07-25T03:18:55.871Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-23T10:00:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【新增接口·前端待处理·管理后台】用车手动加急与派车看板状态颜色
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3、hl-fleet-service
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5181](https://git.1814.love:8443/wx/HL/pulls/5181)
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5178](https://git.1814.love:8443/wx/HL/issues/5178)
|
||||||
|
>
|
||||||
|
> **影响范围**: 订单详情“行程安排”用车卡片、车务派车看板列表及派单详情
|
||||||
|
|
||||||
|
## 业务口径
|
||||||
|
|
||||||
|
- 用车需求的“加急/取消加急”与房务需求保持一致:只改变优先级和页面提示,不修改需求状态、派单状态、司机车辆占用、保险或费用。
|
||||||
|
- 手动加急优先于临近出团、等待回复等自动紧急规则;取消后由后端恢复现有自动紧急度。
|
||||||
|
- 前端不得根据日期自行推断是否加急,也不得乐观修改本地状态;操作成功后重新拉取订单详情和派车看板。
|
||||||
|
- 已完成或已失效需求不能加急。按钮按后端允许状态 `PENDING`、`PROCESSING` 显示。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 权限 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| POST | `/v3/admin/order/vehicle-requirement/{requirementId}/urgent` | 本单定制师或超级管理员 | 手动加急;重复调用幂等 |
|
||||||
|
| POST | `/v3/admin/order/vehicle-requirement/{requirementId}/urgent/cancel` | 本单定制师或超级管理员 | 取消手动加急;重复调用幂等 |
|
||||||
|
|
||||||
|
请求无 Body,成功统一返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": null,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
需要明确处理的业务错误:
|
||||||
|
|
||||||
|
| code | 含义 | 建议提示 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `582084` | 当前账号不是本单定制师且不是超级管理员 | 仅本单定制师可操作用车加急 |
|
||||||
|
| `582087` | 需求已完成、已失效或状态已变化 | 当前用车需求状态已变化,请刷新后重试 |
|
||||||
|
|
||||||
|
## 订单行程接口新增字段
|
||||||
|
|
||||||
|
`GET /v3/admin/order/{id}/itinerary` 的 `data.vehicleGroup.requirement` 新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requirementId": "2046400000000000001",
|
||||||
|
"status": "PENDING",
|
||||||
|
"manualUrgent": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `manualUrgent` | `Boolean` | `true` 显示“取消加急”,`false` 显示“加急” |
|
||||||
|
|
||||||
|
订单详情“行程安排 → 用车安排”卡片应复用房务卡片的交互:
|
||||||
|
|
||||||
|
- 当前有效需求存在且状态为 `PENDING/PROCESSING` 时,在“联系车务”旁显示“加急”或“取消加急”。
|
||||||
|
- 点击后调用对应接口;成功后重拉订单详情,并让派车看板列表失效/刷新。
|
||||||
|
- 提交中禁用按钮,防止连续点击;接口错误显示后端业务文案。
|
||||||
|
|
||||||
|
## 派车看板列表契约
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders` 每条 `data.records[]` 新增/统一返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"assignmentStatus": "unassigned_urgent",
|
||||||
|
"assignmentStatusLabel": "待派车",
|
||||||
|
"manualUrgent": true,
|
||||||
|
"urgentBadge": "手动加急"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `assignmentStatus` | `String` | 后端计算后的权威状态码 |
|
||||||
|
| `assignmentStatusLabel` | `String` | 后端统一中文状态文案,页面直接展示 |
|
||||||
|
| `manualUrgent` | `Boolean` | 是否由定制师手动加急 |
|
||||||
|
| `urgentBadge` | `String/null` | 手动加急固定为“手动加急”;自动加急返回既有 T-N 文案 |
|
||||||
|
|
||||||
|
同一状态内,后端已把手动加急订单排在自动加急和普通订单之前,前端不需要再次排序。
|
||||||
|
|
||||||
|
## 派单详情新增字段
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders/{orderId}` 新增:
|
||||||
|
|
||||||
|
- `data.manualUrgent`
|
||||||
|
- `data.currentAssignment.manualUrgent`
|
||||||
|
- `data.activeAssignments[].manualUrgent`
|
||||||
|
|
||||||
|
当前派单的 `assignmentStatus/assignmentStatusLabel/urgentBadge` 同样是后端计算后的有效状态,详情页不得自行按日期覆盖。
|
||||||
|
|
||||||
|
## 页面颜色映射
|
||||||
|
|
||||||
|
派车看板卡片参照房务卡片形成清晰的状态色,优先使用现有设计 Token;不要只给状态标签上色。
|
||||||
|
|
||||||
|
| 判定顺序 | 卡片语义 | 推荐底色 / 边框 | 标签 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `manualUrgent=true` | 定制师手动加急 | 淡红 `#FFF1F0` / 红 `#FF4D4F` | `urgentBadge` |
|
||||||
|
| `assignmentStatus=unassigned_urgent/holding_urgent` | 系统自动加急 | 淡橙 `#FFF7E6` / 橙 `#FA8C16` | `urgentBadge` |
|
||||||
|
| `assignmentStatus=unassigned` | 待派车 | 淡黄 `#FFFBE6` / 黄 `#FAAD14` | `assignmentStatusLabel` |
|
||||||
|
| `assignmentStatus=holding` | 待司机/车务确认 | 淡蓝 `#E6F4FF` / 蓝 `#1677FF` | `assignmentStatusLabel` |
|
||||||
|
| `assignmentStatus=assigned` | 已确认执行 | 淡绿 `#F6FFED` / 绿 `#52C41A` | `assignmentStatusLabel` |
|
||||||
|
| 完成、取消等终态 | 已结束 | 灰 `#F5F5F5` / 灰 `#BFBFBF` | `assignmentStatusLabel` |
|
||||||
|
|
||||||
|
样式优先级必须是 `manualUrgent` > 自动紧急状态 > 普通状态。卡片左侧强调边、背景和状态标签应同步变化,效果与房务“已回配/待处理”卡片一致。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] `VehicleArrangeCard.vue` 在“联系车务”旁增加“加急/取消加急”,交互和按钮状态复用 `RoomArrangeCard.vue`。
|
||||||
|
- [ ] `ArrangementTab.vue` 和订单详情父组件透传 `urgent/cancel-urgent` 事件,新增用车加急 API 方法。
|
||||||
|
- [ ] 操作成功后重新拉取订单详情和派车看板,不在前端直接翻转 `manualUrgent`。
|
||||||
|
- [ ] 派车看板卡片按上表给整卡着色,优先读取 `manualUrgent`,状态文案读取 `assignmentStatusLabel`。
|
||||||
|
- [ ] 手动加急与自动加急用不同颜色和标签,不显示英文状态码。
|
||||||
|
- [ ] 派单详情读取顶层及当前/有效派单的 `manualUrgent`,不根据出团日期反推。
|
||||||
|
- [ ] 雪花 ID 继续按字符串处理。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 用车加急/取消加急的权限、状态门控和幂等覆盖单元测试。
|
||||||
|
- order-v3 订单详情与 order-v3 → fleet 共享契约均覆盖 `manualUrgent`。
|
||||||
|
- fleet 有效状态、排序、列表和详情映射覆盖手动加急优先规则。
|
||||||
|
- order-v3 与 fleet 全量 `verify` 通过,fleet 同时通过 Spotless 门禁。
|
||||||
|
- 测试环境滚动部署成功:order-v3 任务 `126a59e4`,8086/8186 双实例健康;fleet 任务 `a21c7f2e`,8087/8187 双实例健康。
|
||||||
|
- 以 `wx` 定制师经网关加急,行程接口实际返回 `manualUrgent=true`;以 `admin` 车务读取看板,实际返回 `unassigned_urgent`、`待派车`、`手动加急`,派单详情顶层也返回 `manualUrgent=true`。
|
||||||
|
- 验收结束已由 `wx` 取消加急并复查,看板恢复 `manualUrgent=false`、`unassigned`、`urgentBadge=null`,未遗留测试状态。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`;前端按“前端处理清单”接入即可。
|
||||||
@ -0,0 +1,145 @@
|
|||||||
|
# 订单详情「联系车务」独立未读红点
|
||||||
|
|
||||||
|
> 日期:2026-07-23
|
||||||
|
> 工单:HL #5180
|
||||||
|
> 影响范围:管理后台订单详情 → 行程安排 → 用车安排;聊天 SSE 实时状态
|
||||||
|
> 状态:前端待处理
|
||||||
|
|
||||||
|
## 1. 问题与口径
|
||||||
|
|
||||||
|
车务通过 `FLEET:{orderId}` 会话给定制师发送消息后,顶部全局铃铛能显示未读,但当前订单「联系车务」按钮没有订单维度红点。房务按钮已有同款能力,本次车务必须复用相同的角标样式、实时刷新和已读清零交互。
|
||||||
|
|
||||||
|
房务与车务未读是两个独立业务会话,禁止继续共用一个字段:
|
||||||
|
|
||||||
|
- `unreadMessageCount`:当前登录定制师在本订单 `HOUSE:{orderId}` 会话的未读数,只供「联系房务」使用。
|
||||||
|
- `fleetUnreadMessageCount`:当前登录定制师在本订单 `FLEET:{orderId}` 会话的未读数,只供「联系车务」使用。
|
||||||
|
- 顶部铃铛全局未读只能说明“存在未读”,不能作为当前订单按钮是否亮红点的判断依据。
|
||||||
|
|
||||||
|
## 2. 接口变更
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/{id}/itinerary
|
||||||
|
```
|
||||||
|
|
||||||
|
响应新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"unreadMessageCount": 0,
|
||||||
|
"fleetUnreadMessageCount": 2,
|
||||||
|
"canContactFleet": true,
|
||||||
|
"contactFleetDisabledReason": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `unreadMessageCount` | Integer | HOUSE 订单会话未读;既有字段,语义不变 |
|
||||||
|
| `fleetUnreadMessageCount` | Integer | FLEET 订单会话未读;新增字段;无会话、未登录或软依赖降级时为 `0` |
|
||||||
|
|
||||||
|
两个字段必须分别消费,不能用 `fleetUnreadMessageCount || unreadMessageCount` 一类兜底混用,否则房务消息会错误点亮车务按钮。
|
||||||
|
|
||||||
|
后端内部取数同时增加可选 `unreadScope`:
|
||||||
|
|
||||||
|
- FLEET 不传或传 `TEAM`:保持车务看板既有团队共享未读口径。
|
||||||
|
- FLEET 传 `PERSONAL`:按指定 `adminId` 返回车务发给该定制师的个人未读;订单详情的 `fleetUnreadMessageCount` 使用此口径。
|
||||||
|
- HOUSE:仍按成员行个人未读统计,行为不变。
|
||||||
|
|
||||||
|
该字段属于 order-v3 → user-service 内部契约,管理后台无需直接传递。
|
||||||
|
|
||||||
|
## 3. 前端实现要求
|
||||||
|
|
||||||
|
### 3.1 按钮角标
|
||||||
|
|
||||||
|
`VehicleArrangeCard.vue` 的「联系车务」按钮按 `RoomArrangeCard.vue` 原样复用 `NBadge`:
|
||||||
|
|
||||||
|
- `fleetUnreadMessageCount > 0` 时显示红色数字角标。
|
||||||
|
- `max=99`,`0` 自动隐藏。
|
||||||
|
- 按钮禁用时仍可保留未读提示,不能因为 `canContactFleet=false` 静默吞掉既有会话未读;是否允许重新开会话继续遵守后端门控。
|
||||||
|
- 样式、偏移、尺寸与「联系房务」保持一致,不新增另一套红点 CSS。
|
||||||
|
|
||||||
|
`v3Adapter.js` 需把行程接口的 `fleetUnreadMessageCount` 映射为独立本地字段(建议 `itineraryFleetUnreadCount`),不要覆盖现有 `itineraryUnreadCount`。
|
||||||
|
|
||||||
|
### 3.2 SSE 实时刷新
|
||||||
|
|
||||||
|
订单详情现有 `lastChatSignal` 监听只匹配 `HOUSE:{orderId}`。需要同时支持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HOUSE:{orderId} -> 刷新联系房务角标
|
||||||
|
FLEET:{orderId} -> 刷新联系车务角标
|
||||||
|
```
|
||||||
|
|
||||||
|
收到当前订单的 `FLEET:{orderId}` `im-chat` / `im-chat-read` 信令后,轻量重拉行程接口并只合并 `fleetUnreadMessageCount`;不能整页闪骨架屏,也不能把其他订单的全局未读数套到当前订单。
|
||||||
|
|
||||||
|
### 3.3 已读清零与竞态
|
||||||
|
|
||||||
|
- 打开「联系车务」并收到聊天抽屉 `read` 事件后,立即把当前订单车务角标本地清零。
|
||||||
|
- 房务已读只清 HOUSE 字段,车务已读只清 FLEET 字段,互不影响。
|
||||||
|
- 复用房务现有 `chatReadEpoch`(或等价版本号)防竞态:已读期间较早发出的刷新请求返回时,不得把旧未读数重新覆盖成红点。
|
||||||
|
- 切换订单时按新 `orderId` 重算,不能沿用上一个订单角标。
|
||||||
|
|
||||||
|
## 4. 验收场景
|
||||||
|
|
||||||
|
- [ ] 当前订单无未读时,「联系车务」不显示角标。
|
||||||
|
- [ ] 车务给本单定制师发送 1 条消息后,不刷新页面,顶部铃铛和本单「联系车务」都立即显示红点/数字 `1`。
|
||||||
|
- [ ] 当前订单无车务未读、其他订单有车务未读时,顶部铃铛可亮,但当前订单「联系车务」不亮。
|
||||||
|
- [ ] 当前订单只有房务未读时,只点亮「联系房务」,不得点亮「联系车务」。
|
||||||
|
- [ ] 打开本单车务会话并读完后,「联系车务」角标立即消失,顶部铃铛与站内信列表同步收敛。
|
||||||
|
- [ ] 已读操作与 SSE 刷新并发时,旧请求不会让已清零红点复现。
|
||||||
|
- [ ] 角标样式、最大数字和按钮布局与房务模块一致。
|
||||||
|
|
||||||
|
## 5. 兼容性
|
||||||
|
|
||||||
|
新增响应字段为加性变更。未接入新字段的旧前端行为不变;前端接入后仍使用既有 `POST /admin/message/chat/open-fleet` 打开会话,`orderId` 必须使用数字雪花字符串,不能使用 `HL...` 展示号。
|
||||||
|
|
||||||
|
## 6. 2026-07-23 车务看板回归补充
|
||||||
|
|
||||||
|
本节针对车务管理员在「派单看板」点击「联系定制师」的反向会话入口。它使用车务团队
|
||||||
|
`TEAM` 未读口径,不得复用定制师订单详情的 `PERSONAL` 字段。
|
||||||
|
|
||||||
|
### 6.1 首次点击必须立即打开真实会话
|
||||||
|
|
||||||
|
当前 `FleetBoard` 在首次点击时同一轮设置 `chatOrder` 和 `chatOpen=true`,随后通过
|
||||||
|
`v-if="chatOrder"` 首次挂载 `ChatDrawer`。`ChatDrawer` 对 `props.show` 的 watcher 没有
|
||||||
|
立即执行,因此组件以 `show=true` 首次挂载时不会调用 `open-fleet`,只显示默认「对端」
|
||||||
|
和空线程;关闭后第二次发生 `false -> true` 才会正常调用接口。
|
||||||
|
|
||||||
|
前端需要修复该生命周期缺口:
|
||||||
|
|
||||||
|
- `ChatDrawer` 首次挂载且 `show=true` 时必须执行一次 `openFlow()`;建议在现有合并 watcher
|
||||||
|
保留 `flush: 'post'` 并增加 `immediate: true`,或采用等价的挂载处理。
|
||||||
|
- 首次点击只能调用一次 `POST /admin/message/chat/open-fleet`,不能因 `show/bizId` 同轮变化
|
||||||
|
重复打开或让后一个请求取消前一个请求。
|
||||||
|
- 首次接口响应后立即展示真实 `peerName/peerRoleLabel/thread`;加载完成前保持 loading,
|
||||||
|
不能先落成可交互的「对端」空会话。
|
||||||
|
- `show=false` 首次挂载不得调用打开接口;之后每次 `false -> true` 仍只调用一次。
|
||||||
|
|
||||||
|
### 6.2 车务看板未读角标必须实时刷新
|
||||||
|
|
||||||
|
车务看板列表、密集视图和详情抽屉虽然已经消费 `unreadMessageCount`,但当前只订阅
|
||||||
|
`useFleetDispatchRefresh` 的派车业务信令,没有订阅聊天总线 `lastChatSignal`。因此定制师
|
||||||
|
发来 `FLEET:{orderId}` 新消息后只能整页刷新才出现角标。
|
||||||
|
|
||||||
|
前端需要按房务模块的方式补齐:
|
||||||
|
|
||||||
|
- 监听全局 `lastChatSignal`,仅处理当前页订单的 `FLEET:{orderId}` `im-chat/im-chat-read`
|
||||||
|
信令;其他模块和其他订单不得误刷新角标。
|
||||||
|
- 信令只表示“数据变化”,不能把顶部铃铛的合并未读数直接写进订单。应轻量重拉当前筛选/
|
||||||
|
分页的车务看板订单接口,并只合并对应行的 `unreadMessageCount`。
|
||||||
|
- 轻量刷新不得切换全页 loading、闪白、重置筛选、分页或滚动位置;短时间连续信令需要合并。
|
||||||
|
- 列表 `orders`、当前 `activeOrder`、当前 `chatOrder` 的同一订单计数必须一起收敛。
|
||||||
|
- 打开会话收到 `read` 后立即本地清零,并使用读态纪元/刷新序号防止较早发出的异步刷新把
|
||||||
|
旧未读数重新覆盖回来。
|
||||||
|
- 聊天抽屉正在打开当前会话时由 `ChatDrawer` 实时拉消息并标已读;看板 watcher 不得与其
|
||||||
|
抢写非零计数。
|
||||||
|
|
||||||
|
### 6.3 回归验收
|
||||||
|
|
||||||
|
- [ ] 清缓存后首次点击任一订单「联系定制师」,只发起一次 `open-fleet`,直接显示真实定制师及历史消息,不出现「对端」空会话。
|
||||||
|
- [ ] 关闭再打开同一订单,行为与首次一致,不依赖“点第二次才正常”。
|
||||||
|
- [ ] 定制师给该订单发送 1 条新消息后,车务不刷新页面即可在对应订单按钮看到房务同款红色数字角标。
|
||||||
|
- [ ] 新消息只更新对应订单;其他订单、HOUSE 会话和顶部全局未读不得误点亮该按钮。
|
||||||
|
- [ ] 实时更新角标时页面不闪 loading,筛选、分页、滚动位置保持不变。
|
||||||
|
- [ ] 打开会话后角标立即清零;异步刷新晚返回也不会让红点复现。
|
||||||
|
- [ ] 列表视图、密集视图、订单详情抽屉三处计数一致。
|
||||||
|
- [ ] 增加组件测试:`ChatDrawer(show=true)` 首挂打开一次、`show=false` 首挂不打开、聊天信令轻量更新/已读竞态不复亮。
|
||||||
@ -0,0 +1,425 @@
|
|||||||
|
# 【🔧 修改接口·管理后台】Step2 票种规格默认值(#5185)
|
||||||
|
|
||||||
|
> **PR**: #5191 | **更新时间**: 2026-07-23
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
Step2 门票/游玩项目明细原先可能返回或保存空的 `specName`,前端无法稳定展示票种/规格。现在查询和保存统一补齐“成人票”默认值,同时保留已有的非空规格。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | Step2 查询门票核单明细 | GET | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | 无明确规格的明细统一返回 `specName=成人票` |
|
||||||
|
| 2 | Step2 保存门票核单明细 | PUT | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `specName` 为 `null`、空串或纯空白时按“成人票”保存 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 Step2 查询门票核单明细
|
||||||
|
|
||||||
|
- **使用场景**:进入或刷新核单 Step2 时查询门票/游玩项目明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:是,只读查询。
|
||||||
|
- **限流**:无接口专属限流约定。
|
||||||
|
- **默认语义**:自动生成且无明确规格、或已有明细规格为空时,`specName` 返回“成人票”。
|
||||||
|
- **保留语义**:已有非空规格原样返回,例如“骑马体验”。
|
||||||
|
|
||||||
|
### 3.2 Step2 保存门票核单明细
|
||||||
|
|
||||||
|
- **使用场景**:全量保存 Step2 门票/游玩项目明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:业务数据为全量替换语义;重复提交相同明细得到相同业务内容,行 ID 可能重新生成。
|
||||||
|
- **限流**:无接口专属限流约定。
|
||||||
|
- **默认语义**:`items[].specName` 为 `null`、`""` 或纯空白时,保存并回读为“成人票”。
|
||||||
|
- **保留语义**:非空规格原样保存,例如“骑马体验”不会被替换。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | Long / String | 是 | 订单 ID,必须大于 `0`;19 位 ID 建议按字符串拼入路径 |
|
||||||
|
|
||||||
|
GET 无 Query 参数、无请求体。
|
||||||
|
|
||||||
|
### 4.2 PUT 请求体
|
||||||
|
|
||||||
|
推荐使用对象形式;接口同时兼容直接提交明细数组。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `items` | Array | 是 | 门票/游玩项目明细,全量替换 | 可为空数组;空数组表示清空已保存草稿 |
|
||||||
|
|
||||||
|
### 4.3 `items[]` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `id` | Long / String | 否 | 已存在行 ID;新增或自动生成行可为空 | 19 位 ID 建议使用字符串 |
|
||||||
|
| `sourceType` | String | 是 | 来源类型 | `SCENIC_ASSIGNMENT`、`ACTIVITY_ASSIGNMENT`、`CUSTOM_ASSIGNMENT` |
|
||||||
|
| `sourceTypeName` | String | 否 | 来源类型中文名 | 最长 32 字符 |
|
||||||
|
| `scenicAssignmentId` | Long / String | 否 | 来源记录 ID;手动补充行为空 | 19 位 ID 建议使用字符串 |
|
||||||
|
| `dayNumber` | Integer | 否 | 行程第几天,从 `1` 开始;保存时按 `dayDate` 计算 | 无需前端计算 |
|
||||||
|
| `dayDate` | String | 是 | 项目日期 | `yyyy-MM-dd`,不得早于订单出发日期 |
|
||||||
|
| `scenicName` | String | 是 | 景区或游玩项目名称 | 非空,最长 200 字符 |
|
||||||
|
| `specName` | String | 否 | 票种/规格名称 | 最长 128 字符;`null`、空串、纯空白统一为“成人票” |
|
||||||
|
| `ticketCount` | Integer | 是 | 实际购票数量 | 套餐含项目可填 `0` |
|
||||||
|
| `ticketUnitPrice` | Decimal | 否 | 参考单价,单位元 | 自费项目可填;包价项目可为 `null` |
|
||||||
|
| `sellPrice` | Decimal | 否 | 客户成交单价,单位元 | 大于等于 `0` |
|
||||||
|
| `totalAmount` | Decimal | 否 | 客户成交小计,单位元 | 大于等于 `0`;为空时按 `sellPrice × ticketCount` 计算 |
|
||||||
|
| `plannedCost` | Decimal | 是 | 计划成本,单位元 | 大于等于 `0` |
|
||||||
|
| `actualCost` | Decimal | 是 | 实际成本,单位元 | 大于等于 `0` |
|
||||||
|
| `paymentMethod` | String | 否 | 付款方式 | `SIGNED`、`COMPANY_PAID`、`CASH_PAID`;为空时为 `COMPANY_PAID` |
|
||||||
|
| `paymentMethodName` | String | 否 | 付款方式中文名 | 最长 32 字符 |
|
||||||
|
| `voucherUrls` | Array\<String> | 否 | 凭证图片 URL 列表 | 可为空数组 |
|
||||||
|
| `remark` | String | 否 | 备注 | 最长 500 字符 |
|
||||||
|
|
||||||
|
## 5. 出参(响应)
|
||||||
|
|
||||||
|
### 5.1 统一响应字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务状态码,成功为 `200` |
|
||||||
|
| `message` | String | 响应消息,成功为“成功” |
|
||||||
|
| `data` | Object / Array | GET 为明细数组,PUT 为保存结果对象 |
|
||||||
|
| `traceId` | String / null | 链路追踪 ID |
|
||||||
|
| `success` | Boolean | `code=200` 时为 `true` |
|
||||||
|
|
||||||
|
### 5.2 GET `data[]`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可为空 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `id` | String / null | 是 | 已保存的 19 位行 ID 按字符串返回;未保存的自动生成行可为 `null` |
|
||||||
|
| `sourceType` | String | 否 | 来源类型,取值见 §6.2 |
|
||||||
|
| `sourceTypeName` | String | 是 | 来源类型中文名 |
|
||||||
|
| `scenicAssignmentId` | String / null | 是 | 19 位来源记录 ID 按字符串返回;手动补充行为空 |
|
||||||
|
| `dayNumber` | Integer | 是 | 根据项目日期与订单行程计算的天序 |
|
||||||
|
| `dayDate` | String | 否 | 项目日期,格式为 `yyyy-MM-dd` |
|
||||||
|
| `scenicName` | String | 否 | 景区或游玩项目名称 |
|
||||||
|
| `specName` | String | 否 | 无明确规格时返回“成人票”;已有非空规格原样返回 |
|
||||||
|
| `ticketCount` | Integer | 否 | 实际购票数量 |
|
||||||
|
| `ticketUnitPrice` | Decimal | 是 | 参考单价,单位元 |
|
||||||
|
| `sellPrice` | Decimal | 是 | 客户成交单价,单位元 |
|
||||||
|
| `totalAmount` | Decimal | 是 | 客户成交小计,单位元 |
|
||||||
|
| `plannedCost` | Decimal | 否 | 计划成本,单位元 |
|
||||||
|
| `actualCost` | Decimal | 否 | 实际成本,单位元 |
|
||||||
|
| `paymentMethod` | String | 是 | 付款方式,取值见 §6.3 |
|
||||||
|
| `paymentMethodName` | String | 是 | 付款方式中文名 |
|
||||||
|
| `voucherUrls` | Array\<String> | 是 | 凭证图片 URL 列表 |
|
||||||
|
| `remark` | String | 是 | 备注 |
|
||||||
|
|
||||||
|
### 5.3 PUT `data`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `addedIds` | Array\<String> | 本次新增行 ID 列表 |
|
||||||
|
| `updatedIds` | Array\<String> | 本次更新行 ID 列表 |
|
||||||
|
| `deletedIds` | Array\<String> | 本次删除行 ID 列表 |
|
||||||
|
| `totalActualCost` | String | 保存后实际成本合计,单位元 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `specName`(数据字典 `settlement_ticket_spec`)
|
||||||
|
|
||||||
|
**所属字段**:`items[].specName` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `成人票` | 成人票 | 当前默认票种/规格;字典接口返回的 `dictValue` |
|
||||||
|
|
||||||
|
加载选项使用:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/dict/data/settlement_ticket_spec
|
||||||
|
```
|
||||||
|
|
||||||
|
展示使用字典项 `dictLabel`,提交使用 `dictValue`。本次没有新增 `specCode` 字段。
|
||||||
|
|
||||||
|
### 6.2 `sourceType`
|
||||||
|
|
||||||
|
**所属字段**:`items[].sourceType` | **类型**:String | **必填**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SCENIC_ASSIGNMENT` | 景区 | 来源于景区项目 |
|
||||||
|
| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 来源于游玩项目 |
|
||||||
|
| `CUSTOM_ASSIGNMENT` | 手动补充 | 核单时手动新增 |
|
||||||
|
|
||||||
|
### 6.3 `paymentMethod`
|
||||||
|
|
||||||
|
**所属字段**:`items[].paymentMethod` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SIGNED` | 签单 | 供应商签单 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 未传付款方式时的默认值 |
|
||||||
|
| `CASH_PAID` | 现付 | 现场付款,可附凭证 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 查询或保存成功 |
|
||||||
|
| `400` | 请求参数校验失败 | 必填字段为空、枚举值不合法、金额为负数、字段超长或日期格式错误 |
|
||||||
|
| `401` | 未认证或认证失效 | 未携带有效管理后台 JWT |
|
||||||
|
| `584011` | 当前核单状态不允许录门票核单 | PUT 时订单核单状态不是“待核单”或“核单中” |
|
||||||
|
| `584017` | 订单缺出发日期 | PUT 时无法根据 `dayDate` 计算 `dayNumber` |
|
||||||
|
| `584018` | 项目日期早于订单出发日期 | PUT 的 `items[].dayDate` 早于订单出发日期 |
|
||||||
|
| `500` | 系统异常 | 查询或保存过程发生未预期异常 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:查询自动生成明细
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/settlement/step2
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"sourceType": "SCENIC_ASSIGNMENT",
|
||||||
|
"sourceTypeName": "景区",
|
||||||
|
"scenicAssignmentId": "2079454953641837001",
|
||||||
|
"dayNumber": 1,
|
||||||
|
"dayDate": "2026-07-21",
|
||||||
|
"scenicName": "示例景区",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": 100.00,
|
||||||
|
"sellPrice": 120.00,
|
||||||
|
"totalAmount": 240.00,
|
||||||
|
"plannedCost": 200.00,
|
||||||
|
"actualCost": 200.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 典型成功:提交字典选中的“成人票”
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079454953641836546/settlement/step2
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"sourceType": "SCENIC_ASSIGNMENT",
|
||||||
|
"sourceTypeName": "景区",
|
||||||
|
"scenicAssignmentId": "2079454953641837001",
|
||||||
|
"dayNumber": 1,
|
||||||
|
"dayDate": "2026-07-21",
|
||||||
|
"scenicName": "示例景区",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": 100.00,
|
||||||
|
"sellPrice": 120.00,
|
||||||
|
"totalAmount": 240.00,
|
||||||
|
"plannedCost": 200.00,
|
||||||
|
"actualCost": 200.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"addedIds": ["2079454953641840001"],
|
||||||
|
"updatedIds": [],
|
||||||
|
"deletedIds": [],
|
||||||
|
"totalActualCost": "200.00"
|
||||||
|
},
|
||||||
|
"traceId": "b2c3d4e5-f6a7-8901",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 边界情况:空规格归一为“成人票”
|
||||||
|
|
||||||
|
**保存请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079454953641836546/settlement/step2
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"sourceType": "ACTIVITY_ASSIGNMENT",
|
||||||
|
"sourceTypeName": "游玩项目",
|
||||||
|
"scenicAssignmentId": "2079454953641837002",
|
||||||
|
"dayDate": "2026-07-22",
|
||||||
|
"scenicName": "示例游玩项目",
|
||||||
|
"specName": null,
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": null,
|
||||||
|
"sellPrice": 0,
|
||||||
|
"totalAmount": 0,
|
||||||
|
"plannedCost": 0,
|
||||||
|
"actualCost": 0,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**保存响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"addedIds": ["2079454953641840002"],
|
||||||
|
"updatedIds": [],
|
||||||
|
"deletedIds": [],
|
||||||
|
"totalActualCost": "0"
|
||||||
|
},
|
||||||
|
"traceId": "c3d4e5f6-a7b8-9012",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
随后 GET 回读时,该行的关键字段为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scenicName": "示例游玩项目",
|
||||||
|
"specName": "成人票"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 业务失败:非法来源类型
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079454953641836546/settlement/step2
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"sourceType": "UNKNOWN",
|
||||||
|
"dayDate": "2026-07-21",
|
||||||
|
"scenicName": "示例项目",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 1,
|
||||||
|
"plannedCost": 0,
|
||||||
|
"actualCost": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / CUSTOM_ASSIGNMENT 之一",
|
||||||
|
"data": null,
|
||||||
|
"traceId": "d4e5f6a7-b8c9-0123",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- GET:自动生成明细没有明确规格时,返回 `specName=成人票`。
|
||||||
|
- GET:历史已保存明细的 `specName` 为 `null`、空串或纯空白时,也返回“成人票”。
|
||||||
|
- GET/PUT:已有非空规格保持不变,例如“骑马体验”不会被覆盖。
|
||||||
|
- PUT:`specName` 最大 128 字符;空值会归一为“成人票”而不是报错。
|
||||||
|
- PUT:`items` 为全量数据;遗漏的旧明细不会继续保留。
|
||||||
|
- PUT:仅订单核单状态为“待核单”或“核单中”时允许保存。
|
||||||
|
- 前端从 `settlement_ticket_spec` 字典读取选项,展示 `dictLabel`,提交 `dictValue` 到 `specName`。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `items[].specName` | String,可返回或保存为空 | String;查询和保存的空值统一为“成人票” |
|
||||||
|
| `items[].specCode` | 不存在 | 仍不存在,本次未新增 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 自动生成明细没有明确规格 | `specName` 可能为空或与项目名重复 | `specName=成人票` |
|
||||||
|
| 保存 `specName=null`、空串或纯空白 | 可能按空值保存和回显 | 保存、回读均为“成人票” |
|
||||||
|
| 保存非空自定义规格 | 原样保存 | 仍原样保存 |
|
||||||
|
| 接口数量 | GET、PUT 两个既有接口 | 不变,没有新增 Step2 接口 |
|
||||||
|
|
||||||
|
## 11. 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否,接口路径、请求结构和响应字段均未改变;只收紧了空规格的返回语义。
|
||||||
|
- **前端是否必须同步上线**:否;前端可逐步接入字典下拉,未接入时也会收到稳定的“成人票”默认值。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端如有 `specName || "成人票"` 的临时兜底,可在确认接口已覆盖当前环境后移除。
|
||||||
|
- 不要新增或提交 `specCode`;当前契约只使用 `specName`。
|
||||||
|
- 选择字典项后提交 `dictValue`,不要提交 `dictLabel` 以外的展示元数据或 `dictDataId`。
|
||||||
|
- 自定义非空规格可以继续提交,接口不会强制替换为字典当前默认项。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5185](https://git.1814.love:8443/wx/HL/issues/5185)
|
||||||
|
- **PR**: [#5191](https://git.1814.love:8443/wx/HL/pulls/5191)
|
||||||
|
- **Merge commit**: [03ab19b46](https://git.1814.love:8443/wx/HL/commit/03ab19b463be4b00f92848cb348ce6158918e041)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5186"
|
||||||
|
title: "排车中订单恢复派车派人入口并补齐改派上下文"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "pending"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-23T16:10:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:排车中订单恢复“派车派人”入口
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service
|
||||||
|
> **Issue**: #5186
|
||||||
|
> **日期**: 2026-07-23
|
||||||
|
> **影响范围**: 管理后台车务派单看板及订单派车流程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
排车状态为 `holding` 或 `holding_urgent` 时,订单并非不可操作:后端现在返回 `canAssign=true`,并在 `availableActionCodes` 中下发 `CHANGE_ASSIGNMENT`,允许车务继续进入“派车派人”流程调整司机或车辆。
|
||||||
|
|
||||||
|
2026-07-23 契约修订:改派候选查询需要用 `orderId + requirementId + fleetItemIndex` 精确定位当前订单的当前用车需求槽位。看板列表、详情顶层和详情内每个有效派车组现均稳定返回字符串形式的 `requirementId`,前端直接透传,不自行推导。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 变更类型 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 响应字段取值扩展、新增字段 |
|
||||||
|
| 派单看板详情 | GET | `/admin/fleet/board/orders/:orderId` | 新增字段 |
|
||||||
|
|
||||||
|
请求参数和写接口路径均未改变。
|
||||||
|
|
||||||
|
## 二、响应契约
|
||||||
|
|
||||||
|
`records[]` 中以下字段按服务端返回值处理:
|
||||||
|
|
||||||
|
| `assignmentStatus` | `canAssign` | `availableActionCodes` | 前端行为 |
|
||||||
|
|---|---:|---|---|
|
||||||
|
| `unassigned` | `true` | 包含 `ASSIGN` | 展示“派车派人”,提交既有创建派单接口 |
|
||||||
|
| `unassigned_urgent` | `true` | 包含 `ASSIGN` | 展示“派车派人”,提交既有创建派单接口 |
|
||||||
|
| `holding` | `true` | 包含 `CHANGE_ASSIGNMENT` | 展示“改派”,提交既有 `changeAssignment` 改派接口 |
|
||||||
|
| `holding_urgent` | `true` | 包含 `CHANGE_ASSIGNMENT` | 展示“改派”,提交既有 `changeAssignment` 改派接口 |
|
||||||
|
| `assigned` / `completed` / `canceled` | `false` | 不包含上述可派动作 | 不展示入口 |
|
||||||
|
|
||||||
|
前端不要再用 `assignmentStatus === 'unassigned'` 自行推断入口,也不要因为订单已有司机或车辆就隐藏按钮。入口以 `canAssign === true` 为第一判断,具体提交模式以 `availableActionCodes` 为准。
|
||||||
|
|
||||||
|
排车中进入流程属于调整当前有效派单,不是新增第二条有效派单;继续复用现有改派请求、基线差异提示、司机车辆档期冲突提示和刷新逻辑。
|
||||||
|
|
||||||
|
### 改派候选上下文
|
||||||
|
|
||||||
|
| 响应位置 | 新增字段 | 类型 | 用途 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 列表 `data.records[]` | `requirementId` | `string` | 当前卡片所属用车需求 ID |
|
||||||
|
| 详情 `data` | `requirementId` | `string` | 当前有效用车需求 ID |
|
||||||
|
| 详情 `data.currentAssignment` | `requirementId` | `string` | 当前派车组所属用车需求 ID |
|
||||||
|
| 详情 `data.activeAssignments[]` | `requirementId` | `string` | 每个有效派车组所属用车需求 ID |
|
||||||
|
|
||||||
|
进入改派候选查询时:
|
||||||
|
|
||||||
|
- `orderId` 取列表返回的数字订单 ID;
|
||||||
|
- `requirementId` 优先取详情顶层同名字段,按具体派车组操作时可取该组的同名字段;
|
||||||
|
- `fleetItemIndex` 取当前卡片或当前派车组字段;
|
||||||
|
- 三者必须原样透传给候选接口,不得使用团号、订单号或数组位置替代;
|
||||||
|
- `orderId`、`requirementId` 均按字符串处理,避免 JavaScript 大整数精度丢失。
|
||||||
|
|
||||||
|
当前 `v2.1` 候选请求组装已经读取 `order.requirementId`;后端部署后,从列表进入并合并详情时会获得该字段,无需前端猜测需求 ID。
|
||||||
|
|
||||||
|
### 排车中入口与向导状态
|
||||||
|
|
||||||
|
测试环境现状仍有一处前端状态错位:看板卡片已经显示“排车中”,点击“派车派人”后虽然按 `CHANGE_ASSIGNMENT` 进入 `reassign` 模式,但 `resolveAssignFlowRestoreState(order, mode)` 对所有非 `confirmHold` 模式固定返回 `step: 1`,导致向导错误高亮“订单详情”,底部也显示“下一步 · 排车”。
|
||||||
|
|
||||||
|
前端需要统一按后端状态和动作码恢复入口语义:
|
||||||
|
|
||||||
|
- `assignmentStatus=holding/holding_urgent` 且动作码包含 `CHANGE_ASSIGNMENT` 时,卡片按钮文案显示“改派”,不要继续显示“派车派人”;
|
||||||
|
- 从该入口打开时保持 `mode=reassign`,向导直接进入第 2 步“排车”,第 1 步“订单详情”显示已完成;
|
||||||
|
- 第 2 步带出当前司机、车辆,允许只更换其中一项;提交继续调用既有 `changeAssignment`,不得新增第二条有效派单;
|
||||||
|
- “司机已确认/查看待确认”入口仍使用 `confirmHold` 并恢复第 3 或第 4 步,不能被本次改派逻辑影响;
|
||||||
|
- 首次待派车订单仍从第 1 步开始,按钮仍为“派车派人”。
|
||||||
|
|
||||||
|
以上仅是前端状态机和展示文案调整,后端不新增接口或字段。
|
||||||
|
|
||||||
|
### `holding` 的用户可见状态文案
|
||||||
|
|
||||||
|
`holding` 是后端技术状态码,表示车辆和司机已经锁定、派单通知已经发出,当前正在等待司机回复。面向车务人员时不能继续显示“排车中”,应统一显示为“待确认”:
|
||||||
|
|
||||||
|
- 看板卡片主状态:`holding` 显示“待确认”,`holding_urgent` 显示“待确认即将超时”;
|
||||||
|
- 状态筛选、数量汇总和图例使用同一套“待确认”文案;
|
||||||
|
- 卡片上的司机回执徽标可显示“待回复”,用于补充说明,不能与主状态“排车中”形成两个不同口径;
|
||||||
|
- 技术值仍保持 `holding/holding_urgent`,接口请求参数、状态判断、颜色和改派动作码均不改变;
|
||||||
|
- 首次尚未锁定车辆和司机的 `unassigned` 继续显示“待派车”,确认完成后的 `assigned` 继续显示“已派车”。
|
||||||
|
|
||||||
|
当前 `v2.1` 的 `ORDER_STATUS_META`、`FILTER_STATUS_OPTIONS` 以及后端 `assignmentStatusLabel` 仍含“排车中”旧文案。管理后台应以本节用户口径覆盖展示;如直接消费后端 label,前端需按状态码归一,避免同页出现“排车中”和“待确认”两套名称。
|
||||||
|
|
||||||
|
### 待确认订单的“继续派车”入口
|
||||||
|
|
||||||
|
待确认订单必须同时保留“继续派车”和“改派”两个入口:
|
||||||
|
|
||||||
|
- `availableActionCodes` 包含 `RECORD_DRIVER_CONFIRMATION` 时显示主按钮“继续派车”,点击复用现有 `onConfirmHold(order)`,以 `mode=confirmHold` 打开派单弹窗;
|
||||||
|
- `confirmHold` 根据后端 `stageCode/driverConfirmedAt` 恢复流程:等待司机回复时进入第 3 步“待确认”,已登记司机确认时进入第 4 步“确认执行”;
|
||||||
|
- `availableActionCodes` 包含 `CHANGE_ASSIGNMENT` 时另行显示“改派”,点击进入第 2 步重新选择车辆或司机;
|
||||||
|
- 两个按钮不得互相替代:“继续派车”推进当前有效派单,“改派”修改当前有效派单;
|
||||||
|
- “复制行程单链接”是独立只读能力,复制后端为当前有效派单签发的司机 H5 链接,不参与派单状态流转。
|
||||||
|
|
||||||
|
当前 `v2.1@6e6a11bf` 的 `resolveBoardRowActions()` 仍检查已经废弃的 `CONFIRM` 动作码,而后端生命周期实际下发 `RECORD_DRIVER_CONFIRMATION`,因此截图中“继续派车/司机已确认”按钮没有渲染。前端改为消费真实动作码即可,无需后端增加兼容别名。
|
||||||
|
|
||||||
|
### 看板复制司机 H5 行程单链接
|
||||||
|
|
||||||
|
看板不再维护独立的“发行程单”抽屉,也不在前端模拟发送成功。这里不是打开订单详情的“打印行程单”,而是把后端已经为当前有效派单签发的 H5 链接复制到剪贴板,车务再通过微信等渠道发给司机。司机可在手机浏览器中独立打开,不需要登录管理后台。
|
||||||
|
|
||||||
|
- 将看板按钮文案由“发行程单”改为“复制行程单链接”,事件名同步改为 `copy-itinerary-link`,避免继续表达成“发送”;
|
||||||
|
- 点击时先调用既有看板详情接口 `GET /admin/fleet/board/orders/{orderId}`,不要调用订单打印接口;
|
||||||
|
- 默认复制 `currentAssignment.itineraryUrl`;如果入口明确针对某一条有效派单,则复制对应 `activeAssignments[].itineraryUrl`;
|
||||||
|
- 链接必须直接使用后端返回值,前端不得自行拼接 H5 地址、token、订单 ID 或派单 ID;
|
||||||
|
- 优先使用 `navigator.clipboard.writeText(itineraryUrl)`;当前管理后台可能运行在 HTTP 内网环境,必须同时提供临时 `textarea + document.execCommand('copy')` 降级实现;
|
||||||
|
- 复制成功提示“行程单链接已复制,可发送给司机”;
|
||||||
|
- `itineraryUrl` 为空时不得复制或提示成功,应提示“行程单链接暂不可用,请确认已派车且 H5 配置正常”;
|
||||||
|
- 删除/停用看板自己的 `ItinerarySendSheet.vue`、消息模板和本地 `message.success('已发送行程单')` 假流程;
|
||||||
|
- 不复用 `PrintItineraryModal.vue`,不调用 `GET /v3/admin/order/{orderId}/print-itinerary`;订单详情的“打印行程单”继续作为面向车务的独立打印能力保留;
|
||||||
|
- 链接包含签名 token,前端日志、埋点和错误提示不得记录或展示完整 URL;
|
||||||
|
- 改派成功后必须重新拉取详情并使用新派单的 `itineraryUrl`,不能继续缓存或复制旧派单链接。
|
||||||
|
|
||||||
|
后端链接已绑定当前订单与具体派单,有效期至行程结束后 7 天;公开 H5 接口会校验签名、有效期和派单归属,并实时读取行程数据。复制动作本身不改变派单状态,也不产生“已发送”记录。
|
||||||
|
|
||||||
|
建议前端按以下逻辑落地(函数名可按现有工程调整):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function onCopyItineraryLink(order: BoardOrder) {
|
||||||
|
const orderId = resolveBoardOrderId(order)
|
||||||
|
const detail = await getBoardOrderDetail(orderId)
|
||||||
|
const itineraryUrl = detail.currentAssignment?.itineraryUrl?.trim()
|
||||||
|
|
||||||
|
if (!itineraryUrl) {
|
||||||
|
message.error('行程单链接暂不可用,请确认已派车且 H5 配置正常')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
await navigator.clipboard.writeText(itineraryUrl)
|
||||||
|
} else {
|
||||||
|
copyTextByTextarea(itineraryUrl)
|
||||||
|
}
|
||||||
|
message.success('行程单链接已复制,可发送给司机')
|
||||||
|
} catch {
|
||||||
|
message.error('复制失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyTextByTextarea(text: string) {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = text
|
||||||
|
textarea.setAttribute('readonly', '')
|
||||||
|
textarea.style.position = 'fixed'
|
||||||
|
textarea.style.opacity = '0'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
const copied = document.execCommand('copy')
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
if (!copied) throw new Error('copy failed')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、不影响范围
|
||||||
|
|
||||||
|
- `canRejectRequirement` 仍只在未派阶段可能为 `true`;排车中不得重新开放“驳回用车需求”。
|
||||||
|
- 已派车、已完成、已取消状态不会因本次变更开放派车入口。
|
||||||
|
- 无数据库、Redis、MQ、候选请求字段或错误码变更。
|
||||||
|
|
||||||
|
## 四、前端自测清单
|
||||||
|
|
||||||
|
- [ ] `holding` 订单显示“改派”按钮,点击后带出当前司机、车辆并进入调整流程。
|
||||||
|
- [ ] `holding_urgent` 同样显示“改派”并进入调整流程。
|
||||||
|
- [ ] `holding/holding_urgent + CHANGE_ASSIGNMENT` 卡片按钮显示“改派”,打开后直接高亮第 2 步“排车”,第 1 步为已完成。
|
||||||
|
- [ ] 首次待派车仍从第 1 步开始;`confirmHold` 仍恢复第 3/4 步,三种入口互不串态。
|
||||||
|
- [ ] `holding/holding_urgent` 在卡片、筛选、汇总和图例统一显示“待确认/待确认即将超时”,页面不再出现“排车中”旧文案。
|
||||||
|
- [ ] 技术状态值仍为 `holding`,改派、司机确认和超时判断不因文案变化而改变。
|
||||||
|
- [ ] 待确认订单在 `RECORD_DRIVER_CONFIRMATION` 可用时显示“继续派车”,点击以 `confirmHold` 恢复第 3/4 步。
|
||||||
|
- [ ] “继续派车”和“改派”同时存在且职责分离;前端不再检查不存在的 `CONFIRM` 动作码。
|
||||||
|
- [ ] 待确认或已派车且存在当前有效派单时,看板显示“复制行程单链接”。
|
||||||
|
- [ ] 点击后通过看板详情取得并复制 `currentAssignment.itineraryUrl`,不调用订单打印接口、不在前端拼接链接。
|
||||||
|
- [ ] HTTPS/localhost 使用 Clipboard API,HTTP 内网环境可通过降级方案正常复制。
|
||||||
|
- [ ] 复制成功提示“行程单链接已复制,可发送给司机”;链接为空或复制失败时给出明确错误且不误报成功。
|
||||||
|
- [ ] 复制出的链接可由司机在手机浏览器中独立打开,无需登录管理后台,并展示当前派单的实时行程。
|
||||||
|
- [ ] 看板不再使用 `ItinerarySendSheet` 或模拟“已发送行程单”,改派后不会复制旧派单链接。
|
||||||
|
- [ ] 打开排车步骤时,候选请求同时携带字符串 `orderId`、`requirementId` 和当前 `fleetItemIndex`,不再出现“改派候选查询必须携带当前订单ID、用车需求ID和车型项索引”。
|
||||||
|
- [ ] 提交时调用既有 `changeAssignment`,不调用创建派单接口。
|
||||||
|
- [ ] `assigned`、`completed`、`canceled` 不误显示入口。
|
||||||
|
- [ ] 调整成功后刷新看板,页面只保留一组当前有效派单。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端提交:`aaeb01242`;PR:[wx/HL#5190](https://git.1814.love:8443/wx/HL/pulls/5190),已合并 `dev-v3`。
|
||||||
|
- 状态矩阵、生命周期动作、改派上下文及排车中改派保护已有定向测试覆盖;`BoardOrderServiceTest` 与 `BoardControllerTest` 共 49 项通过。
|
||||||
|
- `mvn -f hl-fleet-service/pom.xml spotless:check` 通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过。
|
||||||
|
- 测试环境 Fleet 滚动部署任务 `3458b3ab` 成功,8087、8187 两实例健康。
|
||||||
|
- 网关按团号 `26-7042` 验证:列表与详情 HTTP/code 200,`requirementId` 在列表、详情顶层、`currentAssignment` 和 `activeAssignments[]` 均存在;携带 `orderId + requirementId + fleetItemIndex + excludeAssignmentId` 调用候选接口 HTTP/code 200,返回 19 辆车、19 名司机候选。
|
||||||
|
- 当前前端 `v2.1` 已从 `order.requirementId` 组装候选请求;但截至 `v2.1@6e6a11bf`,`resolveAssignFlowRestoreState` 对 `reassign` 仍固定恢复第 1 步,且排车中入口文案仍为“派车派人”,需要按上方状态规则调整。
|
||||||
|
|
||||||
|
## 六、相关文档
|
||||||
|
|
||||||
|
- [wx/HL#5186](https://git.1814.love:8443/wx/HL/issues/5186)
|
||||||
|
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5187"
|
||||||
|
title: "多车辆槽位原子批量派车与价格日历带价"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "新增接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@6479adf1caf2a5caeea08a24a42bacecbaaabd6a"
|
||||||
|
target_release: "hl-ui/v2.1"
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "前端 v2.1 已实现按 fleetItemIndex 的多槽位选择、批量提交和重复车辆/司机禁选;测试环境 9527 已提供对应源码,尚待登录态页面实操验收。"
|
||||||
|
updated_at: "2026-07-24"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-23T15:38:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【新增接口·前端待处理·管理后台】多车辆槽位原子批量派车与价格日历带价
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 页面:车务管理 → 派车看板 → 派车派人弹窗、派单详情
|
||||||
|
- 前端交接:仅以本 `hl-api-changelog` 文档为准,不另建前端仓库工单。
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **后端工单**: [wx/HL#5187](https://git.1814.love:8443/wx/HL/issues/5187)
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5189](https://git.1814.love:8443/wx/HL/pulls/5189)
|
||||||
|
>
|
||||||
|
> **兼容性**: 既有单槽位 `POST /admin/fleet/assignments` 不变;多车订单必须改用本次批量接口,
|
||||||
|
> 前端不得循环调用单派接口。
|
||||||
|
|
||||||
|
## 一、业务口径
|
||||||
|
|
||||||
|
一条用车需求可能展开出多个车辆槽位。派车弹窗应按 `fleetItemIndex` 维护多组
|
||||||
|
“车辆 + 司机 + 协议价”,允许一次选择多辆车并一次提交。整批任一槽位失败时不得留下前面
|
||||||
|
已成功、后面失败的半批派单。
|
||||||
|
|
||||||
|
- 同一批内 `fleetItemIndex`、`vehicleId`、`driverId` 分别不可重复。
|
||||||
|
- 前端只允许选择当前需求实际展开出的待派槽位,不得自行增加超过需求数量的车辆。
|
||||||
|
- 雪花 ID 全程按字符串保存和提交。
|
||||||
|
- 同一次提交及其网络重试必须复用同一个 `requestId`;用户修改选择后主动再次提交应生成新值。
|
||||||
|
- `holdMode=1` 表示排车中等待司机确认,`holdMode=0` 表示直接派定;整批模式必须一致。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/batch
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2046800000000000001",
|
||||||
|
"orderNo": "26-4165",
|
||||||
|
"requirementId": "2046800000000000101",
|
||||||
|
"startDate": "2026-07-28",
|
||||||
|
"endDate": "2026-07-30",
|
||||||
|
"pickupAt": "海拉尔",
|
||||||
|
"dropoffAt": "满洲里",
|
||||||
|
"headcount": 8,
|
||||||
|
"chargeableServiceDates": [
|
||||||
|
"2026-07-28",
|
||||||
|
"2026-07-29",
|
||||||
|
"2026-07-30"
|
||||||
|
],
|
||||||
|
"holdMode": 1,
|
||||||
|
"skipCityJunctionException": false,
|
||||||
|
"fromEntry": "from-board",
|
||||||
|
"requestId": "fleet-batch-7fe5c3a8",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"vehicleId": "2046800000000000201",
|
||||||
|
"driverId": "2046800000000000301",
|
||||||
|
"protocolPrice": "520.00",
|
||||||
|
"confirmCrossResident": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 1,
|
||||||
|
"vehicleId": "2046800000000000202",
|
||||||
|
"driverId": "2046800000000000302",
|
||||||
|
"protocolPrice": "860.00",
|
||||||
|
"confirmCrossResident": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 公共字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | ---: | --- |
|
||||||
|
| `orderId` | String(Long) | 是 | 订单 ID |
|
||||||
|
| `orderNo` | String | 否 | 订单号冗余 |
|
||||||
|
| `requirementId` | String(Long) | 是 | 当前生效用车需求 ID |
|
||||||
|
| `startDate` / `endDate` | LocalDate | 是 | 整批服务日期闭区间 |
|
||||||
|
| `pickupAt` / `dropoffAt` | String | 否 | 接送地 |
|
||||||
|
| `headcount` | Integer | 否 | 乘客人数 |
|
||||||
|
| `chargeableServiceDates` | LocalDate[] | 否 | 不传=全部计费;空数组=全部免费 |
|
||||||
|
| `vehicleFeeWaiverReason` | String | 条件必填 | 存在免费服务日时填写 |
|
||||||
|
| `confirmAllServiceDatesFree` | Boolean | 条件必填 | 全部免费时必须为 `true` |
|
||||||
|
| `holdMode` | Integer | 是 | `1=排车中`,`0=直接派定` |
|
||||||
|
| `skipCityJunctionException` | Boolean | 否 | 与单派接口同义 |
|
||||||
|
| `fromEntry` | String | 否 | 操作来源 |
|
||||||
|
| `requestId` | String | 是 | 批次幂等键,最大 64 字符 |
|
||||||
|
| `items` | Object[] | 是 | 1-20 个车辆槽位 |
|
||||||
|
|
||||||
|
### `items[]`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | ---: | --- |
|
||||||
|
| `fleetItemIndex` | Integer | 是 | 当前需求展开后的槽位序号,0 起 |
|
||||||
|
| `vehicleId` | String(Long) | 是 | 所选车辆 ID,批内不可重复 |
|
||||||
|
| `driverId` | String(Long) | 是 | 所选司机 ID,批内不可重复 |
|
||||||
|
| `protocolPrice` | String(BigDecimal) | 否 | 元/车天;不传时后端按车型价格日历兜底 |
|
||||||
|
| `messageTemplateId` | String(Long) | 否 | `holdMode=1` 的通知模板 |
|
||||||
|
| `customBody` | String | 否 | `holdMode=1` 的本次自定义通知正文 |
|
||||||
|
| `confirmCrossResident` | Boolean | 否 | 跨常驻车辆组合的显式确认 |
|
||||||
|
|
||||||
|
成功响应按 `fleetItemIndex` 升序返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"assignment": {
|
||||||
|
"id": "2046800000000000401",
|
||||||
|
"assignmentGroupId": "2046800000000000501",
|
||||||
|
"assignmentSlotId": "2046800000000000601",
|
||||||
|
"assignmentStatus": "holding",
|
||||||
|
"stageCode": "holding_wait_driver",
|
||||||
|
"stageLabel": "排车中·等待司机确认",
|
||||||
|
"currentStep": 2,
|
||||||
|
"skippedStepCodes": [],
|
||||||
|
"protocolPrice": "520.00",
|
||||||
|
"holdSentAt": null,
|
||||||
|
"confirmedAt": null,
|
||||||
|
"sideEffects": null,
|
||||||
|
"dailyDifferences": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"failedFleetItemIndex": null,
|
||||||
|
"dailyDifferences": null
|
||||||
|
},
|
||||||
|
"message": "成功",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
直接派定发生订单/行程/需求冻结基线不一致时返回既有业务码 `605041`,并额外指出失败槽位:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"data": {
|
||||||
|
"assignments": [],
|
||||||
|
"failedFleetItemIndex": 1,
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-29",
|
||||||
|
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||||
|
"message": "逐日车辆可用座位不足"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
无论返回哪一种失败,整批均不产生部分成功数据。前端失败后保留用户当前选择并展示后端文案;
|
||||||
|
`605041` 可同时高亮 `failedFleetItemIndex` 对应槽位及逐日差异。
|
||||||
|
|
||||||
|
## 三、派车弹窗前端修改
|
||||||
|
|
||||||
|
### 3.1 多车辆选择
|
||||||
|
|
||||||
|
当前实现只有全局单值 `selVehicle/selDriver`,再次选择会覆盖上一辆车。需改成按
|
||||||
|
`fleetItemIndex` 保存的槽位数组或 Map:
|
||||||
|
|
||||||
|
```text
|
||||||
|
selectedSlots[fleetItemIndex] = {
|
||||||
|
vehicle,
|
||||||
|
driver,
|
||||||
|
protocolPrice,
|
||||||
|
confirmCrossResident
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 点击某个候选车辆只修改当前待选槽位,不清空其他已选槽位。
|
||||||
|
- 已选摘要、取消车辆、司机选择和常驻组合提示均必须作用于对应槽位。
|
||||||
|
- 提交前校验所有本次待派槽位都有车辆和司机,然后一次调用批量接口。
|
||||||
|
- 禁止用 `for` 循环调用旧单派接口;那会在中途失败时留下半批状态。
|
||||||
|
- 成功后一次关闭弹窗并刷新看板;不得每成功一辆刷新一次。
|
||||||
|
|
||||||
|
#### 当前消费差距
|
||||||
|
|
||||||
|
- `useVehicleDriverPicker.js` 仍只维护一组 `selVehicle/selDriver`。
|
||||||
|
- `AssignModalFooter.vue` 仍只展示一组车辆和司机,并按这一组决定按钮是否可用。
|
||||||
|
- `useAssignFlow.js` 仍只调用 `createAssignment`,没有构造 `items[]`。
|
||||||
|
- `src/api/fleet/board.js` 尚未封装 `POST /fleet/assignments/batch`。
|
||||||
|
|
||||||
|
#### 展示矩阵
|
||||||
|
|
||||||
|
| 场景 | “已选车辆”区域 | 候选/司机联动 | 主操作 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 尚未选择 | 显示 `已选车辆 0/N` 和 N 个待选槽位 | 提示先选择车辆 | 禁用,显示未完成组数 |
|
||||||
|
| 已选一辆 | 槽位 01 显示车牌、车型、司机和移除操作,并成为当前编辑槽位 | 已选车辆标记不可重复;司机只写入当前槽位 | 未完成全部槽位时保持禁用 |
|
||||||
|
| 继续多选 | 新车辆进入下一个待选 `fleetItemIndex`;其他已选槽位保持不变 | 已被其他槽位使用的车辆和司机不可重复选择 | 全部槽位完整后启用 |
|
||||||
|
| 切换槽位 | 高亮当前编辑槽位;允许单独更换车辆、司机和价格 | 候选与司机面板切换到该槽位上下文 | 完整度实时更新 |
|
||||||
|
| 搜索/筛选/翻页 | 已选区域固定可见,集合不丢失 | 只改变候选列表 | 状态保持 |
|
||||||
|
| HOLD 完整 | 显示 `已选择 N/N 辆,司机 N/N` | 每槽位独立司机 | `下一步 · 发送给 N 名司机` |
|
||||||
|
| DIRECT 完整 | 显示 `已选择 N/N 辆,司机 N/N` | 每槽位独立司机 | `直接派定 N 辆车` |
|
||||||
|
| 批量失败 | 保留全部选择;高亮 `failedFleetItemIndex` | 允许修正失败槽位 | 原批次不产生部分成功 |
|
||||||
|
| 批量成功 | 清空选择并关闭弹窗 | 看板只统一刷新一次 | 仅发送一次批量请求 |
|
||||||
|
|
||||||
|
“已选车辆”应作为车辆筛选与候选列表之间持续可见的紧凑区域,不得只在底栏显示最后一辆。
|
||||||
|
选择数量不得超过当前需求的待派车辆槽位数;移除某一槽位不得重排或清空其他槽位。
|
||||||
|
|
||||||
|
### 3.2 车型价格日历自动带价
|
||||||
|
|
||||||
|
候选接口 `vehicles[].protocolPrice` 已返回所选车辆车型在服务开始日的价格日历单价。当前页面
|
||||||
|
只从订单级 `props.order.protocolPrice` 初始化输入框,导致价格日历明明有值仍显示空。
|
||||||
|
|
||||||
|
- 选中车辆时,把该车辆的 `protocolPrice` 写入对应槽位价格框。
|
||||||
|
- 每辆车独立显示、独立可编辑,提交到 `items[].protocolPrice`。
|
||||||
|
- 切换车辆时改为新车辆的价格日历值;不能沿用上一辆车的价格。
|
||||||
|
- 候选值为空时输入框可留空,后端仍会在最终保存时按所选车辆车型 + `startDate` 再兜底一次。
|
||||||
|
- 不得把一个全局价格复制给所有不同车型。
|
||||||
|
|
||||||
|
### 3.3 联系定制师
|
||||||
|
|
||||||
|
派车看板卡片已有“联系定制师”,派单详情第 1 步和后续派车弹窗也应与房务详情保持一致:
|
||||||
|
|
||||||
|
- 在详情可见区域补“联系定制师”按钮,复用现有 `open-fleet` 会话流程。
|
||||||
|
- 订单 ID 使用数字雪花字符串,不能传 `HL...` 展示号或团号。
|
||||||
|
- 按钮位置、图标、禁用态、加载态和聊天抽屉交互复用房务模块,不另做一套样式。
|
||||||
|
- 首次打开真实会话和实时未读角标仍按
|
||||||
|
[#5180 前端交接](./23_5180_订单详情联系车务独立未读红点-修改接口-管理后台.md)处理。
|
||||||
|
|
||||||
|
## 四、派车看板默认状态筛选
|
||||||
|
|
||||||
|
这是前端初始化逻辑修复,不需要后端接口变更:
|
||||||
|
|
||||||
|
- `statusSel` 初始值必须为 `[]`,页面首次进入状态框显示空/不限。
|
||||||
|
- 首次列表请求不得携带 `statuses=unassigned`,默认展示全部状态。
|
||||||
|
- 点击“重置”后的值和首次进入完全一致。
|
||||||
|
- 用户主动选择“待派车”后才传对应状态;刷新筛选结果时不得偷偷恢复默认待派车。
|
||||||
|
|
||||||
|
## 五、前端验收清单
|
||||||
|
|
||||||
|
- [ ] 一条需求展开 2 个车辆槽位时,可同时选择 2 辆不同车辆和 2 名不同司机,第一辆不会被第二辆覆盖。
|
||||||
|
- [ ] 提交只发送 1 次 `/admin/fleet/assignments/batch`,不循环调用旧单派接口。
|
||||||
|
- [ ] 第二槽位失败时页面提示失败,刷新后两个槽位都没有半批残留。
|
||||||
|
- [ ] 价格日历有值时,选择每辆车后各自价格框立即带出对应 `protocolPrice`。
|
||||||
|
- [ ] 修改某辆车价格只影响该槽位,成功响应按槽位回显冻结价格。
|
||||||
|
- [ ] 派单详情第 1 步和派车流程均能直接“联系定制师”,交互与房务一致。
|
||||||
|
- [ ] 派车看板首次进入状态筛选为空,首次请求不传 `statuses`,默认可见全部状态。
|
||||||
|
- [ ] 主动筛选“待派车”及重置行为正确。
|
||||||
|
- [ ] 增加多槽位状态管理、批量请求映射、车型切换带价和默认空筛选的组件/组合式函数测试。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- `mvn -pl hl-fleet-service spotless:check` 通过。
|
||||||
|
- `AssignmentControllerTest + AssignmentServiceTest`:281 项通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过:fleet 2334 项,0 failure / 0 error,1 skipped。
|
||||||
|
- 批量成功、空明细校验、重复槽位校验、字符串雪花 ID、直接派定基线差异及事务/幂等注解均有测试覆盖。
|
||||||
|
- 测试环境部署任务 `28f9048a` 成功,`hl-fleet-service` 两个滚动实例均恢复健康。
|
||||||
|
- 经测试环境网关验证:
|
||||||
|
- 派车看板列表请求返回 HTTP 200 / 业务码 200。
|
||||||
|
- 批量接口空明细返回业务码 400,文案为“派单车辆槽位不能为空”。
|
||||||
|
- 批量接口重复 `fleetItemIndex` 返回业务码 100001,且未产生写入。
|
||||||
|
- 自建并标记测试订单,使用 SUV + MPV 两个槽位执行失败探针:第一槽位合法、第二槽位车辆不存在,
|
||||||
|
接口返回 605001;随后详情仍为 0 个有效派单,证明第一槽位及副作用意图随整批回滚。
|
||||||
|
- 同一测试订单使用两个合法槽位执行成功探针:接口返回 200,结果按
|
||||||
|
`fleetItemIndex=[0,1]` 排序,价格快照分别为 `700.00`、`860.00`,详情恰有 2 个
|
||||||
|
`assigned` 派单。
|
||||||
|
- 使用相同 `requestId` 重放返回业务码 100502;重放后详情仍恰有 2 个有效派单,无重复写入。
|
||||||
|
- 验收后已通过订单取消 API 精确清理自建测试订单,订单状态为 `CANCELLED`,详情有效派单恢复为 0。
|
||||||
|
- 部署后 fleet 服务与网关日志未发现 ERROR;重复槽位探针只产生预期的业务校验 WARN。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5193"
|
||||||
|
title: "用车需求增加独立接机送机选择"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "not_required"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "#5236 明确 supersedes #5193;85851ad6 已删除独立开关并改用实时大交通聚合,继续实现会回滚后续契约。"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-23T17:39:06+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】用车需求增加独立接机送机选择
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3、hl-fleet-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5193](https://git.1814.love:8443/wx/HL/issues/5193)
|
||||||
|
>
|
||||||
|
> **影响范围**: 提交/调整用车需求、订单详情用车摘要、车务派单详情
|
||||||
|
|
||||||
|
## 业务口径
|
||||||
|
|
||||||
|
“是否需要平台接送”拆分为两个相互独立的业务选择:
|
||||||
|
|
||||||
|
- `pickupRequired`:是否需要平台接机/接站;
|
||||||
|
- `dropoffRequired`:是否需要平台送机/送站。
|
||||||
|
|
||||||
|
提交用车需求时两个选项默认都选中,即默认都为 `true`。定制师可以分别取消,支持四种组合。接机与送机选择是用车需求本身的明确口径,不从航班、站点、接送时间或大交通批次推断。
|
||||||
|
|
||||||
|
## 一、提交/调整用车需求
|
||||||
|
|
||||||
|
### 1. 直接提交或修改
|
||||||
|
|
||||||
|
`PUT /v3/admin/order/{id}/vehicle-requirement`
|
||||||
|
|
||||||
|
请求体新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fleet": [
|
||||||
|
{
|
||||||
|
"vehicleType": "SUV",
|
||||||
|
"seats": 7,
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pickupRequired": true,
|
||||||
|
"dropoffRequired": true,
|
||||||
|
"specialTags": ["中文司机"],
|
||||||
|
"remark": "司机会蒙语"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 调整订单
|
||||||
|
|
||||||
|
`POST /v3/admin/order/{id}/adjustment/submit`
|
||||||
|
|
||||||
|
`updates.vehicleRequirement` 新增相同字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"updates": {
|
||||||
|
"vehicleRequirement": {
|
||||||
|
"fleet": [
|
||||||
|
{
|
||||||
|
"vehicleType": "SUV",
|
||||||
|
"seats": 7,
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pickupRequired": false,
|
||||||
|
"dropoffRequired": true,
|
||||||
|
"specialTags": [],
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pickupRequired` | `Boolean` | 前端应显式提交 | `true` 需要接机/接站,`false` 不需要 |
|
||||||
|
| `dropoffRequired` | `Boolean` | 前端应显式提交 | `true` 需要送机/送站,`false` 不需要 |
|
||||||
|
|
||||||
|
兼容规则:
|
||||||
|
|
||||||
|
- 首次提交缺少字段时,后端按 `true` 保存;
|
||||||
|
- 修改或调整既有需求时缺少字段,后端继承当前有效需求的原值;
|
||||||
|
- 前端不要依赖兼容兜底,提交时始终显式发送两个字段。
|
||||||
|
|
||||||
|
## 二、前端表单
|
||||||
|
|
||||||
|
在“车辆安排/提交用车需求”表单中增加两个独立开关或复选框:
|
||||||
|
|
||||||
|
- 是否需要接机/接站;
|
||||||
|
- 是否需要送机/送站。
|
||||||
|
|
||||||
|
交互要求:
|
||||||
|
|
||||||
|
- 新建用车需求时两个选项默认选中;
|
||||||
|
- 编辑或调整时以接口回显值为准,不要每次强制重置为选中;
|
||||||
|
- 两个选项均可独立取消,不做互斥或联动;
|
||||||
|
- 不根据有没有大交通、航班时间或站点决定勾选状态。
|
||||||
|
|
||||||
|
## 三、回显接口
|
||||||
|
|
||||||
|
以下响应均新增 `pickupRequired` 和 `dropoffRequired`,用于无损回显:
|
||||||
|
|
||||||
|
- 用车需求提交/修改响应;
|
||||||
|
- `GET /v3/admin/order/{id}/adjustment/snapshot` 的 `data.vehicleRequirement`;
|
||||||
|
- `GET /v3/admin/order/{id}/itinerary` 的 `data.vehicleGroup.requirement`;
|
||||||
|
- order-v3 内部用车需求契约。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pickupRequired": false,
|
||||||
|
"dropoffRequired": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、车务派单详情
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders/{orderId}` 的 `data.transport` 新增/明确返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pickupRequired": false,
|
||||||
|
"dropoffRequired": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这两个值来自订单当前有效用车需求,是车务详情页展示的权威值。前端不得再用抵达/返程班次、站点、接送时间或批次记录推断。
|
||||||
|
|
||||||
|
页面分别显示:
|
||||||
|
|
||||||
|
| 字段值 | 接机标签 | 送机标签 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `true` | 需要平台接机 | 需要平台送机 |
|
||||||
|
| `false` | 无需平台接机 | 无需平台送机 |
|
||||||
|
|
||||||
|
不要再合并显示单个“需要平台接送”标签。滚动部署期间若字段暂为 `null`,可以暂不显示对应标签;部署完成后现有历史需求会按默认值返回 `true`。
|
||||||
|
|
||||||
|
## 五、前端处理清单
|
||||||
|
|
||||||
|
- [ ] 提交用车需求表单增加“是否需要接机/接站”和“是否需要送机/送站”。
|
||||||
|
- [ ] 新建时两个选项默认选中,提交时显式发送两个 Boolean。
|
||||||
|
- [ ] 调整订单预填读取 `vehicleRequirement.pickupRequired/dropoffRequired`,不覆盖既有值。
|
||||||
|
- [ ] 订单详情用车需求摘要按两个字段分别展示。
|
||||||
|
- [ ] 车务派单详情按 `transport.pickupRequired/dropoffRequired` 分别展示接机、送机标签。
|
||||||
|
- [ ] 不再展示单个“需要平台接送”,不根据大交通信息反推。
|
||||||
|
- [ ] 覆盖需要/不需要的四种组合及旧需求默认双 `true`。
|
||||||
|
|
||||||
|
## 六、不影响范围
|
||||||
|
|
||||||
|
- 大交通录入中的批次级 `pickupRequired` 继续表示该批次自身是否接送,本次不修改其录入和计算逻辑。
|
||||||
|
- 接送班次、站点、时间和备注字段结构不变。
|
||||||
|
- 车型、座位数、数量、特殊诉求和备注提交结构不变。
|
||||||
|
|
||||||
|
## 七、验证证据
|
||||||
|
|
||||||
|
- 后端提交:`2112396b1`;PR:[wx/HL#5197](https://git.1814.love:8443/wx/HL/pulls/5197),已合并 `dev-v3`(合并提交 `f9e05fbe4`)。
|
||||||
|
- 用车需求默认值、显式选择、版本继承、调整透传、订单详情回显及 order-v3 → fleet 契约均有单元测试覆盖。
|
||||||
|
- order-v3 定向测试 311 项、fleet board 定向测试 49 项通过。
|
||||||
|
- `mvn -pl hl-order-service-v3 -am verify`、`mvn -pl hl-fleet-service -am verify` 和 fleet `spotless:check` 全部通过。
|
||||||
|
- 测试环境滚动部署成功:order-v3 任务 `af6ff925`,8086/8186 两实例健康;fleet 任务 `e11bca29`,8087/8187 两实例健康。
|
||||||
|
- 以 `admin` 车务经网关查询团号 `26-8550` 的派车详情,HTTP/code 200,`transport.pickupRequired=true`、`transport.dropoffRequired=true`。
|
||||||
|
- 以 `wx` 定制师经网关查询同订单调整预填,HTTP/code 200,`vehicleRequirement.pickupRequired=true`、`vehicleRequirement.dropoffRequired=true`。
|
||||||
|
- 测试库只读核验:两列均为 `NOT NULL DEFAULT 1`,该历史活动需求已迁移为 `1/1`。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`;前端按“前端处理清单”接入即可。
|
||||||
@ -0,0 +1,230 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5194"
|
||||||
|
title: "待确认详情补全多车多司机与按槽位改派"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "pending"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-23T18:02:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】待确认详情补全多车多司机与按槽位改派
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5194](https://git.1814.love:8443/wx/HL/issues/5194)
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5196](https://git.1814.love:8443/wx/HL/pulls/5196)
|
||||||
|
>
|
||||||
|
> **影响范围**: 派车看板状态、派单弹窗改派、司机待确认页
|
||||||
|
|
||||||
|
## 关键业务口径
|
||||||
|
|
||||||
|
1. `holding` 落库状态不变,但等待司机真实回复时,页面展示文案统一为“待确认”,不得再显示“排车中”。
|
||||||
|
2. 一张订单可以同时存在多个有效车辆槽位,每个槽位有独立车辆、司机和确认阶段。前端必须遍历 `activeAssignments`,不能只读兼容字段 `currentAssignment`。
|
||||||
|
3. 改派以选中的稳定槽位为单位。两辆车中只改一辆时,只提交目标槽位对应的 `activeAssignments[i].id`;其他槽位不取消、不重建、不改变。
|
||||||
|
4. 改派界面必须先展示历史车辆/司机,并要求车务人员在界面中明确清除目标槽位的旧选择后才能选择新车/新司机。
|
||||||
|
5. “清除旧选择”只修改前端草稿状态,**不得先调用取消派单接口**。最终一次调用 `change` 原子替换;用户关闭弹窗时后端原派单保持不变。
|
||||||
|
6. 多车待确认页按槽位分别登记司机回复。只要任一有效 HOLD 槽位未收到司机回复,订单顶部整体步骤仍停在“待确认”。
|
||||||
|
|
||||||
|
## 一、派单详情
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders/{orderId}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `activeAssignments[]` 完整字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `id` | `String` | 当前有效派车组锚点 ID;改派、登记司机确认、最终确认均使用该值 |
|
||||||
|
| `assignmentGroupId` | `String` | 当前派车组 ID;改派后会生成新值 |
|
||||||
|
| `assignmentSlotId` | `String` | 稳定车辆槽位 ID;同一槽位改派前后保持不变 |
|
||||||
|
| `fleetItemIndex` | `Integer` | 用车需求项序号 |
|
||||||
|
| `requiredVehicleType` | `String` | 需求车型 |
|
||||||
|
| `requiredSeats` | `Integer` | 需求座位数 |
|
||||||
|
| `vehicleId` | `String` | 当前车辆 ID |
|
||||||
|
| `vehiclePlate` | `String` | 当前车牌 |
|
||||||
|
| `vehicleModel` | `String` | 当前车型;优先派车冻结快照,缺失时回填车辆档案 |
|
||||||
|
| `vehicleSeats` | `Integer` | 当前车辆座位数 |
|
||||||
|
| `vehicleFleetTeamId` | `String` | 当前车辆所属车队 ID |
|
||||||
|
| `vehicleFleetTeamName` | `String` | 当前车辆所属车队名称 |
|
||||||
|
| `driverId` | `String` | 当前司机 ID |
|
||||||
|
| `driverName` | `String` | 当前司机姓名 |
|
||||||
|
| `driverPhone` | `String` | 当前司机脱敏手机号,例如 `138****1234` |
|
||||||
|
| `assignmentStatus` | `String` | 派生态 |
|
||||||
|
| `assignmentStatusLabel` | `String` | 后端统一展示文案 |
|
||||||
|
| `lifecycleStageCode` | `String` | 当前槽位生命周期阶段 |
|
||||||
|
| `lifecycleStageLabel` | `String` | 当前槽位阶段文案 |
|
||||||
|
| `currentStep` | `Integer` | 当前槽位所在步骤 |
|
||||||
|
| `availableActionCodes` | `String[]` | 当前槽位允许操作 |
|
||||||
|
| `driverConfirmedAt` | `LocalDateTime/null` | 本槽位司机确认时间 |
|
||||||
|
|
||||||
|
示例(字段已脱敏):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"currentAssignment": {
|
||||||
|
"id": "2079502431745396738",
|
||||||
|
"assignmentSlotId": "2079502431745396738"
|
||||||
|
},
|
||||||
|
"activeAssignments": [
|
||||||
|
{
|
||||||
|
"id": "2079502431745396738",
|
||||||
|
"assignmentGroupId": "2079502431745396738",
|
||||||
|
"assignmentSlotId": "2079502431745396738",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"vehicleId": "2079857985374363650",
|
||||||
|
"vehiclePlate": "蒙A-T1557",
|
||||||
|
"vehicleModel": "丰田普拉多",
|
||||||
|
"vehicleSeats": 7,
|
||||||
|
"vehicleFleetTeamId": "2079857981112934401",
|
||||||
|
"vehicleFleetTeamName": "合作车队A",
|
||||||
|
"driverId": "2079857983403024385",
|
||||||
|
"driverName": "司机姓名",
|
||||||
|
"driverPhone": "199****1557",
|
||||||
|
"baseAssignmentStatus": "holding",
|
||||||
|
"assignmentStatus": "holding",
|
||||||
|
"assignmentStatusLabel": "待确认",
|
||||||
|
"lifecycleStageCode": "holding_wait_driver",
|
||||||
|
"lifecycleStageLabel": "待确认",
|
||||||
|
"currentStep": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 兼容与聚合规则
|
||||||
|
|
||||||
|
- `currentAssignment` 仍返回“最新有效派车组”,仅用于兼容旧版单车页面;新页面不得据此判断订单只有一辆车。
|
||||||
|
- `activeAssignments` 只含有效 `holding/assigned` 派车组,按需求项、服务日期、派车组 ID 稳定排序。
|
||||||
|
- `progressSteps` 是订单整体步骤,多车时按最慢有效槽位聚合。
|
||||||
|
- 每辆车的实际阶段以对应 `activeAssignments[i].lifecycleStageCode/currentStep` 为准。
|
||||||
|
- 老异常数据若车辆档案或司机电话确实缺失,对应字段可能为 `null`;页面显示 `-`,不得导致整页报错。
|
||||||
|
|
||||||
|
## 二、按目标槽位改派
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/{assignmentId}/change
|
||||||
|
```
|
||||||
|
|
||||||
|
`assignmentId` 必须使用车务人员选中的 `activeAssignments[i].id`,不要使用订单 ID,也不要默认使用 `currentAssignment.id`。
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"effectiveDate": "2026-07-29",
|
||||||
|
"newVehicleId": "2079857985374363999",
|
||||||
|
"newDriverId": "2079857983403024999",
|
||||||
|
"holdMode": 1,
|
||||||
|
"messageTemplateId": "2073978002412105729",
|
||||||
|
"protocolPrice": 700.00,
|
||||||
|
"reason": "替换第 2 个车辆槽位",
|
||||||
|
"requestId": "change-slot-20260723-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
原子替换成功响应会明确返回稳定槽位和其他未改车辆:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"assignmentId": "新的有效派单锚点ID",
|
||||||
|
"assignmentSlotId": "改派前后不变的槽位ID",
|
||||||
|
"previousAssignmentGroupId": "被替换的旧派车组ID",
|
||||||
|
"newAssignmentGroupId": "新派车组ID",
|
||||||
|
"assignmentStatus": "holding",
|
||||||
|
"effectiveDate": "2026-07-29",
|
||||||
|
"affectedDays": 3,
|
||||||
|
"otherVehicleCount": 1,
|
||||||
|
"warningCode": "ORDER_HAS_OTHER_VEHICLES",
|
||||||
|
"otherVehicles": [
|
||||||
|
{
|
||||||
|
"assignmentSlotId": "未改车辆槽位ID",
|
||||||
|
"vehiclePlate": "蒙A-U1557",
|
||||||
|
"driverName": "另一位司机",
|
||||||
|
"startDate": "2026-07-29",
|
||||||
|
"endDate": "2026-07-31"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 改派页面正确流程
|
||||||
|
|
||||||
|
1. 打开改派时用 `activeAssignments` 渲染全部现有槽位卡片,显示车牌、车型、座位、司机姓名、脱敏电话。
|
||||||
|
2. 用户先选择要替换的槽位;两车订单不得自动选“最新一辆”代替用户决定。
|
||||||
|
3. 目标槽位显示“清除当前车辆/司机”。用户明确点击后,只清空本地候选草稿并解锁新车/新司机选择。
|
||||||
|
4. 未清除目标槽位前禁用候选选择和提交;其他槽位仍只读展示,不跟随清空。
|
||||||
|
5. 提交时只调用一次目标 `id` 的 `change`。禁止先 `DELETE /assignments/{id}`,也禁止先调用 `driver-reject`。
|
||||||
|
6. 成功后重新请求订单详情,用新的 `activeAssignments` 替换页面状态;不要在前端自行拼接新旧派车组。
|
||||||
|
7. `warningCode=ORDER_HAS_OTHER_VEHICLES` 是“还有其他车辆保持不变”的强提示,不是失败,不得继续批量改派其他槽位。
|
||||||
|
|
||||||
|
## 三、多司机待确认页
|
||||||
|
|
||||||
|
司机待确认页必须遍历 `activeAssignments`,每个槽位至少显示:
|
||||||
|
|
||||||
|
- 车型、车牌、座位数;
|
||||||
|
- 司机姓名、脱敏手机号;
|
||||||
|
- 当前阶段文案;
|
||||||
|
- 本槽位的通知模板预览、司机回复摘要、确认凭证和操作按钮。
|
||||||
|
|
||||||
|
每名司机独立调用:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/{activeAssignments[i].id}/driver-confirmation
|
||||||
|
POST /admin/fleet/assignments/{activeAssignments[i].id}/confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
不得因为其中一名司机已确认,就把其他仍待回复的司机一起标记为已确认。
|
||||||
|
|
||||||
|
## 四、模板渲染的 `canceled` 处理
|
||||||
|
|
||||||
|
测试网关已验证真实 `hold_notify` 模板列表和 `render` 接口均为 200,订单 26-7042 的模板正文可正常渲染。页面出现原样英文 `canceled` 是前端取消旧请求被当成业务错误展示,不是后端模板错误。
|
||||||
|
|
||||||
|
前端必须:
|
||||||
|
|
||||||
|
1. 模板、车辆或司机快速切换时允许取消旧 render 请求。
|
||||||
|
2. 对 Axios `CanceledError`、`ERR_CANCELED` 或项目统一的取消请求判定静默处理,不弹错误条、不清空最后一次成功预览。
|
||||||
|
3. 只采纳最新一次请求的响应;较早请求即使后返回也不得覆盖新预览。
|
||||||
|
4. 真正的 HTTP/业务错误才显示“模板加载失败”,并保留“重试加载”。
|
||||||
|
5. 禁止把异常对象的 `message`(例如 `canceled`)直接展示给用户。
|
||||||
|
|
||||||
|
## 五、前端处理清单
|
||||||
|
|
||||||
|
- [ ] 看板卡片对 `holding_wait_driver` 展示“待确认”,不再硬编码“排车中”。
|
||||||
|
- [ ] 派单详情、改派和待确认页全部遍历 `activeAssignments`,不再只读 `currentAssignment`。
|
||||||
|
- [ ] 每个槽位显示车型、车牌、座位、司机姓名和脱敏手机号。
|
||||||
|
- [ ] 多车顶部步骤使用后端 `progressSteps`,每车状态使用本项生命周期字段。
|
||||||
|
- [ ] 改派前展示全部现有槽位,由车务人员明确选择目标槽位。
|
||||||
|
- [ ] 目标槽位必须先在界面中手动清除旧选择,才允许重新选择车辆/司机。
|
||||||
|
- [ ] 清除操作仅修改前端草稿,不调用取消或退回接口;最终只调用一次目标槽位的 `change`。
|
||||||
|
- [ ] 两车只换一车时,另一槽位保持原样;成功后重新拉取详情。
|
||||||
|
- [ ] 每名司机分别登记确认和最终确认,不能用一个槽位状态覆盖全部司机。
|
||||||
|
- [ ] render 请求取消时静默处理,不再显示原样英文 `canceled`。
|
||||||
|
|
||||||
|
## 六、验证证据
|
||||||
|
|
||||||
|
- 后端:fleet 及依赖模块全量 `verify` 成功,2,336 个测试 0 失败、1 个既有跳过;`spotless:check` 通过。
|
||||||
|
- 按槽位改派测试:验证替换目标槽位时不会调用其他槽位的取消语句。
|
||||||
|
- 多车测试:验证完整返回两个稳定槽位及车辆/司机字段;任一司机未回复时整体仍停在待确认。
|
||||||
|
- 部署:测试环境部署任务 `6679d69c` 成功,8087/8187 双实例滚动发布并通过健康检查。
|
||||||
|
- 网关:订单 26-7042 返回 `assignmentStatus=holding`、`assignmentStatusLabel=待确认`、`lifecycleStageCode=holding_wait_driver`、`lifecycleStageLabel=待确认`。
|
||||||
|
- 网关:订单 26-7042 的 `activeAssignments` 已返回稳定槽位、车型、座位、司机和脱敏手机号;真实模板 render 正文长度 240,未出现后端 `canceled` 错误。
|
||||||
|
- 当前测试环境看板前 99 个唯一订单没有多有效派车样本,因此多车网关展示不能靠现存业务数据复验,后端多车契约由自动化测试覆盖。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5199"
|
||||||
|
title: "车务首页订单去重与字段补全"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "pending"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T09:46:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】车务首页订单去重与字段补全
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service、hl-user-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5199](https://git.1814.love:8443/wx/HL/issues/5199)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务角色首页的待安排车辆统计、即将用车订单表格和操作列
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
`GET /admin/profile/dashboard?period=today` 在当前角色为 `VEHICLE_MANAGER` 时,`data.upcomingTrips` 的口径调整为:
|
||||||
|
|
||||||
|
- 只返回近 7 天仍存在未完成派车槽位的订单,未完成态为 `unassigned/unassigned_urgent/holding/holding_urgent`;
|
||||||
|
- 同一订单的多个车型、车辆或派车槽位按 `orderId` 合并为一行;
|
||||||
|
- 返回符合条件的完整订单集合,不再固定截断为 5 条;
|
||||||
|
- 全部派车槽位均已进入 `assigned` 的订单不再出现在待处理列表。
|
||||||
|
|
||||||
|
派车看板 `/admin/fleet/board/orders` 仍保持派车槽位维度和原分页规则,本次不修改。
|
||||||
|
|
||||||
|
## 响应字段
|
||||||
|
|
||||||
|
`data.upcomingTrips[]` 完整结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "70123456789",
|
||||||
|
"assignmentId": "80123456789",
|
||||||
|
"orderNo": "HL202607010001",
|
||||||
|
"teamNo": "26-7218",
|
||||||
|
"productName": "呼伦贝尔 6 日",
|
||||||
|
"customerName": "张先生",
|
||||||
|
"contactName": "张先生",
|
||||||
|
"plannerName": "苏日娜",
|
||||||
|
"consultantName": "苏日娜",
|
||||||
|
"consultantDisplayName": "苏日娜",
|
||||||
|
"departureDate": "2026-07-10",
|
||||||
|
"endDate": "2026-07-15",
|
||||||
|
"headcount": 4,
|
||||||
|
"status": "unassigned_urgent",
|
||||||
|
"statusLabel": "待派车",
|
||||||
|
"urgentBadge": "T-1",
|
||||||
|
"canAssign": true,
|
||||||
|
"canRejectRequirement": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
本次补齐的字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 页面用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `teamNo` | `String/null` | 团号列 |
|
||||||
|
| `contactName` | `String/null` | 联系人列,当前与 `customerName` 同值 |
|
||||||
|
| `plannerName` | `String/null` | 定制师兼容字段 |
|
||||||
|
| `consultantName` | `String/null` | 定制师兼容字段 |
|
||||||
|
| `consultantDisplayName` | `String/null` | 定制师优先展示字段 |
|
||||||
|
| `canAssign` | `Boolean` | 是否显示派车/调整入口 |
|
||||||
|
| `canRejectRequirement` | `Boolean` | 是否显示驳回需求入口 |
|
||||||
|
|
||||||
|
`orderId` 和 `assignmentId` 继续按字符串处理,不得转为 JavaScript `Number`。同订单存在多个未完成派车槽位时,`assignmentId/status/statusLabel/urgentBadge/操作权限` 来自后端排序最靠前的代表槽位。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 表格行以 `orderId` 为订单级唯一键,不按 `assignmentId` 重复渲染。
|
||||||
|
- [ ] 不在前端截取前 5 条,也不再自行做派车槽位去重。
|
||||||
|
- [ ] 团号列读取 `teamNo`。
|
||||||
|
- [ ] 定制师列优先读取 `consultantDisplayName`,为空时可回退 `consultantName/plannerName`。
|
||||||
|
- [ ] 操作列按 `canAssign/canRejectRequirement` 显示已有操作入口,不根据中文状态文案推断。
|
||||||
|
- [ ] 覆盖同订单多车型、多车、超过 5 个未完成订单、部分槽位已派和全部槽位已派场景。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 `pendingArrangeVehicle` 字段名。
|
||||||
|
- 不修改派车看板、矩阵派单、车辆列表或司机列表的分页和展示维度。
|
||||||
|
- 不新增前端路由、权限码或请求参数。
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- 后端 PR:[wx/HL#5201](https://git.1814.love:8443/wx/HL/pulls/5201)。
|
||||||
|
- `FleetDashboardSummaryServiceTest` 覆盖 8 条派车槽位聚合为 6 个未完成订单、重复订单去重和 `assigned` 过滤。
|
||||||
|
- `LogisticsDashboardServiceTest` 覆盖团号、联系人、定制师与操作权限字段透传及 Feign 降级空态。
|
||||||
|
- `hl-fleet-service` 测试环境滚动部署任务 `0944b306` 成功,8087/8187 双实例健康。
|
||||||
|
- `hl-user-service` 测试环境滚动部署任务 `e306a054` 成功,8081/8181 双实例健康。
|
||||||
|
- PR 合并后以 `dev-v3` 再次滚动部署,fleet 任务 `d914f3fb`、user 任务 `3fde7f12` 均成功,
|
||||||
|
部署仓库 HEAD 包含合并提交 `bab22a096d`。
|
||||||
|
- 经网关请求 `GET /admin/profile/dashboard?period=today` 返回 HTTP 200、业务码 200;`upcomingTrips`
|
||||||
|
与相同日期和状态条件下的派单看板订单集合一致,共 4 个唯一订单,重复订单数 0,无已完成订单混入。
|
||||||
|
- `teamNo/contactName/plannerName/consultantName/consultantDisplayName/canAssign/canRejectRequirement`
|
||||||
|
在 4 条记录中均存在且均有有效值;user-service 与 gateway 近期日志未发现本次调用异常。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5200"
|
||||||
|
title: "用车需求驳回历史与重新提交"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "pending"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T11:05:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】用车需求驳回历史与重新提交
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3、hl-fleet-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5200](https://git.1814.love:8443/wx/HL/issues/5200)
|
||||||
|
>
|
||||||
|
> **影响范围**: 订单详情行程安排中的用车需求、驳回后的重新提交
|
||||||
|
|
||||||
|
## 业务口径
|
||||||
|
|
||||||
|
- 用车需求被车务驳回后,旧版本终态失活并作为只读历史保留,不是“已回配”。
|
||||||
|
- 驳回后没有当前 active 用车需求,订单详情允许定制师重新提交。
|
||||||
|
- 重新提交创建新的 active 版本并重新进入车务流程,不复活或覆盖旧版本。
|
||||||
|
- 新旧版本可以同屏展示:当前版本保留原有操作,历史版本只读。
|
||||||
|
|
||||||
|
## 一、订单详情行程安排
|
||||||
|
|
||||||
|
接口:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/{orderId}/itinerary
|
||||||
|
```
|
||||||
|
|
||||||
|
响应 `data` 新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vehicleGroup": null,
|
||||||
|
"vehicleHistory": [
|
||||||
|
{
|
||||||
|
"requirementId": "2080186927616606210",
|
||||||
|
"version": 1,
|
||||||
|
"status": "REJECTED_TO_CONSULTANT",
|
||||||
|
"isActive": false,
|
||||||
|
"submittedAt": "2026-07-23 15:03:06",
|
||||||
|
"returnedAt": "2026-07-24 09:20:00",
|
||||||
|
"returnRemark": "当地无合适车辆",
|
||||||
|
"vehicleTypeSummary": "suv×1",
|
||||||
|
"specialTags": ["儿童安全座椅", "大行李空间"],
|
||||||
|
"pickupRequired": true,
|
||||||
|
"dropoffRequired": true,
|
||||||
|
"remark": "原需求备注"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"canContactFleet": false,
|
||||||
|
"contactFleetDisabledReason": "请先提交有效用车需求后再联系车务"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `vehicleHistory` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `requirementId` | `String` | 历史需求 ID |
|
||||||
|
| `version` | `Integer` | 版本号,列表按版本倒序 |
|
||||||
|
| `status` | `String` | 驳回场景为 `REJECTED_TO_CONSULTANT` 或 `REJECTED_TO_ADMIN` |
|
||||||
|
| `isActive` | `Boolean` | 历史项固定为 `false` |
|
||||||
|
| `submittedAt` | `LocalDateTime` | 原需求提交时间 |
|
||||||
|
| `returnedAt` | `LocalDateTime/null` | 驳回时间 |
|
||||||
|
| `returnRemark` | `String/null` | 驳回原因 |
|
||||||
|
| 其余摘要字段 | 与 `vehicleGroup.requirement` 相同 | 车型、座位、接送、特殊诉求和备注等原需求快照 |
|
||||||
|
|
||||||
|
边界行为:
|
||||||
|
|
||||||
|
- 从未提交用车需求:`vehicleGroup=null`、`vehicleHistory=[]`。
|
||||||
|
- 已驳回且尚未重提:`vehicleGroup=null`、`vehicleHistory` 包含驳回历史。
|
||||||
|
- 已重新提交:`vehicleGroup.requirement` 是新 active 版本,`vehicleHistory` 仍包含旧版本。
|
||||||
|
- 历史列表还可能包含被正常重版替换的失活版本,前端按 `status` 决定是否展示“已驳回”。
|
||||||
|
|
||||||
|
## 二、重新提交
|
||||||
|
|
||||||
|
继续使用现有接口:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/{orderId}/vehicle-requirement
|
||||||
|
```
|
||||||
|
|
||||||
|
驳回后提交的返回语义:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"version": 2,
|
||||||
|
"isActive": true,
|
||||||
|
"status": "PENDING",
|
||||||
|
"branchTaken": "INIT_SUBMIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
版本规则:
|
||||||
|
|
||||||
|
- 新版本号 = 历史最高版本号 + 1;
|
||||||
|
- 新版本为当前 active 需求;
|
||||||
|
- 旧驳回版本继续留在 `vehicleHistory`;
|
||||||
|
- 仅新版本进入车务看板和派单流程。
|
||||||
|
|
||||||
|
## 三、前端处理清单
|
||||||
|
|
||||||
|
- [ ] 订单详情用车卡片固定支持“历史需求”只读区域,不论当前 active 需求是否存在。
|
||||||
|
- [ ] 历史项展示版本、原需求内容、驳回状态、`returnedAt` 和 `returnRemark`。
|
||||||
|
- [ ] 历史项不得显示修改、联系车务、派车或“已回配”等当前需求操作/文案。
|
||||||
|
- [ ] `vehicleGroup=null` 且 `vehicleHistory` 非空时,继续显示“提交用车需求”入口。
|
||||||
|
- [ ] 有新 `vehicleGroup.requirement` 时,同时展示当前需求和旧历史,历史项不覆盖当前状态。
|
||||||
|
- [ ] 不要把 `vehicleHistory` 项映射成当前 `vehicleGroup.requirement`。
|
||||||
|
- [ ] 覆盖驳回未重提、驳回后重提、存在多个历史版本三个场景。
|
||||||
|
|
||||||
|
## 四、兼容说明
|
||||||
|
|
||||||
|
现有前端在 `vehicleGroup=null` 时已能进入“提交用车需求”空态,因此后端部署后不会再把驳回需求误显示成“已回配”。新增历史区域需要前端按上方清单接入;未接入时只是暂不展示历史内容,不影响重新提交。
|
||||||
|
|
||||||
|
## 五、后端验证
|
||||||
|
|
||||||
|
- 驳回 CAS 原子更新 `status`、`is_active=false`、驳回原因/时间并清空接单人。
|
||||||
|
- Fleet Outbox 按“订单需求驳回成功 → 取消未派占位”的顺序执行;重放已失活驳回历史时幂等成功。
|
||||||
|
- 无 active 需求时按历史最高版本递增,兼容旧 active rejected 数据。
|
||||||
|
- 订单/Fleet 相关定向测试累计 658 项通过。
|
||||||
|
- `mvn -pl hl-order-service-v3 -am verify`、`mvn -pl hl-fleet-service -am verify` 和 fleet `spotless:check` 全部通过。
|
||||||
|
- 后端 PR [wx/HL#5206](https://git.1814.love:8443/wx/HL/pulls/5206) 已合并到
|
||||||
|
`dev-v3@c13a035d0`;测试环境滚动部署任务 `a2a5a9fa` 成功,8086/8186 两实例健康。
|
||||||
|
- 网关以 `wx` 验证订单 `26-9919`:驳回迁移后 `vehicleGroup=null`,
|
||||||
|
`vehicleHistory` 返回 v1、`REJECTED_TO_CONSULTANT`、`isActive=false` 及原驳回原因;
|
||||||
|
重新提交返回 v2、`PENDING`,再次查询同时保留 v1 历史和 v2 当前需求。
|
||||||
|
- 测试库核对:v1 已失活且保留,v2 为唯一 active;Fleet 为 v2 生成 6 条
|
||||||
|
`unassigned` 日切片,旧 v1 的 6 条派单保持 `canceled`。
|
||||||
|
- 异常团号 `26-4165` 的 3 条旧派单均已取消并软删除,0 条可见、0 条在途;
|
||||||
|
对应失败 Outbox 已进入 `QUARANTINED`,网关看板按团号搜索返回 0 条。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@b619849eb3c71f2e466dec4539f577213c060db0"
|
||||||
|
updated_at: "2026-07-25T03:25:59.312Z"
|
||||||
|
---
|
||||||
|
# 调整订单行程节点时间回显与修改(修改接口)
|
||||||
|
|
||||||
|
> 日期:2026-07-24
|
||||||
|
> 工单:#5202
|
||||||
|
> 服务:`hl-order-service-v3`
|
||||||
|
> 前端状态:待处理(`D:/work2/hl-ui` 本次未修改)
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
产品行程节点的两类时间在下单时均已固化到订单行程节点:
|
||||||
|
|
||||||
|
- `startTime`:精确开始时间,格式 `HH:mm`。
|
||||||
|
- `timePeriod`:时间说明,值来自 `itinerary_time_period` 字典,例如 `MORNING`。
|
||||||
|
|
||||||
|
本次补齐“调整订单 → 行程”中的完整读写契约:
|
||||||
|
|
||||||
|
- 快照逐节点返回 `startTime`、`timePeriod`。
|
||||||
|
- 调整提交可只修改时间,不要求同时改价或改数量。
|
||||||
|
- 新增节点也可携带两类时间。
|
||||||
|
- 两字段可分别存在,不强制互斥。
|
||||||
|
- 旧订单或未设置时间的节点返回 `null`。
|
||||||
|
|
||||||
|
## 涉及接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 变化 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/{orderId}/adjustment/snapshot` | `data.itinerary.days[].nodes[]` 补齐 `timePeriod`,保留已有 `startTime` |
|
||||||
|
| POST | `/v3/admin/order/{orderId}/adjustment/submit` | `updates.itinerary.days[].nodes[]` 支持提交 `startTime`、`timePeriod` |
|
||||||
|
|
||||||
|
## 快照出参
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"itinerary": {
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"id": "8001",
|
||||||
|
"dayNumber": 2,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "9001",
|
||||||
|
"title": "呼和诺尔草原旅游区",
|
||||||
|
"startTime": "05:05",
|
||||||
|
"timePeriod": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9002",
|
||||||
|
"title": "额尔古纳湿地漂流",
|
||||||
|
"startTime": null,
|
||||||
|
"timePeriod": "EARLY_MORNING"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`timePeriod` 返回字典值,展示文字请使用现有 `itinerary_time_period` 字典翻译,不要在页面硬编码中文。
|
||||||
|
|
||||||
|
## 提交语义
|
||||||
|
|
||||||
|
节点时间沿用 patch 语义:
|
||||||
|
|
||||||
|
| 入参状态 | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| 字段省略或传 `null` | 不修改该字段 |
|
||||||
|
| `startTime: "09:05"` | 设置精确开始时间 |
|
||||||
|
| `timePeriod: "MORNING"` | 设置时间说明 |
|
||||||
|
| `startTime: ""` | 清空精确开始时间,后端落库为 `NULL` |
|
||||||
|
| `timePeriod: ""` | 清空时间说明,后端落库为 `NULL` |
|
||||||
|
|
||||||
|
仅修改时间时的请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"updates": {
|
||||||
|
"itinerary": {
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"id": "8001",
|
||||||
|
"dayNumber": 2,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "9001",
|
||||||
|
"startTime": "06:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9002",
|
||||||
|
"timePeriod": "AFTERNOON"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
清空示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "9001",
|
||||||
|
"startTime": "",
|
||||||
|
"timePeriod": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
非空 `startTime` 必须是 24 小时制 `HH:mm`,例如 `09:05`;`9:05`、`24:00` 均不合法。
|
||||||
|
|
||||||
|
## 管理后台处理清单
|
||||||
|
|
||||||
|
当前 `FunItemAdjustModal.vue` 已从快照读取 `startTime`,但尚未渲染、参与 diff 或写入提交体;`timePeriod` 还未映射。需补:
|
||||||
|
|
||||||
|
- 节点草稿与基线同时保存 `startTime`、`timePeriod`。
|
||||||
|
- 节点行增加精确时间选择器和 `itinerary_time_period` 字典下拉,并回显现有值。
|
||||||
|
- `funItemsChanged` 与节点 diff 同时比较两字段;只有时间变化也要生成节点 patch。
|
||||||
|
- `buildItineraryDays()` 对变化字段按需提交,未变化字段省略。
|
||||||
|
- 用户清空后,提交体将该字段从前端 `null` 转为 `""`;不要直接传 `null`,否则后端按“不修改”处理。
|
||||||
|
- 保留两字段可同时设置的能力,不在前端强制互斥。
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
- 新增出参字段兼容旧调用方。
|
||||||
|
- `startTime` 原字段保持不变。
|
||||||
|
- 未设置时间的历史数据返回 `null`,前端按空态展示。
|
||||||
|
- 本次不改订单金额、节点价格、数量、顺序和资源绑定逻辑。
|
||||||
文件差异内容过多而无法显示
加载差异
@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5205"
|
||||||
|
title: "车务首页汇总与看板人员类型展示"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "pending"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T10:45:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】车务首页汇总与看板人员类型展示
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service、hl-user-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5205](https://git.1814.love:8443/wx/HL/issues/5205)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务首页待安排车辆卡片、即将用车订单状态标签、派单看板人数摘要
|
||||||
|
|
||||||
|
## 1. 首页待安排车辆汇总
|
||||||
|
|
||||||
|
`GET /admin/profile/dashboard?period=today` 的响应结构不变,`data.pendingArrangeVehicle`
|
||||||
|
调整为 `data.upcomingTrips` 中订单级唯一的有效未完成配车订单总数。
|
||||||
|
|
||||||
|
计入口径:
|
||||||
|
|
||||||
|
- `unassigned`、`unassigned_urgent`:待派车;
|
||||||
|
- `holding`、`holding_urgent`:待确认。
|
||||||
|
|
||||||
|
不计入口径:
|
||||||
|
|
||||||
|
- `assigned`:已完成车辆配置;
|
||||||
|
- `canceled`、`completed`:已取消或已完结,不属于有效待处理订单。
|
||||||
|
|
||||||
|
页面不得只统计 `unassigned`,也不得自行按派车槽位累加。当前测试环境验收样例为
|
||||||
|
3 个待派车加 1 个待确认,`pendingArrangeVehicle` 与列表徽标都应显示 4。
|
||||||
|
|
||||||
|
## 2. 首页状态标签颜色
|
||||||
|
|
||||||
|
颜色按稳定状态码映射,不按中文 `statusLabel` 判断:
|
||||||
|
|
||||||
|
| `upcomingTrips[].status` | 标签 | 建议语义色 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `unassigned` / `unassigned_urgent` | 待派车 | warning / 橙色 |
|
||||||
|
| `holding` / `holding_urgent` | 待确认 | processing / 蓝色 |
|
||||||
|
|
||||||
|
紧急程度继续使用 `urgentBadge` 单独表达,不要通过把全部状态渲染成橙色来表示紧急。
|
||||||
|
|
||||||
|
## 3. 派单看板人员类型
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders` 的 `data.records[]` 已返回真实订单人数构成:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"headcount": 5,
|
||||||
|
"adultCount": 2,
|
||||||
|
"childCount": 1,
|
||||||
|
"youngChildCount": 1,
|
||||||
|
"babyCount": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
看板卡片不要只展示 `5人`,应展示人员类型构成,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
成人2 · 儿童1 · 幼童1 · 婴儿1
|
||||||
|
```
|
||||||
|
|
||||||
|
展示规则:
|
||||||
|
|
||||||
|
- 四类字段均为订单真实数据,不得按客户名、标签、总人数或订单 ID 推测;
|
||||||
|
- 数值为 0 的类型可省略;
|
||||||
|
- 四类字段全部为 `null` 时才兼容回退 `headcount + "人"`;
|
||||||
|
- 四类人数合计与 `headcount` 不一致时保留后端原值,并上报数据异常,不在前端静默改数。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 首页待安排车辆卡片直接展示后端 `pendingArrangeVehicle`,不再自行只统计待派车状态。
|
||||||
|
- [ ] 首页列表徽标与 `upcomingTrips.length` 保持一致。
|
||||||
|
- [ ] 待派车使用橙色,待确认使用蓝色;颜色映射使用状态码。
|
||||||
|
- [ ] 派单看板用四类人数构成替换单一总人数文案,零值类型省略。
|
||||||
|
- [ ] 覆盖 3 个待派车 + 1 个待确认、紧急派生态、四类人数混合和全零/空值兼容场景。
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- `FleetDashboardSummaryServiceTest` 覆盖待派车与待确认共同汇总、订单级去重及完成态排除。
|
||||||
|
- 分支 `fix/5205-fleet-dashboard-summary` 已通过测试环境滚动部署任务 `f64ec5ef`,
|
||||||
|
`hl-fleet-service` 的 8087/8187 双实例健康。
|
||||||
|
- 2026-07-24 经测试网关验证:
|
||||||
|
`pendingArrangeVehicle=4`、`upcomingTrips.length=4`、唯一订单数为 4、重复数为 0,
|
||||||
|
状态分布为 `unassigned:3`、`holding:1`。
|
||||||
|
- `GET /admin/fleet/board/orders` 返回 9 条记录,9 条均具有非空的
|
||||||
|
`adultCount/childCount/youngChildCount/babyCount`。
|
||||||
|
- fleet、user 与 gateway 日志未发现本次 dashboard 请求相关异常。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5209"
|
||||||
|
title: "出行人省份与分批大交通关联"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "implemented"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@4424375ef9180e69f22a4f5f6b0b80c9ec2062b7"
|
||||||
|
updated_at: "2026-07-24T07:15:16.554Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T11:58:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】出行人省份与分批大交通关联
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3、hl-fleet-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5209](https://git.1814.love:8443/wx/HL/issues/5209)
|
||||||
|
>
|
||||||
|
> **PR**: [wx/HL#5212](https://git.1814.love:8443/wx/HL/pulls/5212)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务派单详情的出行人省份标签、大交通与出行人关联、分批抵达/离开展示
|
||||||
|
|
||||||
|
## 一、接口变化
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders/{orderId}`
|
||||||
|
|
||||||
|
### 1. 出行人
|
||||||
|
|
||||||
|
`data.travelers[]` 补充省级行政区,并继续返回稳定的大交通计划 ID:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"travelerId": "3001",
|
||||||
|
"nameMasked": "张**",
|
||||||
|
"idNoMasked": "320***********108X",
|
||||||
|
"idProvinceCode": "32",
|
||||||
|
"idProvinceName": "江苏省",
|
||||||
|
"transportPlanIds": ["4001", "4003"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `idProvinceCode` | `String \| null` | 身份证省级行政区代码,例如 `32` |
|
||||||
|
| `idProvinceName` | `String \| null` | 身份证省级行政区名称,例如 `江苏省` |
|
||||||
|
| `transportPlanIds` | `String[]` | 该出行人关联的全部大交通计划 ID |
|
||||||
|
|
||||||
|
省份仅对结构、生日段和省级前缀均可识别的 18 位大陆身份证派生。护照、其他证件、空值或无法识别的身份证返回 `null`;接口不会新增身份证明文。
|
||||||
|
|
||||||
|
### 2. 大交通单段和批次
|
||||||
|
|
||||||
|
`data.transport.arrive`、`data.transport.depart` 及 `data.transport.batches[]` 统一补充:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"planId": "4001",
|
||||||
|
"direction": "ARRIVAL",
|
||||||
|
"travelerIds": ["3001"],
|
||||||
|
"transportNo": "CA1234",
|
||||||
|
"time": "2026-07-29T11:10:00",
|
||||||
|
"station": "满洲里西郊机场",
|
||||||
|
"pickupRequired": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `planId` | `String` | 大交通计划 ID |
|
||||||
|
| `direction` | `String \| null` | `ARRIVAL` 抵达、`DEPARTURE` 离开;极少量历史异常数据可能为 `null` |
|
||||||
|
| `travelerIds` | `String[]` | 本段或本批关联的出行人 ID |
|
||||||
|
| `pickupRequired` | `Boolean \| null` | 本段或本批是否需要平台接送 |
|
||||||
|
|
||||||
|
一起抵达/离开的首段仍放在 `arrive` 或 `depart`。同方向存在更多批次时,后续批次保留在 `batches[]`,不会合并为单个时间或站点。
|
||||||
|
|
||||||
|
## 二、稳定关联算法
|
||||||
|
|
||||||
|
前端必须按 ID 关联,不再按姓名关联:
|
||||||
|
|
||||||
|
1. 将 `travelers[]` 按 `String(travelerId)` 建立索引。
|
||||||
|
2. 将非空的 `transport.arrive`、`transport.depart` 与 `transport.batches[]` 合并为大交通段列表。
|
||||||
|
3. 对每个大交通段遍历 `travelerIds[]`,按字符串 ID 查找对应出行人。
|
||||||
|
4. 需要反向查询时,用出行人的 `transportPlanIds[]` 匹配各段 `planId`。
|
||||||
|
|
||||||
|
所有雪花 ID 都按 JSON 字符串返回。不要转换为 JavaScript `Number`,避免精度丢失。
|
||||||
|
|
||||||
|
`travelerNames` 仅为旧页面兼容展示字段,不是关联键。姓名可能重复、脱敏或变化,不得用于匹配。
|
||||||
|
|
||||||
|
## 三、页面处理
|
||||||
|
|
||||||
|
- 出行人卡片在 `idProvinceName` 非空时显示省份标签;为空时不显示占位标签。
|
||||||
|
- 大交通区域按 `direction` 区分抵达和离开,不根据数组位置猜方向。
|
||||||
|
- 每个批次独立展示时间、站点、班次、接送要求和对应出行人。
|
||||||
|
- 同方向多个批次不得覆盖、去重或压缩为一个批次。
|
||||||
|
- `travelerIds` 为空时显示该交通段,但不要按姓名猜测关联人。
|
||||||
|
- `direction=null` 的历史记录可显示为“方向待完善”,不要默认当作离开。
|
||||||
|
|
||||||
|
## 四、前端处理清单
|
||||||
|
|
||||||
|
- [ ] 出行人卡片读取 `idProvinceName` 并按空值规则显示省份标签。
|
||||||
|
- [ ] 按字符串 `travelerId/planId` 建立双向关联,不转换为 `Number`。
|
||||||
|
- [ ] 同时处理 `transport.arrive`、`transport.depart` 和全部 `transport.batches[]`。
|
||||||
|
- [ ] 按 `direction` 区分抵达/离开,并支持同方向多个批次。
|
||||||
|
- [ ] 每个大交通段展示其 `travelerIds[]` 对应的出行人。
|
||||||
|
- [ ] 不使用 `travelerNames`、脱敏姓名或数组位置作为关联依据。
|
||||||
|
- [ ] 覆盖单批、多批、无关联人、无大交通、省份为空和 `direction=null` 场景。
|
||||||
|
|
||||||
|
## 五、验证证据
|
||||||
|
|
||||||
|
- 后端提交:`45b631cd0`;PR:[wx/HL#5212](https://git.1814.love:8443/wx/HL/pulls/5212)。
|
||||||
|
- `mvn -pl hl-order-service-v3,hl-fleet-service -am verify`、fleet `spotless:check` 和定向测试全部通过。
|
||||||
|
- 测试环境分支部署成功:order task `e1fc0581`、fleet task `d4cea89b`,四个实例健康。
|
||||||
|
- 经网关遍历 11 条看板订单,详情成功 11/11;30 名出行人中 24 名返回可识别省份。
|
||||||
|
- 9 个真实大交通段共验证 28 组双向 ID 关联,所有 ID 均为 JSON 字符串,未出现身份证明文字段。
|
||||||
|
- 临时构造 3 个返程批次验证 `depart + batches[2]` 后已通过业务 API 完整清理,临时批次残留 0。
|
||||||
|
- order、fleet、gateway 两实例自部署起均无目标 ERROR/Exception。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`;前端按“前端处理清单”接入即可。
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@06f9d4dce58ef64c3e5de96e44754c0793286d06"
|
||||||
|
updated_at: "2026-07-24T07:15:17.169Z"
|
||||||
|
---
|
||||||
|
# 车务:派单通知预览补齐接送与行程数据
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service(8087/8187)
|
||||||
|
> **PR**: #5213
|
||||||
|
> **Issue**: #5211
|
||||||
|
> **日期**: 2026-07-24
|
||||||
|
> **影响范围**: 管理后台派车弹窗“排车待确认”通知预览
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
原预览接口只读取订单主表基础字段,导致已有大交通和行程的订单仍把“接团、送团、行程链接、有效期”渲染为空。现在预览与 HOLD 实际发送共用接送口径,并在不创建派单、不写短链记录的前提下补齐行程长链和有效期。
|
||||||
|
|
||||||
|
## 一、变更接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 变更类型 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| 微信通知模板预览 | POST | `/admin/fleet/message-templates/{templateId}/render` | 响应内容修正,结构不变 |
|
||||||
|
|
||||||
|
### 入参
|
||||||
|
|
||||||
|
请求体字段、类型和必填规则均不变:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "订单雪花 ID",
|
||||||
|
"vehicleId": "车辆雪花 ID",
|
||||||
|
"driverId": "司机雪花 ID",
|
||||||
|
"serviceDates": ["2026-07-29", "2026-07-30", "2026-07-31"],
|
||||||
|
"chargeableServiceDates": ["2026-07-29", "2026-07-30", "2026-07-31"],
|
||||||
|
"vehicleFeeWaiverReason": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 出参
|
||||||
|
|
||||||
|
`MessageTemplateRenderRespVO` 结构仍为:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `renderedBody` | String | 已替换变量的完整通知正文 |
|
||||||
|
| `variablesUsed` | String[] | 模板实际引用的变量 key |
|
||||||
|
|
||||||
|
本次修正以下既有变量在 `renderedBody` 中的取值:
|
||||||
|
|
||||||
|
| 变量 | 新口径 |
|
||||||
|
|------|--------|
|
||||||
|
| `order.pickupInfo` | 从订单到达方向大交通格式化;无需平台接送或资料缺失时输出明确文案 |
|
||||||
|
| `order.dropoffInfo` | 从订单返程方向大交通格式化;单方向缺失不再留空 |
|
||||||
|
| `itinerary.url` | 使用 `orderId + 行程结束日` 无副作用现签订单级长链;签发失败时输出明确不可用文案 |
|
||||||
|
| `itinerary.expireAt` | 与行程链接签发口径同源计算;无法计算时输出“待确认” |
|
||||||
|
|
||||||
|
## 二、前端调用约束
|
||||||
|
|
||||||
|
- 前端无需新增请求字段,也无需自行拼接接送或行程文案。
|
||||||
|
- 继续直接展示后端返回的 `renderedBody`。
|
||||||
|
- 预览中的行程链接不会提前创建派单、派单短链或通知记录;真实发送仍由 HOLD 冻结链路生成稳定短链。
|
||||||
|
- 数据不可用时后端返回明确降级文案,前端不要再把这些文案转换为空串。
|
||||||
|
|
||||||
|
## 三、不影响范围
|
||||||
|
|
||||||
|
- 不修改模板、派单和通知接口的 JSON 结构。
|
||||||
|
- 不修改订单、大交通或行程数据。
|
||||||
|
- 不改变 HOLD 通知冻结、Outbox 投递和真实短链幂等规则。
|
||||||
|
- 不涉及 `hl-ui` 代码修改。
|
||||||
|
|
||||||
|
## 四、验证
|
||||||
|
|
||||||
|
- 定向测试:`MessageTemplateRenderServiceTest` + `AssignmentHoldNotificationSnapshotFactoryTest`,22/22 通过。
|
||||||
|
- 全量验证:`mvn -pl hl-fleet-service -am verify`,14 个 reactor 模块通过。
|
||||||
|
- Spotless:598 files clean。
|
||||||
|
- 测试环境网关验收将在 PR 合并并部署后补录到 Issue #5211。
|
||||||
|
|
||||||
|
## 五、相关文档
|
||||||
|
|
||||||
|
- [后端 Issue #5211](https://git.1814.love:8443/wx/HL/issues/5211)
|
||||||
|
- [后端 PR #5213](https://git.1814.love:8443/wx/HL/pulls/5213)
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5215"
|
||||||
|
title: "车务首页未完成状态独立汇总"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "implemented"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@a48846a3e35c06df3aef422e538d5cd198002c2c"
|
||||||
|
updated_at: "2026-07-24T07:15:17.828Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T14:25:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】车务首页未完成状态独立汇总
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service、hl-user-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5215](https://git.1814.love:8443/wx/HL/issues/5215)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务首页待处理汇总卡片
|
||||||
|
|
||||||
|
## 接口变更
|
||||||
|
|
||||||
|
`GET /admin/profile/dashboard?period=today` 在保留 `data.pendingArrangeVehicle`
|
||||||
|
总数的基础上,新增固定顺序的 `data.pendingStatusCards`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"pendingArrangeVehicle": 5,
|
||||||
|
"pendingStatusCards": [
|
||||||
|
{
|
||||||
|
"status": "unassigned",
|
||||||
|
"statusLabel": "待派车",
|
||||||
|
"count": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "holding",
|
||||||
|
"statusLabel": "待确认",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段口径:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `pendingArrangeVehicle` | 近 7 天订单级唯一的全部未完成配车订单数 |
|
||||||
|
| `pendingStatusCards[].status` | 稳定状态键,固定为 `unassigned`、`holding` |
|
||||||
|
| `pendingStatusCards[].statusLabel` | 后端中文标签,分别为“待派车”“待确认” |
|
||||||
|
| `pendingStatusCards[].count` | 对应基础状态的订单级唯一数量 |
|
||||||
|
|
||||||
|
`unassigned_urgent` 归入 `unassigned`,`holding_urgent` 归入 `holding`。
|
||||||
|
`assigned`(配置完成)、`canceled`、`completed` 不进入状态卡。接口始终按
|
||||||
|
`unassigned`、`holding` 顺序返回两项;即使数量为 0 也不省略。
|
||||||
|
|
||||||
|
守恒关系:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sum(pendingStatusCards[].count)
|
||||||
|
== pendingArrangeVehicle
|
||||||
|
== upcomingTrips.length
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端展示
|
||||||
|
|
||||||
|
首页汇总区应展示三张独立卡片:
|
||||||
|
|
||||||
|
| 卡片 | 数据源 | 建议语义色 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 待安排车辆 | `pendingArrangeVehicle` | danger / 红色 |
|
||||||
|
| 待派车 | `pendingStatusCards[status=unassigned].count` | warning / 橙色 |
|
||||||
|
| 待确认 | `pendingStatusCards[status=holding].count` | processing / 蓝色 |
|
||||||
|
|
||||||
|
实现要求:
|
||||||
|
|
||||||
|
- 使用 `pendingStatusCards` 循环渲染状态卡,并以 `status` 作为稳定 key 和颜色映射依据;
|
||||||
|
- 保留现有“待安排车辆”总卡,不要用状态卡替换总数;
|
||||||
|
- 不要从 `upcomingTrips` 或派车槽位在前端重新汇总;
|
||||||
|
- 数量为 0 时仍展示对应状态卡,避免布局和状态口径随数据变化;
|
||||||
|
- 标签优先展示后端 `statusLabel`,颜色不得按中文文案判断。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 首页汇总区展示“待安排车辆、待派车、待确认”三张卡片。
|
||||||
|
- [ ] 待安排车辆使用红色、待派车使用橙色、待确认使用蓝色。
|
||||||
|
- [ ] 状态卡以 `status` 为 key,直接使用后端 `count`,不在前端重新统计。
|
||||||
|
- [ ] 覆盖 `5/4/1`、两个状态均为 0、单个状态为 0 的展示场景。
|
||||||
|
- [ ] 保持现有即将用车订单表格与操作逻辑不变。
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- `FleetDashboardSummaryServiceTest` 覆盖 `unassigned=4`、`holding=1`、
|
||||||
|
紧急派生态归并、订单去重、配置完成排除及零值场景。
|
||||||
|
- `LogisticsDashboardServiceTest` 与 Feign fallback 测试覆盖完整透传、
|
||||||
|
滚动部署兼容和固定两项零值降级。
|
||||||
|
- `mvn -pl hl-user-service,hl-fleet-service -am verify` 已通过。
|
||||||
|
- 分支 `fix/5215-fleet-dashboard-status-cards` 已按 fleet、user 顺序部署,
|
||||||
|
四个服务实例均健康。
|
||||||
|
- 2026-07-24 经测试网关验证:
|
||||||
|
`pendingArrangeVehicle=5`、`unassigned=4`、`holding=1`、
|
||||||
|
`upcomingTrips.length=5`。
|
||||||
|
- fleet、user 与 gateway 部署完成窗口内 ERROR 级别日志均为 0,
|
||||||
|
未发现 dashboard 目标异常。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5216"
|
||||||
|
title: "派车看板补充槽位接送路线与就绪摘要"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@cd493f83a7881401552494fc5a90fbb87395131b"
|
||||||
|
target_release: "hl-ui/v2.1"
|
||||||
|
verified_at: ""
|
||||||
|
path_aliases: "changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-前端待处理-管理后台.md"
|
||||||
|
status_note: "后端与网关已验证;前端 implemented 状态由前端消费线程维护,本次仅迁移 schema。"
|
||||||
|
updated_at: "2026-07-26"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-24T14:24:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·前端待处理·管理后台】派车看板补充槽位接送路线与就绪摘要
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`
|
||||||
|
- 目标分支:`v2.1`
|
||||||
|
- 联调/验收环境:<http://192.168.100.160:9527>
|
||||||
|
- 小程序:无需处理
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3、hl-fleet-service
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5216](https://git.1814.love:8443/wx/HL/issues/5216)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派车看板卡片
|
||||||
|
|
||||||
|
## 业务口径
|
||||||
|
|
||||||
|
派车看板卡片本身应足够车务完成日常派车判断,详情页只用于查看更深信息。每张卡对应一个稳定车辆槽位,
|
||||||
|
同时显示当前需求全部槽位的派车进度、该槽位服务范围、接送批次、路线和资料就绪风险。
|
||||||
|
|
||||||
|
- 看板仍按车辆槽位维度返回,不改为订单维度。
|
||||||
|
- 只统计订单当前有效用车需求,不混入已驳回、已失活或旧版本需求。
|
||||||
|
- 行程或大交通缺失只作风险提示,`readiness.blocksAssignment` 固定为 `false`,不改变 `canAssign`。
|
||||||
|
- 卡片接送摘要不返回出行人、接送备注或大交通自由文本备注。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders
|
||||||
|
```
|
||||||
|
|
||||||
|
请求参数、筛选、排序、分页和 `records[]` 维度不变;每条 `records[]` 新增以下字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"assignmentSlotId": "2080200000000000001",
|
||||||
|
"slotSummary": {
|
||||||
|
"assignmentSlotId": "2080200000000000001",
|
||||||
|
"slotIndex": 2,
|
||||||
|
"totalSlots": 3,
|
||||||
|
"serviceStartDate": "2026-07-29",
|
||||||
|
"serviceEndDate": "2026-07-31",
|
||||||
|
"serviceDays": 3,
|
||||||
|
"requiredVehicleType": "mpv",
|
||||||
|
"requiredVehicleTypeLabel": "商务车",
|
||||||
|
"requiredSeats": 7
|
||||||
|
},
|
||||||
|
"assignmentProgress": {
|
||||||
|
"totalSlots": 3,
|
||||||
|
"unassignedSlots": 1,
|
||||||
|
"holdingSlots": 1,
|
||||||
|
"assignedSlots": 1,
|
||||||
|
"completedSlots": 0,
|
||||||
|
"canceledSlots": 0
|
||||||
|
},
|
||||||
|
"pickupSummary": {
|
||||||
|
"required": true,
|
||||||
|
"statusCode": "PARTIAL",
|
||||||
|
"statusLabel": "接客信息部分缺失",
|
||||||
|
"batchCount": 2,
|
||||||
|
"readyBatchCount": 1,
|
||||||
|
"transportNos": ["MU8345", "K7091"],
|
||||||
|
"earliestTime": "2026-07-29T10:30:00",
|
||||||
|
"latestTime": "2026-07-29T15:20:00",
|
||||||
|
"stations": ["海拉尔机场"]
|
||||||
|
},
|
||||||
|
"dropoffSummary": {
|
||||||
|
"required": true,
|
||||||
|
"statusCode": "READY",
|
||||||
|
"statusLabel": "送客信息已齐",
|
||||||
|
"batchCount": 1,
|
||||||
|
"readyBatchCount": 1,
|
||||||
|
"transportNos": ["CA1234"],
|
||||||
|
"earliestTime": "2026-07-31T18:00:00",
|
||||||
|
"latestTime": "2026-07-31T18:00:00",
|
||||||
|
"stations": ["海拉尔站"]
|
||||||
|
},
|
||||||
|
"routeSummary": "海拉尔区 → 额尔古纳市 → 满洲里市",
|
||||||
|
"daysUntilDeparture": 5,
|
||||||
|
"readiness": {
|
||||||
|
"statusCode": "PARTIAL",
|
||||||
|
"statusLabel": "部分信息待补",
|
||||||
|
"itineraryStatusCode": "READY",
|
||||||
|
"itineraryStatusLabel": "行程已完整",
|
||||||
|
"itineraryDayCount": 3,
|
||||||
|
"itineraryExpectedDayCount": 3,
|
||||||
|
"missingItemCodes": ["PICKUP_TRANSFER"],
|
||||||
|
"missingItemLabels": ["接客信息"],
|
||||||
|
"blocksAssignment": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 当前槽位 `slotSummary`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `assignmentSlotId` | `String` | 稳定车辆槽位 ID,按雪花 ID 字符串处理 |
|
||||||
|
| `slotIndex` | `Integer/null` | 当前槽位序号,**从 1 开始** |
|
||||||
|
| `totalSlots` | `Integer` | 当前有效需求车辆槽位总数 |
|
||||||
|
| `serviceStartDate/serviceEndDate` | `LocalDate/null` | 当前槽位实际服务范围 |
|
||||||
|
| `serviceDays` | `Integer/null` | 服务范围闭区间天数 |
|
||||||
|
| `requiredVehicleType` | `String/null` | 当前槽位车型规范编码 |
|
||||||
|
| `requiredVehicleTypeLabel` | `String` | 当前槽位车型中文标签 |
|
||||||
|
| `requiredSeats` | `Integer/null` | 当前槽位要求座位数 |
|
||||||
|
|
||||||
|
不要用既有整单 `requiredVehicles[]` 的数组位置猜当前卡片车型;当前卡片只读取 `slotSummary`。
|
||||||
|
|
||||||
|
### 整单进度 `assignmentProgress`
|
||||||
|
|
||||||
|
`assignmentProgress` 基于当前有效需求的全部稳定槽位计算,不受本次列表状态、车型、日期或关键词筛选影响。
|
||||||
|
前端可直接展示“3 车:待派 1 / 排车中 1 / 已派 1”,不要用当前页 `records[]` 自行计数。
|
||||||
|
|
||||||
|
### 接送摘要 `pickupSummary/dropoffSummary`
|
||||||
|
|
||||||
|
| `statusCode` | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `READY` | 所有批次时间和站点均完整 |
|
||||||
|
| `PARTIAL` | 至少一个批次完整,但仍有批次缺时间或站点 |
|
||||||
|
| `MISSING` | 当前要求该方向接送,但没有完整批次 |
|
||||||
|
| `NOT_REQUIRED` | 当前用车需求明确不要求该方向接送 |
|
||||||
|
| `SOURCE_UNAVAILABLE` | order-v3 暂不可用,不能把它显示成“无需接送”或“资料已齐” |
|
||||||
|
|
||||||
|
`pickupAt/dropoffAt` 兼容字段继续保留;订单实时上下文可用时,优先回填对应方向第一个有效站点。
|
||||||
|
|
||||||
|
### 行程与就绪度
|
||||||
|
|
||||||
|
- `routeSummary` 按行程天顺序生成,并压缩连续重复地点;无地点时为 `null`。
|
||||||
|
- `daysUntilDeparture` 是服务端当前日期到出团日的自然日数;负数表示已出团。
|
||||||
|
- `readiness.statusCode` 为 `READY/PARTIAL/MISSING/SOURCE_UNAVAILABLE`。
|
||||||
|
- `missingItemCodes` 当前可能包含:
|
||||||
|
- `ORDER_CONTEXT`:订单实时信息不可用;
|
||||||
|
- `ITINERARY_DAYS`:逐日行程缺失、天数不完整或日期仍是旧档期;
|
||||||
|
- `ROUTE_SUMMARY`:行程天没有可用地点;
|
||||||
|
- `PICKUP_TRANSFER`:要求接客但资料不完整;
|
||||||
|
- `DROPOFF_TRANSFER`:要求送客但资料不完整。
|
||||||
|
|
||||||
|
页面使用后端 `statusLabel/missingItemLabels` 展示中文,不自行翻译状态码。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 卡片主信息区展示“第 `slotIndex/totalSlots` 车”、车型标签、座位数和槽位服务日期。
|
||||||
|
- [ ] 展示 `assignmentProgress` 整单进度,不按当前页或筛选后记录重新计算。
|
||||||
|
- [ ] 分别展示接客和送客摘要;多批次显示批次数、班次/车次、时间范围和站点。
|
||||||
|
- [ ] `NOT_REQUIRED` 显示“无需接客/无需送客”,`SOURCE_UNAVAILABLE` 显示“信息暂不可用”。
|
||||||
|
- [ ] 展示 `routeSummary`、`daysUntilDeparture` 和 `readiness` 风险提示。
|
||||||
|
- [ ] 资料缺失时不得禁用派车按钮;操作能力继续只读 `canAssign/availableActionCodes`。
|
||||||
|
- [ ] 不在卡片展示出行人、接送备注、大交通备注等敏感或自由文本信息。
|
||||||
|
- [ ] `assignmentSlotId/assignmentId/assignmentGroupId/requirementId/orderId` 均按字符串处理。
|
||||||
|
- [ ] 覆盖单车、多车、部分已派、多批次接送、无需接送、资料缺失和下游降级场景。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改派车看板请求参数、分页、筛选、排序和操作接口。
|
||||||
|
- 不修改 `canAssign/canRejectRequirement/availableActionCodes` 计算。
|
||||||
|
- 不修改派单详情、矩阵派单、司机车辆占用、保险和费用。
|
||||||
|
- 不迁移数据库,不写入订单、行程、大交通或派单数据。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端提交:`e73740c57caf295cac96d964e540279f581f1fb0`;PR:
|
||||||
|
[wx/HL#5221](https://git.1814.love:8443/wx/HL/pulls/5221)。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过:2354 tests,0 failures/errors,skipped 1;
|
||||||
|
Spotless 603 files clean。
|
||||||
|
- `OrderFleetProviderServiceTest` 33 项、`BoardOrderServiceTest` 48 项及
|
||||||
|
`AssignmentServiceTest` 稳定槽位聚合测试均通过。
|
||||||
|
- `mvn -pl hl-order-service-v3 -am verify` 共执行 6643 tests,其中 6642 项通过;
|
||||||
|
唯一错误是上游 `SettlementFinancialChecksMigrationTest` 在当前环境无法发现 Docker,
|
||||||
|
与本次接口变更无关。Issue #5216 的对应验收项因此仍保持未勾选。
|
||||||
|
- 功能分支部署任务:order-v3 `d0e26c90`、fleet `a4776dfa`,均成功完成双实例滚动部署。
|
||||||
|
- 经测试网关实测 `GET /admin/fleet/board/orders?page=1&pageSize=20`:HTTP 200,返回 3 条真实记录;
|
||||||
|
8 个新增字段在 3 条记录中全部存在且非空,观测到就绪状态 `PARTIAL`,接送状态
|
||||||
|
`READY/MISSING`。
|
||||||
|
- Nacos 实测:`hl-order-service-v3` 8086/8186、`hl-fleet-service` 8087/8187 均为 2/2 健康;
|
||||||
|
四个新实例均有正常启动记录,启动后的运行日志未发现新增 ERROR、FATAL、Exception 或
|
||||||
|
`Caused by`。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`;前端按“前端处理清单”接入即可。
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5226"
|
||||||
|
title: "用车需求提交后实时刷新派单看板"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@716d5e81311628f42d2ac1945089755b4264d47e"
|
||||||
|
target_release: "hl-ui/v2.1"
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "2026-07-24T17:12:49+08:00 后端已部署且网关 SSE 契约已验证;管理台仍待消费 fleet-board-changed,前端状态保持 pending。"
|
||||||
|
updated_at: "2026-07-24T09:29:05.220Z"
|
||||||
|
base: "origin/dev-v3"
|
||||||
|
generated: "2026-07-24T16:41:16+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 用车需求提交后实时刷新派单看板
|
||||||
|
|
||||||
|
> 自动草稿不会代表已验证;完成实际测试后再更新 frontmatter。
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: [wx/HL#5226](https://git.1814.love:8443/wx/HL/issues/5226)
|
||||||
|
- PR: [wx/HL#5232](https://git.1814.love:8443/wx/HL/pulls/5232)
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 来源 |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/internal/notification/fleet-board/broadcast` | Fleet 事务提交后调用 user-service 的内部广播端点 |
|
||||||
|
| `GET` | `/ws/admin-msg/stream` | 既有 SSE 流新增命名事件 `fleet-board-changed` |
|
||||||
|
|
||||||
|
## 契约影响文件
|
||||||
|
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/board/port/vo/FleetBoardChangedFeignReqVO.java`
|
||||||
|
- `hl-user-service/src/main/java/com/hulalv/user/notification/sse/vo/FleetBoardChangedReqVO.java`
|
||||||
|
|
||||||
|
## 前端/调用方动作
|
||||||
|
|
||||||
|
管理台继续复用现有 `/ws/admin-msg/stream` 连接,不新建第二条 EventSource。全局 SSE
|
||||||
|
组合式函数新增命名事件监听:
|
||||||
|
|
||||||
|
```js
|
||||||
|
eventSource.addEventListener('fleet-board-changed', onFleetBoardChanged)
|
||||||
|
```
|
||||||
|
|
||||||
|
事件数据示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "FLEET_BOARD",
|
||||||
|
"targetRoleKey": "VEHICLE_MANAGER",
|
||||||
|
"orderId": "2079000000000000001",
|
||||||
|
"requirementId": "2079000000000000101"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 该事件是“看板数据已失效”信令,不承载订单行数据;收到后重新查询当前看板。
|
||||||
|
- 只刷新 `getBoardSummary` 和当前页 `getBoardOrders`,保留状态、日期、车型、关键词、页码和展开状态。
|
||||||
|
- 事件可能短时间连续到达,必须合并刷新并避免并发请求覆盖;不得每个事件各发一组请求。
|
||||||
|
- `orderId/requirementId` 只用于定位和诊断,按 String 保存,不能转为 Number。
|
||||||
|
- 页面不可见时先标记 dirty,恢复可见或 SSE 重连成功后刷新一次。
|
||||||
|
- 刷新失败保留现有列表,不清空页面;沿用现有错误提示与下一次事件重试。
|
||||||
|
|
||||||
|
### 展示矩阵
|
||||||
|
|
||||||
|
| 场景 | 汇总卡 | 看板列表 | 筛选/页码 | 请求策略 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 页面可见,收到一次事件 | 重新查询 | 重新查询当前页 | 完整保留 | 合并为一轮刷新 |
|
||||||
|
| 短时间收到多次事件 | 最终值更新一次 | 最终值更新一次 | 完整保留 | debounce/coalesce,禁止并发覆盖 |
|
||||||
|
| 刷新进行中又收到事件 | 当前请求完成后再补一次 | 同左 | 完整保留 | 最多保留一个 pending refresh |
|
||||||
|
| 页面隐藏时收到事件 | 暂不请求 | 暂不请求 | 完整保留 | 标记 dirty,恢复可见后刷新一次 |
|
||||||
|
| SSE 重连成功 | 重新查询 | 重新查询当前页 | 完整保留 | 主动补偿一次,覆盖断线窗口 |
|
||||||
|
| 查询失败 | 保留旧值 | 保留旧列表 | 完整保留 | 展示既有错误提示,等待重试 |
|
||||||
|
| 非车务当前角色 | 不收到事件 | 不刷新 | 不变 | 后端仅投递 `VEHICLE_MANAGER` |
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- Fleet 定向测试:254 项通过,0 failure / 0 error。
|
||||||
|
- User 定向测试:40 项通过,0 failure / 0 error。
|
||||||
|
- `mvn -f hl-fleet-service/pom.xml spotless:check` 通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过。
|
||||||
|
- `mvn -pl hl-user-service -am verify` 通过。
|
||||||
|
- 事务语义:只有用车需求展开事务 `AFTER_COMMIT` 才广播;回滚不发事件,广播失败不阻断主流程。
|
||||||
|
- 路由语义:事件名固定为 `fleet-board-changed`,仅投递当前角色为 `VEHICLE_MANAGER` 的连接。
|
||||||
|
- 测试环境部署:`hl-user-service` 任务 `6d567b7f`、`hl-fleet-service` 任务 `561b1051`
|
||||||
|
均成功,两个滚动实例分别恢复健康。
|
||||||
|
- 网关/SSE:`GET /ws/admin-msg/stream` 返回 HTTP 200 和 `text/event-stream`;
|
||||||
|
`8081/8181` 两实例内部广播均返回业务码 200,未带内部令牌返回 403。
|
||||||
|
- 实际事件:当前角色为 `VEHICLE_MANAGER` 的连接收到 `fleet-board-changed`,
|
||||||
|
`type=FLEET_BOARD`,`targetRoleKey=VEHICLE_MANAGER`,订单与需求 ID 按 String 到达。
|
||||||
|
- 脱敏证据:`5226-gateway-sse.json`,SHA-256
|
||||||
|
`00f3a2d441e9993ea7df706924fa792497170f1d4efee7ca190f741ca5844936`。
|
||||||
|
- 兼容性结论:既有 SSE 事件和看板查询接口不变;未消费新命名事件的前端保持原行为。
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5236"
|
||||||
|
title: "用车接送改由大交通默认驱动"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@85851ad68d427e161d9342525af4567c1d108d5f"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端 PR #5241 已合并至 dev-v3(9578f78d5),order/fleet 已部署测试环境(b57ce915/0da3b9e6),双实例 internal 契约与网关汇总/列表/详情已验证;前端仍为 pending,待删除车辆接送开关并改用大交通摘要。"
|
||||||
|
updated_at: "2026-07-24T10:59:50.144Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 用车接送改由大交通默认驱动
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: [wx/HL#5236](https://git.1814.love:8443/wx/HL/issues/5236)
|
||||||
|
- Backend PR: [wx/HL#5241](https://git.1814.love:8443/wx/HL/pulls/5241)
|
||||||
|
- Supersedes: [wx/HL#5193](https://git.1814.love:8443/wx/HL/issues/5193) 中“用车需求独立决定接送”的业务口径
|
||||||
|
- 服务: `hl-order-service-v3`、`hl-fleet-service`
|
||||||
|
- 前端仓库/分支: `mmg/hl-ui` / `v2.1`
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
车辆安排不再让定制师重复选择“是否需要接机/接站”和“是否需要送机/送站”。
|
||||||
|
接送结论由订单当前大交通批次直接决定:
|
||||||
|
|
||||||
|
- `ARRIVAL` 批次聚合接机/接站。
|
||||||
|
- `DEPARTURE` 批次聚合送机/送站。
|
||||||
|
- 同方向任一批 `pickupRequired=true`,该方向为需要接送。
|
||||||
|
- 同方向全部批次均为 `false`,该方向为客人自理。
|
||||||
|
- 没有该方向批次时返回 `null`,表示未知。
|
||||||
|
- 新建大交通未传 `pickupRequired` 时默认保存为 `true`;显式 `false` 保持客人自理。
|
||||||
|
|
||||||
|
用车需求和订单调整中的 `pickupRequired`、`dropoffRequired` 字段暂不删除,继续兼容旧请求和回显,
|
||||||
|
但不再覆盖实时大交通结论。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 变化 |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/v3/admin/order/:id/transport-plan/add` | 新增大交通未传 `pickupRequired` 时默认 `true` |
|
||||||
|
| `POST` | `/v3/admin/order/:id/transport-plan/batch` | 批量替换中每个未传值的批次默认 `true` |
|
||||||
|
| `POST` | `/v3/admin/order/:id/transport-plan/:planId/edit` | 未传该字段时保留原值;显式值正常覆盖 |
|
||||||
|
| `PUT` | `/v3/admin/order/:id/vehicle-requirement` | 两个接送字段改为兼容字段,不再是权威来源 |
|
||||||
|
| `GET` | `/v3/admin/order/:id/adjustment/snapshot?scope=VEHICLE_REQ` | 继续通过 `vehicleTransportSummary` 返回大交通批次摘要 |
|
||||||
|
| `POST` | `/v3/admin/order/:id/adjustment/submit` | `updates.vehicleRequirement` 中两个接送字段仅兼容接收 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders` | 卡片接送就绪状态改为按实时大交通方向聚合 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders/:orderId` | `transport.pickupRequired/dropoffRequired` 只取实时大交通聚合 |
|
||||||
|
|
||||||
|
小程序内部大交通新增与批量接口使用相同默认规则,但本 changelog 的前端处理范围仅为管理后台。
|
||||||
|
|
||||||
|
## 字段语义
|
||||||
|
|
||||||
|
### 大交通请求 `pickupRequired`
|
||||||
|
|
||||||
|
| 场景 | 入参 | 保存结果 |
|
||||||
|
|---|---|---|
|
||||||
|
| 新增单批/批量批次未传 | 字段省略或 `null` | `true` |
|
||||||
|
| 新增单批/批量批次显式自理 | `false` | `false` |
|
||||||
|
| 编辑既有批次未传 | 字段省略或 `null` | 保留原值 |
|
||||||
|
| 编辑既有批次显式修改 | `true` / `false` | 按提交值覆盖 |
|
||||||
|
|
||||||
|
数据库列仍为 `TINYINT(1) NOT NULL`,仅把新记录的数据库默认值从 `0` 改为 `1`,不回填或改写历史行。
|
||||||
|
|
||||||
|
### 派单详情响应
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `transport.pickupRequired` | `Boolean` | 无 ARRIVAL 批次时为 `null` | ARRIVAL 批次聚合 |
|
||||||
|
| `transport.dropoffRequired` | `Boolean` | 无 DEPARTURE 批次时为 `null` | DEPARTURE 批次聚合 |
|
||||||
|
| `transport.arrive/depart` | `Object/null` | 对应整团批次不存在时为 `null` | 到达/返程整团大交通 |
|
||||||
|
| `transport.batches[]` | `Object[]` | 无分批时为空数组 | 分批大交通,保留方向、时间、站点和关联出行人 |
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
1. 删除“调整订单 → 车辆安排”中的“是否需要接机/接站”和“是否需要送机/送站”两个开关。
|
||||||
|
2. 提交用车需求或订单调整时,不再主动提交 `pickupRequired`、`dropoffRequired`。
|
||||||
|
3. 车辆安排页直接展示 `vehicleTransportSummary.arrivals[]` 与 `departures[]`;继续使用其中的
|
||||||
|
`direction`、`time`、`station`、`transportNo`、`pickupRequired`、`pickupRemark` 和
|
||||||
|
`travelerNames[]`。
|
||||||
|
4. 派单看板和详情不得回退到 `vehicleRequirement.pickupRequired/dropoffRequired`;
|
||||||
|
使用看板接送摘要与详情 `transport.pickupRequired/dropoffRequired`。
|
||||||
|
5. 雪花 ID 仍按字符串处理,本次没有字段删除、类型变化或新增错误码。
|
||||||
|
|
||||||
|
## 展示矩阵
|
||||||
|
|
||||||
|
| 大交通场景 | 接机/接站 | 送机/送站 | 页面展示 |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| ARRIVAL 任一批需要,DEPARTURE 全部自理 | `true` | `false` | 分方向显示“平台接 / 客人自理” |
|
||||||
|
| ARRIVAL 全部自理,DEPARTURE 任一批需要 | `false` | `true` | 分方向显示“客人自理 / 平台送” |
|
||||||
|
| 同方向多批混合 | `true` | 按返程批次聚合 | 明细保留每个批次及关联出行人 |
|
||||||
|
| 只有 ARRIVAL | 按到达批次聚合 | `null` | 返程显示未提供,不回退旧用车需求 |
|
||||||
|
| 只有 DEPARTURE | `null` | 按返程批次聚合 | 到达显示未提供,不回退旧用车需求 |
|
||||||
|
| 完全无大交通 | `null` | `null` | 显示“暂无接送机时间” |
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- Order 定向测试 57 项通过。
|
||||||
|
- Fleet `BoardOrderServiceTest` 50 项通过。
|
||||||
|
- 调整/需求/出行人兼容链路 357 项通过。
|
||||||
|
- 调整快照完整字段断言 `AdjustmentServiceTest` 10 项通过。
|
||||||
|
- `mvn -pl hl-order-service-v3 -am verify` 通过。
|
||||||
|
- Order 模块 Surefire 汇总 6646 项,0 失败、0 错误、28 跳过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过:Fleet 模块 2361 项,0 失败、0 错误、1 跳过。
|
||||||
|
- Fleet `spotless:check` 与 `git diff --check` 通过。
|
||||||
|
- OpenAPI/oasdiff: `not_configured`,使用源码字段/语义比对和测试作为 fallback。
|
||||||
|
- Spring Cloud Contract: `not_configured`,使用 order-v3 生产者与 Fleet 消费者测试作为 fallback。
|
||||||
|
- 后端 PR #5241 已合并,merge commit 为 `9578f78d5f0241db502d94b22283cbff0a351c53`。
|
||||||
|
- 测试环境部署任务:order `b57ce915`、fleet `0da3b9e6`。
|
||||||
|
- `order_transport_plan.pickup_required` 已验证为 `TINYINT(1) NOT NULL DEFAULT 1`,Flyway
|
||||||
|
`20260724.001` 执行成功。
|
||||||
|
- order `8086/8186` 均通过 `/v3/internal/order/orders/:orderId/transport` 与批量看板上下文实测;
|
||||||
|
`true/false/null` 三态及 ARRIVAL/DEPARTURE 分方向聚合符合字段语义。
|
||||||
|
- 测试网关 `/admin/fleet/board/summary`、`/orders`、`/orders/:orderId` 均返回成功;
|
||||||
|
详情连续 4 次通过,运行时证据为 `D:/work2/hl-workflow/.tmp/5236-gateway-evidence.json`。
|
||||||
文件差异内容过多而无法显示
加载差异
@ -0,0 +1,427 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@adff10ea74198f4e89a1488e631463bedbcd4eea"
|
||||||
|
updated_at: "2026-07-25T03:37:10.934Z"
|
||||||
|
---
|
||||||
|
# 【修改接口·管理后台】酒店候选补齐房型结算价 (#5237)
|
||||||
|
|
||||||
|
> **PR**: #5240 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-25 10:58
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
管理后台酒店候选列表原来只在候选酒店顶层返回 `protoPrice`,前端无法确认这个价格来自哪个真实房型,也拿不到同一房型同一天的结算价。配房时如果只看房型列表或自行匹配最低价,容易把协议价和结算价口径拆到不同房型。
|
||||||
|
|
||||||
|
本次在候选酒店顶层补齐:
|
||||||
|
|
||||||
|
- `protoPriceRoomTypeId`:产生顶层 `protoPrice` 的真实房型 ID。
|
||||||
|
- `settlementPrice`:与 `protoPriceRoomTypeId` 同一房型、同一天的结算价。
|
||||||
|
|
||||||
|
顶层 `protoPrice`、`protoPriceRoomTypeId`、`settlementPrice` 是同一代表房型口径。未维护结算价时 `settlementPrice = null`,不会用协议价兜底。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询酒店候选(4 场景统一入口) | GET | `/v3/admin/hotel-candidates` | 修改接口 | 候选酒店项新增 `protoPriceRoomTypeId`、`settlementPrice` 两个出参字段;入参不变。 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询酒店候选(4 场景统一入口)
|
||||||
|
|
||||||
|
- **使用场景**:管理后台在订单维度查询某一晚的候选酒店,用于配房选酒店、回显当前已配酒店、按产品池/定制师点名/资源库候选排序。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:只读查询,幂等。
|
||||||
|
- **限流**:无接口级特殊限流;受网关与服务通用限流策略约束。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String | 是 | 订单 ID。后端 Long,JSON/Query 建议按字符串传,避免长 ID 精度问题。 |
|
||||||
|
| `dayNumber` | Integer | 否 | 第几天,从 1 开始;用于推算 `stayDate = departDate + dayNumber - 1`。最小值 1。 |
|
||||||
|
| `stayDate` | String | 否 | 入住日期,格式 `yyyy-MM-dd`;直接指定时优先于 `dayNumber` 推算。 |
|
||||||
|
| `city` | String | 否 | 城市代码或城市名;未传且非关键词模式时默认不按城市限制。 |
|
||||||
|
| `keyword` | String | 否 | 关键词;非空时跨城/省匹配酒店名、城市、省份、地址,此时 `city` 可不传。 |
|
||||||
|
| `limit` | Integer | 否 | 返回候选条数上限,默认 30,最小 1,最大 50。 |
|
||||||
|
| `roomCategory` | String | 否 | 房型字典 code。 |
|
||||||
|
| `roomCount` | Integer | 否 | 需要的房间数;最小 1。 |
|
||||||
|
| `preferredHotelId` | String | 否 | 定制师指定的优先酒店 ID。后端 Long,建议字符串传。 |
|
||||||
|
| `requirementId` | String | 否 | 用房需求 ID;传入后将该需求 days JSON 中当前天的酒店候选作为定制师指定候选。后端 Long,建议字符串传。 |
|
||||||
|
|
||||||
|
### 4.2 请求体字段
|
||||||
|
|
||||||
|
GET 接口无请求体。
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
统一响应结构:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务状态码,成功为 `200`。 |
|
||||||
|
| `message` | String | 响应消息,成功为 `成功`。 |
|
||||||
|
| `data` | Object | 酒店候选查询出参。 |
|
||||||
|
| `traceId` | String | 链路追踪 ID,可能为空。 |
|
||||||
|
| `success` | Boolean | `code == 200` 时为 `true`。 |
|
||||||
|
|
||||||
|
`data` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `stayDate` | String | 入住日期,格式 `yyyy-MM-dd`。 |
|
||||||
|
| `city` | String / null | 本次查询使用的城市;关键词模式或默认不限城市时可为 `null`。 |
|
||||||
|
| `productType` | String | 产品类型:`CORE` / `GROUP` / `CUSTOM`。 |
|
||||||
|
| `candidates` | Array | 候选酒店列表,已按产品类型分流排序。 |
|
||||||
|
|
||||||
|
`data.candidates[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `hotelId` | String | 酒店 ID。 |
|
||||||
|
| `hotelName` | String | 酒店名称。 |
|
||||||
|
| `level` | String / null | 酒店等级。 |
|
||||||
|
| `form` | String / null | 住宿形态。 |
|
||||||
|
| `address` | String / null | 地址。 |
|
||||||
|
| `tags` | Array<String> | 运营标签;无标签时为空数组或 `null`。 |
|
||||||
|
| `contactPerson` | String / null | 联系人。 |
|
||||||
|
| `contactWechat` | String / null | 联系微信。 |
|
||||||
|
| `settleType` | String / null | 结算类型,取值见 §6.1。 |
|
||||||
|
| `city` | String / null | 酒店所在城市。 |
|
||||||
|
| `district` | String / null | 酒店所在区/县。 |
|
||||||
|
| `roomTypes` | Array | 该酒店当日真实房型列表;无房型数据时为空数组。 |
|
||||||
|
| `protoPrice` | String / null | 代表房型协议价。与 `protoPriceRoomTypeId`、顶层 `settlementPrice` 同一房型同一天。 |
|
||||||
|
| `protoPriceRoomTypeId` | String / null | 产生顶层 `protoPrice` 的真实房型 ID。无有效可售协议价时为 `null`。 |
|
||||||
|
| `settlementPrice` | String / null | 与 `protoPriceRoomTypeId` 同一房型、同一天的结算价。未维护时为 `null`,不会用 `protoPrice` 兜底。 |
|
||||||
|
| `todayAvailable` | Integer / null | 今日全房型可用房数合计。 |
|
||||||
|
| `availFreshness` | String / null | 可用数数据时效:`fresh` / `stale` / `never_checked`。 |
|
||||||
|
| `lastCheckedAt` | String / null | 最近一次核房时间,格式 `yyyy-MM-dd'T'HH:mm:ss`。 |
|
||||||
|
| `matchedRoomTypeAvailable` | Integer / null | 匹配房型今日可用数。 |
|
||||||
|
| `matchedRoomTypeId` | String / null | 匹配的房型 ID。 |
|
||||||
|
| `matchedRoomTypeLabel` | String / null | 匹配的房型中文。 |
|
||||||
|
| `quickPickEnabled` | Boolean / null | 是否支持快速配房。 |
|
||||||
|
| `quickPickDisabledReason` | String / null | 置灰原因。 |
|
||||||
|
| `isPoolMatch` | Boolean / null | 是否产品池内。 |
|
||||||
|
| `poolMatchBadge` | Object / null | 产品池内徽章。 |
|
||||||
|
| `isConsultantRecommended` | Boolean / null | 是否被定制师点名。 |
|
||||||
|
| `consultantRecommendBadge` | Object / null | 定制师点名徽章。 |
|
||||||
|
| `historyMatchScore` | Number / null | 历史匹配度,范围 0-1。 |
|
||||||
|
| `score` | Number / null | 排序分数。 |
|
||||||
|
| `recommendation` | String / null | 推荐理由。 |
|
||||||
|
| `recommended` | Boolean / null | 是否为推荐候选。 |
|
||||||
|
| `recommendSource` | String / null | 推荐来源,见 §6.4。 |
|
||||||
|
| `historyScoreStub` | Boolean / null | 历史命中分数是否为 stub。 |
|
||||||
|
| `isCurrentlyAssigned` | Boolean / null | 是否为本天当前已配酒店。 |
|
||||||
|
| `assignedRoomTypeId` | String / null | 本天当前已配的房型 ID;`isCurrentlyAssigned=true` 时可用于预填原房型。 |
|
||||||
|
|
||||||
|
`data.candidates[].roomTypes[]` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `roomTypeId` | String | 房型 ID。 |
|
||||||
|
| `name` | String / null | 房型名称。 |
|
||||||
|
| `roomCategory` | String / null | 房型分类字典值。 |
|
||||||
|
| `bedType` | String / null | 床型,已按字典尽量翻译;字典缺失时可回退为 code。 |
|
||||||
|
| `maxOccupancy` | Integer / null | 最大入住人数。 |
|
||||||
|
| `available` | Integer / null | 今日可用房数;`unlimited=true` 时为 `null`,语义为不限。 |
|
||||||
|
| `unlimited` | Boolean | 是否不限库存。 |
|
||||||
|
| `stock` | Integer / null | 当前可用房;`unlimited=true` 时为 `null`。 |
|
||||||
|
| `protocolPrice` | String / null | 该房型当日协议价。 |
|
||||||
|
| `settlementPrice` | String / null | 该房型当日结算价。 |
|
||||||
|
| `basePrice` | String / null | 标价/挂牌价。 |
|
||||||
|
| `inventoryStatus` | String | 库存状态,见 §6.2。 |
|
||||||
|
|
||||||
|
徽章对象字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `label` | String | 中文徽章文字。 |
|
||||||
|
| `color` | String | 徽章色,见 §6.5。 |
|
||||||
|
| `tooltip` | String | 悬浮提示。 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `settleType`
|
||||||
|
|
||||||
|
**所属字段**:`data.candidates[].settleType` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `cash` | 现付 | 到店或线下现金类结算。 |
|
||||||
|
| `sign` | 签单 | 供应商签单结算。 |
|
||||||
|
| `company` | 公司付 | 公司统一付款结算。 |
|
||||||
|
|
||||||
|
### 6.2 `inventoryStatus`
|
||||||
|
|
||||||
|
**所属字段**:`data.candidates[].roomTypes[].inventoryStatus` | **类型**:String | **必填**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `AVAILABLE` | 可售 | 有余量,或 `unlimited=true` 不限库存。 |
|
||||||
|
| `FULL` | 满房 | 有日历记录,但库存为 0。 |
|
||||||
|
| `CLOSED` | 未开放 | 无该日价格日历记录。 |
|
||||||
|
|
||||||
|
### 6.3 `availFreshness`
|
||||||
|
|
||||||
|
**所属字段**:`data.candidates[].availFreshness` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `fresh` | 最新 | 可用于快速配房判断。 |
|
||||||
|
| `stale` | 过期 | 核房数据过期。 |
|
||||||
|
| `never_checked` | 从未核房 | 无可用核房数据。 |
|
||||||
|
|
||||||
|
### 6.4 `recommendSource`
|
||||||
|
|
||||||
|
**所属字段**:`data.candidates[].recommendSource` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `PRODUCT_POOL` | 产品池 | 来自产品池候选。 |
|
||||||
|
| `CONSULTANT` | 定制师点名 | 来自定制师指定候选。 |
|
||||||
|
| `RESOURCE_LIB` | 资源库 | 来自资源库候选。 |
|
||||||
|
|
||||||
|
### 6.5 `Badge.color`
|
||||||
|
|
||||||
|
**所属字段**:`poolMatchBadge.color` / `consultantRecommendBadge.color` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `blue` | 蓝色 | 普通推荐或池内标识。 |
|
||||||
|
| `gold` | 金色 | 高优先级推荐标识。 |
|
||||||
|
| `gray` | 灰色 | 弱提示标识。 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 查询成功。 |
|
||||||
|
| `400` | 参数错误 | `orderId` 为空、`dayNumber < 1`、`limit` 超出 1-50、`roomCount < 1`、日期格式不是 `yyyy-MM-dd` 等参数绑定或校验失败。 |
|
||||||
|
| `401` | 未认证 | JWT 缺失或无效。 |
|
||||||
|
| `403` | 无权限 | 当前账号无权访问该管理后台接口或订单数据。 |
|
||||||
|
| `581007` | 订单不存在 | `orderId` 对应订单不存在。 |
|
||||||
|
| `500` | 服务内部错误 | 非预期异常。 |
|
||||||
|
|
||||||
|
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
### 8.1 典型成功
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/hotel-candidates?orderId=100001&dayNumber=1&stayDate=2026-07-25&limit=30&roomCount=2 HTTP/1.1
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"stayDate": "2026-07-25",
|
||||||
|
"city": null,
|
||||||
|
"productType": "CORE",
|
||||||
|
"candidates": [
|
||||||
|
{
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "测试酒店",
|
||||||
|
"level": "舒适型",
|
||||||
|
"form": "HOTEL",
|
||||||
|
"address": "呼伦贝尔市海拉尔区测试路 1 号",
|
||||||
|
"tags": ["协议酒店"],
|
||||||
|
"contactPerson": "张经理",
|
||||||
|
"contactWechat": "hotel_mgr",
|
||||||
|
"settleType": "sign",
|
||||||
|
"city": "呼伦贝尔市",
|
||||||
|
"district": "海拉尔区",
|
||||||
|
"protoPrice": "280.00",
|
||||||
|
"protoPriceRoomTypeId": "2023727403196502017",
|
||||||
|
"settlementPrice": "279.00",
|
||||||
|
"todayAvailable": 7,
|
||||||
|
"availFreshness": "fresh",
|
||||||
|
"lastCheckedAt": null,
|
||||||
|
"matchedRoomTypeAvailable": 7,
|
||||||
|
"matchedRoomTypeId": "2023727403196502017",
|
||||||
|
"matchedRoomTypeLabel": "豪华大床房",
|
||||||
|
"quickPickEnabled": true,
|
||||||
|
"quickPickDisabledReason": null,
|
||||||
|
"isPoolMatch": true,
|
||||||
|
"poolMatchBadge": {
|
||||||
|
"label": "产品池内",
|
||||||
|
"color": "blue",
|
||||||
|
"tooltip": "本酒店在产品池内,优先推荐"
|
||||||
|
},
|
||||||
|
"isConsultantRecommended": false,
|
||||||
|
"consultantRecommendBadge": null,
|
||||||
|
"historyMatchScore": 0.85,
|
||||||
|
"score": 1185.0,
|
||||||
|
"recommendation": "池内 · 历史合作 8 单成功率 95%",
|
||||||
|
"recommended": true,
|
||||||
|
"recommendSource": "PRODUCT_POOL",
|
||||||
|
"historyScoreStub": true,
|
||||||
|
"isCurrentlyAssigned": false,
|
||||||
|
"assignedRoomTypeId": null,
|
||||||
|
"roomTypes": [
|
||||||
|
{
|
||||||
|
"roomTypeId": "2023727403196502017",
|
||||||
|
"name": "豪华大床房",
|
||||||
|
"roomCategory": "KING",
|
||||||
|
"bedType": "大床",
|
||||||
|
"maxOccupancy": 2,
|
||||||
|
"available": 7,
|
||||||
|
"unlimited": false,
|
||||||
|
"stock": 7,
|
||||||
|
"protocolPrice": "280.00",
|
||||||
|
"settlementPrice": "279.00",
|
||||||
|
"basePrice": "568.00",
|
||||||
|
"inventoryStatus": "AVAILABLE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "trace-20260725-0001",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况
|
||||||
|
|
||||||
|
**场景说明**:代表房型有协议价但未维护结算价,顶层 `settlementPrice` 返回 `null`,不使用 `protoPrice` 兜底。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/hotel-candidates?orderId=100001&stayDate=2026-07-25&keyword=%E6%B5%B7%E6%8B%89%E5%B0%94&limit=1 HTTP/1.1
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"stayDate": "2026-07-25",
|
||||||
|
"city": null,
|
||||||
|
"productType": "CUSTOM",
|
||||||
|
"candidates": [
|
||||||
|
{
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "测试酒店",
|
||||||
|
"settleType": "cash",
|
||||||
|
"protoPrice": "280.00",
|
||||||
|
"protoPriceRoomTypeId": "2023727403196502017",
|
||||||
|
"settlementPrice": null,
|
||||||
|
"roomTypes": [
|
||||||
|
{
|
||||||
|
"roomTypeId": "2023727403196502017",
|
||||||
|
"name": "豪华大床房",
|
||||||
|
"available": 7,
|
||||||
|
"unlimited": false,
|
||||||
|
"protocolPrice": "280.00",
|
||||||
|
"settlementPrice": null,
|
||||||
|
"basePrice": "568.00",
|
||||||
|
"inventoryStatus": "AVAILABLE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quickPickEnabled": true,
|
||||||
|
"recommended": true,
|
||||||
|
"recommendSource": "RESOURCE_LIB"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "trace-20260725-0002",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败(异常)
|
||||||
|
|
||||||
|
**场景说明**:`orderId` 未传,触发参数校验失败。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/hotel-candidates?stayDate=2026-07-25 HTTP/1.1
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "orderId 不能为空",
|
||||||
|
"data": null,
|
||||||
|
"traceId": "trace-20260725-0003",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:管理后台按订单和入住日查询酒店候选;`stayDate` 可直接传,也可通过 `dayNumber` 和订单出发日推算。
|
||||||
|
- **不适用场景**:不用于前端直接查询内部资源服务;本文只描述管理后台 `/v3/admin/hotel-candidates`。
|
||||||
|
- **特殊边界**:顶层 `protoPrice`、`protoPriceRoomTypeId`、`settlementPrice` 必须按同一代表房型理解;`settlementPrice = null` 表示该代表房型当天未维护结算价。
|
||||||
|
- **特殊边界**:`roomTypes[].settlementPrice` 是每个房型自己的当日结算价;顶层 `settlementPrice` 只对应 `protoPriceRoomTypeId` 指向的代表房型。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.candidates[].protoPriceRoomTypeId` | 不返回 | 返回产生顶层 `protoPrice` 的真实房型 ID;无有效可售协议价为 `null`。 |
|
||||||
|
| `data.candidates[].settlementPrice` | 不返回 | 返回与 `protoPriceRoomTypeId` 同一房型、同一天的结算价;未维护为 `null`。 |
|
||||||
|
| `data.candidates[].protoPrice` | 已返回,但无法判断来自哪个房型 | 仍返回原字段,并与新增的 `protoPriceRoomTypeId`、顶层 `settlementPrice` 组成同一代表房型口径。 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 候选酒店顶层价格展示 | 只能拿到代表协议价 `protoPrice`。 | 可同时拿到代表协议价、代表房型 ID、该代表房型结算价。 |
|
||||||
|
| 结算价为空 | 顶层没有结算价字段。 | 顶层 `settlementPrice` 返回 `null`;不使用 `protoPrice` 兜底。 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。只新增出参字段,已有字段名、类型、入参不变。
|
||||||
|
- **前端是否必须同步上线**:否。老前端可忽略新增字段;需要展示或回填结算价的页面可读取新增字段。
|
||||||
|
- **影响已有数据**:无数据迁移要求;历史未维护结算价的房型按 `settlementPrice = null` 返回。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚方式**:回滚 PR #5240 后,顶层新增字段不再返回。
|
||||||
|
- **回滚后清理**:无前端数据清理要求。
|
||||||
|
- **回滚耗时**:按常规服务回滚流程处理。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端读取顶层 `settlementPrice` 时,不要把 `null` 当作 `protoPrice`;`null` 表示未维护结算价。
|
||||||
|
- 如需定位价格来自哪个房型,使用顶层 `protoPriceRoomTypeId` 去匹配 `roomTypes[].roomTypeId`。
|
||||||
|
- 金额和长 ID 在响应 JSON 中按字符串处理,例如 `"280.00"`、`"2023727403196502017"`。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5237](https://git.1814.love:8443/wx/HL/issues/5237)
|
||||||
|
- **PR**: [#5240](https://git.1814.love:8443/wx/HL/pulls/5240)
|
||||||
|
- **Merge commit**: [dc6e2ef](https://git.1814.love:8443/wx/HL/commit/dc6e2ef6c2b49bd503353814f85723566d4413c6)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,388 @@
|
|||||||
|
---
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@5a155c42395a7abd66c78789b225d6af86bb7fbd"
|
||||||
|
updated_at: "2026-07-25T03:42:03.625Z"
|
||||||
|
---
|
||||||
|
# 【修改接口·管理后台】核单门票来源类型统一 (#5238)
|
||||||
|
|
||||||
|
> **PR**: #5242 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-25 10:03
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单 Step2 门票/游玩项目页签中,手工补充的门票行此前在查询出参中使用 `CUSTOM_ASSIGNMENT`。为避免前端按不同 Tab 或来源类型做额外分支,本次将查询出参的手工门票来源统一为 `MANUAL`,中文名统一为 `手工项目`;保存接口同步允许直接提交 `MANUAL`。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | Step 2 查询门票核单明细 | GET | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | 手工/自定义门票行的 `sourceType` 统一返回 `MANUAL`,`sourceTypeName` 返回 `手工项目` |
|
||||||
|
| 2 | Step 2 录门票核单明细 | PUT | `/v3/admin/order/{orderId}/settlement/step2` | 修改接口 | `items[].sourceType` 新增允许 `MANUAL`;旧 `CUSTOM_ASSIGNMENT` 入参继续兼容 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 Step 2 查询门票核单明细
|
||||||
|
|
||||||
|
- **方法**:GET
|
||||||
|
- **路径**:`/v3/admin/order/{orderId}/settlement/step2`
|
||||||
|
- **接口名**:`listTicket`
|
||||||
|
- **ApiOperation**:Step 2 查询门票核单明细
|
||||||
|
- **使用场景**:进入核单 Step2 门票/游玩项目页签,或保存成功后回读页面明细。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:幂等,只读查询。
|
||||||
|
- **限流**:无单接口额外限流。
|
||||||
|
- **响应结构**:`data` 为 `TicketItemVO[]`。
|
||||||
|
|
||||||
|
### 3.2 Step 2 录门票核单明细
|
||||||
|
|
||||||
|
- **方法**:PUT
|
||||||
|
- **路径**:`/v3/admin/order/{orderId}/settlement/step2`
|
||||||
|
- **接口名**:`saveTicket`
|
||||||
|
- **ApiOperation**:Step 2 录门票核单明细
|
||||||
|
- **使用场景**:保存核单 Step2 门票/游玩项目明细,包含派生门票行和手工补充门票行。
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:全量替换保存;同一份 `items` 重复提交后,以最后一次提交结果为准。
|
||||||
|
- **限流**:无单接口额外限流。
|
||||||
|
- **请求体兼容**:推荐使用 `{ "items": [...] }`;历史数组 body `[...]` 仍兼容。
|
||||||
|
- **响应结构**:`data` 为 `SettlementTicketSaveRespVO`。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 接口 | 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| GET / PUT | `orderId` | string | 是 | 订单 ID,长整型字符串 |
|
||||||
|
|
||||||
|
两个接口均无 Query 参数。
|
||||||
|
|
||||||
|
### 4.2 GET 请求体字段
|
||||||
|
|
||||||
|
GET 无请求体。
|
||||||
|
|
||||||
|
### 4.3 PUT 请求体字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `items` | array | 是 | 门票/游玩项目明细行数组,全量替换保存 | 不允许为 `null` |
|
||||||
|
| `items[].id` | string | 否 | 已存在行 ID;新增行可不传 | 长整型字符串 |
|
||||||
|
| `items[].sourceType` | string | 是 | 来源类型;手工门票推荐传 `MANUAL` | `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `MANUAL` / `CUSTOM_ASSIGNMENT` |
|
||||||
|
| `items[].sourceTypeName` | string | 否 | 来源类型中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||||
|
| `items[].scenicAssignmentId` | string/null | 否 | 来源 assignment ID;手工项目传 `null` | 长整型字符串或 `null` |
|
||||||
|
| `items[].dayNumber` | integer/null | 否 | 行程第几天;保存后以回读值为准 | 从 1 开始 |
|
||||||
|
| `items[].dayDate` | string | 是 | 行程日期 | `yyyy-MM-dd` |
|
||||||
|
| `items[].scenicName` | string | 是 | 景区/游玩项目名称 | 1-200 字符 |
|
||||||
|
| `items[].specName` | string/null | 否 | 规格/票型名称 | 最大 128 字符 |
|
||||||
|
| `items[].ticketCount` | integer | 是 | 实际购票数量;套餐含门票但无额外成本时可填 0 | 整数 |
|
||||||
|
| `items[].ticketUnitPrice` | number/null | 否 | 参考成本单价,单位元 | 小数 |
|
||||||
|
| `items[].sellPrice` | number/null | 否 | 客户成交单价,单位元 | `>= 0` |
|
||||||
|
| `items[].totalAmount` | number/null | 否 | 客户成交小计,单位元 | `>= 0` |
|
||||||
|
| `items[].plannedCost` | number | 是 | 计划成本,单位元 | `>= 0` |
|
||||||
|
| `items[].actualCost` | number | 是 | 实际成本,单位元 | `>= 0` |
|
||||||
|
| `items[].paymentMethod` | string | 否 | 付款方式;不传时按公司付款处理 | `SIGNED` / `COMPANY_PAID` / `CASH_PAID` |
|
||||||
|
| `items[].paymentMethodName` | string | 否 | 付款方式中文名,仅展示字段;保存时可不传 | 最大 32 字符 |
|
||||||
|
| `items[].voucherUrls` | array | 否 | 凭证图片 URL 数组 | 字符串数组 |
|
||||||
|
| `items[].remark` | string/null | 否 | 备注 | 最大 500 字符 |
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
### 5.1 GET 响应字段:`TicketItemVO[]`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | integer | 业务状态码,成功为 `200` |
|
||||||
|
| `message` | string | 响应消息 |
|
||||||
|
| `success` | boolean | 是否成功 |
|
||||||
|
| `data` | array | 门票/游玩项目明细行数组 |
|
||||||
|
| `data[].id` | string/null | 核单明细行 ID;未持久化派生行可能为 `null` |
|
||||||
|
| `data[].sourceType` | string | 来源类型;手工/自定义门票行本次统一返回 `MANUAL` |
|
||||||
|
| `data[].sourceTypeName` | string/null | 来源类型中文名;`MANUAL` 返回 `手工项目` |
|
||||||
|
| `data[].scenicAssignmentId` | string/null | 来源 assignment ID;手工项目为 `null` |
|
||||||
|
| `data[].dayNumber` | integer/null | 行程第几天 |
|
||||||
|
| `data[].dayDate` | string | 行程日期,`yyyy-MM-dd` |
|
||||||
|
| `data[].scenicName` | string | 景区/游玩项目名称 |
|
||||||
|
| `data[].specName` | string/null | 规格/票型名称 |
|
||||||
|
| `data[].ticketCount` | integer | 实际购票数量 |
|
||||||
|
| `data[].ticketUnitPrice` | number/null | 参考成本单价,单位元 |
|
||||||
|
| `data[].sellPrice` | number/null | 客户成交单价,单位元 |
|
||||||
|
| `data[].totalAmount` | number/null | 客户成交小计,单位元 |
|
||||||
|
| `data[].plannedCost` | number | 计划成本,单位元 |
|
||||||
|
| `data[].actualCost` | number | 实际成本,单位元 |
|
||||||
|
| `data[].paymentMethod` | string/null | 付款方式 |
|
||||||
|
| `data[].paymentMethodName` | string/null | 付款方式中文名 |
|
||||||
|
| `data[].voucherUrls` | array | 凭证图片 URL 数组 |
|
||||||
|
| `data[].remark` | string/null | 备注 |
|
||||||
|
|
||||||
|
### 5.2 PUT 响应字段:`SettlementTicketSaveRespVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | integer | 业务状态码,成功为 `200` |
|
||||||
|
| `message` | string | 响应消息 |
|
||||||
|
| `success` | boolean | 是否成功 |
|
||||||
|
| `data.addedIds` | string[] | 本次保存新增的核单明细行 ID 列表 |
|
||||||
|
| `data.updatedIds` | string[] | 本次保存更新的核单明细行 ID 列表;当前全量替换语义下通常为空数组 |
|
||||||
|
| `data.deletedIds` | string[] | 本次保存删除的核单明细行 ID 列表;当前返回通常为空数组 |
|
||||||
|
| `data.totalActualCost` | string | 保存后 Step2 实际成本合计,单位元 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `sourceType`
|
||||||
|
|
||||||
|
**所属字段**:`items[].sourceType`、`data[].sourceType` | **类型**:String | **PUT 必填**:是 | **GET 必返**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SCENIC_ASSIGNMENT` | 景区 | 景区派生来源行;查询和保存语义不变 |
|
||||||
|
| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 游玩项目派生来源行;查询和保存语义不变 |
|
||||||
|
| `MANUAL` | 手工项目 | 本次推荐值;查询手工/自定义门票行统一返回该值,保存接口也允许提交该值 |
|
||||||
|
| `CUSTOM_ASSIGNMENT` | 手工项目(旧入参兼容) | 仅用于兼容旧保存请求;查询响应不再返回该值 |
|
||||||
|
|
||||||
|
### 6.2 `sourceTypeName`
|
||||||
|
|
||||||
|
**所属字段**:`items[].sourceTypeName`、`data[].sourceTypeName` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| sourceType | sourceTypeName | 说明 |
|
||||||
|
|------------|----------------|------|
|
||||||
|
| `SCENIC_ASSIGNMENT` | `景区` | 景区派生来源行 |
|
||||||
|
| `ACTIVITY_ASSIGNMENT` | `游玩项目` | 游玩项目派生来源行 |
|
||||||
|
| `MANUAL` | `手工项目` | 手工/自定义门票行统一展示名 |
|
||||||
|
| `CUSTOM_ASSIGNMENT` | `手工项目` | 旧保存请求兼容;保存成功后回读为 `MANUAL` / `手工项目` |
|
||||||
|
| `null` / 未知值 | `null` | 查询行为不变,不新增兜底文案 |
|
||||||
|
|
||||||
|
### 6.3 `paymentMethod`
|
||||||
|
|
||||||
|
**所属字段**:`items[].paymentMethod`、`data[].paymentMethod` | **类型**:String | **必填**:否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `SIGNED` | 签单 | 现场签单 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 公司统一付款;未传 `paymentMethod` 时按该值处理 |
|
||||||
|
| `CASH_PAID` | 现付 | 现场现金/线下现付 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| HTTP 状态 / code | 含义 | 触发场景 |
|
||||||
|
|------------------|------|----------|
|
||||||
|
| `200` / `200` | 成功 | GET 查询成功或 PUT 保存成功 |
|
||||||
|
| `200` / `401` | 未授权 | 缺少有效的管理后台 `Authorization` 头 |
|
||||||
|
| `400` / `400` | 请求参数非法 | `sourceType` 不在 `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `MANUAL` / `CUSTOM_ASSIGNMENT` 内,或请求体结构不符合要求 |
|
||||||
|
| `200` / `584011` | 当前核单状态不允许录门票核单 | PUT 保存时订单不是可录门票核单的状态 |
|
||||||
|
|
||||||
|
### 7.1 错误结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT 之一",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
### 8.1 典型成功:GET 返回手工项目为 MANUAL
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079576729147338754/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
GET 无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "2080186487600025601",
|
||||||
|
"sourceType": "MANUAL",
|
||||||
|
"sourceTypeName": "手工项目",
|
||||||
|
"scenicAssignmentId": null,
|
||||||
|
"dayNumber": 2,
|
||||||
|
"dayDate": "2026-07-22",
|
||||||
|
"scenicName": "临时补充门票",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": 30.00,
|
||||||
|
"sellPrice": 50.00,
|
||||||
|
"totalAmount": 100.00,
|
||||||
|
"plannedCost": 60.00,
|
||||||
|
"actualCost": 60.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "现场补充"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界成功:查询结果原样 PUT
|
||||||
|
|
||||||
|
**场景说明**:前端可把 GET 回来的 `MANUAL` 行原样放入 `items` 后提交;保存成功后再次 GET 仍返回 `MANUAL` / `手工项目`。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079576729147338754/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "2080186487600025601",
|
||||||
|
"sourceType": "MANUAL",
|
||||||
|
"sourceTypeName": "手工项目",
|
||||||
|
"scenicAssignmentId": null,
|
||||||
|
"dayNumber": 2,
|
||||||
|
"dayDate": "2026-07-22",
|
||||||
|
"scenicName": "临时补充门票",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 2,
|
||||||
|
"ticketUnitPrice": 30.00,
|
||||||
|
"sellPrice": 50.00,
|
||||||
|
"totalAmount": 100.00,
|
||||||
|
"plannedCost": 60.00,
|
||||||
|
"actualCost": 60.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "现场补充"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"addedIds": ["2080186500000000001"],
|
||||||
|
"updatedIds": [],
|
||||||
|
"deletedIds": [],
|
||||||
|
"totalActualCost": "60.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:非法 sourceType
|
||||||
|
|
||||||
|
**场景说明**:`items[].sourceType` 传入未定义值时仍按参数非法处理。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079576729147338754/settlement/step2
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"sourceType": "TAB_MANUAL",
|
||||||
|
"scenicAssignmentId": null,
|
||||||
|
"dayDate": "2026-07-22",
|
||||||
|
"scenicName": "临时补充门票",
|
||||||
|
"specName": "成人票",
|
||||||
|
"ticketCount": 1,
|
||||||
|
"ticketUnitPrice": 0,
|
||||||
|
"sellPrice": 0,
|
||||||
|
"totalAmount": 0,
|
||||||
|
"plannedCost": 0,
|
||||||
|
"actualCost": 0,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "sourceType 必须是 SCENIC_ASSIGNMENT / ACTIVITY_ASSIGNMENT / MANUAL / CUSTOM_ASSIGNMENT 之一",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:核单 Step2 门票/游玩项目页签查询、保存门票明细时使用。
|
||||||
|
- **手工项目保存**:新增或编辑手工门票行时,`items[].sourceType` 推荐传 `MANUAL`,`scenicAssignmentId` 可传 `null`。
|
||||||
|
- **旧入参兼容**:旧页面继续传 `CUSTOM_ASSIGNMENT` 仍可保存;保存成功后再次查询会返回 `MANUAL`。
|
||||||
|
- **查询结果原样提交**:GET 返回的 `MANUAL` 行可原样进入 PUT 的 `items`。
|
||||||
|
- **未变化范围**:`SCENIC_ASSIGNMENT`、`ACTIVITY_ASSIGNMENT` 的查询和保存语义不变;`null` / 未知来源的查询兜底行为不变。
|
||||||
|
- **不适用场景**:人员费用、住宿、餐食、其他支出接口没有本次契约变化。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| GET `data[].sourceType` | 手工/自定义门票行返回 `CUSTOM_ASSIGNMENT` | 手工/自定义门票行统一返回 `MANUAL` |
|
||||||
|
| GET `data[].sourceTypeName` | 手工/自定义门票行可能按旧来源展示 | 手工/自定义门票行统一返回 `手工项目` |
|
||||||
|
| PUT `items[].sourceType` | 允许 `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `CUSTOM_ASSIGNMENT` | 允许 `SCENIC_ASSIGNMENT` / `ACTIVITY_ASSIGNMENT` / `MANUAL` / `CUSTOM_ASSIGNMENT` |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 查询手工门票行 | 前端需要识别 `CUSTOM_ASSIGNMENT` | 前端按 `MANUAL` 识别手工项目 |
|
||||||
|
| 保存手工门票行 | 前端需要把手工 Tab 转成 `CUSTOM_ASSIGNMENT` | 前端可直接提交 `MANUAL` |
|
||||||
|
| 查询结果原样保存 | GET 的旧来源值与页面手工 Tab 值可能不一致 | GET 结果可原样 PUT |
|
||||||
|
| 旧请求兼容 | 旧 `CUSTOM_ASSIGNMENT` 入参可保存 | 继续可保存,回读统一为 `MANUAL` |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。PUT 继续兼容旧 `CUSTOM_ASSIGNMENT` 入参;GET 只统一手工门票来源的展示值。
|
||||||
|
- **前端是否必须同步上线**:否。旧保存请求仍可用;但前端可清理 `MANUAL` 与 `CUSTOM_ASSIGNMENT` 互转逻辑。
|
||||||
|
- **影响已有数据**:不需要前端处理历史数据;页面以后端返回的 `MANUAL` 为准。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- 如接口回滚,前端需恢复兼容 GET 返回 `CUSTOM_ASSIGNMENT` 的判断。
|
||||||
|
- 回滚后不要把 GET 查询结果中的 `sourceType` 假定为一定可原样提交。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端不要再按 Tab 名称把手工项目强制转换成 `CUSTOM_ASSIGNMENT`;新增手工行可以直接传 `MANUAL`。
|
||||||
|
- 前端如有 `sourceType === "CUSTOM_ASSIGNMENT"` 才展示手工项目的判断,需要同步兼容或改为判断 `MANUAL`。
|
||||||
|
- `CUSTOM_ASSIGNMENT` 仅作为旧保存请求兼容值保留,不应再作为新页面查询展示值。
|
||||||
|
- `sourceTypeName` 是展示字段,保存时可不传;保存后以再次查询结果为准。
|
||||||
|
- 非法 `sourceType` 仍会返回参数非法,不新增兜底保存。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5238](https://git.1814.love:8443/wx/HL/issues/5238)
|
||||||
|
- **PR**: [#5242](https://git.1814.love:8443/wx/HL/pulls/5242)
|
||||||
|
- **Merge commit**: [bfb28a258](https://git.1814.love:8443/wx/HL/commit/bfb28a258)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5244"
|
||||||
|
title: "派单详情分别返回接送说明与通用备注"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@ede7d025e2d6d0d90e570d0bfa5d90f588431842"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端 PR #5246 已合并并部署;前端需在派单 Step1 大交通卡片分别渲染两个字段。"
|
||||||
|
updated_at: "2026-07-25T01:24:40.528Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-25T09:05:18+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务派单详情:分别返回接送说明与通用备注
|
||||||
|
|
||||||
|
> **服务**: `hl-order-service-v3`、`hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5244](https://git.1814.love:8443/wx/HL/issues/5244)
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5246](https://git.1814.love:8443/wx/HL/pulls/5246)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派车看板 → 派单弹窗 Step1 → 大交通
|
||||||
|
|
||||||
|
## 业务口径
|
||||||
|
|
||||||
|
`pickupRemark` 与 `remark` 是两个独立字段,不得合并、互相覆盖或只取其中一个:
|
||||||
|
|
||||||
|
- `pickupRemark`:接机/送机说明;ARRIVAL 展示为“接机说明”,DEPARTURE 展示为“送机说明”。
|
||||||
|
- `remark`:大交通通用备注,展示为“备注”。
|
||||||
|
- 整团 `arrive/depart` 与分批 `batches[]` 使用同一字段口径。
|
||||||
|
- 任一字段为 `null` 或空白时,只隐藏该字段对应的展示行,不影响另一字段。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### 管理后台
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders/:orderId
|
||||||
|
```
|
||||||
|
|
||||||
|
`data.transport.arrive`、`data.transport.depart` 与 `data.transport.batches[]` 均包含:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pickupRemark` | `String/null` | 否 | 接机/送机说明 |
|
||||||
|
| `remark` | `String/null` | 否 | 大交通通用备注;既有字段继续保留 |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"transport": {
|
||||||
|
"arrive": {
|
||||||
|
"direction": "ARRIVAL",
|
||||||
|
"pickupRemark": "到达出口举牌接机",
|
||||||
|
"remark": "航班可能延误"
|
||||||
|
},
|
||||||
|
"depart": {
|
||||||
|
"direction": "DEPARTURE",
|
||||||
|
"pickupRemark": "提前三小时送机",
|
||||||
|
"remark": "请再次确认航站楼"
|
||||||
|
},
|
||||||
|
"batches": [
|
||||||
|
{
|
||||||
|
"direction": "ARRIVAL",
|
||||||
|
"pickupRemark": "分批接机说明",
|
||||||
|
"remark": "分批通用备注"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 内部契约
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/internal/order/orders/:orderId/fleet-detail-context
|
||||||
|
```
|
||||||
|
|
||||||
|
order-v3 → fleet 的共享 `OrderTransportForFleetDTO` 在整团段与分批段均独立传递
|
||||||
|
`pickupRemark`、`remark`。这是兼容性增量:路径、HTTP 方法、既有字段、枚举、错误码及
|
||||||
|
`pickupRequired` 三态口径均不变。
|
||||||
|
|
||||||
|
## 前端展示矩阵
|
||||||
|
|
||||||
|
| 方向/模式 | `pickupRemark` | `remark` | 页面展示 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| ARRIVAL,整团或分批 | 有 | 有 | 分别显示“接机说明”和“备注” |
|
||||||
|
| DEPARTURE,整团或分批 | 有 | 有 | 分别显示“送机说明”和“备注” |
|
||||||
|
| 任一方向 | 有 | 空 | 只显示接机/送机说明 |
|
||||||
|
| 任一方向 | 空 | 有 | 只显示备注 |
|
||||||
|
| 任一方向 | 空 | 空 | 两行均不显示 |
|
||||||
|
|
||||||
|
前端不得根据 `pickupRequired` 推导说明文本,也不得用一个字段回填另一个字段。
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 派单弹窗 Step1 大交通卡片读取 `pickupRemark`,按方向显示“接机说明”或“送机说明”。
|
||||||
|
- [ ] 通用备注继续读取 `remark`,与接机/送机说明分行展示。
|
||||||
|
- [ ] 同时覆盖 `arrive`、`depart`、`batches[]`。
|
||||||
|
- [ ] 对 `null`、空字符串和纯空白字符串使用单字段空态规则。
|
||||||
|
- [ ] 不显示 `travelerIds` 等内部关联字段;既有出行人脱敏规则不变。
|
||||||
|
|
||||||
|
## 契约验证状态
|
||||||
|
|
||||||
|
- OpenAPI/oasdiff:`not_configured`。项目当前未配置稳定 Swagger2 → OAS3 导出与 oasdiff 基线。
|
||||||
|
- 消费者契约/Spring Cloud Contract:`not_configured`。项目当前未配置 SCC。
|
||||||
|
- fallback:源码与 Codemap 影响比对、order-v3 生产者测试、fleet 消费者/Controller 测试以及完整 reactor 验证。
|
||||||
|
- 本次没有临时安装 oasdiff 或 Spring Cloud Contract 依赖。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 合并提交:`ca3c5c7310ddc142398382644a40ab57d951248e`。
|
||||||
|
- 定向生产者/消费者测试:88 项通过。
|
||||||
|
- 影响范围测试:25 个 reactor 模块全部通过。
|
||||||
|
- Fleet 完整验证:2361 项测试,0 失败、0 错误、1 跳过;Spotless 606 个 Java 文件通过。
|
||||||
|
- 测试部署:
|
||||||
|
- order-v3 任务 `a5916436`,8086/8186 双实例成功;
|
||||||
|
- fleet 任务 `a198456e`,8087/8187 双实例成功。
|
||||||
|
- 部署面板与 Nacos 均确认两个服务 2/2 running、healthy、enabled;部署后日志新增错误匹配为 0。
|
||||||
|
- 经测试网关验证真实团单:列表与详情 HTTP/业务码均为 200,`relatedDetailReady=true`;
|
||||||
|
ARRIVAL、DEPARTURE 均同时返回非空且取值不同的 `pickupRemark`、`remark`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 `D:/work2/hl-ui`。
|
||||||
|
- 不修改大交通录入、接送默认值、接送需求聚合、派车状态机或历史数据。
|
||||||
|
- 不新增 DDL,不清理、不回填存量大交通备注。
|
||||||
|
|
||||||
|
> 后端与网关已验证;`frontend_status: pending` 表示等待前端真实领取,不代表页面已实现、发布或验证。
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5245"
|
||||||
|
title: "行程短链预览与同槽位改派解析"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@cd8aff9b0c6499a1dee1b9c3ca00ddbceb4b5aed"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端已部署并完成网关验证;用户验收发现排车页缺少新增车辆槽位入口,前端已退回 claimed 继续修复。"
|
||||||
|
updated_at: "2026-07-26T01:23:05.110Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务:行程短链预览与同槽位改派解析
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5245](https://git.1814.love:8443/wx/HL/issues/5245)
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5249](https://git.1814.love:8443/wx/HL/pulls/5249)、
|
||||||
|
> [wx/HL#5250](https://git.1814.love:8443/wx/HL/pulls/5250)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派车弹窗通知预览、车辆/司机批量选择、派单详情
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
- 通知模板预览中的 `itinerary.url` 会为当前派车组即时创建或复用稳定短链,
|
||||||
|
例如 `https://hr.example.com/s/Dabc1234`,不再把完整 HMAC token URL 或“派车后生成”占位文案放进预览正文。
|
||||||
|
- 既有短链和完整 token 长链在原派车组失效后,只允许解析到同一订单、同一
|
||||||
|
`assignmentSlotId` 的唯一当前有效派车组;跨订单、跨槽位、无有效派单或同槽位存在多个
|
||||||
|
active 派车组时继续返回 `605308`。
|
||||||
|
- 批量派单和详情多司机字段是既有契约,本次明确前端消费口径:一次提交 `items[]`,详情展示
|
||||||
|
`activeAssignments[]`,不得只处理兼容代表字段 `currentAssignment`。
|
||||||
|
- 排车页必须提供“+ 添加车辆槽位”入口。新增槽位不是替换“车辆槽位 1”,而是追加一个可独立
|
||||||
|
选择车辆和司机的草稿槽位;多个槽位统一映射为批量派单 `items[]`。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 本次口径 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POST` | `/admin/fleet/message-templates/<templateId>/render` | 请求新增可选 `assignmentGroupId`;有效派车组即时创建/复用稳定短链;旧前端未传时仅在订单、车辆、司机唯一定位一个 active 组时兼容 |
|
||||||
|
| `GET` | `/app/h5/s/<code>` | 继续生成短时 token 并重定向;同槽位改派后的解析由行程接口完成 |
|
||||||
|
| `GET` | `/app/h5/itinerary/<token>` | 原组失效后仅回退同订单、同稳定槽位的唯一 active 派车组 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/batch` | 既有:按 `items[]` 一次提交多个车辆/司机槽位 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders/<orderId>` | 既有:按 `activeAssignments[]` 返回全部当前有效派车组 |
|
||||||
|
|
||||||
|
## 1. 通知模板预览
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/message-templates/<templateId>/render
|
||||||
|
```
|
||||||
|
|
||||||
|
请求新增可选字段 `assignmentGroupId`,响应结构不变。前端在预览包含
|
||||||
|
`itinerary.url` 或 `itinerary.code` 的模板时,应传入当前派车组 ID;后端仅为兼容旧前端,
|
||||||
|
在 `orderId` + `vehicleId` + `driverId` 唯一定位一个 active 派车组时允许省略:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `orderId` | `string` | 是 | 订单雪花 ID |
|
||||||
|
| `vehicleId` | `string` | 是 | 当前派车组车辆雪花 ID |
|
||||||
|
| `driverId` | `string` | 是 | 当前派车组司机雪花 ID |
|
||||||
|
| `assignmentGroupId` | `string` | 行程预览时强烈建议 | 派车组雪花 ID;取自批量派单响应,多车多司机场景必须按槽位传入 |
|
||||||
|
|
||||||
|
派车组有效时,预览会即时创建或复用该组短链:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
code: 200
|
||||||
|
data:
|
||||||
|
renderedBody: "请查看行程:https://hr.example.com/s/Dabc1234"
|
||||||
|
variablesUsed:
|
||||||
|
- "itinerary.url"
|
||||||
|
```
|
||||||
|
|
||||||
|
未传 `assignmentGroupId` 且订单、车辆、司机无法唯一定位 active 派车组,或显式派车组无效时,
|
||||||
|
`itinerary.url` 使用“行程链接暂不可用,请联系车务确认”,`itinerary.code` 为空字符串。
|
||||||
|
短链配置、注册或数据库失败时接口直接返回错误,不静默降级为占位文案;任何场景都不会回退或
|
||||||
|
暴露完整 HMAC URL。同一派车组通过显式 ID 或兼容定位重复预览、发送、重试时复用同一短链。
|
||||||
|
|
||||||
|
## 2. 同稳定槽位改派后的旧链接
|
||||||
|
|
||||||
|
短链先通过 `/app/h5/s/<code>` 重定向到短时 token;短链与直接保存的完整 token 最终都进入
|
||||||
|
`/app/h5/itinerary/<token>`,因此使用同一组回退规则:
|
||||||
|
|
||||||
|
| token 原派单与当前派单 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| 原派车组仍有 `holding` / `assigned` 服务日 | 使用原派车组当前 active 视图 |
|
||||||
|
| 原组失效,同 `orderId` + 同 `assignmentSlotId` 恰有一个 active 组 | 使用当前改派组 |
|
||||||
|
| 仅有其他订单或其他槽位的 active 组 | `605308` |
|
||||||
|
| 同槽位无 active 组 | `605308` |
|
||||||
|
| 同槽位存在多个 active 组 | `605308`,失败封闭 |
|
||||||
|
|
||||||
|
本次不改变 token 签名、有效期、短链 code 结构或错误码。
|
||||||
|
|
||||||
|
## 3. 前端多车辆/多司机消费
|
||||||
|
|
||||||
|
### 批量派单
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/batch
|
||||||
|
```
|
||||||
|
|
||||||
|
每个已选车辆槽位生成一个 `items[]` 元素,所有槽位一次提交:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
orderId: "2080000000000000001"
|
||||||
|
requirementId: "2080000000000000002"
|
||||||
|
startDate: "2026-07-29"
|
||||||
|
endDate: "2026-07-31"
|
||||||
|
holdMode: 1
|
||||||
|
requestId: "assign-2080000000000000001-v1"
|
||||||
|
items:
|
||||||
|
- fleetItemIndex: 0
|
||||||
|
vehicleId: "2080000000000000101"
|
||||||
|
driverId: "2080000000000000201"
|
||||||
|
- fleetItemIndex: 1
|
||||||
|
vehicleId: "2080000000000000102"
|
||||||
|
driverId: "2080000000000000202"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fleetItemIndex` 从 0 开始,对应需求展开后的稳定车辆槽位。
|
||||||
|
- `vehicleId`、`driverId` 必填;雪花 ID 全程按字符串处理。
|
||||||
|
- `protocolPrice`、`messageTemplateId`、`customBody`、`confirmCrossResident` 是单槽位可选字段。
|
||||||
|
- 前端维护可编辑槽位列表。初始槽位来自当前有效派车组或订单用车需求;点击
|
||||||
|
“+ 添加车辆槽位”后追加一个空白草稿槽位,不得覆盖或复用既有槽位。
|
||||||
|
- 每个草稿槽位独立选择一辆车和一名司机;未提交的新槽位允许删除,已有
|
||||||
|
`holding` / `assigned` 槽位不得被“删除草稿”操作静默撤销。
|
||||||
|
- 进入下一步前校验所有可提交槽位均已选择车辆和司机,并为每个槽位生成唯一
|
||||||
|
`fleetItemIndex`。页面可见槽位数必须等于本次提交的 `items[]` 数量。
|
||||||
|
- 不得为每辆车循环调用单条 `POST /admin/fleet/assignments` 代替批量接口。
|
||||||
|
- 批量响应按 `data.assignments[].assignment.assignmentGroupId` 返回各槽位派车组 ID;
|
||||||
|
前端逐项调用模板预览时传入对应 `assignmentGroupId`,不得只预览代表项。
|
||||||
|
|
||||||
|
### 派单详情
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders/<orderId>
|
||||||
|
```
|
||||||
|
|
||||||
|
按 `data.activeAssignments[]` 渲染每个有效派车组,至少消费:
|
||||||
|
|
||||||
|
| 字段 | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| `assignmentGroupId` | 派车组稳定展示 key |
|
||||||
|
| `assignmentSlotId` | 同一需求车辆槽位的稳定身份 |
|
||||||
|
| `fleetItemIndex` | 槽位顺序 |
|
||||||
|
| `vehicleId` / `vehiclePlate` / `vehicleModel` | 车辆展示 |
|
||||||
|
| `driverId` / `driverName` / `driverPhone` | 司机展示;电话已脱敏 |
|
||||||
|
| `assignmentStatus` / `assignmentStatusLabel` | 当前有效状态 |
|
||||||
|
| `lifecycleStageCode` | 生命周期阶段 |
|
||||||
|
|
||||||
|
`currentAssignment` 仅为兼容代表项,不能用来判断订单只有一辆车或只展示一名司机。
|
||||||
|
`activeAssignments` 无数据时使用空列表空态,不复制代表项凑数。
|
||||||
|
|
||||||
|
## 前端展示矩阵
|
||||||
|
|
||||||
|
| 场景 | 数据源 | 页面行为 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 通知预览传入有效派车组 | `assignmentGroupId` + `renderedBody` 中的 `itinerary.url` | 即时创建或复用并展示稳定短链 |
|
||||||
|
| 旧前端未传派车组但订单、车辆、司机唯一定位 | `orderId` + `vehicleId` + `driverId` | 兼容定位并返回同一稳定短链 |
|
||||||
|
| 派车组缺失、无效或定位不唯一 | “行程链接暂不可用,请联系车务确认” | 展示不可用态,不把文案当可发送链接 |
|
||||||
|
| 已有车辆槽位 | `activeAssignments[]` 或当前排车草稿 | 按稳定槽位逐项展示;允许重选当前槽位的车辆或司机 |
|
||||||
|
| 新增车辆槽位 | 前端草稿槽位列表 | 展示“+ 添加车辆槽位”;每次点击只追加一个空白槽位,不替换已有槽位 |
|
||||||
|
| 新增槽位未选完整 | 草稿槽位的 `vehicleId` / `driverId` | 槽位显示未完成警示,禁用“下一步”;不生成可发送通知 |
|
||||||
|
| 删除未提交槽位 | 前端草稿槽位列表 | 只删除新增且未提交的草稿槽位,不撤销已有有效派车组 |
|
||||||
|
| 一单多个车辆槽位 | `items[]` | 每个槽位各选一辆车和一名司机,一次批量提交;可见槽位数与 `items[]` 数量守恒 |
|
||||||
|
| 详情有多个 active 派车组 | `activeAssignments[]` | 按槽位逐项展示车辆、司机、脱敏电话和状态 |
|
||||||
|
| 详情无 active 派车组 | `activeAssignments=[]` | 展示无有效派单空态 |
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 排车页提供“+ 添加车辆槽位”入口,允许连续新增多个草稿槽位,不得只重选“车辆槽位 1”。
|
||||||
|
- [ ] 每个新增槽位分别选择一辆车和一名司机,并支持删除未提交的草稿槽位。
|
||||||
|
- [ ] “下一步”前校验所有槽位,按页面槽位顺序生成唯一 `fleetItemIndex`,可见槽位与
|
||||||
|
`items[]` 一一对应。
|
||||||
|
- [ ] 统一提交 `POST /admin/fleet/assignments/batch` 的 `items[]`,保留批次级 `requestId`。
|
||||||
|
- [ ] 批量派单响应逐项保存 `assignmentGroupId`;通知预览传入当前槽位的
|
||||||
|
`orderId`、`vehicleId`、`driverId`、`assignmentGroupId`,只把真实短链视为可发送链接。
|
||||||
|
- [ ] 派单详情按 `activeAssignments[]` 展示全部车辆/司机,不只读 `currentAssignment`。
|
||||||
|
- [ ] 司机电话使用后端脱敏值,雪花 ID 始终按字符串处理。
|
||||||
|
- [ ] 覆盖无 active、多 active、短链不可用等空态/失败封闭场景。
|
||||||
|
|
||||||
|
## 前端验收反馈
|
||||||
|
|
||||||
|
- 2026-07-25 用户页面验收:排车页仅显示“车辆槽位 1”,只能在该槽位内重选车辆或司机,
|
||||||
|
无法新增第二个槽位;当前前端提交不满足多车辆、多司机批量派单要求。
|
||||||
|
- 状态因此由 `implemented` 回退为 `claimed`。前端完成新增槽位、逐槽位选择和批量提交后,
|
||||||
|
应填写新的 `frontend_ref` 再迁移为 `implemented`。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- OpenAPI/oasdiff:`not_configured`。项目未配置可复现的 Swagger2 → OAS3 导出与 oasdiff 基线;
|
||||||
|
本次使用源码语义比对、Controller/Service 定向测试与测试网关证据兜底。
|
||||||
|
- 消费者契约/Spring Cloud Contract:`not_required`。本次没有内部 Feign 或共享 Java DTO 变化。
|
||||||
|
- 后端定向测试:41 项通过,0 失败、0 错误、0 跳过。
|
||||||
|
- Fleet Spotless:606 个 Java 文件检查通过。
|
||||||
|
- 完整 reactor `verify`:3209 项测试,0 失败、0 错误、1 跳过;其中 fleet 2373 项,
|
||||||
|
0 失败、0 错误、1 跳过。
|
||||||
|
- 后端 PR #5249 合并提交:`433ef238f09eba2258c996093b1d8cb2309a8e83`。
|
||||||
|
- 后端 PR #5250 合并提交:`d939995bd266f11076eb79ea183e37a968e01afc`。
|
||||||
|
- 测试部署任务:`8eae87b2`;`hl-fleet-service` 的 `8187`、`8087` 两实例均健康。
|
||||||
|
- 测试网关已验证:显式 `assignmentGroupId` 与唯一兼容定位返回同一 7 位短码;
|
||||||
|
重复预览保持稳定,短链 302、H5 JSON 与 HTML 均成功;失效组返回 `605308`,
|
||||||
|
篡改签名返回 `605306`。脱敏证据已回写工单 #5245。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改或部署 `D:/work2/hl-ui`。
|
||||||
|
- 除模板预览请求新增可选 `assignmentGroupId` 外,不删除 API 字段,不改变既有字段类型、
|
||||||
|
必填性或枚举;模板预览响应结构不变。预览在命中有效派车组时会幂等写入短链记录。
|
||||||
|
- 不修改批量派单事务、价格、跨常驻确认、保险或通知冻结规则。
|
||||||
|
- 不新增 DDL,不清理、不回填存量数据。
|
||||||
|
|
||||||
|
> `frontend_status: claimed` 表示前端已领取但仍需修复“新增车辆槽位”;尚未形成可验收的完整实现。
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5253"
|
||||||
|
title: "按车辆记录订单总车费并接入核单"
|
||||||
|
consumer: "multiple"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@e43200d6eede059f82aacfa00de5656b379f8981"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端已合入 dev-v3 并部署测试环境;自动计价、手工总价、缺价阻断、部分改派分段、核单实时合计与冻结均已通过网关验证。前端尚未领取,frontend_status 保持 pending。"
|
||||||
|
updated_at: "2026-07-26T04:47:17.025Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-26T08:57:17+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 按车辆记录订单总车费并接入核单
|
||||||
|
|
||||||
|
车务在每个车辆槽位/派车段记录一个最终总车费;价格日历只提供自动参考,
|
||||||
|
车务可以手工改总价但不回写价格日历。订单核单冻结 Fleet 最终快照并按同一来源计入实际成本。
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: #5253
|
||||||
|
- 后端分支: `feat/5253-vehicle-total-fee`
|
||||||
|
- Changelog 分支: `docs/5253-vehicle-total-fee`
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 表面 | 方法 | 路径 | 变化 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `frontend_api` | `POST` | `/admin/fleet/assignments/candidates` | 入参新增计费服务日;车辆候选新增自动总车费、完整性和缺价日期 |
|
||||||
|
| `frontend_api` | `POST` | `/admin/fleet/assignments` | 入参新增单车最终总车费、调整原因、计费日/免费日口径;响应回显费用快照 |
|
||||||
|
| `frontend_api` | `POST` | `/admin/fleet/assignments/batch` | 每辆车分别提交最终总车费与调整原因,并共享计费日/免费日口径 |
|
||||||
|
| `frontend_api` | `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | 最终确认前可补/改该派车段总车费;响应回显最终费用 |
|
||||||
|
| `frontend_api` | `POST` | `/admin/fleet/assignments/:assignmentId/change` | 改派新段与原车保留段分别记录总车费 |
|
||||||
|
| `frontend_api` | `GET` | `/admin/fleet/board/orders/:orderId` | 每个派车组新增自动参考、最终总价、来源、调整原因、计费/免费日期与冻结态 |
|
||||||
|
| `frontend_api` | `GET` | `/v3/admin/order/:orderId/settlement/vehicle-fees` | 新增核单车辆总车费明细;冻结前读 Fleet,冻结后读 Order 快照 |
|
||||||
|
| `frontend_api` | `POST` | `/v3/admin/order/:orderId/settlement/vehicle-fees/confirm` | 新增确认并冻结车辆总车费 |
|
||||||
|
| `frontend_api` | `POST` | `/v3/admin/order/:orderId/settlement/step6/submit` | 响应新增 `vehicleCost` |
|
||||||
|
| `frontend_api` | `GET` | `/v3/admin/order/:orderId/settlement/summary` | 响应新增 `vehicleCost` |
|
||||||
|
| `internal_feign` / `shared_java` | `GET` | `/internal/fleet/orders/:orderId/vehicle-fees` | 新增 Fleet→Order 的按派车组只读费用快照 |
|
||||||
|
|
||||||
|
路径中的 `:orderId`、`:assignmentId` 表示既有雪花 ID 字符串传输约定。
|
||||||
|
|
||||||
|
## 字段与行为
|
||||||
|
|
||||||
|
### 派车候选与写接口
|
||||||
|
|
||||||
|
- `AssignmentCandidateReqVO` 新增 `chargeableServiceDates`;不传默认全部服务日,
|
||||||
|
空数组表示全部免费。
|
||||||
|
- 候选车辆新增:
|
||||||
|
- `autoVehicleFeeTotal`:价格日历覆盖日期的小计,金额按字符串消费;
|
||||||
|
- `vehicleFeePriceComplete`:是否覆盖全部计费服务日;
|
||||||
|
- `missingVehicleFeeDates`:缺价的计费服务日;
|
||||||
|
- 旧字段 `protocolPrice` 保留但已废弃,新页面不得用它计算总车费。
|
||||||
|
- 创建/批量创建/改派新增 `vehicleFeeTotal`、`vehicleFeeAdjustmentReason`、
|
||||||
|
`chargeableServiceDates`、`vehicleFeeWaiverReason`、
|
||||||
|
`confirmAllServiceDatesFree`。
|
||||||
|
- 部分改派额外新增 `retainedVehicleFeeTotal` 与
|
||||||
|
`retainedVehicleFeeAdjustmentReason`。原车保留段和替换段分别录入,
|
||||||
|
后端不按天数比例拆分。
|
||||||
|
- 最终确认新增 `vehicleFeeTotal` 与 `vehicleFeeAdjustmentReason`。
|
||||||
|
- 创建、改派和确认响应新增 `vehicleFeeAutoTotal`、
|
||||||
|
`vehicleFeeAutoComplete`、`vehicleFeeTotal`、`vehicleFeeSource`、
|
||||||
|
`vehicleFeeAdjustmentReason`;金额字段按字符串消费。
|
||||||
|
|
||||||
|
### 看板详情
|
||||||
|
|
||||||
|
每个派车组新增:
|
||||||
|
|
||||||
|
- `chargeableServiceDates`、`freeServiceDates`、`vehicleFeeWaiverReason`;
|
||||||
|
- `vehicleFeeAutoTotal`、`vehicleFeeAutoComplete`;
|
||||||
|
- `vehicleFeeTotal`、`vehicleFeeSource`(`AUTO`、`MANUAL`、`INCOMPLETE`);
|
||||||
|
- `vehicleFeeAdjustmentReason`、`vehicleFeeFrozen`。
|
||||||
|
|
||||||
|
派车完成后 `vehicleFeeFrozen=true`,不得原地编辑费用;只能走改派形成新的费用段。
|
||||||
|
订单核单完成后禁止继续改派。
|
||||||
|
|
||||||
|
### 核单车辆总车费
|
||||||
|
|
||||||
|
`SettlementVehicleFeesRespVO`:
|
||||||
|
|
||||||
|
- 顶层:`orderId`、`frozen`、`totalVehicleFee`、`items`;
|
||||||
|
- 每项按一个派车组返回车辆、司机、日期、计费/免费日期、自动参考、
|
||||||
|
最终总价、来源、调整审计和 `settlementReady`;
|
||||||
|
- `POST .../confirm` 仅接受全部当前派车组 `settlementReady=true`
|
||||||
|
且最终总车费非空的快照,成功后 `frozen=true`;
|
||||||
|
- 两辆车返回两项并分别计费,`totalVehicleFee` 是各项 `vehicleFeeTotal` 之和;
|
||||||
|
- `vehicleCost` 同步进入核单提交与汇总实际成本,司机费用只保留司机额外费用,
|
||||||
|
避免车辆基础服务费重复计入。
|
||||||
|
|
||||||
|
### 内部契约
|
||||||
|
|
||||||
|
新增共享 DTO `OrderVehicleFeeSnapshotDTO`,包含需求、派车组、稳定车辆槽位、
|
||||||
|
车辆/司机、服务区间、计费/免费日期、自动参考、最终总价、调整审计和
|
||||||
|
`settlementReady`。Order 必须按当前 `requirementId` 精确筛选,
|
||||||
|
不得跨需求或跨改派段合并。
|
||||||
|
|
||||||
|
## 契约影响文件
|
||||||
|
|
||||||
|
- `hl-common/hl-common-core/src/main/java/com/hulalv/common/dto/fleet/OrderVehicleFeeSnapshotDTO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/controller/OrderDriverVehicleInternalController.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/AssignmentCandidateReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/AssignmentCandidateRespVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/AssignmentWriteRespVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/BatchCreateAssignmentReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/ChangeAssignmentReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/ChangeAssignmentRespVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/ConfirmReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/ConfirmRespVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/CreateAssignmentReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/board/vo/BoardOrderDetailVO.java`
|
||||||
|
- `hl-fleet-service/src/test/java/com/hulalv/fleet/assignment/controller/OrderDriverVehicleInternalControllerTest.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/fleet/feign/FleetDriverVehicleFeignClient.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/fleet/feign/FleetDriverVehicleFeignFallbackFactory.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/settlement/controller/admin/SettlementController.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/settlement/controller/admin/vo/SettlementSubmitRespVO.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/settlement/controller/admin/vo/SettlementSummaryRespVO.java`
|
||||||
|
- `hl-order-service-v3/src/main/java/com/hulalv/order/settlement/controller/admin/vo/SettlementVehicleFeesRespVO.java`
|
||||||
|
- `hl-order-service-v3/src/test/java/com/hulalv/order/fleet/feign/FleetDriverVehicleFeignContractTest.java`
|
||||||
|
- `hl-order-service-v3/src/test/java/com/hulalv/order/settlement/controller/admin/SettlementControllerTest.java`
|
||||||
|
|
||||||
|
## 前端/调用方动作
|
||||||
|
|
||||||
|
- 派车弹窗按“每辆车一个总车费”展示和提交;不要展示或要求车务填写每日价格。
|
||||||
|
- 默认展示后端 `autoVehicleFeeTotal`。缺价时用
|
||||||
|
`vehicleFeePriceComplete=false` 和 `missingVehicleFeeDates` 提示,
|
||||||
|
车务仍必须填写该车最终总车费及调整原因后才能直接派定/最终确认。
|
||||||
|
- 车务手改只提交 `vehicleFeeTotal`,不得写回车型价格日历。
|
||||||
|
- 收费日/免费日仅作为记录与核单依据;全部免费时必须提交免费原因和二次确认。
|
||||||
|
- 多车订单为每个车辆槽位分别编辑总车费,不提供订单级总价输入框。
|
||||||
|
- 已派定/已完成段按 `vehicleFeeFrozen` 禁用直接编辑,只保留改派入口;
|
||||||
|
已核单订单同时禁用改派。
|
||||||
|
- 核单页先查询 `GET .../vehicle-fees` 展示逐车明细和合计,
|
||||||
|
再调用 `POST .../vehicle-fees/confirm` 冻结;冻结后只读。
|
||||||
|
- 所有雪花 ID 和金额字段按字符串处理,禁止转 JavaScript `number`。
|
||||||
|
- `frontend_status` 保持 `pending`;真实前端领取后使用工作流迁移到 `claimed`。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- Fleet producer:`mvn -pl hl-fleet-service -am verify` 通过;
|
||||||
|
`mvn -pl hl-fleet-service -am spotless:check` 通过。
|
||||||
|
- Order consumer:`mvn -pl hl-order-service-v3 -am verify` 通过。
|
||||||
|
- 契约定向测试:
|
||||||
|
`OrderDriverVehicleInternalControllerTest`、
|
||||||
|
`FleetDriverVehicleFeignContractTest`、
|
||||||
|
`SettlementControllerTest` 通过。
|
||||||
|
- 费用行为覆盖:自动合计、缺价、手工覆盖、全免费、部分改派、
|
||||||
|
派定后冻结、核单冻结、首个计费日只入账一次及并发门禁均有测试。
|
||||||
|
- OpenAPI/oasdiff:`not_configured`;fallback 证据见任务胶囊
|
||||||
|
`openapi-fallback.md`。
|
||||||
|
- producer/consumer 或 Spring Cloud Contract:`not_configured`;
|
||||||
|
fallback 证据见任务胶囊 `consumer-contract-fallback.md`。
|
||||||
|
- 测试部署:Fleet Deploy Panel 任务 `5131f604` 成功,8087/8187
|
||||||
|
双实例健康;补充修复 PR `wx/HL#5261` 已合入 `dev-v3`。
|
||||||
|
- 网关验证:自动计价完整;缺价返回 `605044` 阻断;多车分别保存
|
||||||
|
`1720.00` 与 `1234.56`;部分改派同一稳定槽位拆为原车保留段
|
||||||
|
`900.00` 与新车接替段 `1100.00`,核单实时合计 `2000.00`;
|
||||||
|
核单确认后冻结快照只读。`gateway_status=verified`。
|
||||||
|
- 测试数据:隔离订单已按 `hl-data-cleanup/v1` manifest
|
||||||
|
`5253-cfcc4c8eea2b` 事务清理,全部目标后置计数为 0,价格日历未变化。
|
||||||
|
- 兼容性结论:现有路径均为增量字段;旧 `protocolPrice` 继续返回,
|
||||||
|
但新页面必须改用每车总费用字段。
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5255"
|
||||||
|
title: "已派车恢复改派与行程单入口更正"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@f9251d0ffabf13641630588733d103e00f35e85d"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端已部署并完成网关验证;前端需恢复已派车改派按钮,并将看板行程单入口直接复用订单详情现有打印实现。本文件覆盖 #5186 的已派车禁用改派及复制司机 H5 链接口径。"
|
||||||
|
updated_at: "2026-07-26T03:32:07.805Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务:已派车恢复改派与行程单入口更正
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5255](https://git.1814.love:8443/wx/HL/issues/5255)
|
||||||
|
>
|
||||||
|
> **后端 PR**: [wx/HL#5258](https://git.1814.love:8443/wx/HL/pulls/5258)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派单看板操作区、既有派车/改派弹窗、行程单打印入口
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
- 当前有效派单状态为 `assigned` 时,派单看板列表现在返回 `canAssign=true`,并继续下发
|
||||||
|
`CHANGE_ASSIGNMENT`。车辆已经配好后,只要派单尚未进入 `completed/canceled` 终态,车务仍可
|
||||||
|
随时进入既有改派流程,再次更换当前车辆槽位的车辆、司机或两者。
|
||||||
|
- `holding/holding_urgent` 的改派能力不变;`completed/canceled` 继续不可改派。
|
||||||
|
- 看板操作区的行程单入口统一为“打印行程单”,直接复用订单详情已有的
|
||||||
|
`PrintItineraryModal.vue` 和 `getPrintItinerary(orderId)`,不新写打印组件、打印接口或打印数据模型。
|
||||||
|
|
||||||
|
## ⚠️ 对 #5186 的口径更正
|
||||||
|
|
||||||
|
本文件取代
|
||||||
|
`23_5186_排车中订单恢复派车派人入口-修改接口-管理后台.md`
|
||||||
|
中的以下两项旧交接:
|
||||||
|
|
||||||
|
1. #5186 响应矩阵把 `assigned` 与终态一起写成 `canAssign=false`。该口径已失效:
|
||||||
|
`assigned` 现在允许改派,只有 `completed/canceled` 禁止改派。
|
||||||
|
2. #5186 要求看板复制 `currentAssignment.itineraryUrl`,并明确“不复用
|
||||||
|
`PrintItineraryModal.vue`”。该口径已撤销:看板不再把“复制司机 H5 链接”作为本入口的实现,
|
||||||
|
而是直接复用订单详情现有打印代码。
|
||||||
|
|
||||||
|
#5186 中与本次两项更正无关的候选上下文、稳定槽位和待确认流程说明继续有效。本次也不删除后端
|
||||||
|
司机 H5 短链能力;只是看板这个用户入口不再消费它。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 本次口径 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/admin/fleet/board/orders` | 既有字段语义更正:有效 `assigned` 记录返回 `canAssign=true`,且 `availableActionCodes` 包含 `CHANGE_ASSIGNMENT` |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/change` | 既有改派接口,路径和请求/响应契约不变;继续按稳定车辆槽位与生效日原子替换 |
|
||||||
|
| `GET` | `/v3/admin/order/<orderId>/print-itinerary` | 订单详情已有打印接口,本次无后端变更;看板直接复用现有前端调用 |
|
||||||
|
|
||||||
|
## 1. 派单看板动作语义
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders` 的字段名、类型和必填性均未变化。前端按后端动作字段渲染:
|
||||||
|
|
||||||
|
| `assignmentStatus` | `canAssign` | `availableActionCodes` | 看板主操作 |
|
||||||
|
| --- | ---: | --- | --- |
|
||||||
|
| `unassigned/unassigned_urgent` | `true` | 包含 `ASSIGN` | “派车派人”,进入既有首次派车流程 |
|
||||||
|
| `holding/holding_urgent` | `true` | 包含 `CHANGE_ASSIGNMENT` | “改派”,进入既有改派流程 |
|
||||||
|
| `assigned` | `true` | 包含 `CHANGE_ASSIGNMENT` | “改派”,已配车后仍可再次调整 |
|
||||||
|
| `completed/canceled` | `false` | 不包含 `CHANGE_ASSIGNMENT` | 不显示改派入口 |
|
||||||
|
|
||||||
|
前端不得再把“已有车辆/司机”或 `assignmentStatus === 'assigned'` 当作隐藏改派按钮的条件。
|
||||||
|
入口首先以 `canAssign === true` 判断是否可进入,再以 `availableActionCodes` 区分首次派车或改派。
|
||||||
|
|
||||||
|
## 2. 继续复用既有按槽位改派
|
||||||
|
|
||||||
|
点击“改派”后继续使用当前派车弹窗和现有
|
||||||
|
`POST /admin/fleet/assignments/<assignmentId>/change`:
|
||||||
|
|
||||||
|
- `assignmentId` 取用户选中的 `activeAssignments[]` 派车组/槽位,不得固定取代表项后误改其它车;
|
||||||
|
- 生效日及之后只替换该稳定 `assignmentSlotId` 的逐日切片;
|
||||||
|
- 同一订单其它车辆槽位保持不变;
|
||||||
|
- 可只换车辆、只换司机或同时替换;
|
||||||
|
- 成功后重新拉取看板列表与详情,不缓存旧的 `canAssign`、动作码或派车组数据;
|
||||||
|
- 后端返回 `ORDER_HAS_OTHER_VEHICLES` 等既有警告时继续沿用当前强提示。
|
||||||
|
|
||||||
|
本次没有新增写接口,也没有修改改派请求字段、错误码或事务规则。
|
||||||
|
|
||||||
|
## 3. 行程单入口直接复用订单详情打印代码
|
||||||
|
|
||||||
|
管理后台现有可复用实现位于:
|
||||||
|
|
||||||
|
- `src/views/order-v2/detail/modals/PrintItineraryModal.vue`
|
||||||
|
- `src/api/orderV2.js` 的 `getPrintItinerary(orderId)`
|
||||||
|
- 既有接口 `GET /v3/admin/order/<orderId>/print-itinerary`
|
||||||
|
|
||||||
|
看板按以下方式接入:
|
||||||
|
|
||||||
|
- 操作文案统一为“打印行程单”,点击后把当前订单的字符串 `orderId` 传给现有
|
||||||
|
`PrintItineraryModal`;
|
||||||
|
- 打印预览、加载、错误提示、页面排版和打印动作全部沿用订单详情现有组件;
|
||||||
|
- 可抽取共享挂载点或直接复用组件,但不得复制组件源码形成第二套打印实现;
|
||||||
|
- 不新增 fleet 打印 API,不在前端重新组装行程节点、费用、住宿、大交通或每日行程;
|
||||||
|
- 不调用看板详情去读取 `currentAssignment.itineraryUrl`,不执行剪贴板复制,也不继续使用
|
||||||
|
`ItinerarySendSheet.vue` 承载这个入口;
|
||||||
|
- 无 `orderId` 时不打开弹窗、不提示成功,沿用现有缺少订单上下文的错误态。
|
||||||
|
|
||||||
|
## 前端展示矩阵
|
||||||
|
|
||||||
|
| 页面区域 | 展示内容 | 数据来源 | 包含/排除状态 | 空数据表现 | 标签与颜色 | 守恒规则 |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| 派车看板操作区 | 派车派人 | `availableActionCodes` 含 `ASSIGN` | 包含待派车;排除终态 | 无候选沿用现有提示 | 沿用现有待派状态色 | 只创建目标槽位 |
|
||||||
|
| 派车看板操作区 | 改派 | `availableActionCodes` 含 `CHANGE_ASSIGNMENT` | 包含待确认;排除终态 | 无有效派单不显示 | 沿用现有待确认状态色 | 只替换选中槽位 |
|
||||||
|
| 派车看板操作区 | 改派 | `canAssign=true` 且含 `CHANGE_ASSIGNMENT` | 包含已派车且未完结;排除已完结/已取消 | 无剩余可改服务日时展示既有后端错误 | 沿用现有已派状态色 | 配完仍可再次改派,其他槽位不变 |
|
||||||
|
| 派车看板操作区 | 不显示改派 | 无 `CHANGE_ASSIGNMENT` | 仅已完结/已取消 | 不展示按钮 | 沿用终态灰 | 不恢复终态 |
|
||||||
|
| 派车看板操作区 | 打印行程单 | 订单 `orderId` 与既有打印接口 | 包含可查看订单;排除无订单 ID | 沿用现有打印弹窗加载/错误态 | 沿用现有打印按钮样式 | 只复用一套 `PrintItineraryModal.vue` |
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] `assigned + canAssign=true + CHANGE_ASSIGNMENT` 显示“改派”,点击进入既有改派流程。
|
||||||
|
- [ ] `holding/holding_urgent` 继续显示“改派”,首次待派仍显示“派车派人”。
|
||||||
|
- [ ] `completed/canceled` 不显示改派入口。
|
||||||
|
- [ ] 用户选择哪个 `activeAssignments[]` 槽位,就把该槽位的 `assignmentId` 传给既有 change 接口。
|
||||||
|
- [ ] 改派成功后刷新列表与详情,当前槽位更新,其他槽位数量和身份保持不变。
|
||||||
|
- [ ] 将看板行程单操作统一为“打印行程单”,直接复用订单详情
|
||||||
|
`PrintItineraryModal.vue + getPrintItinerary(orderId)`。
|
||||||
|
- [ ] 不新增打印组件/API,不读取或复制 `currentAssignment.itineraryUrl`,不继续使用
|
||||||
|
`ItinerarySendSheet.vue` 实现该入口。
|
||||||
|
- [ ] 覆盖无订单 ID、打印接口失败和打印数据空态,全部沿用既有打印弹窗行为。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端提交:`9ea7d555a078101cbf57d1571d106cd0ec2863af`;PR #5258。
|
||||||
|
- `BoardControllerTest + BoardOrderServiceTest` 共 54 项通过。
|
||||||
|
- `AssignmentServiceTest#change_directFromEffectiveDate_replacesDailySlicesAndWarnsOtherVehicle`
|
||||||
|
通过,验证只替换目标稳定槽位,其它车辆槽位不取消。
|
||||||
|
- `mvn -pl hl-fleet-service spotless:check` 通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify` 通过;fleet 207 套件、2,373 项测试,
|
||||||
|
0 失败、0 错误、1 个既有跳过。
|
||||||
|
- 测试环境已滚动部署 `hl-fleet-service`;Nacos `test` 命名空间的 8087、8187 两实例健康。
|
||||||
|
- 测试网关查询 `assigned` 返回 4 条真实记录,全部为
|
||||||
|
`canAssign=true + CHANGE_ASSIGNMENT`,HTTP/code 均为 200。
|
||||||
|
- 测试环境当时没有 `holding/holding_urgent/completed/canceled` 样本;这些状态不冒充网关实测,
|
||||||
|
由已通过的服务状态矩阵与 Controller JSON 测试覆盖。
|
||||||
|
- 网关证据 SHA-256:
|
||||||
|
`1409d22ce9fc2c86101d9f811fef867e0493f177191fb8ac5ee30eb4427185e2`。
|
||||||
|
- OpenAPI/oasdiff:`not_configured`。字段名、类型和 requiredness 未变;仓库没有可复现的
|
||||||
|
Swagger2 → OAS3 导出链且未安装 `oasdiff`,已用 Controller JSON、服务状态矩阵和当前
|
||||||
|
`hl-ui` 消费源码做人工回退核对。
|
||||||
|
- 消费者契约/Spring Cloud Contract:`not_required`。本次没有内部 Feign 或共享 Java DTO 变化。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改或部署 `D:/work2/hl-ui`;`frontend_status` 保持 `pending`,页面实现和发布独立流转。
|
||||||
|
- 不删除司机 H5 行程短链、签名 token 或公开行程接口;仅更正看板入口的前端消费方式。
|
||||||
|
- 不新增/删除 API 字段,不改变字段类型、必填性、错误码或雪花 ID 的字符串消费要求。
|
||||||
|
- 不修改首次派车、待确认推进、取消派单、司机确认、资源占用或通知冻结规则。
|
||||||
|
- 不新增 DDL,不迁移、清理或回填数据。
|
||||||
@ -0,0 +1,281 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5257"
|
||||||
|
title: "车务看板按用车需求聚合多车型槽位"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@594dfa44b497b40a1e75c81b3e60abba8deb6137"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-26T11:13:51+08:00"
|
||||||
|
status_note: "后端 PR #5259 已合并为 dev-v3@01bf9c627,并重新部署测试环境及通过网关复验;本契约明确替代 #5216 的按槽位卡片维度。前端尚未认领,需按需求卡消费 assignmentSlots。"
|
||||||
|
updated_at: "2026-07-26T03:40:29.152Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务看板按用车需求聚合多车型槽位
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: [wx/HL#5257](https://git.1814.love:8443/wx/HL/issues/5257)
|
||||||
|
- 服务: `hl-fleet-service`
|
||||||
|
- 前端仓库/分支: `mmg/hl-ui` / `v2.1`
|
||||||
|
- 影响范围: 车务管理 → 派车看板列表、需求卡和需求详情
|
||||||
|
- 被本契约替代的旧口径:
|
||||||
|
[#5216 派车看板补充槽位接送路线与就绪摘要](./24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-管理后台.md)
|
||||||
|
中“每张卡对应一个稳定车辆槽位”的维度说明
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
同一订单的一条当前有效用车需求可能同时需要多种车型、多个车辆槽位。例如
|
||||||
|
`SUV×1 + 商务车×1` 是 **1 条用车需求、合计 2 辆车**,不能显示成 2 条“用车需求”。
|
||||||
|
|
||||||
|
`GET /admin/fleet/board/orders` 的 `data.records[]` 从派车槽位粒度调整为当前
|
||||||
|
active `requirementId` 粒度:
|
||||||
|
|
||||||
|
- 同一 `requirementId` 只返回一条 record。
|
||||||
|
- `requiredVehicles[]` 展示全部车型组及数量。
|
||||||
|
- `assignmentProgress.totalSlots` 展示合计需要的车辆数。
|
||||||
|
- 新增 `assignmentSlots[]`,完整保留逐辆派车身份、状态、车辆和司机。
|
||||||
|
- 原顶层单槽位字段兼容保留,统一表示当前最需要处理的代表槽位。
|
||||||
|
- 状态、车型或司机筛选命中需求内任一槽位时,需求只返回一次。
|
||||||
|
- 后端先按需求聚合,再排序和分页;`total` 与汇总接口不再按槽位重复计数。
|
||||||
|
|
||||||
|
请求参数、路径、HTTP method、响应包络、错误码、数据库和派车执行语义均不变。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": "26-5256",
|
||||||
|
"orderId": "2080200000000000000",
|
||||||
|
"requirementId": "2080200000000000900",
|
||||||
|
"requiredVehicles": [
|
||||||
|
{
|
||||||
|
"vehicleType": "suv",
|
||||||
|
"categoryLabel": "SUV",
|
||||||
|
"seats": 5,
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"vehicleType": "mpv",
|
||||||
|
"categoryLabel": "商务车",
|
||||||
|
"seats": 7,
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assignmentProgress": {
|
||||||
|
"totalSlots": 2,
|
||||||
|
"unassignedSlots": 2,
|
||||||
|
"holdingSlots": 0,
|
||||||
|
"assignedSlots": 0,
|
||||||
|
"completedSlots": 0,
|
||||||
|
"canceledSlots": 0
|
||||||
|
},
|
||||||
|
"assignmentStatus": "unassigned",
|
||||||
|
"assignmentId": "2080200000000000001",
|
||||||
|
"assignmentSlotId": "2080200000000000101",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"canAssign": true,
|
||||||
|
"canRejectRequirement": true,
|
||||||
|
"assignmentSlots": [
|
||||||
|
{
|
||||||
|
"assignmentId": "2080200000000000001",
|
||||||
|
"assignmentGroupId": "2080200000000000201",
|
||||||
|
"assignmentSlotId": "2080200000000000101",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"slotSummary": {
|
||||||
|
"requiredVehicleType": "suv",
|
||||||
|
"requiredVehicleTypeLabel": "SUV",
|
||||||
|
"requiredSeats": 5
|
||||||
|
},
|
||||||
|
"baseAssignmentStatus": "unassigned",
|
||||||
|
"assignmentStatus": "unassigned",
|
||||||
|
"assignmentStatusLabel": "待派车",
|
||||||
|
"availableActionCodes": ["ASSIGN"],
|
||||||
|
"vehiclePlate": null,
|
||||||
|
"vehicleModel": null,
|
||||||
|
"vehicleSeats": null,
|
||||||
|
"driverName": null,
|
||||||
|
"driverPhone": null,
|
||||||
|
"urgentBadge": null,
|
||||||
|
"canAssign": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assignmentId": "2080200000000000002",
|
||||||
|
"assignmentGroupId": "2080200000000000202",
|
||||||
|
"assignmentSlotId": "2080200000000000102",
|
||||||
|
"fleetItemIndex": 1,
|
||||||
|
"slotSummary": {
|
||||||
|
"requiredVehicleType": "mpv",
|
||||||
|
"requiredVehicleTypeLabel": "商务车",
|
||||||
|
"requiredSeats": 7
|
||||||
|
},
|
||||||
|
"baseAssignmentStatus": "unassigned",
|
||||||
|
"assignmentStatus": "unassigned",
|
||||||
|
"assignmentStatusLabel": "待派车",
|
||||||
|
"availableActionCodes": ["ASSIGN"],
|
||||||
|
"vehiclePlate": null,
|
||||||
|
"vehicleModel": null,
|
||||||
|
"vehicleSeats": null,
|
||||||
|
"driverName": null,
|
||||||
|
"driverPhone": null,
|
||||||
|
"urgentBadge": null,
|
||||||
|
"canAssign": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增字段 `assignmentSlots[]`
|
||||||
|
|
||||||
|
所有雪花 ID 必须按字符串消费。
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `assignmentId` | `String` | 否 | 逐槽位派车/改派操作使用的派单 ID |
|
||||||
|
| `assignmentGroupId` | `String` | 否 | 同一派车组 ID |
|
||||||
|
| `assignmentSlotId` | `String` | 否 | 跨逐日切片稳定的车辆槽位 ID |
|
||||||
|
| `fleetItemIndex` | `Integer` | 否 | 当前需求 `fleet[]` 展开后的槽位序号,0 起 |
|
||||||
|
| `slotSummary` | `Object` | 否 | 该槽位车型、座位、服务范围和整单槽位数 |
|
||||||
|
| `baseAssignmentStatus` | `String` | 否 | 基础落库态 |
|
||||||
|
| `assignmentStatus` | `String` | 否 | 当前有效状态,可含紧急派生态 |
|
||||||
|
| `assignmentStatusLabel` | `String` | 否 | 后端中文状态标签 |
|
||||||
|
| `lifecycleStageCode/lifecycleStageLabel` | `String` | 否 | 该槽位生命周期阶段 |
|
||||||
|
| `currentStep` | `Integer` | 否 | 该槽位当前步骤 |
|
||||||
|
| `availableActionCodes` | `String[]` | 否 | 该槽位可用动作;需求级驳回不在此数组 |
|
||||||
|
| `vehiclePlate/vehicleModel` | `String` | 是 | 已派车辆信息 |
|
||||||
|
| `vehicleSeats` | `Integer` | 是 | 已派车辆座位数 |
|
||||||
|
| `vehicleFleetTeamId` | `String` | 是 | 已派车辆所属车队 ID |
|
||||||
|
| `vehicleFleetTeamName/vehicleFleetTeamType/vehicleFleetTeamSettleType` | `String` | 是 | 已派车辆所属车队信息 |
|
||||||
|
| `driverName` | `String` | 是 | 已派司机姓名 |
|
||||||
|
| `driverPhone` | `String` | 是 | 已脱敏司机手机号 |
|
||||||
|
| `urgentBadge` | `String` | 是 | `T-N` 或 `Nh`;非紧急为 `null` |
|
||||||
|
| `canAssign` | `Boolean` | 否 | 该槽位是否可进入派车/改派流程 |
|
||||||
|
|
||||||
|
## 顶层兼容字段
|
||||||
|
|
||||||
|
以下既有字段没有删除,旧前端继续读取不会报错:
|
||||||
|
|
||||||
|
- `assignmentId/assignmentGroupId/assignmentSlotId/fleetItemIndex`
|
||||||
|
- `slotSummary`
|
||||||
|
- `assignmentStatus/assignmentStatusLabel`
|
||||||
|
- `lifecycleStageCode/lifecycleStageLabel/currentStep/availableActionCodes`
|
||||||
|
- `currentVehicle*`
|
||||||
|
- `currentDriver*`
|
||||||
|
- `urgentBadge/canAssign/canRejectRequirement`
|
||||||
|
|
||||||
|
它们统一指向代表槽位,选择优先级为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
未派 → 排车中 → 已派 → 已完成 → 已取消
|
||||||
|
```
|
||||||
|
|
||||||
|
同级按 `fleetItemIndex/assignmentSlotId/assignmentId` 稳定排序。部分已派时,顶层仍指向
|
||||||
|
未派槽位,保证原“派车派人”入口可继续派下一辆;所有槽位的真值以
|
||||||
|
`assignmentSlots[]` 和 `assignmentProgress` 为准。
|
||||||
|
|
||||||
|
`canRejectRequirement` 与需求级驳回动作只读外层 record。任一槽位已进入
|
||||||
|
`holding/assigned` 时,外层不会错误开放驳回;槽位级 `availableActionCodes` 不包含需求级驳回。
|
||||||
|
|
||||||
|
## 筛选、分页和汇总
|
||||||
|
|
||||||
|
- 状态多选:任一槽位命中即返回该需求一次。
|
||||||
|
- 车型多选:任一需求槽位命中即返回该需求一次,`requiredVehicles[]` 仍保留全部车型。
|
||||||
|
- 司机筛选/关键词:任一槽位司机命中即返回该需求一次。
|
||||||
|
- 混合状态:顶层状态取代表槽位状态;完整状态分布读取 `assignmentProgress` 和
|
||||||
|
`assignmentSlots[]`。
|
||||||
|
- 空态:无当前有效需求或无 fleet 看板候选时返回 `records=[]/total=0`,不按人数合成虚假槽位。
|
||||||
|
- 顺序:先聚合为唯一 requirement record,再确定性排序和分页。
|
||||||
|
- `data.total`:查询范围内唯一 active `requirementId` 数,不是车辆槽位数。
|
||||||
|
- `GET /admin/fleet/board/summary`:状态和今日出团数使用相同需求粒度,不重复计数。
|
||||||
|
|
||||||
|
守恒关系:
|
||||||
|
|
||||||
|
```text
|
||||||
|
assignmentProgress.totalSlots
|
||||||
|
== unassignedSlots
|
||||||
|
+ holdingSlots
|
||||||
|
+ assignedSlots
|
||||||
|
+ completedSlots
|
||||||
|
+ canceledSlots
|
||||||
|
|
||||||
|
assignmentProgress.totalSlots
|
||||||
|
== sum(requiredVehicles[].count)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 看板列表对每个 `records[]` 只渲染一张需求卡,不再按
|
||||||
|
`fleetItemIndex/assignmentSlotId` 拆卡,也不得展开 `assignmentSlots[]` 生成额外卡片。
|
||||||
|
- [ ] 列表 row key 优先使用 `requirementId`;仅兼容历史空值时回退 `id/orderId`。
|
||||||
|
- [ ] 需求卡展示 `requiredVehicles[]` 的全部车型组,并显示
|
||||||
|
`assignmentProgress.totalSlots` 为“需要 N 辆车”。
|
||||||
|
- [ ] 需求详情遍历 `assignmentSlots[]` 展示全部车辆槽位、各自状态和已派车辆/司机。
|
||||||
|
- [ ] 顶层按钮可继续使用代表槽位字段;逐辆操作必须使用所选
|
||||||
|
`assignmentSlots[i].assignmentId/fleetItemIndex/assignmentSlotId`,不得复用其他槽位 ID。
|
||||||
|
- [ ] 需求级驳回只使用外层 `canRejectRequirement/availableActionCodes`,不从槽位数组推断。
|
||||||
|
- [ ] 混合状态显示以 `assignmentProgress` 为准,不用顶层单一状态覆盖所有槽位。
|
||||||
|
- [ ] 继续按既有稳定状态 token 映射颜色,不按中文文案判断色值。
|
||||||
|
- [ ] 覆盖单车型×1、多车型各×1、同车型×2、部分已派、全已派、完结/取消和空列表场景。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改用车需求 `fleet[]` 的业务语义。
|
||||||
|
- 不合并、删除或改写真实 `fleet_assignment`;派车仍逐辆、逐槽位执行。
|
||||||
|
- 不修改派车、确认、取消、改派、需求驳回接口的请求结构。
|
||||||
|
- 不修改 §7 矩阵派单的车辆槽位维度。
|
||||||
|
- 不修改数据库、网关路由、错误码、车辆/司机占用、保险或费用。
|
||||||
|
- 本交接不代表已修改、发布或验证 `mmg/hl-ui`。
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- 精确复现测试:同一 `requirementId` 下 `SUV×1 + 商务车×1` 返回
|
||||||
|
`total=1/records=1`、`requiredVehicles=2` 组、`assignmentSlots=2`、
|
||||||
|
`assignmentProgress.totalSlots=2`。
|
||||||
|
- 同车型×2、部分已派、全已派、混合完结/取消、状态多选、聚合后分页和汇总去重均有自动化覆盖。
|
||||||
|
- 相关定向测试 64 项通过,0 failures/errors。
|
||||||
|
- OpenAPI diff 状态为 `not_configured`:当前环境没有 `oasdiff`,仓库也没有可复现的
|
||||||
|
Swagger 2 → OpenAPI 3 导出链;契约审查保存了 Controller/VO 字段对比和自动化测试
|
||||||
|
作为人工 fallback 证据。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 commit:`fd641651144ab429ec1b371e1902a70bf3e7f025`
|
||||||
|
- 后端 PR:[wx/HL#5259](https://git.1814.love:8443/wx/HL/pulls/5259)
|
||||||
|
- 合并 commit:`dev-v3@01bf9c6274d5c40e00e65b84c5224189b0fd06b8`
|
||||||
|
- 合并后测试部署:现有部署 API 已滚动发布 `dev-v3`,
|
||||||
|
`hl-fleet-service:8087/8187` 均健康;构建、发布成功,部署日志尾部的
|
||||||
|
`ERROR/FATAL/Exception` 命中数为 0。
|
||||||
|
- 网关复现订单 `26-5256`:HTTP/业务码均为 200,`records=1`、`total=1`、
|
||||||
|
`requiredVehicles=SUV×1+MPV×1`、`assignmentSlots=2` 且逐槽位 ID 唯一、
|
||||||
|
`assignmentProgress.totalSlots=2`、状态数量之和为 2;脱敏证据 SHA-256 为
|
||||||
|
`7ba2b05d8bdf335207771c59e089357dbe25d1fe0ac4aa0db1c91f910da563f5`。
|
||||||
|
- fleet 定向测试 64 项通过;`mvn -pl hl-fleet-service spotless:check`
|
||||||
|
611 个文件通过;`mvn -pl hl-fleet-service -am verify` 中 fleet 2386 项测试
|
||||||
|
0 failures/errors、skipped 1,完整 reactor `BUILD SUCCESS`。
|
||||||
|
- OpenAPI diff 为 `not_configured`,已保存 Controller/VO 字段对比、兼容语义、
|
||||||
|
自动化测试和脱敏网关结构作为人工 fallback 证据。
|
||||||
|
- `frontend_status=pending`:本次未修改、部署或验证 `mmg/hl-ui`;
|
||||||
|
页面仍需按“1 条需求卡 + 2 个槽位详情”的新契约完成消费。
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5262"
|
||||||
|
title: "派车逐日车费、只读总价与核单实时接口"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "mmg/hl-ui@2dfe8ab40f3db446d0a079d7511986a2d7fdce33"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-26T16:37:00+08:00"
|
||||||
|
path_aliases: "changelogs-v2/2026-07/26_5262_派车逐日车费与核单实时接口-修改接口-前端待处理-管理后台.md"
|
||||||
|
status_note: "后端 PR wx/HL#5266 已合并到 dev-v3(9bfd21de6),Fleet/Order 测试双实例已部署并完成管理端网关与 internal Feign 实测;hl-admin 已在 2dfe8ab40f3db446d0a079d7511986a2d7fdce33 接入逐日车费、只读总价和旧字段移除,verify:changed 通过,尚未发布及页面联调。本契约替代 #5253 的“手工填写每车总价”口径。"
|
||||||
|
updated_at: "2026-07-26"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-26T16:20:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 派车逐日车费、只读总价与核单实时接口
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: [wx/HL#5262](https://git.1814.love:8443/wx/HL/issues/5262)
|
||||||
|
- 服务:`hl-fleet-service`、`hl-order-service-v3`
|
||||||
|
- 前端仓库:`mmg/hl-ui`(本文仅交接,不代表已修改前端)
|
||||||
|
- 替代口径:[#5253 按车辆记录订单总车费并接入核单](./26_5253_按车辆记录订单总车费并接入核单-修改接口-管理后台.md)
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
1. 多日派车按服务日保存 assignment,每个槽位 4 天即 4 条每日记录。
|
||||||
|
2. 价格日历改为提供逐日参考价;车务可覆盖本次派车的某日车费,覆盖值不回写价格日历。
|
||||||
|
3. `vehicleFeeTotal` 改为只读合计,恒等于收费日 `assignmentPrice` 之和。
|
||||||
|
4. 创建、批量派车、确认和改派请求继续兼容解析旧总价字段,但只要传值即返回稳定业务错误,不再接受手工总价。
|
||||||
|
5. 配置车辆不写 Order 核单表;核单后端通过新的 Fleet internal API 实时读取逐日车辆费用。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 变化 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POST` | `/admin/fleet/assignments/candidates` | 车辆候选新增逐日车费参考 |
|
||||||
|
| `POST` | `/admin/fleet/assignments` | 新增 `dailyVehicleFees`;旧 `vehicleFeeTotal` 禁止传值 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/batch` | 每个最终车辆槽位分别提交 `dailyVehicleFees` |
|
||||||
|
| `POST` | `/admin/fleet/assignments/:assignmentId/change` | 新派车段提交逐日车费;保留段沿用原逐日快照 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | 旧总价字段禁止传值;总价由已保存逐日车费只读计算 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders/:orderId` | 槽位返回 `dailyVehicleFees` 和只读合计 |
|
||||||
|
|
||||||
|
## 请求字段
|
||||||
|
|
||||||
|
`dailyVehicleFees[]`:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `serviceDate` | `LocalDate` | 是 | 本次覆盖的服务日期 |
|
||||||
|
| `price` | `Decimal` | 是 | 本次派车单日车费,最小 `0.00`,最多 2 位小数 |
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- 未提交覆盖值的收费日使用车型价格日历当天价格。
|
||||||
|
- 收费日缺少日历价格且未提交覆盖值时,后端拒绝最终派车。
|
||||||
|
- 覆盖值与日历参考价不一致时提交 `vehicleFeeAdjustmentReason`。
|
||||||
|
- 免费日期 `assignmentPrice` 固定为 `"0.00"`,不计入总价。
|
||||||
|
- `vehicleFeeTotal`、`retainedVehicleFeeTotal` 以及对应旧调整原因字段不得再由前端提交。
|
||||||
|
|
||||||
|
## 响应字段
|
||||||
|
|
||||||
|
候选、派车写响应和看板槽位新增或统一返回 `dailyVehicleFees[]`:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `serviceDate` | `LocalDate` | 服务日期 |
|
||||||
|
| `chargeable` | `Boolean` | 是否收取车费 |
|
||||||
|
| `calendarPrice` | `Decimal/null` | 车型价格日历参考价;缺价时为空 |
|
||||||
|
| `assignmentPrice` | `Decimal/null` | 本次派车单日车费;收费日必须有值,免费日为 `"0.00"` |
|
||||||
|
| `source` | `String` | `CALENDAR` / `OVERRIDE` / `FREE` / `MISSING` |
|
||||||
|
| `calendarPriceMissing` | `Boolean` | 价格日历是否缺价 |
|
||||||
|
|
||||||
|
`vehicleFeeTotal` 继续返回,但语义变为只读:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vehicleFeeTotal = sum(dailyVehicleFees[chargeable=true].assignmentPrice)
|
||||||
|
```
|
||||||
|
|
||||||
|
金额字段按字符串消费,雪花 ID 继续按字符串消费。
|
||||||
|
|
||||||
|
## 页面展示矩阵
|
||||||
|
|
||||||
|
| 区域 | 展示 | 空态 | 状态/颜色 | 守恒规则 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 收费日期 | 每日显示日历参考价与本次派车价 | 选车前“待计算”;缺价“价格日历缺价” | 参考价中性、覆盖蓝、缺价橙 | 一服务日一条派车记录 |
|
||||||
|
| 最终总车费 | 只读合计,不渲染金额输入框 | 缺价时“价格不完整” | 正常中性、缺价橙 | 等于全部收费日本次派车价之和 |
|
||||||
|
| 已结束行程 | 只允许查看逐日价格和总价 | 不适用 | 只读灰 | 前端禁用与后端拒绝一致 |
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 移除“最终总车费”输入框,改为只读合计。
|
||||||
|
- [ ] 收费日期逐日展示 `calendarPrice`,并允许编辑当前槽位的 `assignmentPrice`。
|
||||||
|
- [ ] 仅把修改后的日期组装为 `dailyVehicleFees`,不调用车型价格日历写接口。
|
||||||
|
- [ ] 使用 `source` 和 `calendarPriceMissing` 展示覆盖与缺价状态。
|
||||||
|
- [ ] 创建、批量派车、确认和改派请求不再传旧总价字段。
|
||||||
|
- [ ] 行程结束后禁用逐日车费、槽位和改派入口。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- OpenAPI/oasdiff:`not_configured`;已完成 Controller/VO 源码比对和 Fleet 接口测试回退证据。
|
||||||
|
- Spring Cloud Contract:`not_configured`;已完成 Fleet producer、Order Feign consumer 和 shared DTO 测试回退证据。
|
||||||
|
- Fleet:`mvn -pl hl-fleet-service -am verify` 与 `spotless:check` 通过。
|
||||||
|
- Order:`mvn -pl hl-order-service-v3 -am verify` 完成,Surefire 零失败并生成可执行 JAR。
|
||||||
|
- 部署:Fleet `630858f8`、Order `63f9ba60` 成功,8087/8187 与 8086/8186 双实例健康。
|
||||||
|
- 网关:管理端订单详情返回 4 条逐日车费及约定的 6 个逐日字段;internal Feign 正向响应严格为顶层 5 个字段、item 12 个字段。
|
||||||
|
- `frontend_status`:`pending`;真实领取后再迁移为 `claimed`。
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5263"
|
||||||
|
title: "最终确认按车选择发送行程短信"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "mmg/hl-ui@138136e50c4bbf9931ee020bd28d40977264b64e"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "前端已在 138136e5 接入 HOLD 最终确认逐车短信必选、权威状态查询和 FAILED 受控重试;定向 7 个文件 62 项及 verify:changed 全量验证通过。尚未发布或完成真实页面联调。"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-26T17:33:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 最终确认按车选择发送行程短信
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: [wx/HL#5263](https://git.1814.love:8443/wx/HL/issues/5263)
|
||||||
|
- Changelog PR: [wx/hl-api-changelog#41](https://git.1814.love:8443/wx/hl-api-changelog/pulls/41)
|
||||||
|
- 服务:`hl-fleet-service`、`hl-user-service`
|
||||||
|
- 前端仓库:`mmg/hl-ui`(本文仅交接,不代表已修改前端)
|
||||||
|
- 前置车费契约:[#5262 派车逐日车费、只读总价与核单实时接口](./26_5262_派车逐日车费与核单实时接口-修改接口-前端待处理-管理后台.md)
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
1. 仅在 HOLD 排车的最终确认阶段,每个车辆组必须显式选择“发送短信”或“不发送短信”,没有默认值。
|
||||||
|
2. 选择发送时,确认事务只落可靠发送意图;短信异步发送失败不会回滚已完成的派车确认。
|
||||||
|
3. 短信只发给该车辆组当前师傅,包含订单摘要、接送摘要和签名行程短链,不包含客户手机号。
|
||||||
|
4. 选择不发送时只完成派车确认,不创建行程短信事件。
|
||||||
|
5. 多车订单逐车独立选择、独立投递、独立查询状态和受控重试。
|
||||||
|
6. 已派定后的订单人数基线复核只能沿用原选择,不允许借复核修改选择或重复发送。
|
||||||
|
7. 直接派车流程不受影响;#5262 已废弃的手工车辆总价入参仍然禁止提交。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 变化 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | 请求新增必填 `sendItinerarySms`;响应新增短信选择、事件与状态 |
|
||||||
|
| `GET` | `/admin/fleet/assignments/:assignmentId/itinerary-sms` | 新增单车/车辆组短信审计状态查询 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/:assignmentId/itinerary-sms/retry` | 新增明确失败后的车务受控重试 |
|
||||||
|
|
||||||
|
## 最终确认
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/:assignmentId/confirm`
|
||||||
|
|
||||||
|
#### 请求字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `sendItinerarySms` | `Boolean` | 是 | `true` 发送;`false` 不发送;省略或 `null` 返回参数错误 |
|
||||||
|
| `requestId` | `String` | 是 | 最长 64 字符的幂等请求标识 |
|
||||||
|
| `vehicleFeeTotal` | `Decimal` | 否 | 历史兼容字段;非空即拒绝,最终总车费继续由 #5262 逐日车费只读合计 |
|
||||||
|
| `vehicleFeeAdjustmentReason` | `String` | 否 | 历史兼容字段;非空即拒绝 |
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sendItinerarySms": true,
|
||||||
|
"requestId": "fleet-final-confirm-26-8411-car-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 新增响应字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `sendItinerarySms` | `Boolean` | 本车辆组最终确认时保存的选择 |
|
||||||
|
| `itinerarySmsEventId` | `String/null` | 可靠短信事件 ID;不发送时为空 |
|
||||||
|
| `itinerarySmsStatus` | `String` | 首次确认返回 `PENDING` 或 `NOT_SENT`;已派定复核回显真实状态 |
|
||||||
|
|
||||||
|
## 短信状态
|
||||||
|
|
||||||
|
### `GET /admin/fleet/assignments/:assignmentId/itinerary-sms`
|
||||||
|
|
||||||
|
响应 `data`:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `assignmentId` | `String` | 派单 ID |
|
||||||
|
| `assignmentGroupId` | `String` | 跨服务日车辆组 ID |
|
||||||
|
| `assignmentSlotId` | `String` | 稳定车辆槽位 ID |
|
||||||
|
| `sendItinerarySms` | `Boolean/null` | 未最终确认或历史数据时为 `null` |
|
||||||
|
| `status` | `String` | `NOT_APPLICABLE` / `NOT_SENT` / `PENDING` / `SENT` / `FAILED` / `CANCELED` |
|
||||||
|
| `eventId` | `String/null` | 可靠短信事件 ID |
|
||||||
|
| `retryCount` | `Integer` | 已发生的失败重试次数 |
|
||||||
|
| `canRetry` | `Boolean` | 当前是否允许受控重试 |
|
||||||
|
| `sentAt` | `LocalDateTime/null` | 供应商确认的真实发送时间 |
|
||||||
|
| `lastError` | `String/null` | 已脱敏的最近失败或待对账原因 |
|
||||||
|
|
||||||
|
前端以 `status` 为权威,不得仅凭最终确认接口成功就显示“短信已发送”。
|
||||||
|
|
||||||
|
## 受控重试
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/:assignmentId/itinerary-sms/retry`
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reason": "短信通道配置已恢复,车务确认重发",
|
||||||
|
"requestId": "retry-sms-26-8411-car-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- 仅 `FAILED` 且 `canRetry=true` 时展示并调用重试。
|
||||||
|
- `SENT`、`PENDING`、待供应商对账、`CANCELED`、未选择发送时禁止重试。
|
||||||
|
- 重试复用原事件与供应商幂等键,不新建并行短信事件。
|
||||||
|
- 司机或车辆组身份已变化时,旧事件收敛为 `CANCELED`,不得发给旧师傅。
|
||||||
|
|
||||||
|
## 短信与隐私约束
|
||||||
|
|
||||||
|
短信模板参数固定为:
|
||||||
|
|
||||||
|
| 参数 | 内容 |
|
||||||
|
| --- | --- |
|
||||||
|
| `summary` | 脱敏订单摘要 |
|
||||||
|
| `transfer` | 接送摘要 |
|
||||||
|
| `code` | 签名行程短链 |
|
||||||
|
|
||||||
|
短信正文及模板参数不得包含客户手机号。真实联系人信息仅在既有签名行程 H5 中按授权展示。
|
||||||
|
|
||||||
|
## 页面展示矩阵
|
||||||
|
|
||||||
|
| 区域/状态 | 数据源 | 展示 | 空态/禁用 | 颜色 | 守恒规则 |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 最终确认车辆卡片 | 本地待提交选择 | “发送短信”/“不发送短信”二选一 | 未选择时禁止确认并提示必选 | 发送蓝色,不发送中性灰 | 每个车辆组恰好一个选择 |
|
||||||
|
| 确认后状态 | `GET .../itinerary-sms.status` | 待发送/已发送/发送失败/已取消/未发送 | 历史数据为“不适用” | 待发送蓝、已发送绿、失败红、取消灰、未发送中性灰 | 不以确认成功冒充发送成功 |
|
||||||
|
| 失败操作 | `canRetry` | “重试短信” | `canRetry=false` 时隐藏或禁用 | 可重试橙色 | 同一事件串行重试 |
|
||||||
|
| 多车订单 | 每个 `assignmentGroupId` | 每车独立选择与状态 | 不做订单级统一默认 | 各卡片独立 | 一车选择不得覆盖另一车 |
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 最终确认页按车辆组渲染无默认值的短信二选一。
|
||||||
|
- [ ] 未完成选择时不提交确认请求,并展示明确校验提示。
|
||||||
|
- [ ] 确认请求始终显式提交 `sendItinerarySms`,不再依赖后端默认值。
|
||||||
|
- [ ] 确认后通过状态接口展示真实投递状态。
|
||||||
|
- [ ] 仅在 `FAILED && canRetry=true` 时允许填写原因并调用重试。
|
||||||
|
- [ ] 已派定复核回显原选择并保持只读,不提供改选入口。
|
||||||
|
- [ ] 短信状态按展示矩阵处理空态、颜色及多车独立性。
|
||||||
|
- [ ] 确认和复核请求继续不提交 #5262 已废弃的手工总车费字段。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- OpenAPI/oasdiff:`not_configured`;已完成 Controller/VO 源码比对和接口测试回退证据。
|
||||||
|
- 消费者契约:`not_required`;未修改 internal Feign 或共享 Java DTO。
|
||||||
|
- 代码与测试:PR `wx/HL#5270` 已合入 `dev-v3`,merge commit 为 `f0a96c10124c3188e60e1291e7f28d768af50e3a`;`mvn -pl hl-user-service,hl-fleet-service -am test`、`mvn -pl hl-fleet-service spotless:check`、`mvn -pl hl-fleet-service -am verify` 均通过。
|
||||||
|
- 测试部署:`hl-user-service` 与 `hl-fleet-service` 已从合并后的 `dev-v3` 完成双实例滚动部署并通过健康检查。
|
||||||
|
- 网关:显式“不发送”业务验收 11/11 通过;合并后只读复验 3/3 通过,最终状态为 `assigned + NOT_SENT`,无短信事件且不可重试。
|
||||||
|
- `frontend_status`:`pending`;真实领取后再迁移为 `claimed` 并填写 `frontend_owner`。
|
||||||
@ -395,3 +395,33 @@ GET /admin/fleet/message-templates
|
|||||||
- 车队对账页 Network 必须看到 `/admin/fleet/reconciliation/cars` 和 `/insurance`,默认周期是当前月 `2026-07-01 ~ 2026-07-31`。
|
- 车队对账页 Network 必须看到 `/admin/fleet/reconciliation/cars` 和 `/insurance`,默认周期是当前月 `2026-07-01 ~ 2026-07-31`。
|
||||||
- 车管模板页 Network 必须看到 `/admin/fleet/message-templates`。
|
- 车管模板页 Network 必须看到 `/admin/fleet/message-templates`。
|
||||||
- 车务菜单测试账号必须是车务角色;不要用 `admin`、`adminle`、`wx`、定制师账号验证车务菜单。
|
- 车务菜单测试账号必须是车务角色;不要用 `admin`、`adminle`、`wx`、定制师账号验证车务菜单。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 2026-07-27 补充:矩阵车辆行常驻司机全部误显示“待派司机”
|
||||||
|
|
||||||
|
### 8.1 运行态与源码证据
|
||||||
|
|
||||||
|
- `/fleet/matrix` 的 19 条车辆行全部显示“待派司机”。
|
||||||
|
- 同次页面请求 `GET /admin/fleet/matrix/grid` 返回 200;响应车辆中存在非空
|
||||||
|
`primaryDriverName` 和脱敏 `primaryDriverPhone`,因此不是后端漏返回,也不是全部车辆都未绑定常驻司机。
|
||||||
|
- `useFleetMatrixData.adaptMatrixVehicle()` 已把这两个字段保留到车辆行对象。
|
||||||
|
- `VehicleGantt.primaryDriverOf(v)` 却忽略车辆行字段,只执行
|
||||||
|
`findPrimaryDriver(props.drivers, v.plate)`。
|
||||||
|
- 主矩阵和车辆分窗传入的 `drivers` 均为空列表且没有额外加载动作,所以每辆车都稳定落入
|
||||||
|
“待派司机”空态。
|
||||||
|
|
||||||
|
### 8.2 前端修复口径
|
||||||
|
|
||||||
|
1. 矩阵车辆行展示必须以 `data.vehicles[].primaryDriverName` 为权威来源;非空时直接显示该姓名。
|
||||||
|
2. `primaryDriverPhone` 已由后端脱敏,可按现有设计选择展示,但不得为显示姓名再拉全量司机列表。
|
||||||
|
3. 只有 `primaryDriverName` 为 `null` 或空白时,才显示“待派司机”空态。
|
||||||
|
4. 主矩阵和 `matrix-solo?solo=byVehicle` 必须复用同一解析逻辑,不能一处读取车辆行、一处反查本地司机数组。
|
||||||
|
5. 本问题不涉及后端接口、数据库、派单状态或候选规则变更;不要创建 `mmg/hl-ui` 配合工单,直接消费本 changelog。
|
||||||
|
|
||||||
|
### 8.3 前端验收 checklist
|
||||||
|
|
||||||
|
- [ ] 构造一辆 `primaryDriverName` 非空的车辆,主矩阵车辆行显示接口姓名而不是“待派司机”。
|
||||||
|
- [ ] `primaryDriverName=null` 的车辆仍显示“待派司机”。
|
||||||
|
- [ ] 车辆分窗与主矩阵展示一致。
|
||||||
|
- [ ] 车队、车型、状态筛选及派车占用条不受本次展示修复影响。
|
||||||
|
|||||||
@ -0,0 +1,390 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5264"
|
||||||
|
title: "移除核单分类手动确认门禁"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "c182af23fb724ab6915d75750951b1db0dcc603d"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T14:38:26+08:00"
|
||||||
|
status_note: "hl-admin 已移除‘本分类已确认’入口及 allConfirmed/confirmStatus 后续流程门禁;业务提交 c182af23 已在 origin/v2.1 可达,全量 checkpoint 通过"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·管理后台】移除核单分类手动确认门禁 (#5264)
|
||||||
|
|
||||||
|
> **PR**: #5274 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-28 09:42
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单流程不再要求财务在八个核单分类上逐一点击“本分类已确认”。管理后台只需要保存各分类明细;明细完整且可用于报账时,即可生成主报账人报账表。旧分类确认查询和确认接口保留兼容返回,但确认状态不再作为主报账、单团核算、Step6 提交或财务确认的门禁。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询原型八个核单分类确认状态 | GET | `/v3/admin/order/:orderId/settlement/category-checks` | 修改接口 | 响应字段保留,但 `allConfirmed` / `confirmStatus` 仅用于兼容展示,不再决定后续流程能否继续 |
|
||||||
|
| 2 | 按最近读取指纹确认单个核单分类 | POST | `/v3/admin/order/:orderId/settlement/category-checks/:category/confirm` | 修改接口 | 标记为废弃兼容;管理后台停止调用并移除“本分类已确认”入口 |
|
||||||
|
| 3 | 生成主报账人报账表 | POST | `/v3/admin/order/:orderId/settlement/reports/reimbursement/generate` | 修改接口 | 生成条件改为核单明细保存完整,不再要求八分类手动确认 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询原型八个核单分类确认状态
|
||||||
|
|
||||||
|
- **方法 / 路径**:`GET /v3/admin/order/:orderId/settlement/category-checks`
|
||||||
|
- **使用场景**:旧页面或兼容逻辑读取八分类状态。
|
||||||
|
- **认证**:需要管理后台登录态;房控角色不可访问。
|
||||||
|
- **幂等性**:是,只读查询。
|
||||||
|
- **限流**:无单独接口限流约定。
|
||||||
|
- **接口说明**:字段结构保持不变;`allConfirmed` 和 `items[].confirmStatus` 不再用于判断主报账、单团核算、Step6 或财务确认是否可继续。
|
||||||
|
|
||||||
|
### 3.2 按最近读取指纹确认单个核单分类(废弃兼容)
|
||||||
|
|
||||||
|
- **方法 / 路径**:`POST /v3/admin/order/:orderId/settlement/category-checks/:category/confirm`
|
||||||
|
- **使用场景**:仅兼容旧前端请求;新管理后台不再调用。
|
||||||
|
- **认证**:需要管理后台登录态和财务写权限;房控角色不可访问。
|
||||||
|
- **幂等性**:同一分类、同一 `expectedSourceFingerprint` 重复确认返回当前兼容状态。
|
||||||
|
- **限流**:无单独接口限流约定。
|
||||||
|
- **接口说明**:接口仍校验请求体和分类枚举,但确认投影不再作为后续流程门禁。前端应移除“本分类已确认”按钮、状态卡门禁和基于 `allConfirmed` 的下一步禁用逻辑。
|
||||||
|
|
||||||
|
### 3.3 生成主报账人报账表
|
||||||
|
|
||||||
|
- **方法 / 路径**:`POST /v3/admin/order/:orderId/settlement/reports/reimbursement/generate`
|
||||||
|
- **使用场景**:核单明细保存完整后生成或刷新主报账人报账表。
|
||||||
|
- **认证**:需要管理后台登录态和财务写权限;房控角色不可访问。
|
||||||
|
- **幂等性**:同一来源数据已生成时,可返回当前报账表;来源变化后重新生成。
|
||||||
|
- **限流**:无单独接口限流约定。
|
||||||
|
- **接口说明**:生成门禁改为逐分类明细完整性校验;不再要求先调用八分类确认接口。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 接口 | 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|------|----------|
|
||||||
|
| 三个接口共用 | `orderId` | `String` | 是 | 订单 ID,按字符串处理 | 必须为大于 0 的数字 |
|
||||||
|
| 分类确认接口 | `category` | `String` | 是 | 核单分类编码 | 见 §6.1 `SettlementCategory` |
|
||||||
|
|
||||||
|
### 4.2 请求体字段
|
||||||
|
|
||||||
|
#### 4.2.1 `GET /category-checks`
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 4.2.2 `POST /category-checks/:category/confirm`(废弃兼容)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `expectedSourceFingerprint` | `String` | 是 | 最近读取的分类源事实 SHA-256;废弃兼容字段 | 64 位小写十六进制字符串 |
|
||||||
|
| `confirmEmpty` | `Boolean` | 是 | 是否明确确认空分类;废弃兼容字段 | `true` / `false` |
|
||||||
|
|
||||||
|
#### 4.2.3 `POST /reports/reimbursement/generate`
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
### 5.1 `SettlementCategoryChecksRespVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `orderId` | `String` | 订单 ID |
|
||||||
|
| `allConfirmed` | `Boolean` | 兼容字段;不再作为后续流程门禁 |
|
||||||
|
| `items` | `Array<ItemVO>` | 八个分类状态列表 |
|
||||||
|
|
||||||
|
### 5.2 `SettlementCategoryChecksRespVO.ItemVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | `String` | 分类编码,见 §6.1 |
|
||||||
|
| `categoryName` | `String` | 分类中文名 |
|
||||||
|
| `rowCount` | `Integer` | 当前分类明细行数 |
|
||||||
|
| `empty` | `Boolean` | 当前分类是否为空 |
|
||||||
|
| `sourceFingerprint` | `String` | 当前分类源事实指纹 |
|
||||||
|
| `confirmStatus` | `String` | 兼容字段,见 §6.2;不再作为后续流程门禁 |
|
||||||
|
| `confirmedBy` | `String/null` | 兼容字段,确认人 ID |
|
||||||
|
| `confirmedByName` | `String/null` | 兼容字段,确认人姓名 |
|
||||||
|
| `confirmedAt` | `String/null` | 兼容字段,确认时间,格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||||
|
|
||||||
|
### 5.3 `SettlementReimbursementReportRespVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | `String` | 主报账表 ID |
|
||||||
|
| `orderId` | `String` | 订单 ID |
|
||||||
|
| `reportStatus` | `String` | 报告状态,见 §6.3 |
|
||||||
|
| `sourceFingerprint` | `String` | 报账来源指纹 |
|
||||||
|
| `primaryReporterId` | `String/null` | 主报账人 ID |
|
||||||
|
| `primaryReporterName` | `String/null` | 主报账人姓名 |
|
||||||
|
| `primaryReporterRole` | `String/null` | 主报账人角色 |
|
||||||
|
| `reportVersion` | `Integer` | 报告版本号 |
|
||||||
|
| `driverCollectedTailAmount` | `Decimal` | 司机代收尾款金额 |
|
||||||
|
| `approvedAdvanceAmount` | `Decimal` | 已审批预支金额 |
|
||||||
|
| `reportablePaidCostAmount` | `Decimal` | 可报账已支付成本 |
|
||||||
|
| `reporterNetAmount` | `Decimal` | 报账人净额 |
|
||||||
|
| `primaryReporterCollectedAmount` | `Decimal` | 主报账人已收金额 |
|
||||||
|
| `publicPrepaidAmount` | `Decimal` | 公共预付金额 |
|
||||||
|
| `primaryReporterDueAmount` | `Decimal` | 主报账人应结金额 |
|
||||||
|
| `advanceOutstandingAmount` | `Decimal` | 预支未结金额 |
|
||||||
|
| `reconNetAmount` | `Decimal` | 对账净额 |
|
||||||
|
| `transferDirection` | `String/null` | 转账方向 |
|
||||||
|
| `transferAmount` | `Decimal` | 转账金额 |
|
||||||
|
| `incomeLines` | `Array<Object>` | 收入明细行 |
|
||||||
|
| `expenseLines` | `Array<Object>` | 支出明细行 |
|
||||||
|
| `advanceLines` | `Array<Object>` | 预支明细行 |
|
||||||
|
| `vehicleLines` | `Array<Object>` | 车辆费用明细行 |
|
||||||
|
| `transferStatus` | `String/null` | 转账状态 |
|
||||||
|
| `transferDate` | `String/null` | 转账日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `transferRef` | `String/null` | 转账凭证号 |
|
||||||
|
| `advanceSettledFlag` | `Boolean/null` | 预支是否已结清 |
|
||||||
|
| `signedVoucher` | `Object/null` | 签字凭证信息 |
|
||||||
|
| `generatedBy` | `String/null` | 生成人 ID |
|
||||||
|
| `generatedByName` | `String/null` | 生成人姓名 |
|
||||||
|
| `generatedAt` | `String/null` | 生成时间,格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||||
|
| `confirmedBy` | `String/null` | 确认人 ID |
|
||||||
|
| `confirmedByName` | `String/null` | 确认人姓名 |
|
||||||
|
| `confirmedAt` | `String/null` | 确认时间,格式 `yyyy-MM-dd'T'HH:mm:ss` |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `category`(SettlementCategory)
|
||||||
|
|
||||||
|
**所属字段**:路径参数 `category`、响应 `items[].category` | **类型**:`String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `HOTEL` | 住宿 | 住宿核单明细 |
|
||||||
|
| `TICKET` | 门票/游玩项目 | 门票和游玩项目核单明细 |
|
||||||
|
| `MEAL` | 餐食 | 餐食费用明细 |
|
||||||
|
| `VEHICLE` | 车辆 | 车辆费用明细 |
|
||||||
|
| `GUIDE` | 导游 | 导游费用明细 |
|
||||||
|
| `PHOTOGRAPHER` | 摄影 | 摄影费用明细 |
|
||||||
|
| `OTHER_INCOME` | 其他收入 | 其他收入明细 |
|
||||||
|
| `OTHER_EXPENSE` | 其他支出 | 其他支出明细 |
|
||||||
|
|
||||||
|
### 6.2 `confirmStatus`(兼容状态)
|
||||||
|
|
||||||
|
**所属字段**:`items[].confirmStatus` | **类型**:`String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `UNCONFIRMED` | 未确认 | 兼容旧确认投影;不再阻止生成主报账表 |
|
||||||
|
| `CONFIRMED` | 已确认 | 兼容旧确认投影;不再作为后续流程门禁 |
|
||||||
|
| `STALE` | 已变化 | 兼容旧确认投影;不再作为后续流程门禁 |
|
||||||
|
|
||||||
|
### 6.3 `reportStatus`(SettlementReportStatus)
|
||||||
|
|
||||||
|
**所属字段**:`reportStatus` | **类型**:`String`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `GENERATED` | 已生成 | 主报账表已生成,尚未确认 |
|
||||||
|
| `CONFIRMED` | 已确认 | 主报账表已确认 |
|
||||||
|
| `STALE` | 来源已变化 | 当前来源指纹与已保存报账表不一致 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `200` | 成功 | 查询、兼容确认或生成主报账表成功 |
|
||||||
|
| `400` | 请求参数错误 | `orderId` 非法、兼容确认接口缺少请求体、`expectedSourceFingerprint` 不是 64 位小写十六进制、`confirmEmpty` 缺失 |
|
||||||
|
| `404` | 接口或资源不存在 | 路径不存在,或访问不存在的订单 |
|
||||||
|
| `584315` | 核单来源数据已变化,请刷新后重新生成 | 报告来源指纹变化 |
|
||||||
|
| `584317` | 当前报告状态不允许执行该操作 | 当前核单状态不允许生成或确认报告 |
|
||||||
|
| `584319` | 核单存在未知分类或历史迁移数据不完整 | `category` 不是 §6.1 中的值 |
|
||||||
|
| `584320` | 核单分类明细尚未保存完整或数据不可用于报账 | 生成主报账表时,某个分类明细缺必填业务信息或不可用于报账;响应会带具体分类名 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:未逐类确认也可生成主报账表
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/60001/settlement/reports/reimbursement/generate
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "910000000000000001",
|
||||||
|
"orderId": "60001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"primaryReporterId": "11001",
|
||||||
|
"primaryReporterName": "张三",
|
||||||
|
"primaryReporterRole": "GUIDE",
|
||||||
|
"reportVersion": 1,
|
||||||
|
"driverCollectedTailAmount": 0.00,
|
||||||
|
"approvedAdvanceAmount": 2000.00,
|
||||||
|
"reportablePaidCostAmount": 8300.00,
|
||||||
|
"reporterNetAmount": 6300.00,
|
||||||
|
"primaryReporterCollectedAmount": 0.00,
|
||||||
|
"publicPrepaidAmount": 1000.00,
|
||||||
|
"primaryReporterDueAmount": 6300.00,
|
||||||
|
"advanceOutstandingAmount": 0.00,
|
||||||
|
"reconNetAmount": 6300.00,
|
||||||
|
"transferDirection": "PAY_TO_REPORTER",
|
||||||
|
"transferAmount": 6300.00,
|
||||||
|
"incomeLines": [],
|
||||||
|
"expenseLines": [
|
||||||
|
{
|
||||||
|
"category": "HOTEL",
|
||||||
|
"categoryName": "住宿",
|
||||||
|
"amount": 3600.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"advanceLines": [],
|
||||||
|
"vehicleLines": [],
|
||||||
|
"transferStatus": "PENDING",
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": false,
|
||||||
|
"signedVoucher": null,
|
||||||
|
"generatedBy": "11",
|
||||||
|
"generatedByName": "旧核单员",
|
||||||
|
"generatedAt": "2026-07-27T10:15:30",
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况:查询兼容状态仍返回 `allConfirmed=false`
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/60001/settlement/category-checks
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"orderId": "60001",
|
||||||
|
"allConfirmed": false,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"category": "HOTEL",
|
||||||
|
"categoryName": "住宿",
|
||||||
|
"rowCount": 1,
|
||||||
|
"empty": false,
|
||||||
|
"sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"confirmStatus": "UNCONFIRMED",
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:分类明细未保存完整
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/60001/settlement/reports/reimbursement/generate
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584320,
|
||||||
|
"msg": "核单分类「住宿」明细尚未保存完整或数据不可用于报账",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:管理后台核单流程;分类明细已保存完整后生成主报账人报账表。
|
||||||
|
- **不适用场景**:继续用 `allConfirmed=true` 作为“生成主报账表”“生成单团核算表”“Step6 提交”“财务确认”的前置条件。
|
||||||
|
- **特殊边界**:`POST /category-checks/:category/confirm` 仍可能返回 200,但它只是兼容旧调用,不代表新流程需要或应该调用。
|
||||||
|
- **明细完整性口径**:生成主报账表时,八个分类都必须存在可用于报账的明细快照;缺少分类、金额非法、业务必填项为空或来源数据不可用时返回 `584320`。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `allConfirmed` | 后续流程可能按该字段判断八分类是否已全部确认 | 字段保留兼容,但不再作为后续流程门禁 |
|
||||||
|
| `items[].confirmStatus` | `UNCONFIRMED` / `CONFIRMED` / `STALE` 可能影响页面下一步按钮 | 字段保留兼容,但不再作为后续流程门禁 |
|
||||||
|
| `SettlementCategoryConfirmReqVO.expectedSourceFingerprint` | 分类确认接口必填 | 仍为兼容接口必填;新前端停止调用该接口 |
|
||||||
|
| `SettlementCategoryConfirmReqVO.confirmEmpty` | 分类确认接口必填 | 仍为兼容接口必填;新前端停止调用该接口 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 主报账表生成 | 要求八个分类确认状态全部满足手动确认口径 | 核单明细保存完整即可生成 |
|
||||||
|
| 分类确认按钮 | 前端需要逐分类调用确认接口 | 前端停止调用确认接口,并移除“本分类已确认”入口 |
|
||||||
|
| 单团核算 / Step6 / 财务确认门禁 | 可能间接受八分类确认状态影响 | 不再读取八分类手动确认状态作为门禁 |
|
||||||
|
| 明细不完整时生成主报账表 | 可能表现为八分类未确认或来源变化类提示 | 返回 `584320`,提示具体分类明细未保存完整或不可用于报账 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。旧查询字段和旧确认接口保留,但确认接口已废弃。
|
||||||
|
- **前端是否必须同步上线**:建议同步。前端应移除“本分类已确认”按钮、`allConfirmed` 门禁和基于 `confirmStatus` 的下一步禁用逻辑。
|
||||||
|
- **影响已有数据**:不要求前端迁移数据;历史确认状态仅作为兼容显示值。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚方式**:如需恢复旧流程,回滚 PR #5274 对应后端变更。
|
||||||
|
- **回滚后清理**:前端若已移除按钮,回滚后需要恢复八分类确认入口和 `allConfirmed` 门禁。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 管理后台不要再新增对 `POST /category-checks/:category/confirm` 的调用。
|
||||||
|
- 页面上原“本分类已确认”按钮、确认进度提示和 `allConfirmed=false` 禁用下一步的逻辑可以移除。
|
||||||
|
- 查询分类状态接口可继续用于兼容老页面,但不要把 `UNCONFIRMED` 或 `STALE` 解释为主报账表不可生成。
|
||||||
|
- 生成主报账表失败时优先识别 `584320`,它表示需要补齐对应分类明细,而不是要求点击分类确认。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR:[#5274](https://git.1814.love:8443/wx/HL/pulls/5274)。
|
||||||
|
- 合并提交:[`8635973e6`](https://git.1814.love:8443/wx/HL/commit/8635973e6)。
|
||||||
|
- 实现提交:[`07ac323a1`](https://git.1814.love:8443/wx/HL/commit/07ac323a1)。
|
||||||
|
- Source frontmatter 已记录后端部署完成、网关验证通过;前端按本交接独立完成消费与验证。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5264](https://git.1814.love:8443/wx/HL/issues/5264)
|
||||||
|
- **PR**: [#5274](https://git.1814.love:8443/wx/HL/pulls/5274)
|
||||||
|
- **Merge commit**: [8635973e6](https://git.1814.love:8443/wx/HL/commit/8635973e6)
|
||||||
|
- **Implementation commit**: [07ac323a1](https://git.1814.love:8443/wx/HL/commit/07ac323a1)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yaosu
|
||||||
|
- **前端对接**: 管理后台前端
|
||||||
@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5279"
|
||||||
|
title: "车务派单全链路 E2E 前端缺陷交接"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-admin"
|
||||||
|
frontend_ref: "2ee0988c235c0483a5b51f8cc0deb195d7fd8573"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "本次没有新增或修改后端字段、类型、必填性、路径和错误码;测试环境当前契约已验证。前端需修正动作码、部分日期、HOLD 费用草稿、刷新重置和冲突日期等消费逻辑。"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务:派单全链路 E2E 前端缺陷交接
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`、`hl-order-service-v3`
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5279](https://git.1814.love:8443/wx/HL/issues/5279)
|
||||||
|
>
|
||||||
|
> **前端基线**: `mmg/hl-ui v2.1@5e6718f952ed156c2e71bc38d08c3482e72ba744`
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派单看板、矩阵派单、派车弹窗、派单详情;订单详情 → 用车需求调整
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
这是一份**当前契约消费纠错和前端缺陷交接**,不是后端接口变更:
|
||||||
|
|
||||||
|
- 后端 #5275 已保证部分日期派车只消费目标切片,剩余日期和稳定槽位守恒;
|
||||||
|
- 后端 #5277 已恢复完整已派需求 `canAssign=true + CHANGE_ASSIGNMENT`;
|
||||||
|
- 测试环境通过真实后台 UI 完成 HOLD、DIRECT、司机确认、最终确认、司机拒接、改车、改司机、需求驳回/换版/重派、逐日费用、免费服务日、冲突和重复提交;
|
||||||
|
- 当前剩余阻断均可在最新前端稳定复现,后端不应增加旧动作码别名或重复状态来迁就页面。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
本次不新增接口、字段或错误码,仅纠正以下现有管理后台接口的前端消费:
|
||||||
|
|
||||||
|
| 方法 | 路径 | 当前契约用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/admin/fleet/board/orders` | 看板代表行、能力字段、稳定槽位和未派切片上下文 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders/<orderId>` | 当前有效派车组、逐日费用和独立生命周期 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/candidates` | 车辆/司机可用窗口与真实冲突明细 |
|
||||||
|
| `POST` | `/admin/fleet/assignments` | HOLD/DIRECT 首次派车 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/change` | 按稳定槽位改车、改司机或同时改派 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/driver-confirmation` | 登记司机回复 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/confirm` | 最终确认执行 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/driver-reject` | 司机拒接并退回未派 |
|
||||||
|
| `DELETE` | `/admin/fleet/assignments/<assignmentId>` | 取消派单 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/<assignmentId>/early-complete` | 提前完结 |
|
||||||
|
|
||||||
|
## 一、动作码必须消费后端当前值
|
||||||
|
|
||||||
|
权威字段来自:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders
|
||||||
|
GET /admin/fleet/board/orders/<orderId>
|
||||||
|
```
|
||||||
|
|
||||||
|
前端按 `canAssign` 和 `availableActionCodes` 渲染,不按中文状态或本地别名推断。
|
||||||
|
|
||||||
|
| 生命周期 | `currentStep` | 当前动作码 | 正确页面行为 |
|
||||||
|
| --- | ---: | --- | --- |
|
||||||
|
| `unassigned` | 2 | `ASSIGN`, `REJECT_REQUIREMENT` | 派车派人、驳回需求 |
|
||||||
|
| `holding_wait_driver` | 3 | `CHANGE_ASSIGNMENT`, `RECORD_DRIVER_CONFIRMATION`, `DRIVER_REJECT`,出团前/当天另有 `CANCEL_ASSIGNMENT` | 继续派车、司机拒接、改派、取消 |
|
||||||
|
| `driver_confirmed` | 4 | `CHANGE_ASSIGNMENT`, `CONFIRM_EXECUTION`,出团前/当天另有 `CANCEL_ASSIGNMENT` | 恢复第 4 步确认执行;不得只剩改派 |
|
||||||
|
| `assigned` 且未过出团日 | 4 | `CHANGE_ASSIGNMENT`, `CANCEL_ASSIGNMENT` | 改派、取消派单 |
|
||||||
|
| `assigned` 且已过出团日 | 4 | `CHANGE_ASSIGNMENT`, `EARLY_COMPLETE` | 改派、提前完结 |
|
||||||
|
| `completed/canceled` | 终态 | 无可写动作 | 只读 |
|
||||||
|
|
||||||
|
当前前端存在三处精确错位:
|
||||||
|
|
||||||
|
1. 检查 `CANCEL`,而后端下发 `CANCEL_ASSIGNMENT`;
|
||||||
|
2. 检查 `COMPLETE_EARLY`,而后端下发 `EARLY_COMPLETE`;
|
||||||
|
3. `driver_confirmed` 已下发 `CONFIRM_EXECUTION`,但卡片和详情没有恢复确认执行入口。
|
||||||
|
|
||||||
|
`DRIVER_REJECT` 与 `RECORD_DRIVER_CONFIRMATION` 是现行值,继续沿用。不要让后端同时下发新旧两套动作码。
|
||||||
|
|
||||||
|
## 二、部分日期稳定槽必须保留用户点击上下文
|
||||||
|
|
||||||
|
### 复现
|
||||||
|
|
||||||
|
测试单 `26-6263` 的同一 `assignmentSlotId`:
|
||||||
|
|
||||||
|
- 07-30、07-31:已有 HOLD;
|
||||||
|
- 08-01:唯一剩余 `unassigned`;
|
||||||
|
- 看板卡片正确显示“用车 08-01 · 1天”;
|
||||||
|
- 8 月矩阵未派池也只显示 08-01。
|
||||||
|
|
||||||
|
但当前前端:
|
||||||
|
|
||||||
|
- 从看板进入 Step 2 后改成编辑 07-30/07-31 的 active HOLD;
|
||||||
|
- 从矩阵车辆 08-01 空闲格进入时,顶部虽显示“有效服务日期:08-01”,Step 2 却显示 `已选 0/0 天`,继续复用 07-30/07-31 费用并返回 0 个候选。
|
||||||
|
|
||||||
|
### 正确规则
|
||||||
|
|
||||||
|
- 首次派车 `mode=assign` 必须保留用户点击的看板代表行:`assignmentId/assignmentSlotId/fleetItemIndex/startDate/endDate`;详情异步返回后不能被 `currentAssignment` 覆盖;
|
||||||
|
- 矩阵入口以 `entryContext.clickedDate + startDate + endDate` 与订单合法服务段求交集;本例结果固定为 08-01;
|
||||||
|
- `currentAssignment/activeAssignments[]` 只用于改派和确认已有有效派车组,不代表未派切片;
|
||||||
|
- 实际计费服务日为空时,应从已经校验通过的 `assignmentDateRange` 回退生成,不得读取另一 active 组的逐日费用;
|
||||||
|
- 提交仍使用原稳定 `assignmentSlotId`,只消费 08-01,不得重建第二槽位或覆盖 07-30/07-31。
|
||||||
|
|
||||||
|
## 三、HOLD 手工补价不能在司机确认后丢失
|
||||||
|
|
||||||
|
### 复现
|
||||||
|
|
||||||
|
`26-9140` 改派时:
|
||||||
|
|
||||||
|
- 07-30/07-31 使用日历价 860;
|
||||||
|
- 08-01 日历缺价,页面提交覆盖价 860 和调价原因;
|
||||||
|
- `/change` 返回并落库总价 2580;
|
||||||
|
- 派单详情也正确展示 08-01 覆盖价和原因。
|
||||||
|
|
||||||
|
登记司机确认后,Step 4 又从候选价格日历重建费用,08-01 变为“待补价”,总价变为“价格不完整”,`validateVehicleFeeDraft` 阻断最终确认。
|
||||||
|
|
||||||
|
### 正确规则
|
||||||
|
|
||||||
|
- HOLD 已创建后,`activeAssignments[]/currentAssignment` 的 `dailyVehicleFees`、`vehicleFeeTotal`、`vehicleFeeAdjustmentReason`、`chargeableServiceDates` 和 `vehicleFeeWaiverReason` 是当前派车组权威快照;
|
||||||
|
- 候选接口的价格只用于新选车草稿或日历参考,不得覆盖已经保存的 HOLD 费用;
|
||||||
|
- 第 3 步登记司机回复和第 4 步确认执行都不能清空已保存覆盖价;
|
||||||
|
- 详情重拉后应按 `assignmentId/assignmentGroupId` 恢复同一派车组快照。
|
||||||
|
|
||||||
|
## 四、后台刷新不得重置正在编辑的派车草稿
|
||||||
|
|
||||||
|
实测派车弹窗打开后,后台订单刷新会触发整套初始化:
|
||||||
|
|
||||||
|
- 候选接口已返回 19 辆车和 11 名司机,但两个列表被清空,计数/筛选项仍保留;
|
||||||
|
- 用户已切换 DIRECT,提交前又恢复为 HOLD;
|
||||||
|
- 再点一次筛选会重新请求并恢复候选。
|
||||||
|
|
||||||
|
前端应做到:
|
||||||
|
|
||||||
|
- `props.order` 仅因列表/SSE 刷新生成新对象时,不重置当前弹窗;
|
||||||
|
- 只有订单 ID、目标需求 ID、目标稳定槽位、模式或用户主动关闭/重开变化时才重新初始化;
|
||||||
|
- 已编辑车辆、司机、日期、收费日、逐日价、原因、跨常驻确认和 HOLD/DIRECT 模式全部保留;
|
||||||
|
- 若服务端基线真的变化,使用现有 baseline-difference 强提示并让用户决定,不静默改写草稿。
|
||||||
|
|
||||||
|
## 五、多槽 HOLD 必须按槽位推进
|
||||||
|
|
||||||
|
`26-5256` 有两个稳定车辆槽位。槽位 1 登记司机确认后:
|
||||||
|
|
||||||
|
- 详情真值为槽位 1 `driver_confirmed`、槽位 2 `holding_wait_driver`;
|
||||||
|
- 卡片却仍把代表司机显示为“待回复”;
|
||||||
|
- “继续派车”入口消失,槽位 2 无法登记司机回复。
|
||||||
|
|
||||||
|
正确行为:
|
||||||
|
|
||||||
|
- 订单只要任一 active 槽位包含 `RECORD_DRIVER_CONFIRMATION`,卡片和详情保留“继续派车”;
|
||||||
|
- 打开后默认选中最早待回复槽位,也允许切换其它槽位;
|
||||||
|
- 一个槽位确认不得覆盖或隐藏另一个槽位状态;
|
||||||
|
- 订单聚合步骤取最慢槽位,代表卡文案必须明确“代表槽位”,不能冒充全单状态。
|
||||||
|
|
||||||
|
## 六、冲突窗口字段不能混用
|
||||||
|
|
||||||
|
候选响应同时包含:
|
||||||
|
|
||||||
|
| 字段 | 含义 | 页面用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `conflicts[]` | 真正占用冲突;`blocking=true` 的日期禁止选择 | 展示冲突订单与冲突日期 |
|
||||||
|
| `availabilityWindows[]` | 当前查询区间内仍可用的连续窗口 | 仅在资源可用/部分可用提示中展示 |
|
||||||
|
| `availabilityReasonCode` | `AVAILABLE`、`ASSIGNMENT_CONFLICT` 等判定码 | 决定禁用和提示类型 |
|
||||||
|
| `availabilityReasonMessage` | 后端判定文案 | 展示主提示 |
|
||||||
|
|
||||||
|
实测后端冲突是 08-01,`availabilityWindows` 是 08-02..08-04;页面却显示“存在派单冲突 · 08-02 至 08-04”。禁用结果正确,日期解释相反。
|
||||||
|
|
||||||
|
冲突文案应从 `conflicts[].startDate/endDate` 汇总;`availabilityWindows` 不得拼在冲突文案后。
|
||||||
|
|
||||||
|
## 七、提交与刷新反馈
|
||||||
|
|
||||||
|
### 1. 防重复提交
|
||||||
|
|
||||||
|
真实 UI 同一时刻双击 DIRECT 发出两个不同 `requestId`:
|
||||||
|
|
||||||
|
- 第一笔成功并只生成一个有效派车;
|
||||||
|
- 第二笔被后端守恒门禁拒绝,返回 `605033`:`用车需求完成回写处理中,请稍后重试`。
|
||||||
|
|
||||||
|
前端应在第一笔进入提交函数时立即短路后续点击,并在按钮、快捷键和程序触发路径共用同一 `submitting` 门禁。同一草稿的网络重试应复用 requestId,不应每次生成新值。
|
||||||
|
|
||||||
|
### 2. 司机拒接后的详情
|
||||||
|
|
||||||
|
司机拒接成功后,卡片立即变为未派,但当前详情抽屉仍保留旧 HOLD 车辆、费用和动作。关闭重开才正确。成功后需用最新服务端详情整体替换旧对象,不得继续把 mutation 前的行快照合并回来。
|
||||||
|
|
||||||
|
### 3. 车务角色快捷入口
|
||||||
|
|
||||||
|
车务角色下顶部可见“订单列表”,点击却进入未注册 `/housekeeper/orders` 并显示 404。未注册/无权限时不要展示快捷入口;有权限时应确保动态路由已注册再导航。
|
||||||
|
|
||||||
|
## 八、前端验收清单
|
||||||
|
|
||||||
|
- [ ] `CANCEL_ASSIGNMENT` 显示并执行取消;不再检查 `CANCEL`。
|
||||||
|
- [ ] `EARLY_COMPLETE` 显示并执行提前完结;不再检查 `COMPLETE_EARLY`。
|
||||||
|
- [ ] `driver_confirmed + CONFIRM_EXECUTION` 可从卡片和详情恢复第 4 步。
|
||||||
|
- [ ] 多槽 HOLD 任一槽位待回复时保留“继续派车”,逐槽推进且代表状态不串槽。
|
||||||
|
- [ ] `26-6263` 从看板和 8 月矩阵都只编辑/提交 08-01,07-30/07-31 不变。
|
||||||
|
- [ ] 已保存 HOLD 的手工逐日价、调价原因和免费日配置在司机确认、详情刷新、最终确认之间保持不变。
|
||||||
|
- [ ] SSE/列表刷新不清空候选、车辆司机草稿或切换 HOLD/DIRECT 模式。
|
||||||
|
- [ ] 冲突日期取 `conflicts[]`,可用窗口取 `availabilityWindows[]`,两者文案不混用。
|
||||||
|
- [ ] DIRECT/HOLD 第一击后立即禁用所有重复提交路径;同草稿重试复用 requestId。
|
||||||
|
- [ ] 司机拒接后当前卡片和详情同时刷新到 unassigned。
|
||||||
|
- [ ] 车务角色的订单快捷入口不再进入 404。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 `dev-v3@79297c838b62af2426cc83ad0e78cffbd961096b` 已部署,Fleet 8087/8187 两实例健康;
|
||||||
|
- #5277 Fleet reactor 2,427 项通过,0 失败、0 错误、1 跳过;Spotless 通过;
|
||||||
|
- 网关验证:完整 assigned 单槽和三槽均为 `canAssign=true + CHANGE_ASSIGNMENT`;
|
||||||
|
- 真实 UI 写入通过:HOLD、DIRECT、司机确认、最终确认(不发送短信)、司机拒接、只换车、只换司机、同时换车司机、需求驳回、V2/V3 换版重派、收费/免费日、重复提交;
|
||||||
|
- 后端守恒:重复 DIRECT 只生成一笔有效派车;需求 `DONE_ADJUST` 返回 `assignmentDeletedCount=1`,旧资源随后可重新选择;
|
||||||
|
- 部分日期后端真值:07-30/07-31 为 HOLD,08-01 唯一 unassigned,三天共用稳定槽位,无重复/孤儿;
|
||||||
|
- 关键页面截图:`fleet-partial-continuation-broken.png`、`5277-assigned-reassign-restored.png`;
|
||||||
|
- 完整逐步证据由 #5279 工单评论和测试记录保留。
|
||||||
|
|
||||||
|
## 十、契约审查
|
||||||
|
|
||||||
|
- 本文件不对应新的 Controller、VO、字段、类型、必填性、动作码或错误码变更;
|
||||||
|
- OpenAPI/oasdiff:`not_required`,没有新的 `frontend_api` diff;
|
||||||
|
- Consumer Contract:`not_required`,没有 Feign/shared Java 变化;
|
||||||
|
- 当前动作码由 `AssignmentLifecycleResolver` 和现有服务测试确认;部署网关响应与源码一致;
|
||||||
|
- 前端代码审查确认当前仍检查 `CANCEL`、`COMPLETE_EARLY`,且没有消费 `CONFIRM_EXECUTION`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 `hl-ui`,不在 `mmg/hl-ui` 建工单;本文件 `frontend_status` 保持 `pending`;
|
||||||
|
- 不要求后端新增兼容动作码、重复字段、特殊前端分支或放松资源/幂等守恒;
|
||||||
|
- 不修改派车状态机、计费公式、库存/占用、保险、通知、Outbox 或数据库结构;
|
||||||
|
- 不把前端实现、发布、页面验收纳入后端工单关闭条件。
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5282"
|
||||||
|
title: "矩阵年度月度订单统计"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "新增接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "7d0589d6"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "管理后台已由提交 7d0589d6 完成年度月度订单统计消费并通过验证"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-27T10:04:56+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 矩阵年度月度订单统计
|
||||||
|
|
||||||
|
> 后端契约已部署并经测试网关验证;`frontend_status` 独立反映管理端交付状态。
|
||||||
|
|
||||||
|
## 关联
|
||||||
|
|
||||||
|
- Issue: #5282
|
||||||
|
- PR: wx/HL#5286
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/month-counts`
|
||||||
|
|
||||||
|
一次查询指定年份的矩阵月度订单状态统计,统计口径与 `GET /admin/fleet/matrix/grid` 的 `statusCounts` 一致。
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
|
||||||
|
| 参数 | 位置 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `year` | query | `Integer` | 是 | 沿用矩阵 `YearMonth` 校验,越界返回 `605010` |
|
||||||
|
| `season` | query | `String` | 否 | 不传或空白时按 `active` 处理 |
|
||||||
|
| `fleetTeamIds` | query | `Long[]` | 否 | 可重复参数;空数组表示不过滤车队 |
|
||||||
|
| `typeKeys` | query | `String[]` | 否 | 可重复参数;空数组表示不过滤车型 |
|
||||||
|
|
||||||
|
响应 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"year": 2026,
|
||||||
|
"months": [
|
||||||
|
{
|
||||||
|
"month": 1,
|
||||||
|
"statusCounts": {
|
||||||
|
"totalAssignments": 0,
|
||||||
|
"unassignedAssignments": 0,
|
||||||
|
"assignedAssignments": 0,
|
||||||
|
"totalOrders": 0,
|
||||||
|
"unassignedOrders": 0,
|
||||||
|
"partialOrders": 0,
|
||||||
|
"assignedOrders": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `months` 固定返回 12 项,按 `month=1..12` 升序;无订单月份不省略,各计数字段为 `0`。
|
||||||
|
- 一条跨月派车记录按实际服务日期覆盖的月份分别计数;已取消、已关闭订单沿用矩阵现有口径排除。
|
||||||
|
- 年份越界沿用矩阵业务错误码 `605010`;缺失必填参数沿用统一参数校验响应。
|
||||||
|
|
||||||
|
## 契约影响文件
|
||||||
|
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/matrix/controller/MatrixController.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/matrix/vo/MatrixMonthCountsReqVO.java`
|
||||||
|
- `hl-fleet-service/src/main/java/com/hulalv/fleet/matrix/vo/MatrixMonthCountsRespVO.java`
|
||||||
|
- `hl-fleet-service/src/test/java/com/hulalv/fleet/matrix/controller/MatrixControllerTest.java`
|
||||||
|
|
||||||
|
## 前端/调用方动作
|
||||||
|
|
||||||
|
- 车务矩阵页面按当前年份、赛季、车队和车型筛选请求本接口。
|
||||||
|
- 月份选择器读取对应月份的 `statusCounts.totalAssignments`;有效零值显示 `0`,请求尚未完成或失败显示 `--`。
|
||||||
|
- 跨年切换时按目标年份加载;可按筛选键缓存结果,筛选变化后重新获取。
|
||||||
|
- 数组查询参数必须使用重复 key(Axios `paramsSerializer` 的 `indexes` 设为 `null`),不要发送带下标的参数名。
|
||||||
|
|
||||||
|
## 兼容性与路由
|
||||||
|
|
||||||
|
- 新增 GET 路径,不修改既有路径、请求参数、响应字段、枚举或错误码,对既有消费者向后兼容。
|
||||||
|
- 网关已有 `Path=/admin/fleet/**` 路由覆盖,无需新增网关配置。
|
||||||
|
- 无 internal Feign 或 shared Java 契约变更。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端定向测试:`MatrixServiceTest,MatrixControllerTest` 共 39 项通过,覆盖固定 12 月零值、跨月、终态排除、筛选、数组绑定、JSON 和与 grid 的统计守恒。
|
||||||
|
- 后端完整门禁:`mvn -pl hl-fleet-service -am verify` 通过(2434 tests,0 failures/errors,1 skipped);Fleet Spotless 626 文件通过。
|
||||||
|
- 测试部署:PR `wx/HL#5286` 合入 `dev-v3`,Deploy Panel 任务 `57a121a0` 成功,8087/8187 双实例健康。
|
||||||
|
- 测试网关:2026 年返回固定 12 项且字段与派单/订单守恒通过;2099 年固定 12 项且全部计数为 `0`。脱敏证据:`C:/Users/Administrator/AppData/Local/hl-workflow/evidence/5282/gateway-month-counts.json`。
|
||||||
|
- DB 地面真相:`not_verified`;现有只读探针的数据源安全守卫拒绝本地地址,未绕过门禁。零值另由 2099 网关实测与 Service 测试覆盖。
|
||||||
|
- 管理端本地实现:全量 Vitest 125 文件 / 1160 项通过;生产构建、ESLint 通过;6 个改动文件 Prettier 检查通过。因前端分支尚未推送/发布,frontmatter 仍保持 `frontend_status: pending`。
|
||||||
|
- OpenAPI diff:`not_configured`。当前仓库仅提供 Swagger 2,且本机未配置 `oasdiff`;已人工核对路径、方法、参数、响应、空态、错误码、网关与消费者,未伪报自动 diff 通过。
|
||||||
|
- 人工契约证据:`C:/Users/Administrator/AppData/Local/hl-workflow/evidence/5282/contract-review.md`。
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5283"
|
||||||
|
title: "车务派车候选响应被订单刷新清空"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-admin"
|
||||||
|
frontend_ref: "2ee0988c235c0483a5b51f8cc0deb195d7fd8573"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端候选接口契约与日期冲突口径正常;前端 AssignModal 因同订单对象刷新而重复初始化,清空已成功返回的候选。"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务派车候选响应被订单刷新清空
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **后端工单**: [wx/HL#5283](https://git.1814.love:8443/wx/HL/issues/5283)
|
||||||
|
>
|
||||||
|
> **前端消费端**: `mmg/hl-ui v2.1`
|
||||||
|
>
|
||||||
|
> **影响页面**: 车务管理 → 派车看板/矩阵 → 派车弹窗 `AssignModal`
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
本次不是后端候选过滤或 API 契约缺陷,不新增或修改接口、字段、类型、必填性、错误码及派单状态机:
|
||||||
|
|
||||||
|
- 同一真实请求经测试网关返回 `vehicleTotal=19`,其中 11 辆 `AVAILABLE`,其余车辆因真实日期冲突不可用;
|
||||||
|
- 专用测试车辆在目标日期区间均返回 `AVAILABLE`,车辆、车型、车队和维保状态未造成错误排除;
|
||||||
|
- 页面显示“共 0 辆”发生在响应成功之后:看板刷新/SSE 以新对象替换同一订单,`AssignModal` 再次执行 `resetOptionPages()` 并递增 `requestSeq`,从而清空候选并丢弃已返回结果;
|
||||||
|
- 不得通过放宽后端车辆/车队/车型或冲突过滤来掩盖前端状态重置问题。
|
||||||
|
|
||||||
|
该问题也属于 [#5279 车务派单全链路 E2E 前端缺陷交接](./27_5279_车务派单全链路E2E前端缺陷交接-修改接口-管理后台.md)“后台刷新不得重置正在编辑的派车草稿”的同类消费缺陷;#5283 补充了候选 19/11 的独立网关证据和稳定初始化键要求。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/candidates
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
接口继续返回车辆、司机两套独立分页候选。前端应消费 `vehicles.records/total` 与 `drivers.records/total`(兼容别名 `list` 仍保留),并以 `available`、`availabilityReasonCode`、`conflicts[]` 和 `availabilityWindows[]` 展示可用性;本次没有后端契约变化。
|
||||||
|
|
||||||
|
## 前端修复口径
|
||||||
|
|
||||||
|
### 1. 以稳定业务身份决定是否重新初始化
|
||||||
|
|
||||||
|
不得仅监听 `props.order` 对象身份。应使用 `initializationIdentity` 或等价稳定键,至少覆盖:
|
||||||
|
|
||||||
|
- `orderId`;
|
||||||
|
- 当前 `requirementId`;
|
||||||
|
- 目标 `assignmentSlotId/fleetItemIndex`;
|
||||||
|
- `assign/change` 模式及必要入口上下文。
|
||||||
|
|
||||||
|
只有上述业务身份真正变化,或用户主动关闭后重新打开弹窗时,才允许执行 `resetOptionPages()` 和重建派车草稿。同一订单仅因列表刷新或 SSE 生成新对象时不得重置。
|
||||||
|
|
||||||
|
### 2. 保留候选与编辑草稿
|
||||||
|
|
||||||
|
同一稳定业务身份下刷新订单对象时,必须保留:
|
||||||
|
|
||||||
|
- 车辆/司机候选 `records/total/page/pageSize`;
|
||||||
|
- 车队、车型、关键字和可用性筛选;
|
||||||
|
- 已选车辆、司机、日期和稳定槽位;
|
||||||
|
- 收费服务日、逐日价格、调价/免费原因;
|
||||||
|
- 跨常驻确认以及 `HOLD/DIRECT` 模式。
|
||||||
|
|
||||||
|
若服务端基线确实变化,继续使用现有 baseline-difference 强提示,由用户决定如何处理;不得静默清空或改写草稿。
|
||||||
|
|
||||||
|
### 3. 保留并发响应保护,但不得误杀当前请求
|
||||||
|
|
||||||
|
继续保留 `requestSeq` 或等价的旧响应隔离机制。只有新业务身份或新查询真正开始时才递增序号;同一订单对象替换不得把已经成功返回的当前候选标记为过期。快速连续刷新时,旧请求不能覆盖新请求,新请求成功结果也不能被无关初始化清空。
|
||||||
|
|
||||||
|
## 前端验收清单
|
||||||
|
|
||||||
|
- [ ] 专用测试订单 `26-4700`、`2026-08-04..2026-08-07`、SUV、3 人请求返回后,页面候选数量与接口一致,不再错误显示 0。
|
||||||
|
- [ ] 相同订单、需求、槽位和模式下,列表/SSE 替换订单对象后,车辆和司机候选仍保留。
|
||||||
|
- [ ] 候选分页总数、筛选条件、已选项和费用草稿不被后台刷新清空。
|
||||||
|
- [ ] 已切换的 `HOLD/DIRECT` 模式不会因同订单刷新恢复默认值。
|
||||||
|
- [ ] 订单、需求、槽位或模式真实变化时仍能正确重新初始化。
|
||||||
|
- [ ] 快速连续刷新时,旧响应不能覆盖新响应,当前成功响应也不会被误判过期。
|
||||||
|
- [ ] Network 记录仍使用现有候选接口,无新增或修改后端请求契约。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 测试网关与 Fleet 双实例同口径响应:`code=200`、车辆总数 19、可用 11、真实冲突 8;
|
||||||
|
- 目标测试车辆均为 `AVAILABLE`,目标日期冲突数为 0;
|
||||||
|
- 后端 worktree 零代码改动,当前候选过滤、日期闭区间冲突和 API 契约保持不变;
|
||||||
|
- 前端 HMR 现场曾出现稳定 `initializationIdentity` 方向的修正,但尚无 `mmg/hl-ui v2.1` 远端提交与发布验收依据,因此本文件保持 `frontend_status: "pending"`。
|
||||||
|
|
||||||
|
## 契约审查
|
||||||
|
|
||||||
|
- Frontend API:现有接口消费纠错,无 Controller/DTO/VO/字段变化,OpenAPI diff 为 `not_required`;
|
||||||
|
- Internal Feign / shared Java:无变化,Consumer Contract 为 `not_required`;
|
||||||
|
- 后端真值由已部署测试环境网关响应和现有 Fleet 源码确认;changelog 发布不代表前端已实现或已发布。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 `hl-ui`,不在 `mmg/hl-ui` 创建工单;
|
||||||
|
- 不修改后端候选过滤、日期冲突、车辆/司机占用、派单状态机、计费、保险、通知或数据库;
|
||||||
|
- 不把前端实现、发布和页面验收冒充为本次后端代码交付。
|
||||||
@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5284"
|
||||||
|
title: "建议车辆槽位由车务自由删减并按最终实派方案提交"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "d459b946"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "最终实派方案行为已由 #5262 部署;#5284 收口既有接口语义并纠正 #5245 旧前端口径;管理后台已由提交 d459b946 完成槽位自由删减和最终方案 batch 提交并通过验证。"
|
||||||
|
updated_at: "2026-07-27"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务:建议车辆槽位由车务自由删减
|
||||||
|
|
||||||
|
> **服务**: `hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **工单**: [wx/HL#5284](https://git.1814.love:8443/wx/HL/issues/5284)
|
||||||
|
>
|
||||||
|
> **影响范围**: 车务管理 → 派单看板 → 订单派车弹窗的车辆槽位编辑与批量提交
|
||||||
|
|
||||||
|
## ⚠️ 关键纠错
|
||||||
|
|
||||||
|
截图中默认出现但没有删除入口的“车辆槽位 1”,是页面依据订单当前用车需求
|
||||||
|
`requiredVehicles[]` 展开的**定制师/订单建议槽位**,不是车务必须保留的最终槽位。
|
||||||
|
|
||||||
|
#5245 曾写“只删除新增且未提交的草稿槽位”。该表述只是在强调不能用本地删除撤销已有派车,
|
||||||
|
但被页面实现成了“建议生成的初始槽位不可删除”。**这个实现口径需要纠正:所有尚未提交的本地槽位都可删除,
|
||||||
|
包括建议生成的初始槽位和车务后加的槽位。最终配几辆、配什么车型由车务决定。**
|
||||||
|
|
||||||
|
边界保持不变:
|
||||||
|
|
||||||
|
- 编辑态允许暂时删至 0 个槽位,并保留“添加车辆槽位”入口;
|
||||||
|
- 最终提交时 `items[]` 仍必须包含 1–20 个完整槽位;
|
||||||
|
- 已经存在的 `holding/assigned/completed` 有效派车不是本地草稿,不能直接删除,继续走取消或改派流程。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
本次不新增 JSON 字段、路径或错误码,只明确现有接口的权威语义:
|
||||||
|
|
||||||
|
| 方法 | 路径 | 当前权威语义 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/admin/fleet/board/orders` | `requiredVehicles[]` 和 `assignmentProgress.suggestedSlots` 是订单建议,仅作参考 |
|
||||||
|
| `GET` | `/admin/fleet/board/orders/<orderId>` | `suggestedVehicleCount` 是建议数量,`actualVehicleCount` 是车务当前实派数量 |
|
||||||
|
| `POST` | `/admin/fleet/assignments/batch` | `items[]` 是车务本次保留的**完整最终实派方案**,无需覆盖全部建议槽位 |
|
||||||
|
|
||||||
|
## 批量提交契约
|
||||||
|
|
||||||
|
### `items[]` 是完整最终方案
|
||||||
|
|
||||||
|
假设订单建议 2 辆 SUV,页面可以删除两个建议槽位后重新添加 1 个槽位,并提交:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2080000000000000001",
|
||||||
|
"requirementId": "2080000000000000002",
|
||||||
|
"startDate": "2026-08-04",
|
||||||
|
"endDate": "2026-08-07",
|
||||||
|
"holdMode": 1,
|
||||||
|
"requestId": "fleet-final-plan-example",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 5,
|
||||||
|
"vehicleId": "2080000000000000101",
|
||||||
|
"driverId": "2080000000000000201"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- `items[]` 数量可以少于、等于或多于建议数量,但必须为 1–20;
|
||||||
|
- `fleetItemIndex` 可沿用建议索引,也可使用未占用的新索引,不要求连续;
|
||||||
|
- 批内 `fleetItemIndex`、车辆和司机各自唯一;
|
||||||
|
- 未被最终方案保留的 `unassigned` 建议占位会退出待派和完成条件;
|
||||||
|
- 若漏传已有 `holding/assigned` 等在途槽位,后端拒绝整批提交并提示先取消,不会静默删除已有派车;
|
||||||
|
- 任一槽位失败仍整批回滚,前端不得循环调用单派接口替代批量提交。
|
||||||
|
|
||||||
|
### 看板建议数与实派数分离
|
||||||
|
|
||||||
|
最终方案提交后可能出现:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"assignmentProgress": {
|
||||||
|
"suggestedSlots": 2,
|
||||||
|
"finalizedByFleet": true,
|
||||||
|
"totalSlots": 1,
|
||||||
|
"unassignedSlots": 0,
|
||||||
|
"holdingSlots": 1,
|
||||||
|
"assignedSlots": 0,
|
||||||
|
"completedSlots": 0,
|
||||||
|
"canceledSlots": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
此时 `suggestedSlots=2` 只保留建议事实,页面进度、完成条件和最终车辆数都按 `totalSlots=1` 计算。
|
||||||
|
|
||||||
|
## 前端展示矩阵
|
||||||
|
|
||||||
|
| 场景 | 数据源 | 页面行为 | 守恒规则 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 订单建议 | `requiredVehicles[]`、`suggestedSlots` | 标注“建议”,只作为车型/座位/数量参考 | 不决定最终槽位数 |
|
||||||
|
| 建议生成的初始槽位 | 前端未提交草稿 | 与新增草稿相同,显示删除入口 | 可见草稿槽位均可删除 |
|
||||||
|
| 车务新增槽位 | 前端未提交草稿 | 显示删除入口 | 可见草稿槽位均可删除 |
|
||||||
|
| 删除到 0 个 | 本地草稿为空 | 展示空态和“添加车辆槽位”,禁用下一步/提交 | 编辑态可为 0,提交态最少 1 |
|
||||||
|
| 最终批量提交 | `items[]` | 只提交车务最终保留的槽位,不补回已删除建议 | 可见最终槽位与 `items[]` 一一对应 |
|
||||||
|
| 已有有效派车 | `activeAssignments[]` | 不显示本地“删除草稿”;使用取消/改派动作 | 不静默撤销 `holding/assigned` |
|
||||||
|
| 最终进度 | `assignmentProgress` | `finalizedByFleet=true` 后按 `totalSlots` 展示 | 状态数量之和等于最终 `totalSlots` |
|
||||||
|
|
||||||
|
## 前端处理清单
|
||||||
|
|
||||||
|
- [ ] 移除 `slot.isNewDraft === true` 对删除按钮的唯一门控;建议生成的未提交初始槽位也必须可删除。
|
||||||
|
- [ ] 删除任一未提交槽位后同步清理该槽位的车辆、司机、逐日车费和跨常驻确认草稿,不能残留参与提交。
|
||||||
|
- [ ] 删除当前槽位后切到相邻槽位;删至 0 个时进入明确空态,不读取已删除槽位的 picker 状态。
|
||||||
|
- [ ] 0 槽位时保留“添加车辆槽位”,并禁用下一步/最终提交;不要向后端发送空 `items[]`。
|
||||||
|
- [ ] 最终 payload 只由当前可见槽位生成;不得按 `requiredVehicles[]` 数量补回已删除建议槽位。
|
||||||
|
- [ ] `requiredVehicles[]`、`suggestedVehicleCount`、`suggestedSlots` 的文案统一为“建议/参考”,不得展示为车务必配数量。
|
||||||
|
- [ ] 已有 `activeAssignments[]` 不走本地草稿删除;继续使用后端下发的取消/改派动作。
|
||||||
|
- [ ] 雪花 ID 继续按字符串消费,`fleetItemIndex` 不要求连续且不得因删除后重排而串到已有稳定槽位。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 本工单不修改 `hl-ui`,不在 `mmg/hl-ui` 建工单;`frontend_status` 保持 `pending`。
|
||||||
|
- 不修改派单状态机、车辆/司机占用、通知、保险、对账、逐日车费、Outbox 或数据库结构。
|
||||||
|
- 不放宽空批次:编辑态可删至 0,不等于后端接受空 `items[]`。
|
||||||
|
- 不允许通过漏传已有有效派车实现静默删除。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端最终方案能力来自已部署的 #5262;#5284 未新增运行时行为,因此 `gateway_status=not_required`。
|
||||||
|
- 定向回归 5 项通过:建议数与最终数可不同、遗漏建议占位不阻塞完成、已有在途槽位漏传阻断、看板建议/实派分离、空 `items[]` 拒绝。
|
||||||
|
- Fleet 全量 `test`:2431 项,0 失败、0 错误、1 跳过。
|
||||||
|
- Fleet 模块 `spotless:check`:通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify`:2431 项,0 失败、0 错误、1 跳过,JAR 构建成功。
|
||||||
|
- OpenAPI/oasdiff:`not_configured`;已人工比对路径、字段、类型和必填性,确认只有 Swagger/Javadoc 语义收口。
|
||||||
|
- Consumer Contract:`not_required`;没有 internal Feign 或 shared Java 变化。
|
||||||
|
- 前端状态:`pending`;领取后按 `pending → claimed → implemented → released → verified` 真实流转。
|
||||||
@ -0,0 +1,297 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5292"
|
||||||
|
title: "车务按服务日逐车配置用车、接机与价格"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "0a35da1e3b88f9c375d3e79516780e5a66f2b8ad"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T14:38:26+08:00"
|
||||||
|
status_note: "hl-admin 已修复可编辑 UNPLANNED/新槽位默认用车、ARRIVAL 参与状态保持及新增槽位候选有界加载;最终提交 0a35da1e 已在 origin/v2.1 可达,全量 checkpoint 通过"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:按服务日逐车配置用车、接机与价格
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **后端 PR**:[wx/HL#5296](https://git.1814.love:8443/wx/HL/pulls/5296)
|
||||||
|
> **Issue**:#5292
|
||||||
|
> **日期**:2026-07-27
|
||||||
|
> **影响范围**:管理后台车务看板派车弹窗和订单派车详情
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
新派车页面不再提交“收取车费日期”。派车保存改为提交完整的“服务日期 × 稳定车辆槽位”矩阵,每个单元格独立说明是否用车、车辆、司机、ARRIVAL 接机参与和当天实际价格。
|
||||||
|
|
||||||
|
旧 `items` 输入暂时保留用于滚动发布兼容;旧收费日期字段只能随旧 `items` 使用,`dailyPlan` 模式提交这些字段会被拒绝。
|
||||||
|
|
||||||
|
## 2026-07-28 产品口径补充:默认全部行程用车
|
||||||
|
|
||||||
|
新建派车草稿进入“排车”步骤时,当前需求的**全部可编辑服务日、全部新建稳定车辆槽位均默认勾选“当天用车”**。车务只需要选择车辆、司机和逐日价格;只有主动取消某日勾选或点击“明确不用车”,才表示该日明确不用车。
|
||||||
|
|
||||||
|
### 初始化规则
|
||||||
|
|
||||||
|
1. 后端 `planState=UNPLANNED` 表示尚未形成最终方案,不等于 `NOT_USED`。前端首次把可编辑 `UNPLANNED` 日格转换为草稿时应初始化 `used=true`,复选框默认选中,并显示“待选车辆/司机”或等价规划中状态。
|
||||||
|
2. 新增车辆槽位时,该槽位覆盖的全部可编辑服务日同样默认 `used=true`,不得全部初始化为未勾选。
|
||||||
|
3. 已有 `planFinalized=true` 的 `USED/NOT_USED`、只读日格、已关账日格和已有有效派车必须按服务端事实保留;已经明确保存为 `NOT_USED` 的日期重新打开时仍保持未选中,不得重新默认用车。
|
||||||
|
4. 默认选中只是前端草稿口径,不代表已经完成派车。未选择车辆、司机或逐日价格时仍不得提交,也不得伪造 `planFinalized=true`。
|
||||||
|
5. 用户主动取消“当天用车”时才写入草稿 `used=false/planState=NOT_USED`;某日所有槽位均不用车时继续执行 `confirmNoVehicleServiceDates=true` 二次确认。
|
||||||
|
|
||||||
|
### 当前前端偏差
|
||||||
|
|
||||||
|
前端提交 `7b99fe7d35530073176af1a0376cba7b33503319` 中,`createDailyVehiclePlan()` 在详情提供 `dailyVehiclePlan` 时把所有未最终确认日格按 `UNPLANNED + used=false` 直接用于草稿;`appendDailyVehiclePlanSlot()` 也把新槽位日格初始化为 `used=false`。因此页面出现整段行程“尚未规划”、所有“当天用车”均未选中的状态,与本次明确口径不符。
|
||||||
|
|
||||||
|
前端应区分“服务端基线状态”和“当前编辑草稿默认值”,不能通过把 `UNPLANNED` 改成服务端 `USED` 来伪造最终事实;仅在可编辑的新草稿层默认选中。
|
||||||
|
|
||||||
|
### 前端验收清单
|
||||||
|
|
||||||
|
- [ ] 4 天行程首次进入排车步骤时,4 个可编辑日格的“当天用车”全部默认选中。
|
||||||
|
- [ ] 候选尚未选定时显示待选车辆/司机而非“明确不用车”,且下一步仍因车辆、司机或价格缺失而受阻。
|
||||||
|
- [ ] 新增车辆槽位后,该槽位全部可编辑服务日也默认选中。
|
||||||
|
- [ ] 车务取消其中一天后,仅该日变为明确不用车,其余日期保持选中和已编辑草稿。
|
||||||
|
- [ ] 已保存的 `NOT_USED`、已有有效派车及只读日期重新打开后保持服务端事实,不被默认逻辑覆盖。
|
||||||
|
- [ ] 补充 `createDailyVehiclePlan()`、`appendDailyVehiclePlanSlot()` 和真实挂载 `FleetAssignModal` 的回归测试,覆盖首次进入、增加槽位、重新打开以及主动取消用车。
|
||||||
|
- [ ] 提交请求仍满足:每个 `used=true` 日格具备车辆、司机和价格;全天不用车时带明确二次确认。
|
||||||
|
|
||||||
|
## 2026-07-28 页面阻断:ARRIVAL 接机参与无法选中
|
||||||
|
|
||||||
|
真实页面复验中,服务日 **2026-08-04** 已标记“要求接机”,该日格已勾选“当天用车”、已选车辆和司机、无只读或锁定提示,但点击“参与 ARRIVAL 接机 / 接站”后复选框无法保持选中。该操作发生在派车草稿提交前,属于前端日格交互与状态同步阻断,不是后端保存接口拒绝。
|
||||||
|
|
||||||
|
当前 `origin/v2.1@e744c909` 中:
|
||||||
|
|
||||||
|
- `DailyVehiclePlanMatrix.vue` 的复选框仅发出 `update:pickup(key, checked)`;
|
||||||
|
- `AssignModal.vue` 的 `handleDailyPlanPickupUpdate()` 只将结果写入本地 `dailyPlan`;
|
||||||
|
- 现有 `daily-vehicle-plan-matrix.spec.js` 只断言子组件已发出事件,没有挂载 `FleetAssignModal` 验证父层接收后、候选状态 watcher 运行后以及切换日格后的值是否仍为 `true`。
|
||||||
|
|
||||||
|
因此,子组件事件测试通过不能作为页面可用证据。前端需要从浏览器事件开始逐段核对 `NCheckbox → update:pickup → handleDailyPlanPickupUpdate → dailyPlan → 提交 payload`,找出勾选值被丢弃或覆盖的位置;不得通过跳过逐日方案校验或伪造后端字段规避。
|
||||||
|
|
||||||
|
### 前端验收清单
|
||||||
|
|
||||||
|
- [ ] 对 `pickupRequired=true`、`used=true` 且可编辑的日格,点击后复选框立即选中,并在多轮 `nextTick`、候选状态刷新以及价格编辑后保持选中。
|
||||||
|
- [ ] 切换到其他服务日再返回,接机参与状态仍保留;再次点击可以明确取消。
|
||||||
|
- [ ] 更新只作用于当前 `serviceDate + fleetItemIndex`,不得串改同日其他车辆或其他服务日。
|
||||||
|
- [ ] 将该日改为不用车时自动清除 `pickupParticipant`;重新用车后由车务再次明确选择,不沿用陈旧值。
|
||||||
|
- [ ] 提交前的 `dailyPlan` 以及最终请求体均包含该日格 `pickupParticipant=true`;后端返回成功后详情回显一致。
|
||||||
|
- [ ] 要求接机的服务日至少一辆实际用车标记参与接机;未选择时继续显示既有校验提示,选择后提示收敛。
|
||||||
|
- [ ] 新增 `FleetAssignModal` 父子联动回归测试,覆盖事件接收、watcher 稳定、日格切换和 payload;不能只断言 `DailyVehiclePlanMatrix` 发出了事件。
|
||||||
|
- [ ] 页面 Console 无 `Maximum recursive updates exceeded` 或 `unhandledrejection`,复选框操作不得重新触发无界候选请求。
|
||||||
|
|
||||||
|
在该页面交互修复并完成真实浏览器复验前,`frontend_status` 保持 `claimed`,不得流转为 `implemented/released/verified`。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 原子提交最终派车方案 | POST | `/admin/fleet/assignments/batch` | 请求体扩展与校验调整 | 新增完整 `dailyPlan`,保留旧 `items` 兼容 |
|
||||||
|
| 2 | 车务看板订单详情 | GET | `/admin/fleet/board/orders/<orderId>` | 响应字段新增 | 返回逐日逐车计划、每车小计和订单车辆总计 |
|
||||||
|
|
||||||
|
## 二、原子提交最终派车方案
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/batch`
|
||||||
|
|
||||||
|
**请求 VO**:`BatchCreateAssignmentReqVO`
|
||||||
|
|
||||||
|
### 新增顶层字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `dailyPlan` | `DailyPlanItem[]` | 新页面必填 | 与旧 `items` 二选一;最多 4000 项 | 完整“服务日期 × 稳定车辆槽位”矩阵 |
|
||||||
|
| `confirmNoVehicleServiceDates` | `Boolean` | 条件必填 | 某日全部槽位 `used=false` 时必须为 `true` | 全天无需用车二次确认 |
|
||||||
|
|
||||||
|
`orderId`、`requirementId` 和所有 Long ID 继续按 JSON 字符串传输。
|
||||||
|
|
||||||
|
### `DailyPlanItem`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `fleetItemIndex` | `Integer` | 是 | `>= 0` | 稳定车辆槽位序号 |
|
||||||
|
| `serviceDate` | `String(date)` | 是 | `yyyy-MM-dd`,必须属于当前需求服务日 | 服务日期 |
|
||||||
|
| `used` | `Boolean` | 是 | - | 当天是否实际用车 |
|
||||||
|
| `vehicleId` | `String(Long)` | 条件必填 | `used=true` 必填;不用车必须为空 | 当天车辆 |
|
||||||
|
| `driverId` | `String(Long)` | 条件必填 | `used=true` 必填;不用车必须为空 | 当天司机 |
|
||||||
|
| `pickupParticipant` | `Boolean` | 否 | 仅表示 ARRIVAL 接机/接站;不用车不得为 `true` | 当天是否参与接机 |
|
||||||
|
| `assignmentPrice` | `String(decimal)` | 条件必填 | `used=true` 必填;非负、整数最多 10 位、小数最多 2 位 | 本车当天实际价格,可为 `0.00` |
|
||||||
|
| `priceAdjustmentReason` | `String` | 条件必填 | 最多 256 字;实际价偏离价格日历时必填 | 改价原因 |
|
||||||
|
| `confirmCrossResident` | `Boolean` | 否 | 跨常驻车时按既有规则确认 | 跨常驻车确认 |
|
||||||
|
|
||||||
|
### 正确请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "789",
|
||||||
|
"orderNo": "26-4165",
|
||||||
|
"requirementId": "790",
|
||||||
|
"startDate": "2026-08-04",
|
||||||
|
"endDate": "2026-08-05",
|
||||||
|
"headcount": 3,
|
||||||
|
"holdMode": 0,
|
||||||
|
"fromEntry": "from-board",
|
||||||
|
"requestId": "daily-plan-26-4165-v1",
|
||||||
|
"confirmNoVehicleServiceDates": false,
|
||||||
|
"dailyPlan": [
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"serviceDate": "2026-08-04",
|
||||||
|
"used": true,
|
||||||
|
"vehicleId": "701",
|
||||||
|
"driverId": "801",
|
||||||
|
"pickupParticipant": true,
|
||||||
|
"assignmentPrice": "800.00",
|
||||||
|
"priceAdjustmentReason": "首日短途优惠"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 1,
|
||||||
|
"serviceDate": "2026-08-04",
|
||||||
|
"used": true,
|
||||||
|
"vehicleId": "702",
|
||||||
|
"driverId": "802",
|
||||||
|
"pickupParticipant": false,
|
||||||
|
"assignmentPrice": "900.00",
|
||||||
|
"priceAdjustmentReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"serviceDate": "2026-08-05",
|
||||||
|
"used": false,
|
||||||
|
"vehicleId": null,
|
||||||
|
"driverId": null,
|
||||||
|
"pickupParticipant": false,
|
||||||
|
"assignmentPrice": null,
|
||||||
|
"priceAdjustmentReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fleetItemIndex": 1,
|
||||||
|
"serviceDate": "2026-08-05",
|
||||||
|
"used": true,
|
||||||
|
"vehicleId": "702",
|
||||||
|
"driverId": "802",
|
||||||
|
"pickupParticipant": false,
|
||||||
|
"assignmentPrice": "900.00",
|
||||||
|
"priceAdjustmentReason": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提交规则
|
||||||
|
|
||||||
|
- 每个保留槽位必须覆盖当前需求的全部服务日;同一 `fleetItemIndex + serviceDate` 不能重复。
|
||||||
|
- 同一服务日不能重复使用同一车辆或同一司机。
|
||||||
|
- `used=false` 不占用车辆/司机、不投保、不计费;车辆、司机、接机和价格必须为空或 false。
|
||||||
|
- 某日所有槽位均不用车时允许提交,但必须设置 `confirmNoVehicleServiceDates=true`。
|
||||||
|
- 大交通 ARRIVAL 要求平台接机时,当日至少一辆 `used=true` 的车辆必须 `pickupParticipant=true`。
|
||||||
|
- 大交通未要求接机时,仍允许人工标记一辆或多辆使用中的车辆参与接机。
|
||||||
|
- 连续日期使用相同车辆和司机时后端自动合并派车组;逐日价格仍分别冻结。
|
||||||
|
- 过去日期、已完结日期或已关账对账期不能修改。
|
||||||
|
- 新 `dailyPlan` 不得提交 `chargeableServiceDates`、`vehicleFeeWaiverReason`、`confirmAllServiceDatesFree`。
|
||||||
|
|
||||||
|
### 参数错误响应
|
||||||
|
|
||||||
|
本项目参数校验失败沿用 HTTP 200 + 业务 `code=400`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "dailyPlan 与旧 items 必须二选一,dailyPlan 不得提交旧收费日期字段",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、车务看板订单详情
|
||||||
|
|
||||||
|
### `GET /admin/fleet/board/orders/<orderId>`
|
||||||
|
|
||||||
|
**响应 VO**:`Result<BoardOrderDetailVO>`
|
||||||
|
|
||||||
|
### 新增响应字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `dailyVehiclePlan` | `DailyVehiclePlanVO[]` | 无记录返回 `[]` | 按服务日期、槽位序号稳定排序 |
|
||||||
|
| `vehicleFeeSummaries` | `VehicleFeeSummaryVO[]` | 无实际用车返回 `[]` | 按实际车辆汇总小计 |
|
||||||
|
| `vehicleFeeTotal` | `String(decimal)` | 无费用返回 `"0.00"` | 当前需求全部实际用车日总计 |
|
||||||
|
|
||||||
|
### `DailyVehiclePlanVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `serviceDate` | `String(date)` | 服务日期 |
|
||||||
|
| `fleetItemIndex` | `Integer` | 稳定槽位序号 |
|
||||||
|
| `assignmentSlotId` | `String(Long)` | 稳定槽位 ID |
|
||||||
|
| `assignmentId` | `String(Long)` | 每日切片 ID;显式不用车也返回占位 ID |
|
||||||
|
| `assignmentGroupId` | `String(Long)` | 当前连续派车组 ID |
|
||||||
|
| `planFinalized` | `Boolean` | 该日格是否已经业务最终确认 |
|
||||||
|
| `planState` | `String` | `UNPLANNED`(建议占位)/ `NOT_USED`(明确不用车)/ `USED`(实际用车) |
|
||||||
|
| `used` | `Boolean` | 当天是否实际用车;必须结合 `planFinalized/planState` 区分未规划与明确不用车 |
|
||||||
|
| `pickupParticipant` | `Boolean` | 当天车辆是否参与 ARRIVAL 接机 |
|
||||||
|
| `pickupRequired` | `Boolean` | 大交通当天是否要求接机 |
|
||||||
|
| `vehicleId` / `driverId` | `String(Long)` | 不用车时为 `null` |
|
||||||
|
| `vehiclePlate` / `vehicleModel` / `driverName` | `String` | 不用车时为 `null` |
|
||||||
|
| `driverPhone` | `String` | 脱敏手机号;不用车时为 `null` |
|
||||||
|
| `calendarPrice` | `String(decimal)` | 价格日历参考价;缺价或不用车时为 `null` |
|
||||||
|
| `assignmentPrice` | `String(decimal)` | 本车当天实际价;不用车时为 `null` |
|
||||||
|
| `priceSource` | `String` | `CALENDAR` / `OVERRIDE` / `MISSING` / `NOT_USED` |
|
||||||
|
| `priceAdjustmentReason` | `String` | 改价原因,无则 `null` |
|
||||||
|
| `assignmentStatus` | `String` | 基础派单状态;显式不用车为 `unassigned` |
|
||||||
|
| `readOnly` | `Boolean` | 过去、已完结或已关账日期为 `true` |
|
||||||
|
| `readOnlyReason` | `String` | `服务日期已过去` / `派单已完结` / `对账期已关账`,可编辑时为 `null` |
|
||||||
|
|
||||||
|
### `VehicleFeeSummaryVO`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `vehicleId` | `String(Long)` | 实际车辆 ID |
|
||||||
|
| `vehiclePlate` | `String` | 车牌 |
|
||||||
|
| `vehicleModel` | `String` | 车型 |
|
||||||
|
| `amount` | `String(decimal)` | 该车辆全部实际用车日小计 |
|
||||||
|
|
||||||
|
金额守恒:`vehicleFeeTotal = sum(vehicleFeeSummaries[].amount) = sum(dailyVehiclePlan[used=true].assignmentPrice)`。
|
||||||
|
|
||||||
|
## 四、前端改造清单
|
||||||
|
|
||||||
|
1. 删除派车弹窗中的“收取车费日期”和免费日期提交逻辑。
|
||||||
|
2. 按服务日渲染稳定车辆槽位矩阵;每格维护 `used`、车辆、司机、ARRIVAL 接机参与和当天实际价格。
|
||||||
|
3. 某日全不用车时显示二次确认,并提交 `confirmNoVehicleServiceDates=true`。
|
||||||
|
4. 默认价格使用详情/报价返回的日历价;修改价格时强制填写原因,允许 `0.00`。
|
||||||
|
5. 详情展示每辆车小计和订单车辆总计;金额以字符串解析,不能用浮点累计。
|
||||||
|
6. `readOnly=true` 的日格禁止编辑,并展示 `readOnlyReason`。
|
||||||
|
7. 新页面只提交 `dailyPlan`,不得同时提交旧 `items` 或收费日期字段。
|
||||||
|
|
||||||
|
## 五、数据库和历史数据
|
||||||
|
|
||||||
|
- `fleet_assignment.daily_vehicle_used`:`1` 表示当天实际用车,`0` 表示明确不用车;滚动发布期间允许 `NULL`,读取侧按车辆/司机事实回退,避免旧节点新写入被误判。
|
||||||
|
- `fleet_assignment.pickup_participant`:`1` 表示当天该车参与 ARRIVAL 接机/接站;滚动发布期间允许 `NULL` 并按 `false` 兼容。
|
||||||
|
- 历史已派日迁移为实际用车;原免费日实际价格迁为 `0.00`;未派占位迁为不用车;历史接机参与默认 `false`。
|
||||||
|
- 全程明确不用车可由 Fleet 以 `vehicleCount=0` 完成需求;Order 端车辆和司机快照均为空。
|
||||||
|
|
||||||
|
## 六、不影响范围
|
||||||
|
|
||||||
|
- 不修改 `hl-ui` 仓库,由本 changelog 交接前端。
|
||||||
|
- 不改变单派接口及旧 `items` 滚动兼容输入。
|
||||||
|
- 不把 DEPARTURE 送机/送站映射到 `pickupParticipant`。
|
||||||
|
- 不改变订单或产品价格日历接口。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR #5296 已 squash 合并至 `dev-v3`,`hl-order-service-v3` 与 `hl-fleet-service` 已滚动部署测试环境,双实例健康检查通过。
|
||||||
|
- Fleet 最新 `dev-v3` reactor verify:2452 项测试,0 failures,0 errors,1 skipped;Spotless、Jar、JaCoCo 均成功。
|
||||||
|
- Order 全量:6759 项测试,0 failures,0 errors,29 skipped;零车辆回调生产者/消费者及迁移定向回归通过。
|
||||||
|
- 真实测试网关 `GET /admin/fleet/board/orders/<orderId>`:HTTP 200、业务 code 200;返回 3 条 `dailyVehiclePlan`,包含 `planFinalized`、`planState`、`used`、接机、价格和只读字段;逐车汇总数组及字符串总计存在。
|
||||||
|
- 真实测试网关 `POST /admin/fleet/assignments/batch` 安全负例:`dailyPlan` 携带旧收费日期字段时 HTTP 200、业务 code 400,确认新旧模式互斥;该探针不产生业务写入。
|
||||||
|
- OpenAPI diff 与 Spring Cloud Contract 未配置;已通过源码字段对比、Controller/Service 及 Fleet → Order 双端普通测试提供人工回退证据,未冒充工具通过。
|
||||||
|
|
||||||
|
当前状态:
|
||||||
|
|
||||||
|
- `backend_status: deployed`
|
||||||
|
- `gateway_status: verified`
|
||||||
|
- `frontend_status: claimed`
|
||||||
|
|
||||||
|
关联 Issue:[wx/HL#5292](https://git.1814.love:8443/wx/HL/issues/5292)
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5299"
|
||||||
|
title: "车务矩阵车辆司机预选与常驻标记"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-admin"
|
||||||
|
frontend_ref: "45c713a8b30b13c9b57d102cfcce82885744a7e9"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T14:38:26+08:00"
|
||||||
|
status_note: "hl-admin 已修复 FleetAssignModal 递归更新、补齐车辆预选/常驻三态与车辆行实际司机汇总;最终提交 45c713a8 已在 origin/v2.1 可达,checkpoint 通过"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:矩阵车辆司机预选与常驻标记
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5299
|
||||||
|
> **影响页面**:管理后台车务管理 → 矩阵派单
|
||||||
|
> **兼容性**:仅新增响应字段,旧客户端可继续忽略
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
当前矩阵车辆行虽然已有常驻司机姓名,但缺少常驻司机稳定 ID;订单甘特条也没有直接返回该派车组的实际车辆、司机及常驻关系。页面因此无法稳定完成以下行为:
|
||||||
|
|
||||||
|
- 从具体车辆上下文发起派单时自动带出车辆和可派常驻司机;
|
||||||
|
- 行头展示车辆常驻司机;
|
||||||
|
- 已排订单条块展示实际执行司机,并区分常驻/临时司机。
|
||||||
|
|
||||||
|
本次后端补齐稳定读契约。前端不得按姓名判断常驻关系,也不得复制相邻订单的临时司机作为新派单默认值。
|
||||||
|
|
||||||
|
## 2026-07-28 前端运行态阻断:`FleetAssignModal` 递归更新
|
||||||
|
|
||||||
|
### 现场结论
|
||||||
|
|
||||||
|
前端实现提交 `bfcfafc69335f289071fe3e6dce74bacb636c55d` 已进入 `v2.1`,但当前测试页面打开逐日派车弹窗并加载车辆、司机候选后稳定出现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Maximum recursive updates exceeded in component <FleetAssignModal>
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器 Console 同时记录多次 `unhandledrejection`;Network 中多条 `POST /admin/fleet/assignments/candidates` 均返回 HTTP 200,但车辆、司机区域持续停留在 loading,无法进入下一步。因此本次不是候选接口、日期冲突或后端状态机错误,而是前端实现后的响应式自反馈回归。`frontend_status: "implemented"` 仅表示代码已存在,不代表已发布或页面闭环。
|
||||||
|
|
||||||
|
### 高可信自反馈链
|
||||||
|
|
||||||
|
当前 `AssignModal.vue` 的深度 watcher 同时观察 `candidateEvidence`、`candidateSelectionReady`、`autoSelectedResidentDriverSource` 等候选派生状态,并在回调 `syncActiveSlotSelection()` 中无条件重建 `dailyPlan`、回写当前槽位。`dailyPlan` 又参与计算其他槽位排除 ID、候选可选态和新的 `candidateEvidence`;即使业务值没有变化,新数组/对象身份仍会再次触发同一 watcher,形成“观察候选派生值 → 回写逐日方案 → 候选派生值重新计算 → 再次回写”的闭环。
|
||||||
|
|
||||||
|
前端修复必须同时满足:
|
||||||
|
|
||||||
|
1. `syncActiveSlotSelection()` 先比较当前日格与待写 payload;语义完全相同时直接返回,不创建新 `dailyPlan`/槽位对象。
|
||||||
|
2. watcher 使用稳定原始值或稳定 fingerprint,不深度监听会被自身回写间接失效的派生对象;候选响应、用户选择和槽位同步应有单向边界。
|
||||||
|
3. `updateDailyVehiclePlanCell()` 或等价更新器在无真实字段变化时返回原引用,禁止仅因对象重建触发后续 effect。
|
||||||
|
4. 保留 #5283 的稳定 `initializationIdentity` 与 `requestSeq` 旧响应隔离;不得退回监听 `props.order` 对象身份或通过删除并发保护掩盖循环。
|
||||||
|
5. 正常打开弹窗只允许一次初始候选请求;需要自动常驻司机二次校验时最多再请求一次。状态稳定后不得继续请求,loading 必须收敛。
|
||||||
|
|
||||||
|
### 前端回归验收
|
||||||
|
|
||||||
|
- [ ] 打开订单逐日派车弹窗并取得候选成功响应后,Console 不再出现 `Maximum recursive updates` 或相关 `unhandledrejection`。
|
||||||
|
- [ ] 候选请求数量有明确上界;自动常驻司机场景最多“初始查询 + 携两侧 ID 二次校验”,不存在持续请求。
|
||||||
|
- [ ] 车辆、司机列表结束 loading,接口返回的分页总数与页面一致,可正常进入下一步。
|
||||||
|
- [ ] 已选车辆、司机、逐日费用和跨常驻确认写回一次后保持稳定,多轮 `nextTick` 不再重建相同 `dailyPlan`。
|
||||||
|
- [ ] 相同业务身份的 SSE/列表对象替换仍保留候选与草稿;真实订单、需求、槽位或模式变化时才重新初始化。
|
||||||
|
- [ ] 新增真实挂载 `FleetAssignModal` 的回归测试,模拟候选成功和自动常驻二次校验,断言无未处理 Promise、请求次数有界且草稿稳定;仅做静态源码断言不足以验收。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/grid`
|
||||||
|
|
||||||
|
`data.vehicles[]` 新增:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `primaryDriverId` | `String(Long)` | 无常驻司机为 `null` | 车辆主档的权威常驻司机 ID |
|
||||||
|
|
||||||
|
既有 `primaryDriverName`、`primaryDriverPhone` 继续返回;手机号保持脱敏。三个字段共同用于行头展示,常驻判断以 ID 为准。
|
||||||
|
|
||||||
|
`data.vehicles[].assignments[]` 新增:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `vehicleId` | `String(Long)` | 未派为空 | 该甘特条对应派车组的实际车辆 ID |
|
||||||
|
| `vehiclePlate` | `String` | 未派为空 | 实际车牌 |
|
||||||
|
| `driverId` | `String(Long)` | 未派为空 | 该派车组实际司机 ID |
|
||||||
|
| `driverName` | `String` | 未派为空 | 该派车组实际司机姓名 |
|
||||||
|
| `residentMatch` | `Boolean` | 无实际司机为 `null` | `true`=实际司机是该车常驻司机;`false`=实际司机存在但不是该车常驻司机或车辆无常驻 |
|
||||||
|
|
||||||
|
矩阵继续排除取消切片;同一派车组存在取消日缺口时按有效连续服务段拆成多个甘特条,不得把外包络日期当作司机持续占用。
|
||||||
|
|
||||||
|
## 前端必须调整
|
||||||
|
|
||||||
|
### 1. 从矩阵车辆上下文发起派单
|
||||||
|
|
||||||
|
1. 将所在行 `vehicles[].id` 直接作为当前选择车辆,并在候选请求中传 `selectedVehicleId`。
|
||||||
|
2. 候选接口 `POST /admin/fleet/assignments/candidates` 会返回既有字段 `selectedVehicleResidentDriver`。
|
||||||
|
3. 仅当该快照存在且 `available=true` 时,才把其司机 ID 作为默认司机。
|
||||||
|
4. 无常驻司机、常驻司机冲突或不可派时,车辆仍保持预选,司机保持“待选择”,并展示候选返回的不可用原因。
|
||||||
|
5. 不得取前后相邻订单的实际司机作为新订单默认司机。
|
||||||
|
|
||||||
|
### 2. 矩阵司机展示
|
||||||
|
|
||||||
|
#### 2.1 车辆行司机汇总数据源
|
||||||
|
|
||||||
|
后端契约已经足够,前端不得新增接口或按车牌反查司机:
|
||||||
|
|
||||||
|
- 常驻司机取当前车辆行 `vehicles[].primaryDriverId`、`primaryDriverName`;常驻关系以 ID 为准。
|
||||||
|
- 订单实际司机只取同一车辆行当前响应中的 `vehicles[].assignments[].driverId`、`driverName`;这些是当前矩阵月份和筛选条件已加载的真实派车段。
|
||||||
|
- `assignments[]` 后端已按月内 `startDay` 升序返回。前端按响应数组顺序扫描,以司机首次出现的位置作为其他司机的稳定展示顺序,不按姓名另行排序,也不混入上一月份、上一筛选条件、未派窗口、候选列表或 `drivers` 页面数据。
|
||||||
|
- `residentMatch` 继续用于订单条块的常驻/临时三态标记;车辆行汇总去重使用稳定 `driverId`,不得只按姓名猜测同一人。
|
||||||
|
|
||||||
|
#### 2.2 汇总与展示规则
|
||||||
|
|
||||||
|
1. 若 `primaryDriverId` 非空,先把常驻司机放在结果第一项,显示 `primaryDriverName(常驻)`。ID 存在但姓名异常为空时使用 `姓名未标注(常驻)`,不得输出空白项。
|
||||||
|
2. 随后按 `assignments[]` 当前顺序遍历实际司机:`driverId` 或去空后的 `driverName` 为空则跳过;相同 `driverId` 只保留第一次出现。
|
||||||
|
3. 订单实际司机与 `primaryDriverId` 相同时,不再追加普通姓名,只保留第一项带“(常驻)”标记的展示。
|
||||||
|
4. 其他实际司机按首次出现顺序追加,使用中文逗号 `,` 连接。不得把同一司机跨多个订单或拆分派车段重复展示。
|
||||||
|
5. 无常驻司机但存在订单实际司机时,直接展示实际司机汇总,**不得只显示“无常驻司机”**。
|
||||||
|
6. 只有常驻司机和订单实际司机都不存在时,才显示“无常驻司机”。若行宽不足允许视觉省略,但必须通过 `title`、tooltip 或等价交互查看完整汇总,不得静默丢失司机。
|
||||||
|
|
||||||
|
展示样例:
|
||||||
|
|
||||||
|
| 常驻司机 | 当前行订单司机(按首次出现顺序) | 车辆行展示 |
|
||||||
|
|---|---|---|
|
||||||
|
| 张三 | 张三、李四、王五、李四、赵六 | `张三(常驻),李四,王五,赵六` |
|
||||||
|
| 无 | 李四、王五、李四 | `李四,王五` |
|
||||||
|
| 张三 | 张三、张三 | `张三(常驻)` |
|
||||||
|
| 无 | 空 | `无常驻司机` |
|
||||||
|
|
||||||
|
#### 2.3 订单条块保持既有语义
|
||||||
|
|
||||||
|
已排订单条块继续展示本条 `driverName`:
|
||||||
|
|
||||||
|
- `residentMatch=true`:标记“常驻”。
|
||||||
|
- `residentMatch=false`:标记“临时”。
|
||||||
|
- `residentMatch=null`:显示“待派司机”。
|
||||||
|
|
||||||
|
不要从 `drivers` 页面列表或姓名/车牌文本反推常驻关系;所有 Long ID 继续按字符串比较,禁止转为 JS `Number`。
|
||||||
|
|
||||||
|
#### 2.4 前端验收清单
|
||||||
|
|
||||||
|
- [ ] 截图中车辆无常驻司机但订单已有实际司机时,车辆行显示订单实际司机姓名,不再只显示“无常驻司机”。
|
||||||
|
- [ ] 常驻司机与多名订单司机并存时,展示严格为 `张三(常驻),李四,王五,赵六`,常驻第一且只出现一次。
|
||||||
|
- [ ] 同一实际司机出现在多个订单或多个有效派车段时只展示一次;空 ID、空姓名和未派条块不产生空白分隔项。
|
||||||
|
- [ ] 其他司机顺序跟随当前 `assignments[]` 首次出现顺序;切换月份、车队、车型或状态筛选后按新响应重新计算,不残留旧司机。
|
||||||
|
- [ ] 订单条块的 `driverName/residentMatch` 常驻、临时、待派展示不回归。
|
||||||
|
- [ ] 补充纯汇总函数或 `VehicleGantt` 组件测试,至少覆盖上表四组样例、Long ID 字符串去重和筛选响应替换。
|
||||||
|
- [ ] 真实页面复验行宽溢出场景可查看完整司机列表,Console 无异常,且不增加司机列表或候选接口请求。
|
||||||
|
|
||||||
|
在本节完成并通过真实矩阵页面复验前,`frontend_status` 保持 `claimed`,不得流转为 `implemented/released/verified`。
|
||||||
|
|
||||||
|
### 3. 同步完成 #5292 页面改造
|
||||||
|
|
||||||
|
用户截图仍出现“收取车费日期”,说明当前测试页面仍在使用旧构建或旧逻辑。新页面必须继续执行 #5292:
|
||||||
|
|
||||||
|
- 删除“收取车费日期”及旧免费日期提交逻辑;
|
||||||
|
- 使用 `dailyVehiclePlan` 渲染“服务日期 × 稳定车辆槽位”;
|
||||||
|
- 保存时只提交 `dailyPlan`,不得同时提交旧 `items`、`chargeableServiceDates`、`vehicleFeeWaiverReason` 或 `confirmAllServiceDatesFree`。
|
||||||
|
|
||||||
|
## 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "9007199254740993",
|
||||||
|
"plate": "蒙A-88888",
|
||||||
|
"primaryDriverId": "9007199254740994",
|
||||||
|
"primaryDriverName": "王师傅",
|
||||||
|
"primaryDriverPhone": "138****1234",
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"id": "9007199254740995",
|
||||||
|
"assignmentGroupId": "9007199254740996",
|
||||||
|
"vehicleId": "9007199254740993",
|
||||||
|
"vehiclePlate": "蒙A-88888",
|
||||||
|
"driverId": "9007199254740994",
|
||||||
|
"driverName": "王师傅",
|
||||||
|
"residentMatch": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
所有 Long ID 仍按 JSON 字符串处理,禁止转为 JS `Number`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改派单状态机、车辆/司机占用、保险、对账或常驻关系写入。
|
||||||
|
- 不自动选择不可派司机,不绕过候选接口与最终派单锁内校验。
|
||||||
|
- 不修改 `hl-ui` 仓库;前端消费状态独立流转。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5300](https://git.1814.love:8443/wx/HL/pulls/5300) 已 squash 合并至 `dev-v3`,合并提交 `fca8cedc6`。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康。
|
||||||
|
- 定向矩阵测试:43 tests,0 failures,0 errors,0 skipped。
|
||||||
|
- Fleet reactor verify:2456 tests,0 failures,0 errors,1 skipped;模块 Spotless、Jar、JaCoCo 成功。
|
||||||
|
- 真实测试网关 `GET /admin/fleet/matrix/grid?year=2026&month=8&season=active`:HTTP/code 200;返回 19 辆车、38 个派车段,新增车辆/司机字段完整,Long ID 均为字符串,`residentMatch` 三态合法,常驻司机手机号全部脱敏,无业务写入。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5299-gateway.json`,SHA-256 `d94ee78fbe52cc193a28e5bf182e1353d2eca9d87824b1c2eb526c62a4fe645d`。
|
||||||
|
- OpenAPI/oasdiff:项目尚未配置可复现 Swagger2→OAS3 与 oasdiff,状态为 `not_configured`;使用源码字段对比、Controller 序列化测试及真实网关响应作为人工回退证据,未冒充工具通过。
|
||||||
|
|
||||||
|
当前状态:后端已部署、网关已验证;前端仅有可达的部分实现 `bfcfafc6`,运行态复验及车辆行司机汇总均未通过,因此 source 状态保持 `claimed`;修复并通过上述页面验收前不得流转为 `implemented`、`released` 或 `verified`。
|
||||||
|
|
||||||
|
关联:#5299、#5292、#5283。
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "4933"
|
||||||
|
title: "矩阵空闲格候选排除车辆日期冲突"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "27b5161696add9bab725611900888488b95ff510"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T17:51:52+08:00"
|
||||||
|
status_note: "后端月度未派清单契约未变;本次交接前端按所选车辆空闲闭区间过滤候选。"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务矩阵:空闲格候选必须排除车辆日期冲突
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`(既有接口,无后端代码变更)
|
||||||
|
>
|
||||||
|
> **Issue**:#4933 的矩阵空闲格直接派单前端实现回归
|
||||||
|
>
|
||||||
|
> **日期**:2026-07-28
|
||||||
|
>
|
||||||
|
> **影响范围**:管理后台车务矩阵点击车辆空闲格后的“选择当前矩阵可派订单”抽屉
|
||||||
|
|
||||||
|
## 关键结论
|
||||||
|
|
||||||
|
`GET /admin/fleet/matrix/unassigned-orders` 的既有语义是返回当前年月及车型筛选下的**全局未派订单池**,不是针对某辆车某段空闲日期计算后的候选集合。前端点击空闲格时已经持有 `vehicleId`、`clickedDate`、`startDate`、`endDate` 和该车辆矩阵占用,但当前抽屉只按“未绑定车辆”过滤,因此会展示完整用车区间不在空闲区间内、甚至与该车已有派车日期重叠的订单。
|
||||||
|
|
||||||
|
本次不要求后端新增字段或改变全局未派池语义。前端必须在打开空闲格抽屉时,根据点击上下文和当前矩阵车辆占用过滤候选;派单提交时仍由后端在资源锁内执行最终冲突重校验。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 后端结构变化 | 前端正确消费方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| GET | `/admin/fleet/matrix/unassigned-orders` | 无;仍传 `year`、`month`、可选 `typeKeys[]`,仍返回当月全局有效未派行 | 仅作为候选数据源;进入某辆车空闲格抽屉前,结合点击上下文和该车矩阵占用做闭区间资格过滤 |
|
||||||
|
| GET | `/admin/fleet/matrix/grid` | 无 | 继续作为车辆、派车段和空闲区间的数据源,不新增前端猜测的返程日、缓冲日或状态 |
|
||||||
|
|
||||||
|
### 前端过滤不变量
|
||||||
|
|
||||||
|
1. 候选订单的全部有效 `serviceDateSegments[]` 必须完整落入所选空闲闭区间 `[startDate, endDate]`;只有后端未提供该数组字段时,才兼容使用订单 `startDate/endDate`。
|
||||||
|
2. 候选完整有效日期与所选车辆当前矩阵占用不得有任一日期重叠;首日、末日及边界同日均按占用处理。
|
||||||
|
3. 不允许为了让候选“可派”而把订单完整区间静默裁成空闲区间交集。候选不满足完整容纳条件时,应直接从抽屉排除。
|
||||||
|
4. 前端过滤不得替代创建或改派接口的后端锁内冲突校验;矩阵快照过期时以后端写侧拒绝为准。
|
||||||
|
5. 取消派车继续按矩阵现有有效性口径排除;不得自行增加返程后缓冲日或改变派车状态语义。
|
||||||
|
|
||||||
|
### 验收矩阵
|
||||||
|
|
||||||
|
以下日期均按闭区间判断:
|
||||||
|
|
||||||
|
| 场景 | 已有占用 | 候选完整区间 | 所选空闲区间 | 预期 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 候选起始日重叠 | 08-03~08-05 | 08-05~08-08 | 08-06~08-31 | 排除 |
|
||||||
|
| 候选结束日重叠 | 08-08~08-10 | 08-05~08-08 | 08-01~08-07 | 排除 |
|
||||||
|
| 候选包含已有占用 | 08-05~08-06 | 08-04~08-07 | 08-01~08-31 | 排除 |
|
||||||
|
| 候选被已有占用包含 | 08-03~08-09 | 08-04~08-07 | 08-01~08-31 | 排除 |
|
||||||
|
| 边界相邻但不重叠 | 08-03~08-05 | 08-06~08-07 | 08-06~08-31 | 保留 |
|
||||||
|
| 完整区间不在空闲区间 | 无额外占用 | 08-04~08-07 | 08-06~08-31 | 排除,不得裁成 08-06~08-07 |
|
||||||
|
| 无冲突且完整容纳 | 08-03~08-05 | 08-29~08-31 | 08-06~08-31 | 保留 |
|
||||||
|
|
||||||
|
## 运行复现
|
||||||
|
|
||||||
|
测试页面 `fleet/matrix` 的 2026-08 月矩阵中:
|
||||||
|
|
||||||
|
- 点击车辆 `蒙C05E05` 的 `2026-08-21` 空闲格;页面明确给出的空闲区间是 `2026-08-06~2026-08-31`。
|
||||||
|
- 抽屉仍显示订单 `26-4700`,完整区间为 `2026-08-04~2026-08-07`。
|
||||||
|
- 同一车辆既有订单 `26-4338` 的逐日用车详情显示 `2026-08-03`、`2026-08-04`、`2026-08-05` 均为已规划用车。
|
||||||
|
- 因而 `26-4700` 既未被空闲区间完整容纳,又在 08-04、08-05 与该车已有占用重叠,必须排除。
|
||||||
|
- 同抽屉订单 `26-5568` 的完整区间为 `2026-08-29~2026-08-31`,应继续保留。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 运行页面已复现当前失败:空闲格抽屉显示 `共 2 单`,其中包含不合格的 `26-4700`。
|
||||||
|
- 运行订单详情已核实车辆 `蒙C05E05` 在 08-03~08-05 的逐日用车事实,不是根据甘特位置猜测日期。
|
||||||
|
- `hl-ui origin/v2.1@e744c9096a6807439b3e4514a7687cf2f058b3fc`:
|
||||||
|
- `useFleetMatrixData.js` 调用月度未派接口时仅传 `year/month/typeKeys`;
|
||||||
|
- `index.vue` 的 `idleAssignableOrders` 仅过滤 `!order.vehicle`;
|
||||||
|
- `matrixAssignmentContext.js` 已构造车辆和空闲区间上下文,但当前只在选中订单后用于派单弹窗。
|
||||||
|
- `wx/HL origin/dev-v3@883fc61a006f6d7e40ca1c8723ffc6d28e37bf75`:`MatrixUnassignedReqVO` 只有 `year/month/typeKeys`,`MatrixService#queryUnassignedOrders` 按月返回全局未派行,当前行为符合既有接口契约。
|
||||||
|
- 前端修复、上述七类单测、发布及页面复核尚未执行,因此 `frontend_status` 保持 `pending`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改后端请求/响应字段、错误码或状态机。
|
||||||
|
- 不修改派单创建、改派的车辆锁、司机锁和闭区间冲突校验。
|
||||||
|
- 不修改全局“打开未派订单窗口”的月度未派池展示;本规则仅用于从具体车辆空闲格进入的候选抽屉。
|
||||||
|
- 不涉及生产或数据库写入。
|
||||||
@ -0,0 +1,580 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5295"
|
||||||
|
title: "Step3 聚合车辆费用"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "7cd0b151ce96e04206450d262ab5f0de4ee69ee6"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T21:20:30+08:00"
|
||||||
|
status_note: "hl-order-service-v3 已部署并通过网关验证;hl-admin 已改用 Step3 GET 聚合 vehicleFees,PUT 保存响应仅按成功/失败处理;全量 checkpoint 通过"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·管理后台】Step3 聚合车辆费用 (#5295)
|
||||||
|
|
||||||
|
> **PR**: #5297 | **更新时间**: 2026-07-28 11:26
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单 Step3 页面原来需要分别读取人员费用和车辆费用。本次把车辆费用聚合到 Step3 查询响应里:进入 Step3 时同屏拿到人员费用与车辆费用;保存人员费用时,同一次保存会校验车辆费用是否满足核单条件,保存接口响应只表达成功或失败。
|
||||||
|
|
||||||
|
## 2. 变更接口
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | Step 3 查询人员费用核单明细 | GET | `/v3/admin/order/:orderId/settlement/step3` | 修改接口 | 响应新增 `vehicleFees`,包含车辆费用顶层状态、总金额和逐日明细 |
|
||||||
|
| 2 | Step 3 录人员费用核单明细 | PUT | `/v3/admin/order/:orderId/settlement/step3` | 修改接口 | 保存人员费用时校验车辆费用;响应只表达成功或失败,`data` 不返回操作数据 ID |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 GET Step 3 查询人员费用核单明细
|
||||||
|
|
||||||
|
- **方法 + 路径**: `GET /v3/admin/order/:orderId/settlement/step3`
|
||||||
|
- **接口名**: Step 3 查询人员费用核单明细
|
||||||
|
- **使用场景**: 进入核单 Step3 页面时调用,展示人员费用表和车辆费用块。
|
||||||
|
- **认证**: 需要管理后台登录态;无权限或未登录按统一鉴权错误返回。
|
||||||
|
- **幂等性**: 幂等,只读查询。
|
||||||
|
- **限流**: 无接口级特殊限流。
|
||||||
|
- **响应类型**: `Result<SettlementStaffFeesSaveRespVO>`
|
||||||
|
|
||||||
|
### 3.2 PUT Step 3 录人员费用核单明细
|
||||||
|
|
||||||
|
- **方法 + 路径**: `PUT /v3/admin/order/:orderId/settlement/step3`
|
||||||
|
- **接口名**: Step 3 录人员费用核单明细
|
||||||
|
- **使用场景**: 用户保存 Step3 人员费用时调用。保存成功以 `code=200` 和 `message` 表达,`data` 不返回新增、更新、删除 ID 或车辆费用块。
|
||||||
|
- **认证**: 需要管理后台登录态和核单资金写权限。
|
||||||
|
- **幂等性**: 同一 `items` 内容重复提交,人员费用结果保持一致;车辆费用已冻结后再次保存仍按成功或失败返回。
|
||||||
|
- **限流**: 无接口级特殊限流。
|
||||||
|
- **响应类型**: `Result<Void>`
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 适用接口 | 说明 |
|
||||||
|
|------|------|------|----------|------|
|
||||||
|
| `orderId` | Long/String | 是 | GET、PUT | 订单 ID,必须大于 0;JSON 示例中按字符串展示,避免大整数精度问题 |
|
||||||
|
|
||||||
|
GET 无 Query 参数,无请求体。
|
||||||
|
|
||||||
|
### 4.2 PUT 请求体字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `items` | Array<StaffFeeItem> | 是 | 人员费用行数组,全量替换语义 | 数组字段必须存在;数组元素按下表校验 |
|
||||||
|
| `items[].id` | Long/String | 否 | 已存在行 ID;为空表示新增 | 已有行更新时传 |
|
||||||
|
| `items[].staffRole` | String | 是 | 人员角色 | 仅允许 `LEADER`、`DRIVER`、`GUIDE`、`PHOTOGRAPHER`、`OTHER` |
|
||||||
|
| `items[].staffId` | Long/String | 否 | 关联人员分配 ID | 多人聚合行可为空 |
|
||||||
|
| `items[].detail` | Object | 是 | 按 `staffRole` 区分的明细 JSON | 不能省略;各角色结构见下表 |
|
||||||
|
| `items[].reimburse` | Decimal/String | 否 | 小额报销金额 | 必须大于等于 0;为空按 0 处理 |
|
||||||
|
| `items[].paymentMethod` | String | 否 | 统一付款类型 | 仅允许 `CASH_PAID`、`COMPANY_PAID`、`SIGNED` |
|
||||||
|
| `items[].voucherUrls` | Array<String> | 否 | 人员费用凭证 URL 数组 | 最多 9 个;每个元素必须是 http/https URL,单个最多 1024 字符 |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 否 | 人员费用核单确认状态 | 仅允许 `UNCONFIRMED`、`CONFIRMED` |
|
||||||
|
| `items[].settleStatus` | String | 否 | 辅助人员结算状态 | 仅允许 `PENDING`、`COMPLETED`;主报账人行可为空 |
|
||||||
|
| `items[].settledDate` | String(date) | 否 | 辅助人员结算日期 | 格式 `YYYY-MM-DD` |
|
||||||
|
| `items[].transferRef` | String | 条件必填 | 辅助人员结算转账流水号 | `settleStatus=COMPLETED` 时必填,最多 128 字符 |
|
||||||
|
| `items[].remark` | String | 否 | 备注 | 最多 500 字符 |
|
||||||
|
|
||||||
|
### 4.3 `detail` 字段结构
|
||||||
|
|
||||||
|
| `staffRole` | `detail` 结构 | 必填说明 |
|
||||||
|
|-------------|---------------|----------|
|
||||||
|
| `DRIVER` | `days[]`(`service_date`、`vehicle_brief`、`daily_fee`、`is_used`、`note`),以及 `extra_cost`、`extra_breakdown[]` | `days[]` 必须存在;每个元素必须包含 `service_date`、`daily_fee` |
|
||||||
|
| `GUIDE` | `persons[]`(`name`、`days`、`per_day`、`note`) | `persons[]` 必须存在;每个元素必须包含 `name`、`days`、`per_day` |
|
||||||
|
| `PHOTOGRAPHER` | `persons[]`(`name`、`days`、`per_day`、`note`) | `persons[]` 必须存在;每个元素必须包含 `name`、`days`、`per_day` |
|
||||||
|
| `LEADER` | `days`、`per_day` | `days`、`per_day` 必须存在 |
|
||||||
|
| `OTHER` | `items[]`(`name`、`amount`、`note`) | `items[]` 用于其他人员费用明细 |
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
### 5.1 统一响应包装
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务状态码;`200` 表示成功 |
|
||||||
|
| `data` | Object/null | GET 成功时为 `SettlementStaffFeesSaveRespVO`;PUT 成功和失败时为 `null` |
|
||||||
|
| `message` | String | 响应消息 |
|
||||||
|
|
||||||
|
### 5.2 GET 成功响应 `data` 字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `totalActualCost` | String(decimal) | 人员费用实际成本合计 |
|
||||||
|
| `items` | Array<StaffFeeRespItem> | 人员费用明细行 |
|
||||||
|
| `vehicleFees` | Object | GET 本次新增:车辆费用块;无有效车辆需求时仍返回对象,`items=[]`、金额为 `0.00`、`frozen=false` |
|
||||||
|
|
||||||
|
### 5.3 `data.items[]` 人员费用明细
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String | 人员费用行 ID |
|
||||||
|
| `staffRole` | String | 人员角色:`LEADER`、`DRIVER`、`GUIDE`、`PHOTOGRAPHER`、`OTHER` |
|
||||||
|
| `staffId` | String/null | 关联人员分配 ID |
|
||||||
|
| `staffName` | String/null | 人员姓名快照 |
|
||||||
|
| `totalPlannedCost` | String(decimal) | 计划成本 |
|
||||||
|
| `totalActualCost` | String(decimal) | 实际成本 |
|
||||||
|
| `detail` | Object | 按 `staffRole` 区分的明细 JSON |
|
||||||
|
| `reimburse` | String(decimal) | 小额报销金额 |
|
||||||
|
| `paymentMethod` | String/null | 人员费用付款类型:`CASH_PAID`、`COMPANY_PAID`、`SIGNED` |
|
||||||
|
| `voucherUrls` | Array<String> | 凭证 URL 数组 |
|
||||||
|
| `settlementConfirmStatus` | String | 核单确认状态:`UNCONFIRMED`、`CONFIRMED` |
|
||||||
|
| `settleStatus` | String/null | 辅助人员结算状态:`PENDING`、`COMPLETED`;主报账人行可为空 |
|
||||||
|
| `settledDate` | String(date)/null | 辅助人员结算日期 |
|
||||||
|
| `transferRef` | String/null | 辅助人员结算转账流水号 |
|
||||||
|
| `isPrimaryReporter` | Boolean | 是否主报账人 |
|
||||||
|
| `remark` | String/null | 备注 |
|
||||||
|
|
||||||
|
### 5.4 `data.vehicleFees` 车辆费用顶层
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `frozen` | Boolean | 车辆费用是否已冻结;保存成功并冻结后,后续 GET 返回 `true` |
|
||||||
|
| `requirementId` | String/null | 当前车辆需求 ID;无有效车辆需求时为 `null` |
|
||||||
|
| `settlementReady` | Boolean | 车辆费用是否满足核单条件;为 `false` 时 PUT 可能返回 `584101` |
|
||||||
|
| `totalAmount` | String(decimal) | 车辆费用总金额 |
|
||||||
|
| `totalVehicleFee` | String(decimal) | 车辆费用总金额,兼容旧字段名;前端展示可读取该字段 |
|
||||||
|
| `items` | Array<VehicleFeeItem> | 车辆费用逐日明细;无有效车辆需求时为空数组 |
|
||||||
|
|
||||||
|
### 5.5 `data.vehicleFees.items[]` 车辆费用明细
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `sourceDetailId` | String/null | 逐日费用来源明细 ID;冻结后的旧数据可能为空 |
|
||||||
|
| `serviceDate` | String(date)/null | 逐日服务日期 |
|
||||||
|
| `assignmentGroupId` | String/null | 派车组 ID;逐日来源可为空 |
|
||||||
|
| `assignmentSlotId` | String/null | 派车明细 ID;逐日来源可为空 |
|
||||||
|
| `vehicleId` | String/null | 车辆 ID |
|
||||||
|
| `vehiclePlate` | String/null | 车牌号 |
|
||||||
|
| `vehicleModelId` | String/null | 车型 ID |
|
||||||
|
| `vehicleModelName` | String/null | 车型名称 |
|
||||||
|
| `vehicleModel` | String/null | 车型展示文本,兼容旧字段 |
|
||||||
|
| `driverId` | String/null | 司机 ID |
|
||||||
|
| `driverName` | String/null | 司机姓名 |
|
||||||
|
| `startDate` | String(date)/null | 费用服务开始日期;逐日费用通常等于 `serviceDate` |
|
||||||
|
| `endDate` | String(date)/null | 费用服务结束日期;逐日费用通常等于 `serviceDate` |
|
||||||
|
| `chargeableServiceDates` | Array<String(date)> | 计费服务日期列表 |
|
||||||
|
| `freeServiceDates` | Array<String(date)> | 免费服务日期列表 |
|
||||||
|
| `vehicleFeeWaiverReason` | String/null | 免车费原因 |
|
||||||
|
| `dailyPrice` | String(decimal) | 当日车费 |
|
||||||
|
| `paymentTypeCode` | String | 车辆费用付款类型编码:`CASH_PAID`、`SIGNED`、`COMPANY_PAID` |
|
||||||
|
| `paymentTypeName` | String/null | 车辆费用付款类型名称 |
|
||||||
|
| `amount` | String(decimal) | 本条核单金额 |
|
||||||
|
| `autoVehicleFeeTotal` | String(decimal) | 自动计算车辆费用金额 |
|
||||||
|
| `autoVehicleFeeComplete` | Boolean | 自动计算金额是否完整 |
|
||||||
|
| `vehicleFeeTotal` | String(decimal) | 本条车辆费用金额,兼容旧字段名 |
|
||||||
|
| `vehicleFeeSource` | String | 费用来源:`AUTO` 或 `MANUAL` |
|
||||||
|
| `vehicleFeeAdjustmentReason` | String/null | 手工调整原因 |
|
||||||
|
| `vehicleFeeAdjustedBy` | String/null | 手工调整人 ID |
|
||||||
|
| `vehicleFeeAdjustedAt` | String(datetime)/null | 手工调整时间 |
|
||||||
|
| `settlementReady` | Boolean | 本条车辆费用是否满足核单条件 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `paymentTypeCode`(车辆费用付款类型)
|
||||||
|
|
||||||
|
**所属字段**: `data.vehicleFees.items[].paymentTypeCode` | **类型**: `String` | **必填**: 是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `CASH_PAID` | 现付 | 车辆费用已由现场现金或等价方式支付 |
|
||||||
|
| `SIGNED` | 签单 | 车辆费用采用签单方式结算 |
|
||||||
|
| `COMPANY_PAID` | 公司支付 | 车辆费用由公司统一支付 |
|
||||||
|
|
||||||
|
### 6.2 `paymentMethod`(人员费用付款类型)
|
||||||
|
|
||||||
|
**所属字段**: `items[].paymentMethod`、`data.items[].paymentMethod` | **类型**: `String` | **必填**: 否
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `CASH_PAID` | 现付 | 人员费用已现场支付 |
|
||||||
|
| `COMPANY_PAID` | 公司支付 | 人员费用由公司支付 |
|
||||||
|
| `SIGNED` | 签单 | 人员费用采用签单方式 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `584100` | 车辆总车费暂时不可用 | GET 或 PUT Step3 读取车辆费用失败,或车辆费用响应与当前订单不匹配 |
|
||||||
|
| `584101` | 存在未完结派车或未确认车辆总车费,暂不能核单 | PUT Step3 保存时,当前车辆费用 `settlementReady=false`,或任一车辆费用明细未满足冻结条件 |
|
||||||
|
| `584102` | 当前用车需求没有可核单的车辆总车费 | PUT Step3 保存时存在有效车辆需求,但车辆费用明细为空 |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:GET Step3 返回 9 行车辆费用
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/settlement/step3
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"totalActualCost": "3600.00",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "9300000000001",
|
||||||
|
"staffRole": "DRIVER",
|
||||||
|
"staffId": "6800001001",
|
||||||
|
"staffName": "司机A",
|
||||||
|
"totalPlannedCost": "2100.00",
|
||||||
|
"totalActualCost": "2100.00",
|
||||||
|
"detail": {
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"service_date": "2026-07-29",
|
||||||
|
"vehicle_brief": "蒙A12345",
|
||||||
|
"daily_fee": "700.00",
|
||||||
|
"is_used": true,
|
||||||
|
"note": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra_cost": "0.00",
|
||||||
|
"extra_breakdown": []
|
||||||
|
},
|
||||||
|
"reimburse": "0.00",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED",
|
||||||
|
"settleStatus": "PENDING",
|
||||||
|
"settledDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"isPrimaryReporter": false,
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicleFees": {
|
||||||
|
"orderId": "2079454953641836546",
|
||||||
|
"frozen": false,
|
||||||
|
"requirementId": "2079000000000000001",
|
||||||
|
"settlementReady": true,
|
||||||
|
"totalAmount": "6780.00",
|
||||||
|
"totalVehicleFee": "6780.00",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"sourceDetailId": "2080000000000000001",
|
||||||
|
"serviceDate": "2026-07-29",
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"vehicleId": "300000000000000001",
|
||||||
|
"vehiclePlate": "蒙A12345",
|
||||||
|
"vehicleModelId": "400000000000000001",
|
||||||
|
"vehicleModelName": "商务车",
|
||||||
|
"vehicleModel": "商务车",
|
||||||
|
"driverId": "500000000000000001",
|
||||||
|
"driverName": "宝音德力格尔",
|
||||||
|
"startDate": "2026-07-29",
|
||||||
|
"endDate": "2026-07-29",
|
||||||
|
"chargeableServiceDates": ["2026-07-29"],
|
||||||
|
"freeServiceDates": [],
|
||||||
|
"vehicleFeeWaiverReason": null,
|
||||||
|
"dailyPrice": "700.00",
|
||||||
|
"paymentTypeCode": "COMPANY_PAID",
|
||||||
|
"paymentTypeName": "公司支付",
|
||||||
|
"amount": "700.00",
|
||||||
|
"autoVehicleFeeTotal": "700.00",
|
||||||
|
"autoVehicleFeeComplete": true,
|
||||||
|
"vehicleFeeTotal": "700.00",
|
||||||
|
"vehicleFeeSource": "AUTO",
|
||||||
|
"vehicleFeeAdjustmentReason": null,
|
||||||
|
"vehicleFeeAdjustedBy": null,
|
||||||
|
"vehicleFeeAdjustedAt": null,
|
||||||
|
"settlementReady": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceDetailId": "2080000000000000002",
|
||||||
|
"serviceDate": "2026-07-29",
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"vehicleId": "300000000000000002",
|
||||||
|
"vehiclePlate": "蒙A23456",
|
||||||
|
"vehicleModelId": "400000000000000002",
|
||||||
|
"vehicleModelName": "越野车",
|
||||||
|
"vehicleModel": "越野车",
|
||||||
|
"driverId": "500000000000000002",
|
||||||
|
"driverName": "阿拉坦",
|
||||||
|
"startDate": "2026-07-29",
|
||||||
|
"endDate": "2026-07-29",
|
||||||
|
"chargeableServiceDates": ["2026-07-29"],
|
||||||
|
"freeServiceDates": [],
|
||||||
|
"vehicleFeeWaiverReason": null,
|
||||||
|
"dailyPrice": "700.00",
|
||||||
|
"paymentTypeCode": "SIGNED",
|
||||||
|
"paymentTypeName": "签单",
|
||||||
|
"amount": "700.00",
|
||||||
|
"autoVehicleFeeTotal": "700.00",
|
||||||
|
"autoVehicleFeeComplete": true,
|
||||||
|
"vehicleFeeTotal": "700.00",
|
||||||
|
"vehicleFeeSource": "AUTO",
|
||||||
|
"vehicleFeeAdjustmentReason": null,
|
||||||
|
"vehicleFeeAdjustedBy": null,
|
||||||
|
"vehicleFeeAdjustedAt": null,
|
||||||
|
"settlementReady": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sourceDetailId": "2080000000000000003",
|
||||||
|
"serviceDate": "2026-07-29",
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"vehicleId": "300000000000000003",
|
||||||
|
"vehiclePlate": "蒙A34567",
|
||||||
|
"vehicleModelId": "400000000000000003",
|
||||||
|
"vehicleModelName": "中巴",
|
||||||
|
"vehicleModel": "中巴",
|
||||||
|
"driverId": "500000000000000003",
|
||||||
|
"driverName": "巴雅尔",
|
||||||
|
"startDate": "2026-07-29",
|
||||||
|
"endDate": "2026-07-29",
|
||||||
|
"chargeableServiceDates": ["2026-07-29"],
|
||||||
|
"freeServiceDates": [],
|
||||||
|
"vehicleFeeWaiverReason": null,
|
||||||
|
"dailyPrice": "860.00",
|
||||||
|
"paymentTypeCode": "CASH_PAID",
|
||||||
|
"paymentTypeName": "现付",
|
||||||
|
"amount": "860.00",
|
||||||
|
"autoVehicleFeeTotal": "860.00",
|
||||||
|
"autoVehicleFeeComplete": true,
|
||||||
|
"vehicleFeeTotal": "860.00",
|
||||||
|
"vehicleFeeSource": "AUTO",
|
||||||
|
"vehicleFeeAdjustmentReason": null,
|
||||||
|
"vehicleFeeAdjustedBy": null,
|
||||||
|
"vehicleFeeAdjustedAt": null,
|
||||||
|
"settlementReady": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:上例只展开 2026-07-29 的 3 行;同一订单还可能继续返回 2026-07-30、2026-07-31 的逐日车辆费用。验收样例中 3 天 x 3 司机共 9 行,合计 `6780.00`。
|
||||||
|
|
||||||
|
### 8.2 边界情况:无有效车辆需求时返回空未冻结块
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079576729147338754/settlement/step3
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"totalActualCost": "0.00",
|
||||||
|
"items": [],
|
||||||
|
"vehicleFees": {
|
||||||
|
"orderId": "2079576729147338754",
|
||||||
|
"frozen": false,
|
||||||
|
"requirementId": null,
|
||||||
|
"settlementReady": false,
|
||||||
|
"totalAmount": "0.00",
|
||||||
|
"totalVehicleFee": "0.00",
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 典型成功:PUT Step3 只返回成功结果
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079454953641836546/settlement/step3
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "9300000000001",
|
||||||
|
"staffRole": "DRIVER",
|
||||||
|
"staffId": "6800001001",
|
||||||
|
"detail": {
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"service_date": "2026-07-29",
|
||||||
|
"vehicle_brief": "蒙A12345",
|
||||||
|
"daily_fee": "700.00",
|
||||||
|
"is_used": true,
|
||||||
|
"note": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra_cost": "0.00",
|
||||||
|
"extra_breakdown": []
|
||||||
|
},
|
||||||
|
"reimburse": "0.00",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED",
|
||||||
|
"settleStatus": "PENDING",
|
||||||
|
"settledDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 业务失败:PUT 时车辆费用未满足核单条件
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/2079454953641836546/settlement/step3
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "9300000000001",
|
||||||
|
"staffRole": "DRIVER",
|
||||||
|
"staffId": "6800001001",
|
||||||
|
"detail": {
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"service_date": "2026-07-29",
|
||||||
|
"vehicle_brief": "蒙A12345",
|
||||||
|
"daily_fee": "700.00",
|
||||||
|
"is_used": true,
|
||||||
|
"note": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra_cost": "0.00",
|
||||||
|
"extra_breakdown": []
|
||||||
|
},
|
||||||
|
"reimburse": "0.00",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "UNCONFIRMED",
|
||||||
|
"settleStatus": "PENDING",
|
||||||
|
"settledDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584101,
|
||||||
|
"message": "存在未完结派车或未确认车辆总车费,暂不能核单",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**: 核单 Step3 页面查询和保存;页面需要同时展示人员费用与车辆费用时,直接使用 GET Step3 响应。
|
||||||
|
- **车辆费用可为空的场景**: 订单没有有效车辆需求时,GET Step3 返回 `vehicleFees.items=[]`、`frozen=false`、`settlementReady=false`、金额为 `0.00`。
|
||||||
|
- **PUT 保存门禁**: 存在有效车辆需求时,PUT Step3 会校验车辆费用是否满足核单条件;不满足时返回 `584101`,本次人员费用保存不视为成功。
|
||||||
|
- **空明细门禁**: 存在有效车辆需求但没有可核单车辆费用明细时,PUT Step3 返回 `584102`。
|
||||||
|
- **已冻结场景**: 车辆费用已冻结后,GET Step3 返回冻结后的 `vehicleFees`。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data`(PUT Step3) | 返回新增、更新、删除 ID 等操作数据 | 只表达成功或失败,成功时 `data=null` |
|
||||||
|
| `data.vehicleFees` | GET Step3 不返回 | GET Step3 返回车辆费用块 |
|
||||||
|
| `data.vehicleFees.frozen` | 无 | GET Step3 返回车辆费用是否冻结 |
|
||||||
|
| `data.vehicleFees.requirementId` | 无 | GET Step3 返回当前车辆需求 ID;无有效车辆需求时为 `null` |
|
||||||
|
| `data.vehicleFees.settlementReady` | 无 | GET Step3 返回车辆费用是否满足核单条件 |
|
||||||
|
| `data.vehicleFees.totalAmount` | 无 | GET Step3 返回车辆费用总金额 |
|
||||||
|
| `data.vehicleFees.totalVehicleFee` | 无 | GET Step3 返回车辆费用总金额兼容字段 |
|
||||||
|
| `data.vehicleFees.items[]` | 无 | GET Step3 返回逐日车辆费用明细 |
|
||||||
|
| `data.vehicleFees.items[].sourceDetailId` | 无 | GET Step3 返回逐日费用来源明细 ID |
|
||||||
|
| `data.vehicleFees.items[].serviceDate` | 无 | GET Step3 返回逐日服务日期 |
|
||||||
|
| `data.vehicleFees.items[].dailyPrice` | 无 | GET Step3 返回当日车费 |
|
||||||
|
| `data.vehicleFees.items[].paymentTypeCode` | 无 | GET Step3 返回车辆费用付款类型编码 |
|
||||||
|
| `data.vehicleFees.items[].paymentTypeName` | 无 | GET Step3 返回车辆费用付款类型名称 |
|
||||||
|
| `data.vehicleFees.items[].amount` | 无 | GET Step3 返回本条核单金额 |
|
||||||
|
| `data.vehicleFees.items[].settlementReady` | 无 | GET Step3 返回本条车辆费用是否满足核单条件 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 进入 Step3 页面 | 需要单独读取人员费用和车辆费用 | 调用 GET Step3 即可拿到人员费用与车辆费用 |
|
||||||
|
| 保存 Step3 | 只保存人员费用,响应可能携带操作数据 ID | 保存人员费用时同步校验车辆费用;成功响应 `data=null`,不返回新增、更新、删除 ID |
|
||||||
|
| 无有效车辆需求 | Step3 查询无法直接表达车辆费用空态 | GET Step3 内直接返回空未冻结 `vehicleFees` 块 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否。GET Step3 响应新增 `vehicleFees`,PUT Step3 成功响应 `data=null`。
|
||||||
|
- **前端是否必须同步上线**: 否,但建议管理后台 Step3 页面尽快切到 `data.vehicleFees`,并按 PUT Step3 成功响应不含操作数据 ID 处理。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚后前端表现**: 如果回滚到旧契约,GET Step3 不再包含 `data.vehicleFees`;前端需要保留对 `vehicleFees` 缺失的空值兼容。
|
||||||
|
- **前端兼容建议**: 读取 `data.vehicleFees` 前先判空;为空时按车辆费用空态展示。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 新 Step3 页面读取车辆费用时优先使用 `GET /v3/admin/order/:orderId/settlement/step3` 返回的 `data.vehicleFees`。
|
||||||
|
- `paymentTypeCode` 是车辆费用付款类型字段,枚举值为 `CASH_PAID`、`SIGNED`、`COMPANY_PAID`;不要用人员费用的 `paymentMethod` 去覆盖车辆费用字段。
|
||||||
|
- `totalAmount` 与 `totalVehicleFee` 都表示车辆费用总金额;为兼容旧页面,当前两者应按同一金额展示。
|
||||||
|
- `frozen=false` 不等于接口失败;无有效车辆需求或车辆费用尚未满足核单条件时都可能返回未冻结块。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5297](https://git.1814.love:8443/wx/HL/pulls/5297) 已合并至 `dev-v3`,合并提交 `5194183f6`。
|
||||||
|
- `dev-v3@ca23f64fc` 执行 `mvn -pl hl-order-service-v3 -am test` 通过。
|
||||||
|
- 测试环境滚动部署任务 `90374dab` 成功;`hl-order-service-v3` 8086/8186 双实例健康。
|
||||||
|
- 经测试网关只读调用 `GET /v3/admin/order/:orderId/settlement/step3`:HTTP 200、业务码 200,响应包含 `vehicleFees`,样本返回 9 条逐日费用明细。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5295-gateway.json`,SHA-256 `dafa5257c4df241e9511c95dba1f689397742133e9f46aefa4701a6d00228ab6`。
|
||||||
|
- OpenAPI/oasdiff:项目未配置可复现的 Swagger 2 到 OAS3 导出与 oasdiff,状态为 `not_configured`;已用 Controller/VO 源码对比、Controller 测试和真实网关响应完成人工回退核对。
|
||||||
|
- `frontend_status` 保持 `pending`;前端真实领取后再迁移为 `claimed` 并填写 `frontend_owner`。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5295](https://git.1814.love:8443/wx/HL/issues/5295)
|
||||||
|
- **PR**: [#5297](https://git.1814.love:8443/wx/HL/pulls/5297)
|
||||||
|
- **Merge commit**: [5194183f6](https://git.1814.love:8443/wx/HL/commit/5194183f6)
|
||||||
|
- **Feature commit**: [07d36dd39](https://git.1814.love:8443/wx/HL/commit/07d36dd3989789379388895b86a0954434013108)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **接口负责人**: @yaosutu
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5301"
|
||||||
|
title: "配车矩阵统一手动加急状态与统计"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "e1d0a44a734fefc4f12ff1b71d218df0b265dc62"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-28T14:38:26+08:00"
|
||||||
|
status_note: "hl-admin 已消费人工加急徽章、精确 statuses 筛选与 grid/month 五键统计;业务提交 e1d0a44a 已在 origin/v2.1 可达,checkpoint 通过"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:配车矩阵统一手动加急状态与统计
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5301
|
||||||
|
> **影响页面**:管理后台车务管理 → 配车矩阵
|
||||||
|
> **兼容性**:只新增可选请求参数与响应字段;路径、方法、既有字段、持久化状态不变
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
派车看板已经使用当前有效用车需求的 `manualUrgent` 派生人工加急,配车矩阵此前只计算临近出团与 HOLD 超时自动紧急态。同一需求因此可能在看板显示加急、矩阵仍显示普通待派/待确认,矩阵筛选和月份统计也无法精确区分人工加急。
|
||||||
|
|
||||||
|
本次后端统一矩阵全部读视图的有效状态来源,并提供稳定的人工加急字段、中文标签、精确状态筛选和五键计数。人工加急不修改落库派单状态。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/grid`
|
||||||
|
|
||||||
|
#### 1. 新增请求参数 `statuses`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| `statuses` | `String[]` | 否 | 按有效状态精确筛选,多值取并集;非空且至少含一个合法值时优先于旧 `status` |
|
||||||
|
|
||||||
|
合法值:
|
||||||
|
|
||||||
|
- `unassigned`
|
||||||
|
- `unassigned_urgent`
|
||||||
|
- `holding`
|
||||||
|
- `holding_urgent`
|
||||||
|
- `assigned`
|
||||||
|
- `completed`
|
||||||
|
|
||||||
|
兼容规则:
|
||||||
|
|
||||||
|
- 旧 `status=unassigned` 继续同时包含 `unassigned` 与 `unassigned_urgent`;
|
||||||
|
- 旧 `status=assigned` 继续包含 `holding`、`holding_urgent` 与 `assigned`;
|
||||||
|
- `statuses` 全空或全非法时回退旧 `status`;
|
||||||
|
- 未传二者时保持全部展示。
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/matrix/grid?year=2026&month=8&season=active&statuses=unassigned_urgent&statuses=holding_urgent
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `data.vehicles[].assignments[]` 新增字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 空值规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `manualUrgent` | `Boolean` | 固定 `true/false` | 当前有效用车需求是否人工加急;order-v3 上下文整体不可用时为 `false`,不得前端猜测 |
|
||||||
|
| `assignmentStatusLabel` | `String` | 正常非空 | 后端统一有效状态中文标签,例如“待派车”“待确认”“已派车” |
|
||||||
|
| `urgentBadge` | `String` | 非紧急为空 | 人工加急固定“手动加急”;自动紧急继续返回既有 T-N/HOLD 超时文案 |
|
||||||
|
|
||||||
|
有效状态规则:
|
||||||
|
|
||||||
|
- 基础态 `unassigned` 且 `manualUrgent=true` → `assignmentStatus=unassigned_urgent`;
|
||||||
|
- 基础态 `holding` 且 `manualUrgent=true` → `assignmentStatus=holding_urgent`;
|
||||||
|
- 人工加急优先于临近出团/HOLD 超时自动派生;
|
||||||
|
- `assigned`、`completed` 不因人工加急改变;`canceled` 继续排除;
|
||||||
|
- 只消费当前有效需求上下文,旧需求派单不会继承当前需求的人工加急。
|
||||||
|
|
||||||
|
#### 3. `data.statusCounts.effectiveStatusCounts` 新增固定五键
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"unassigned": 2,
|
||||||
|
"unassigned_urgent": 1,
|
||||||
|
"holding": 3,
|
||||||
|
"holding_urgent": 1,
|
||||||
|
"assigned": 4
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 五个键始终存在,缺类为 `0`;
|
||||||
|
- 每个活跃派车组只进入一个精确状态;
|
||||||
|
- 五键之和恒等于既有 `statusCounts.totalAssignments`;
|
||||||
|
- 既有 `unassignedAssignments`、`assignedAssignments` 和订单级统计保持兼容聚合口径,不删除、不改名。
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/month-counts`
|
||||||
|
|
||||||
|
每月 `statusCounts` 同样新增 `effectiveStatusCounts`:
|
||||||
|
|
||||||
|
- 固定返回 1–12 月,零值月份不省略;
|
||||||
|
- 同样固定五键;
|
||||||
|
- 与相同 `year/month/season/fleetTeamIds/typeKeys` 的 grid 顶部统计守恒;
|
||||||
|
- 年度读取仍为一次批量上下文,不循环产生 N+1。
|
||||||
|
|
||||||
|
## 前端必须调整
|
||||||
|
|
||||||
|
1. 派车条直接展示后端 `assignmentStatusLabel`;不要在前端维护第二套中文状态映射。
|
||||||
|
2. `manualUrgent=true` 且状态为 `unassigned_urgent/holding_urgent` 时,展示 `urgentBadge=手动加急`,颜色与派车看板人工加急保持一致。
|
||||||
|
3. 需要精确状态 Tab/筛选时改传 `statuses[]`;不要用旧 `status=unassigned` 期待只命中普通待派。
|
||||||
|
4. 精确分类数字使用 `statusCounts.effectiveStatusCounts`;既有全部/未派/已派聚合卡片可继续使用旧统计字段。
|
||||||
|
5. 月份切换统计直接使用 `month-counts[].statusCounts.effectiveStatusCounts`,不要前端遍历当前月卡片重算。
|
||||||
|
6. 继续保持 #5299 的实际车辆/司机/常驻标记和 Long ID 字符串处理;禁止转为 JS `Number`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改人工加急写接口或 order-v3 需求状态。
|
||||||
|
- 不修改派单落库状态、车辆/司机占用、费用、保险、对账或常驻关系。
|
||||||
|
- 不新增 Feign、数据库查询或逐订单 N+1。
|
||||||
|
- 不修改 `hl-ui` 仓库;前端消费状态独立流转。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5304](https://git.1814.love:8443/wx/HL/pulls/5304) 已 squash 合并至 `dev-v3`,合并提交 `ed56bec1f`。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康。
|
||||||
|
- 定向 `MatrixServiceTest,MatrixControllerTest,BoardCandidateSourceTest`:51 tests,0 failures,0 errors,0 skipped;覆盖人工加急、自动回退、精确筛选、五键守恒、当前需求门禁与全局降级。
|
||||||
|
- Fleet reactor verify:2474 tests,0 failures,0 errors,1 skipped;Spotless 628 Java files clean。
|
||||||
|
- 真实测试网关:grid 新字段完整且 `manualUrgent` 全为非空 Boolean;五键固定且和等于 `totalAssignments`;`statuses=assigned` 在旧 `status=unassigned` 同时传入时仍只返回 assigned,证明精确筛选优先;month-counts 固定 12 月且同月五键与 grid 相等;Long ID 为字符串、手机号脱敏、全程无业务写入。
|
||||||
|
- 当前测试矩阵数据没有 `manualUrgent=true` 的活跃派车组,因此真实网关未伪报人工加急正例;正例由 Service/Controller 定向测试覆盖。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5301-gateway.json`,SHA-256 `6ae185282e2194a63168298168888f4c03086916b7ad91268b24ac133b2b363d`。
|
||||||
|
- OpenAPI/oasdiff:项目未配置可复现 Swagger2→OAS3 与 oasdiff,状态为 `not_configured`;使用源码字段对比、Controller/Service 测试及真实网关响应作为人工回退证据。Spring Cloud Contract 为 `not_required`。
|
||||||
|
|
||||||
|
当前状态:后端已部署、网关已验证,前端消费保持 `pending`。
|
||||||
|
|
||||||
|
关联:#5301、#5299。
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5302"
|
||||||
|
title: "派车档期按完整组判定同城衔接"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "仅修正候选与预校验的既有冲突语义,不新增字段或页面动作;前端继续按后端 available、availabilityReasonCode、conflicts 与 conflict 渲染即可"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:派车档期按完整组判定同城衔接
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5302
|
||||||
|
> **影响页面**:管理后台车务管理 → 派单候选弹窗、保存前预校验
|
||||||
|
> **兼容性**:路径、方法、请求/响应字段和错误码形状不变,仅修正既有业务语义
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
派车数据已按服务日期拆成逐日切片。同一辆车或司机已有 D1–D3 多日派车时,旧实现只读取新请求窗口内命中的切片;若新请求只查内部日 D2,D2 会被误当作旧派车组的真实首日/末日,并可能错误套用 R3-EX“真实首尾同城可衔接”,把双重占用误报为可共享。
|
||||||
|
|
||||||
|
本次后端在初筛命中后一次批量补齐同 `assignmentGroupId` 的全部有效切片,再按完整组真实首日、末日及首接客地、末送客地判断冲突。候选咨询、保存前预校验及最终写侧重校验使用同一口径。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/candidates`
|
||||||
|
|
||||||
|
响应形状不变,以下既有字段的语义修正:
|
||||||
|
|
||||||
|
| 场景 | `available` | `availabilityReasonCode` | `conflicts[].blocking` | `conflicts[].cityJunctionShareCandidate` |
|
||||||
|
|---|---:|---|---:|---:|
|
||||||
|
| 请求日位于已有多日派车组内部 | `false` | `ASSIGNMENT_CONFLICT` | `true` | `false` |
|
||||||
|
| 请求日仅与完整组真实首日或末日相接,且接送城市满足 R3-EX | `true` | `CITY_JUNCTION_SHAREABLE` | `false` | `true` |
|
||||||
|
| 无重叠 | `true` | `AVAILABLE` | - | - |
|
||||||
|
|
||||||
|
内部日冲突的 `conflicts[].startDate/endDate` 返回完整有效派车组范围;`availabilityWindows` 按该真实阻断范围扣减,不再把内部日当成可用窗口。
|
||||||
|
|
||||||
|
车辆和司机分别采用同一口径。仅查询 `holding/assigned` 活跃切片;历史空 `assignmentGroupId` 数据继续按原记录区间判断。
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/precheck`
|
||||||
|
|
||||||
|
响应形状不变:
|
||||||
|
|
||||||
|
- 内部日双重占用返回 `data.conflict=true`;
|
||||||
|
- `data.conflicts[]` 的车辆、司机冲突日期段均为完整有效派车组范围;
|
||||||
|
- 内部日 `cityJunctionShareCandidate=false`;
|
||||||
|
- 真实首尾同城衔接仍按既有 R3-EX 返回非阻断结果。
|
||||||
|
|
||||||
|
最终创建、改派、车务确认及撤销取消恢复仍在资源锁内重新校验,不信任前端咨询结果;本次同步修正这些写侧入口,避免候选正确但最终写入口径不同。
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
无需修改前端代码或请求参数:
|
||||||
|
|
||||||
|
1. 继续以候选返回的 `available` 和 `availabilityReasonCode` 控制可选状态与原因展示;
|
||||||
|
2. 继续以预校验返回的 `conflict` 决定是否阻止提交;
|
||||||
|
3. 不要在前端自行按单日日期或城市覆盖后端冲突结果;
|
||||||
|
4. 已有 `ASSIGNMENT_CONFLICT`、`CITY_JUNCTION_SHAREABLE` 展示分支可直接消费修正后的数据。
|
||||||
|
|
||||||
|
因此 `frontend_status=not_required`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不新增或删除 API 字段,不修改 Long ID 字符串、手机号脱敏及错误码契约。
|
||||||
|
- 不修改派单状态机、占用写入、保险、价格、对账或车辆/司机常驻关系。
|
||||||
|
- 不将 `completed/canceled` 切片重新计入活跃占用。
|
||||||
|
- 不修改 `hl-ui` 仓库。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5303](https://git.1814.love:8443/wx/HL/pulls/5303) 已 squash 合并至 `dev-v3`,合并提交 `d114232ad`。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康。
|
||||||
|
- 定向 `AssignmentServiceTest,AssignmentCandidateServiceTest,FleetAssignmentMapperTest`:342 tests,0 failures,0 errors,0 skipped。
|
||||||
|
- Fleet reactor verify:2466 tests,0 failures,0 errors,1 skipped;Spotless 628 Java files clean。
|
||||||
|
- 真实测试网关选择一个 D1–D4 活跃派车组的内部日 D2 调用候选与预校验:HTTP/code 200;车辆与司机均 `available=false`、`ASSIGNMENT_CONFLICT`、`blocking=true`、`cityJunctionShareCandidate=false`,冲突范围返回完整 D1–D4;全程无业务写入。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5302-gateway.json`,SHA-256 `b482a78d4129998c0ca3a8ee0e9a10ee63dfb25239fe1bd041b17f51043d4524`。
|
||||||
|
- OpenAPI/oasdiff:项目未配置可复现 Swagger2→OAS3 与 oasdiff,状态为 `not_configured`;使用源码对比、定向测试和真实网关响应作为人工回退证据。Spring Cloud Contract 为 `not_required`。
|
||||||
|
|
||||||
|
当前状态:后端已部署、网关已验证,前端无需改造。
|
||||||
|
|
||||||
|
关联:#5302。
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5305"
|
||||||
|
title: "逐日方案重提与完成闭环不变量"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "仅修正既有逐日派车保存与需求完成事务语义;接口路径、方法、请求响应字段和前端交互均不变"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:逐日方案重提与完成闭环不变量
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5305
|
||||||
|
> **影响页面**:管理后台车务管理 → 逐日逐车派车方案保存
|
||||||
|
> **兼容性**:接口路径、方法、请求/响应字段、错误码和数据库结构不变,仅修正既有事务语义
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
逐日派车支持重复提交最终方案。旧实现存在五类边界:全程不用车或没有实际创建命令时会绕过最终订单基线;留车/直派模式变化可能被误判为无变化;混合方案中的历史已完结切片可能因旧冻结标记缺失阻断需求完成;仅修改逐日价格原因不会落库;同槽索引的旧取消版本可能抢占当前稳定槽位。
|
||||||
|
|
||||||
|
本次后端统一修复上述重提、冻结和完成闭环,不新增前端参数或返回字段。
|
||||||
|
|
||||||
|
## 变更接口(既有语义修正)
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/batch`
|
||||||
|
|
||||||
|
请求和响应形状不变:
|
||||||
|
|
||||||
|
1. 包含 `dailyPlan` 的保存会在资源锁内完成写入后、冻结最终方案和生成需求完成 Outbox 前,再次读取当前订单详情、行程和 active 用车需求;日期、人数、行程或需求版本漂移时整批事务回滚。
|
||||||
|
2. 即使全部 `dailyPlan[].used=false`,或本次没有实际创建车辆/司机派单,也执行同一最终基线门禁。
|
||||||
|
3. 重提时同时比较目标 `holdMode`、派单状态、车辆、司机、接机参与、逐日价格和逐日价格调整原因:
|
||||||
|
- `holding → direct` 会按直派目标重新落地;
|
||||||
|
- `assigned → hold` 会按留车目标重新落地;
|
||||||
|
- 原因新增、修改和在不再偏离日历价时清空不再被忽略;偏离日历价时原因必填规则保持不变。
|
||||||
|
4. 历史 `completed` 逐日切片即使来自旧数据、没有 `dispatchPlanFinalized=1`,在同一矩阵已有显式冻结切片时也按不可变的已完成权威日处理;不修改其业务状态,也不会把纯旧版未冻结拓扑误认成新最终方案。
|
||||||
|
5. 同一 `fleetItemIndex` 同时存在旧 `canceled` 版本和当前在途/非取消版本时,稳定槽位优先沿用当前版本,避免重写命中历史槽位。
|
||||||
|
|
||||||
|
所有失败仍沿用现有错误响应结构;事务失败不产生需求完成事件。
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
无需修改前端代码:
|
||||||
|
|
||||||
|
- 继续提交现有 `dailyPlan`、`holdMode` 和逐日价格原因;
|
||||||
|
- 基线漂移时继续展示后端现有失败提示并刷新后重试;
|
||||||
|
- 不需要新增字段、状态分支或页面组件。
|
||||||
|
|
||||||
|
因此 `frontend_status=not_required`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 Controller、VO/DTO/BO、Feign 或 shared Java 契约。
|
||||||
|
- 不修改数据库字段、迁移、Long ID 字符串和手机号脱敏规则。
|
||||||
|
- 不改变已完成/已取消业务状态,不放宽历史、完结或关账只读门禁。
|
||||||
|
- 不修改 `hl-ui` 仓库。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR `[wx/HL#5306](https://git.1814.love:8443/wx/HL/pulls/5306)` 已 squash 合并至 `dev-v3`,合并提交 ``594d932db``。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康。
|
||||||
|
- 定向 `AssignmentServiceTest,FleetAssignmentMapperTest`:328 tests,0 failures,0 errors,0 skipped。
|
||||||
|
- Fleet reactor verify:2480 tests,0 failures,0 errors,1 skipped;Spotless 628 Java files clean。
|
||||||
|
- 独立 Reviewer 无未解决 P0–P1。
|
||||||
|
- 真实测试网关通过登录和车务矩阵只读查询确认部署后网关、认证及 Fleet 服务链路正常;本次内部事务修正没有可安全构造的写侧正例,核心五类边界由定向测试覆盖,探针不产生业务写入。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5305-gateway.json`,SHA-256 ``4698b3b8b1765715f6dc823664b3d24df2e977d26e6e19da08b5e298c6b6b0f6``。
|
||||||
|
- 契约审查:`not_required`;当前 diff 无 Controller、请求响应模型、Feign 或 shared Java 变化。
|
||||||
|
|
||||||
|
当前状态:后端已部署、网关兼容探针已通过,前端无需改造。
|
||||||
|
|
||||||
|
关联:#5305。
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5307"
|
||||||
|
title: "最终不用车稳定槽看板状态与筛选"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "后端 PR #5309 已合并并部署测试环境;既有字段的稳定槽聚合与状态筛选语义已修正,前端无需新增字段或改造"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:最终不用车稳定槽看板状态与筛选
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5307
|
||||||
|
> **影响页面**:管理后台车务看板、配车矩阵、未派窗口及当天清单
|
||||||
|
> **兼容性**:接口路径、方法、请求参数和响应字段均不变化;仅修正既有状态、代表资源、统计和筛选语义
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
逐日最终方案允许同一稳定车辆槽位部分日期实际用车、其他日期明确不用车。此前最终不用车切片仍以基础状态 `unassigned` 持久化,会覆盖同槽真实已派切片,导致看板误显示待派、代表车辆/司机为空,并使状态筛选和矩阵统计出现幽灵待派。
|
||||||
|
|
||||||
|
本次统一识别 `dispatchPlanFinalized=1,dailyVehicleUsed=0` 的最终不用车事实。该事实不占车辆/司机、不投保、不计费,也不能重新开放普通派车动作。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### 车务看板 `/admin/fleet/board/orders`
|
||||||
|
|
||||||
|
- 同一稳定槽存在实际用车日和最终不用车日时,槽位状态、代表车辆和代表司机来自真实用车切片;稳定槽日期范围仍覆盖全部当前有效服务日。
|
||||||
|
- 全日期均最终不用车时,仅在看板只读聚合中归入“最终方案已完成”的既有 `assigned` 分面;数据库业务状态仍保持 `unassigned`,不会伪造派车、取消或完结事件。
|
||||||
|
- 全日期最终不用车记录固定关闭 `canAssign`、`canRejectRequirement`、`availableActionCodes` 和紧急徽章,不开放重复派车或需求驳回。
|
||||||
|
- `assignmentProgress` 按权威稳定槽守恒:混合槽和全最终不用车槽不再计入 `unassignedSlots`。
|
||||||
|
- `status/statuses` 先取得基础态候选超集,输出前再按最终稳定槽状态复筛;`assigned` 与 `unassigned` 互斥筛选不再返回同一需求。
|
||||||
|
- 大候选游标扫描会在 5,000 安全上限前排除普通待派,只保留可能聚合为最终方案完成的 final-unused 候选。
|
||||||
|
|
||||||
|
### 配车矩阵 `/admin/fleet/matrix/*`
|
||||||
|
|
||||||
|
- 最终不用车切片不进入车辆甘特条、顶部待派统计、月份统计、未派窗口、当天待派清单、并行派车或衔接计算。
|
||||||
|
- 混合槽仍展示所有真实用车车辆段及实际司机,不由无车切片覆盖。
|
||||||
|
- 全日期最终不用车不会产生幽灵待派;详情接口原有 `dailyVehiclePlan[].planState=NOT_USED` 事实保持不变。
|
||||||
|
- 普通未最终化 `unassigned`、`holding/assigned/completed`、人工加急五键统计、Long ID 字符串和手机号脱敏均保持原语义。
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
无需新增字段或修改请求。继续直接消费后端现有:
|
||||||
|
|
||||||
|
- `assignmentStatus`、`assignmentStatusLabel`
|
||||||
|
- `canAssign`、`canRejectRequirement`、`availableActionCodes`
|
||||||
|
- `assignmentProgress.finalizedByFleet`
|
||||||
|
- `assignmentSlots[]`
|
||||||
|
- 矩阵 `statusCounts` 与 `effectiveStatusCounts`
|
||||||
|
- 详情 `dailyVehiclePlan[].planState`
|
||||||
|
|
||||||
|
不要根据基础 `unassigned`、车辆为空或司机为空自行覆盖后端返回的稳定槽状态和操作权限。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改派单五态数据库状态机。
|
||||||
|
- 不修改逐日方案写入、车辆/司机占用、费用、保险、对账或完成回调。
|
||||||
|
- 不新增 Feign、数据库查询或逐订单 N+1。
|
||||||
|
- 不修改 `hl-ui`;本次为既有字段语义纠正,前端状态为 `not_required`。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5309](https://git.1814.love:8443/wx/HL/pulls/5309) 已 squash 合并至 `dev-v3`,合并提交 `ca23f64fc`。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康。
|
||||||
|
- 定向核心:395 tests,0 failures,0 errors;Reviewer 上限回归 `BoardOrderServiceTest` 59 tests 通过。
|
||||||
|
- Fleet reactor verify:2487 tests,0 failures,0 errors,1 skipped;Spotless 629 Java files clean。
|
||||||
|
- 独立 Reviewer 首轮发现 assigned 候选超集可能提前触发 5,000 上限;修复并补充“5,000 普通待派 + 1 最终不用车”游标测试后,终审无 P0–P2。
|
||||||
|
- 真实测试网关只读验证:矩阵精确状态筛选仅返回请求状态、五键统计守恒;看板 assigned/unassigned 返回状态与筛选一致且需求集合互斥;已完成最终方案未进入 unassigned;Long ID 仍为字符串、手机号保持脱敏,全程无业务写入。
|
||||||
|
- 测试环境 API 未暴露内部 final-unused 标记且当前样本未确认存在合法混合槽,因此未伪报真实网关正例;混合槽和全 final-unused 正例由 Service 定向测试覆盖。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5307-gateway.json`,SHA-256 `699363c3a06b0d40205ea477c2a0a67daadc2e1718d08ba1102733e66cf2d505`。
|
||||||
|
- OpenAPI 与 Spring Cloud Contract:`not_required`,因为 Controller、DTO/VO/BO、Feign、shared Java、枚举及错误码形状均未变化。
|
||||||
|
|
||||||
|
当前状态:后端已部署、网关已验证,前端无需改造。
|
||||||
|
|
||||||
|
关联:#5307、#5292、#5301。
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5308"
|
||||||
|
title: "最终派车方案取消恢复代际"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "仅修正 Fleet 内部最终方案身份、取消恢复与需求完成语义;接口路径、请求响应字段和前端交互均不变"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:最终派车方案取消恢复代际
|
||||||
|
|
||||||
|
> **服务**:`hl-fleet-service`
|
||||||
|
> **Issue**:#5308
|
||||||
|
> **影响页面**:管理后台车务管理 → 逐日派车方案保存、手工取消/恢复、DIRECT/HOLD 改派
|
||||||
|
> **兼容性**:接口路径、方法、请求/响应字段、错误码及业务五态不变;仅修正内部最终方案身份和需求完成闭环
|
||||||
|
|
||||||
|
## 问题与目标
|
||||||
|
|
||||||
|
自定义最终方案完成后,手工取消某个稳定车辆槽位会触发需求重开,恢复后应重新完成。旧实现只有布尔型最终标记,无法区分当前方案、合法改派 tombstone 和后续重提的新方案;多槽部分取消时,剩余槽位可能被误判为新的完整方案,旧取消行也可能在新方案后被错误恢复。
|
||||||
|
|
||||||
|
本次为 Fleet 内部最终派车方案增加持久化代际。代际是数据库内部不透明令牌,不进入 API、完成回调或前端状态。
|
||||||
|
|
||||||
|
## 变更接口(既有语义修正)
|
||||||
|
|
||||||
|
### `POST /admin/fleet/assignments/batch`
|
||||||
|
|
||||||
|
请求和响应形状不变:
|
||||||
|
|
||||||
|
1. 每次最终方案提交会按精确 `assignmentIds` 将全部当前可变切片标记为同一新代,并严格核对实际更新行数;精确重提、仅价格原因重提和 partial daily rewrite 均按新当前代收敛。
|
||||||
|
2. 当前方案按 `stable slotId × serviceDate` 全拓扑校验。多个非空 current generation、当前代外 active 行、孤儿 generation,以及存在退休历史但没有可解析非空 current 代的场景均 fail closed,不再回退为订单建议数量的“看似完整”方案。
|
||||||
|
3. 滚动发布窗口仅兼容“单一非空 current generation + `dispatchPlanFinalized=1` 的 legacy null 行”;这些行按同代完整拓扑校验,不按 generation 数值大小推断先后。
|
||||||
|
|
||||||
|
### 既有取消、恢复与改派接口
|
||||||
|
|
||||||
|
路径和字段均不变:
|
||||||
|
|
||||||
|
- 手工取消保留当前代身份;只恢复一个被取消槽位时仍保持不完整,全部当前代逻辑 key 恢复后才重新生成或重启 `REQUIREMENT_DONE`。
|
||||||
|
- REOPEN 先到时,恢复后按相同 topology fingerprint 重新激活完成事件;恢复先到时,迟到 REOPEN 会因当前拓扑已完整而跳过,不回退需求状态。
|
||||||
|
- DIRECT/HOLD 正常改派的 replacement 继承当前代。合法 canceled tombstone 在存在唯一同代 active replacement 时不污染完整性;HOLD 在确认前仍不完整。
|
||||||
|
- 已退休旧代、已有同代 active replacement 的 tombstone、以及全局失效后仅保留退休代际证据的取消行均禁止恢复。
|
||||||
|
- driver reject、daily rewrite、订单/需求系统取消会退休 current 标记并保留 generation 作为禁止恢复证据;跨 requirement rebind 会清除旧身份,下一次最终提交建立新代。
|
||||||
|
- `completed`、`canceled` 业务状态和历史/完结/关账只读规则不变;generation 不进入完成 topology fingerprint。
|
||||||
|
|
||||||
|
## 数据库迁移
|
||||||
|
|
||||||
|
新增 Fleet 内部 nullable BIGINT:`fleet_assignment.dispatch_plan_generation`。
|
||||||
|
|
||||||
|
- 迁移先把存量 `canceled + dispatch_plan_finalized=1` 的历史改派 tombstone 退休为非 current。
|
||||||
|
- 再仅对非 canceled 的存量 current 行按 requirement 回填兼容 generation。
|
||||||
|
- canceled 历史不猜测代际;无新增索引,读取仍按 `requirement_id` 批量完成。
|
||||||
|
|
||||||
|
## 前端处理
|
||||||
|
|
||||||
|
无需修改前端代码:
|
||||||
|
|
||||||
|
- 继续使用现有逐日方案保存、取消、恢复、DIRECT/HOLD 改派接口和状态字段;
|
||||||
|
- 不新增 `generation` 请求或响应字段,不应在前端推断方案代际;
|
||||||
|
- 状态刷新、错误提示和页面交互保持现状。
|
||||||
|
|
||||||
|
因此 `frontend_status=not_required`,且不修改 `hl-ui`。
|
||||||
|
|
||||||
|
## 不影响范围
|
||||||
|
|
||||||
|
- 不修改 Controller mapping、请求/响应 DTO/VO/BO、Feign、shared Java、枚举或错误码。
|
||||||
|
- 不修改车辆/司机冲突规则、费用、保险、对账或业务五态。
|
||||||
|
- 不按 Snowflake 数值比较代际先后。
|
||||||
|
- 不产生逐槽或逐候选 N+1。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 后端 PR [wx/HL#5312](https://git.1814.love:8443/wx/HL/pulls/5312) 已 squash 合并至 `dev-v3`,合并提交 `883fc61a0`。
|
||||||
|
- `hl-fleet-service` 已滚动部署测试环境,8087/8187 双实例健康;验证部署任务 `493f862d` 成功。
|
||||||
|
- 定向 `AssignmentServiceTest`:302 tests,0 failures,0 errors,0 skipped。
|
||||||
|
- Fleet reactor verify:2507 tests,0 failures,0 errors,1 skipped;Spotless 629 Java files clean。
|
||||||
|
- 真表 BIGINT 落库读回、全局失效退休证据及迁移先退休 tombstone 再回填 active 的行为测试通过。
|
||||||
|
- 两轮独立 Reviewer 共发现 4 个 P1,均已修复并补充回归测试;无未解决 P0/P1。
|
||||||
|
- 真实测试网关只读验证:矩阵精确状态筛选和统计守恒、看板 assigned/unassigned 互斥、Long ID 字符串及手机号脱敏均保持兼容,全程无业务写入。
|
||||||
|
- 内部 generation 不通过网关暴露,且取消/恢复正例必须产生业务写入,因此未伪造网关正例;完整代际生命周期由 Service、Mapper 真表和迁移测试覆盖。
|
||||||
|
- 网关证据:`D:/work2/HL-v3/.tmp/5308-gateway.json`,SHA-256 `3778da1c8c0d270e3d1da75821e54bfce49ea25bc36f09e8440932ed6e4efa07`。
|
||||||
|
- OpenAPI 与 Spring Cloud Contract:`not_required`,因为无 API/Feign/shared Java 契约形状变化。
|
||||||
|
|
||||||
|
当前状态:后端已合并、部署并通过网关兼容探针,前端无需改造。
|
||||||
|
|
||||||
|
关联:#5308、#5292、#5305。
|
||||||
@ -0,0 +1,892 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5310"
|
||||||
|
title: "核单其他收支保存即确认"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "pi:019fadb7-dac9-74bf-9581-058835251208"
|
||||||
|
frontend_ref: "1444fc7f0bf34efaec0ee9f775f7d529b609b847"
|
||||||
|
target_release: "v2.1"
|
||||||
|
verified_at: "2026-07-29T20:43:00+08:00"
|
||||||
|
status_note: "管理后台已停止调用已删除的其他收支独立确认接口;保存行为按后续 #5320 最终契约显式提交确认状态,pnpm checkpoint 全部通过。"
|
||||||
|
updated_at: "2026-07-28"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-28T11:54:21+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·管理后台】核单其他收支保存即确认 (#5310)
|
||||||
|
|
||||||
|
> **PR**: #5313 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-28 11:54
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单「其他收入」「其他支出」不再需要先保存、再单独点确认。保存成功即视为已确认,前端不再调用独立确认接口。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 新增其他收入并原子创建订单增费 | POST | `/v3/admin/order/{orderId}/settlement/other-incomes` | 修改 | 保存成功后返回 `settlementConfirmStatus=CONFIRMED`、`settlementConfirmStatusName=已确认` |
|
||||||
|
| 2 | 修改其他收入;金额或项目名变化时原子冲销并重建订单增费 | PUT | `/v3/admin/order/{orderId}/settlement/other-incomes/{incomeId}` | 修改 | 保存成功后返回 `settlementConfirmStatus=CONFIRMED`、`settlementConfirmStatusName=已确认` |
|
||||||
|
| 3 | 新增其他支出 | POST | `/v3/admin/order/{orderId}/settlement/other-expenses` | 修改 | 保存成功后返回 `settlementConfirmStatus=CONFIRMED` |
|
||||||
|
| 4 | 修改其他支出 | PUT | `/v3/admin/order/{orderId}/settlement/other-expenses/{settlementId}` | 修改 | 保存成功后返回 `settlementConfirmStatus=CONFIRMED` |
|
||||||
|
| 5 | 批量确认其他收入 | POST | `/v3/admin/order/{orderId}/settlement/other-incomes/confirm` | 删除 | 接口删除;前端不要再调用 |
|
||||||
|
| 6 | 确认其他支出 | POST | `/v3/admin/order/{orderId}/settlement/other-expenses/confirm` | 删除 | 接口删除;前端不要再调用 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 新增其他收入
|
||||||
|
|
||||||
|
- **方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/other-incomes`
|
||||||
|
- **接口名**:新增其他收入并原子创建订单增费
|
||||||
|
- **使用场景**:在核单其他收入页新增一条其他收入。
|
||||||
|
- **认证**:需要管理后台登录态;需要资金写入权限。
|
||||||
|
- **幂等性**:是;同一订单内 `requestId` 永久唯一,相同 `requestId` 且请求载荷一致时返回同一条记录。
|
||||||
|
- **限流**:无接口专属限流。
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | Long | 是 | 订单 ID,必须大于 0 |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `requestId` | String | 是 | 客户端生成的稳定幂等请求 ID,同订单内永久唯一 | 非空,最长 64 字符 |
|
||||||
|
| `incomeDate` | String(date) | 是 | 收入日期,格式 `YYYY-MM-DD` | 非空 |
|
||||||
|
| `projectName` | String | 是 | 项目名称 | 非空,最长 100 字符 |
|
||||||
|
| `projectCategory` | String | 是 | 项目类别 | 非空,最长 64 字符 |
|
||||||
|
| `specification` | String | 否 | 票种/规格 | 最长 100 字符 |
|
||||||
|
| `quantity` | Decimal | 是 | 数量 | >= 0,最多 8 位整数、4 位小数 |
|
||||||
|
| `unitPrice` | Decimal | 是 | 核算单价 | >= 0,最多 8 位整数、2 位小数 |
|
||||||
|
| `settlementAmount` | Decimal | 是 | 核算金额 | > 0,最多 8 位整数、2 位小数;必须等于 `quantity * unitPrice` 四舍五入到 2 位 |
|
||||||
|
| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` |
|
||||||
|
| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项最长 1024 字符;必须是 http/https |
|
||||||
|
| `remark` | String | 否 | 备注 | 最长 500 字符 |
|
||||||
|
|
||||||
|
**响应字段:`Result<SettlementOtherIncomeItemRespVO>`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务码,成功为 `200` |
|
||||||
|
| `msg` | String | 响应消息 |
|
||||||
|
| `data.id` | String | 其他收入 ID |
|
||||||
|
| `data.requestId` | String | 手工新增幂等请求 ID;自动投影为空 |
|
||||||
|
| `data.incomeDate` | String(date) | 收入日期 |
|
||||||
|
| `data.projectName` | String | 项目名称 |
|
||||||
|
| `data.projectCategory` | String | 项目类别 |
|
||||||
|
| `data.projectCategoryName` | String | 项目类别名称 |
|
||||||
|
| `data.specification` | String | 票种/规格 |
|
||||||
|
| `data.quantity` | Decimal | 数量 |
|
||||||
|
| `data.unitPrice` | Decimal | 核算单价 |
|
||||||
|
| `data.settlementAmount` | Decimal | 核算金额 |
|
||||||
|
| `data.paymentMethod` | String | 付款类型 |
|
||||||
|
| `data.paymentMethodName` | String | 付款类型名称 |
|
||||||
|
| `data.voucherUrls` | String[] | 凭证 URL 列表 |
|
||||||
|
| `data.settlementConfirmStatus` | String | 确认状态;本接口保存成功返回 `CONFIRMED` |
|
||||||
|
| `data.settlementConfirmStatusName` | String | 确认状态名称;本接口保存成功返回 `已确认` |
|
||||||
|
| `data.remark` | String | 备注 |
|
||||||
|
| `data.sourceType` | String | 来源类型 |
|
||||||
|
| `data.sourceTypeName` | String | 来源类型名称 |
|
||||||
|
| `data.sourceId` | String | 来源附加费 ID |
|
||||||
|
|
||||||
|
**错误码**
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | 必填缺失、字段长度超限、枚举非法、凭证 URL 非 http/https、金额不等于数量乘单价 |
|
||||||
|
| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不是待核单或核单中 |
|
||||||
|
| `584075` | 其他收入关联的附加费来源无效 | 保存后无法得到有效来源记录 |
|
||||||
|
| `584076` | 其他收入核算金额必须等于数量乘以核算单价 | `settlementAmount` 与 `quantity * unitPrice` 不一致 |
|
||||||
|
| `584087` | requestId 已用于另一笔其他收入 | 同订单重复使用 `requestId`,但请求载荷不同 |
|
||||||
|
| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 |
|
||||||
|
|
||||||
|
**示例:典型成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/60001/settlement/other-incomes
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "oi-20260728-0001",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "临时加收房差",
|
||||||
|
"projectCategory": "房差",
|
||||||
|
"specification": "双人间",
|
||||||
|
"quantity": 2,
|
||||||
|
"unitPrice": 120.00,
|
||||||
|
"settlementAmount": 240.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/income-1.jpg"],
|
||||||
|
"remark": "现场补收"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "99001",
|
||||||
|
"requestId": "oi-20260728-0001",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "临时加收房差",
|
||||||
|
"projectCategory": "房差",
|
||||||
|
"projectCategoryName": "房差",
|
||||||
|
"specification": "双人间",
|
||||||
|
"quantity": 2,
|
||||||
|
"unitPrice": 120.00,
|
||||||
|
"settlementAmount": 240.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"paymentMethodName": "现付",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/income-1.jpg"],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": "现场补收",
|
||||||
|
"sourceType": "ORDER_SURCHARGE",
|
||||||
|
"sourceTypeName": "订单增费",
|
||||||
|
"sourceId": "88001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:边界成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "oi-20260728-0002",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "其他收入",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"quantity": 0.0001,
|
||||||
|
"unitPrice": 100.00,
|
||||||
|
"settlementAmount": 0.01,
|
||||||
|
"paymentMethod": "SIGNED",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "99002",
|
||||||
|
"requestId": "oi-20260728-0002",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "其他收入",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"projectCategoryName": "其他",
|
||||||
|
"specification": null,
|
||||||
|
"quantity": 0.0001,
|
||||||
|
"unitPrice": 100.00,
|
||||||
|
"settlementAmount": 0.01,
|
||||||
|
"paymentMethod": "SIGNED",
|
||||||
|
"paymentMethodName": "签单",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": null,
|
||||||
|
"sourceType": "ORDER_SURCHARGE",
|
||||||
|
"sourceTypeName": "订单增费",
|
||||||
|
"sourceId": "88002"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:业务失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "oi-20260728-0003",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "加收费用",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"quantity": 2,
|
||||||
|
"unitPrice": 100.00,
|
||||||
|
"settlementAmount": 199.00,
|
||||||
|
"paymentMethod": "CASH_PAID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584076,
|
||||||
|
"msg": "其他收入核算金额必须等于数量乘以核算单价",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 修改其他收入
|
||||||
|
|
||||||
|
- **方法 + 路径**:`PUT /v3/admin/order/{orderId}/settlement/other-incomes/{incomeId}`
|
||||||
|
- **接口名**:修改其他收入;金额或项目名变化时原子冲销并重建订单增费
|
||||||
|
- **使用场景**:修改已有其他收入;也用于把存量 `UNCONFIRMED` 记录重新保存为 `CONFIRMED`。
|
||||||
|
- **认证**:需要管理后台登录态;需要资金写入权限。
|
||||||
|
- **幂等性**:否。
|
||||||
|
- **限流**:无接口专属限流。
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | Long | 是 | 订单 ID,必须大于 0 |
|
||||||
|
| `incomeId` | Long | 是 | 其他收入 ID,必须大于 0 |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
同 §3.1,但不包含 `requestId`。
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
同 §3.1;保存成功后 `data.settlementConfirmStatus=CONFIRMED`、`data.settlementConfirmStatusName=已确认`。
|
||||||
|
|
||||||
|
**错误码**
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | 必填缺失、字段长度超限、枚举非法、金额不一致 |
|
||||||
|
| `584073` | 其他收入不存在或不属于当前订单 | `incomeId` 不存在或不属于 `orderId` |
|
||||||
|
| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不是待核单或核单中 |
|
||||||
|
| `584076` | 其他收入核算金额必须等于数量乘以核算单价 | `settlementAmount` 与 `quantity * unitPrice` 不一致 |
|
||||||
|
| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 |
|
||||||
|
|
||||||
|
**示例:典型成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/60001/settlement/other-incomes/99001
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "临时加收房差",
|
||||||
|
"projectCategory": "房差",
|
||||||
|
"specification": "双人间",
|
||||||
|
"quantity": 2,
|
||||||
|
"unitPrice": 130.00,
|
||||||
|
"settlementAmount": 260.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/income-2.jpg"],
|
||||||
|
"remark": "修改金额"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "99001",
|
||||||
|
"requestId": "oi-20260728-0001",
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "临时加收房差",
|
||||||
|
"projectCategory": "房差",
|
||||||
|
"projectCategoryName": "房差",
|
||||||
|
"specification": "双人间",
|
||||||
|
"quantity": 2,
|
||||||
|
"unitPrice": 130.00,
|
||||||
|
"settlementAmount": 260.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/income-2.jpg"],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": "修改金额",
|
||||||
|
"sourceType": "ORDER_SURCHARGE",
|
||||||
|
"sourceTypeName": "订单增费",
|
||||||
|
"sourceId": "88003"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:边界成功(存量未确认重存)**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"incomeDate": "2026-07-20",
|
||||||
|
"projectName": "历史其他收入",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"quantity": 1,
|
||||||
|
"unitPrice": 88.00,
|
||||||
|
"settlementAmount": 88.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "重存后确认"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "99010",
|
||||||
|
"requestId": "oi-old-001",
|
||||||
|
"incomeDate": "2026-07-20",
|
||||||
|
"projectName": "历史其他收入",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"projectCategoryName": "其他",
|
||||||
|
"specification": null,
|
||||||
|
"quantity": 1,
|
||||||
|
"unitPrice": 88.00,
|
||||||
|
"settlementAmount": 88.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"paymentMethodName": "现付",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": "重存后确认",
|
||||||
|
"sourceType": "ORDER_SURCHARGE",
|
||||||
|
"sourceTypeName": "订单增费",
|
||||||
|
"sourceId": "88010"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:业务失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"incomeDate": "2026-07-28",
|
||||||
|
"projectName": "不存在记录",
|
||||||
|
"projectCategory": "其他",
|
||||||
|
"quantity": 1,
|
||||||
|
"unitPrice": 10.00,
|
||||||
|
"settlementAmount": 10.00,
|
||||||
|
"paymentMethod": "CASH_PAID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584073,
|
||||||
|
"msg": "其他收入不存在或不属于当前订单",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 新增其他支出
|
||||||
|
|
||||||
|
- **方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/other-expenses`
|
||||||
|
- **接口名**:新增其他支出
|
||||||
|
- **使用场景**:在核单其他支出页新增一条其他支出。
|
||||||
|
- **认证**:需要管理后台登录态;需要资金写入权限。
|
||||||
|
- **幂等性**:否。
|
||||||
|
- **限流**:无接口专属限流。
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | Long | 是 | 订单 ID,必须大于 0 |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `expenseType` | String | 是 | 支出类型 | `FUEL` / `TOLL` / `PARKING` / `RENTAL` / `MAINTENANCE` / `OTHER` |
|
||||||
|
| `projectName` | String | 是 | 项目名称 | 非空,最长 200 字符 |
|
||||||
|
| `expenseDate` | String(date) | 否 | 发生日期,格式 `YYYY-MM-DD` | 可空 |
|
||||||
|
| `actualAmount` | Decimal | 是 | 实际金额 | >= 0,最多 8 位整数、2 位小数 |
|
||||||
|
| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` |
|
||||||
|
| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项最长 1024 字符;必须是 http/https |
|
||||||
|
| `remark` | String | 否 | 备注 | 最长 512 字符 |
|
||||||
|
|
||||||
|
**响应字段:`Result<SettlementOtherExpenseRespVO>`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | 业务码,成功为 `200` |
|
||||||
|
| `msg` | String | 响应消息 |
|
||||||
|
| `data.id` | String | 其他支出 ID |
|
||||||
|
| `data.expenseType` | String | 支出类型 |
|
||||||
|
| `data.projectName` | String | 项目名称 |
|
||||||
|
| `data.expenseDate` | String(date) | 发生日期 |
|
||||||
|
| `data.actualAmount` | String | 实际金额 |
|
||||||
|
| `data.paymentMethod` | String | 付款类型 |
|
||||||
|
| `data.voucherUrls` | String[] | 凭证 URL 列表 |
|
||||||
|
| `data.settlementConfirmStatus` | String | 确认状态;本接口保存成功返回 `CONFIRMED` |
|
||||||
|
| `data.remark` | String | 备注 |
|
||||||
|
|
||||||
|
**错误码**
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | 必填缺失、字段长度超限、枚举非法 |
|
||||||
|
| `584095` | 其他支出字段超出允许范围 | `expenseType` 非法、项目名为空或超长、金额超范围、备注超长 |
|
||||||
|
| `584096` | 付款类型不合法 | `paymentMethod` 不是允许值 |
|
||||||
|
| `584097` | 凭证 URL 格式或数量不合法 | 凭证数量超限、非 http/https、单项超长 |
|
||||||
|
| `584307` | 当前核单状态不允许写入餐食或其他支出 | 订单核单状态不是待核单或核单中 |
|
||||||
|
| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 |
|
||||||
|
|
||||||
|
**示例:典型成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/60001/settlement/other-expenses
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "TOLL",
|
||||||
|
"projectName": "过路费",
|
||||||
|
"expenseDate": "2026-07-28",
|
||||||
|
"actualAmount": 50.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/toll.jpg"],
|
||||||
|
"remark": "高速通行费"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "801",
|
||||||
|
"expenseType": "TOLL",
|
||||||
|
"projectName": "过路费",
|
||||||
|
"expenseDate": "2026-07-28",
|
||||||
|
"actualAmount": "50.00",
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/toll.jpg"],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"remark": "高速通行费"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:边界成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "OTHER",
|
||||||
|
"projectName": "0元备注支出",
|
||||||
|
"expenseDate": null,
|
||||||
|
"actualAmount": 0.00,
|
||||||
|
"paymentMethod": "SIGNED",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "802",
|
||||||
|
"expenseType": "OTHER",
|
||||||
|
"projectName": "0元备注支出",
|
||||||
|
"expenseDate": null,
|
||||||
|
"actualAmount": "0.00",
|
||||||
|
"paymentMethod": "SIGNED",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:业务失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "BAD_TYPE",
|
||||||
|
"projectName": "非法支出类型",
|
||||||
|
"actualAmount": 10.00,
|
||||||
|
"paymentMethod": "CASH_PAID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584095,
|
||||||
|
"msg": "其他支出字段超出允许范围",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 修改其他支出
|
||||||
|
|
||||||
|
- **方法 + 路径**:`PUT /v3/admin/order/{orderId}/settlement/other-expenses/{settlementId}`
|
||||||
|
- **接口名**:修改其他支出
|
||||||
|
- **使用场景**:修改已有其他支出;也用于把存量 `UNCONFIRMED` 记录重新保存为 `CONFIRMED`。
|
||||||
|
- **认证**:需要管理后台登录态;需要资金写入权限。
|
||||||
|
- **幂等性**:否。
|
||||||
|
- **限流**:无接口专属限流。
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | Long | 是 | 订单 ID,必须大于 0 |
|
||||||
|
| `settlementId` | Long | 是 | 其他支出 ID,必须大于 0 |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
同 §3.3。
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
同 §3.3;保存成功后 `data.settlementConfirmStatus=CONFIRMED`。
|
||||||
|
|
||||||
|
**错误码**
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | 必填缺失、字段长度超限、枚举非法 |
|
||||||
|
| `584091` | 其他支出不存在或不属于当前订单 | `settlementId` 不存在或不属于 `orderId` |
|
||||||
|
| `584095` | 其他支出字段超出允许范围 | `expenseType` 非法、项目名为空或超长、金额超范围、备注超长 |
|
||||||
|
| `584096` | 付款类型不合法 | `paymentMethod` 不是允许值 |
|
||||||
|
| `584097` | 凭证 URL 格式或数量不合法 | 凭证数量超限、非 http/https、单项超长 |
|
||||||
|
| `584307` | 当前核单状态不允许写入餐食或其他支出 | 订单核单状态不是待核单或核单中 |
|
||||||
|
| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 |
|
||||||
|
|
||||||
|
**示例:典型成功**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/60001/settlement/other-expenses/801
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "PARKING",
|
||||||
|
"projectName": "停车费",
|
||||||
|
"expenseDate": "2026-07-28",
|
||||||
|
"actualAmount": 35.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/parking.jpg"],
|
||||||
|
"remark": "改为停车费"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "801",
|
||||||
|
"expenseType": "PARKING",
|
||||||
|
"projectName": "停车费",
|
||||||
|
"expenseDate": "2026-07-28",
|
||||||
|
"actualAmount": "35.00",
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"voucherUrls": ["https://cdn.example.com/vouchers/parking.jpg"],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"remark": "改为停车费"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:边界成功(存量未确认重存)**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "OTHER",
|
||||||
|
"projectName": "历史其他支出",
|
||||||
|
"expenseDate": "2026-07-20",
|
||||||
|
"actualAmount": 1.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": "重存后确认"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "810",
|
||||||
|
"expenseType": "OTHER",
|
||||||
|
"projectName": "历史其他支出",
|
||||||
|
"expenseDate": "2026-07-20",
|
||||||
|
"actualAmount": "1.00",
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"remark": "重存后确认"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:业务失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expenseType": "TOLL",
|
||||||
|
"projectName": "不存在记录",
|
||||||
|
"expenseDate": "2026-07-28",
|
||||||
|
"actualAmount": 50.00,
|
||||||
|
"paymentMethod": "CASH_PAID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584091,
|
||||||
|
"msg": "其他支出不存在或不属于当前订单",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 已删除:批量确认其他收入
|
||||||
|
|
||||||
|
- **原方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/other-incomes/confirm`
|
||||||
|
- **原接口名**:批量确认其他收入
|
||||||
|
- **变更后**:接口已删除;新增/修改其他收入保存成功即确认。
|
||||||
|
- **请求体**:不再支持。旧请求体形如 `{"incomeIds":["99001"]}`,前端不要再发送。
|
||||||
|
- **响应**:不再返回原 `confirmedCount`;调用该路径按不存在接口处理。
|
||||||
|
|
||||||
|
**示例:删除后调用失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"incomeIds": ["99001"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"msg": "Not Found",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 已删除:确认其他支出
|
||||||
|
|
||||||
|
- **原方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/other-expenses/confirm`
|
||||||
|
- **原接口名**:确认其他支出
|
||||||
|
- **变更后**:接口已删除;新增/修改其他支出保存成功即确认。
|
||||||
|
- **请求体**:不再支持。旧接口无请求体。
|
||||||
|
- **响应**:不再返回 `Boolean`;调用该路径按不支持的方法处理。
|
||||||
|
|
||||||
|
**示例:删除后调用失败**
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 405,
|
||||||
|
"msg": "Method Not Allowed",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
各接口入参已在 §3 按接口自包含列出。
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
各接口出参已在 §3 按接口自包含列出。核心变化是保存类接口的确认状态返回值变为已确认:
|
||||||
|
|
||||||
|
| 接口 | 字段 | 类型 | 当前返回 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| POST/PUT 其他收入 | `data.settlementConfirmStatus` | String | `CONFIRMED` |
|
||||||
|
| POST/PUT 其他收入 | `data.settlementConfirmStatusName` | String | `已确认` |
|
||||||
|
| POST/PUT 其他支出 | `data.settlementConfirmStatus` | String | `CONFIRMED` |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 paymentMethod(付款类型)
|
||||||
|
|
||||||
|
**所属字段**:`paymentMethod` | **类型**:`String` | **必填**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `CASH_PAID` | 现付 | 现场现金或线下现付 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 公司承担或公司支付 |
|
||||||
|
| `SIGNED` | 签单 | 签单结算 |
|
||||||
|
|
||||||
|
### 6.2 settlementConfirmStatus(确认状态)
|
||||||
|
|
||||||
|
**所属字段**:`settlementConfirmStatus` | **类型**:`String` | **必填**:响应字段
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `UNCONFIRMED` | 未确认 | 历史存量状态;本次变更后新增/修改保存不再产生该状态 |
|
||||||
|
| `CONFIRMED` | 已确认 | 新增/修改保存成功后的状态 |
|
||||||
|
|
||||||
|
### 6.3 expenseType(其他支出类型)
|
||||||
|
|
||||||
|
**所属字段**:`expenseType` | **类型**:`String` | **必填**:是
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `FUEL` | 油费 | 车辆类支出 |
|
||||||
|
| `TOLL` | 过路费 | 车辆类支出 |
|
||||||
|
| `PARKING` | 停车费 | 车辆类支出 |
|
||||||
|
| `RENTAL` | 租车费 | 车辆类支出 |
|
||||||
|
| `MAINTENANCE` | 维修费 | 车辆类支出 |
|
||||||
|
| `OTHER` | 其他 | 非上述类型的其他支出 |
|
||||||
|
|
||||||
|
### 6.4 sourceType(其他收入来源类型)
|
||||||
|
|
||||||
|
**所属字段**:`sourceType` | **类型**:`String` | **必填**:响应字段
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `ORDER_SURCHARGE` | 订单增费 | 其他收入关联的订单增费来源 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | 请求体格式错误、必填缺失、字段长度或格式非法 |
|
||||||
|
| `404` | 接口不存在 | 继续调用已删除的其他收入确认接口 |
|
||||||
|
| `405` | 方法不支持 | 继续调用已删除的其他支出确认接口 |
|
||||||
|
| `584073` | 其他收入不存在或不属于当前订单 | 修改其他收入时 `incomeId` 无效 |
|
||||||
|
| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不是待核单或核单中 |
|
||||||
|
| `584075` | 其他收入关联的附加费来源无效 | 其他收入来源无效 |
|
||||||
|
| `584076` | 其他收入核算金额必须等于数量乘以核算单价 | 其他收入金额不一致 |
|
||||||
|
| `584087` | requestId 已用于另一笔其他收入 | 新增其他收入幂等键冲突 |
|
||||||
|
| `584089` | 核单或结算已完成,资金数据不可再修改 | 资金数据已锁定 |
|
||||||
|
| `584091` | 其他支出不存在或不属于当前订单 | 修改其他支出时 `settlementId` 无效 |
|
||||||
|
| `584095` | 其他支出字段超出允许范围 | 支出类型、项目名、金额或备注非法 |
|
||||||
|
| `584096` | 付款类型不合法 | `paymentMethod` 非法 |
|
||||||
|
| `584097` | 凭证 URL 格式或数量不合法 | 凭证 URL 非法 |
|
||||||
|
| `584098` | 餐食或其他支出存在未确认记录 | Step6 前仍有历史未确认餐食或其他支出 |
|
||||||
|
| `584307` | 当前核单状态不允许写入餐食或其他支出 | 订单核单状态不是待核单或核单中 |
|
||||||
|
|
||||||
|
## 8. 示例(3 组:典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
典型成功、边界成功、业务失败示例已按接口内联在 §3.1 至 §3.6。
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:订单核单状态为待核单或核单中,且当前账号具备资金写入权限时,可以新增/修改其他收入和其他支出。
|
||||||
|
- **不适用场景**:核单或结算已完成后,不允许再保存资金数据。
|
||||||
|
- **特殊边界**:历史已存在的 `UNCONFIRMED` 其他收入/其他支出不会因为本次接口变更自动变为 `CONFIRMED`;需要前端对该行发起对应 `PUT` 保存,保存成功后才会返回 `CONFIRMED`。
|
||||||
|
- **删除接口边界**:不要再调用 `/other-incomes/confirm` 和 `/other-expenses/confirm`;保存类接口成功即可完成确认。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 其他收入 `settlementConfirmStatus` | POST/PUT 保存后返回 `UNCONFIRMED`,需再调确认接口变为 `CONFIRMED` | POST/PUT 保存成功直接返回 `CONFIRMED` |
|
||||||
|
| 其他收入 `settlementConfirmStatusName` | POST/PUT 保存后返回 `未确认` | POST/PUT 保存成功返回 `已确认` |
|
||||||
|
| 其他支出 `settlementConfirmStatus` | POST/PUT 保存后返回 `UNCONFIRMED`,需再调确认接口变为 `CONFIRMED` | POST/PUT 保存成功直接返回 `CONFIRMED` |
|
||||||
|
| 其他收入确认响应 `confirmedCount` | `POST /other-incomes/confirm` 返回确认数量 | 接口删除,不再返回 |
|
||||||
|
| 其他支出确认响应 `data` | `POST /other-expenses/confirm` 返回 `true` | 接口删除,不再返回 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 新增其他收入 | 保存后仍是未确认,需要再调用确认接口 | 保存成功即确认 |
|
||||||
|
| 修改其他收入 | 修改后变为未确认,需要再调用确认接口 | 保存成功即确认 |
|
||||||
|
| 新增其他支出 | 保存后仍是未确认,需要再调用确认接口 | 保存成功即确认 |
|
||||||
|
| 修改其他支出 | 修改后变为未确认,需要再调用确认接口 | 保存成功即确认 |
|
||||||
|
| 存量未确认记录 | 可调用独立确认接口批量确认 | 需要逐条用 PUT 重存确认 |
|
||||||
|
| 独立确认按钮 | 调用确认接口 | 不再调用确认接口 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:是。两个确认接口删除,继续调用会失败。
|
||||||
|
- **前端是否必须同步上线**:是。需要停止调用已删除确认接口,并以保存接口返回的 `settlementConfirmStatus` 作为确认结果。
|
||||||
|
- **影响已有数据**:历史 `UNCONFIRMED` 其他收入/其他支出不会自动确认;需要通过对应 PUT 保存后确认。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- **回滚方式**:如需恢复旧交互,回滚本次接口契约变更对应 PR。
|
||||||
|
- **回滚后清理**:无前端侧额外清理数据。
|
||||||
|
- **回滚耗时**:以后端发布节奏为准。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 前端保存其他收入/其他支出成功后,不要再追加调用确认接口。
|
||||||
|
- 前端如有独立「确认其他收入」「确认其他支出」按钮或批量确认流程,需要改为保存即确认的交互。
|
||||||
|
- 前端如检测到历史 `UNCONFIRMED` 记录,需要提示用户重新保存该条记录;重存后响应会返回 `CONFIRMED`。
|
||||||
|
- 餐食费用确认接口 `POST /v3/admin/order/{orderId}/settlement/meals/confirm` 本次未删除,不属于本文变更范围。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5310](https://git.1814.love:8443/wx/HL/issues/5310)
|
||||||
|
- **PR**: [#5313](https://git.1814.love:8443/wx/HL/pulls/5313)
|
||||||
|
- **Merge commit**: [9b3af7c](https://git.1814.love:8443/wx/HL/commit/9b3af7c8587547fa7b1a0d4282bc4783dff98ea6)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
@ -0,0 +1,875 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5324"
|
||||||
|
title: "删除旧核单兼容接口"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "删除接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
backend_ref: "PR #5328 · merge 709105c1a1d26ae1c867fcc998286781f499faf2"
|
||||||
|
deployment_status: "deployed"
|
||||||
|
deployment_ref: "deploy-panel task ad042377"
|
||||||
|
gateway_status: "verified"
|
||||||
|
verification_status: "verified"
|
||||||
|
verification_ref: "D:/work/project-doc/PRPs/reports/5328-deploy-qa-report.md · D:/work/project-doc/test/5328/evidence.json"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "2ab427c62a8d44155e740f4abfcc5c812a5b63ad"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-29T10:21:44+08:00"
|
||||||
|
status_note: "部署 task ad042377 成功;8086/8186 正常;网关 20/20 HTTP 200、0 网络错误、0 个 5xx、RST 0;3 个删除路由均返回业务 404,4 个保留路由进入业务门禁且零写入。管理后台待迁移到双报表确认后调用 finalize 的五步流程。"
|
||||||
|
updated_at: "2026-07-29"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-28T18:04:49+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【删除接口·管理后台】删除旧核单兼容接口 (#5324)
|
||||||
|
|
||||||
|
> **PR**: #5328 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-28 18:04
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单完成入口统一为“双报表确认后完成核单”:管理后台先读取并确认主报账人报账表,再读取并确认单团核算表,最后携带两份报告的当前来源指纹调用 `finalize`。旧分类确认兼容接口和旧 Step6 提交入口不再提供。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
### 2.1 删除的接口
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|
||||||
|
|---|------|------|------|----------|----------|
|
||||||
|
| 1 | 查询核单分类确认状态 | GET | `/v3/admin/order/{orderId}/settlement/category-checks` | 删除 | 删除调用及分类确认状态门禁 |
|
||||||
|
| 2 | 确认单个核单分类 | POST | `/v3/admin/order/{orderId}/settlement/category-checks/{category}/confirm` | 删除 | 删除调用及“本分类已确认”交互 |
|
||||||
|
| 3 | 旧 Step6 提交核单 | POST | `/v3/admin/order/{orderId}/settlement/step6/submit` | 删除 | 改为下表五步流程 |
|
||||||
|
|
||||||
|
### 2.2 唯一替代流程
|
||||||
|
|
||||||
|
| 顺序 | 接口 | 方法 | 路径 | 用途 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| 1 | 查询主报账人报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 获取实时数据和主报账表 `sourceFingerprint` |
|
||||||
|
| 2 | 确认主报账人报账表 | POST | `/v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm` | 确认转账、预支结清标志和签字凭证 |
|
||||||
|
| 3 | 查询单团核算表 | GET | `/v3/admin/order/{orderId}/settlement/reports/group` | 获取实时数据和单团核算表 `sourceFingerprint` |
|
||||||
|
| 4 | 确认单团核算表 | POST | `/v3/admin/order/{orderId}/settlement/reports/group/confirm` | 确认当前单团核算结果 |
|
||||||
|
| 5 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 携带两份已确认报告的当前指纹完成核单 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
以下五个接口都需要管理后台登录态,房务角色不可访问;`orderId` 为必填路径参数,类型为 `Long/String`,值必须大于 0。
|
||||||
|
|
||||||
|
### 3.1 查询主报账人报账表
|
||||||
|
|
||||||
|
- **方法 + 路径**:`GET /v3/admin/order/{orderId}/settlement/reports/reimbursement`
|
||||||
|
- **使用场景**:进入报账报表页面、确认前刷新、来源数据变化后重新获取。
|
||||||
|
- **幂等性**:幂等,只读。
|
||||||
|
- **请求体**:无。
|
||||||
|
- **成功响应**:`Result<SettlementReimbursementReportRespVO>`,完整字段见 §5.2。
|
||||||
|
- **关键规则**:前端必须保存本次响应的 `data.sourceFingerprint`;确认请求不得使用缓存的旧指纹。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"primaryReporterId": "3001",
|
||||||
|
"primaryReporterName": "王司机",
|
||||||
|
"primaryReporterRole": "DRIVER",
|
||||||
|
"reportVersion": 1,
|
||||||
|
"driverCollectedTailAmount": 2000.00,
|
||||||
|
"approvedAdvanceAmount": 500.00,
|
||||||
|
"reportablePaidCostAmount": 1000.00,
|
||||||
|
"reporterNetAmount": 1500.00,
|
||||||
|
"primaryReporterCollectedAmount": 2000.00,
|
||||||
|
"publicPrepaidAmount": 1000.00,
|
||||||
|
"primaryReporterDueAmount": 1000.00,
|
||||||
|
"advanceOutstandingAmount": 500.00,
|
||||||
|
"reconNetAmount": 1500.00,
|
||||||
|
"transferDirection": "REPORTER_TO_COMPANY",
|
||||||
|
"transferAmount": 1500.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{
|
||||||
|
"type": "DRIVER_CASH_RECEIPT",
|
||||||
|
"receiptId": "9100000000001",
|
||||||
|
"amount": 2000.00,
|
||||||
|
"channel": "DRIVER_CASH",
|
||||||
|
"payType": "CASH",
|
||||||
|
"collectorStaffId": "3001",
|
||||||
|
"collectorName": "王司机",
|
||||||
|
"collectorRole": "DRIVER",
|
||||||
|
"receivedAt": "2026-07-27T18:30:00",
|
||||||
|
"remark": "司机代收尾款"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expenseLines": [
|
||||||
|
{
|
||||||
|
"category": "HOTEL",
|
||||||
|
"kind": "HOTEL",
|
||||||
|
"hotelAssignmentId": "9200000000001",
|
||||||
|
"hotelName": "示例酒店",
|
||||||
|
"stayDate": "2026-07-20",
|
||||||
|
"amount": 1000.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"advanceLines": [
|
||||||
|
{
|
||||||
|
"type": "APPROVED_ADVANCE",
|
||||||
|
"advanceId": "9300000000001",
|
||||||
|
"payeeStaffId": "3001",
|
||||||
|
"payeeName": "王司机",
|
||||||
|
"payeeRole": "DRIVER",
|
||||||
|
"advanceType": "PUBLIC",
|
||||||
|
"amount": 500.00,
|
||||||
|
"purpose": "途中费用",
|
||||||
|
"voucherUrl": "https://oss.example.com/advance.jpg",
|
||||||
|
"status": "APPROVED",
|
||||||
|
"submittedAt": "2026-07-18T10:00:00",
|
||||||
|
"approvedAt": "2026-07-18T11:00:00",
|
||||||
|
"approvedBy": "10001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicleLines": [],
|
||||||
|
"transferStatus": null,
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": false,
|
||||||
|
"signedVoucher": null,
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 确认主报账人报账表
|
||||||
|
|
||||||
|
- **方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm`
|
||||||
|
- **使用场景**:已核对主报账表,且转账、预支标记和签字凭证已经填写完毕。
|
||||||
|
- **幂等性**:同一当前指纹和完全相同的确认内容可重复提交;确认后改传其它内容返回 `584317`。
|
||||||
|
- **请求体**:见 §4.2。
|
||||||
|
- **成功响应**:与 §3.1 相同,`reportStatus=CONFIRMED`,并返回确认信息。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/confirm
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"transferStatus": "COMPLETED",
|
||||||
|
"transferDate": "2026-07-28",
|
||||||
|
"transferRef": "BANK-20260728-001",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "司机签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/signed-voucher.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": "签字凭证已回收"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "9400000000001",
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "CONFIRMED",
|
||||||
|
"sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"primaryReporterId": "3001",
|
||||||
|
"primaryReporterName": "王司机",
|
||||||
|
"primaryReporterRole": "DRIVER",
|
||||||
|
"reportVersion": 1,
|
||||||
|
"driverCollectedTailAmount": 2000.00,
|
||||||
|
"approvedAdvanceAmount": 500.00,
|
||||||
|
"reportablePaidCostAmount": 1000.00,
|
||||||
|
"reporterNetAmount": 1500.00,
|
||||||
|
"primaryReporterCollectedAmount": 2000.00,
|
||||||
|
"publicPrepaidAmount": 1000.00,
|
||||||
|
"primaryReporterDueAmount": 1000.00,
|
||||||
|
"advanceOutstandingAmount": 500.00,
|
||||||
|
"reconNetAmount": 1500.00,
|
||||||
|
"transferDirection": "REPORTER_TO_COMPANY",
|
||||||
|
"transferAmount": 1500.00,
|
||||||
|
"incomeLines": [],
|
||||||
|
"expenseLines": [],
|
||||||
|
"advanceLines": [],
|
||||||
|
"vehicleLines": [],
|
||||||
|
"transferStatus": "COMPLETED",
|
||||||
|
"transferDate": "2026-07-28",
|
||||||
|
"transferRef": "BANK-20260728-001",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "司机签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/signed-voucher.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": "签字凭证已回收"
|
||||||
|
},
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": "10001",
|
||||||
|
"confirmedByName": "财务管理员",
|
||||||
|
"confirmedAt": "2026-07-28T18:10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 查询单团核算表
|
||||||
|
|
||||||
|
- **方法 + 路径**:`GET /v3/admin/order/{orderId}/settlement/reports/group`
|
||||||
|
- **使用场景**:主报账表确认后查看单团收入、成本、毛利和人均指标。
|
||||||
|
- **幂等性**:幂等,只读。
|
||||||
|
- **请求体**:无。
|
||||||
|
- **成功响应**:`Result<SettlementGroupReportRespVO>`,完整字段见 §5.3。
|
||||||
|
- **关键规则**:前端必须保存本次响应的 `data.sourceFingerprint`;确认请求不得使用缓存的旧指纹。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/group
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
"baseOrderAmount": 24800.00,
|
||||||
|
"otherIncomeAmount": 500.00,
|
||||||
|
"discountAmount": 300.00,
|
||||||
|
"adjustedReceivableAmount": 25000.00,
|
||||||
|
"paidAmount": 25000.00,
|
||||||
|
"actualRefundedAmount": 0.00,
|
||||||
|
"netRevenueAmount": 25000.00,
|
||||||
|
"netReceivedAmount": 25000.00,
|
||||||
|
"outstandingAmount": 0.00,
|
||||||
|
"hotelCost": 4280.00,
|
||||||
|
"ticketCost": 3680.00,
|
||||||
|
"mealCost": 860.00,
|
||||||
|
"vehicleCost": 1260.00,
|
||||||
|
"guideCost": 800.00,
|
||||||
|
"photographerCost": 600.00,
|
||||||
|
"otherExpenseCost": 300.00,
|
||||||
|
"insurancePremium": 180.00,
|
||||||
|
"totalCost": 11960.00,
|
||||||
|
"paidCost": 11960.00,
|
||||||
|
"unpaidCost": 0.00,
|
||||||
|
"grossProfit": 13040.00,
|
||||||
|
"grossProfitRate": 0.5216,
|
||||||
|
"travelerCount": 5,
|
||||||
|
"perCapitaRevenue": 5000.00,
|
||||||
|
"perCapitaCost": 2392.00,
|
||||||
|
"perCapitaProfit": 2608.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{"type": "BASE_ORDER", "amount": 24800.00},
|
||||||
|
{"type": "OTHER_INCOME", "amount": 500.00},
|
||||||
|
{"type": "DISCOUNT", "amount": -300.00},
|
||||||
|
{"type": "ACTUAL_REFUND", "amount": 0.00}
|
||||||
|
],
|
||||||
|
"costCategories": [
|
||||||
|
{"category": "HOTEL", "amount": 4280.00},
|
||||||
|
{"category": "TICKET", "amount": 3680.00},
|
||||||
|
{"category": "MEAL", "amount": 860.00},
|
||||||
|
{"category": "VEHICLE", "amount": 1260.00},
|
||||||
|
{"category": "GUIDE", "amount": 800.00},
|
||||||
|
{"category": "PHOTOGRAPHER", "amount": 600.00},
|
||||||
|
{"category": "OTHER_EXPENSE", "amount": 300.00},
|
||||||
|
{"category": "INSURANCE", "amount": 180.00}
|
||||||
|
],
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 确认单团核算表
|
||||||
|
|
||||||
|
- **方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/reports/group/confirm`
|
||||||
|
- **使用场景**:主报账表已经确认,且已核对当前单团收入、成本和利润。
|
||||||
|
- **幂等性**:相同当前指纹重复确认返回已确认结果。
|
||||||
|
- **请求体**:见 §4.3。
|
||||||
|
- **成功响应**:与 §3.3 相同,`reportStatus=CONFIRMED`,并返回确认人和确认时间。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "9500000000001",
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "CONFIRMED",
|
||||||
|
"sourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
"baseOrderAmount": 24800.00,
|
||||||
|
"otherIncomeAmount": 500.00,
|
||||||
|
"discountAmount": 300.00,
|
||||||
|
"adjustedReceivableAmount": 25000.00,
|
||||||
|
"paidAmount": 25000.00,
|
||||||
|
"actualRefundedAmount": 0.00,
|
||||||
|
"netRevenueAmount": 25000.00,
|
||||||
|
"netReceivedAmount": 25000.00,
|
||||||
|
"outstandingAmount": 0.00,
|
||||||
|
"hotelCost": 4280.00,
|
||||||
|
"ticketCost": 3680.00,
|
||||||
|
"mealCost": 860.00,
|
||||||
|
"vehicleCost": 1260.00,
|
||||||
|
"guideCost": 800.00,
|
||||||
|
"photographerCost": 600.00,
|
||||||
|
"otherExpenseCost": 300.00,
|
||||||
|
"insurancePremium": 180.00,
|
||||||
|
"totalCost": 11960.00,
|
||||||
|
"paidCost": 11960.00,
|
||||||
|
"unpaidCost": 0.00,
|
||||||
|
"grossProfit": 13040.00,
|
||||||
|
"grossProfitRate": 0.5216,
|
||||||
|
"travelerCount": 5,
|
||||||
|
"perCapitaRevenue": 5000.00,
|
||||||
|
"perCapitaCost": 2392.00,
|
||||||
|
"perCapitaProfit": 2608.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{"type": "BASE_ORDER", "amount": 24800.00},
|
||||||
|
{"type": "OTHER_INCOME", "amount": 500.00},
|
||||||
|
{"type": "DISCOUNT", "amount": -300.00},
|
||||||
|
{"type": "ACTUAL_REFUND", "amount": 0.00}
|
||||||
|
],
|
||||||
|
"costCategories": [
|
||||||
|
{"category": "HOTEL", "amount": 4280.00},
|
||||||
|
{"category": "TICKET", "amount": 3680.00},
|
||||||
|
{"category": "MEAL", "amount": 860.00},
|
||||||
|
{"category": "VEHICLE", "amount": 1260.00},
|
||||||
|
{"category": "GUIDE", "amount": 800.00},
|
||||||
|
{"category": "PHOTOGRAPHER", "amount": 600.00},
|
||||||
|
{"category": "OTHER_EXPENSE", "amount": 300.00},
|
||||||
|
{"category": "INSURANCE", "amount": 180.00}
|
||||||
|
],
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": "10001",
|
||||||
|
"confirmedByName": "财务管理员",
|
||||||
|
"confirmedAt": "2026-07-28T18:12:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 完成核单
|
||||||
|
|
||||||
|
- **方法 + 路径**:`POST /v3/admin/order/{orderId}/settlement/finalize`
|
||||||
|
- **使用场景**:两份报告均已确认且来源仍为当前版本时,点击“完成核单”。
|
||||||
|
- **幂等性**:已完成且存在当前终态结果时,重复提交返回当前终态结果。
|
||||||
|
- **请求体**:见 §4.4;请求体在业务上必填。
|
||||||
|
- **成功响应**:`Result<SettlementSubmitRespVO>`,完整字段见 §5.4。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"remark": "双报表已核对完成",
|
||||||
|
"reimbursementExpectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"groupExpectedSourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"summaryId": "9600000000001",
|
||||||
|
"finalSnapshotId": "9600000000002",
|
||||||
|
"finalSnapshotVersionNo": 1,
|
||||||
|
"finalSnapshotStatus": "FINALIZED",
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"settledAt": "2026-07-28T18:15:00",
|
||||||
|
"totalAmount": 25000.00,
|
||||||
|
"paidAmount": 25000.00,
|
||||||
|
"balanceAmount": 0.00,
|
||||||
|
"roomCost": 4280.00,
|
||||||
|
"ticketCost": 3680.00,
|
||||||
|
"staffCost": 1400.00,
|
||||||
|
"subsidyCost": 0.00,
|
||||||
|
"mealCost": 860.00,
|
||||||
|
"vehicleCost": 1260.00,
|
||||||
|
"otherExpenseCost": 300.00,
|
||||||
|
"insurancePremium": 180.00,
|
||||||
|
"totalActualCost": 11960.00,
|
||||||
|
"driverTransferAmount": 1000.00,
|
||||||
|
"profitAmount": 13040.00,
|
||||||
|
"profitRate": 0.5216,
|
||||||
|
"orderStatusAfter": "待财务复核",
|
||||||
|
"mqTriggered": true,
|
||||||
|
"warnings": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 五个替代接口共用路径参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| `orderId` | Long/String | 是 | 订单 ID | 必须大于 0 |
|
||||||
|
|
||||||
|
两个 GET 接口没有 Query 参数和请求体。
|
||||||
|
|
||||||
|
### 4.2 主报账表确认请求体
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `expectedSourceFingerprint` | String | 是 | §3.1 最新响应的 `data.sourceFingerprint` | 64 位小写十六进制 |
|
||||||
|
| `transferStatus` | String | 是 | 转账处理状态 | 固定传 `COMPLETED` |
|
||||||
|
| `transferDate` | String/date | 条件必填 | 转账日期 | `reporterNetAmount != 0` 时必填;格式 `YYYY-MM-DD` |
|
||||||
|
| `transferRef` | String | 条件必填 | 转账流水号或可追溯凭证号 | `reporterNetAmount != 0` 时不得为空白 |
|
||||||
|
| `advanceSettledFlag` | Boolean | 是 | 预支款项是否已处理完毕 | 不得为 `null` |
|
||||||
|
| `signedVoucher` | Object | 是 | 签字凭证 | 不得为 `null` |
|
||||||
|
| `signedVoucher.files` | Array | 是 | 签字凭证文件列表 | 至少 1 项 |
|
||||||
|
| `signedVoucher.files[].name` | String | 否 | 文件名 | 可为空 |
|
||||||
|
| `signedVoucher.files[].url` | String | 是 | 文件地址 | 不得为空白 |
|
||||||
|
| `signedVoucher.note` | String | 否 | 凭证备注 | 可为空 |
|
||||||
|
|
||||||
|
当 `reporterNetAmount = 0` 时,`transferDate` 和 `transferRef` 可不传;`transferStatus` 仍必须是 `COMPLETED`,签字凭证仍必须至少包含一个有效文件。
|
||||||
|
|
||||||
|
### 4.3 单团核算表确认请求体
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `expectedSourceFingerprint` | String | 是 | §3.3 最新响应的 `data.sourceFingerprint` | 64 位小写十六进制 |
|
||||||
|
|
||||||
|
### 4.4 完成核单请求体
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `remark` | String | 否 | 本次完成核单的整体备注 | 最长 500 字 |
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | String | 是 | 已确认主报账表的当前 `sourceFingerprint` | 64 位小写十六进制 |
|
||||||
|
| `groupExpectedSourceFingerprint` | String | 是 | 已确认单团核算表的当前 `sourceFingerprint` | 64 位小写十六进制 |
|
||||||
|
|
||||||
|
### 4.5 指纹传递关系
|
||||||
|
|
||||||
|
| 来源 | 确认接口字段 | 完成核单字段 |
|
||||||
|
|------|--------------|--------------|
|
||||||
|
| `GET .../reports/reimbursement` 的 `data.sourceFingerprint` | `POST .../reports/reimbursement/confirm` 的 `expectedSourceFingerprint` | `POST .../finalize` 的 `reimbursementExpectedSourceFingerprint` |
|
||||||
|
| `GET .../reports/group` 的 `data.sourceFingerprint` | `POST .../reports/group/confirm` 的 `expectedSourceFingerprint` | `POST .../finalize` 的 `groupExpectedSourceFingerprint` |
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
### 5.1 统一响应外层
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `code` | Integer | `200` 表示成功;其它值见 §7 |
|
||||||
|
| `msg` | String | 结果说明 |
|
||||||
|
| `data` | Object/null | 成功时为业务数据,失败时通常为 `null` |
|
||||||
|
|
||||||
|
所有 Long ID 以 JSON 字符串消费,避免前端数字精度损失;金额字段为十进制数。
|
||||||
|
|
||||||
|
### 5.2 主报账人报账表响应
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String/null | 报账表记录 ID;仅实时预览、尚未确认时可为 `null` |
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `reportStatus` | String | `GENERATED` / `CONFIRMED` / `STALE`,见 §6.1 |
|
||||||
|
| `sourceFingerprint` | String | 当前来源指纹,64 位小写十六进制 |
|
||||||
|
| `primaryReporterId` | String/null | 主报账人 ID |
|
||||||
|
| `primaryReporterName` | String/null | 主报账人姓名 |
|
||||||
|
| `primaryReporterRole` | String/null | 主报账人角色 |
|
||||||
|
| `reportVersion` | Integer/null | 报账表版本 |
|
||||||
|
| `driverCollectedTailAmount` | Decimal | 司机代收尾款 |
|
||||||
|
| `approvedAdvanceAmount` | Decimal | 已审批预支合计 |
|
||||||
|
| `reportablePaidCostAmount` | Decimal | 主报账人已支付、可报账成本合计 |
|
||||||
|
| `reporterNetAmount` | Decimal | 报账净额:司机代收尾款 + 已审批预支 - 可报账已支付成本 |
|
||||||
|
| `primaryReporterCollectedAmount` | Decimal | 主报账人代收金额 |
|
||||||
|
| `publicPrepaidAmount` | Decimal | 公共预支金额 |
|
||||||
|
| `primaryReporterDueAmount` | Decimal | 主报账人应报账金额 |
|
||||||
|
| `advanceOutstandingAmount` | Decimal | 待处理预支金额 |
|
||||||
|
| `reconNetAmount` | Decimal | 报账净额兼容字段 |
|
||||||
|
| `transferDirection` | String | 转账方向,见 §6.2 |
|
||||||
|
| `transferAmount` | Decimal | 需转账金额,取 `reporterNetAmount` 绝对值 |
|
||||||
|
| `incomeLines` | Array<Object> | 司机代收尾款明细 |
|
||||||
|
| `expenseLines` | Array<Object> | 主报账人现金支付成本明细 |
|
||||||
|
| `advanceLines` | Array<Object> | 已审批预支明细 |
|
||||||
|
| `vehicleLines` | Array<Object> | 车辆逐日明细;允许空数组 |
|
||||||
|
| `transferStatus` | String/null | 未确认时可为空;确认后为 `COMPLETED` |
|
||||||
|
| `transferDate` | String/date/null | 转账日期 |
|
||||||
|
| `transferRef` | String/null | 转账流水号或凭证号 |
|
||||||
|
| `advanceSettledFlag` | Boolean | 预支是否已处理完毕 |
|
||||||
|
| `signedVoucher` | Object/null | 签字凭证,结构同 §4.2 |
|
||||||
|
| `generatedBy` | String/null | 历史生成操作人 ID |
|
||||||
|
| `generatedByName` | String/null | 历史生成操作人姓名 |
|
||||||
|
| `generatedAt` | String/date-time/null | 历史生成时间 |
|
||||||
|
| `confirmedBy` | String/null | 确认人 ID |
|
||||||
|
| `confirmedByName` | String/null | 确认人姓名 |
|
||||||
|
| `confirmedAt` | String/date-time/null | 确认时间 |
|
||||||
|
|
||||||
|
`incomeLines[]` 的固定字段为 `type`、`receiptId`、`amount`、`channel`、`payType`、`collectorStaffId`、`collectorName`、`collectorRole`、`receivedAt`、`remark`。
|
||||||
|
|
||||||
|
`advanceLines[]` 的固定字段为 `type`、`advanceId`、`payeeStaffId`、`payeeName`、`payeeRole`、`advanceType`、`amount`、`purpose`、`voucherUrl`、`status`、`submittedAt`、`approvedAt`、`approvedBy`。
|
||||||
|
|
||||||
|
`expenseLines[]` 至少包含 `category`、`kind`、`amount`、`paymentMethod`;按分类还会包含对应的名称、日期、数量、单价、人员或车辆标识、凭证和备注字段。前端列表应按字段是否存在展示,不依赖固定列宽。
|
||||||
|
|
||||||
|
### 5.3 单团核算表响应
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | String/null | 单团核算表记录 ID;仅实时预览、尚未确认时可为 `null` |
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `reportStatus` | String | `GENERATED` / `CONFIRMED` / `STALE` |
|
||||||
|
| `sourceFingerprint` | String | 当前来源指纹,64 位小写十六进制 |
|
||||||
|
| `baseOrderAmount` | Decimal | 订单基础金额 |
|
||||||
|
| `otherIncomeAmount` | Decimal | 其他收入 |
|
||||||
|
| `discountAmount` | Decimal | 优惠金额 |
|
||||||
|
| `adjustedReceivableAmount` | Decimal | 调整后应收 |
|
||||||
|
| `paidAmount` | Decimal | 已收金额 |
|
||||||
|
| `actualRefundedAmount` | Decimal | 实际退款 |
|
||||||
|
| `netRevenueAmount` | Decimal | 净收入 |
|
||||||
|
| `netReceivedAmount` | Decimal | 净已收 |
|
||||||
|
| `outstandingAmount` | Decimal | 待收金额 |
|
||||||
|
| `hotelCost` | Decimal | 住宿成本 |
|
||||||
|
| `ticketCost` | Decimal | 门票/游玩项目成本 |
|
||||||
|
| `mealCost` | Decimal | 餐食成本 |
|
||||||
|
| `vehicleCost` | Decimal | 车辆成本 |
|
||||||
|
| `guideCost` | Decimal | 导游成本 |
|
||||||
|
| `photographerCost` | Decimal | 摄影成本 |
|
||||||
|
| `otherExpenseCost` | Decimal | 其他支出成本 |
|
||||||
|
| `insurancePremium` | Decimal | 保险保费 |
|
||||||
|
| `totalCost` | Decimal | 总成本 |
|
||||||
|
| `paidCost` | Decimal | 已支付成本 |
|
||||||
|
| `unpaidCost` | Decimal | 未支付成本 |
|
||||||
|
| `grossProfit` | Decimal | 毛利 |
|
||||||
|
| `grossProfitRate` | Decimal | 毛利率;收入为 0 时为 0 |
|
||||||
|
| `travelerCount` | Integer | 出行人数 |
|
||||||
|
| `perCapitaRevenue` | Decimal | 人均收入 |
|
||||||
|
| `perCapitaCost` | Decimal | 人均成本 |
|
||||||
|
| `perCapitaProfit` | Decimal | 人均利润 |
|
||||||
|
| `incomeLines` | Array<Object> | 收入构成;元素字段为 `type`、`amount` |
|
||||||
|
| `costCategories` | Array<Object> | 成本构成;元素字段为 `category`、`amount` |
|
||||||
|
| `generatedBy` | String/null | 历史生成操作人 ID |
|
||||||
|
| `generatedByName` | String/null | 历史生成操作人姓名 |
|
||||||
|
| `generatedAt` | String/date-time/null | 历史生成时间 |
|
||||||
|
| `confirmedBy` | String/null | 确认人 ID |
|
||||||
|
| `confirmedByName` | String/null | 确认人姓名 |
|
||||||
|
| `confirmedAt` | String/date-time/null | 确认时间 |
|
||||||
|
|
||||||
|
### 5.4 完成核单响应
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `summaryId` | String | 核单汇总 ID |
|
||||||
|
| `finalSnapshotId` | String | 核单终态版本 ID |
|
||||||
|
| `finalSnapshotVersionNo` | Integer | 核单终态版本号 |
|
||||||
|
| `finalSnapshotStatus` | String | 核单终态状态,成功时为 `FINALIZED` |
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `settledAt` | String/date-time | 核单完成时间 |
|
||||||
|
| `totalAmount` | Decimal | 订单总金额 |
|
||||||
|
| `paidAmount` | Decimal | 已收金额 |
|
||||||
|
| `balanceAmount` | Decimal | 待收金额;完成核单时必须为 0 |
|
||||||
|
| `roomCost` | Decimal | 住宿实际成本 |
|
||||||
|
| `ticketCost` | Decimal | 门票实际成本 |
|
||||||
|
| `staffCost` | Decimal | 人员实际成本 |
|
||||||
|
| `subsidyCost` | Decimal | 补助实际成本 |
|
||||||
|
| `mealCost` | Decimal | 餐食实际成本 |
|
||||||
|
| `vehicleCost` | Decimal | 车辆实际成本 |
|
||||||
|
| `otherExpenseCost` | Decimal | 其他支出实际成本 |
|
||||||
|
| `insurancePremium` | Decimal | 保险保费 |
|
||||||
|
| `totalActualCost` | Decimal | 总实际成本 |
|
||||||
|
| `driverTransferAmount` | Decimal | 需与司机/主报账人结算的金额 |
|
||||||
|
| `profitAmount` | Decimal | 公司毛利 |
|
||||||
|
| `profitRate` | Decimal | 公司毛利率 |
|
||||||
|
| `orderStatusAfter` | String | 完成核单后的订单状态 |
|
||||||
|
| `mqTriggered` | Boolean | 核单完成事件是否已触发 |
|
||||||
|
| `warnings` | Array<String> | 软提示列表;不阻塞成功结果 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `reportStatus`
|
||||||
|
|
||||||
|
**所属字段**:两份报告的 `reportStatus` | **类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `GENERATED` | 待确认 | 当前实时数据可供核对,尚未确认 |
|
||||||
|
| `CONFIRMED` | 已确认 | 当前来源数据已经确认 |
|
||||||
|
| `STALE` | 已失效 | 来源数据已变化,旧确认不能用于完成核单 |
|
||||||
|
|
||||||
|
### 6.2 `transferDirection`
|
||||||
|
|
||||||
|
**所属字段**:主报账表 `transferDirection` | **类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `REPORTER_TO_COMPANY` | 报账人转给公司 | `reporterNetAmount > 0` |
|
||||||
|
| `COMPANY_TO_REPORTER` | 公司转给报账人 | `reporterNetAmount < 0` |
|
||||||
|
| `BALANCED` | 无需转账 | `reporterNetAmount = 0` |
|
||||||
|
|
||||||
|
### 6.3 `transferStatus`
|
||||||
|
|
||||||
|
**所属字段**:主报账表确认请求和响应 `transferStatus` | **类型**:String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `COMPLETED` | 已完成 | 确认主报账表时唯一允许值 |
|
||||||
|
|
||||||
|
### 6.4 单团收入行 `type`
|
||||||
|
|
||||||
|
| 值 | 中文 | 金额符号 |
|
||||||
|
|----|------|----------|
|
||||||
|
| `BASE_ORDER` | 订单基础收入 | 正数 |
|
||||||
|
| `OTHER_INCOME` | 其他收入 | 正数 |
|
||||||
|
| `DISCOUNT` | 优惠 | 负数 |
|
||||||
|
| `ACTUAL_REFUND` | 实际退款 | 负数或 0 |
|
||||||
|
|
||||||
|
### 6.5 单团成本行 `category`
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|----|------|
|
||||||
|
| `HOTEL` | 住宿 |
|
||||||
|
| `TICKET` | 门票/游玩项目 |
|
||||||
|
| `MEAL` | 餐食 |
|
||||||
|
| `VEHICLE` | 车辆 |
|
||||||
|
| `GUIDE` | 导游 |
|
||||||
|
| `PHOTOGRAPHER` | 摄影 |
|
||||||
|
| `OTHER_EXPENSE` | 其他支出 |
|
||||||
|
| `INSURANCE` | 保险 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 参数校验失败 | `orderId <= 0`、请求体缺字段、指纹格式错误等 |
|
||||||
|
| `403` | 无访问或写入权限 | 房务角色访问,或操作人没有核单写权限 |
|
||||||
|
| `404` | 接口不存在 | 调用本次删除的 3 个旧接口 |
|
||||||
|
| `584082` | 存在待收尾款,请收齐后再提交核单 | `finalize` 时单团核算表 `outstandingAmount != 0` |
|
||||||
|
| `584312` | 主报账表尚未确认或数据已变化 | 单团核算表确认前,主报账表未确认或已失效 |
|
||||||
|
| `584314` | 单团核算表尚未确认或数据已变化 | `finalize` 时单团核算表未确认、已失效或指纹不匹配 |
|
||||||
|
| `584315` | 核单来源数据已变化,请刷新后重新确认 | 确认报告时提交的 `expectedSourceFingerprint` 不是当前值 |
|
||||||
|
| `584316` | 核单报告发生并发变化,请刷新后重试 | 多人同时确认同一报告发生冲突 |
|
||||||
|
| `584317` | 当前报告状态不允许执行该操作 | 确认内容不合法,或报告当前状态不允许重复变更 |
|
||||||
|
| `584325` | 完成核单必须提交主报账和单团核算的当前指纹 | `finalize` 缺少任一指纹或指纹不是 64 位小写十六进制 |
|
||||||
|
|
||||||
|
## 8. 示例(典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
### 8.1 典型成功:五步完成核单
|
||||||
|
|
||||||
|
1. 调用 `GET .../reports/reimbursement`,保存响应 `sourceFingerprint=aaaa...`。
|
||||||
|
2. 调用 `POST .../reports/reimbursement/confirm`,`expectedSourceFingerprint` 传 `aaaa...`,响应状态为 `CONFIRMED`。
|
||||||
|
3. 调用 `GET .../reports/group`,保存响应 `sourceFingerprint=bbbb...`。
|
||||||
|
4. 调用 `POST .../reports/group/confirm`,`expectedSourceFingerprint` 传 `bbbb...`,响应状态为 `CONFIRMED`。
|
||||||
|
5. 调用 `POST .../finalize`,两个指纹分别传 `aaaa...` 和 `bbbb...`,响应 `finalSnapshotStatus=FINALIZED`。
|
||||||
|
|
||||||
|
各步完整请求和响应见 §3.1~§3.5。
|
||||||
|
|
||||||
|
### 8.2 边界:报账净额为 0
|
||||||
|
|
||||||
|
当最新主报账表返回 `reporterNetAmount=0`、`transferDirection=BALANCED` 时:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/confirm
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"transferStatus": "COMPLETED",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "司机签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/signed-voucher.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "CONFIRMED",
|
||||||
|
"sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"reporterNetAmount": 0.00,
|
||||||
|
"transferDirection": "BALANCED",
|
||||||
|
"transferAmount": 0.00,
|
||||||
|
"transferStatus": "COMPLETED",
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "司机签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/signed-voucher.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 异常:来源数据变化
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584315,
|
||||||
|
"msg": "核单来源数据已变化,请刷新后重新确认",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
收到该错误后重新执行对应 GET,使用新的 `data.sourceFingerprint` 重新确认;不得继续用旧指纹调用 `finalize`。
|
||||||
|
|
||||||
|
### 8.4 异常:仍有待收尾款
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"reimbursementExpectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"groupExpectedSourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584082,
|
||||||
|
"msg": "存在待收尾款,请收齐后再提交核单",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- 必须按“查询主报账表 → 确认主报账表 → 查询单团核算表 → 确认单团核算表 → 完成核单”的顺序执行。
|
||||||
|
- 两份报告的指纹互不通用;禁止把主报账表指纹传到单团核算字段,或反向混用。
|
||||||
|
- 每次确认前都应重新 GET;当 `reportStatus=STALE` 或收到 `584315` 时,必须刷新数据并使用新指纹。
|
||||||
|
- 单团核算表确认依赖当前有效的主报账表确认,否则返回 `584312`。
|
||||||
|
- `finalize` 同时校验两份报告已确认、指纹仍为当前值,以及 `outstandingAmount=0`。
|
||||||
|
- 主报账表 `reporterNetAmount != 0` 时,确认请求必须提供 `transferDate` 和非空 `transferRef`。
|
||||||
|
- 主报账表确认始终要求至少一个含有效 `url` 的签字凭证文件。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 接口级对比
|
||||||
|
|
||||||
|
| 功能 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 分类状态 | 调用 `GET .../category-checks` | 不再查询分类确认状态 |
|
||||||
|
| 分类确认 | 调用 `POST .../category-checks/{category}/confirm` | 保存各核单明细即可,不再单独确认分类 |
|
||||||
|
| 报账与单团核算 | 可能绕过双报表直接提交旧 Step6 | 必须分别 GET、confirm 两份报告 |
|
||||||
|
| 完成核单 | `POST .../step6/submit` | `POST .../finalize`,请求体必须携带两个当前指纹 |
|
||||||
|
|
||||||
|
### 10.2 请求体对比
|
||||||
|
|
||||||
|
| 入口 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 旧 `step6/submit` | 旧提交请求 | 接口删除 |
|
||||||
|
| 新 `finalize` | 不适用 | `remark` 可选;`reimbursementExpectedSourceFingerprint`、`groupExpectedSourceFingerprint` 必填 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:是。3 个旧接口已删除。
|
||||||
|
- **前端是否必须同步上线**:是。仍调用任一旧接口的管理后台将收到 404;旧 Step6 提交必须迁移为五步流程。
|
||||||
|
|
||||||
|
### 11.2 回滚原则
|
||||||
|
|
||||||
|
- 后端回退时,前端仍可保留五步新流程。
|
||||||
|
- 前端不得因为短期回退重新新增分类确认入口或恢复旧 Step6 调用;如需临时兼容,应单独确认接口契约后再处理。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 删除 `category-checks` 查询、分类确认 API 封装、分类确认按钮和相关状态门禁。
|
||||||
|
- 删除 `step6/submit` API 封装及所有调用点。
|
||||||
|
- “完成核单”按钮改为调用 `finalize`,并在调用前确保两份报告都为 `CONFIRMED`。
|
||||||
|
- 页面状态中分别保存两份 `sourceFingerprint`,不要只保存一个通用指纹。
|
||||||
|
- 主报账表确认成功后再开放单团核算确认;单团核算确认成功后再开放“完成核单”。
|
||||||
|
- 收到 `584312`、`584314`、`584315`、`584316` 时刷新对应报告,不得自动使用旧数据重试。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5324](https://git.1814.love:8443/wx/HL/issues/5324)
|
||||||
|
- **PR**: [#5328](https://git.1814.love:8443/wx/HL/pulls/5328)
|
||||||
|
- **Merge commit**: [709105c1a1](https://git.1814.love:8443/wx/HL/commit/709105c1a1d26ae1c867fcc998286781f499faf2)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
|
- **前端负责人**: 待认领
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5337"
|
||||||
|
title: "Fleet 日期清单隔离陈旧失败与当前弹窗状态"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "0f025662443f50ca40d118043ed491a2d918a855"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-29T13:45:27+08:00"
|
||||||
|
status_note: "frontend-only:复用 #4760 已部署 day-orders 契约,无新增后端发布;本次只交接前端异步 identity 修复,fresh gateway probe 不适用"
|
||||||
|
updated_at: "2026-07-29"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:日期清单隔离陈旧失败与当前弹窗状态
|
||||||
|
|
||||||
|
> **Tracking Issue**:[wx/HL#5337](https://git.1814.love:8443/wx/HL/issues/5337)
|
||||||
|
> **责任端**:`mmg/hl-ui` Fleet Matrix 前端
|
||||||
|
> **前端基线**:`v2.1@18daf2a03e9946c46d7bf31c7577adcb05d3c168`
|
||||||
|
> **性质**:frontend-only;不是后端缺陷,不新增后端接口、字段、错误码或部署
|
||||||
|
|
||||||
|
## 问题与根因
|
||||||
|
|
||||||
|
日期弹窗快速从 A 日期切到 B 日期时,`fetchDayOrders` 的 `dayRequestSeq` 只在 Promise 成功后检查。A 旧请求若在 B 新请求成功后才失败,会在 `await` 处直接 reject,随后 A 对应的 `openDayList` catch 无 `requestSeq + date` 身份校验并无条件清空共享 `dayOrders`。
|
||||||
|
|
||||||
|
结果是标题仍属于 B,但 rows 被旧 A 清空,页面显示 B 日期假空数据,统一 error 也可能属于旧 A。旧 success 已有序号保护;缺口仅在 failure/error/clear/loading/title 的共同身份约束。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/day-orders`
|
||||||
|
|
||||||
|
接口契约保持不变,继续沿用 `#4760`:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/matrix/day-orders?date=YYYY-MM-DD
|
||||||
|
```
|
||||||
|
|
||||||
|
- 请求参数、响应结构、空值和错误码均不变。
|
||||||
|
- `backend_status=deployed` 仅表示 `#4760` 的既有接口契约已部署,不表示 #5337 有后端代码发布。
|
||||||
|
- `gateway_status=not_required`:根因是前端 Promise 乱序与共享状态写入,接口 fresh probe 不能证明竞态修复。
|
||||||
|
|
||||||
|
## 前端必须调整
|
||||||
|
|
||||||
|
1. 为每次日期请求建立稳定的 `requestSeq + date` identity。
|
||||||
|
2. 只有当前 identity 可以写 rows、清空 rows、更新 error/loading 或改变弹窗状态。
|
||||||
|
3. 陈旧 success 与陈旧 failure 都必须 no-op,不能依赖只有成功路径执行的序号检查。
|
||||||
|
4. title、rows、error、loading 必须绑定同一 identity;禁止新日期标题搭配旧错误或假空数据。
|
||||||
|
|
||||||
|
## 前端验收
|
||||||
|
|
||||||
|
- [ ] deferred 测试覆盖 A旧 success 晚于B新 success、A旧 failure 晚于B新 success、A旧 success 晚于B新 failure、当前请求 failure 四类交错。
|
||||||
|
- [ ] A旧 failure 晚于B新 success 时,B rows、B title 与 B error state 保持不变,旧 A 完全 no-op。
|
||||||
|
- [ ] 只有当前 `requestSeq + date` identity 可以写 rows、clear、error 与 loading。
|
||||||
|
- [ ] 真实 Matrix 页面挂载测试快速点击两个日期列头并控制 Promise 顺序,断言最终标题、行数、空态和错误均属于最新日期。
|
||||||
|
- [ ] 不改变 `/day-orders` API 字段、后端行为或公共契约。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 权威审计:`C:/Users/Administrator/AppData/Local/Temp/fleet-frontend-current-remote-audit-task_e6c54bb3a8b9.md`
|
||||||
|
- 审计 SHA-256:`ab3ceafdf3a433ea7d0a99d1f8962839ed49d2819d4eab1d6de13c85e891d882`
|
||||||
|
- 当前前端远端源码:`mmg/hl-ui v2.1@18daf2a03e9946c46d7bf31c7577adcb05d3c168`
|
||||||
|
- 源码证据:`useFleetMatrixData.js:553-559`、`matrix/index.vue:902-908`、`DayListModal.vue:10-16,120-124`。
|
||||||
|
- 审计已确认当前测试没有 day-orders deferred race 页面挂载覆盖;本条目初始状态为 `pending`,不宣称前端已修复或测试已通过。
|
||||||
|
|
||||||
|
## 关联与去重
|
||||||
|
|
||||||
|
- `#4760` 是 day-orders 既有接口和 Fleet Matrix 真实数据源责任,本条目不修改其历史文件。
|
||||||
|
- D-03 的 `todayDay` 未消费仍归 `#4760` existing-follow-up,不在 #5337 创建重复验收。
|
||||||
|
- D-02 byOrder 分窗状态守恒由独立 `#5338` 跟踪。
|
||||||
|
|
||||||
|
## 后端 closeout 持久证据(2026-07-31)
|
||||||
|
|
||||||
|
- 消费端修复提交:`mmg/hl-ui@0f025662443f50ca40d118043ed491a2d918a855`;无关联 PR,提交已进入 `v2.1` 主线历史。
|
||||||
|
- 测试环境自动部署:task `a44ef795`,`v2.1@6b3b04c58da5126f922cdaea52caeb13ab343444`,2026-07-31 11:27:30–11:27:40,`status=success`、`exit_code=0`、`has_build_error=false`。
|
||||||
|
- A/B 乱序定向回归:从修复提交归档到独立 OS Temp 副本后实跑 `useFleetMatrixData.spec.js`、`day-orders-race.spec.js`、`matrix.spec.js`,结果为 3 files / 25 tests passed。
|
||||||
|
- 测试日志 SHA-256:`abc6d6a39f9f40aed114b2c338850341f8583949be012012d12de7ff165c9175`;关单时已把命令、结果与哈希持久回写 `wx/HL#5337`。
|
||||||
|
- 后端 API 仍复用 #4760 的 `GET /admin/fleet/matrix/day-orders`,无 #5337 后端代码、数据库、网关或生产变更。
|
||||||
|
- 本节只补后端关单和消费端实现证据;没有实际测试页面人工操作证据,因此 `frontend_status` 继续保持 `implemented`,不升级为 `released` 或 `verified`。
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5338"
|
||||||
|
title: "Fleet 按订单分窗保持路由状态集合守恒"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-pi"
|
||||||
|
frontend_ref: "868739d793aff1ca265296a7bf2cd681fcb557bd"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "frontend-only:复用 #4760 grid/unassigned 与 #5301 coarse status 已部署契约;#5338 无后端 diff、PR、测试或部署要求。前端代码已有 implemented 证据,但尚无 target_release 或页面验证证据,不上调 released/verified"
|
||||||
|
updated_at: "2026-07-31"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fleet:按订单分窗保持路由状态集合守恒
|
||||||
|
|
||||||
|
> **Tracking Issue**:[wx/HL#5338](https://git.1814.love:8443/wx/HL/issues/5338)
|
||||||
|
> **责任端**:`mmg/hl-ui` Fleet Matrix 分窗前端
|
||||||
|
> **前端基线**:`v2.1@18daf2a03e9946c46d7bf31c7577adcb05d3c168`
|
||||||
|
> **性质**:frontend-only;不是后端缺陷,不新增后端接口、字段、错误码或部署
|
||||||
|
|
||||||
|
## 问题与根因
|
||||||
|
|
||||||
|
修复前,`matrix-solo?solo=byOrder&status=assigned` 已把 route status 传给 grid,且 coarse assigned 正确映射为 `holding/holding_urgent/assigned`;但共享 composable 会继续合并不带 status 的 `unassigned-orders`,`SoloByOrderView.visibleOrders` 又只排除 canceled,未按 route status 最终收口。
|
||||||
|
|
||||||
|
这会令 assigned 分窗混入无车的 `unassigned/unassigned_urgent` rows。主矩阵原本已使用共享 `matchesMatrixStatusFilter`;#5338 只补齐 byOrder 分窗的同一保护,不归因于后端 grid 或 coarse status 契约。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/grid`
|
||||||
|
|
||||||
|
### `GET /admin/fleet/matrix/unassigned-orders`
|
||||||
|
|
||||||
|
接口契约保持不变,继续沿用 `#4760/#5301`:
|
||||||
|
|
||||||
|
- coarse `assigned` = `holding + holding_urgent + assigned`。
|
||||||
|
- coarse `unassigned` = `unassigned + unassigned_urgent`。
|
||||||
|
- 只有 route `all` 的最终显示集合可以同时包含已派与未派两侧。
|
||||||
|
- `backend_status=deployed` 仅表示上述既有接口契约已部署,不表示 #5338 有后端代码发布。
|
||||||
|
- `gateway_status=not_required`:根因是前端将两个成功响应合并后缺少 view-level route filter,fresh API probe 不能证明页面集合守恒。
|
||||||
|
|
||||||
|
## 前端实现结果(implemented,不代表 released/verified)
|
||||||
|
|
||||||
|
1. `SoloByOrderView` 最终展示复用共享 `matchesMatrixStatusFilter`,未维护第二套状态映射。
|
||||||
|
2. route query、grid/unassigned 请求、visibleOrders 与跨窗口 drag source 使用同一筛选 identity。
|
||||||
|
3. `assigned` 只显示 `holding/holding_urgent/assigned`,排除全部无车未派 rows。
|
||||||
|
4. `unassigned` 只显示 `unassigned/unassigned_urgent`,排除已绑车 rows;仅 `all` 合并两侧。
|
||||||
|
5. 最终过滤后的同一数组同时用于渲染与拖拽,drag source 不再绕过 visibleOrders。
|
||||||
|
|
||||||
|
## 前端实现验收(6/6;不代表页面发布/验证)
|
||||||
|
|
||||||
|
- [x] 组件挂载测试覆盖 route `all/unassigned/assigned`,显示集合与主矩阵同筛选口径守恒。
|
||||||
|
- [x] `assigned` 样本同时包含 `holding/holding_urgent/assigned`,并排除 `unassigned/unassigned_urgent` 无车 rows。
|
||||||
|
- [x] `unassigned` 只含未派 rows 并排除所有已绑车 rows;只有 `all` 合并两侧。
|
||||||
|
- [x] 最终展示与 drag source 都复用 `matchesMatrixStatusFilter` 的同一过滤结果。
|
||||||
|
- [x] year/month/fleetTeamIds/typeKeys/status 从 route、请求到 visible/drag 集合保持同一身份。
|
||||||
|
- [x] 不改变 grid、unassigned-orders、statuses[] 或 coarse status 后端契约。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- 前端业务提交:[`mmg/hl-ui@868739d793aff1ca265296a7bf2cd681fcb557bd`](https://git.1814.love:8443/mmg/hl-ui/commit/868739d793aff1ca265296a7bf2cd681fcb557bd),仅修改 `SoloByOrderView.vue` 并新增 `SoloByOrderView.spec.js`;没有后端文件。
|
||||||
|
- 前端任务账本:`mmg/hl-ui:.claude/tasks/done/changelog-5338-3c335437bf.md`,记录 `status=done`、`source_status=implemented`、6 个 mount tests、21 个 composable tests、source 46 tests、ESLint/Prettier/Stylelint 与 production build 通过。
|
||||||
|
- Changelog 领取/完成提交:`wx/hl-api-changelog@4ad3a92f66741651b60d6d1c6f3d12983e96ea56` / `@73c4daab04bf094507698d9ab814cdbdd711bf73`。
|
||||||
|
- 2026-07-31 只读复核时,当前 `v2.1@6b3b04c58da5126f922cdaea52caeb13ab343444` 仍保留共享 `matchesMatrixStatusFilter`、`visibleOrders` 最终过滤与 `setupCrossWindowDrag(() => visibleOrders.value)`;实现提交已在当前分支祖先链中。
|
||||||
|
- Deploy Panel 测试环境任务 `a44ef795` 已把当前 `v2.1@6b3b04c58da5` 构建部署成功,但本条目没有 `target_release` 或浏览器页面验证证据;因此 `frontend_status` 保持 `implemented`,不得表述为 `released` 或 `verified`。
|
||||||
|
- 前端仓库没有关联 PR,交付形态为直接提交;本记录不追建事后 PR。
|
||||||
|
|
||||||
|
## 后端独立闭环证据
|
||||||
|
|
||||||
|
- #5338 是 frontend-only 集合过滤,后端 contract review 结论为 **no contract change**:Controller、DTO/VO、Feign、字段、枚举、错误码与数据库均无 diff;故 #5338 的后端 merge/test/deploy 均为 `not_required`,不伪造 PR、测试或发布。
|
||||||
|
- 既有 `grid + unassigned-orders` 契约已由 #4760 完成:PR #4761/#4772 已合并,Fleet/User 定向测试、Fleet reactor、Spotless、测试部署及独立账号真实 API 回归证据均持久记录在 [wx/HL#4760](https://git.1814.love:8443/wx/HL/issues/4760)。
|
||||||
|
- coarse/精确状态契约已由 #5301 完成:PR #5304 squash 合并到 `dev-v3@ed56bec1fa466f92e0d41d7b92de3ad4756517b3`;51 个定向测试、Fleet reactor 2474 tests、Spotless、Fleet 双实例部署及真实网关验证记录在 [wx/HL#5301](https://git.1814.love:8443/wx/HL/issues/5301)。
|
||||||
|
- 当前 `dev-v3` 源码仍明确实现 coarse `assigned = holding + holding_urgent + assigned`,并保留 grid/unassigned-orders 契约;#5338 不需要 fresh gateway probe,因为 API 成功不能证明前端合并后的页面集合。
|
||||||
|
|
||||||
|
## 关联与去重
|
||||||
|
|
||||||
|
- `#4760` 规定 byOrder 使用 grid + unassigned-orders 并携带 route status;本条目补充最终显示集合守恒,不修改其历史文件。
|
||||||
|
- `#5301` 规定 coarse assigned 与精确 statuses[];本条目不改变该契约。
|
||||||
|
- D-03 的 `todayDay` 未消费仍归 `#4760` existing-follow-up,不在 #5338 创建重复验收。
|
||||||
|
- D-01 day-orders 陈旧失败隔离由独立 `#5337` 跟踪。
|
||||||
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
@ -0,0 +1,894 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5342"
|
||||||
|
title: "核单报表取消中间确认并由 finalize 固化"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "pi:019fadb7-dac9-74bf-9581-058835251208"
|
||||||
|
frontend_ref: "1444fc7f0bf34efaec0ee9f775f7d529b609b847"
|
||||||
|
target_release: "v2.1"
|
||||||
|
verified_at: "2026-07-29T20:43:00+08:00"
|
||||||
|
status_note: "管理后台已删除两张报表的中间确认调用;finalize 请求结构按后续 #5343 最终契约收口,pnpm checkpoint 全部通过。"
|
||||||
|
updated_at: "2026-07-29"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【修改接口·管理后台】核单报表取消中间确认并由 finalize 固化 (#5342)
|
||||||
|
|
||||||
|
> **PR**: #5345 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-29 13:28
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单流程不再要求用户分别确认“主报账人报账表”和“单团核算表”。核单未完成时,两张报表查询接口按当前业务数据实时返回;点击完成核单时,前端一次性提交转账、垫资结清和签字凭证信息,服务端按提交时的当前数据重新计算并固化终态。核单完成后,两张报表查询接口只返回该次完成核单时固化的内容,后续来源数据变化不会改写该终态结果。
|
||||||
|
|
||||||
|
## 变更接口(2. 变更清单)
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询主报账人报账表 | GET | `/v3/admin/order/:orderId/settlement/reports/reimbursement` | 行为修改 | 未完成核单时实时计算;完成核单后读取终态结果 |
|
||||||
|
| 2 | 查询单团核算表 | GET | `/v3/admin/order/:orderId/settlement/reports/group` | 行为修改 | 未完成核单时实时计算;完成核单后读取终态结果 |
|
||||||
|
| 3 | 确认主报账人报账表 | POST | `/v3/admin/order/:orderId/settlement/reports/reimbursement/confirm` | 删除 | 接口下线,调用返回业务码 `404` |
|
||||||
|
| 4 | 确认单团核算表 | POST | `/v3/admin/order/:orderId/settlement/reports/group/confirm` | 删除 | 接口下线,调用返回业务码 `404` |
|
||||||
|
| 5 | 完成核单 | POST | `/v3/admin/order/:orderId/settlement/finalize` | 请求与行为修改 | 请求体改为必填;删除两个客户端指纹;新增转账、垫资和签字凭证字段;提交时实时重算并固化终态 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询主报账人报账表
|
||||||
|
|
||||||
|
- **方法 + 路径**: `GET /v3/admin/order/:orderId/settlement/reports/reimbursement`
|
||||||
|
- **使用场景**: 打开核单报账表或刷新核单数据
|
||||||
|
- **认证**: 管理后台 JWT;房务角色不可访问
|
||||||
|
- **幂等性**: 幂等,只读
|
||||||
|
- **请求体**: 无
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `id` | String | 是 | 报账表记录 ID;实时报表和终态快照中可为 `null` |
|
||||||
|
| `orderId` | String | 否 | 订单 ID |
|
||||||
|
| `reportStatus` | String | 否 | 报表状态,见 §6.1 |
|
||||||
|
| `sourceFingerprint` | String | 否 | 当前报表来源指纹,仅用于识别数据版本;前端不再回传 |
|
||||||
|
| `primaryReporterId` | String | 是 | 主报账人 ID |
|
||||||
|
| `primaryReporterName` | String | 是 | 主报账人姓名 |
|
||||||
|
| `primaryReporterRole` | String | 是 | 主报账人角色 |
|
||||||
|
| `reportVersion` | Integer | 否 | 报账表结构版本 |
|
||||||
|
| `driverCollectedTailAmount` | Decimal | 否 | 主报账人代收尾款 |
|
||||||
|
| `approvedAdvanceAmount` | Decimal | 否 | 已审批垫资金额 |
|
||||||
|
| `reportablePaidCostAmount` | Decimal | 否 | 可报账的已付成本 |
|
||||||
|
| `reporterNetAmount` | Decimal | 否 | 报账人净额;大于 0 表示报账人应转给公司,小于 0 表示公司应转给报账人 |
|
||||||
|
| `primaryReporterCollectedAmount` | Decimal | 否 | 主报账人代收金额 |
|
||||||
|
| `publicPrepaidAmount` | Decimal | 否 | 公共预支金额 |
|
||||||
|
| `primaryReporterDueAmount` | Decimal | 否 | 主报账人应报账金额 |
|
||||||
|
| `advanceOutstandingAmount` | Decimal | 否 | 未结清垫资金额 |
|
||||||
|
| `reconNetAmount` | Decimal | 否 | 报账净额 |
|
||||||
|
| `transferDirection` | String | 否 | 转账方向,见 §6.2 |
|
||||||
|
| `transferAmount` | Decimal | 否 | 应转账金额的绝对值 |
|
||||||
|
| `incomeLines` | Array\<Object> | 否 | 主报账人代收明细,字段见下表 |
|
||||||
|
| `expenseLines` | Array\<Object> | 否 | 现金已付成本明细,字段随费用分类变化,字段见下表 |
|
||||||
|
| `advanceLines` | Array\<Object> | 否 | 已审批垫资明细,字段见下表 |
|
||||||
|
| `vehicleLines` | Array\<Object> | 否 | 车辆独立明细;当前返回空数组,车辆金额已进入费用分类和汇总金额 |
|
||||||
|
| `transferStatus` | String | 是 | 未完成核单时为 `null`;终态为 `COMPLETED` |
|
||||||
|
| `transferDate` | String/date | 是 | 转账日期,格式 `YYYY-MM-DD` |
|
||||||
|
| `transferRef` | String | 是 | 转账流水号 |
|
||||||
|
| `advanceSettledFlag` | Boolean | 是 | 垫资是否结清 |
|
||||||
|
| `signedVoucher` | Object | 是 | 签字凭证;结构同 finalize 请求的 `signedVoucher` |
|
||||||
|
| `generatedBy` | String | 是 | 历史生成操作人 ID;实时/终态模式下可为 `null` |
|
||||||
|
| `generatedByName` | String | 是 | 历史生成操作人姓名;实时/终态模式下可为 `null` |
|
||||||
|
| `generatedAt` | String/date-time | 是 | 历史生成时间;实时/终态模式下可为 `null` |
|
||||||
|
| `confirmedBy` | String | 是 | 完成核单操作人 ID;未完成核单时为 `null` |
|
||||||
|
| `confirmedByName` | String | 是 | 完成核单操作人姓名;未完成核单时为 `null` |
|
||||||
|
| `confirmedAt` | String/date-time | 是 | 完成核单时间;未完成核单时为 `null` |
|
||||||
|
|
||||||
|
**`incomeLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | String | 固定为 `DRIVER_CASH_RECEIPT` |
|
||||||
|
| `receiptId` | String | 收款记录 ID |
|
||||||
|
| `amount` | Decimal | 收款金额 |
|
||||||
|
| `channel` | String | 收款渠道;当前参与报账的值为 `DRIVER_CASH` |
|
||||||
|
| `payType` | String/null | 支付类型 |
|
||||||
|
| `collectorStaffId` | String/null | 收款人员 ID |
|
||||||
|
| `collectorName` | String/null | 收款人员姓名 |
|
||||||
|
| `collectorRole` | String/null | 收款人员角色 |
|
||||||
|
| `receivedAt` | String/date-time/null | 收款时间 |
|
||||||
|
| `remark` | String/null | 备注 |
|
||||||
|
|
||||||
|
**`advanceLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | String | 固定为 `APPROVED_ADVANCE` |
|
||||||
|
| `advanceId` | String | 垫资记录 ID |
|
||||||
|
| `payeeStaffId` | String/null | 收款人员 ID |
|
||||||
|
| `payeeName` | String/null | 收款人员姓名 |
|
||||||
|
| `payeeRole` | String/null | 收款人员角色 |
|
||||||
|
| `advanceType` | String/null | 垫资类型 |
|
||||||
|
| `amount` | Decimal | 已审批金额 |
|
||||||
|
| `purpose` | String/null | 用途 |
|
||||||
|
| `voucherUrl` | String/null | 垫资凭证地址 |
|
||||||
|
| `status` | String | 垫资状态 |
|
||||||
|
| `submittedAt` | String/date-time/null | 提交时间 |
|
||||||
|
| `approvedAt` | String/date-time/null | 审批时间 |
|
||||||
|
| `approvedBy` | String/null | 审批人 ID |
|
||||||
|
|
||||||
|
**`expenseLines[]` 公共字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | String | 费用分类,见 §6.3 |
|
||||||
|
| `kind` | String | 明细类型,例如 `HOTEL`、`TICKET`、`MEAL`、`VEHICLE_FEE`、`STAFF:DRIVER` |
|
||||||
|
| `amount` | Decimal | 当前行实际成本 |
|
||||||
|
| `paymentMethod` | String | 当前仅包含 `CASH_PAID` 行 |
|
||||||
|
|
||||||
|
`expenseLines[]` 会按 `kind` 追加以下分类字段:
|
||||||
|
|
||||||
|
- `HOTEL`: `hotelAssignmentId`、`hotelId`、`roomTypeId`、`dayNumber`、`stayDate`、`hotelName`、`roomType`、`roomTypeName`、`roomCount`、`unitPrice`、`plannedCost`、`sourceType`、`sourceId`、`voucherUrls`、`remark`。
|
||||||
|
- `TICKET`: `sourceType`、`scenicAssignmentId`、`dayNumber`、`dayDate`、`scenicName`、`specName`、`ticketCount`、`ticketUnitPrice`、`sellPrice`、`totalAmount`、`plannedCost`、`voucherUrls`、`remark`。
|
||||||
|
- `MEAL`: `mealType`、`mealDate`、`mealName`、`quantity`、`unitPrice`、`voucherUrls`、`remark`。
|
||||||
|
- `VEHICLE_FEE`: `sourceRecordType`、`sourceDetailId`、`serviceDate`、`vehicleId`、`vehiclePlate`、`vehicleModelId`、`vehicleModelName`、`driverId`、`driverName`、`startDate`、`endDate`、`dailyPrice`、`paymentTypeCode`、`paymentTypeName`。
|
||||||
|
- `STAFF:*`: `staffRole`、`staffId`、`staffName`、`totalPlannedCost`、`voucherUrls`、`reimburse`、`settleStatus`、`settledDate`、`transferRef`、`detail`、`remark`。
|
||||||
|
- `EXPENSE:*`: `expenseType`、`projectName`、`expenseDate`、`voucherUrls`、`remark`。
|
||||||
|
- `SUBSIDY:*`: `subsidyType`、`projectName`、`expenseDate`、`voucherUrls`、`remark`。
|
||||||
|
|
||||||
|
**行为**
|
||||||
|
|
||||||
|
- 订单不存在当前终态快照时,每次请求均按当前核单数据计算,`reportStatus=GENERATED`。
|
||||||
|
- 订单存在当前终态快照时,返回完成核单时固化的报账表,`reportStatus=CONFIRMED`。
|
||||||
|
- `sourceFingerprint` 继续返回,但不再作为任何前端确认或 finalize 入参。
|
||||||
|
|
||||||
|
### 3.2 查询单团核算表
|
||||||
|
|
||||||
|
- **方法 + 路径**: `GET /v3/admin/order/:orderId/settlement/reports/group`
|
||||||
|
- **使用场景**: 打开单团核算表或刷新核算结果
|
||||||
|
- **认证**: 管理后台 JWT;房务角色不可访问
|
||||||
|
- **幂等性**: 幂等,只读
|
||||||
|
- **请求体**: 无
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `id` | String | 是 | 单团核算表记录 ID;实时报表和终态快照中可为 `null` |
|
||||||
|
| `orderId` | String | 否 | 订单 ID |
|
||||||
|
| `reportStatus` | String | 否 | 报表状态,见 §6.1 |
|
||||||
|
| `sourceFingerprint` | String | 否 | 当前报表来源指纹,仅用于识别数据版本;前端不再回传 |
|
||||||
|
| `baseOrderAmount` | Decimal | 否 | 订单基础金额 |
|
||||||
|
| `otherIncomeAmount` | Decimal | 否 | 其他收入金额 |
|
||||||
|
| `discountAmount` | Decimal | 否 | 优惠金额 |
|
||||||
|
| `adjustedReceivableAmount` | Decimal | 否 | 调整后应收金额 |
|
||||||
|
| `paidAmount` | Decimal | 否 | 已收金额 |
|
||||||
|
| `actualRefundedAmount` | Decimal | 否 | 实际退款金额 |
|
||||||
|
| `netRevenueAmount` | Decimal | 否 | 净收入 |
|
||||||
|
| `netReceivedAmount` | Decimal | 否 | 净已收 |
|
||||||
|
| `outstandingAmount` | Decimal | 否 | 待收金额 |
|
||||||
|
| `hotelCost` | Decimal | 否 | 住宿成本 |
|
||||||
|
| `ticketCost` | Decimal | 否 | 门票/游玩项目成本 |
|
||||||
|
| `mealCost` | Decimal | 否 | 餐食成本 |
|
||||||
|
| `vehicleCost` | Decimal | 否 | 车辆成本 |
|
||||||
|
| `guideCost` | Decimal | 否 | 导游/领队成本 |
|
||||||
|
| `photographerCost` | Decimal | 否 | 摄影成本 |
|
||||||
|
| `otherExpenseCost` | Decimal | 否 | 其他支出成本 |
|
||||||
|
| `insurancePremium` | Decimal | 否 | 保险保费 |
|
||||||
|
| `totalCost` | Decimal | 否 | 总成本 |
|
||||||
|
| `paidCost` | Decimal | 否 | 已付成本 |
|
||||||
|
| `unpaidCost` | Decimal | 否 | 未付成本 |
|
||||||
|
| `grossProfit` | Decimal | 否 | 毛利 |
|
||||||
|
| `grossProfitRate` | Decimal | 否 | 毛利率,小数形式 |
|
||||||
|
| `travelerCount` | Integer | 否 | 出行人数 |
|
||||||
|
| `perCapitaRevenue` | Decimal | 否 | 人均收入 |
|
||||||
|
| `perCapitaCost` | Decimal | 否 | 人均成本 |
|
||||||
|
| `perCapitaProfit` | Decimal | 否 | 人均利润 |
|
||||||
|
| `incomeLines` | Array\<Object> | 否 | 收入汇总行,固定结构见下表 |
|
||||||
|
| `costCategories` | Array\<Object> | 否 | 成本分类汇总,固定结构见下表 |
|
||||||
|
| `generatedBy` | String | 是 | 历史生成操作人 ID;实时/终态模式下可为 `null` |
|
||||||
|
| `generatedByName` | String | 是 | 历史生成操作人姓名;实时/终态模式下可为 `null` |
|
||||||
|
| `generatedAt` | String/date-time | 是 | 历史生成时间;实时/终态模式下可为 `null` |
|
||||||
|
| `confirmedBy` | String | 是 | 完成核单操作人 ID;未完成核单时为 `null` |
|
||||||
|
| `confirmedByName` | String | 是 | 完成核单操作人姓名;未完成核单时为 `null` |
|
||||||
|
| `confirmedAt` | String/date-time | 是 | 完成核单时间;未完成核单时为 `null` |
|
||||||
|
|
||||||
|
**`incomeLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | String | `BASE_ORDER`、`OTHER_INCOME`、`DISCOUNT` 或 `ACTUAL_REFUND` |
|
||||||
|
| `amount` | Decimal | 金额;优惠和实际退款以负数返回 |
|
||||||
|
|
||||||
|
**`costCategories[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | String | `HOTEL`、`TICKET`、`MEAL`、`VEHICLE`、`GUIDE`、`PHOTOGRAPHER`、`OTHER_EXPENSE` 或 `INSURANCE` |
|
||||||
|
| `amount` | Decimal | 分类成本 |
|
||||||
|
|
||||||
|
**行为**
|
||||||
|
|
||||||
|
- 订单不存在当前终态快照时,每次请求均按当前核单数据计算,`reportStatus=GENERATED`。
|
||||||
|
- 订单存在当前终态快照时,返回完成核单时固化的单团核算表,`reportStatus=CONFIRMED`。
|
||||||
|
- `sourceFingerprint` 继续返回,但不再作为任何前端确认或 finalize 入参。
|
||||||
|
|
||||||
|
### 3.3 完成核单
|
||||||
|
|
||||||
|
- **方法 + 路径**: `POST /v3/admin/order/:orderId/settlement/finalize`
|
||||||
|
- **使用场景**: 用户检查实时主报账表和单团核算表后,点击完成核单
|
||||||
|
- **认证**: 管理后台 JWT;需要核单资金写权限;房务角色不可访问
|
||||||
|
- **幂等性**: 已存在当前终态快照时,重复请求返回已有终态结果,不重新生成新终态
|
||||||
|
- **限流**: 无接口级特殊限流
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|
||||||
|
|------|------|------|------|----------|
|
||||||
|
| `remark` | String | 否 | 核单整体备注 | 最多 `500` 字符 |
|
||||||
|
| `transferDate` | String/date | 条件必填 | 转账日期 | `reporterNetAmount != 0` 时必填;格式 `YYYY-MM-DD` |
|
||||||
|
| `transferRef` | String | 条件必填 | 转账流水号 | `reporterNetAmount != 0` 时必须为非空字符串;最多 `128` 字符 |
|
||||||
|
| `advanceSettledFlag` | Boolean | 是 | 垫资是否结清;必须明确传 `true` 或 `false` | 不可为 `null` |
|
||||||
|
| `signedVoucher` | Object | 是 | 签字凭证 | 不可为 `null` |
|
||||||
|
| `signedVoucher.files` | Array\<Object> | 是 | 签字凭证文件 | `1`~`9` 项;重复 URL 按规范化后的 URL 去重并保留首项 |
|
||||||
|
| `signedVoucher.files[].name` | String | 否 | 文件名 | 最多 `255` 字符 |
|
||||||
|
| `signedVoucher.files[].url` | String | 是 | 文件地址 | 非空;最多 `1024` 字符;必须是带有效主机名的绝对 `http/https` URL |
|
||||||
|
| `signedVoucher.note` | String | 否 | 签字凭证备注 | 最多 `500` 字符 |
|
||||||
|
|
||||||
|
以下字段已经删除,前端不得继续发送:
|
||||||
|
|
||||||
|
| 删除字段 | 原类型 | 迁移方式 |
|
||||||
|
|----------|--------|----------|
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | String | 删除本地缓存和提交逻辑;完成核单时不再回传报账表指纹 |
|
||||||
|
| `groupExpectedSourceFingerprint` | String | 删除本地缓存和提交逻辑;完成核单时不再回传单团核算表指纹 |
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `summaryId` | String | 核单汇总 ID |
|
||||||
|
| `finalSnapshotId` | String | 核单终态快照 ID |
|
||||||
|
| `finalSnapshotVersionNo` | Integer | 终态快照版本号,从 `1` 开始 |
|
||||||
|
| `finalSnapshotStatus` | String | 当前终态固定为 `FINALIZED` |
|
||||||
|
| `orderId` | String | 订单 ID |
|
||||||
|
| `settledAt` | String/date-time | 核单完成时间 |
|
||||||
|
| `totalAmount` | String/Decimal | 订单总金额快照 |
|
||||||
|
| `paidAmount` | String/Decimal | 已付金额快照 |
|
||||||
|
| `balanceAmount` | String/Decimal | 尾款金额快照 |
|
||||||
|
| `roomCost` | String/Decimal | 住宿实际成本 |
|
||||||
|
| `ticketCost` | String/Decimal | 门票实际成本 |
|
||||||
|
| `staffCost` | String/Decimal | 人员费用实际成本 |
|
||||||
|
| `subsidyCost` | String/Decimal | 补助实际成本 |
|
||||||
|
| `mealCost` | String/Decimal | 餐食实际成本 |
|
||||||
|
| `vehicleCost` | String/Decimal | 车辆基础服务总车费 |
|
||||||
|
| `otherExpenseCost` | String/Decimal | 其他支出实际成本 |
|
||||||
|
| `insurancePremium` | String/Decimal | 保险实际保费 |
|
||||||
|
| `totalActualCost` | String/Decimal | 总实际成本 |
|
||||||
|
| `driverTransferAmount` | String/Decimal | 给司机/主报账人转回金额 |
|
||||||
|
| `profitAmount` | String/Decimal | 公司毛利 |
|
||||||
|
| `profitRate` | Decimal | 毛利率;订单总金额为 `0` 时返回 `0` |
|
||||||
|
| `orderStatusAfter` | String | 当前返回 `待财务复核` |
|
||||||
|
| `mqTriggered` | Boolean | 当前固定返回 `false`;完成核单不发布结算 MQ |
|
||||||
|
| `warnings` | Array\<String> | 软预警列表;不阻塞完成核单 |
|
||||||
|
|
||||||
|
**提交行为**
|
||||||
|
|
||||||
|
1. 前端不再先调用任何报表“确认”接口。
|
||||||
|
2. 服务端按提交时的当前核单数据重新计算两张报表和所有汇总金额,不采信前端缓存的金额或指纹。
|
||||||
|
3. `transferDate`、`transferRef`、`advanceSettledFlag` 和 `signedVoucher` 与本次完成核单结果一并固化。
|
||||||
|
4. 成功后,两张 GET 报表接口返回本次固化结果。
|
||||||
|
|
||||||
|
### 3.4 已删除的报表确认接口
|
||||||
|
|
||||||
|
以下接口不再有可用请求契约:
|
||||||
|
|
||||||
|
| 原接口 | 原请求字段 | 当前结果 |
|
||||||
|
|--------|------------|----------|
|
||||||
|
| `POST /v3/admin/order/:orderId/settlement/reports/reimbursement/confirm` | `expectedSourceFingerprint`、`transferStatus`、`transferDate`、`transferRef`、`advanceSettledFlag`、`signedVoucher` | 业务码 `404` |
|
||||||
|
| `POST /v3/admin/order/:orderId/settlement/reports/group/confirm` | `expectedSourceFingerprint` | 业务码 `404` |
|
||||||
|
|
||||||
|
前端必须删除这两个请求,不要用忽略 `404`、重试或降级继续调用的方式兼容。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 通用路径参数
|
||||||
|
|
||||||
|
| 接口 | 字段 | 类型 | 必填 | 规则 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| 两张 GET 报表、finalize | `orderId` | String/Long | 是 | 必须大于 `0` |
|
||||||
|
|
||||||
|
### 4.2 请求体变化总览
|
||||||
|
|
||||||
|
| 接口 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 主报账表确认 | 独立 POST 提交报账表指纹及凭证 | 接口删除 |
|
||||||
|
| 单团核算表确认 | 独立 POST 提交单团核算表指纹 | 接口删除 |
|
||||||
|
| finalize | 请求体可缺省;主要提交两个报告指纹和可选 `remark` | 请求体必填;提交 `remark`、`transferDate`、`transferRef`、`advanceSettledFlag`、`signedVoucher`;不再提交任何指纹 |
|
||||||
|
|
||||||
|
## 5. 出参
|
||||||
|
|
||||||
|
### 5.1 报表查询
|
||||||
|
|
||||||
|
- 两张 GET 接口的字段结构保持不变。
|
||||||
|
- 未完成核单时返回最新实时计算结果。
|
||||||
|
- 完成核单后返回完成核单时固化的结果。
|
||||||
|
- 报表 `sourceFingerprint` 仍存在于出参,但只表示数据版本,前端不得再将其用于确认或 finalize。
|
||||||
|
|
||||||
|
### 5.2 完成核单
|
||||||
|
|
||||||
|
- finalize 响应字段结构保持 `SettlementSubmitRespVO`。
|
||||||
|
- `finalSnapshotId`、`finalSnapshotVersionNo`、`finalSnapshotStatus` 标识本次固化结果。
|
||||||
|
- `mqTriggered` 的当前契约为固定 `false`,前端不得用该字段判断是否需要等待 MQ。
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `reportStatus`
|
||||||
|
|
||||||
|
**所属字段**: 两张报表响应 `reportStatus` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 当前语义 |
|
||||||
|
|----|------|----------|
|
||||||
|
| `GENERATED` | 实时结果 | 订单未完成核单,响应按当前数据实时计算 |
|
||||||
|
| `CONFIRMED` | 已固化 | 订单已完成核单,响应来自终态结果 |
|
||||||
|
| `STALE` | 历史过期状态 | 仅兼容历史报表数据;新实时查询流程不要求前端据此重新生成或确认 |
|
||||||
|
|
||||||
|
### 6.2 `transferDirection`
|
||||||
|
|
||||||
|
**所属字段**: 主报账人报账表 `transferDirection` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `REPORTER_TO_COMPANY` | 报账人转给公司 | `reporterNetAmount > 0` |
|
||||||
|
| `COMPANY_TO_REPORTER` | 公司转给报账人 | `reporterNetAmount < 0` |
|
||||||
|
| `BALANCED` | 已平衡 | `reporterNetAmount = 0` |
|
||||||
|
|
||||||
|
### 6.3 报账费用分类
|
||||||
|
|
||||||
|
**所属字段**: `expenseLines[].category`、`costCategories[].category` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `HOTEL` | 住宿 | 住宿成本 |
|
||||||
|
| `TICKET` | 门票/游玩项目 | 门票及游玩项目成本 |
|
||||||
|
| `MEAL` | 餐食 | 餐食成本 |
|
||||||
|
| `VEHICLE` | 车辆 | 车辆成本 |
|
||||||
|
| `GUIDE` | 导游/领队 | 导游及领队成本 |
|
||||||
|
| `PHOTOGRAPHER` | 摄影 | 摄影成本 |
|
||||||
|
| `OTHER_EXPENSE` | 其他支出 | 其他支出成本 |
|
||||||
|
| `INSURANCE` | 保险 | 保险保费;仅用于单团核算表成本分类 |
|
||||||
|
|
||||||
|
### 6.4 `finalSnapshotStatus`
|
||||||
|
|
||||||
|
**所属字段**: finalize 响应 `finalSnapshotStatus` | **类型**: String
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `FINALIZED` | 已固化 | 当前核单终态有效 |
|
||||||
|
|
||||||
|
### 6.5 `transferStatus`
|
||||||
|
|
||||||
|
**所属字段**: 主报账人报账表 `transferStatus` | **类型**: String/null
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `COMPLETED` | 已完成 | finalize 成功后固化到终态报账表 |
|
||||||
|
| `null` | 未固化 | 尚未完成核单的实时报表 |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 请求参数校验失败 | `orderId <= 0`、finalize 缺请求体/必填字段、字段超长、凭证文件数量不在 1~9 |
|
||||||
|
| `403` | 无访问权限 | 房务角色或无权访问当前订单 |
|
||||||
|
| `404` | 接口不存在 | 继续调用两个已删除的 `/confirm` 接口 |
|
||||||
|
| `584051` | 当前核单状态不允许提交结算 | review 状态不是待核单或核单中 |
|
||||||
|
| `584056` | 订单已结算,不能重复提交 | 已存在结算汇总但缺少可返回的当前终态 |
|
||||||
|
| `584071` | 无权访问该订单(公司隔离) | 当前管理员不能查看该订单 |
|
||||||
|
| `584077` | 存在未确认的其他收入 | finalize 前其他收入未确认 |
|
||||||
|
| `584078` | 其他收入关联附加费已失效或金额不一致 | finalize 前其他收入与当前附加费不一致 |
|
||||||
|
| `584079` | 存在未纳入核单分类的有效附加费 | finalize 前还有有效附加费未进入核单 |
|
||||||
|
| `584081` | 对账数据不一致 | 已付金额与有效支付、线下收款合计不一致 |
|
||||||
|
| `584082` | 存在待收尾款 | 单团核算 `outstandingAmount != 0` |
|
||||||
|
| `584085` | 存在辅助人员结算未完成 | 辅助人员未全部完成结算或缺转账流水 |
|
||||||
|
| `584092` | 存在未确认的人员费用 | 人员费用确认状态未全部完成 |
|
||||||
|
| `584097` | 凭证 URL 格式或数量不合法 | URL 不是有效绝对 `http/https` 地址、为空、过长或数量超限 |
|
||||||
|
| `584100` | 车辆总车费暂时不可用 | 报表查询或 finalize 暂时无法取得车辆费用 |
|
||||||
|
| `584101` | 存在未完结派车或未确认车辆总车费 | 当前车辆数据尚不能用于核单 |
|
||||||
|
| `584102` | 当前用车需求没有可核单的车辆总车费 | 有用车需求但没有可用车辆费用 |
|
||||||
|
| `584315` | 核单来源数据已变化,请刷新后重新确认 | finalize 提交期间当前用车需求发生变化 |
|
||||||
|
| `584316` | 核单报告发生并发变化,请刷新后重试 | 同一订单并发完成核单发生冲突 |
|
||||||
|
| `584317` | 当前报告状态不允许执行该操作 | 报账人净额非 0 但缺转账日期/流水,或签字凭证不可用 |
|
||||||
|
| `584320` | 核单分类明细尚未保存完整 | 当前分类数据不能用于报账和 finalize |
|
||||||
|
|
||||||
|
## 验证证据(8. 示例)
|
||||||
|
|
||||||
|
### 8.1 典型成功:查询实时主报账人报账表
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "7a0e84a9f9d0cb411f9cff8d6a0d1c047726bb05b68c8e23af5629afdbdd67c1",
|
||||||
|
"primaryReporterId": "3001",
|
||||||
|
"primaryReporterName": "示例报账人",
|
||||||
|
"primaryReporterRole": "DRIVER",
|
||||||
|
"reportVersion": 1,
|
||||||
|
"driverCollectedTailAmount": 2000.00,
|
||||||
|
"approvedAdvanceAmount": 500.00,
|
||||||
|
"reportablePaidCostAmount": 1200.00,
|
||||||
|
"reporterNetAmount": 1300.00,
|
||||||
|
"primaryReporterCollectedAmount": 2000.00,
|
||||||
|
"publicPrepaidAmount": 1200.00,
|
||||||
|
"primaryReporterDueAmount": 800.00,
|
||||||
|
"advanceOutstandingAmount": 500.00,
|
||||||
|
"reconNetAmount": 1300.00,
|
||||||
|
"transferDirection": "REPORTER_TO_COMPANY",
|
||||||
|
"transferAmount": 1300.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{
|
||||||
|
"type": "DRIVER_CASH_RECEIPT",
|
||||||
|
"receiptId": "9100000000001",
|
||||||
|
"amount": 2000.00,
|
||||||
|
"channel": "DRIVER_CASH",
|
||||||
|
"payType": "CASH",
|
||||||
|
"collectorStaffId": "3001",
|
||||||
|
"collectorName": "示例报账人",
|
||||||
|
"collectorRole": "DRIVER",
|
||||||
|
"receivedAt": "2026-07-28T15:30:00",
|
||||||
|
"remark": "示例代收尾款"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expenseLines": [
|
||||||
|
{
|
||||||
|
"category": "HOTEL",
|
||||||
|
"kind": "HOTEL",
|
||||||
|
"hotelAssignmentId": "9200000000001",
|
||||||
|
"hotelId": "1001",
|
||||||
|
"roomTypeId": "2001",
|
||||||
|
"dayNumber": 1,
|
||||||
|
"stayDate": "2026-07-20",
|
||||||
|
"hotelName": "示例酒店",
|
||||||
|
"roomType": "STANDARD",
|
||||||
|
"roomTypeName": "标准间",
|
||||||
|
"roomCount": 2,
|
||||||
|
"unitPrice": 300.00,
|
||||||
|
"plannedCost": 600.00,
|
||||||
|
"amount": 600.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"sourceType": "HOUSE_ASSIGNMENT",
|
||||||
|
"sourceId": "9200000000001",
|
||||||
|
"voucherUrls": ["https://oss.example.com/vouchers/hotel-1.pdf"],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"advanceLines": [
|
||||||
|
{
|
||||||
|
"type": "APPROVED_ADVANCE",
|
||||||
|
"advanceId": "9300000000001",
|
||||||
|
"payeeStaffId": "3001",
|
||||||
|
"payeeName": "示例报账人",
|
||||||
|
"payeeRole": "DRIVER",
|
||||||
|
"advanceType": "PUBLIC",
|
||||||
|
"amount": 500.00,
|
||||||
|
"purpose": "行程公共支出",
|
||||||
|
"voucherUrl": "https://oss.example.com/vouchers/advance-1.pdf",
|
||||||
|
"status": "APPROVED",
|
||||||
|
"submittedAt": "2026-07-19T10:00:00",
|
||||||
|
"approvedAt": "2026-07-19T11:00:00",
|
||||||
|
"approvedBy": "10001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicleLines": [],
|
||||||
|
"transferStatus": null,
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": null,
|
||||||
|
"signedVoucher": null,
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 典型成功:查询实时单团核算表
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/group
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "651cb6708a49f169e2ccb1b455267def9cc1a69d06935f9d9eeb41919897e0fb",
|
||||||
|
"baseOrderAmount": 24800.00,
|
||||||
|
"otherIncomeAmount": 500.00,
|
||||||
|
"discountAmount": 300.00,
|
||||||
|
"adjustedReceivableAmount": 25000.00,
|
||||||
|
"paidAmount": 25000.00,
|
||||||
|
"actualRefundedAmount": 0.00,
|
||||||
|
"netRevenueAmount": 25000.00,
|
||||||
|
"netReceivedAmount": 25000.00,
|
||||||
|
"outstandingAmount": 0.00,
|
||||||
|
"hotelCost": 4280.00,
|
||||||
|
"ticketCost": 3680.00,
|
||||||
|
"mealCost": 860.00,
|
||||||
|
"vehicleCost": 1260.00,
|
||||||
|
"guideCost": 800.00,
|
||||||
|
"photographerCost": 600.00,
|
||||||
|
"otherExpenseCost": 300.00,
|
||||||
|
"insurancePremium": 180.00,
|
||||||
|
"totalCost": 11960.00,
|
||||||
|
"paidCost": 11960.00,
|
||||||
|
"unpaidCost": 0.00,
|
||||||
|
"grossProfit": 13040.00,
|
||||||
|
"grossProfitRate": 0.521600,
|
||||||
|
"travelerCount": 5,
|
||||||
|
"perCapitaRevenue": 5000.00,
|
||||||
|
"perCapitaCost": 2392.00,
|
||||||
|
"perCapitaProfit": 2608.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{
|
||||||
|
"type": "BASE_ORDER",
|
||||||
|
"amount": 24800.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "OTHER_INCOME",
|
||||||
|
"amount": 500.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "DISCOUNT",
|
||||||
|
"amount": -300.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ACTUAL_REFUND",
|
||||||
|
"amount": 0.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"costCategories": [
|
||||||
|
{
|
||||||
|
"category": "HOTEL",
|
||||||
|
"amount": 4280.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "TICKET",
|
||||||
|
"amount": 3680.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "MEAL",
|
||||||
|
"amount": 860.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "VEHICLE",
|
||||||
|
"amount": 1260.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "GUIDE",
|
||||||
|
"amount": 800.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "PHOTOGRAPHER",
|
||||||
|
"amount": 600.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "OTHER_EXPENSE",
|
||||||
|
"amount": 300.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "INSURANCE",
|
||||||
|
"amount": 180.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 典型成功:完成核单并固化
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"remark": "核单完成",
|
||||||
|
"transferDate": "2026-07-29",
|
||||||
|
"transferRef": "BANK-20260729-001",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/vouchers/signed-20260729.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": "签字凭证已回收"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"summaryId": "9400000000001",
|
||||||
|
"finalSnapshotId": "9400000000002",
|
||||||
|
"finalSnapshotVersionNo": 1,
|
||||||
|
"finalSnapshotStatus": "FINALIZED",
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"settledAt": "2026-07-29T13:28:22",
|
||||||
|
"totalAmount": "25000.00",
|
||||||
|
"paidAmount": "25000.00",
|
||||||
|
"balanceAmount": "0.00",
|
||||||
|
"roomCost": "4280.00",
|
||||||
|
"ticketCost": "3680.00",
|
||||||
|
"staffCost": "1400.00",
|
||||||
|
"subsidyCost": "0.00",
|
||||||
|
"mealCost": "860.00",
|
||||||
|
"vehicleCost": "1260.00",
|
||||||
|
"otherExpenseCost": "300.00",
|
||||||
|
"insurancePremium": "180.00",
|
||||||
|
"totalActualCost": "11960.00",
|
||||||
|
"driverTransferAmount": "11780.00",
|
||||||
|
"profitAmount": "13040.00",
|
||||||
|
"profitRate": 0.5216,
|
||||||
|
"orderStatusAfter": "待财务复核",
|
||||||
|
"mqTriggered": false,
|
||||||
|
"warnings": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 边界:报账人净额为 0
|
||||||
|
|
||||||
|
当最新 `reporterNetAmount=0` 时,`transferDate` 和 `transferRef` 可以省略;`advanceSettledFlag` 和 `signedVoucher` 仍必须提交。
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000002/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"remark": "收支已平衡",
|
||||||
|
"advanceSettledFlag": false,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "签字单.jpg",
|
||||||
|
"url": "https://oss.example.com/vouchers/signed-balanced.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"summaryId": "9400000000011",
|
||||||
|
"finalSnapshotId": "9400000000012",
|
||||||
|
"finalSnapshotVersionNo": 1,
|
||||||
|
"finalSnapshotStatus": "FINALIZED",
|
||||||
|
"orderId": "1914050000000002",
|
||||||
|
"settledAt": "2026-07-29T13:40:00",
|
||||||
|
"totalAmount": "0.00",
|
||||||
|
"paidAmount": "0.00",
|
||||||
|
"balanceAmount": "0.00",
|
||||||
|
"roomCost": "0.00",
|
||||||
|
"ticketCost": "0.00",
|
||||||
|
"staffCost": "0.00",
|
||||||
|
"subsidyCost": "0.00",
|
||||||
|
"mealCost": "0.00",
|
||||||
|
"vehicleCost": "0.00",
|
||||||
|
"otherExpenseCost": "0.00",
|
||||||
|
"insurancePremium": "0.00",
|
||||||
|
"totalActualCost": "0.00",
|
||||||
|
"driverTransferAmount": "0.00",
|
||||||
|
"profitAmount": "0.00",
|
||||||
|
"profitRate": 0,
|
||||||
|
"orderStatusAfter": "待财务复核",
|
||||||
|
"mqTriggered": false,
|
||||||
|
"warnings": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 异常:仍调用已删除的确认接口
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"expectedSourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"msg": "请求地址不存在",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 异常:车辆费用尚未可核单
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"transferDate": "2026-07-29",
|
||||||
|
"transferRef": "BANK-20260729-001",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "签字单.pdf",
|
||||||
|
"url": "https://oss.example.com/vouchers/signed-20260729.pdf"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584101,
|
||||||
|
"msg": "存在未完结派车或未确认车辆总车费,暂不能核单",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- 报表 GET 的“实时”以每次请求时可用于核单的当前数据为准,前端不要把上一次响应当作提交凭据。
|
||||||
|
- 完成核单前,前端可以重复查询两张报表;无需执行任何“生成”或“确认”步骤。
|
||||||
|
- finalize 不接收前端金额。页面展示金额与提交时权威数据发生变化时,以提交时重新计算结果为准。
|
||||||
|
- 报账人净额不为 `0` 时,必须同时提交 `transferDate` 和非空 `transferRef`。
|
||||||
|
- `signedVoucher.files` 原始数组必须为 `1`~`9` 项;URL 会去除首尾空格、规范化并按 URL 去重。
|
||||||
|
- 完成核单成功后,两张报表进入终态读取;只有业务上的核单反确认使当前终态失效后,查询才重新进入实时模式。
|
||||||
|
- finalize 成功响应中的 `warnings` 是软预警,不表示提交失败。
|
||||||
|
- 已有当前终态时重复调用 finalize 返回已有终态,不会依据本次请求改写已固化的转账或凭证信息。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 接口/字段 | 修改前 | 修改后 |
|
||||||
|
|-----------|--------|--------|
|
||||||
|
| finalize 请求体 | 可缺省 | 必填 |
|
||||||
|
| `remark` | 可选,最多 500 字符 | 保持不变 |
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | finalize 必填 | 删除 |
|
||||||
|
| `groupExpectedSourceFingerprint` | finalize 必填 | 删除 |
|
||||||
|
| `transferDate` | 在主报账表确认接口提交 | 移至 finalize;报账人净额非 0 时必填 |
|
||||||
|
| `transferRef` | 在主报账表确认接口提交 | 移至 finalize;报账人净额非 0 时必填,最多 128 字符 |
|
||||||
|
| `advanceSettledFlag` | 在主报账表确认接口提交 | 移至 finalize,必填 |
|
||||||
|
| `signedVoucher` | 在主报账表确认接口提交 | 移至 finalize,必填 |
|
||||||
|
| `signedVoucher.files` | 原确认接口字段 | finalize 中要求 1~9 项 |
|
||||||
|
| `signedVoucher.files[].name` | 原确认接口未明确长度 | 最多 255 字符 |
|
||||||
|
| `signedVoucher.files[].url` | 原确认接口未明确长度 | 必填;最多 1024 字符;绝对 http/https URL |
|
||||||
|
| `signedVoucher.note` | 原确认接口未明确长度 | 最多 500 字符 |
|
||||||
|
| `mqTriggered` | 示例和历史说明可能按 `true` 理解 | 当前固定 `false` |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 主报账表 | 先读取,再调用独立 confirm | GET 实时读取;不再确认 |
|
||||||
|
| 单团核算表 | 主报账表确认后再读取并 confirm | GET 实时读取;不再确认 |
|
||||||
|
| 数据变化处理 | 前端携带两张报表指纹,指纹过期时刷新重试 | 前端不携带指纹;finalize 按提交时数据重算 |
|
||||||
|
| 转账/垫资/签字信息 | 主报账表 confirm 时提交 | finalize 时一次提交 |
|
||||||
|
| 完成核单后查询 | 依赖已确认报表记录 | 返回完成核单时固化的终态结果 |
|
||||||
|
| 重复 finalize | 依赖旧报告确认门禁 | 已有当前终态时返回已有结果 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 是。两个 POST 确认接口删除,finalize 请求字段和必填规则改变。
|
||||||
|
- **前端是否必须同步调整**: 是。旧页面继续调用 `/confirm` 会收到业务码 `404`;旧 finalize 请求缺少新必填字段会收到业务码 `400`。
|
||||||
|
- **查询字段兼容性**: 两张 GET 报表的顶层字段结构保持不变,但数据时效语义变为“未终态实时、终态固定”。
|
||||||
|
|
||||||
|
### 11.2 回滚说明
|
||||||
|
|
||||||
|
- 如果接口契约回滚,前端需要同步恢复两次确认请求和两个指纹字段。
|
||||||
|
- 前后端不能混用新旧流程:新版前端不再保留报表确认指纹,旧版后端仍会要求指纹和独立确认。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 删除“确认主报账表”“确认单团核算表”按钮、请求封装、loading 状态、重试逻辑和指纹缓存。
|
||||||
|
- 页面展示仍调用两个 GET 接口;无需在详情加载时调用任何写接口。
|
||||||
|
- “完成核单”按钮直接提交 finalize 新请求体。
|
||||||
|
- 不要把 GET 返回的 `sourceFingerprint` 填回 finalize。
|
||||||
|
- 不要继续发送已删除字段;即使服务端当前可能忽略未知 JSON 字段,前端类型和请求对象也应删除。
|
||||||
|
- `signedVoucher` 不是可选附件:至少需要一个有效 URL。
|
||||||
|
- `mqTriggered=false` 是当前固定契约,不要显示“MQ 触发失败”或据此轮询。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5342](https://git.1814.love:8443/wx/HL/issues/5342)
|
||||||
|
- **PR**: [#5345](https://git.1814.love:8443/wx/HL/pulls/5345)
|
||||||
|
- **Merge commit**: [eb9ecfafad](https://git.1814.love:8443/wx/HL/commit/eb9ecfafadde4cb9e2dbe5be8193abcf569ca44c)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
|
- **消费端**: v3 管理后台
|
||||||
@ -0,0 +1,874 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5343"
|
||||||
|
title: "核单确认收口到完成核单"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "pi:019fadb7-dac9-74bf-9581-058835251208"
|
||||||
|
frontend_ref: "1444fc7f0bf34efaec0ee9f775f7d529b609b847"
|
||||||
|
target_release: "v2.1"
|
||||||
|
verified_at: "2026-07-29T20:43:00+08:00"
|
||||||
|
status_note: "管理后台已删除旧报表 confirm 流程,完成核单改为提交双指纹与嵌套 reimbursementConfirmation;pnpm checkpoint 全部通过。"
|
||||||
|
updated_at: "2026-07-29"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# ⚠️【修改接口·管理后台】核单确认收口到完成核单 (#5343)
|
||||||
|
|
||||||
|
> **PR**: #5347 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-29
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
主报账表和单团核算表不再各自提供“确认”写操作。页面先通过两张 GET 报表取得同一轮核单事实对应的两个 `sourceFingerprint`,再由“完成核单”一次提交双指纹、转账信息、预支处理标志和签字凭证。
|
||||||
|
|
||||||
|
本文纠正并取代 `29_5342_核单报表取消中间确认并由finalize固化-修改接口-管理后台.md` 中关于 finalize 请求的说明:**双指纹没有删除,仍是 finalize 必填字段;转账与凭证字段必须放在必填的 `reimbursementConfirmation` 对象内。**
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 查询主报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 行为明确 | 返回主报账数据及 `sourceFingerprint`,该指纹必须回传给 finalize |
|
||||||
|
| 2 | 查询单团核算表 | GET | `/v3/admin/order/{orderId}/settlement/reports/group` | 行为明确 | 返回单团核算数据及 `sourceFingerprint`,该指纹必须回传给 finalize |
|
||||||
|
| 3 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 请求与行为修改 | 必填双指纹和嵌套 `reimbursementConfirmation`;成功后一次完成核单 |
|
||||||
|
| 4 | 确认主报账表 | POST | `/v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm` | 删除接口 | 路由继续保持删除,不得调用 |
|
||||||
|
| 5 | 确认单团核算表 | POST | `/v3/admin/order/{orderId}/settlement/reports/group/confirm` | 删除接口 | 路由继续保持删除,不得调用 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询主报账表
|
||||||
|
|
||||||
|
- **方法与路径**:`GET /v3/admin/order/{orderId}/settlement/reports/reimbursement`
|
||||||
|
- **使用场景**:展示主报账表,并在调用 finalize 前取得最新主报账指纹
|
||||||
|
- **认证**:管理后台 JWT;房务角色不可访问
|
||||||
|
- **幂等性**:幂等,只读
|
||||||
|
- **限流**:无接口级特殊限流
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**请求体**
|
||||||
|
|
||||||
|
无。
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| `data` 字段 | JSON 类型 | 可空 | 说明 |
|
||||||
|
|-------------|-----------|:---:|------|
|
||||||
|
| `id` | string | 是 | 报账表记录 ID |
|
||||||
|
| `orderId` | string | 否 | 订单 ID |
|
||||||
|
| `reportStatus` | string | 否 | 报表状态,见 §6.1 |
|
||||||
|
| `sourceFingerprint` | string | 否 | 64 位小写十六进制 SHA-256;传给 finalize 的 `reimbursementExpectedSourceFingerprint` |
|
||||||
|
| `primaryReporterId` | string | 是 | 主报账人 ID |
|
||||||
|
| `primaryReporterName` | string | 是 | 主报账人姓名 |
|
||||||
|
| `primaryReporterRole` | string | 是 | 主报账人角色 |
|
||||||
|
| `reportVersion` | integer | 否 | 报账表结构版本 |
|
||||||
|
| `driverCollectedTailAmount` | number | 否 | 主报账人代收尾款 |
|
||||||
|
| `approvedAdvanceAmount` | number | 否 | 已审批预支金额 |
|
||||||
|
| `reportablePaidCostAmount` | number | 否 | 可报账的已付成本 |
|
||||||
|
| `reporterNetAmount` | number | 否 | 主报账人净额;决定转账日期和流水是否必填 |
|
||||||
|
| `primaryReporterCollectedAmount` | number | 否 | 主报账人代收金额 |
|
||||||
|
| `publicPrepaidAmount` | number | 否 | 公共预支金额 |
|
||||||
|
| `primaryReporterDueAmount` | number | 否 | 主报账人应报账金额 |
|
||||||
|
| `advanceOutstandingAmount` | number | 否 | 未结清预支金额 |
|
||||||
|
| `reconNetAmount` | number | 否 | 报账净额 |
|
||||||
|
| `transferDirection` | string | 否 | 转账方向,见 §6.2 |
|
||||||
|
| `transferAmount` | number | 否 | 应转账金额的绝对值 |
|
||||||
|
| `incomeLines` | array<object> | 否 | 主报账人代收明细,结构见下表 |
|
||||||
|
| `expenseLines` | array<object> | 否 | 主报账成本明细,结构见下表 |
|
||||||
|
| `advanceLines` | array<object> | 否 | 已审批预支明细,结构见下表 |
|
||||||
|
| `vehicleLines` | array<object> | 否 | 车辆独立明细;没有独立行时为 `[]` |
|
||||||
|
| `transferStatus` | string | 是 | 未完成核单时可为 `null`;终态为 `COMPLETED` |
|
||||||
|
| `transferDate` | string(date) | 是 | 转账日期,格式 `YYYY-MM-DD` |
|
||||||
|
| `transferRef` | string | 是 | 转账流水号 |
|
||||||
|
| `advanceSettledFlag` | boolean | 是 | 预支是否已处理 |
|
||||||
|
| `signedVoucher` | object | 是 | 签字凭证;结构与 finalize 的凭证一致 |
|
||||||
|
| `generatedBy` | string | 是 | 历史生成操作人 ID |
|
||||||
|
| `generatedByName` | string | 是 | 历史生成操作人姓名 |
|
||||||
|
| `generatedAt` | string(date-time) | 是 | 历史生成时间 |
|
||||||
|
| `confirmedBy` | string | 是 | 完成核单操作人 ID |
|
||||||
|
| `confirmedByName` | string | 是 | 完成核单操作人姓名 |
|
||||||
|
| `confirmedAt` | string(date-time) | 是 | 完成核单时间 |
|
||||||
|
|
||||||
|
**`incomeLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | string | 当前为 `DRIVER_CASH_RECEIPT` |
|
||||||
|
| `receiptId` | string | 收款记录 ID |
|
||||||
|
| `amount` | number | 收款金额 |
|
||||||
|
| `channel` | string | 收款渠道 |
|
||||||
|
| `payType` | string/null | 支付类型 |
|
||||||
|
| `collectorStaffId` | string/null | 收款人员 ID |
|
||||||
|
| `collectorName` | string/null | 收款人员姓名 |
|
||||||
|
| `collectorRole` | string/null | 收款人员角色 |
|
||||||
|
| `receivedAt` | string(date-time)/null | 收款时间 |
|
||||||
|
| `remark` | string/null | 备注 |
|
||||||
|
|
||||||
|
**`advanceLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | string | 当前为 `APPROVED_ADVANCE` |
|
||||||
|
| `advanceId` | string | 预支记录 ID |
|
||||||
|
| `payeeStaffId` | string/null | 收款人员 ID |
|
||||||
|
| `payeeName` | string/null | 收款人员姓名 |
|
||||||
|
| `payeeRole` | string/null | 收款人员角色 |
|
||||||
|
| `advanceType` | string/null | 预支类型 |
|
||||||
|
| `amount` | number | 已审批金额 |
|
||||||
|
| `purpose` | string/null | 用途 |
|
||||||
|
| `voucherUrl` | string/null | 预支凭证地址 |
|
||||||
|
| `status` | string | 预支状态 |
|
||||||
|
| `submittedAt` | string(date-time)/null | 提交时间 |
|
||||||
|
| `approvedAt` | string(date-time)/null | 审批时间 |
|
||||||
|
| `approvedBy` | string/null | 审批人 ID |
|
||||||
|
|
||||||
|
**`expenseLines[]` 公共字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | string | 费用分类,见 §6.3 |
|
||||||
|
| `kind` | string | 明细类型,例如 `HOTEL`、`TICKET`、`MEAL`、`VEHICLE_FEE`、`STAFF:DRIVER` |
|
||||||
|
| `amount` | number | 当前行实际成本 |
|
||||||
|
| `paymentMethod` | string | 当前报账明细使用 `CASH_PAID` |
|
||||||
|
|
||||||
|
不同 `kind` 还会携带相应业务字段:
|
||||||
|
|
||||||
|
- `HOTEL`:`hotelAssignmentId`、`hotelId`、`roomTypeId`、`dayNumber`、`stayDate`、`hotelName`、`roomType`、`roomTypeName`、`roomCount`、`unitPrice`、`plannedCost`、`sourceType`、`sourceId`、`voucherUrls`、`remark`。
|
||||||
|
- `TICKET`:`sourceType`、`scenicAssignmentId`、`dayNumber`、`dayDate`、`scenicName`、`specName`、`ticketCount`、`ticketUnitPrice`、`sellPrice`、`totalAmount`、`plannedCost`、`voucherUrls`、`remark`。
|
||||||
|
- `MEAL`:`mealType`、`mealDate`、`mealName`、`quantity`、`unitPrice`、`voucherUrls`、`remark`。
|
||||||
|
- `VEHICLE_FEE`:`sourceRecordType`、`sourceDetailId`、`serviceDate`、`vehicleId`、`vehiclePlate`、`vehicleModelId`、`vehicleModelName`、`driverId`、`driverName`、`startDate`、`endDate`、`dailyPrice`、`paymentTypeCode`、`paymentTypeName`。
|
||||||
|
- `STAFF:*`:`staffRole`、`staffId`、`staffName`、`totalPlannedCost`、`voucherUrls`、`reimburse`、`settleStatus`、`settledDate`、`transferRef`、`detail`、`remark`。
|
||||||
|
- `EXPENSE:*`:`expenseType`、`projectName`、`expenseDate`、`voucherUrls`、`remark`。
|
||||||
|
- `SUBSIDY:*`:`subsidyType`、`projectName`、`expenseDate`、`voucherUrls`、`remark`。
|
||||||
|
|
||||||
|
**错误与业务边界**
|
||||||
|
|
||||||
|
- `orderId <= 0` 返回 `400`。
|
||||||
|
- 房务角色或无订单访问权限返回 `403`/对应订单访问错误。
|
||||||
|
- 未完成核单时返回当前核单事实的实时视图和当前指纹。
|
||||||
|
- 已完成核单时返回当前有效终态版本中的报账表和该版本指纹。
|
||||||
|
- 管理员反确认后再次 GET 会回到实时视图;前端必须重新取得指纹。
|
||||||
|
|
||||||
|
**典型请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**典型响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "7a0e84a9f9d0cb411f9cff8d6a0d1c047726bb05b68c8e23af5629afdbdd67c1",
|
||||||
|
"primaryReporterId": "3001",
|
||||||
|
"primaryReporterName": "示例报账人",
|
||||||
|
"primaryReporterRole": "DRIVER",
|
||||||
|
"reportVersion": 1,
|
||||||
|
"driverCollectedTailAmount": 2000.00,
|
||||||
|
"approvedAdvanceAmount": 500.00,
|
||||||
|
"reportablePaidCostAmount": 1200.00,
|
||||||
|
"reporterNetAmount": 1300.00,
|
||||||
|
"primaryReporterCollectedAmount": 2000.00,
|
||||||
|
"publicPrepaidAmount": 1200.00,
|
||||||
|
"primaryReporterDueAmount": 800.00,
|
||||||
|
"advanceOutstandingAmount": 500.00,
|
||||||
|
"reconNetAmount": 1300.00,
|
||||||
|
"transferDirection": "REPORTER_TO_COMPANY",
|
||||||
|
"transferAmount": 1300.00,
|
||||||
|
"incomeLines": [],
|
||||||
|
"expenseLines": [],
|
||||||
|
"advanceLines": [],
|
||||||
|
"vehicleLines": [],
|
||||||
|
"transferStatus": null,
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": null,
|
||||||
|
"signedVoucher": null,
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
},
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 查询单团核算表
|
||||||
|
|
||||||
|
- **方法与路径**:`GET /v3/admin/order/{orderId}/settlement/reports/group`
|
||||||
|
- **使用场景**:展示单团核算表,并在调用 finalize 前取得最新单团指纹
|
||||||
|
- **认证**:管理后台 JWT;房务角色不可访问
|
||||||
|
- **幂等性**:幂等,只读
|
||||||
|
- **限流**:无接口级特殊限流
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**请求体**
|
||||||
|
|
||||||
|
无。
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| `data` 字段 | JSON 类型 | 可空 | 说明 |
|
||||||
|
|-------------|-----------|:---:|------|
|
||||||
|
| `id` | string | 是 | 单团核算表记录 ID |
|
||||||
|
| `orderId` | string | 否 | 订单 ID |
|
||||||
|
| `reportStatus` | string | 否 | 报表状态,见 §6.1 |
|
||||||
|
| `sourceFingerprint` | string | 否 | 64 位小写十六进制 SHA-256;传给 finalize 的 `groupExpectedSourceFingerprint` |
|
||||||
|
| `baseOrderAmount` | number | 否 | 订单基础金额 |
|
||||||
|
| `otherIncomeAmount` | number | 否 | 其他收入金额 |
|
||||||
|
| `discountAmount` | number | 否 | 优惠金额 |
|
||||||
|
| `adjustedReceivableAmount` | number | 否 | 调整后应收金额 |
|
||||||
|
| `paidAmount` | number | 否 | 已收金额 |
|
||||||
|
| `actualRefundedAmount` | number | 否 | 实际退款金额 |
|
||||||
|
| `netRevenueAmount` | number | 否 | 净收入 |
|
||||||
|
| `netReceivedAmount` | number | 否 | 净已收 |
|
||||||
|
| `outstandingAmount` | number | 否 | 待收金额;不为 `0` 时不能 finalize |
|
||||||
|
| `hotelCost` | number | 否 | 住宿成本 |
|
||||||
|
| `ticketCost` | number | 否 | 门票/游玩项目成本 |
|
||||||
|
| `mealCost` | number | 否 | 餐食成本 |
|
||||||
|
| `vehicleCost` | number | 否 | 车辆成本 |
|
||||||
|
| `guideCost` | number | 否 | 导游/领队成本 |
|
||||||
|
| `photographerCost` | number | 否 | 摄影成本 |
|
||||||
|
| `otherExpenseCost` | number | 否 | 其他支出成本 |
|
||||||
|
| `insurancePremium` | number | 否 | 保险保费 |
|
||||||
|
| `totalCost` | number | 否 | 总成本 |
|
||||||
|
| `paidCost` | number | 否 | 已付成本 |
|
||||||
|
| `unpaidCost` | number | 否 | 未付成本 |
|
||||||
|
| `grossProfit` | number | 否 | 毛利 |
|
||||||
|
| `grossProfitRate` | number | 否 | 毛利率,小数形式 |
|
||||||
|
| `travelerCount` | integer | 否 | 出行人数 |
|
||||||
|
| `perCapitaRevenue` | number | 否 | 人均收入 |
|
||||||
|
| `perCapitaCost` | number | 否 | 人均成本 |
|
||||||
|
| `perCapitaProfit` | number | 否 | 人均利润 |
|
||||||
|
| `incomeLines` | array<object> | 否 | 收入汇总行 |
|
||||||
|
| `costCategories` | array<object> | 否 | 成本分类汇总 |
|
||||||
|
| `generatedBy` | string | 是 | 历史生成操作人 ID |
|
||||||
|
| `generatedByName` | string | 是 | 历史生成操作人姓名 |
|
||||||
|
| `generatedAt` | string(date-time) | 是 | 历史生成时间 |
|
||||||
|
| `confirmedBy` | string | 是 | 完成核单操作人 ID |
|
||||||
|
| `confirmedByName` | string | 是 | 完成核单操作人姓名 |
|
||||||
|
| `confirmedAt` | string(date-time) | 是 | 完成核单时间 |
|
||||||
|
|
||||||
|
**`incomeLines[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `type` | string | `BASE_ORDER`、`OTHER_INCOME`、`DISCOUNT` 或 `ACTUAL_REFUND` |
|
||||||
|
| `amount` | number | 金额;优惠和实际退款以负数返回 |
|
||||||
|
|
||||||
|
**`costCategories[]` 字段**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | string | `HOTEL`、`TICKET`、`MEAL`、`VEHICLE`、`GUIDE`、`PHOTOGRAPHER`、`OTHER_EXPENSE` 或 `INSURANCE` |
|
||||||
|
| `amount` | number | 分类成本 |
|
||||||
|
|
||||||
|
**错误与业务边界**
|
||||||
|
|
||||||
|
- `orderId <= 0` 返回 `400`。
|
||||||
|
- 房务角色或无订单访问权限返回 `403`/对应订单访问错误。
|
||||||
|
- 未完成核单时返回实时视图;已完成核单时返回当前有效终态版本。
|
||||||
|
- 管理员反确认后,下一次 GET 会生成新的实时结果和指纹。
|
||||||
|
|
||||||
|
**典型请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/1914050000000001/settlement/reports/group
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**典型响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"reportStatus": "GENERATED",
|
||||||
|
"sourceFingerprint": "651cb6708a49f169e2ccb1b455267def9cc1a69d06935f9d9eeb41919897e0fb",
|
||||||
|
"baseOrderAmount": 24800.00,
|
||||||
|
"otherIncomeAmount": 500.00,
|
||||||
|
"discountAmount": 300.00,
|
||||||
|
"adjustedReceivableAmount": 25000.00,
|
||||||
|
"paidAmount": 25000.00,
|
||||||
|
"actualRefundedAmount": 0.00,
|
||||||
|
"netRevenueAmount": 25000.00,
|
||||||
|
"netReceivedAmount": 25000.00,
|
||||||
|
"outstandingAmount": 0.00,
|
||||||
|
"hotelCost": 4280.00,
|
||||||
|
"ticketCost": 3680.00,
|
||||||
|
"mealCost": 860.00,
|
||||||
|
"vehicleCost": 5200.00,
|
||||||
|
"guideCost": 800.00,
|
||||||
|
"photographerCost": 600.00,
|
||||||
|
"otherExpenseCost": 1200.00,
|
||||||
|
"insurancePremium": 180.00,
|
||||||
|
"totalCost": 16800.00,
|
||||||
|
"paidCost": 16800.00,
|
||||||
|
"unpaidCost": 0.00,
|
||||||
|
"grossProfit": 8200.00,
|
||||||
|
"grossProfitRate": 0.328,
|
||||||
|
"travelerCount": 5,
|
||||||
|
"perCapitaRevenue": 5000.00,
|
||||||
|
"perCapitaCost": 3360.00,
|
||||||
|
"perCapitaProfit": 1640.00,
|
||||||
|
"incomeLines": [
|
||||||
|
{"type": "BASE_ORDER", "amount": 24800.00},
|
||||||
|
{"type": "OTHER_INCOME", "amount": 500.00},
|
||||||
|
{"type": "DISCOUNT", "amount": -300.00},
|
||||||
|
{"type": "ACTUAL_REFUND", "amount": 0.00}
|
||||||
|
],
|
||||||
|
"costCategories": [
|
||||||
|
{"category": "HOTEL", "amount": 4280.00},
|
||||||
|
{"category": "TICKET", "amount": 3680.00},
|
||||||
|
{"category": "MEAL", "amount": 860.00},
|
||||||
|
{"category": "VEHICLE", "amount": 5200.00},
|
||||||
|
{"category": "GUIDE", "amount": 800.00},
|
||||||
|
{"category": "PHOTOGRAPHER", "amount": 600.00},
|
||||||
|
{"category": "OTHER_EXPENSE", "amount": 1200.00},
|
||||||
|
{"category": "INSURANCE", "amount": 180.00}
|
||||||
|
],
|
||||||
|
"generatedBy": null,
|
||||||
|
"generatedByName": null,
|
||||||
|
"generatedAt": null,
|
||||||
|
"confirmedBy": null,
|
||||||
|
"confirmedByName": null,
|
||||||
|
"confirmedAt": null
|
||||||
|
},
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 完成核单
|
||||||
|
|
||||||
|
- **方法与路径**:`POST /v3/admin/order/{orderId}/settlement/finalize`
|
||||||
|
- **使用场景**:两张报表核对完成后,一次提交双指纹和主报账凭据
|
||||||
|
- **认证**:管理后台 JWT;房务角色不可访问
|
||||||
|
- **幂等性**:严格幂等,比较双指纹、规范化后的凭据和 `remark`
|
||||||
|
- **限流**:无接口级特殊限流
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `orderId` | String/Long | 是 | 订单 ID,必须大于 `0` |
|
||||||
|
|
||||||
|
**请求体字段**
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 必填 | 校验与规范化 |
|
||||||
|
|------|-----------|:---:|--------------|
|
||||||
|
| `remark` | string/null | 否 | 最长 500;去除首尾空格,空串按 `null` 比较 |
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | string | 是 | 必须等于主报账 GET 返回的 64 位小写十六进制 `sourceFingerprint` |
|
||||||
|
| `groupExpectedSourceFingerprint` | string | 是 | 必须等于单团 GET 返回的 64 位小写十六进制 `sourceFingerprint` |
|
||||||
|
| `reimbursementConfirmation` | object | 是 | 主报账转账、预支和签字凭据 |
|
||||||
|
| `reimbursementConfirmation.transferDate` | string(date)/null | 条件必填 | `reporterNetAmount != 0` 时必填;净额为 `0` 时可为 `null` |
|
||||||
|
| `reimbursementConfirmation.transferRef` | string/null | 条件必填 | 去除首尾空格后最长 128;净额非 `0` 时长度必须为 1~128 |
|
||||||
|
| `reimbursementConfirmation.advanceSettledFlag` | boolean | 是 | 必须明确传值,`false` 合法 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher` | object | 是 | 缺失返回 `400` |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files` | array<object> | 业务必填 | 1~9 项;为 `null`、空数组、超过 9 项或含 `null` 项返回 `584317` |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files[].url` | string | 业务必填 | 去除首尾空格后长度 1~1024;不符合返回 `584317` |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files[].name` | string/null | 否 | 去除首尾空格;空串归一化为 `null`;非空最长 255 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.note` | string/null | 否 | 去除首尾空格;空串归一化为 `null`;非空最长 500 |
|
||||||
|
|
||||||
|
`transferStatus` **不得提交**。finalize 成功后,报账终态中的 `transferStatus` 固定为 `COMPLETED`。
|
||||||
|
|
||||||
|
签字凭证文件按规范化后的 `url`、`name` 升序稳定保存。不得依赖请求数组原顺序进行严格幂等判断。
|
||||||
|
|
||||||
|
**响应字段**
|
||||||
|
|
||||||
|
| `data` 字段 | JSON 类型 | 说明 |
|
||||||
|
|-------------|-----------|------|
|
||||||
|
| `summaryId` | string | 核单汇总 ID |
|
||||||
|
| `finalSnapshotId` | string | 核单终态快照 ID |
|
||||||
|
| `finalSnapshotVersionNo` | integer | 终态版本号;首次为 1,反确认后再次 finalize 为上一版本 + 1 |
|
||||||
|
| `finalSnapshotStatus` | string | 成功固定为 `FINALIZED` |
|
||||||
|
| `orderId` | string | 订单 ID |
|
||||||
|
| `settledAt` | string(date-time) | ISO-8601 核单完成时间 |
|
||||||
|
| `totalAmount` | string | 订单总金额快照 |
|
||||||
|
| `paidAmount` | string | 已付金额快照 |
|
||||||
|
| `balanceAmount` | string | 尾款金额快照 |
|
||||||
|
| `roomCost` | string | 住宿实际成本 |
|
||||||
|
| `ticketCost` | string | 门票实际成本 |
|
||||||
|
| `staffCost` | string | 人员费用实际成本 |
|
||||||
|
| `subsidyCost` | string | 补助实际成本 |
|
||||||
|
| `mealCost` | string | 餐食实际成本 |
|
||||||
|
| `vehicleCost` | string | 车辆成本 |
|
||||||
|
| `otherExpenseCost` | string | 其他支出实际成本 |
|
||||||
|
| `insurancePremium` | string | 保险实际保费 |
|
||||||
|
| `totalActualCost` | string | 总实际成本 |
|
||||||
|
| `driverTransferAmount` | string | 给司机/主报账人转回金额 |
|
||||||
|
| `profitAmount` | string | 公司毛利 |
|
||||||
|
| `profitRate` | number | 毛利率;订单总金额为 0 时为 0 |
|
||||||
|
| `orderStatusAfter` | string | 成功后为 `待财务复核` |
|
||||||
|
| `mqTriggered` | boolean | 当前固定为 `false` |
|
||||||
|
| `warnings` | array<string> | 软预警列表;无预警为 `[]` |
|
||||||
|
|
||||||
|
**错误与业务边界**
|
||||||
|
|
||||||
|
- 缺 body、非法 JSON、`remark` 超长、双指纹格式错误,或缺少 `reimbursementConfirmation`、`advanceSettledFlag`、`signedVoucher`:返回 `400`。
|
||||||
|
- 双指纹任一与当前冻结事实不一致:返回 `584315`,须重新 GET 两张报表。
|
||||||
|
- `transferRef` 条件不满足或超过 128,凭证 `files`/文件项/`url` 无效,或 `name`/`note` 超长:返回 `584317`。
|
||||||
|
- 单团核算的 `outstandingAmount != 0`:返回 `584082`,不能完成核单。
|
||||||
|
- 完全相同的终态请求重试返回原 `summaryId`、`finalSnapshotId` 和版本号,不产生新版本。
|
||||||
|
- 已有当前终态时,双指纹、规范化凭据或 `remark` 任一不同:返回 `584316`。
|
||||||
|
- 任一失败不留下部分完成结果。
|
||||||
|
|
||||||
|
**典型请求:净报账金额非 0**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"remark": "主报账人与单团核算均已核对",
|
||||||
|
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
||||||
|
"reimbursementConfirmation": {
|
||||||
|
"transferDate": "2026-07-29",
|
||||||
|
"transferRef": "FT202607290001",
|
||||||
|
"advanceSettledFlag": false,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "司机签字报账单.pdf",
|
||||||
|
"url": "https://oss.example.com/settlement/driver-signed-20260729.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": "司机现场签字后上传"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**典型响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"summaryId": "9600000000001",
|
||||||
|
"finalSnapshotId": "9600000000002",
|
||||||
|
"finalSnapshotVersionNo": 1,
|
||||||
|
"finalSnapshotStatus": "FINALIZED",
|
||||||
|
"orderId": "1914050000000001",
|
||||||
|
"settledAt": "2026-07-29T10:30:25",
|
||||||
|
"totalAmount": "24800.00",
|
||||||
|
"paidAmount": "24800.00",
|
||||||
|
"balanceAmount": "0.00",
|
||||||
|
"roomCost": "4280.00",
|
||||||
|
"ticketCost": "3680.00",
|
||||||
|
"staffCost": "7000.00",
|
||||||
|
"subsidyCost": "720.00",
|
||||||
|
"mealCost": "860.00",
|
||||||
|
"vehicleCost": "5200.00",
|
||||||
|
"otherExpenseCost": "1200.00",
|
||||||
|
"insurancePremium": "180.00",
|
||||||
|
"totalActualCost": "23120.00",
|
||||||
|
"driverTransferAmount": "22940.00",
|
||||||
|
"profitAmount": "1680.00",
|
||||||
|
"profitRate": 0.0677,
|
||||||
|
"orderStatusAfter": "待财务复核",
|
||||||
|
"mqTriggered": false,
|
||||||
|
"warnings": []
|
||||||
|
},
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**边界请求:`reporterNetAmount = 0`**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000002/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"remark": null,
|
||||||
|
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
||||||
|
"reimbursementConfirmation": {
|
||||||
|
"transferDate": null,
|
||||||
|
"transferRef": null,
|
||||||
|
"advanceSettledFlag": false,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": null,
|
||||||
|
"url": "https://oss.example.com/settlement/zero-net-signed.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"note": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**边界响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"summaryId": "9600000000011",
|
||||||
|
"finalSnapshotId": "9600000000012",
|
||||||
|
"finalSnapshotVersionNo": 1,
|
||||||
|
"finalSnapshotStatus": "FINALIZED",
|
||||||
|
"orderId": "1914050000000002",
|
||||||
|
"settledAt": "2026-07-29T10:35:00",
|
||||||
|
"totalAmount": "0.00",
|
||||||
|
"paidAmount": "0.00",
|
||||||
|
"balanceAmount": "0.00",
|
||||||
|
"roomCost": "0.00",
|
||||||
|
"ticketCost": "0.00",
|
||||||
|
"staffCost": "0.00",
|
||||||
|
"subsidyCost": "0.00",
|
||||||
|
"mealCost": "0.00",
|
||||||
|
"vehicleCost": "0.00",
|
||||||
|
"otherExpenseCost": "0.00",
|
||||||
|
"insurancePremium": "0.00",
|
||||||
|
"totalActualCost": "0.00",
|
||||||
|
"driverTransferAmount": "0.00",
|
||||||
|
"profitAmount": "0.00",
|
||||||
|
"profitRate": 0,
|
||||||
|
"orderStatusAfter": "待财务复核",
|
||||||
|
"mqTriggered": false,
|
||||||
|
"warnings": []
|
||||||
|
},
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**异常请求:凭证包含空 URL**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
||||||
|
"reimbursementConfirmation": {
|
||||||
|
"transferDate": "2026-07-29",
|
||||||
|
"transferRef": "FT202607290001",
|
||||||
|
"advanceSettledFlag": true,
|
||||||
|
"signedVoucher": {
|
||||||
|
"files": [
|
||||||
|
{"name": "签字单.pdf", "url": " "}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**异常响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584317,
|
||||||
|
"message": "当前报告状态不允许执行该操作",
|
||||||
|
"data": null,
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**异常请求:缺少 `signedVoucher`**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/finalize
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
||||||
|
"reimbursementConfirmation": {
|
||||||
|
"transferDate": "2026-07-29",
|
||||||
|
"transferRef": "FT202607290001",
|
||||||
|
"advanceSettledFlag": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**异常响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "参数校验失败",
|
||||||
|
"data": null,
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 已删除:确认主报账表
|
||||||
|
|
||||||
|
- **原方法与路径**:`POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm`
|
||||||
|
- **当前契约**:接口已删除,无有效请求体或成功响应。
|
||||||
|
- **前端动作**:删除请求封装、按钮、loading、重试和错误忽略逻辑。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/confirm
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"message": "请求地址不存在",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 已删除:确认单团核算表
|
||||||
|
|
||||||
|
- **原方法与路径**:`POST /v3/admin/order/{orderId}/settlement/reports/group/confirm`
|
||||||
|
- **当前契约**:接口已删除,无有效请求体或成功响应。
|
||||||
|
- **前端动作**:删除请求封装、按钮、loading、重试和错误忽略逻辑。
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
|
||||||
|
Authorization: Bearer <admin-jwt>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"message": "请求地址不存在",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 接口入参汇总
|
||||||
|
|
||||||
|
| 接口 | 入参 |
|
||||||
|
|------|------|
|
||||||
|
| 主报账 GET | 路径参数 `orderId`;无请求体 |
|
||||||
|
| 单团 GET | 路径参数 `orderId`;无请求体 |
|
||||||
|
| finalize | 路径参数 `orderId`;请求体必须包含两个指纹及 `reimbursementConfirmation` |
|
||||||
|
| 两个旧 confirm | 已删除,无有效入参 |
|
||||||
|
|
||||||
|
双指纹映射必须严格如下:
|
||||||
|
|
||||||
|
| 来源 | finalize 字段 |
|
||||||
|
|------|---------------|
|
||||||
|
| 主报账 GET 的 `data.sourceFingerprint` | `reimbursementExpectedSourceFingerprint` |
|
||||||
|
| 单团 GET 的 `data.sourceFingerprint` | `groupExpectedSourceFingerprint` |
|
||||||
|
|
||||||
|
## 5. 出参汇总
|
||||||
|
|
||||||
|
- 两张 GET 均返回 `Result<报表对象>`,其中 `sourceFingerprint` 是 finalize 的提交凭据。
|
||||||
|
- finalize 返回 `Result<SettlementSubmitRespVO>`,完整字段见 §3.3。
|
||||||
|
- 两个旧 confirm 不再返回业务成功响应,只会命中不存在的路由。
|
||||||
|
- 金额序列化以各字段表和示例为准:finalize 的金额字段为字符串,两张 GET 的金额字段为 JSON number。
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `reportStatus`
|
||||||
|
|
||||||
|
**所属字段**:两张报表响应 `reportStatus` | **类型**:string
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `GENERATED` | 实时结果 | 当前不存在有效终态,按当前核单事实计算 |
|
||||||
|
| `CONFIRMED` | 已固化 | 返回当前有效终态版本中的报表 |
|
||||||
|
| `STALE` | 历史过期 | 兼容历史报表状态,不用于当前 finalize |
|
||||||
|
|
||||||
|
### 6.2 `transferDirection`
|
||||||
|
|
||||||
|
**所属字段**:主报账响应 `transferDirection` | **类型**:string
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `REPORTER_TO_COMPANY` | 报账人转公司 | `reporterNetAmount > 0` |
|
||||||
|
| `COMPANY_TO_REPORTER` | 公司转报账人 | `reporterNetAmount < 0` |
|
||||||
|
| `BALANCED` | 已平衡 | `reporterNetAmount = 0` |
|
||||||
|
|
||||||
|
### 6.3 `category`
|
||||||
|
|
||||||
|
**所属字段**:`expenseLines[].category`、`costCategories[].category` | **类型**:string
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `HOTEL` | 住宿 | 住宿成本 |
|
||||||
|
| `TICKET` | 门票/游玩项目 | 门票及游玩成本 |
|
||||||
|
| `MEAL` | 餐食 | 餐食成本 |
|
||||||
|
| `VEHICLE` | 车辆 | 车辆成本 |
|
||||||
|
| `GUIDE` | 导游/领队 | 导游及领队成本 |
|
||||||
|
| `PHOTOGRAPHER` | 摄影 | 摄影成本 |
|
||||||
|
| `OTHER_EXPENSE` | 其他支出 | 其他支出成本 |
|
||||||
|
| `INSURANCE` | 保险 | 保险保费 |
|
||||||
|
|
||||||
|
### 6.4 `finalSnapshotStatus`
|
||||||
|
|
||||||
|
**所属字段**:finalize 响应 `finalSnapshotStatus` | **类型**:string
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `FINALIZED` | 已完成核单 | 当前终态版本有效 |
|
||||||
|
|
||||||
|
### 6.5 `transferStatus`
|
||||||
|
|
||||||
|
**所属字段**:主报账响应 `transferStatus` | **类型**:string/null
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|----|------|------|
|
||||||
|
| `COMPLETED` | 转账凭据已随核单固化 | finalize 成功后固定值 |
|
||||||
|
| `null` | 尚未固化 | 实时报表可为空 |
|
||||||
|
|
||||||
|
`transferStatus` 只出现在响应中,不是 finalize 入参。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `400` | 请求/参数校验失败 | `orderId <= 0`、缺请求体、非法 JSON、双指纹格式错误、缺 `reimbursementConfirmation`/`advanceSettledFlag`/`signedVoucher`、`remark` 超长 |
|
||||||
|
| `403` | 无访问权限 | 房务角色或无权访问当前订单 |
|
||||||
|
| `404` | 路由不存在 | 调用两个已删除的报表 confirm 接口 |
|
||||||
|
| `584082` | 存在待收尾款 | 单团核算 `outstandingAmount != 0` |
|
||||||
|
| `584100` | 车辆费用暂时不可用 | 报表查询或 finalize 当前无法取得可核单车辆费用 |
|
||||||
|
| `584101` | 车辆事实未完成 | 存在未完结派车或未确认车辆费用 |
|
||||||
|
| `584102` | 缺少车辆费用 | 有用车需求但没有可核单车辆费用 |
|
||||||
|
| `584315` | 核单来源数据已变化 | 车辆候选与冻结事实不一致,或任一双指纹过期 |
|
||||||
|
| `584316` | 并发或严格幂等冲突 | 终态重试请求不同、并发完成/反确认冲突 |
|
||||||
|
| `584317` | 转账条件或签字凭证不合法 | 净额非 0 缺日期/流水、流水超长、files/文件项/url 无效、name/note 超长 |
|
||||||
|
| `584320` | 核单明细未准备好 | 当前分类数据不能用于报账或 finalize |
|
||||||
|
| `584321` | 缺少当前终态 | 后续财务复核缺少 current `FINALIZED` 终态 |
|
||||||
|
| `584325` | 双指纹兜底校验失败 | finalize 发现双指纹不完整或不合法 |
|
||||||
|
| `584326` | 终态组合不一致 | 当前终态、关联汇总或订单终态不匹配 |
|
||||||
|
|
||||||
|
## 8. 示例索引
|
||||||
|
|
||||||
|
| 场景 | 位置 |
|
||||||
|
|------|------|
|
||||||
|
| 主报账 GET 典型请求与响应 | §3.1 |
|
||||||
|
| 单团 GET 典型请求与响应 | §3.2 |
|
||||||
|
| finalize 净额非 0 典型成功 | §3.3 |
|
||||||
|
| finalize 净额为 0 合法边界 | §3.3 |
|
||||||
|
| finalize 凭证 URL 非法返回 584317 | §3.3 |
|
||||||
|
| finalize 缺 `signedVoucher` 返回 400 | §3.3 |
|
||||||
|
| 两个旧 confirm 返回 404 | §3.4、§3.5 |
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- 必须先分别 GET 两张报表,再把两个 `sourceFingerprint` 一一映射到 finalize;不能复用旧指纹、互换字段或只传一个。
|
||||||
|
- 任一核单事实变化后,旧双指纹都会失效;收到 `584315` 后必须重新 GET 两张表。
|
||||||
|
- `outstandingAmount` 必须为 `0` 才能 finalize。
|
||||||
|
- `reporterNetAmount != 0` 时,`transferDate` 和非空 `transferRef` 同时必填;净额为 `0` 时二者可为 `null`。
|
||||||
|
- `advanceSettledFlag=false` 是有效业务值,不等同于缺失。
|
||||||
|
- `signedVoucher` 始终必填,且 `files` 必须有 1~9 个合法文件项。
|
||||||
|
- 完全相同请求重试严格幂等;任何双指纹、规范化凭据或 `remark` 差异均返回 `584316`。
|
||||||
|
- 管理员反确认使当前终态失效后,两张 GET 重新返回实时结果;再次 finalize 必须使用新双指纹,成功响应的 `finalSnapshotVersionNo` 为上一版本 + 1。
|
||||||
|
- finalize 成功后订单进入“待财务复核”。既有财务复核接口 `POST /v3/admin/order/{orderId}/settlement/confirm` 的请求/响应结构未在本次变更:请求仅含可选 `confirmRemark`;当前没有独立财务角色校验;成功 `data` 为 `orderId`、`settlementStatus=COMPLETED`、`settledAt`、`flowStatus=SETTLED`。其复核前提为当前有效 `FINALIZED` 终态及其关联汇总,旧报表 confirm 状态不参与判断。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 接口/字段 | 修改前或错误说明 | 当前正确契约 |
|
||||||
|
|-----------|------------------|--------------|
|
||||||
|
| finalize 双指纹 | #5342 通知误写为删除 | 两个字段均必填 |
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | 误写为不再回传 | 来自主报账 GET 的 `sourceFingerprint` |
|
||||||
|
| `groupExpectedSourceFingerprint` | 误写为不再回传 | 来自单团 GET 的 `sourceFingerprint` |
|
||||||
|
| `reimbursementConfirmation` | #5342 把内部字段错误提升到 finalize 顶层 | 必填嵌套对象 |
|
||||||
|
| `transferDate` | 误写为 finalize 顶层 | 位于 `reimbursementConfirmation` |
|
||||||
|
| `transferRef` | 误写为 finalize 顶层 | 位于 `reimbursementConfirmation`,trim 后最长 128 |
|
||||||
|
| `advanceSettledFlag` | 误写为 finalize 顶层 | 位于 `reimbursementConfirmation`,必填 boolean |
|
||||||
|
| `signedVoucher` | 误写为 finalize 顶层 | 位于 `reimbursementConfirmation`,必填 object |
|
||||||
|
| `transferStatus` | 可能沿用旧 confirm 传值 | finalize 不接收,成功后固定为 `COMPLETED` |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 修改前 | 当前 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 主报账确认 | 独立 POST confirm | 接口删除,由 finalize 一次完成 |
|
||||||
|
| 单团确认 | 独立 POST confirm | 接口删除,由 finalize 一次完成 |
|
||||||
|
| finalize 前的数据校验 | 分散在两个 confirm | 两张 GET 取双指纹,finalize 一次校验 |
|
||||||
|
| 重复 finalize | 旧流程语义不明确 | 完全相同返回原结果,任一差异返回 `584316` |
|
||||||
|
| 反确认后再次核单 | 可能沿用旧报表结果 | 重新 GET 新指纹,再 finalize 生成版本号 + 1 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:是。两个 POST confirm 已删除,finalize 的双指纹及嵌套凭据均为必填。
|
||||||
|
- **前端是否必须同步上线**:是。按 #5342 错误契约提交会因缺双指纹或缺 `reimbursementConfirmation` 返回 `400`/业务错误。
|
||||||
|
- **查询兼容性**:两张 GET 的字段结构保持,`sourceFingerprint` 的用途明确为 finalize 必填凭据。
|
||||||
|
|
||||||
|
### 11.2 回滚说明
|
||||||
|
|
||||||
|
- 前后端必须使用同一版核单流程;不能混用“独立 confirm”和“finalize 双指纹”两套调用顺序。
|
||||||
|
- 若后端契约回滚,前端也需同步恢复对应请求模型与调用链,不能只单独回滚一端。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 删除两个报表确认按钮及对应请求、loading、重试、错误忽略代码。
|
||||||
|
- 保留两个 GET 返回的 `sourceFingerprint`,并在点击完成核单前保存当前两份值。
|
||||||
|
- finalize 请求模型必须新增必填 `reimbursementConfirmation`,其余凭据字段不得放在顶层。
|
||||||
|
- 不要发送 `transferStatus`;页面在 finalize 成功后按响应/重新 GET 展示终态。
|
||||||
|
- 不要继续沿用 #5342 通知中的“删除双指纹”“finalize 顶层凭据字段”实现。
|
||||||
|
- 对 `584315` 进行刷新两张报表后重试;对 `584316` 不要静默覆盖终态。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5343](https://git.1814.love:8443/wx/HL/issues/5343)
|
||||||
|
- **PR**: [#5347](https://git.1814.love:8443/wx/HL/pulls/5347)
|
||||||
|
- **Merge commit**: [a892a6b56a](https://git.1814.love:8443/wx/HL/commit/a892a6b56a2c3c0c2e4e345156096ac1ac5750c0)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yst
|
||||||
|
- **消费端**: v3 管理后台
|
||||||
@ -0,0 +1,911 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5356"
|
||||||
|
title: "核单八类来源确认状态与车辆 Step3 接口统一"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "Pi"
|
||||||
|
frontend_ref: "9b9865a50304b1acc55ab5e4f94bb7dfe52293c0"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-30"
|
||||||
|
status_note: "管理后台已统一八类核单来源与逐行确认状态,车辆改用 Order Step3 GET/PUT 并携带 version、保护 FLEET 权威字段;pnpm checkpoint 全量通过,业务提交 9b9865a50304b1acc55ab5e4f94bb7dfe52293c0 已推送至 origin/v2.1。后端车辆 DTO 正向数据与 Full E2E 仍受测试订单无可核单车辆费用限制。"
|
||||||
|
updated_at: "2026-07-30"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# ⚠️【修改接口·管理后台】核单八类来源确认状态与车辆 Step3 接口统一 (#5356)
|
||||||
|
|
||||||
|
> **PR**: #5362 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-30 15:46
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单页面需要用同一套规则识别“手工行”和“系统来源行”,并逐行完成确认。此前各 Tab 的 `sourceType`、确认状态和车辆费用入口不一致,车辆数据还残留过已下线接口的字段口径。本次统一八类核单来源与逐行确认语义,并新增 Order 侧车辆 Step3 草稿查询、全量保存接口。
|
||||||
|
|
||||||
|
管理后台应以本文列出的 Order 侧接口为准;已删除的
|
||||||
|
`GET /v3/admin/order/:orderId/settlement/vehicle-fees` 和
|
||||||
|
`POST /v3/admin/order/:orderId/settlement/vehicle-fees/confirm`
|
||||||
|
继续保持下线,不得恢复调用。
|
||||||
|
|
||||||
|
## 变更接口(2. 变更清单)
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | Step 1 查询住宿核单明细 | GET | `/v3/admin/order/:orderId/settlement/step1` | 修改 | 来源值统一;系统派生行初始为未确认;ID 按字符串返回 |
|
||||||
|
| 2 | Step 1 保存住宿核单明细 | PUT | `/v3/admin/order/:orderId/settlement/step1` | 修改 | 支持逐行 `UNCONFIRMED/CONFIRMED`;手工新行必须先未确认 |
|
||||||
|
| 3 | Step 2 查询门票核单明细 | GET | `/v3/admin/order/:orderId/settlement/step2` | 修改 | 响应新增逐行确认状态;ID 按字符串返回 |
|
||||||
|
| 4 | Step 2 保存门票核单明细 | PUT | `/v3/admin/order/:orderId/settlement/step2` | 修改 | 请求新增逐行确认状态;手工新行必须先未确认 |
|
||||||
|
| 5 | 查询车辆核单草稿 | GET | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 新增 | Order 侧车辆 Step3 唯一查询入口 |
|
||||||
|
| 6 | 全量保存车辆核单草稿 | PUT | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 新增 | 带 `version` 全量保存,支持车务行确认和手工行维护 |
|
||||||
|
| 7 | 查询领队人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | 修改 | 新增来源与确认状态名称 |
|
||||||
|
| 8 | 保存领队人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` |
|
||||||
|
| 9 | 查询司机人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | 修改 | 新增来源与确认状态名称 |
|
||||||
|
| 10 | 保存司机人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` |
|
||||||
|
| 11 | 查询导游人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 修改 | 新增来源与确认状态名称 |
|
||||||
|
| 12 | 保存导游人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` |
|
||||||
|
| 13 | 查询摄影师人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 修改 | 新增来源与确认状态名称 |
|
||||||
|
| 14 | 保存摄影师人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` |
|
||||||
|
| 15 | 查询其他人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/others` | 修改 | 新增来源与确认状态名称 |
|
||||||
|
| 16 | 保存其他人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/others` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` |
|
||||||
|
| 17 | 查询餐食费用 | GET | `/v3/admin/order/:orderId/settlement/meals` | 修改 | 响应新增来源、来源名称、确认状态名称 |
|
||||||
|
| 18 | 新增餐食费用 | POST | `/v3/admin/order/:orderId/settlement/meals` | 修改 | 请求新增来源和确认状态;新行必须先未确认 |
|
||||||
|
| 19 | 修改餐食费用 | PUT | `/v3/admin/order/:orderId/settlement/meals/:settlementId` | 修改 | 可把已存在未确认行保存为已确认 |
|
||||||
|
| 20 | 查询其他支出 | GET | `/v3/admin/order/:orderId/settlement/other-expenses` | 修改 | 响应新增来源、来源名称、确认状态名称 |
|
||||||
|
| 21 | 新增其他支出 | POST | `/v3/admin/order/:orderId/settlement/other-expenses` | 修改 | 手工新行必须先未确认 |
|
||||||
|
| 22 | 修改其他支出 | PUT | `/v3/admin/order/:orderId/settlement/other-expenses/:settlementId` | 修改 | 可把已存在未确认行保存为已确认 |
|
||||||
|
| 23 | 查询其他收入 | GET | `/v3/admin/order/:orderId/settlement/other-incomes` | 修改 | `sourceType` 从内部来源值改为 `MANUAL/SYSTEM` |
|
||||||
|
| 24 | 新增其他收入 | POST | `/v3/admin/order/:orderId/settlement/other-incomes` | 修改 | 手工新行必须先未确认 |
|
||||||
|
| 25 | 修改其他收入 | PUT | `/v3/admin/order/:orderId/settlement/other-incomes/:incomeId` | 修改 | 可把已存在未确认行保存为已确认 |
|
||||||
|
| 26 | 完成核单 | POST | `/v3/admin/order/:orderId/settlement/finalize` | 修改 | 只接受八类来源同步完成且所有非空明细均已确认的数据 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
以下接口均需登录态 JWT 和现有订单查看/核单权限;无接口级特殊限流。GET 为只读幂等;PUT 为全量替换幂等;POST 新增其他收入使用 `requestId` 保证同订单幂等。
|
||||||
|
|
||||||
|
### 3.1 住宿 Step 1
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 使用场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/step1` | 打开住宿 Tab、刷新系统配房来源 |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/step1` | 全量保存住宿行及逐行确认状态 |
|
||||||
|
|
||||||
|
**路径参数**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `orderId` | String(Long) | 是 | 订单 ID,必须大于 0 |
|
||||||
|
|
||||||
|
**PUT 请求体**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `items` | HotelItem[] | 是 | 全量数组;缺少的手工现存行按删除处理 |
|
||||||
|
| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 |
|
||||||
|
| `items[].hotelAssignmentId` | String(Long) | 否 | 配房来源行 ID;手工行为空 |
|
||||||
|
| `items[].hotelId` | String(Long) | 否 | 酒店 ID |
|
||||||
|
| `items[].roomTypeId` | String(Long) | 否 | 房型 ID |
|
||||||
|
| `items[].stayDate` | Date | 是 | `yyyy-MM-dd` |
|
||||||
|
| `items[].hotelName` | String | 是 | 最长 200 字符 |
|
||||||
|
| `items[].roomType` | String | 否 | 房型摘要,最长 64 字符 |
|
||||||
|
| `items[].roomTypeName` | String | 否 | 房型/规格名称,最长 128 字符 |
|
||||||
|
| `items[].roomCount` | Integer | 是 | 总间数 |
|
||||||
|
| `items[].unitPrice` | Decimal | 否 | 核算单价,必须大于等于 0 |
|
||||||
|
| `items[].plannedCost` | Decimal | 是 | 计划成本,必须大于等于 0 |
|
||||||
|
| `items[].actualCost` | Decimal | 是 | 实际成本,必须大于等于 0 |
|
||||||
|
| `items[].paymentMethod` | String | 条件必填 | `SIGNED/COMPANY_PAID/CASH_PAID`;手工行必填 |
|
||||||
|
| `items[].settleType` | String | 否 | `cash/sign/company`,兼容配房来源付款口径 |
|
||||||
|
| `items[].sourceType` | String | 是 | `HOUSE_ASSIGNMENT/MANUAL/SYSTEM`;`TEMPLATE` 仅兼容旧入参 |
|
||||||
|
| `items[].sourceId` | String(Long) | 否 | 系统来源业务 ID |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `items[].remark` | String | 否 | 最长 500 字符 |
|
||||||
|
| `items[].voucherUrls` | String[] | 否 | 凭证 URL |
|
||||||
|
|
||||||
|
**GET 响应 `data[]`**
|
||||||
|
|
||||||
|
除上述行字段外,还返回:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `paymentMethodName` | String | 付款方式名称 |
|
||||||
|
| `sourceTypeName` | String | 来源名称:配房结果/手工/系统 |
|
||||||
|
| `settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
|
||||||
|
**PUT 响应 `data`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `addedIds` | Long[] | 新增行 ID |
|
||||||
|
| `updatedIds` | Long[] | 更新行 ID |
|
||||||
|
| `deletedIds` | Long[] | 删除行 ID |
|
||||||
|
| `totalActualCost` | String(Decimal) | 保存后住宿实际成本合计 |
|
||||||
|
|
||||||
|
**业务边界**
|
||||||
|
|
||||||
|
- 配房来源行首次进入草稿返回 `UNCONFIRMED`;带已有 `id` 保存时可改为 `CONFIRMED`。
|
||||||
|
- 手工新行 `id=null` 时只允许 `UNCONFIRMED`;保存取得 ID 后,下一次 PUT 才可改为 `CONFIRMED`。
|
||||||
|
- `TEMPLATE` 只兼容旧请求,响应统一为 `SYSTEM`。
|
||||||
|
|
||||||
|
### 3.2 门票 Step 2
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 使用场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/step2` | 打开门票/游玩项目 Tab、刷新行程来源 |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/step2` | 全量保存门票行及逐行确认状态 |
|
||||||
|
|
||||||
|
**PUT 请求体**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `items` | TicketItem[] | 是 | 全量数组 |
|
||||||
|
| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 |
|
||||||
|
| `items[].sourceType` | String | 是 | `SCENIC_ASSIGNMENT/ACTIVITY_ASSIGNMENT/MANUAL`;`CUSTOM_ASSIGNMENT` 仅兼容旧入参 |
|
||||||
|
| `items[].scenicAssignmentId` | String(Long) | 系统行是 | 景区或活动来源 ID |
|
||||||
|
| `items[].dayNumber` | Integer | 否 | 行程第几天,响应派生 |
|
||||||
|
| `items[].dayDate` | Date | 是 | 行程日 |
|
||||||
|
| `items[].scenicName` | String | 是 | 项目名,最长 200 字符 |
|
||||||
|
| `items[].specName` | String | 否 | 票型/规格,最长 128 字符 |
|
||||||
|
| `items[].ticketCount` | Integer | 是 | 实际购票数量,可为 0 |
|
||||||
|
| `items[].ticketUnitPrice` | Decimal | 否 | 参考单价 |
|
||||||
|
| `items[].sellPrice` | Decimal | 否 | 客户成交单价,必须大于等于 0 |
|
||||||
|
| `items[].totalAmount` | Decimal | 否 | 客户成交小计,必须大于等于 0 |
|
||||||
|
| `items[].plannedCost` | Decimal | 是 | 计划成本,必须大于等于 0 |
|
||||||
|
| `items[].actualCost` | Decimal | 是 | 实际成本,必须大于等于 0 |
|
||||||
|
| `items[].paymentMethod` | String | 否 | `SIGNED/COMPANY_PAID/CASH_PAID` |
|
||||||
|
| `items[].voucherUrls` | String[] | 否 | 凭证 URL |
|
||||||
|
| `items[].remark` | String | 否 | 最长 500 字符 |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
|
||||||
|
**GET 响应 `data[]`**
|
||||||
|
|
||||||
|
返回完整 `TicketItem`,并增加:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `sourceTypeName` | String | 景区/游玩项目/手工 |
|
||||||
|
| `paymentMethodName` | String | 付款方式名称 |
|
||||||
|
| `settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
|
||||||
|
**PUT 响应**与住宿 Step 1 相同:`addedIds/updatedIds/deletedIds/totalActualCost`。
|
||||||
|
|
||||||
|
**业务边界**
|
||||||
|
|
||||||
|
- 系统来源首次同步为 `UNCONFIRMED`,来源事实变化后会重新变为 `UNCONFIRMED`。
|
||||||
|
- 手工新行必须先保存为 `UNCONFIRMED`,已有 ID 后可保存为 `CONFIRMED`。
|
||||||
|
- 响应不再返回 `CUSTOM_ASSIGNMENT`,历史自定义值统一返回 `MANUAL`。
|
||||||
|
|
||||||
|
### 3.3 车辆 Step 3
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 使用场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 查询 Order 侧车辆核单草稿 |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 带版本全量保存车辆行 |
|
||||||
|
|
||||||
|
**PUT 请求体**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `version` | Long | 是 | GET 返回的草稿版本,最小 0 |
|
||||||
|
| `items` | VehicleItem[] | 是 | 全量明细;所有现存 `FLEET` 行必须原样带回 |
|
||||||
|
| `items[].id` | String(Long) | FLEET/更新时是 | 行 ID;手工新行为空 |
|
||||||
|
| `items[].sourceType` | String | 是 | `FLEET/MANUAL` |
|
||||||
|
| `items[].serviceDate` | Date | 是 | 服务日期 |
|
||||||
|
| `items[].vehicleId` | String(Long) | 否 | 车辆 ID |
|
||||||
|
| `items[].vehiclePlate` | String | 否 | 车牌,最长 64 字符 |
|
||||||
|
| `items[].vehicleModelId` | String(Long) | 否 | 车型 ID |
|
||||||
|
| `items[].vehicleModelName` | String | 否 | 车型名,最长 128 字符 |
|
||||||
|
| `items[].driverId` | String(Long) | 否 | 司机 ID |
|
||||||
|
| `items[].driverName` | String | 否 | 司机名,最长 64 字符 |
|
||||||
|
| `items[].amount` | Decimal | 是 | 金额,0~10 位整数、2 位小数 |
|
||||||
|
| `items[].paymentMethod` | String | 是 | `CASH_PAID/SIGNED/COMPANY_PAID` |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `items[].remark` | String | 否 | 最长 500 字符 |
|
||||||
|
| `items[].voucherUrls` | String[] | 否 | 最多 9 个 http/https URL,单个最长 1024 字符 |
|
||||||
|
|
||||||
|
未知字段会被拒绝。
|
||||||
|
|
||||||
|
**GET/PUT 响应 `data`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orderId` | String(Long) | 订单 ID |
|
||||||
|
| `version` | Long | 当前草稿版本 |
|
||||||
|
| `totalAmount` | Decimal | 当前全部明细金额合计 |
|
||||||
|
| `allConfirmed` | Boolean | 非空行是否全部已确认;合法空集为 `true` |
|
||||||
|
| `items` | VehicleItem[] | 当前全量明细 |
|
||||||
|
| `items[].id` | String(Long) | 行 ID |
|
||||||
|
| `items[].sourceType` | String | `FLEET/MANUAL` |
|
||||||
|
| `items[].sourceTypeName` | String | 车务/手工 |
|
||||||
|
| `items[].serviceDate` | Date | 服务日期 |
|
||||||
|
| `items[].vehicleId` | String(Long) | 车辆 ID,可空 |
|
||||||
|
| `items[].vehiclePlate` | String | 车牌,可空 |
|
||||||
|
| `items[].vehicleModelId` | String(Long) | 车型 ID,可空 |
|
||||||
|
| `items[].vehicleModelName` | String | 车型名,可空 |
|
||||||
|
| `items[].driverId` | String(Long) | 司机 ID,可空 |
|
||||||
|
| `items[].driverName` | String | 司机名,可空 |
|
||||||
|
| `items[].amount` | Decimal | 核单金额 |
|
||||||
|
| `items[].paymentMethod` | String | 付款方式编码 |
|
||||||
|
| `items[].paymentMethodName` | String | 付款方式名称 |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 确认状态 |
|
||||||
|
| `items[].settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
| `items[].remark` | String | 备注 |
|
||||||
|
| `items[].voucherUrls` | String[] | 凭证 URL |
|
||||||
|
|
||||||
|
**业务边界**
|
||||||
|
|
||||||
|
- `FLEET` 行的日期、车辆、司机、金额和付款方式不可修改或删除;只允许修改确认状态、备注、凭证。
|
||||||
|
- 全量保存时必须带回全部 `FLEET` 行。手工行可新增、修改或从全量数组中删除。
|
||||||
|
- 手工新行必须先保存为 `UNCONFIRMED`;已有 ID 后可保存为 `CONFIRMED`。
|
||||||
|
- `version` 不匹配返回 584108,必须重新 GET 后再保存。
|
||||||
|
- 旧响应字段 `frozen/requirementId/settlementReady/totalVehicleFee` 及 Fleet 对账明细字段不再对管理后台输出。
|
||||||
|
|
||||||
|
### 3.4 人员费用五个 Tab
|
||||||
|
|
||||||
|
**接口路径**
|
||||||
|
|
||||||
|
| 角色 | GET/PUT 路径 | `detail` 结构 |
|
||||||
|
|---|---|---|
|
||||||
|
| 领队 | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | `days + per_day` |
|
||||||
|
| 司机 | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | `days[] + extra_cost + extra_breakdown[]` |
|
||||||
|
| 导游 | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | `persons[]` |
|
||||||
|
| 摄影师 | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | `persons[]` |
|
||||||
|
| 其他 | `/v3/admin/order/:orderId/settlement/staff-fees/others` | `items[]` |
|
||||||
|
|
||||||
|
**PUT 请求体**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `items` | StaffItem[] | 是 | 当前角色全量数组;空数组清空该 Tab 可删除的行 |
|
||||||
|
| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 |
|
||||||
|
| `items[].sourceType` | String | 是 | `STAFF_ASSIGNMENT/MANUAL/SYSTEM` |
|
||||||
|
| `items[].staffId` | String(Long) | 否 | 人员安排 ID;聚合或手工行可空 |
|
||||||
|
| `items[].detail` | Object | 是 | 由路径角色固定,结构见下表 |
|
||||||
|
| `items[].reimburse` | Decimal | 否 | 小额报销,空按 0 |
|
||||||
|
| `items[].paymentMethod` | String | 否 | 空按 `COMPANY_PAID` |
|
||||||
|
| `items[].voucherUrls` | String[] | 否 | 最多 9 个 http/https URL |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `items[].settleStatus` | String | 否 | 辅助人员 `PENDING/COMPLETED`,空按 `PENDING` |
|
||||||
|
| `items[].settledDate` | Date | 否 | 辅助人员结算日期 |
|
||||||
|
| `items[].transferRef` | String | 条件必填 | `settleStatus=COMPLETED` 时必填,最长 128 字符 |
|
||||||
|
| `items[].remark` | String | 否 | 最长 500 字符 |
|
||||||
|
|
||||||
|
**角色 `detail` 字段**
|
||||||
|
|
||||||
|
| 路径角色 | 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| leaders | `days` | Integer | 是 | 天数,最小 0 |
|
||||||
|
| leaders | `per_day` | Decimal | 是 | 每天费用,最小 0 |
|
||||||
|
| drivers | `days` | DriverDay[] | 是 | 服务日明细 |
|
||||||
|
| drivers | `days[].service_date` | Date | 是 | 服务日期 |
|
||||||
|
| drivers | `days[].vehicle_brief` | String | 否 | 车辆摘要 |
|
||||||
|
| drivers | `days[].daily_fee` | Decimal | 是 | 日费,仅回显,不计入人员费用 |
|
||||||
|
| drivers | `days[].is_used` | Boolean | 否 | 是否使用 |
|
||||||
|
| drivers | `days[].note` | String | 否 | 备注 |
|
||||||
|
| drivers | `extra_cost` | Decimal | 否 | 额外费用,空按 0 |
|
||||||
|
| drivers | `extra_breakdown` | ExtraItem[] | 否 | 合计必须等于 `extra_cost` |
|
||||||
|
| drivers | `extra_breakdown[].name` | String | 是 | 费用名 |
|
||||||
|
| drivers | `extra_breakdown[].amount` | Decimal | 是 | 金额,最小 0 |
|
||||||
|
| drivers | `extra_breakdown[].note` | String | 否 | 备注 |
|
||||||
|
| guides/photographers | `persons` | Person[] | 是 | 人员计费明细 |
|
||||||
|
| guides/photographers | `persons[].name` | String | 是 | 姓名 |
|
||||||
|
| guides/photographers | `persons[].days` | Integer | 是 | 天数,最小 0 |
|
||||||
|
| guides/photographers | `persons[].per_day` | Decimal | 是 | 每天费用,最小 0 |
|
||||||
|
| guides/photographers | `persons[].note` | String | 否 | 备注 |
|
||||||
|
| others | `items` | OtherItem[] | 是 | 其他人员费用项 |
|
||||||
|
| others | `items[].name` | String | 是 | 费用名称 |
|
||||||
|
| others | `items[].amount` | Decimal | 是 | 金额,最小 0 |
|
||||||
|
| others | `items[].note` | String | 否 | 备注 |
|
||||||
|
|
||||||
|
**GET 响应 `data`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `totalActualCost` | Decimal | 当前 Tab 实际费用合计 |
|
||||||
|
| `items` | StaffItem[] | 已保存行;未保存时可返回候选草稿 |
|
||||||
|
| `items[].id` | String(Long) | 行 ID;未保存候选为空 |
|
||||||
|
| `items[].sourceType` | String | 来源编码 |
|
||||||
|
| `items[].sourceTypeName` | String | 人员安排/手工/系统 |
|
||||||
|
| `items[].staffId` | String(Long) | 人员安排 ID,可空 |
|
||||||
|
| `items[].staffName` | String | 人员姓名或聚合摘要 |
|
||||||
|
| `items[].detail` | Object | 对应角色明细 |
|
||||||
|
| `items[].totalPlannedCost` | Decimal | 计划成本 |
|
||||||
|
| `items[].totalActualCost` | Decimal | 实际成本 |
|
||||||
|
| `items[].reimburse` | Decimal | 小额报销 |
|
||||||
|
| `items[].paymentMethod` | String | 付款方式 |
|
||||||
|
| `items[].voucherUrls` | String[] | 凭证 URL |
|
||||||
|
| `items[].settlementConfirmStatus` | String | 确认状态 |
|
||||||
|
| `items[].settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
| `items[].settleStatus` | String | 辅助人员结算状态;主报账人为空 |
|
||||||
|
| `items[].settledDate` | Date | 结算日期 |
|
||||||
|
| `items[].transferRef` | String | 转账流水号 |
|
||||||
|
| `items[].isPrimaryReporter` | Boolean | 是否主报账人 |
|
||||||
|
| `items[].remark` | String | 备注 |
|
||||||
|
|
||||||
|
PUT 成功返回统一成功包,`data=null`。
|
||||||
|
|
||||||
|
### 3.5 餐食费用
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 请求/响应 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/meals` | `data` 为 MealItem[] |
|
||||||
|
| POST | `/v3/admin/order/:orderId/settlement/meals` | 请求 MealSave;响应 MealItem |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/meals/:settlementId` | 请求 MealSave;响应 MealItem |
|
||||||
|
|
||||||
|
**MealSave 请求**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `mealType` | String | 是 | `BREAKFAST/LUNCH/DINNER` |
|
||||||
|
| `mealDate` | Date | 否 | 发生日期 |
|
||||||
|
| `mealName` | String | 是 | 最长 200 字符 |
|
||||||
|
| `quantity` | Integer | 是 | 1~10000 |
|
||||||
|
| `unitPrice` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 |
|
||||||
|
| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` |
|
||||||
|
| `sourceType` | String | 是 | `MEAL_ASSIGNMENT/MANUAL/SYSTEM`;新增接口请传 `MANUAL` |
|
||||||
|
| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL |
|
||||||
|
| `remark` | String | 否 | 最长 512 字符 |
|
||||||
|
|
||||||
|
`unitPrice × quantity` 不得超过 `99999999.99`。
|
||||||
|
|
||||||
|
**MealItem 响应**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | String(Long) | 餐食费用 ID |
|
||||||
|
| `mealType` | String | 餐型 |
|
||||||
|
| `mealDate` | Date | 发生日期 |
|
||||||
|
| `mealName` | String | 餐食名称 |
|
||||||
|
| `quantity` | Integer | 数量 |
|
||||||
|
| `unitPrice` | String(Decimal) | 单价 |
|
||||||
|
| `actualAmount` | String(Decimal) | 实际金额 |
|
||||||
|
| `paymentMethod` | String | 付款类型 |
|
||||||
|
| `sourceType` | String | 来源编码 |
|
||||||
|
| `sourceTypeName` | String | 餐饮安排/手工/系统 |
|
||||||
|
| `voucherUrls` | String[] | 凭证 URL |
|
||||||
|
| `settlementConfirmStatus` | String | 确认状态 |
|
||||||
|
| `settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
| `remark` | String | 备注 |
|
||||||
|
|
||||||
|
POST 创建的是手工行,必须先传 `UNCONFIRMED`;PUT 已存在行时可传 `CONFIRMED`,且不得改变原 `sourceType`。
|
||||||
|
|
||||||
|
### 3.6 其他支出
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 请求/响应 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/other-expenses` | `data` 为 OtherExpenseItem[] |
|
||||||
|
| POST | `/v3/admin/order/:orderId/settlement/other-expenses` | 请求 OtherExpenseSave;响应 OtherExpenseItem |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/other-expenses/:settlementId` | 请求 OtherExpenseSave;响应 OtherExpenseItem |
|
||||||
|
|
||||||
|
**OtherExpenseSave 请求**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `expenseType` | String | 是 | `FUEL/TOLL/PARKING/RENTAL/MAINTENANCE/OTHER` |
|
||||||
|
| `projectName` | String | 是 | 最长 200 字符 |
|
||||||
|
| `expenseDate` | Date | 否 | 发生日期 |
|
||||||
|
| `actualAmount` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 |
|
||||||
|
| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` |
|
||||||
|
| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL |
|
||||||
|
| `remark` | String | 否 | 最长 512 字符 |
|
||||||
|
|
||||||
|
**OtherExpenseItem 响应**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | String(Long) | 其他支出 ID |
|
||||||
|
| `expenseType` | String | 支出类型 |
|
||||||
|
| `projectName` | String | 项目名称 |
|
||||||
|
| `expenseDate` | Date | 发生日期 |
|
||||||
|
| `actualAmount` | String(Decimal) | 实际金额 |
|
||||||
|
| `paymentMethod` | String | 付款类型 |
|
||||||
|
| `sourceType` | String | `MANUAL/SYSTEM` |
|
||||||
|
| `sourceTypeName` | String | 手工/系统 |
|
||||||
|
| `voucherUrls` | String[] | 凭证 URL |
|
||||||
|
| `settlementConfirmStatus` | String | 确认状态 |
|
||||||
|
| `settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
| `remark` | String | 备注 |
|
||||||
|
|
||||||
|
POST 创建的是 `MANUAL` 行且必须先为 `UNCONFIRMED`;已有 ID 后通过 PUT 可改为 `CONFIRMED`。
|
||||||
|
|
||||||
|
### 3.7 其他收入
|
||||||
|
|
||||||
|
**接口**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 请求/响应 |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/v3/admin/order/:orderId/settlement/other-incomes` | `data` 为列表聚合对象 |
|
||||||
|
| POST | `/v3/admin/order/:orderId/settlement/other-incomes` | 请求 Create;响应 OtherIncomeItem |
|
||||||
|
| PUT | `/v3/admin/order/:orderId/settlement/other-incomes/:incomeId` | 请求 Update;响应 OtherIncomeItem |
|
||||||
|
|
||||||
|
**Create/Update 请求**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `requestId` | String | 仅 POST 是 | 同订单永久唯一,最长 64 字符 |
|
||||||
|
| `incomeDate` | Date | 是 | 收入日期 |
|
||||||
|
| `projectName` | String | 是 | 最长 100 字符 |
|
||||||
|
| `projectCategory` | String | 是 | 启用字典值,最长 64 字符 |
|
||||||
|
| `specification` | String | 否 | 票种/规格,最长 100 字符 |
|
||||||
|
| `quantity` | Decimal | 是 | 最多 8 位整数、4 位小数,最小 0 |
|
||||||
|
| `unitPrice` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 |
|
||||||
|
| `settlementAmount` | Decimal | 是 | 最多 8 位整数、2 位小数,必须大于 0,且等于数量乘单价(四舍五入到 2 位) |
|
||||||
|
| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` |
|
||||||
|
| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL |
|
||||||
|
| `remark` | String | 否 | 最长 500 字符 |
|
||||||
|
|
||||||
|
**OtherIncomeItem 响应**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | String(Long) | 其他收入 ID |
|
||||||
|
| `requestId` | String | 手工新增幂等 ID;系统投影为空 |
|
||||||
|
| `incomeDate` | Date | 收入日期 |
|
||||||
|
| `projectName` | String | 项目名称 |
|
||||||
|
| `projectCategory` | String | 项目类别 |
|
||||||
|
| `projectCategoryName` | String | 项目类别名称 |
|
||||||
|
| `specification` | String | 票种/规格 |
|
||||||
|
| `quantity` | Decimal | 数量 |
|
||||||
|
| `unitPrice` | Decimal | 核算单价 |
|
||||||
|
| `settlementAmount` | Decimal | 核算金额 |
|
||||||
|
| `paymentMethod` | String | 付款类型 |
|
||||||
|
| `paymentMethodName` | String | 付款类型名称 |
|
||||||
|
| `voucherUrls` | String[] | 凭证 URL |
|
||||||
|
| `settlementConfirmStatus` | String | 确认状态 |
|
||||||
|
| `settlementConfirmStatusName` | String | 未确认/已确认 |
|
||||||
|
| `remark` | String | 备注 |
|
||||||
|
| `sourceType` | String | `MANUAL/SYSTEM` |
|
||||||
|
| `sourceTypeName` | String | 手工/系统 |
|
||||||
|
| `sourceId` | String(Long) | 关联来源 ID |
|
||||||
|
|
||||||
|
**GET 聚合响应**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `items` | OtherIncomeItem[] | 其他收入明细 |
|
||||||
|
| `deductions` | Deduction[] | 只读减费明细 |
|
||||||
|
| `deductions[].id` | String(Long) | 减费 ID |
|
||||||
|
| `deductions[].discountName` | String | 减费名称 |
|
||||||
|
| `deductions[].discountAmount` | Decimal | 减费金额 |
|
||||||
|
| `deductions[].sourceType` | String | 减费来源 |
|
||||||
|
| `deductions[].sourceId` | String(Long) | 来源业务 ID |
|
||||||
|
| `deductions[].createdAt` | DateTime | 创建时间 |
|
||||||
|
| `summary.surchargeAmount` | Decimal | 有效增费合计 |
|
||||||
|
| `summary.discountAmount` | Decimal | 有效减费合计 |
|
||||||
|
| `summary.netAdjustmentAmount` | Decimal | 增费减去减费 |
|
||||||
|
|
||||||
|
手工新增行的公开来源为 `MANUAL`;系统自动投影行公开来源为 `SYSTEM`。原公开值 `ORDER_SURCHARGE` 不再返回。
|
||||||
|
|
||||||
|
### 3.8 完成核单
|
||||||
|
|
||||||
|
**接口**:`POST /v3/admin/order/:orderId/settlement/finalize`
|
||||||
|
|
||||||
|
**请求体**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `remark` | String | 否 | 整体备注,最长 500 字符 |
|
||||||
|
| `reimbursementExpectedSourceFingerprint` | String | 是 | 主报账表 64 位小写 SHA-256 指纹 |
|
||||||
|
| `groupExpectedSourceFingerprint` | String | 是 | 单团核算表 64 位小写 SHA-256 指纹 |
|
||||||
|
| `reimbursementConfirmation` | Object | 是 | 主报账确认凭据 |
|
||||||
|
| `reimbursementConfirmation.transferDate` | Date | 条件必填 | 主报账净额非 0 时必填 |
|
||||||
|
| `reimbursementConfirmation.transferRef` | String | 条件必填 | 主报账净额非 0 时必填,最长 128 字符 |
|
||||||
|
| `reimbursementConfirmation.advanceSettledFlag` | Boolean | 是 | 预支是否已处理;`false` 是合法值 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher` | Object | 是 | 签字凭证 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files` | File[] | 是 | 1~9 项 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files[].name` | String | 否 | 文件名,最长 255 字符 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.files[].url` | String | 是 | 文件 URL,最长 1024 字符 |
|
||||||
|
| `reimbursementConfirmation.signedVoucher.note` | String | 否 | 最长 500 字符 |
|
||||||
|
|
||||||
|
**响应 `data`**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `summaryId` | String(Long) | 核单汇总 ID |
|
||||||
|
| `finalSnapshotId` | String(Long) | 终态快照 ID |
|
||||||
|
| `finalSnapshotVersionNo` | Integer | 快照版本 |
|
||||||
|
| `finalSnapshotStatus` | String | 成功时为 `FINALIZED` |
|
||||||
|
| `orderId` | String(Long) | 订单 ID |
|
||||||
|
| `settledAt` | DateTime | 核单完成时间 |
|
||||||
|
| `totalAmount` | String(Decimal) | 订单总金额快照 |
|
||||||
|
| `paidAmount` | String(Decimal) | 已付金额快照 |
|
||||||
|
| `balanceAmount` | String(Decimal) | 尾款金额快照 |
|
||||||
|
| `roomCost` | String(Decimal) | 住宿实际成本 |
|
||||||
|
| `ticketCost` | String(Decimal) | 门票实际成本 |
|
||||||
|
| `staffCost` | String(Decimal) | 人员费用实际成本 |
|
||||||
|
| `subsidyCost` | String(Decimal) | 补助实际成本 |
|
||||||
|
| `mealCost` | String(Decimal) | 餐食实际成本 |
|
||||||
|
| `vehicleCost` | String(Decimal) | 车辆实际成本 |
|
||||||
|
| `otherExpenseCost` | String(Decimal) | 其他支出实际成本 |
|
||||||
|
| `insurancePremium` | String(Decimal) | 保险实际保费 |
|
||||||
|
| `totalActualCost` | String(Decimal) | 总实际成本 |
|
||||||
|
| `driverTransferAmount` | String(Decimal) | 给主报账人的转回金额 |
|
||||||
|
| `profitAmount` | String(Decimal) | 公司毛利 |
|
||||||
|
| `profitRate` | Decimal | 毛利率小数 |
|
||||||
|
| `orderStatusAfter` | String | 完成后的订单状态 |
|
||||||
|
| `mqTriggered` | Boolean | 当前固定为 `false` |
|
||||||
|
| `warnings` | String[] | 不阻塞完成核单的软预警 |
|
||||||
|
|
||||||
|
**业务边界**
|
||||||
|
|
||||||
|
- 住宿、门票/游玩、餐食、车辆、导游、摄影、其他收入、其他支出八类来源必须同步完成。
|
||||||
|
- 任一非空分类存在 `UNCONFIRMED` 行时,finalize 返回 584310,不生成终态快照。
|
||||||
|
- 系统来源事实变化会使对应行重新变为 `UNCONFIRMED`;应刷新、复核并保存后再 finalize。
|
||||||
|
|
||||||
|
## 4. 接口入参汇总
|
||||||
|
|
||||||
|
| 输入类型 | 适用接口 | 关键变化 |
|
||||||
|
|---|---|---|
|
||||||
|
| 路径参数 | 全部 26 个接口 | `orderId` 必填;行级修改另有 `settlementId/incomeId` |
|
||||||
|
| 全量明细 | 住宿、门票、车辆、五个人员 Tab | 必须提交完整 `items`;新增手工行先传 `UNCONFIRMED` |
|
||||||
|
| 单行保存 | 餐食、其他支出、其他收入 | POST 新增先未确认,PUT 已有行可确认 |
|
||||||
|
| 车辆版本 | 车辆 PUT | 必须原样回传最近 GET 的 `version` |
|
||||||
|
| 完成核单凭据 | finalize | 双报告指纹 + 主报账转账/签字凭据 |
|
||||||
|
|
||||||
|
完整字段、必填性和校验已分别内联在 §3.1~§3.8。
|
||||||
|
|
||||||
|
## 5. 出参字段汇总
|
||||||
|
|
||||||
|
| 变化 | 适用响应 |
|
||||||
|
|---|---|
|
||||||
|
| 新增 `sourceType/sourceTypeName` | 人员、餐食、其他支出;其他收入的来源语义调整 |
|
||||||
|
| 新增 `settlementConfirmStatus/settlementConfirmStatusName` | 门票;餐食、其他支出、人员补齐名称 |
|
||||||
|
| Long ID 按字符串返回 | 住宿、门票、人员、车辆以及已有明确字符串序列化的资金明细 |
|
||||||
|
| 新车辆草稿结构 | `orderId/version/totalAmount/allConfirmed/items` |
|
||||||
|
| 删除车辆内部/兼容字段 | 不再输出 `frozen/requirementId/settlementReady/totalVehicleFee` 及 Fleet 对账字段 |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 住宿 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `HOUSE_ASSIGNMENT` | 配房结果 | 系统配房来源 |
|
||||||
|
| `MANUAL` | 手工 | 管理后台手工新增 |
|
||||||
|
| `SYSTEM` | 系统 | 其他系统来源;替代旧公开值 `TEMPLATE` |
|
||||||
|
| `TEMPLATE` | 历史兼容 | 仅请求兼容,响应不返回 |
|
||||||
|
|
||||||
|
### 6.2 门票 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `SCENIC_ASSIGNMENT` | 景区 | 景区安排来源 |
|
||||||
|
| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 活动安排来源 |
|
||||||
|
| `MANUAL` | 手工 | 手工新增;响应统一值 |
|
||||||
|
| `CUSTOM_ASSIGNMENT` | 历史兼容 | 仅请求兼容,响应归一为 `MANUAL` |
|
||||||
|
|
||||||
|
### 6.3 餐食 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `MEAL_ASSIGNMENT` | 餐饮安排 | 系统餐饮安排来源 |
|
||||||
|
| `MANUAL` | 手工 | 管理后台新增 |
|
||||||
|
| `SYSTEM` | 系统 | 其他系统来源 |
|
||||||
|
|
||||||
|
### 6.4 车辆 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `FLEET` | 车务 | 车务同步行,业务字段不可改删 |
|
||||||
|
| `MANUAL` | 手工 | 核单页手工补录 |
|
||||||
|
|
||||||
|
### 6.5 人员 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `STAFF_ASSIGNMENT` | 人员安排 | 系统人员安排来源 |
|
||||||
|
| `MANUAL` | 手工 | 手工新增 |
|
||||||
|
| `SYSTEM` | 系统 | 其他系统来源 |
|
||||||
|
|
||||||
|
### 6.6 其他收入/其他支出 `sourceType`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `MANUAL` | 手工 | 管理后台创建 |
|
||||||
|
| `SYSTEM` | 系统 | 自动投影或系统来源 |
|
||||||
|
|
||||||
|
### 6.7 `settlementConfirmStatus`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `UNCONFIRMED` | 未确认 | 首次系统同步或手工新行的初始状态 |
|
||||||
|
| `CONFIRMED` | 已确认 | 已有行复核后保存的状态 |
|
||||||
|
|
||||||
|
### 6.8 `paymentMethod`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `CASH_PAID` | 现付 | 现场/主报账人支付 |
|
||||||
|
| `COMPANY_PAID` | 公司付款 | 公司直接支付 |
|
||||||
|
| `SIGNED` | 签单 | 签单结算 |
|
||||||
|
|
||||||
|
### 6.9 餐型 `mealType`
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|---|---|
|
||||||
|
| `BREAKFAST` | 早餐 |
|
||||||
|
| `LUNCH` | 午餐 |
|
||||||
|
| `DINNER` | 晚餐 |
|
||||||
|
|
||||||
|
### 6.10 支出类型 `expenseType`
|
||||||
|
|
||||||
|
| 值 | 中文 |
|
||||||
|
|---|---|
|
||||||
|
| `FUEL` | 油费 |
|
||||||
|
| `TOLL` | 过路费 |
|
||||||
|
| `PARKING` | 停车费 |
|
||||||
|
| `RENTAL` | 租赁费 |
|
||||||
|
| `MAINTENANCE` | 维修保养 |
|
||||||
|
| `OTHER` | 其他 |
|
||||||
|
|
||||||
|
### 6.11 人员辅助结算状态 `settleStatus`
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PENDING` | 待结算 | 默认值 |
|
||||||
|
| `COMPLETED` | 已结算 | 必须同时提交 `transferRef` |
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `400` | 参数校验失败 | 必填缺失、格式/长度/枚举错误、车辆或人员请求出现未知字段 |
|
||||||
|
| `584001/584010/584020/584050/584070` | 订单不存在 | 对应住宿、门票、人员、finalize 或通用查询找不到订单 |
|
||||||
|
| `584002/584011/584021` | 当前状态不可写 | 住宿、门票、人员费用不在允许的核单阶段 |
|
||||||
|
| `584006/584012/584022` | 枚举或角色非法 | 住宿付款、门票来源、人员角色非法 |
|
||||||
|
| `584067/584068/584069` | 住宿资源无效或暂不可用 | 手工酒店/房型无效或资源服务不可用 |
|
||||||
|
| `584071` | 无权访问该订单 | 公司隔离或现有订单权限不满足 |
|
||||||
|
| `584073` | 其他收入不存在 | `incomeId` 不属于当前订单 |
|
||||||
|
| `584074` | 当前状态不允许修改其他收入 | 核单状态不可写 |
|
||||||
|
| `584076` | 其他收入金额不一致 | `settlementAmount != quantity × unitPrice` |
|
||||||
|
| `584077` | 存在未确认的其他收入 | 生成报告或完成核单前仍有其他收入未确认 |
|
||||||
|
| `584086` | 无权修改核单资金数据 | 非主管、管理员或财务 |
|
||||||
|
| `584087` | `requestId` 冲突 | 同订单相同 `requestId` 被另一笔请求占用 |
|
||||||
|
| `584089` | 核单或结算已完成 | 再次修改资金明细 |
|
||||||
|
| `584090/584091` | 餐食/其他支出不存在 | 行 ID 不属于当前订单 |
|
||||||
|
| `584092` | 存在未确认的人员费用 | 生成报告或完成核单前仍有人员费用未确认 |
|
||||||
|
| `584094/584095` | 餐食/其他支出字段非法 | 字段越界或试图改变系统来源 |
|
||||||
|
| `584096/584097` | 付款方式/凭证非法 | 枚举错误或 URL 数量、格式错误 |
|
||||||
|
| `584098` | 餐食或其他支出存在未确认记录 | 生成报告或完成核单前仍有餐食/其他支出未确认 |
|
||||||
|
| `584100/584101/584102` | 车辆来源不可用/未就绪/为空 | 车务数据不可读、未完结/未确认、无可核单费用 |
|
||||||
|
| `584103/584104/584105` | 其他收入字典非法或不可用 | 项目类别、规格无效或字典不可用 |
|
||||||
|
| `584106` | 确认状态非法 | 非 `UNCONFIRMED/CONFIRMED` |
|
||||||
|
| `584107` | 手工新增行必须先未确认 | `id=null` 的手工新行直接传 `CONFIRMED` |
|
||||||
|
| `584108` | 车辆草稿版本冲突 | PUT 的 `version` 已过期 |
|
||||||
|
| `584109` | 车务来源字段不可改删 | 修改/漏传 `FLEET` 行的权威字段 |
|
||||||
|
| `584310` | 八类核单未全部确认或数据已变化 | finalize 前有非空未确认行、来源未就绪 |
|
||||||
|
| `584315` | 报告来源已变化 | finalize 双报告指纹过期 |
|
||||||
|
| `584317` | 当前报告状态不允许操作 | finalize 凭据结构或状态不满足 |
|
||||||
|
| `584325` | 完成核单必须提交当前指纹 | 双报告指纹缺失 |
|
||||||
|
|
||||||
|
## 验证证据(8. 示例:典型 / 边界 / 异常)
|
||||||
|
|
||||||
|
已完成以下测试环境路由与业务负向验证:
|
||||||
|
|
||||||
|
- 部署任务 `80f1695a` 成功,order-v3 主、副实例滚动完成。
|
||||||
|
- 两个核算中订单实调
|
||||||
|
`GET /v3/admin/order/:orderId/settlement/step3/vehicles`
|
||||||
|
均进入新代码并返回业务前置码 `584102`,证明新路由已生效。
|
||||||
|
- 旧 `GET /v3/admin/order/:orderId/settlement/vehicle-fees` 返回 `404`,证明旧入口已下线。
|
||||||
|
|
||||||
|
本节下列 JSON 是按已合并 Controller/VO 契约给出的自包含调用示例。由于测试订单缺少可核单车辆费用,本次未取得车辆 DTO 正向数据,也未完成车辆链路 Full E2E;不得把上述 584102 负向结果描述为正向业务通过。
|
||||||
|
|
||||||
|
### 8.1 典型成功:确认车辆系统来源行
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /v3/admin/order/900000000001/settlement/step3/vehicles
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "930000000001",
|
||||||
|
"sourceType": "FLEET",
|
||||||
|
"serviceDate": "2026-07-30",
|
||||||
|
"vehicleId": "880000000001",
|
||||||
|
"vehiclePlate": "藏A12345",
|
||||||
|
"vehicleModelId": "870000000001",
|
||||||
|
"vehicleModelName": "七座商务车",
|
||||||
|
"driverId": "860000000001",
|
||||||
|
"driverName": "张师傅",
|
||||||
|
"amount": 1200.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"remark": "金额已核对",
|
||||||
|
"voucherUrls": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"orderId": "900000000001",
|
||||||
|
"version": 4,
|
||||||
|
"totalAmount": 1200.00,
|
||||||
|
"allConfirmed": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "930000000001",
|
||||||
|
"sourceType": "FLEET",
|
||||||
|
"sourceTypeName": "车务",
|
||||||
|
"serviceDate": "2026-07-30",
|
||||||
|
"vehicleId": "880000000001",
|
||||||
|
"vehiclePlate": "藏A12345",
|
||||||
|
"vehicleModelId": "870000000001",
|
||||||
|
"vehicleModelName": "七座商务车",
|
||||||
|
"driverId": "860000000001",
|
||||||
|
"driverName": "张师傅",
|
||||||
|
"amount": 1200.00,
|
||||||
|
"paymentMethod": "COMPANY_PAID",
|
||||||
|
"paymentMethodName": "公司付款",
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"settlementConfirmStatusName": "已确认",
|
||||||
|
"remark": "金额已核对",
|
||||||
|
"voucherUrls": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况:合法空车辆草稿
|
||||||
|
|
||||||
|
无当前用车需求时,GET 可返回合法空集;`allConfirmed=true` 表示“空集中没有未确认行”,不表示存在车辆费用。
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/900000000001/settlement/step3/vehicles
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"orderId": "900000000001",
|
||||||
|
"version": 1,
|
||||||
|
"totalAmount": 0.00,
|
||||||
|
"allConfirmed": true,
|
||||||
|
"items": []
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:手工新行直接确认
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/900000000001/settlement/meals
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mealType": "LUNCH",
|
||||||
|
"mealDate": "2026-07-30",
|
||||||
|
"mealName": "团队午餐",
|
||||||
|
"quantity": 10,
|
||||||
|
"unitPrice": 50.00,
|
||||||
|
"paymentMethod": "CASH_PAID",
|
||||||
|
"sourceType": "MANUAL",
|
||||||
|
"settlementConfirmStatus": "CONFIRMED",
|
||||||
|
"voucherUrls": [],
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 584107,
|
||||||
|
"message": "手工新增核单明细必须先保存为未确认",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- ✅ **系统来源首次同步**:生成已有 ID 的 `UNCONFIRMED` 行;复核后可直接在对应 PUT 中保存为 `CONFIRMED`。
|
||||||
|
- ✅ **手工新增**:第一次必须保存为 `UNCONFIRMED`;接口返回 ID 后,第二次更新才允许保存为 `CONFIRMED`。
|
||||||
|
- ✅ **来源统一**:手工行统一公开为 `MANUAL`;系统行公开为各分类系统来源值,无法细分的系统行为 `SYSTEM`。
|
||||||
|
- ❌ **不可混用确认和辅助结算状态**:`settlementConfirmStatus` 表示核单确认;人员 `settleStatus` 表示辅助人员款项是否结清。
|
||||||
|
- ❌ **不可修改系统权威字段**:系统来源事实变化后应重新 GET;车辆 `FLEET` 行不得由前端改删。
|
||||||
|
- ⚠️ **finalize 门禁**:八个核单分类来源必须就绪,且每个非空分类全部逐行 `CONFIRMED`。
|
||||||
|
- ⚠️ **空分类**:合法空分类没有未确认行,但来源同步仍必须就绪;`allConfirmed=true` 不等于有费用。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 范围 | 改前 | 改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| 住宿系统来源 | `TEMPLATE` | `SYSTEM`;`TEMPLATE` 仅兼容旧入参 |
|
||||||
|
| 门票手工来源 | 可能返回 `CUSTOM_ASSIGNMENT` | 统一返回 `MANUAL` |
|
||||||
|
| 其他收入来源 | `ORDER_SURCHARGE` | `MANUAL` 或 `SYSTEM` |
|
||||||
|
| 门票行确认 | 无逐行确认字段 | 新增 `settlementConfirmStatus/Name` |
|
||||||
|
| 人员请求行 | 无 `id/sourceType/settlementConfirmStatus` | 三字段纳入全量保存契约 |
|
||||||
|
| 餐食请求/响应 | 无公开来源,确认名称不完整 | 增加 `sourceType/sourceTypeName/settlementConfirmStatusName` |
|
||||||
|
| 其他支出响应 | 无公开来源和确认名称 | 增加 `sourceType/sourceTypeName/settlementConfirmStatusName` |
|
||||||
|
| 住宿/门票/人员 ID | 部分按 JSON 数字输出 | 明细 `id`、来源 ID 按字符串输出 |
|
||||||
|
| 车辆顶层响应 | `frozen/requirementId/settlementReady/totalVehicleFee/items` | `orderId/version/totalAmount/allConfirmed/items` |
|
||||||
|
| 车辆行响应 | 暴露 Fleet 对账、冻结和自动车费字段 | 仅输出核单需要的来源、车辆、司机、金额、付款、确认、凭证字段 |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| 系统派生行初始状态 | 分类规则不一致,部分直接视为已确认 | 统一先 `UNCONFIRMED`,用户复核后保存为 `CONFIRMED` |
|
||||||
|
| 手工新增并确认 | 部分接口允许一次保存即确认 | 必须先未确认,取得 ID 后再确认 |
|
||||||
|
| 车辆入口 | 旧 `/settlement/vehicle-fees` 已下线且无新独立编辑入口 | 使用 `/settlement/step3/vehicles` GET/PUT |
|
||||||
|
| 车辆并发保存 | 无前端草稿版本 | 必须携带 `version`,冲突时刷新 |
|
||||||
|
| 完成核单 | 分类确认来源不完全统一 | 只接受八类来源就绪且非空行全部已确认的数据 |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:是。车辆入口和响应结构为新契约;其他收入 `sourceType` 值发生变化;多个请求/响应新增确认与来源字段。
|
||||||
|
- **前端是否必须同步上线**:是。需切换车辆接口、适配字符串 ID、新来源枚举和“先保存未确认、再确认”的交互。
|
||||||
|
|
||||||
|
### 11.2 回滚说明
|
||||||
|
|
||||||
|
若后端回滚,前端需同时回滚车辆 Step3 新入口及新增字段依赖;旧
|
||||||
|
`/settlement/vehicle-fees` 两个接口在本次变更前已下线,不能作为回滚兜底。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- 删除对旧 `GET /settlement/vehicle-fees` 和 `POST /settlement/vehicle-fees/confirm` 的任何残留调用。
|
||||||
|
- 车辆保存必须回传最近 GET 的 `version` 和全部 `FLEET` 行;584108 时刷新后让用户重新确认。
|
||||||
|
- 不再把 `ORDER_SURCHARGE`、`TEMPLATE`、`CUSTOM_ASSIGNMENT` 当作新响应值。
|
||||||
|
- 所有 Long 类型字符串 ID 按字符串比较、传递,不转为 JavaScript Number。
|
||||||
|
- finalize 返回 584310 时,应刷新相关 Tab;来源变化可能已把已确认行重新置为未确认。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5356](https://git.1814.love:8443/wx/HL/issues/5356)
|
||||||
|
- **PR**: [#5362](https://git.1814.love:8443/wx/HL/pulls/5362)
|
||||||
|
- **Feature commit**: [6e396f6fc4](https://git.1814.love:8443/wx/HL/commit/6e396f6fc48fbf6581e87224bb85f2c811759727)
|
||||||
|
- **Merge commit**: [cfac945db2](https://git.1814.love:8443/wx/HL/commit/cfac945db268640b0b9e60b4d8c7ab55739a69e3)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @yaosutu
|
||||||
@ -0,0 +1,261 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5360"
|
||||||
|
title: "核单车辆异步下拉"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "新增接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "partial"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "Pi"
|
||||||
|
frontend_ref: "v2.1@0927f28de6c091d1eb1c867f5c96088358057043"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-30"
|
||||||
|
status_note: "管理后台已在核单车辆手工行接入异步车辆下拉,按关键词远程检索并回填车辆、车型与常驻司机字符串 ID;pnpm checkpoint 全量通过,业务提交 0927f28de6c091d1eb1c867f5c96088358057043 已推送至 origin/v2.1。测试服真实订单正向响应与角色权限仍受有效登录态缺失限制。"
|
||||||
|
updated_at: "2026-07-31"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-30T17:18:34+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# ✨【新增接口·管理后台】核单车辆异步下拉 (#5360)
|
||||||
|
|
||||||
|
| 头部字段 | 当前值 |
|
||||||
|
|---|---|
|
||||||
|
| PR / 服务 | [#5361](https://git.1814.love:8443/wx/HL/pulls/5361) / `hl-order-service-v3` |
|
||||||
|
| 后端状态 | `deployed`:已合入 `dev-v3` 并部署测试服 |
|
||||||
|
| 网关状态 | `partial`:路由和鉴权响应已验证,正向业务响应待有效登录态复验 |
|
||||||
|
| 前端回写标志 | 已实现 |
|
||||||
|
| 前端认领信息 | `frontend_owner: Pi`;`frontend_ref: v2.1@0927f28de6c091d1eb1c867f5c96088358057043` |
|
||||||
|
| 更新时间 | `2026-07-30` |
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
核单页面需要按车牌、品牌型号、车型大类或常驻司机姓名异步检索车辆。新增轻量只读下拉接口,返回可直接作为车辆选项使用的七个字段。
|
||||||
|
|
||||||
|
## 2. 变更清单
|
||||||
|
|
||||||
|
| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 查询核单车辆异步下拉 | `GET` | `/v3/admin/order/{orderId}/settlement/vehicle-options` | ✨ 新增接口 | 按关键词检索车辆,默认最多返回 10 条,最多返回 20 条 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 查询核单车辆异步下拉
|
||||||
|
|
||||||
|
- **接口说明**:`keyword` 可匹配车牌、品牌型号、车型大类和常驻司机姓名;`limit` 默认 10、最大 20。
|
||||||
|
- **使用场景**:核单页面加载车辆选择器或按关键词刷新候选项。
|
||||||
|
- **认证**:需要管理后台登录态。房务管理员和房务组长不可调用;管理员、超级管理员可查看任意订单,其他后台角色仅可查看本人作为定制师的订单。
|
||||||
|
- **幂等性**:幂等,只读查询,无请求体、无幂等键。
|
||||||
|
- **限流**:本接口未声明独立限流规则。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 默认值 | 说明与校验规则 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `orderId` | path | `String` | 是 | — | 订单 ID,必须是大于 0 的整数;按字符串传递,避免 JavaScript 数字精度损失 |
|
||||||
|
| `keyword` | query | `String` | 否 | 空 | 模糊匹配车牌、品牌型号、车型大类或常驻司机姓名;不传或仅空白字符表示不过滤 |
|
||||||
|
| `limit` | query | `Integer` | 否 | `10` | 期望返回条数;不传或非正数按 10 处理,超过 20 按 20 处理 |
|
||||||
|
|
||||||
|
### 4.2 请求体字段
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
响应类型:`Result<List<SettlementVehicleOptionRespVO>>`。
|
||||||
|
|
||||||
|
### 5.1 统一响应
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `code` | `Integer` | 否 | `200` 表示成功;其他值见错误码 |
|
||||||
|
| `message` | `String` | 否 | 响应消息,成功时为 `成功` |
|
||||||
|
| `data` | `Array<VehicleOption>` | 失败时可空 | 车辆下拉项数组;没有匹配项时为 `[]` |
|
||||||
|
| `traceId` | `String` | 是 | 链路追踪 ID,未注入时可为 `null` 或不返回 |
|
||||||
|
| `success` | `Boolean` | 否 | `code === 200` 时为 `true` |
|
||||||
|
|
||||||
|
### 5.2 `data[]` 车辆下拉项
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `vehicleId` | `String` | 否 | 车辆 ID;JSON 固定按字符串返回 |
|
||||||
|
| `plate` | `String` | 是 | 车牌 |
|
||||||
|
| `modelName` | `String` | 是 | 品牌型号 |
|
||||||
|
| `typeName` | `String` | 是 | 车型大类名称 |
|
||||||
|
| `primaryDriverId` | `String` | 是 | 常驻司机 ID;无常驻司机时为 `null`;有值时按字符串返回 |
|
||||||
|
| `primaryDriverName` | `String` | 是 | 常驻司机姓名;无常驻司机时为 `null` |
|
||||||
|
| `label` | `String` | 否 | 下拉展示文案,依次包含车牌、品牌型号、车型大类和常驻司机姓名;无常驻司机时最后一段为 `无常驻司机` |
|
||||||
|
|
||||||
|
`data[]` 严格只有以上七个字段,不包含车辆费用、支付方式或其他未声明字段。
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
本接口的入参和出参不包含枚举或数据字典字段。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `400` | `订单 ID 必须大于 0` | `orderId <= 0`,参数校验失败 |
|
||||||
|
| `581007` | `订单不存在` | `orderId` 对应订单不存在 |
|
||||||
|
| `581008` | `无权查看此订单` | 非管理员后台角色访问其他定制师的订单,或请求上下文缺少可用于判断订单归属的管理员 ID |
|
||||||
|
| `581045` | `房务角色无权查看订单详情,房务仅可配房` | 房务管理员或房务组长调用本接口 |
|
||||||
|
| `584072` | `车务司机车辆信息暂时不可用,请稍后重试` | 车辆候选信息暂时不可用 |
|
||||||
|
|
||||||
|
管理后台登录态无效或缺失时,请求会在进入本接口前被统一认证拦截。
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/settlement/vehicle-options?keyword=%E5%BC%A0%E5%B8%88%E5%82%85&limit=10
|
||||||
|
Authorization: Bearer <管理后台访问令牌>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"vehicleId": "9202101",
|
||||||
|
"plate": "蒙A-88888",
|
||||||
|
"modelName": "丰田汉兰达",
|
||||||
|
"typeName": "SUV",
|
||||||
|
"primaryDriverId": "9204101",
|
||||||
|
"primaryDriverName": "张师傅",
|
||||||
|
"label": "蒙A-88888***丰田汉兰达***SUV***张师傅"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traceId": "a1b2c3d4-e5f6-7890",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况
|
||||||
|
|
||||||
|
**场景说明**:不传关键词;`limit=20` 使用允许的最大返回条数;示例项没有常驻司机。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2079454953641836546/settlement/vehicle-options?limit=20
|
||||||
|
Authorization: Bearer <管理后台访问令牌>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"vehicleId": "9202102",
|
||||||
|
"plate": "蒙A-66666",
|
||||||
|
"modelName": "别克GL8",
|
||||||
|
"typeName": "商务车",
|
||||||
|
"primaryDriverId": null,
|
||||||
|
"primaryDriverName": null,
|
||||||
|
"label": "蒙A-66666***别克GL8***商务车***无常驻司机"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traceId": "b2c3d4e5-f6a7-8901",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
没有匹配项时,`data` 返回空数组:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": [],
|
||||||
|
"traceId": "b2c3d4e5-f6a7-8901",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败
|
||||||
|
|
||||||
|
**场景说明**:`orderId=0`,不满足大于 0 的校验规则。
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/0/settlement/vehicle-options
|
||||||
|
Authorization: Bearer <管理后台访问令牌>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "订单 ID 必须大于 0",
|
||||||
|
"data": null,
|
||||||
|
"traceId": "c3d4e5f6-a7b8-9012",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:管理后台核单页面只读查询车辆候选;可按车牌、品牌型号、车型大类或常驻司机姓名搜索。
|
||||||
|
- **访问范围**:管理员、超级管理员可访问全部订单;其他允许查看订单详情的后台角色仅可访问本人作为定制师的订单。
|
||||||
|
- **不适用角色**:房务管理员、房务组长不可查看本接口数据。
|
||||||
|
- **返回范围**:查询结果最多 20 条;无匹配项返回 `[]`;接口不返回车辆费用、支付方式等核单数据。
|
||||||
|
- **特殊边界**:`keyword` 为空或空白时不过滤;`limit <= 0` 按 10 处理;`limit > 20` 按 20 处理。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
本次为新增接口,不修改任何既有接口的字段、类型、必填性、枚举或错误码,因此无字段级、行为级替换关系。
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。新增独立 GET 路径,不影响既有调用方。
|
||||||
|
- **前端是否必须同步上线**:否。未接入本接口的旧版管理后台可继续运行;需要核单车辆异步搜索能力时再接入。
|
||||||
|
- **回滚影响**:若新接口不可用,前端应停用本下拉数据源,不应改用未在本文声明的字段或接口代替。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- `vehicleId` 和非空的 `primaryDriverId` 必须始终按字符串保存、比较和提交,不能转为 JavaScript `Number`。
|
||||||
|
- 前端只依赖 `data[]` 中声明的七个字段;`primaryDriverId`、`primaryDriverName` 允许为 `null`。
|
||||||
|
- 搜索时传用户输入的 `keyword` 即可;不需要为车牌、车型或司机姓名拆分多次请求。
|
||||||
|
- 本接口为只读查询,成功响应不表示已选择、保存或核单确认车辆。
|
||||||
|
- 测试服已确认新路径进入统一鉴权链路;因现有测试登录态失效,真实订单正向响应、关键词过滤、`limit` 边界与角色权限仍待使用有效管理后台登录态复验。
|
||||||
|
|
||||||
|
### 12.1 验证状态
|
||||||
|
|
||||||
|
- **验证模式**:`TARGETED_FALLBACK`,仅执行只读 GET,无写入。
|
||||||
|
- **已验证**:测试服双实例 OpenAPI 已加载本接口;真实网关请求已进入统一鉴权链路并返回标准五字段错误体。
|
||||||
|
- **待复验**:现有测试登录态已失效,真实订单 `code=200` 响应、七字段运行时值、关键词过滤、`limit` 边界和角色权限尚未形成正向实证。
|
||||||
|
- **验证边界**:上述状态不代表核单 Full E2E 已完成。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**: [#5360](https://git.1814.love:8443/wx/HL/issues/5360)
|
||||||
|
- **PR**: [#5361](https://git.1814.love:8443/wx/HL/pulls/5361)
|
||||||
|
- **Merge commit**: [0ff4ef45ccd0f4ebb1d3be00b27cde06f26bce0b](https://git.1814.love:8443/wx/HL/commit/0ff4ef45ccd0f4ebb1d3be00b27cde06f26bce0b)
|
||||||
|
- **Feature commit**: [5be7985164cdae5417cdeb2a2be7d104dc8fdae8](https://git.1814.love:8443/wx/HL/commit/5be7985164cdae5417cdeb2a2be7d104dc8fdae8)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**:yaosutu
|
||||||
|
- **前端状态**:已实现并推送 `0927f28de6c091d1eb1c867f5c96088358057043`
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5363"
|
||||||
|
title: "车务派车详情大交通契约补全"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "pending"
|
||||||
|
gateway_status: "pending"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "Pi"
|
||||||
|
frontend_ref: "v2.1@f4cac12fff3297fb42e6a217b1764b5408339133"
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-07-31"
|
||||||
|
status_note: "管理后台已补全大交通交通方式、真实双端路线与 legacy-only fail-closed 展示,checkpoint 通过并推送 f4cac12fff3297fb42e6a217b1764b5408339133;后端 PR #5365 虽已合并,但尚未部署或执行网关验证,因此 backend/gateway 继续保持 pending,前端不宣称页面联调 verified。"
|
||||||
|
updated_at: "2026-08-01"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 车务:派车详情大交通契约补全 (#5363)
|
||||||
|
|
||||||
|
> **服务**:`hl-order-service-v3`、`hl-fleet-service`
|
||||||
|
>
|
||||||
|
> **PR**:[#5365](https://git.1814.love:8443/wx/HL/pulls/5365)(已合并,merge `e8e654a482`)
|
||||||
|
>
|
||||||
|
> **Backend Issue**:[#5363](https://git.1814.love:8443/wx/HL/issues/5363)
|
||||||
|
>
|
||||||
|
> **Frontend tracking**:[#5366](https://git.1814.love:8443/wx/HL/issues/5366)(管理后台已实现静态契约与回归测试;后端未部署前不宣称页面联调完成)
|
||||||
|
>
|
||||||
|
> **日期**:2026-07-30
|
||||||
|
>
|
||||||
|
> **影响范围**:管理后台车务派车详情的大交通整团段与分批批次
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 变更类型 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 查询车务派车订单详情 | `GET` | `/admin/fleet/board/orders/:orderId` | 响应字段 additive 新增 |
|
||||||
|
|
||||||
|
接口路径、HTTP 方法、请求参数、错误码和既有响应字段均不变。
|
||||||
|
|
||||||
|
## 二、新增响应字段
|
||||||
|
|
||||||
|
以下字段同时新增到:
|
||||||
|
|
||||||
|
- `data.transport.arrive`
|
||||||
|
- `data.transport.depart`
|
||||||
|
- `data.transport.batches[]`
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 可空 | 来源约束 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `transportType` | `String` | 是 | order-v3 大交通计划开放字符串原值 | 当前已知 `FLIGHT`、`TRAIN`、`SELF_DRIVE`、`BUS`、`OTHER`;未来未知非空值也原样透传;只有精确 `SELF_DRIVE` 表示自驾 |
|
||||||
|
| `departStation` | `String` | 是 | order-v3 `departStation` 原值,源字段上限 100 字符 | 同一大交通计划的真实出发站;源未提供时为 `null` |
|
||||||
|
| `arriveStation` | `String` | 是 | order-v3 `arriveStation` 原值,源字段上限 100 字符 | 同一大交通计划的真实到达站;源未提供时为 `null` |
|
||||||
|
|
||||||
|
响应中的 `time` 仍为现有 date-time 字符串或 `null`,本次不改变时间口径。
|
||||||
|
|
||||||
|
## 三、兼容与部分路线语义
|
||||||
|
|
||||||
|
既有 `station` 保留且只作为方向相关的 legacy compatibility 字段:
|
||||||
|
|
||||||
|
- `direction=ARRIVAL`:`station = arriveStation`,它只代表已知到达端;
|
||||||
|
- `direction=DEPARTURE`:`station = departStation`,它只代表已知出发端。
|
||||||
|
|
||||||
|
路线展示只认两个新增端点:
|
||||||
|
|
||||||
|
- 两端都有值:显示 `departStation → arriveStation`;
|
||||||
|
- 只有到达端:显示 `未知 → arriveStation`;
|
||||||
|
- 只有出发端:显示 `departStation → 未知`;
|
||||||
|
- 两端都没有而只有 legacy `station`:仅显示独立字段 `旧数据站点(路线不完整):station`,禁止箭头和完整路线语义。
|
||||||
|
|
||||||
|
禁止把 `station` 放入或复制到任一真实端点。新 `departStation` 或 `arriveStation` 为空时保持缺失,不得根据 `station`、方向、班次号、时间、备注或接送地点推断或补造。
|
||||||
|
|
||||||
|
## 四、交通方式语义
|
||||||
|
|
||||||
|
`transportType` 是 nullable/open `String`,不是 closed enum。当前可观测值与稳定文案为:
|
||||||
|
|
||||||
|
- `FLIGHT`:飞机;
|
||||||
|
- `TRAIN`:火车;
|
||||||
|
- `SELF_DRIVE`:自驾;
|
||||||
|
- `BUS`:大巴;
|
||||||
|
- `OTHER`:其他。
|
||||||
|
|
||||||
|
只有精确 `transportType === "SELF_DRIVE"` 表示自驾。`null` 显示“未提供”;未来未知非空值显示“未知交通方式”并 fail-closed,不得丢弃原值、归并为已知类型,也不得从 `transportNo`、`time`、站点或备注推断交通类型。
|
||||||
|
|
||||||
|
## 五、响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"transport": {
|
||||||
|
"arrive": {
|
||||||
|
"planId": "9007199254740993",
|
||||||
|
"direction": "ARRIVAL",
|
||||||
|
"travelerIds": ["9007199254740995"],
|
||||||
|
"transportType": "FLIGHT",
|
||||||
|
"transportNo": "CA1234",
|
||||||
|
"time": "2026-07-29T10:30:00",
|
||||||
|
"station": "海拉尔东山国际机场",
|
||||||
|
"departStation": "北京首都机场",
|
||||||
|
"arriveStation": "海拉尔东山国际机场"
|
||||||
|
},
|
||||||
|
"depart": null,
|
||||||
|
"batches": [
|
||||||
|
{
|
||||||
|
"planId": "9007199254740997",
|
||||||
|
"direction": "DEPARTURE",
|
||||||
|
"travelerIds": ["9007199254740999"],
|
||||||
|
"transportType": "TRAIN",
|
||||||
|
"transportNo": "G5678",
|
||||||
|
"time": "2026-07-31T17:20:00",
|
||||||
|
"station": "海拉尔站",
|
||||||
|
"departStation": "海拉尔站",
|
||||||
|
"arriveStation": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、前端消费动作
|
||||||
|
|
||||||
|
1. 路线只使用真实 `departStation` 与 `arriveStation`;单端缺失显示明确“未知”,legacy-only `station` 仅显示为独立“旧数据站点(路线不完整)”。
|
||||||
|
2. 仅按权威 `transportType=SELF_DRIVE` 进入自驾展示;`BUS`/`OTHER` 使用稳定文案,`null` 与未知字符串按上节 fail-closed。
|
||||||
|
3. `planId` 与 `travelerIds[]` 均为 JSON `String`,必须端到端保持字符串,禁止转为 JavaScript `Number`;精度安全用例使用示例中的超大 ID。
|
||||||
|
4. 前端已在 `v2.1@f4cac12fff3297fb42e6a217b1764b5408339133` 完成实现与具名 negative tests,`frontend_status` 为 `implemented`;后端未部署前不升级为页面联调 `verified`。
|
||||||
|
5. 领取后按标准状态流转回写 `frontend_owner`、`frontend_ref` 和 `frontend_status`。
|
||||||
|
|
||||||
|
## 七、Shared Java / Internal Feign 影响
|
||||||
|
|
||||||
|
面向前端的公开管理端契约是 `GET /admin/fleet/board/orders/:orderId`。order-v3 producer 与 Fleet consumer 之间另有内部契约 `GET /v3/internal/order/orders/:orderId/transport`,响应共享 Java DTO `OrderTransportForFleetDTO.TransportSegment/TransportBatch`。
|
||||||
|
|
||||||
|
- `transportType`、`departStation`、`arriveStation` 都是 additive nullable/open `String`;旧 consumer 可忽略新增 JSON key,旧 producer 缺 key 时 Fleet 按 `null` 消费。
|
||||||
|
- 滚动发布顺序应先保证 order-v3 producer 兼容,再由 Fleet consumer 使用;不允许把 oasdiff/SCC 的 `not_configured` 写成 PASS。
|
||||||
|
- shared DTO 的非 board 消费者包括 assignment、H5 itinerary、通知快照/模板与 `OrderQueryFacade` 读路径;本次仅增加它们可忽略的字段,不改变其既有行为,也不授权它们推断路线或交通类型。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- blocker focused:30 tests,0 failure/error,4 个无 Docker 条件 skip。
|
||||||
|
- order producer/internal Controller 定向测试:54 tests,0 failure/error/skip。
|
||||||
|
- Fleet consumer/admin Controller 定向测试:65 tests,0 failure/error/skip。
|
||||||
|
- Fleet Spotless:BUILD SUCCESS。
|
||||||
|
- Fleet reactor verify:2730 tests,0 failure,0 error,2 skips。
|
||||||
|
- order-v3 reactor verify:7210 tests,0 failure,0 error,35 skips。
|
||||||
|
- 最终证据索引:`hl-5363-final-ac493-evidence-index.json`,`safe=true`,7 files。
|
||||||
|
- oasdiff:`not_configured`;以字段级源码对比和 Controller JSON 测试作为 fallback。
|
||||||
|
- Spring Cloud Contract:`not_configured`;以 producer/consumer 测试和两个 reactor verify 作为 fallback。
|
||||||
|
- changelog 草稿门禁:仓库单元测试 46/46、文件名校验、path aliases 校验及 workflow `lint --allow-pending` 均通过。
|
||||||
|
|
||||||
|
`--allow-pending` 只证明初始 pending 草稿结构合法,不是发布态 lint 证据。后端已合并但未部署,网关亦未验证,因此 `backend_status` 与 `gateway_status` 保持 `pending`;前端仅以 checkpoint 与已推送业务提交收口为 `implemented`,不表述为已联调或 `verified`。
|
||||||
|
|
||||||
|
## 九、不影响范围
|
||||||
|
|
||||||
|
- 无 DDL、无历史数据迁移。
|
||||||
|
- 不改变派车状态机、候选/占用、费用、保险、Outbox 或消息模板。
|
||||||
|
- 不包含多司机通知/确认、整段资源应用或需求级原子确认。
|
||||||
|
- 不代表 `hl-ui` 已实现、发布或完成页面验证。
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5368"
|
||||||
|
title: "非订单页面统一补齐真实团号与团号搜索"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "verified"
|
||||||
|
frontend_owner: "Pi"
|
||||||
|
frontend_ref: "hl-admin@4c8bdb97c89fa399e5fbb13240ea082f64fefc59"
|
||||||
|
target_release: "v2.1"
|
||||||
|
verified_at: "2026-08-03"
|
||||||
|
status_note: "PR #5386 已合并(dev-v3@07834e4c5);order-v2/order-v3/user/fleet 已部署 TEST(tasks 03b0fecb/2b54e87b/258865cf/d290d667)并经网关验证:profile/orders teamNo=groupCode+keyword 搜索、contract teamNo 过滤分页正确、fleet insurance/board teamNo 返回与查询(orderNo 片段不命中)、chat 会话 teamNo、月度对账 teamNo、雪花 ID JSON String。OpenAPI/oasdiff 与 Spring Cloud Contract 仍 not_configured(人工回退证据见 D:/tmp/hl5368-gateway-evidence/)。管理后台已由 Pi 领取并开始逐页核对真实团号消费。"
|
||||||
|
updated_at: "2026-08-04"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 管理端:非订单页面统一补齐真实团号与团号搜索
|
||||||
|
|
||||||
|
> **服务**: `hl-user-service`、`hl-order-service-v2`、`hl-order-service-v3`、`hl-fleet-service`
|
||||||
|
> **Issue**: [wx/HL#5368](https://git.1814.love:8443/wx/HL/issues/5368)
|
||||||
|
> **前端交接 Issue**: `wx/hl-api-changelog#64`
|
||||||
|
> **日期**: 2026-07-31
|
||||||
|
> **影响范围**: 管理后台工作台、订单列表、合同、评价、车务、房务、会话等订单关联页面
|
||||||
|
> **候选基线**: `dev-v3@07834e4c5`(PR #5386 已合并)
|
||||||
|
> **合并 PR**: [#5386](https://git.1814.love:8443/wx/HL/pulls/5386) head `b65cff23a`,merge commit `07834e4c5`
|
||||||
|
> **后端/网关状态**: backend=deployed,gateway=verified(2026-08-03)
|
||||||
|
|
||||||
|
> **契约裁决(yst 2026-08-03T16:24,承接 #5372)**: `/internal/order/designer-list` 仅扩展 keyword 对真实 `groupCode` 的模糊搜索,保留原响应字段,**不新增 `teamNo`**;对外 `/admin/order/list` 与 `/admin/profile/orders` 返回 `teamNo: String|null`,user-service 在公开边界执行 `teamNo=groupCode`。后端已按此实现并合并。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
1. 非订单管理页面统一新增 canonical 字段 `teamNo: string | null`。它只表示真实团号:
|
||||||
|
- order-v3 来源为 `order_main.team_no`;
|
||||||
|
- order-v2 来源为既有 `groupCode`;
|
||||||
|
- 车务司机险来源为派单时真实团号快照;历史无值保持 `null`。
|
||||||
|
2. **禁止 `teamNo || orderNo` 回退**。`teamNo` 无值时前端统一显示 `-`(或既定空值占位),不得把 `orderNo`、`groupNo` 或其他编号伪装成团号。
|
||||||
|
3. `orderNo` 不删除:仍用于订单管理、内部关联、兼容展示和原有订单号搜索,但它不是团号。
|
||||||
|
4. 所有雪花 ID(例如 `orderId`、`contractId`、`schemeId`、`taskId`、`assignmentId`、`driverId`、`requirementId`、`bizId`)按 JSON String 处理。前端不得转为 JavaScript `Number`,路由、行键和动作请求继续使用这些稳定 ID,不得使用 `teamNo` 替代。
|
||||||
|
5. 本文覆盖并纠正历史 `#5156` changelog 中“团号空值回退订单号”的建议:本次统一口径为**无真实团号即空值,不回退订单号**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、通用字段契约
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 空值 | 用途 | 兼容规则 |
|
||||||
|
|------|-----------|------|------|----------|
|
||||||
|
| `teamNo` | `string \| null` | 未生成、历史无快照或下游降级时为 `null` | 页面团号展示、约定接口的团号搜索 | canonical 新字段;禁止从其他编号推导 |
|
||||||
|
| `orderNo` | `string \| null` | 依原接口 | 订单号展示、订单管理、内部关联及原有搜索 | 保留,不删除;不得作为团号回退 |
|
||||||
|
| `groupCode` | `string \| null` | 依原接口 | order-v2 兼容 | 保留;`teamNo` 的真实值取自该字段,但前端新代码读 `teamNo` |
|
||||||
|
| `groupNo` | `string \| null` | 依原接口 | 司机相关历史兼容 | 保留;司机详情新代码读 `teamNo` |
|
||||||
|
| 各类雪花 ID | `string` 或 `string \| null` | 依业务字段 | 路由、行键、详情和动作接口参数 | 禁止 `Number(id)`、数学运算或以 `teamNo` 替代 |
|
||||||
|
|
||||||
|
前端统一展示示例:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const visibleTeamNo = teamNo?.trim() || '-'
|
||||||
|
// 禁止:teamNo?.trim() || orderNo?.trim() || '-'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
| # | 页面/接口 | 方法与路径 | 请求变化 | 响应变化 |
|
||||||
|
|---|-----------|------------|----------|----------|
|
||||||
|
| 1 | 管理后台工作台 | `GET /admin/profile/dashboard` | 无 | 各角色订单项补 `teamNo` |
|
||||||
|
| 2 | 定制师“我的订单” | `GET /admin/profile/orders` | `keyword` 增加团号模糊匹配 | `data.records[].teamNo` |
|
||||||
|
| 3 | 订单管理列表 | `GET /admin/order/list` | `keyword` 增加团号模糊匹配 | `data.records[].teamNo`;保留 `groupCode` |
|
||||||
|
| 4 | 合同列表 | `GET /v3/admin/contract/list` | 新增可选 query `teamNo`,模糊匹配 | `data.records[].teamNo`;合同/订单/方案 ID 为字符串 |
|
||||||
|
| 5 | 合同详情 | `GET /v3/admin/contract/<id>` | 无 | `data.teamNo`;相关雪花 ID 为字符串 |
|
||||||
|
| 6 | 评价列表 | `GET /v3/admin/review/list` | `keyword` 的 OR 搜索新增团号 | `data.records[].teamNo` |
|
||||||
|
| 7 | 评价详情 | `GET /v3/admin/review/<reviewId>` | 无 | `data.teamNo` |
|
||||||
|
| 8 | 司机险任务 | `GET /admin/fleet/insurance/tasks` | 新增可选 query `teamNo`,仅模糊匹配真实 `team_no` | `data.records[].teamNo`;历史空值为 `null` |
|
||||||
|
| 9 | 司机详情 | `GET /admin/fleet/drivers/<driverId>` | 无 | `data.relatedOrders[].teamNo`;保留 `groupNo` |
|
||||||
|
| 10 | 车务派单看板 | `GET /admin/fleet/board/orders` | 既有 `teamNo` 搜索只匹配真实团号,不再匹配订单号 | 既有 `teamNo` 保持;空值不回退 `orderNo` |
|
||||||
|
| 11 | 车务看板详情 | `GET /admin/fleet/board/orders/<orderId>` | 无 | 既有 `teamNo` 保持;空值不回退 `orderNo` |
|
||||||
|
| 12 | 车务矩阵 | `GET /admin/fleet/matrix/grid` | 团号搜索/展示遵循真实 `teamNo` | `assignments[].teamNo` 不回退 |
|
||||||
|
| 13 | 车务矩阵未派单 | `GET /admin/fleet/matrix/unassigned-orders` | 团号搜索/展示遵循真实 `teamNo` | `data[].teamNo` 不回退 |
|
||||||
|
| 14 | 车务矩阵单日清单 | `GET /admin/fleet/matrix/day-orders` | 团号搜索/展示遵循真实 `teamNo` | `data[].teamNo` 不回退 |
|
||||||
|
| 15 | 会话列表 | `GET /admin/message/chat/conversations` | 无 | `data.records[].teamNo`;`bizId`、`peerAdminId`、`requirementId` 为字符串 |
|
||||||
|
| 16 | 打开会话 | `POST /admin/message/chat/open`、`/open-house`、`/open-house-lead`、`/open-fleet` | 无 | `data.order.teamNo`;订单卡需求 ID 为字符串 |
|
||||||
|
| 17 | 房务选单池 | `GET /v3/admin/order/grab-pool/hotel-requirements` | `keyword` OR 搜索新增真实团号 | `data.records[].teamNo` |
|
||||||
|
| 18 | 房务我的/全部接单 | `GET /v3/admin/order/grab-pool/my-claims/hotel`、`/all-claims/hotel` | `keyword` OR 搜索新增真实团号 | `data.list[].teamNo` |
|
||||||
|
| 19 | 房务待办 | `GET /v3/admin/order/todos` | `keyword` 保留 title/reason OR 语义并增加真实团号 | `data.list[].teamNo` |
|
||||||
|
| 20 | 房务月度对账明细 | `GET /v3/admin/house/reconciliation/monthly/hotel-orders` | 无 | `data[].teamNo` |
|
||||||
|
|
||||||
|
内部 Feign 链路同步透传 `teamNo`,供 `/admin/profile/dashboard` 和管理端会话使用;前端不得直接调用 internal API。
|
||||||
|
|
||||||
|
> **契约裁决(yst 2026-08-03,承接 #5372)**:`GET /internal/order/designer-list` 仅扩展既有 `keyword` 对真实 `groupCode` 的模糊搜索,**保留原响应字段,不新增 `teamNo`**;对外 `/admin/order/list` 与 `/admin/profile/orders` 返回 `teamNo: String|null`,user-service 在公开边界执行 `teamNo = groupCode`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情与搜索口径
|
||||||
|
|
||||||
|
### 1. 工作台与我的订单
|
||||||
|
|
||||||
|
#### `GET /admin/profile/dashboard`
|
||||||
|
|
||||||
|
新增响应字段路径:
|
||||||
|
|
||||||
|
| 角色/区域 | 字段路径 | 类型 |
|
||||||
|
|-----------|----------|------|
|
||||||
|
| 管理员、定制师即将出行 | `data.upcomingTrips[].teamNo` | `string \| null` |
|
||||||
|
| 房务即将入住 | `data.upcomingTrips[].teamNo` | `string \| null` |
|
||||||
|
| 房务待办卡片 | `data.todoCards[].teamNo` | `string \| null` |
|
||||||
|
|
||||||
|
#### `GET /admin/profile/orders`
|
||||||
|
|
||||||
|
- `data.records[].teamNo: string | null`,真实值来自 order-v2 `groupCode`。
|
||||||
|
- `keyword` 的 OR 搜索范围扩展为:订单号、团号、联系人姓名、产品名。
|
||||||
|
- 原响应 `groupCode` 保留兼容。
|
||||||
|
|
||||||
|
### 2. 订单管理列表 `GET /admin/order/list`
|
||||||
|
|
||||||
|
- 新增 `data.records[].teamNo: string | null`,值来自真实 `groupCode`。
|
||||||
|
- 保留 `data.records[].groupCode`。
|
||||||
|
- `keyword` 搜索规则:
|
||||||
|
- 订单号、团号、联系人姓名、产品名:模糊匹配;
|
||||||
|
- 完整手机号:精确匹配;
|
||||||
|
- 各条件保持 OR 语义。
|
||||||
|
|
||||||
|
示例字段值:
|
||||||
|
|
||||||
|
| 字段 | 示例 |
|
||||||
|
|------|------|
|
||||||
|
| `orderId` | `"2045390643479412737"` |
|
||||||
|
| `orderNo` | `"HL20260731123456"` |
|
||||||
|
| `groupCode` | `"26-0801"` |
|
||||||
|
| `teamNo` | `"26-0801"` |
|
||||||
|
|
||||||
|
### 3. 合同
|
||||||
|
|
||||||
|
#### `GET /v3/admin/contract/list`
|
||||||
|
|
||||||
|
新增 query:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 规则 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `teamNo` | `string` | 否 | 模糊匹配 `order_main.team_no`;空白按未传处理 |
|
||||||
|
|
||||||
|
新增响应字段 `data.records[].teamNo: string | null`。
|
||||||
|
|
||||||
|
#### `GET /v3/admin/contract/<id>`
|
||||||
|
|
||||||
|
新增响应字段 `data.teamNo: string | null`。
|
||||||
|
|
||||||
|
下列 ID 以 JSON String 返回:`contractId`、`orderId`、`schemeId`;详情中的 `travelers[].travelerId` 及状态日志中的 `logId`、`contractId` 同样按字符串处理。`schemeId`、`travelerId` 等可空字段保持 `null`。
|
||||||
|
|
||||||
|
#### ⚠️ v1/v3 切换口径(2026-08-04 决策)
|
||||||
|
|
||||||
|
合同页**全量使用 v3 端点**,不再使用 v1:
|
||||||
|
|
||||||
|
- 创建合同:`POST /v3/admin/contract/create`、`POST /v3/admin/contract/create-by-scheme`
|
||||||
|
- 刷新合同状态:`GET /v3/admin/contract/{id}/status`(实测成功)
|
||||||
|
- 列表、详情、作废、下载、重发短信:均走 `/v3/admin/contract/*`
|
||||||
|
|
||||||
|
v1(`/admin/contract/*`)不再用于合同页:
|
||||||
|
|
||||||
|
- v1 存量历史合同(约 20 条,SIGNING/VOIDED)自然消亡,**不迁移、不做兼容转换**;
|
||||||
|
- v1/v3 合同数据隔离不互通:v3 创建的合同 `contractId` 不能调 v1 接口(返回错误码 `510001`);v1 创建的旧合同不会出现在 v3 列表/详情中。
|
||||||
|
|
||||||
|
前端合同页应统一按上述 v3 端点实现,移除 v1 合同调用,不得混用两套接口。
|
||||||
|
|
||||||
|
### 4. 评价
|
||||||
|
|
||||||
|
#### `GET /v3/admin/review/list`
|
||||||
|
|
||||||
|
- 新增 `data.records[].teamNo: string | null`。
|
||||||
|
- `keyword` 保留原 `content`、`userNickname`、`orderNo`、`targetName` 的 OR 语义,并新增真实 `teamNo` 模糊匹配。
|
||||||
|
|
||||||
|
#### `GET /v3/admin/review/<reviewId>`
|
||||||
|
|
||||||
|
新增 `data.teamNo: string | null`。评价与订单等雪花 ID 继续按字符串消费。
|
||||||
|
|
||||||
|
### 5. 司机险任务 `GET /admin/fleet/insurance/tasks`
|
||||||
|
|
||||||
|
新增 query:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 规则 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `teamNo` | `string` | 否 | 最长 64;trim 后模糊匹配任务真实 `team_no`,**不匹配 `order_no`** |
|
||||||
|
|
||||||
|
新增 `data.records[].teamNo: string | null`:
|
||||||
|
|
||||||
|
- 新任务保存派单时的真实团号快照;
|
||||||
|
- 历史任务没有快照时返回 `null`,不伪造、不回填 `orderNo`;
|
||||||
|
- `taskId`、`driverId`、`assignmentId`、`orderId`、`insuranceOrderId`、`handledBy` 均作为 JSON String(可空字段允许 `null`)。
|
||||||
|
|
||||||
|
### 6. 司机详情 `GET /admin/fleet/drivers/<driverId>`
|
||||||
|
|
||||||
|
- `data.relatedOrders[].teamNo: string | null` 为 canonical 团号。
|
||||||
|
- `data.relatedOrders[].groupNo` 保留兼容。
|
||||||
|
- `teamNo` 无真实来源时返回 `null`,不从 `orderNo` 或 `groupNo` 推导。
|
||||||
|
|
||||||
|
### 7. 车务看板与矩阵
|
||||||
|
|
||||||
|
已有 `teamNo` 字段继续使用,但统一收紧:
|
||||||
|
|
||||||
|
- 看板 `teamNo` 查询仅匹配真实团号,不再把 `orderNo` 当团号命中;
|
||||||
|
- 页面可见团号只显示 `teamNo`;空值显示 `-`,不得回退 `orderNo`;
|
||||||
|
- 看板、详情、矩阵的路由、行键、拖拽、派单、改派、取消等动作继续使用 `orderId`、`assignmentId`、`assignmentGroupId` 等字符串 ID;
|
||||||
|
- `orderNo` 可继续在明确标注“订单号”的区域展示,不得标成团号。
|
||||||
|
|
||||||
|
### 8. 管理端会话
|
||||||
|
|
||||||
|
- `GET /admin/message/chat/conversations`:`data.records[].teamNo: string | null`。
|
||||||
|
- 四个打开会话接口:`data.order.teamNo: string | null`。
|
||||||
|
- order-v3 不可达、订单无团号或历史摘要无值时,`teamNo` 返回 `null`,绝不回退 `orderNo`。
|
||||||
|
- `bizId`、`peerAdminId`、`requirementId` 等雪花 ID 作为字符串处理。
|
||||||
|
|
||||||
|
### 9. 房务选单池、我的订单、待办与对账
|
||||||
|
|
||||||
|
- 选单池、我的接单、全部接单的 `keyword` 在原订单号/客人姓名/电话/产品名 OR 搜索基础上增加真实团号模糊匹配。
|
||||||
|
- 待办 `keyword` 保持 `title`/`reason` OR 搜索,并增加按真实团号预解析订单 ID;筛选在分页前生效。
|
||||||
|
- 相关列表项和月度对账酒店订单明细新增 `teamNo: string | null`。
|
||||||
|
- 房务工作台通过 order-v3 → user-service Feign 链路透传同一字段;下游降级时字段保持 `null`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、前端正确调用与展示
|
||||||
|
|
||||||
|
1. 页面展示团号只读 `teamNo`,空值显示 `-`;不要使用 `orderNo`、`groupCode` 或 `groupNo` 做运行时回退。
|
||||||
|
2. 订单管理已有代码可继续使用 `groupCode`,但新改页面统一迁移到 canonical `teamNo`。
|
||||||
|
3. 明确标注“订单号”的区域可以继续显示 `orderNo`;“团号”区域不得混入订单号。
|
||||||
|
4. 搜索框按各接口契约传参:
|
||||||
|
- 合同、司机险使用独立 query `teamNo`;
|
||||||
|
- profile orders、订单列表、评价、房务列表使用原 `keyword`;
|
||||||
|
- 车务看板使用既有 `teamNo` 参数,但其语义已收紧为只查真实团号。
|
||||||
|
5. 所有雪花 ID 从响应到 store、路由参数、表格 row key 和动作 payload 全程保持字符串。禁止 `parseInt`、一元 `+`、`Number()` 或数值排序。
|
||||||
|
6. `frontend_status` 必须保持 `pending`;只有前端按自身流程完成、验证并回填合法引用后才能迁移状态,后端不得代填。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、边界行为
|
||||||
|
|
||||||
|
- 真实团号未生成:`teamNo: null`,页面显示 `-`。
|
||||||
|
- 历史司机险任务没有团号快照:`teamNo: null`,不做订单号回退。
|
||||||
|
- order-v3/Feign 降级:工作台或会话中 `teamNo` 可为 `null`,页面不得报错。
|
||||||
|
- 独立 `teamNo` 参数为空白:按未传处理。
|
||||||
|
- 团号关键词搜索在数据库分页前生效;不得仅过滤当前页。
|
||||||
|
- 原有 `orderNo`、`groupCode`、`groupNo` 及其他响应字段保持兼容。
|
||||||
|
- 本次为只读字段扩展和查询语义扩展,不改变订单状态机、合同状态机、评价审核、房务接单、车务派单和聊天权限。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、展示与分页守恒
|
||||||
|
|
||||||
|
- 同一字符串 `orderId` 在工作台、合同、评价、车务、房务与聊天响应中的非空 `teamNo` 必须一致。
|
||||||
|
- 新字段与搜索扩展不增删业务状态,不改变状态标签、颜色、权限、排序主键或动作参数。
|
||||||
|
- 合同、评价、司机险、房务等分页列表必须在数据库分页前应用团号条件;`total` 与切页结果守恒,禁止仅过滤当前页。
|
||||||
|
- 房务 `todoCards` 与 `upcomingTrips` 中同一订单的 `teamNo` 必须一致;OPEN 聚合与历史分页均遵循相同真实团号来源。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- 不删除 `orderNo`、`groupCode`、`groupNo`。
|
||||||
|
- 不改变任何写接口的稳定 ID 入参。
|
||||||
|
- 不授权前端直连 order-v3 internal API。
|
||||||
|
- 不代表后端已合并、网关已放行、测试服已部署或前端已适配。
|
||||||
|
- `D:/work2/hl-ui` 未修改;前端变更由前端 owner 独立完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证证据(已完成项与剩余门禁)
|
||||||
|
|
||||||
|
- [x] PR #5386 已合并至 `dev-v3@07834e4c5`;合并前独立只读复审 P0/P1=0、唯一 P2(profile/orders 镜像缺 displayStatus/displayStatusLabel/updateTime 透传)已修复并回归。
|
||||||
|
- [x] 全量验证通过:order-v2 3496 tests、order-v3 7272 tests、user 3512 tests、fleet 2887 tests,全 0 failures;fleet spotless:check 通过;`FleetInsuranceTaskTeamNoMysqlTest` 以外部 MySQL 8 实跑 3/3。证据:`D:/tmp/hl5368-fleet-verify-final2.log`、`D:/tmp/hl5368-user-test4.log`。
|
||||||
|
- [x] 4 服务已部署 TEST:order-v2 task `03b0fecb`、order-v3 `2b54e87b`、user `258865cf`、fleet `d290d667`(均 success)。
|
||||||
|
- [x] 网关实测(`api.test.1814.love:9443`):profile/orders 返回 `teamNo=groupCode` 且 keyword 团号搜索 total 正确;`/admin/order/list` 同;contract list 团号过滤分页前生效(精确 total=1/模糊 total=2)且 detail 返回 teamNo;fleet insurance tasks 返回并可按 teamNo 查询(命中 4/不存在 0);fleet board teamNo 搜索仅匹配真实团号(orderNo 片段不命中);chat 订单会话返回 teamNo、非订单会话 null;house 月度对账返回 teamNo;所有雪花 ID 均 JSON String;同一订单 2079576729147338754 在 contract 与月度对账均返回 `26-4165`。证据:`D:/tmp/hl5368-gateway-evidence/gateway-evidence.md`。
|
||||||
|
- [ ] OpenAPI/oasdiff 与 Spring Cloud Contract 仍未配置(`not_configured`);已用上述生产者/消费者 JUnit 与真实网关 HTTP 作为人工回退证据。
|
||||||
|
- [ ] 测试环境 review 列表与 grab-pool 无业务数据(total=0),团号 OR 搜索由 Mapper 单测覆盖,未做真数据命中验证。
|
||||||
|
- [ ] 前端完成展示、搜索、路由与动作回归后,由前端 owner 更新 `frontend_status`;当前保持 `pending`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、相关文档
|
||||||
|
|
||||||
|
- 后端 Issue: [wx/HL#5368](https://git.1814.love:8443/wx/HL/issues/5368)
|
||||||
|
- 后端 Draft PR: [wx/HL#5386](https://git.1814.love:8443/wx/HL/pulls/5386)
|
||||||
|
- 前端交接 Issue: `wx/hl-api-changelog#64`
|
||||||
|
- 历史车务契约: `changelogs-v2/2026-07/85_5156_车务看板与矩阵主标识显示团号-前端待处理-管理后台.md`(其中团号空值回退订单号的建议被本次口径取代)
|
||||||
@ -0,0 +1,238 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v1"
|
||||||
|
ticket: "5131"
|
||||||
|
title: "车队独立管理及车队字典下线"
|
||||||
|
consumer: "admin"
|
||||||
|
backend: "verified"
|
||||||
|
gateway: "verified"
|
||||||
|
frontend: "implemented"
|
||||||
|
frontend_status: "implemented"
|
||||||
|
frontend_owner: "hl-ui-codex"
|
||||||
|
frontend_ref: "mmg/hl-ui@ac4d5fd6292b13eb504f5393dfe07781800b9233"
|
||||||
|
updated_at: "2026-07-25T01:18:23.686Z"
|
||||||
|
base: "dev-v3"
|
||||||
|
generated: "2026-07-22T10:46:00+08:00"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 【新增接口·修改接口·前端需联调·管理后台/H5】车队独立管理及车队字典下线
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service + hl-user-service
|
||||||
|
> **日期**: 2026-07-22
|
||||||
|
> **工单**: #5131
|
||||||
|
> **影响范围**: 车队管理、车辆档案、司机 H5、自带车审核、派车候选、矩阵、车队对账
|
||||||
|
|
||||||
|
## 关键变化
|
||||||
|
|
||||||
|
`fleet_attribution` 不再是车队数据源。后端新增 `fleet_team` 主数据,统一维护:
|
||||||
|
|
||||||
|
- `teamName`:车队名称。
|
||||||
|
- `teamType`:`SELF_OPERATED` 自有 / `COOPERATIVE` 合作。
|
||||||
|
- `leaderName`、`leaderPhone`:负责人及电话;列表电话脱敏,详情返回编辑原值。
|
||||||
|
- `settleType`:直接复用资源付款方式 `resource_settle_type`,当前值为 `cash` / `sign` / `company`。
|
||||||
|
- `status`:`ACTIVE` / `DISABLED`。
|
||||||
|
|
||||||
|
车辆及相关链路以 `fleetTeamId` 为权威关联。旧 `fleet` 稳定编码仅在客户端切换期保留兼容,不得再用于生成选项或写死 `own/coopA/coopB`。
|
||||||
|
|
||||||
|
## 变更接口
|
||||||
|
|
||||||
|
### 车队管理
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| GET | `/admin/fleet/teams/page` | 分页;支持 `keyword/teamType/status/settleType` |
|
||||||
|
| GET | `/admin/fleet/teams/options` | 有效车队下拉;编辑存量时可传 `includeDisabledId` 回显当前停用车队 |
|
||||||
|
| GET | `/admin/fleet/teams/:fleetTeamId` | 详情;负责人电话返回原值供编辑 |
|
||||||
|
| POST | `/admin/fleet/teams` | 新增 |
|
||||||
|
| PUT | `/admin/fleet/teams/:fleetTeamId` | 编辑 |
|
||||||
|
| DELETE | `/admin/fleet/teams/:fleetTeamId` | 删除;仅名下无车辆且无未完结司机自助录入时允许 |
|
||||||
|
| POST | `/admin/fleet/teams/:fleetTeamId/disable` | 停用;仍有在役车辆返回 `601103` |
|
||||||
|
| POST | `/admin/fleet/teams/:fleetTeamId/enable` | 启用 |
|
||||||
|
|
||||||
|
保存请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"teamName": "合作车队一队",
|
||||||
|
"teamType": "COOPERATIVE",
|
||||||
|
"leaderName": "张三",
|
||||||
|
"leaderPhone": "13800138000",
|
||||||
|
"settleType": "sign",
|
||||||
|
"sortOrder": 20,
|
||||||
|
"remark": "旺季合作车队"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
下拉响应项:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fleetTeamId": "2080000000000000001",
|
||||||
|
"teamCode": "ft_fsq1ab23cd",
|
||||||
|
"teamName": "合作车队一队",
|
||||||
|
"teamType": "COOPERATIVE",
|
||||||
|
"settleType": "sign",
|
||||||
|
"status": "ACTIVE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
雪花 ID 一律按字符串处理,禁止 `Number()` / `parseInt()`。
|
||||||
|
|
||||||
|
## 修改接口
|
||||||
|
|
||||||
|
### 车辆档案
|
||||||
|
|
||||||
|
- `POST /admin/fleet/vehicles`、`PUT /admin/fleet/vehicles/:id`:新增 `fleetTeamId`,新前端必传。
|
||||||
|
- `GET /admin/fleet/vehicles/page`:新增筛选参数 `fleetTeamId`;列表项新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||||
|
- `GET /admin/fleet/vehicles/:id`:详情新增同上字段。
|
||||||
|
- 车辆导入模板把车队列改为“车队名称”,填写独立车队管理中的有效名称;历史表头和稳定编码仍兼容。
|
||||||
|
|
||||||
|
### 司机 H5 与审核
|
||||||
|
|
||||||
|
- `GET /app/h5/driver-onboard/init`:链接可编辑时新增 `fleetTeamOptions[]`,只包含 `fleetTeamId/teamName/teamType`,不暴露负责人和结算资料;续签会额外包含当前已停用车队用于原值回显。
|
||||||
|
- `SubmitVehicleVO`、续签常驻车回显新增 `fleetTeamId`。
|
||||||
|
- 待审核详情 `vehicle`、审核通过请求 `ownVehicle` 新增 `fleetTeamId`。
|
||||||
|
- H5 和管理端都必须提交 ID;旧 `fleet` 仅兼容已打开的旧页面。
|
||||||
|
|
||||||
|
### 派车候选与矩阵
|
||||||
|
|
||||||
|
- 派车车辆候选新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||||
|
- `GET /admin/fleet/board/orders` 已派车辆新增 `currentVehicleFleetTeamId/currentVehicleFleetTeamName/currentVehicleFleetTeamType/currentVehicleFleetTeamSettleType`。
|
||||||
|
- `GET /admin/fleet/matrix/grid` 新增 `fleetTeamIds[]`;`fleets[]` 废弃。
|
||||||
|
- 矩阵车辆行新增 `fleetTeamId/fleetTeamName/fleetType/settleType`。
|
||||||
|
- 响应新增 `fleetTeamCounts[]`,每项包含 `fleetTeamId/teamName/count`;`fleetCount` 仅过渡兼容。
|
||||||
|
|
||||||
|
### 对账
|
||||||
|
|
||||||
|
- 车费车队分组新增 `fleetTeamId/fleetType/settleType`,名称使用对账快照。
|
||||||
|
- `GET /admin/fleet/reconciliation/cars` 与 CSV 导出新增 `fleetTeamIds[]`;传入后优先于旧 `fleets[]`。
|
||||||
|
- 保险车队分组新增 `fleetTeamId/fleetName/fleetType/settleType`。
|
||||||
|
- 实际结算保存新增 `fleetTeamId`;旧 `fleet` 废弃。
|
||||||
|
- 后端按车队类型派生 `OWN_COST/COOP_QUOTE`,不再把 `own` 当特殊业务编码。
|
||||||
|
|
||||||
|
历史字典迁入的车队可能没有负责人资料。新增、编辑请求中的 `leaderName`、`leaderPhone`、
|
||||||
|
`settleType`、`sortOrder` 均为必填;前端编辑存量车队时必须提示车务人员补录真实资料,禁止用占位姓名或虚假电话自动填充。
|
||||||
|
|
||||||
|
## 独立菜单与权限
|
||||||
|
|
||||||
|
user-service 在“车务管理”目录下新增子菜单:
|
||||||
|
|
||||||
|
- 路由:`/fleet/teams`
|
||||||
|
- 组件:`fleet/teams/index`
|
||||||
|
- 权限:`fleet:team:list`、`fleet:team:create`、`fleet:team:update`、`fleet:team:status`、`fleet:team:delete`
|
||||||
|
- 默认角色:`SUPER_ADMIN`、`ADMIN`、`VEHICLE_MANAGER`
|
||||||
|
|
||||||
|
前端必须新增对应组件,否则菜单发布后会出现空路由。
|
||||||
|
|
||||||
|
## 前端必须修改的范围
|
||||||
|
|
||||||
|
### 2026-07-24 页面复测反馈:列表列宽与暗色模式
|
||||||
|
|
||||||
|
测试环境 `/fleet/teams` 页面已经能展示负责人和脱敏电话,但当前样式仍需前端修正,本反馈不涉及后端接口或字段变化:
|
||||||
|
|
||||||
|
1. 表格列宽分配失衡。“车队名称”列占用过多空白,把“负责人 / 负责人电话”等核心联系人信息推到页面右侧,首屏信息密度过低。
|
||||||
|
2. 暗色模式不能只替换页面背景。当前筛选区、表头、行分隔线、空值、状态标签和操作区的层级与对比度不足,部分边界难以辨认。
|
||||||
|
3. 样式必须复用项目主题 token;禁止在本页写死仅适用于浅色模式的背景色、文字色或边框色。负责人电话仍只展示接口返回的脱敏值,样式调整不得绕过脱敏。
|
||||||
|
|
||||||
|
#### 展示矩阵
|
||||||
|
|
||||||
|
| 视口 / 主题 | 车队名称 | 负责人 / 负责人电话 | 其他列 | 验收表现 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `>= 1440px`,浅色 | 弹性列,限制最大占比;超长省略并可查看完整名称 | 建议分别保留约 `120px / 140px`,左对齐 | 类型、付款方式、数量、排序、状态和操作按内容定宽 | 联系人紧邻业务字段,首屏无大段无意义空白 |
|
||||||
|
| `>= 1440px`,暗色 | 同浅色列宽规则 | 同浅色列宽规则 | 使用暗色主题 token | 页面、筛选区、表头、数据行、状态标签和操作区层级清楚 |
|
||||||
|
| `1024px - 1439px`,浅色/暗色 | 优先收缩并省略,不能无限占宽 | 不压缩为空或挤出主要阅读区 | 保留操作列可用宽度 | 联系人信息仍可直接阅读 |
|
||||||
|
| `< 1024px`,浅色/暗色 | 设置表格最小宽度 | 保持可读宽度 | 允许横向滚动 | 不通过隐藏关键列或强行挤压完成适配 |
|
||||||
|
|
||||||
|
空负责人和空电话统一显示 `—`。联系人文本左对齐;车辆数、排序、状态和操作居中。浅色与暗色模式都必须覆盖默认、悬停、聚焦、禁用和空数据状态;普通文本与背景建议至少达到 `4.5:1` 对比度,控件边界和状态提示应清晰可辨。
|
||||||
|
|
||||||
|
### 管理后台
|
||||||
|
|
||||||
|
1. 新增 `src/api/fleet/teams.js` 和 `src/views/fleet/teams/index.vue`,完成车队分页、新增、编辑、启停和删除:
|
||||||
|
- “车队管理”必须显示在“车务管理”目录内,不得作为一级菜单处理。
|
||||||
|
- 按上面的展示矩阵修正表格列宽和明暗主题样式,不能让“车队名称”列挤占联系人信息区域。
|
||||||
|
- 仅 `vehicleCount === 0` 时展示/启用删除动作;调用删除接口后刷新列表。
|
||||||
|
- 后端仍会独立校验车辆及未完结司机录入关联,返回 `601107` 时提示“请先完成车辆/司机转移”。
|
||||||
|
- 编辑历史迁入车队时补齐负责人、负责人电话、付款方式和排序。
|
||||||
|
2. 车辆档案:
|
||||||
|
- `src/views/fleet/vehicles/index.vue`
|
||||||
|
- `src/views/fleet/vehicles/components/VehicleEditModal.vue`
|
||||||
|
- `src/api/fleet/vehicles.js`
|
||||||
|
使用 `/admin/fleet/teams/options`,表单和筛选绑定 `fleetTeamId`,展示 `fleetTeamName`。
|
||||||
|
3. 自带车审核和车辆选择:
|
||||||
|
- `src/views/fleet/drivers/pending/index.vue`
|
||||||
|
- `src/views/fleet/drivers/components/VehiclePickerModal.vue`
|
||||||
|
- `src/api/fleet/drivers.js`
|
||||||
|
不再读取 `fleet_attribution`。
|
||||||
|
4. 派车看板、矩阵和共享甘特:删除 `own/coopA/coopB` 固定数组和固定颜色映射,按 API 返回的 ID/名称动态分组。涉及:
|
||||||
|
- `src/views/fleet/board/composables/useVehicleDriverPicker.js`
|
||||||
|
- `src/views/fleet/board/components/VehiclePickerList.vue`
|
||||||
|
- `src/views/fleet/matrix/**`
|
||||||
|
- `src/views/fleet/_shared/fleetDisplay.js`
|
||||||
|
- `src/views/fleet/_shared/gantt/**`
|
||||||
|
5. 车队对账:`src/views/fleet/recon/**` 删除三车队固定循环、固定展开状态和固定 CSV 顺序;实际结算提交 `fleetTeamId`。
|
||||||
|
|
||||||
|
动态车队颜色可由 `fleetTeamId` 做稳定哈希映射,但不得用数组下标产生每次刷新变化的颜色。
|
||||||
|
|
||||||
|
### 司机 H5
|
||||||
|
|
||||||
|
以下文件把硬编码 `<option value="own/coopA/coopB">` 改为初始化响应的 `fleetTeamOptions`,提交 `fleetTeamId`:
|
||||||
|
|
||||||
|
- `src/views/h5/driver-intake/DriverIntakeForm.vue`
|
||||||
|
- `src/views/h5/driver-intake/composables/useIntakeForm.js`
|
||||||
|
- `src/views/h5/driver-intake/composables/useRenewPrefill.js`
|
||||||
|
- `src/views/h5/driver-intake/steps/StepVehicleReg.vue`
|
||||||
|
- `src/views/h5/driver-intake/steps/RenewUpdate.vue`
|
||||||
|
|
||||||
|
## 删除字典与发布顺序
|
||||||
|
|
||||||
|
user-service 迁移会精确删除:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DELETE FROM sys_dict_data WHERE dict_type = 'fleet_attribution';
|
||||||
|
DELETE FROM sys_dict_type WHERE dict_type = 'fleet_attribution';
|
||||||
|
```
|
||||||
|
|
||||||
|
必须按以下顺序发布,禁止先删字典:
|
||||||
|
|
||||||
|
1. 发布 `hl-fleet-service`,完成 `fleet_team` 建表、存量回填和兼容接口上线。
|
||||||
|
2. 发布已完成本清单的 `hl-ui`,确认车辆、审核、H5、矩阵和对账不再读取该字典。
|
||||||
|
3. 最后发布 `hl-user-service`,新增独立菜单并删除字典。
|
||||||
|
|
||||||
|
若环境中曾在字典里新增但从未被车辆、待审核或对账引用的车队,发布前需先在独立车队管理中补建;迁移会自动收集所有已有业务引用编码,但不会跨服务读取未使用的字典配置。
|
||||||
|
|
||||||
|
## 兼容与业务规则
|
||||||
|
|
||||||
|
- 车队名称唯一;内部 `teamCode` 创建后不可修改。
|
||||||
|
- 车队已关联车辆后不能切换自有/合作类型,防止历史结算语义漂移。
|
||||||
|
- 停用车队不出现在普通下拉;存量车辆编辑可回显当前停用车队,但不能切入其他停用车队。
|
||||||
|
- 车队下仍有 `ACTIVE` 车辆时禁止停用,须先转移或停用车辆。
|
||||||
|
- 车队只有在名下无车辆、无未完结司机自助录入时才能删除;正式司机通过常驻车辆归属,车辆未转移时删除同样会被拒绝(`601107`)。
|
||||||
|
- 停用车队的存量车辆不得恢复在役,也不会进入派车候选或矩阵。
|
||||||
|
- 对账保存车队名称、类型和付款方式快照,后续改主档不修改历史账期。
|
||||||
|
- 负责人电话属于敏感信息,列表只展示脱敏值,不得写日志或进入前端埋点。
|
||||||
|
|
||||||
|
## 验收清单
|
||||||
|
|
||||||
|
- [ ] 独立车队菜单可分页、新增、编辑、启停,付款方式与资源页选项一致。
|
||||||
|
- [ ] `/fleet/teams` 在桌面端不再由“车队名称”列制造大段空白,负责人和脱敏电话位于首屏连续阅读区;窄屏按展示矩阵滚动而不是隐藏或挤压关键列。
|
||||||
|
- [ ] `/fleet/teams` 的浅色、暗色模式均使用主题 token,筛选区、表头、数据行、空值、状态标签和操作区在默认/悬停/聚焦/禁用状态下层级清晰。
|
||||||
|
- [ ] “车队管理”位于“车务管理”目录下;空车队可删除,非空车队删除入口禁用或明确提示后端 `601107`。
|
||||||
|
- [ ] 历史迁入车队可通过编辑补齐负责人、负责人电话、付款方式和排序,保存时不允许提交空资料。
|
||||||
|
- [ ] 车辆新增/编辑/筛选/详情/导入均使用动态车队,不再出现固定三项。
|
||||||
|
- [ ] 司机 H5 新招、续签和管理端自带车审核均可选择动态车队并正确回显。
|
||||||
|
- [ ] 派车候选、矩阵、甘特和对账能展示任意新增车队,颜色和分组稳定。
|
||||||
|
- [ ] 全前端搜索不到 `fleet_attribution` 运行时读取,也没有业务代码写死 `own/coopA/coopB` 车队集合。
|
||||||
|
- [ ] 按发布顺序上线后,删除字典不会导致下拉为空、标签显示编码或请求失败。
|
||||||
|
- [ ] 雪花 ID 全程按字符串处理,负责人电话未出现在日志、埋点或列表明文。
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- `mvn -pl hl-fleet-service -am -DskipTests compile`:通过。
|
||||||
|
- 受影响链路 12 个测试类定向执行:388 项通过,0 failure,0 error。
|
||||||
|
- user-service 菜单迁移审计:1 项通过,0 failure,0 error。
|
||||||
|
- `mvn -pl hl-user-service,hl-fleet-service -am test`:通过。
|
||||||
|
- `mvn -pl hl-fleet-service -am verify`:通过;fleet 绑定的 `spotless:check` 同步通过。
|
||||||
|
- 测试环境已部署 `hl-fleet-service@feat/fleet-team-management`,8087/8187 双实例健康。
|
||||||
|
- 测试网关只读实测:车队分页、有效车队下拉、车辆分页均 HTTP/业务码 200;动态车队字段齐全,负责人电话列表脱敏。
|
||||||
|
- 前端页面联调及 `hl-user-service` 菜单/删字典迁移:待前端完成动态车队与独立菜单页面后按发布顺序执行。
|
||||||
|
|
||||||
|
> 本文是前端接入通知,不代表已修改或发布 `mmg/hl-ui`。
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# 【行为变更·管理后台】订单调整保留配房与房务驳回定制师待办(#4907)
|
# 【行为变更·管理后台】订单调整保留配房与房务驳回定制师待办(#4907)
|
||||||
|
|
||||||
> 2026-07-14 最终状态:#4907 后端链路已完成并关闭;最终全量复测与前端接入总览见 `57_房务全量API复测与前端最终接入核对-管理后台.md`。前端页面验收属于独立交付,不作为后端工单关单门禁。
|
> 2026-07-16 最终后端状态:零配房最终确认动作契约已由 PR #5014 补齐并合入 `dev-v3`。最新代码、部署、网关 API、DB/库存和日志证据均通过;前端页面实现属于独立交付,不作为后端工单关单门禁。
|
||||||
|
|
||||||
> 服务:`hl-order-service-v3`
|
> 服务:`hl-order-service-v3`
|
||||||
>
|
>
|
||||||
@ -8,9 +8,9 @@
|
|||||||
>
|
>
|
||||||
> 接口结构:新增“单条配房晚次与资源原子调整”接口;其余沿用订单调整、房务详情、最终确认和房务驳回现有接口
|
> 接口结构:新增“单条配房晚次与资源原子调整”接口;其余沿用订单调整、房务详情、最终确认和房务驳回现有接口
|
||||||
>
|
>
|
||||||
> 后端状态:PR [#4915](https://git.1814.love:8443/wx/HL/pulls/4915)、[#4925](https://git.1814.love:8443/wx/HL/pulls/4925)、[#4926](https://git.1814.love:8443/wx/HL/pulls/4926)、[#4928](https://git.1814.love:8443/wx/HL/pulls/4928)、[#4931](https://git.1814.love:8443/wx/HL/pulls/4931) 已合并;最新测试环境部署任务 `eb216570` 成功,`8086/8186` 双实例 UP
|
> 后端状态:既有 PR [#4915](https://git.1814.love:8443/wx/HL/pulls/4915)、[#4925](https://git.1814.love:8443/wx/HL/pulls/4925)、[#4926](https://git.1814.love:8443/wx/HL/pulls/4926)、[#4928](https://git.1814.love:8443/wx/HL/pulls/4928)、[#4931](https://git.1814.love:8443/wx/HL/pulls/4931) 与最新 PR [#5014](https://git.1814.love:8443/wx/HL/pulls/5014) 均已合并;最终验证基线 `dev-v3@d54435af7`,测试环境部署任务 `86d9bf11` 成功,`8086/8186` 双实例 UP
|
||||||
>
|
>
|
||||||
> 联调证据:2026-07-12 最终网关四场景探针通过,覆盖连续改需求、跨晚次原子移动、最终确认、供应商驳回、订单取消、库存迁移与库存不足补偿;报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260712-212622.json` 为 `ok=true`
|
> 联调证据:2026-07-16 部署后重新使用隔离订单执行接口面、缺口流程、订单日志和调整闭环四组探针,全部通过;最终调整报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260716-184129.json` 为 `ok=true`
|
||||||
|
|
||||||
## 0. 2026-07-12 追加:返工标签唯一口径与前端未完成项
|
## 0. 2026-07-12 追加:返工标签唯一口径与前端未完成项
|
||||||
|
|
||||||
@ -202,6 +202,29 @@ POST /admin/house/assignments/requirements/{requirementId}/finalize
|
|||||||
|
|
||||||
前端收到该错误后保留当前配房数据,提示房务逐日处理;不得清空页面状态或隐藏超出新行程的旧配房。
|
前端收到该错误后保留当前配房数据,提示房务逐日处理;不得清空页面状态或隐藏超出新行程的旧配房。
|
||||||
|
|
||||||
|
### 2.1 零配房动作契约
|
||||||
|
|
||||||
|
详情接口与最终确认写接口现已使用同一业务口径:
|
||||||
|
|
||||||
|
- 当前生效需求由当前房务持有。
|
||||||
|
- `houseStatus=CLAIMING`。
|
||||||
|
- 没有任何配房记录。
|
||||||
|
- 没有未闭环询房。
|
||||||
|
|
||||||
|
满足以上条件时,房务详情返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"actions": {
|
||||||
|
"canFinalize": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
以下场景仍保持禁用:存在部分配房、存在未闭环询房、非当前持有人、非当前生效需求或需求已经完成。最终确认成功后必须重新加载详情和待办;后端会关闭相关待办且不会创建配房或变更库存。
|
||||||
|
|
||||||
## 3. 房务驳回后的定制师待办
|
## 3. 房务驳回后的定制师待办
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@ -247,10 +270,14 @@ Content-Type: application/json
|
|||||||
|
|
||||||
## 5. 后端验证证据
|
## 5. 后端验证证据
|
||||||
|
|
||||||
- PR:`wx/HL#4910/#4911/#4913/#4915/#4925/#4926/#4928/#4931`
|
- PR:`wx/HL#4910/#4911/#4913/#4915/#4925/#4926/#4928/#4931/#5014`
|
||||||
- 最新测试环境部署任务:`eb216570`,`8086/8186` 双实例均 UP
|
- 最终验证基线:`dev-v3@d54435af7`
|
||||||
- 定向测试:276 项通过;模块全量 5523 项仅复现 clean baseline 的 3 失败 + 2 错误,无新增回归
|
- 模块全量:5609 项测试,0 failure,0 error,15 skipped,`BUILD SUCCESS`
|
||||||
- 最终网关全流程报告:`D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260712-212622.json`,四场景全部 `ok=true`
|
- 最新测试环境部署任务:`86d9bf11`,`8086/8186` 双实例均 UP
|
||||||
- 实测通过:增晚、减晚、人数变化、连续调整新旧待办替代、改期+增晚同次提交、酒店/房型/房间数替换、入住日期对齐、人工清空、零配房最终确认、供应商驳回、订单取消、库存成功迁移、库存不足整单回滚
|
- 网关接口面:68/68 通过,报告 `D:/work2/HL-v3/.tmp/house-api-surface-probe-20260716-182908.json`
|
||||||
- 返工标签:`REQUIREMENT_ADJUSTED` 压制同需求 `PENDING_ARRANGE`;最终确认、供应商驳回、订单取消后统计均从 1 回到 0
|
- 缺口流程:3/3 通过,报告 `D:/work2/HL-v3/.tmp/house-api-gap-flow-probe-20260716-183121.json`
|
||||||
|
- 订单日志:3/3 通过,报告 `D:/work2/HL-v3/.tmp/house-order-log-flow-probe-20260716-183224.json`
|
||||||
|
- 调整闭环:4/4 通过,报告 `D:/work2/HL-v3/.tmp/house-adjustment-flow-probe-20260716-184129.json`
|
||||||
|
- 实测通过:增晚、减晚、人数变化、连续调整新旧待办替代、改期+增晚、跨晚次原子调整、酒店/房型/房间数替换、入住日期对齐、人工清空、零配房最终确认、供应商驳回、订单取消、库存成功迁移、库存不足整单回滚
|
||||||
|
- 返工标签:`REQUIREMENT_ADJUSTED` 压制同需求 `PENDING_ARRANGE`;最终确认、供应商驳回、订单取消后返工待办均正确关闭
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,273 @@
|
|||||||
|
# 【前端对接·管理后台】车务需求级派单完成回调与滚动发布契约
|
||||||
|
|
||||||
|
> Issue: [wx/HL#4935](https://git.1814.love:8443/wx/HL/issues/4935)
|
||||||
|
>
|
||||||
|
> PR: [wx/HL#4994](https://git.1814.love:8443/wx/HL/pulls/4994)、[wx/HL#5009](https://git.1814.love:8443/wx/HL/pulls/5009)
|
||||||
|
>
|
||||||
|
> 服务: `hl-fleet-service` / `hl-order-service-v3`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-16
|
||||||
|
>
|
||||||
|
> 影响范围: 车务派单完成、用车需求驳回、订单资源状态、看板刷新与部署兼容
|
||||||
|
|
||||||
|
## 一、关键纠正
|
||||||
|
|
||||||
|
此前链路可能在单个日期或单辆车派定后提前把整个用车需求写成 `DONE`。本次改为:
|
||||||
|
|
||||||
|
- 一个用车需求只做一次最终完成回调。
|
||||||
|
- 只有全部服务日期、全部车型项均已生成有效派单,并且每条逐日配置同时绑定车辆和司机,后端才允许整个需求完成。
|
||||||
|
- 前端不得根据“某一天已派”“某一辆车已派”自行把需求或订单资源节点标成完成。
|
||||||
|
- 前端继续直接使用看板/详情接口返回的状态、文案和能力字段,不维护独立状态映射。
|
||||||
|
|
||||||
|
## 二、前端接口结论
|
||||||
|
|
||||||
|
本次不新增前端调用接口,管理后台继续使用:
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 前端用途 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 派单看板汇总 | GET | `/admin/fleet/board/summary` | 状态选项、文案、数量 |
|
||||||
|
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 分页卡片、状态与能力字段 |
|
||||||
|
| 派单看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 当前需求、逐日行程、当前派单 |
|
||||||
|
| 派单时间线 | GET | `/admin/fleet/board/orders/{orderId}/timeline` | 已发生操作记录 |
|
||||||
|
|
||||||
|
前端处理规则:
|
||||||
|
|
||||||
|
1. 状态筛选使用 `summary.statusOptions`,卡片文案使用 `assignmentStatusLabel`。
|
||||||
|
2. 派车入口只看 `canAssign`,驳回入口只看 `canRejectRequirement`。
|
||||||
|
3. 派单、驳回或重试成功后重新请求汇总、列表和当前详情,不能只在本地改一张卡片。
|
||||||
|
4. 同一需求仍有未完成日期或其他车辆项时,后端保持进行中;前端不得提前展示“已完成”。
|
||||||
|
5. 后端部署开关关闭期间,需求级完成/驳回事件会保留待重放;前端不需要轮询内部 Outbox,也不得调用内部回调。
|
||||||
|
|
||||||
|
## 三、管理后台响应示例
|
||||||
|
|
||||||
|
### 3.1 仍有未完成配置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"assignmentStatus": "holding",
|
||||||
|
"assignmentStatusLabel": "排车中",
|
||||||
|
"canAssign": true,
|
||||||
|
"canRejectRequirement": false,
|
||||||
|
"currentAssignment": {
|
||||||
|
"requirementId": "2075001000000000001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
该响应只表示需求仍在处理,不能因 `currentAssignment` 非空推断整个需求已完成。
|
||||||
|
|
||||||
|
### 3.2 整个需求完成后
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"assignmentStatusLabel": "已派车",
|
||||||
|
"canAssign": false,
|
||||||
|
"canRejectRequirement": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际字段以看板接口当前 OpenAPI 为准;状态中文和能力判断均由后端返回。
|
||||||
|
|
||||||
|
## 四、内部回调契约
|
||||||
|
|
||||||
|
> 本节供后端与 QA 验收。以下 `/v3/internal/**` 接口不经过管理后台,不配置公网网关路由,前端禁止调用。
|
||||||
|
|
||||||
|
### 4.1 最终完成回调
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/internal/order/vehicle-assignment/callback
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2074746808742928386",
|
||||||
|
"requirementId": "2075001000000000001",
|
||||||
|
"vehicleId": "2076001000000000001",
|
||||||
|
"vehicleType": "suv",
|
||||||
|
"vehicleCount": 1,
|
||||||
|
"licensePlate": "蒙A12345",
|
||||||
|
"brand": "丰田汉兰达",
|
||||||
|
"seats": 7,
|
||||||
|
"plannedDailyFee": "1300.00",
|
||||||
|
"dailyFeeSource": "PRICE_CALENDAR",
|
||||||
|
"driverStaffId": "2077001000000000001",
|
||||||
|
"driverName": "测试司机",
|
||||||
|
"driverPhone": "13800000000",
|
||||||
|
"topologyFingerprint": "<64位 SHA-256 摘要>",
|
||||||
|
"remark": "需求级最终派单快照"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
新版 Fleet 必填字段:`orderId`、`requirementId`、`vehicleId`、`vehicleType`、`vehicleCount`、`topologyFingerprint`。其中 `topologyFingerprint` 是 Fleet 根据该需求全部有效逐日派单生成的 64 位 SHA-256 摘要;其余快照字段允许为空,后端不得伪造车牌、品牌、座位、价格或司机信息。
|
||||||
|
|
||||||
|
`vehicleId`、车牌和司机字段是稳定排序后的代表派单,`vehicleCount` 是该需求实际车辆组总数。完整逐日、多车辆和多司机拓扑仍以 Fleet 派单明细为准,不能从该轻量快照反推完整派车表。
|
||||||
|
|
||||||
|
滚动发布期间,旧版 Fleet 不传 `topologyFingerprint` 时,Order-v3 会根据完整回调快照生成 `legacy:` 前缀摘要并持久化。该兼容仅用于先升级 Order-v3、后升级 Fleet 的过渡期;新版 Fleet 仍必须发送摘要。
|
||||||
|
|
||||||
|
### 4.2 轻量进度回写
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/internal/order/orders/{orderId}/requirement/vehicle/status?requirementId={requirementId}&status=PROCESSING
|
||||||
|
```
|
||||||
|
|
||||||
|
该接口只允许 `PROCESSING`。`DONE` 必须走最终完成回调并冻结快照。
|
||||||
|
|
||||||
|
### 4.3 驳回回写
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/internal/order/orders/{orderId}/requirement/vehicle/reject
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requirementId": "2075001000000000001",
|
||||||
|
"returnRemark": "车型需求不完整,请定制师补充",
|
||||||
|
"operatorId": "2078001000000000001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
只有当前需求不存在 `holding/assigned` 有效派单时才允许驳回。
|
||||||
|
|
||||||
|
## 五、状态、幂等和错误分支
|
||||||
|
|
||||||
|
| 场景 | 结果 | 副作用 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PENDING/PROCESSING` 且无快照 | 原子写快照并完成需求 | 同事务写需求 `DONE`、订单车辆状态 `DONE`、待办/日志并尝试推进订单 |
|
||||||
|
| `DONE` 且摘要相同 | 幂等成功 | 不加需求写锁、不更新 `update_time`,不重复同步司机、待办、日志或推进订单 |
|
||||||
|
| `DONE` 且摘要变化 | 刷新轻量快照 | 只更新同一需求快照和司机信息,不重复推进订单、待办或时间线 |
|
||||||
|
| 旧版回调未传摘要 | 兼容成功 | Order-v3 生成稳定 `legacy:` 摘要;相同旧请求重放仍为零写入 |
|
||||||
|
| `DONE` 但无快照 | 返回 `582081` | 禁止补造快照,禁止继续副作用 |
|
||||||
|
| active 状态已有快照 | 返回 `582082` | 禁止重复回写 |
|
||||||
|
| 需求不存在或失效 | 返回 `582080` | 无写入 |
|
||||||
|
| 非法状态流转 | 返回 `582083` | 无写入 |
|
||||||
|
| 订单/需求已取消 | 跳过 | 不写完成快照,不推进订单 |
|
||||||
|
|
||||||
|
并发与重放需区分:同一摘要在 5 秒互斥窗口外再次提交时返回成功且数据库零写入;互斥窗口内的并发重复请求返回可识别冲突 `100502`,同样不得重复写快照、待办、流水或推进订单。前端遇到该冲突应刷新当前需求状态,不得自行补写完成状态。
|
||||||
|
|
||||||
|
错误响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 582081,
|
||||||
|
"message": "用车需求状态不允许回写配车",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、滚动发布与回滚
|
||||||
|
|
||||||
|
1. 先部署全部 `hl-order-service-v3` 实例并确认 Flyway 成功。
|
||||||
|
2. 保持 `FLEET_REQUIREMENT_LIFECYCLE_ENABLED=false`,再部署全部 `hl-fleet-service` 实例。
|
||||||
|
3. 确认 Fleet Flyway、健康与普通 Outbox 消费正常后,再启用开关。
|
||||||
|
4. 开关关闭时,需求级 Outbox 事件不会占用普通事件扫描窗口,也不会被丢弃;开启后继续重放。
|
||||||
|
5. 异常时先关闭开关,再回滚服务制品;兼容字段和历史 Outbox 不做破坏性回滚。
|
||||||
|
|
||||||
|
## 七、前端必须处理
|
||||||
|
|
||||||
|
1. 不新增内部回调请求,不把内部错误码做成独立前端流程。
|
||||||
|
2. 不按每日派单行数或单车派定结果推导需求完成。
|
||||||
|
3. 继续使用后端返回的 `statusOptions`、`assignmentStatusLabel`、`canAssign`、`canRejectRequirement`。
|
||||||
|
4. 操作成功后刷新服务端状态;并发处理中若能力字段变化,以最新接口响应为准。
|
||||||
|
5. 不修改既有分页、团号、联系人、定制师、逐日行程和大交通字段接法;这些仍以 `57_4882` 文档为准。
|
||||||
|
|
||||||
|
## 八、不影响范围
|
||||||
|
|
||||||
|
- 不修改 `hl-ui`,本文件仅做后端契约告知。
|
||||||
|
- 不新增管理后台分页或不分页接口。
|
||||||
|
- 不改变车型大类、司机占一座、司机险只计车队成本等既有口径。
|
||||||
|
- 不处理团期配车。
|
||||||
|
|
||||||
|
## 九、验证状态
|
||||||
|
|
||||||
|
### 9.1 合并前代码验证
|
||||||
|
|
||||||
|
```text
|
||||||
|
hl-order-service-v3 targeted: 198 tests,0 failures,0 errors,0 skipped
|
||||||
|
hl-order-service-v3 full verify: 5582 tests,0 failures,0 errors,15 skipped
|
||||||
|
hl-fleet-service targeted: 226 tests,0 failures,0 errors,0 skipped
|
||||||
|
hl-fleet-service full verify: 1721 tests,0 failures,0 errors,0 skipped
|
||||||
|
独立终审: P0=0,P1=0,P2=0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 测试环境部署
|
||||||
|
|
||||||
|
- `hl-order-service-v3` Deploy Panel 任务 `29b541d6` 成功;`8086/8186` 双实例均启动并监听。
|
||||||
|
- `hl-fleet-service` Deploy Panel 任务 `aaee8d01` 成功;`8087/8187` 双实例均启动并监听。
|
||||||
|
- Nacos 已启用 `fleet.assign.requirement-lifecycle-enabled=true` 与 `fleet.feign.writeback.enabled=true`。
|
||||||
|
- 发布顺序按“Order-v3 全实例 -> Fleet 全实例 -> 开启需求级生命周期开关”执行,未跨过滚动发布护栏。
|
||||||
|
|
||||||
|
### 9.3 真实 API 与数据验收
|
||||||
|
|
||||||
|
使用独立车务账号和真实测试订单完成 DIRECT、HOLD、取消后迟到回调、同摘要重放、摘要变化刷新、非法参数及失效需求分支验收;未使用 `admin`、`wx` 或 Mock 数据。
|
||||||
|
|
||||||
|
公网网关 `https://api.test.1814.love:9443` 最终验证:
|
||||||
|
|
||||||
|
| 请求 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 车务账号登录 | HTTP 200 |
|
||||||
|
| `GET /admin/fleet/board/summary` | HTTP 200,状态码/文案/数量由后端返回 |
|
||||||
|
| `GET /admin/fleet/board/orders?status=assigned&orderNo=...` | HTTP 200,精准返回 1 条 |
|
||||||
|
| `GET /admin/fleet/board/orders/{orderId}` | HTTP 200,返回逐日行程、车型诉求、司机确认凭证及当前派单 |
|
||||||
|
| `GET /admin/fleet/board/orders/{orderId}/timeline` | HTTP 200,返回完整操作时间线 |
|
||||||
|
|
||||||
|
HOLD 模式真实终态校验:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requirementStatus": "DONE",
|
||||||
|
"vehicleControlStatus": "DONE",
|
||||||
|
"hasFleetAssigned": true,
|
||||||
|
"snapshotCount": 1,
|
||||||
|
"activeDailySlices": 6,
|
||||||
|
"activeDailySliceStatus": "assigned",
|
||||||
|
"driverConfirmationEvidenceCount": 1,
|
||||||
|
"completionOutboxStatus": "SUCCESS"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
取消订单迟到回调保持 `hasFleetAssigned=false`、快照数为 0、有效逐日派单数为 0;相同拓扑摘要在互斥窗口外重放返回 HTTP 200 且不重复推进待办、流水或订单状态,窗口内并发重复返回 `100502` 且无重复副作用。
|
||||||
|
|
||||||
|
### 9.4 OpenAPI 与日志
|
||||||
|
|
||||||
|
- Order-v3 OpenAPI 已公开内部最终回调及 `VehicleAssignmentCallbackReqVO` 的 6 个必填字段。
|
||||||
|
- Fleet OpenAPI 已公开创建、预检、取消、改派、最终确认、司机确认/拒绝、提前结束、需求驳回与撤销取消等 10 个生命周期接口。
|
||||||
|
- 2026-07-16 22:22 后四个目标实例均无 `ERROR` 级日志;目标订单与需求在四实例中均为 0 条 WARN/ERROR,日志可见司机确认、最终回调成功和 Order-v3 快照刷新。
|
||||||
|
- 测试环境另有保险 PDF 缺失与历史脏订单降级 WARN,未关联本次目标订单,不作为本契约成功响应的一部分。
|
||||||
|
|
||||||
|
Issue #4935 的代码、部署、网关 API、MySQL 终态和服务日志证据均已补齐,可按后端验收清单关单。
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 当前看板字段、分页、统计、行程与保险:`57_4882_车务派单看板当前订单字段统计行程与保险事务收口-管理后台.md`
|
||||||
|
- 车务提需求与派单看板:`53_4871_车务提需求派单看板闭环契约-管理后台.md`
|
||||||
|
- 团号与定制师筛选:`54_4876_派单看板团号与定制师下拉筛选-管理后台.md`
|
||||||
|
- 后端最终回调契约:`hl-backend-changelog/changelogs/2026-07/14_1022_order-v3_vehicle-assignment-callback-contract.md`
|
||||||
@ -0,0 +1,775 @@
|
|||||||
|
# 【前端对接·管理后台】车务看板、详情、候选与矩阵读模型统一
|
||||||
|
|
||||||
|
> Issue: [wx/HL#4936](https://git.1814.love:8443/wx/HL/issues/4936)
|
||||||
|
>
|
||||||
|
> PR: [wx/HL#5031](https://git.1814.love:8443/wx/HL/pulls/5031)、[wx/HL#5032](https://git.1814.love:8443/wx/HL/pulls/5032)、[wx/HL#5034](https://git.1814.love:8443/wx/HL/pulls/5034)
|
||||||
|
>
|
||||||
|
> 服务: `hl-fleet-service` / `hl-order-service-v3`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-18
|
||||||
|
>
|
||||||
|
> 影响范围: 车务派单看板汇总与列表、派单详情、车辆/司机候选、矩阵月视图、相邻订单衔接风险
|
||||||
|
|
||||||
|
## 一、对接结论
|
||||||
|
|
||||||
|
1. 派单看板继续使用分页接口,`pageSize` 最大 100;没有新增“不分页全量接口”。
|
||||||
|
2. `/summary` 与 `/orders` 共用日期、车型、司机、联系人、团号、定制师和 `keyword` 筛选;汇总忽略 `status/statuses/page/pageSize`,返回同一筛选范围内的全部状态分面。`pendingCount/pendingUrgentCount/todayDepartCount/holdingTimeoutCount` 同样随这些订单筛选变化;`idleVehicleCount/idleDriverCount` 是不随订单筛选变化的全局资源指标。
|
||||||
|
3. 派单列表、详情和矩阵均以**当前订单 + 当前有效用车需求 + 当前有效派车组**为准,历史需求和历史派单不能覆盖当前数据。
|
||||||
|
4. 详情一次返回逐日行程、大交通、当前需求、全部有效派车组、生命周期、凭证和操作记录。
|
||||||
|
5. 候选车辆和司机分别分页,允许先选车或先选司机;返回完整闭区间可用时间窗、结构化可用性原因、冲突和常驻关系。
|
||||||
|
6. 矩阵按整月查询,但同一跨月派车组先补齐完整组再裁剪显示;不会因只查到月内一天而丢失真实起止日期。
|
||||||
|
7. 矩阵相邻订单衔接风险由后端返回 `status/statusLabel/style/reasonCode/reasonMessage`,前端不得自行根据颜色或时间重新推导。
|
||||||
|
8. **大交通允许不填写。** 无大交通时仍可提交用车需求、查询候选、预检和派车;前端只能显示提示,不得禁用派车按钮。
|
||||||
|
9. 所有雪花 ID 均按字符串处理,禁止转为 JavaScript `Number`。
|
||||||
|
10. 本次未修改 `hl-ui`,前端只按本文完成接口对接。
|
||||||
|
|
||||||
|
## 二、接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 用途 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | 看板汇总 | GET | `/admin/fleet/board/summary` | 同筛选状态计数、急单数、资源数、定制师选项 |
|
||||||
|
| 2 | 看板列表 | GET | `/admin/fleet/board/orders` | 分页卡片、统一筛选、当前状态和操作能力 |
|
||||||
|
| 3 | 看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 当前订单、当前需求、行程、大交通、派车组和日志 |
|
||||||
|
| 4 | 出行人脱敏列表 | GET | `/admin/fleet/board/orders/{orderId}/travelers` | 默认脱敏查看出行人 |
|
||||||
|
| 5 | 出行人明文查询 | POST | `/admin/fleet/board/orders/{orderId}/travelers/plain` | 有权限且有审计理由时查看明文 |
|
||||||
|
| 6 | 派单候选 | POST | `/admin/fleet/assignments/candidates` | 车辆和司机独立分页、冲突、可用时间窗、常驻关系 |
|
||||||
|
| 7 | 矩阵月视图 | GET | `/admin/fleet/matrix/grid` | 车辆月历、派车段、并行车辆、大交通和衔接风险 |
|
||||||
|
|
||||||
|
## 三、看板汇总与列表公共筛选
|
||||||
|
|
||||||
|
### 3.1 查询参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `statuses` | `string[]` | 否 | 多状态任一命中;支持重复 query 参数或逗号分隔。 |
|
||||||
|
| `status` | `string` | 否 | 单状态/逗号分隔别名,与 `statuses` 合并。 |
|
||||||
|
| `startDayFrom` | `date` | 否 | 日期区间起,与当前行程闭区间做重叠匹配。 |
|
||||||
|
| `startDate` | `date` | 否 | `startDayFrom` 别名;前者未传时生效。 |
|
||||||
|
| `startDayTo` | `date` | 否 | 日期区间止,与当前行程闭区间做重叠匹配。 |
|
||||||
|
| `endDate` | `date` | 否 | `startDayTo` 别名;前者未传时生效。 |
|
||||||
|
| `vehicleTypeKeys` | `string[]` | 否 | 车型大类:`suv/mpv/bus/sedan`,任一命中。 |
|
||||||
|
| `typeKeys` | `string[]` | 否 | `vehicleTypeKeys` 别名。 |
|
||||||
|
| `driverName` | `string` | 否 | 当前司机姓名模糊匹配。 |
|
||||||
|
| `keyword` | `string` | 否 | 司机、联系人/客户、团号、订单号、当前负责定制师展示名任一包含即命中。定制师展示名为企业微信昵称优先、用户名兜底;order-v3 降级时回退派单快照,只匹配后端最终解析出的一个展示名。 |
|
||||||
|
| `contactName` | `string` | 否 | 联系人/客户名模糊匹配。 |
|
||||||
|
| `contactKeyword` | `string` | 否 | `contactName` 别名。 |
|
||||||
|
| `teamNo` | `string` | 否 | 团号包含匹配,例如 `7218` 可命中 `26-7218`。 |
|
||||||
|
| `consultantId` | `string` | 否 | 当前负责定制师管理员 ID 精确匹配。 |
|
||||||
|
| `plannerName` | `string` | 否 | 定制师显示名模糊匹配兼容参数。 |
|
||||||
|
| `consultantName` | `string` | 否 | `plannerName` 别名。 |
|
||||||
|
| `variant` | `string` | 否 | `list` 默认;`grid` 为兼容值,其他值返回参数错误。 |
|
||||||
|
| `page` | `int` | 列表否 | 默认 1;汇总忽略。 |
|
||||||
|
| `pageSize` | `int` | 列表否 | 默认 20、最大 100;汇总忽略。 |
|
||||||
|
|
||||||
|
状态值:
|
||||||
|
|
||||||
|
```text
|
||||||
|
unassigned / unassigned_urgent / holding / holding_urgent /
|
||||||
|
assigned / change_requested / completed / canceled
|
||||||
|
```
|
||||||
|
|
||||||
|
状态含义由后端 `statusOptions` 返回。`holding` 是“车务已排车、司机尚未完成确认链路”,不是“车务正在浏览详情”。
|
||||||
|
|
||||||
|
### 3.2 汇总请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/summary?startDate=2026-07-01&endDate=2026-07-31&typeKeys=suv&keyword=王
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 汇总响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"pendingCount": 3,
|
||||||
|
"pendingUrgentCount": 1,
|
||||||
|
"todayDepartCount": 1,
|
||||||
|
"idleVehicleCount": 9,
|
||||||
|
"idleDriverCount": 5,
|
||||||
|
"holdingTimeoutCount": 1,
|
||||||
|
"statusCounts": {
|
||||||
|
"unassigned": 3,
|
||||||
|
"holding": 1,
|
||||||
|
"assigned": 2,
|
||||||
|
"changeRequested": 0,
|
||||||
|
"completed": 4,
|
||||||
|
"canceled": 1,
|
||||||
|
"unassignedUrgent": 1,
|
||||||
|
"holdingUrgent": 1
|
||||||
|
},
|
||||||
|
"statusOptions": [
|
||||||
|
{
|
||||||
|
"value": "unassigned",
|
||||||
|
"label": "待派车",
|
||||||
|
"count": 3,
|
||||||
|
"urgentCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "holding",
|
||||||
|
"label": "排车中",
|
||||||
|
"count": 1,
|
||||||
|
"urgentCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "assigned",
|
||||||
|
"label": "已派车",
|
||||||
|
"count": 2,
|
||||||
|
"urgentCount": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consultantOptions": [
|
||||||
|
{
|
||||||
|
"value": "2000000000000000001",
|
||||||
|
"label": "企业微信昵称"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
一致性规则:
|
||||||
|
|
||||||
|
```text
|
||||||
|
同一组非状态筛选条件下:
|
||||||
|
summary.statusOptions[value=X].count
|
||||||
|
== orders?statuses=X 返回的 data.total
|
||||||
|
```
|
||||||
|
|
||||||
|
`idleVehicleCount/idleDriverCount` 是当前物理资源指标,不受订单文字筛选影响;其他订单状态计数使用同一筛选后的记录集。
|
||||||
|
|
||||||
|
### 3.4 列表请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders?page=1&pageSize=20&statuses=unassigned,holding&keyword=7218&consultantId=2000000000000000001
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 列表响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20,
|
||||||
|
"total": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": "HL202607180001",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"orderId": "2000000000000000101",
|
||||||
|
"teamNo": "26-7218",
|
||||||
|
"assignmentId": "2000000000000000201",
|
||||||
|
"assignmentGroupId": "2000000000000000201",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"customerName": "测试联系人",
|
||||||
|
"contactName": "测试联系人",
|
||||||
|
"productName": "测试产品",
|
||||||
|
"headcount": 4,
|
||||||
|
"adultCount": 3,
|
||||||
|
"childCount": 1,
|
||||||
|
"youngChildCount": 0,
|
||||||
|
"babyCount": 0,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"days": 3,
|
||||||
|
"pickupAt": null,
|
||||||
|
"dropoffAt": null,
|
||||||
|
"isHailarPickup": false,
|
||||||
|
"isHailarDropoff": false,
|
||||||
|
"consultantId": "2000000000000000001",
|
||||||
|
"plannerName": "企业微信昵称",
|
||||||
|
"consultantName": "企业微信昵称",
|
||||||
|
"consultantDisplayName": "企业微信昵称",
|
||||||
|
"specialTags": ["中文司机", "大行李空间"],
|
||||||
|
"requirementRemark": "无大交通,按行程安排车辆",
|
||||||
|
"requiredVehicles": [
|
||||||
|
{
|
||||||
|
"vehicleType": "suv",
|
||||||
|
"categoryLabel": "SUV系列",
|
||||||
|
"seats": 7,
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assignmentStatus": "unassigned",
|
||||||
|
"assignmentStatusLabel": "待派车",
|
||||||
|
"lifecycleStageCode": "requirement_pending",
|
||||||
|
"lifecycleStageLabel": "待车务派车",
|
||||||
|
"currentStep": 1,
|
||||||
|
"availableActionCodes": ["ASSIGN", "REJECT_REQUIREMENT"],
|
||||||
|
"urgentBadge": null,
|
||||||
|
"canAssign": true,
|
||||||
|
"canRejectRequirement": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 列表字段绑定规则
|
||||||
|
|
||||||
|
| 字段 | 前端规则 |
|
||||||
|
|---|---|
|
||||||
|
| `teamNo` | 展示当前团号;空值不回退拼造。 |
|
||||||
|
| `contactName` | 卡片联系人。 |
|
||||||
|
| `consultantDisplayName` | 定制师展示名,企业微信昵称优先、用户名兜底。 |
|
||||||
|
| `startDate/endDate/days` | 当前订单档期,闭区间含首尾。 |
|
||||||
|
| `specialTags/requirementRemark` | 当前有效用车需求,不得混入历史需求。 |
|
||||||
|
| `assignmentStatusLabel/lifecycleStageLabel` | 直接展示,前端不维护独立中文映射。 |
|
||||||
|
| `availableActionCodes/canAssign/canRejectRequirement` | 决定操作入口;急单样式不得隐藏按钮。 |
|
||||||
|
|
||||||
|
列表按派车组聚合;底层一天一条派车切片不会把同一派车组重复成多张卡片。紧急待处理在前、普通进行中次之、终态沉底,同优先级以稳定 ID 兜底;前端不得二次排序。
|
||||||
|
|
||||||
|
## 四、派单详情
|
||||||
|
|
||||||
|
### 4.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders/2000000000000000101
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
路径参数是数字订单 ID,按字符串传递,不是订单号。
|
||||||
|
|
||||||
|
### 4.2 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "HL202607180001",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "26-7218",
|
||||||
|
"customerName": "测试联系人",
|
||||||
|
"headcount": 4,
|
||||||
|
"adultCount": 3,
|
||||||
|
"childCount": 1,
|
||||||
|
"youngChildCount": 0,
|
||||||
|
"babyCount": 0,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"pickupAt": null,
|
||||||
|
"dropoffAt": null,
|
||||||
|
"productName": "测试产品",
|
||||||
|
"consultantId": "2000000000000000001",
|
||||||
|
"plannerName": "企业微信昵称",
|
||||||
|
"consultantName": "企业微信昵称",
|
||||||
|
"consultantDisplayName": "企业微信昵称",
|
||||||
|
"specialTags": ["中文司机", "大行李空间"],
|
||||||
|
"requirementRemark": "无大交通,按行程安排车辆",
|
||||||
|
"itinerary": {
|
||||||
|
"theme": "草原三日",
|
||||||
|
"route": "海拉尔 → 额尔古纳 → 满洲里",
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"dayNumber": 1,
|
||||||
|
"date": "2026-07-20",
|
||||||
|
"title": "抵达海拉尔",
|
||||||
|
"detail": "市区行程"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dayNumber": 2,
|
||||||
|
"date": "2026-07-21",
|
||||||
|
"title": "额尔古纳",
|
||||||
|
"detail": "草原行程"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dayNumber": 3,
|
||||||
|
"date": "2026-07-22",
|
||||||
|
"title": "满洲里",
|
||||||
|
"detail": "返程"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"transferTimeHint": "暂无接送机时间",
|
||||||
|
"arrive": null,
|
||||||
|
"depart": null,
|
||||||
|
"batches": [],
|
||||||
|
"pickupRequired": null
|
||||||
|
},
|
||||||
|
"currentAssignment": {
|
||||||
|
"id": "2000000000000000201",
|
||||||
|
"assignmentGroupId": "2000000000000000201",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"requiredVehicleType": "suv",
|
||||||
|
"requiredSeats": 7,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"vehicleId": "2000000000000000301",
|
||||||
|
"vehiclePlate": "蒙A·TEST1",
|
||||||
|
"driverId": "2000000000000000401",
|
||||||
|
"driverName": "测试司机",
|
||||||
|
"baseAssignmentStatus": "assigned",
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"lifecycleStageCode": "confirmed",
|
||||||
|
"lifecycleStageLabel": "已确认执行",
|
||||||
|
"currentStep": 4,
|
||||||
|
"availableActionCodes": ["CANCEL", "CHANGE_DRIVER", "COMPLETE_EARLY"],
|
||||||
|
"protocolPrice": "1300.00",
|
||||||
|
"driverConfirmationEvidenceFileIds": []
|
||||||
|
},
|
||||||
|
"activeAssignments": [
|
||||||
|
{
|
||||||
|
"assignmentGroupId": "2000000000000000201",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"vehiclePlate": "蒙A·TEST1",
|
||||||
|
"driverName": "测试司机",
|
||||||
|
"assignmentStatus": "assigned"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationLog": {
|
||||||
|
"records": [],
|
||||||
|
"total": 0,
|
||||||
|
"summary": {"totalCount": 0}
|
||||||
|
},
|
||||||
|
"relatedDetailReady": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 当前需求和派车组隔离
|
||||||
|
|
||||||
|
- `currentAssignment` 是最新一个有效派车组的兼容字段。
|
||||||
|
- `activeAssignments` 才是当前需求下全部有效派车组;一单多车时必须渲染完整数组。
|
||||||
|
- 历史需求、已取消组和旧订单快照不能进入当前需求详情。
|
||||||
|
- `baseAssignmentStatus` 是落库基础态;`assignmentStatus` 是当前有效状态,前端展示后者。
|
||||||
|
|
||||||
|
### 4.4 逐日行程规则
|
||||||
|
|
||||||
|
- `itinerary.days` 来自 `order_itinerary_day`,一天一条事实数据。
|
||||||
|
- 返回日期必须位于当前 `[startDate,endDate]`,按 `dayNumber` 稳定排序。
|
||||||
|
- 改期后按当前订单档期对齐;越界、旧版本和非法日序数据不返回。
|
||||||
|
- 无有效行程时返回 `days=[]`,前端显示空态,不得生成假行程。
|
||||||
|
|
||||||
|
### 4.5 大交通规则
|
||||||
|
|
||||||
|
有数据时:
|
||||||
|
|
||||||
|
- 到达接客使用大交通 `arriveTime`。
|
||||||
|
- 返程送客使用大交通 `departTime`。
|
||||||
|
- 分批接送完整返回 `batches`。
|
||||||
|
|
||||||
|
无数据时固定返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"transport": {
|
||||||
|
"transferTimeHint": "暂无接送机时间",
|
||||||
|
"arrive": null,
|
||||||
|
"depart": null,
|
||||||
|
"batches": [],
|
||||||
|
"pickupRequired": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**空大交通是正常业务状态,不是参数错误,也不是派车阻断条件。** `pickupAt/dropoffAt` 同样允许为 `null`。
|
||||||
|
|
||||||
|
## 五、车辆与司机候选
|
||||||
|
|
||||||
|
### 5.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/candidates
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2000000000000000101",
|
||||||
|
"requirementId": "2000000000000000501",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"headcount": 4,
|
||||||
|
"selectedVehicleId": null,
|
||||||
|
"selectedDriverId": null,
|
||||||
|
"excludeAssignmentId": null,
|
||||||
|
"vehicleKeyword": "GL8",
|
||||||
|
"driverKeyword": "张",
|
||||||
|
"vehiclePage": 1,
|
||||||
|
"vehiclePageSize": 20,
|
||||||
|
"driverPage": 1,
|
||||||
|
"driverPageSize": 20
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`pickupAt/dropoffAt` 未出现是合法请求;有城市信息时可额外传入,供城市衔接判断使用。
|
||||||
|
|
||||||
|
### 5.2 请求参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `orderId` | `string` | 改派时是 | 当前订单 ID。 |
|
||||||
|
| `requirementId` | `string` | 改派时是 | 当前有效用车需求 ID。 |
|
||||||
|
| `fleetItemIndex` | `int` | 否 | 当前车型项序号,从 0 开始。 |
|
||||||
|
| `startDate/endDate` | `date` | 是 | 请求用车闭区间。 |
|
||||||
|
| `pickupAt/dropoffAt` | `string` | 否 | 城市衔接辅助信息;空值不阻断候选和派车。 |
|
||||||
|
| `headcount` | `int` | 否 | 乘客人数,不含司机。车辆乘客容量=`seats-1`。 |
|
||||||
|
| `selectedVehicleId` | `string` | 否 | 已选车辆,支持先选车。 |
|
||||||
|
| `selectedDriverId` | `string` | 否 | 已选司机,支持先选司机。 |
|
||||||
|
| `excludeAssignmentId` | `string` | 否 | 改派时排除当前派单,且必须属于当前订单和需求。 |
|
||||||
|
| `vehicleKeyword` | `string` | 否 | 车牌或车型。 |
|
||||||
|
| `driverKeyword` | `string` | 否 | 司机姓名或完整手机号。 |
|
||||||
|
| `vehiclePage/driverPage` | `int` | 是 | 各自分页页码,默认 1。 |
|
||||||
|
| `vehiclePageSize/driverPageSize` | `int` | 是 | 各自每页条数,最大 100。 |
|
||||||
|
|
||||||
|
### 5.3 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"vehicles": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"vehicleId": "2000000000000000301",
|
||||||
|
"plate": "蒙A·TEST1",
|
||||||
|
"modelName": "测试车型",
|
||||||
|
"seats": 7,
|
||||||
|
"passengerCapacity": 6,
|
||||||
|
"seatsEnough": true,
|
||||||
|
"fleet": "own",
|
||||||
|
"selected": false,
|
||||||
|
"available": true,
|
||||||
|
"availabilityReasonCode": "AVAILABLE",
|
||||||
|
"availabilityReasonMessage": "所选服务日期内可用",
|
||||||
|
"availabilityWindows": [
|
||||||
|
{"startDate": "2026-07-20", "endDate": "2026-07-22"}
|
||||||
|
],
|
||||||
|
"residentMatch": false,
|
||||||
|
"crossResident": false,
|
||||||
|
"requiresCrossResidentConfirmation": false,
|
||||||
|
"relationMessage": null,
|
||||||
|
"conflicts": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"drivers": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"driverId": "2000000000000000401",
|
||||||
|
"name": "测试司机",
|
||||||
|
"maskedPhone": "138****0000",
|
||||||
|
"years": 8,
|
||||||
|
"season": "active",
|
||||||
|
"completedOrderCount": 12,
|
||||||
|
"rating": 5.0,
|
||||||
|
"ratingDefaulted": true,
|
||||||
|
"selected": false,
|
||||||
|
"available": true,
|
||||||
|
"availabilityReasonCode": "CITY_JUNCTION_SHAREABLE",
|
||||||
|
"availabilityReasonMessage": "仅存在可衔接的同城边界占用",
|
||||||
|
"availabilityWindows": [
|
||||||
|
{"startDate": "2026-07-20", "endDate": "2026-07-22"}
|
||||||
|
],
|
||||||
|
"residentMatch": false,
|
||||||
|
"crossResident": false,
|
||||||
|
"requiresCrossResidentConfirmation": false,
|
||||||
|
"conflicts": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20
|
||||||
|
},
|
||||||
|
"selectedRelation": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 候选判定规则
|
||||||
|
|
||||||
|
| `availabilityReasonCode` | 含义 | `available` |
|
||||||
|
|---|---|---|
|
||||||
|
| `AVAILABLE` | 整个请求闭区间无阻塞占用 | `true` |
|
||||||
|
| `CITY_JUNCTION_SHAREABLE` | 只有满足规则的城市边界衔接 | `true` |
|
||||||
|
| `ASSIGNMENT_CONFLICT` | 请求区间内存在阻塞派单 | `false` |
|
||||||
|
|
||||||
|
- `availabilityWindows` 是请求区间内的实际可用**闭区间**,冲突会切分时间窗。
|
||||||
|
- 车辆和司机都必须覆盖完整请求区间才可直接选中。
|
||||||
|
- 司机无评价时返回 `rating=5.0` 且 `ratingDefaulted=true`;有真实评价时为 `false`。
|
||||||
|
- 车辆总座位数包含司机,`passengerCapacity=seats-1`;前端不得把司机座位再次给乘客。
|
||||||
|
- `selectedRelation` 在车辆和司机都已选时返回常驻关系。跨常驻组合必须显示后端提示并显式确认。
|
||||||
|
|
||||||
|
## 六、矩阵月视图
|
||||||
|
|
||||||
|
### 6.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/matrix/grid?year=2026&month=7&season=active&fleets=own,coopA&typeKeys=suv&status=all
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 请求参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `year` | `int` | 是 | 查询年份。 |
|
||||||
|
| `month` | `int` | 是 | 1-12,越界返回车务月份错误。 |
|
||||||
|
| `season` | `string` | 否 | `active` 默认;也支持 `pending/archived/blacklist`。 |
|
||||||
|
| `fleets` | `string[]` | 否 | `own/coopA/coopB` 多选。 |
|
||||||
|
| `typeKeys` | `string[]` | 否 | `suv/mpv/bus/sedan` 多选。 |
|
||||||
|
| `status` | `string` | 否 | `all` 默认、`unassigned`、`assigned`。 |
|
||||||
|
|
||||||
|
### 6.3 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"year": 2026,
|
||||||
|
"month": 7,
|
||||||
|
"daysInMonth": 31,
|
||||||
|
"todayDay": 18,
|
||||||
|
"weekendDays": [4, 5, 11, 12, 18, 19, 25, 26],
|
||||||
|
"fleetCount": {"own": 5, "coopA": 4, "coopB": 3},
|
||||||
|
"statusCounts": {
|
||||||
|
"totalAssignments": 21,
|
||||||
|
"unassignedAssignments": 10,
|
||||||
|
"assignedAssignments": 11,
|
||||||
|
"totalOrders": 16,
|
||||||
|
"unassignedOrders": 5,
|
||||||
|
"partialOrders": 2,
|
||||||
|
"assignedOrders": 11
|
||||||
|
},
|
||||||
|
"unassignedWindowCount": 5,
|
||||||
|
"vehicles": [
|
||||||
|
{
|
||||||
|
"id": "2000000000000000301",
|
||||||
|
"plate": "蒙A·TEST1",
|
||||||
|
"modelName": "测试车型",
|
||||||
|
"seats": 7,
|
||||||
|
"fleet": "own",
|
||||||
|
"primaryDriverName": "测试司机",
|
||||||
|
"primaryDriverPhone": "138****0000",
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"id": "2000000000000000201",
|
||||||
|
"assignmentGroupId": "2000000000000000201",
|
||||||
|
"orderNumericId": "2000000000000000101",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "26-7218",
|
||||||
|
"consultantId": "2000000000000000001",
|
||||||
|
"consultantName": "企业微信昵称",
|
||||||
|
"customerName": "测试联系人",
|
||||||
|
"headcount": 4,
|
||||||
|
"adultCount": 3,
|
||||||
|
"childCount": 1,
|
||||||
|
"startDay": 20,
|
||||||
|
"endDay": 22,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"clippedHead": false,
|
||||||
|
"clippedTail": false,
|
||||||
|
"vehicleCategory": "suv",
|
||||||
|
"categoryLabel": "SUV系列",
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"protocolPrice": "1300.00",
|
||||||
|
"vehicleSpecialTags": ["中文司机"],
|
||||||
|
"vehicleRequirementRemark": "按行程安排",
|
||||||
|
"pickupTransports": [],
|
||||||
|
"dropoffTransports": [],
|
||||||
|
"parallelAssignments": [
|
||||||
|
{
|
||||||
|
"assignmentGroupId": "2000000000000000201",
|
||||||
|
"fleetItemIndex": 0,
|
||||||
|
"vehicleCategory": "suv",
|
||||||
|
"categoryLabel": "SUV系列",
|
||||||
|
"requiredSeats": 7,
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"vehicleId": "2000000000000000301",
|
||||||
|
"vehiclePlate": "蒙A·TEST1",
|
||||||
|
"driverId": "2000000000000000401",
|
||||||
|
"driverName": "测试司机",
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"protocolPrice": "1300.00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"fromAssignmentGroupId": "2000000000000000201",
|
||||||
|
"toAssignmentGroupId": "2000000000000000202",
|
||||||
|
"fromEndDate": "2026-07-22",
|
||||||
|
"toStartDate": "2026-07-23",
|
||||||
|
"previousDepartureTime": null,
|
||||||
|
"nextArrivalTime": null,
|
||||||
|
"previousCity": null,
|
||||||
|
"nextCity": null,
|
||||||
|
"connectionMinutes": null,
|
||||||
|
"thresholdMinutes": 120,
|
||||||
|
"status": "MISSING_TIME",
|
||||||
|
"statusLabel": "缺少接送时间",
|
||||||
|
"style": "RED_DASHED",
|
||||||
|
"reasonCode": "PREVIOUS_REQUIRED_DEPARTURE_MISSING",
|
||||||
|
"reasonMessage": "前一订单缺少必需送客批次"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 派单统计口径
|
||||||
|
|
||||||
|
- `totalAssignments` 是派车行数,不是订单数。
|
||||||
|
- `totalOrders` 是去重订单数。
|
||||||
|
- 一单同时存在已派和未派项时计入 `partialOrders`,也计入 `unassignedOrders`。
|
||||||
|
- `unassignedWindowCount` 是含未派项的去重订单数,不等于未派逐日切片条数。
|
||||||
|
- 跨月派车组使用完整原始 `startDate/endDate`,仅 `startDay/endDay` 裁剪到当前月;`clippedHead/clippedTail` 告诉前端是否跨月续接。
|
||||||
|
|
||||||
|
### 6.5 相邻订单衔接四态
|
||||||
|
|
||||||
|
| `status` | `style` | 含义 |
|
||||||
|
|---|---|---|
|
||||||
|
| `MISSING_TIME` | `RED_DASHED` | 缺少必需大交通、时刻或城市,无法完成衔接判断。 |
|
||||||
|
| `DIFFERENT_CITY` | `DARK_RED` | 前后订单城市不同。 |
|
||||||
|
| `SAME_CITY_TOO_SHORT` | `LIGHT_RED` | 同城但间隔小于配置阈值。 |
|
||||||
|
| `SAME_CITY_OK` | `GREEN` | 同城且间隔达到配置阈值。 |
|
||||||
|
|
||||||
|
原因码:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PREVIOUS_REQUIRED_DEPARTURE_MISSING
|
||||||
|
NEXT_REQUIRED_ARRIVAL_MISSING
|
||||||
|
PREVIOUS_DEPARTURE_TIME_MISSING
|
||||||
|
NEXT_ARRIVAL_TIME_MISSING
|
||||||
|
PREVIOUS_DEPARTURE_CITY_MISSING
|
||||||
|
NEXT_ARRIVAL_CITY_MISSING
|
||||||
|
DIFFERENT_CITY
|
||||||
|
SAME_CITY_INTERVAL_TOO_SHORT
|
||||||
|
SAME_CITY_INTERVAL_SUFFICIENT
|
||||||
|
```
|
||||||
|
|
||||||
|
同城最小衔接阈值读取 Nacos 配置,默认 120 分钟;恰好等于阈值属于 `SAME_CITY_OK`。
|
||||||
|
|
||||||
|
注意:`MISSING_TIME` 是矩阵风险提示,不表示订单不能派车。用户未填写大交通时仍允许完成派车。
|
||||||
|
|
||||||
|
## 七、出行人权限与审计
|
||||||
|
|
||||||
|
### 7.1 脱敏列表
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/fleet/board/orders/2000000000000000101/travelers
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
默认返回姓名、年龄类型和脱敏证件/手机号;前端日常派车只使用此接口。
|
||||||
|
|
||||||
|
### 7.2 明文查询
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/board/orders/2000000000000000101/travelers/plain
|
||||||
|
Authorization: Bearer <fleet-manager-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reason": "司机出发前核对接客人信息"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
明文接口必须经过权限校验并记录操作人、订单、理由和时间。前端不得缓存、日志打印或二次持久化明文个人信息。
|
||||||
|
|
||||||
|
## 八、前端必须处理
|
||||||
|
|
||||||
|
1. 看板使用分页接口,分页器读取 `data.total/page/pageSize`。
|
||||||
|
2. 状态项、状态中文、急单数读取 `summary.statusOptions`,不维护独立枚举和独立计数。
|
||||||
|
3. 汇总请求必须携带与列表相同的非状态筛选;不要把当前状态筛选传成汇总统计范围。
|
||||||
|
4. 卡片展示 `teamNo/contactName/headcount/consultantDisplayName/startDate/endDate/specialTags/requirementRemark`。
|
||||||
|
5. 定制师选择值传 `consultantId`;显示使用后端返回的企业微信优先名称。
|
||||||
|
6. 统一文本框传 `keyword`,可同时匹配司机、联系人、团号、订单号和当前定制师展示名;定制师按企业微信昵称优先、用户名兜底,前端不得自行并行匹配多个名称别名。
|
||||||
|
7. 操作按钮使用 `availableActionCodes/canAssign/canRejectRequirement`,不能按颜色或前端状态猜测。
|
||||||
|
8. 详情多车读取 `activeAssignments`;`currentAssignment` 只是兼容的最新一组。
|
||||||
|
9. `transport.transferTimeHint="暂无接送机时间"` 时显示提示,但保持派车入口可用。
|
||||||
|
10. 候选资源分别读取 `vehicles/drivers` 分页;显示后端原因和常驻关系提示。
|
||||||
|
11. 矩阵直接使用 `connections[].status/style/reasonCode/reasonMessage`;大红、淡红、虚线红和绿色语义不能自行交换。
|
||||||
|
12. 所有雪花 ID 当字符串处理。
|
||||||
|
|
||||||
|
## 九、兼容性与不影响范围
|
||||||
|
|
||||||
|
- 保留 `status/startDate/endDate/typeKeys/contactKeyword/consultantName` 等兼容别名。
|
||||||
|
- 不新增前端内部接口,不改变现有派车写接口。
|
||||||
|
- 不要求填写大交通,也不把空大交通改成校验错误。
|
||||||
|
- 不改变“一天一条派车切片”的数据库事实模型;本轮只修正读模型聚合。
|
||||||
|
- 不处理团期配车。
|
||||||
|
- 不修改 `hl-ui`。
|
||||||
|
|
||||||
|
## 十、验证证据
|
||||||
|
|
||||||
|
### 10.1 代码与测试
|
||||||
|
|
||||||
|
```text
|
||||||
|
hl-fleet-service 定向测试:253/253 通过
|
||||||
|
hl-fleet-service 全量 verify:1799/1799 通过
|
||||||
|
hl-order-service-v3 相关契约测试:30/30 通过
|
||||||
|
独立代码评审:无 P0/P1 阻断项
|
||||||
|
OpenAPI 说明定向校验:spotless:check + compile 通过
|
||||||
|
```
|
||||||
|
|
||||||
|
Order-v3 全量测试中 4 项环境/基线失败已在同提交干净基线复现:3 项为 H2 缺少 `payment_manual_receipt`,1 项为既有本地缓存架构门禁;不由本次车务改动引入。
|
||||||
|
|
||||||
|
### 10.2 部署
|
||||||
|
|
||||||
|
```text
|
||||||
|
hl-order-service-v3:Deploy Panel 任务 22ec81e8,8086/8186 双实例成功
|
||||||
|
hl-fleet-service:Deploy Panel 任务 68487f94,8087/8187 双实例成功
|
||||||
|
hl-fleet-service OpenAPI 口径补充:Deploy Panel 任务 249594e3,8087/8187 双实例成功
|
||||||
|
```
|
||||||
|
|
||||||
|
部署后已通过网关读取车务 OpenAPI,确认线上文档明确区分“同一订单筛选范围内的状态计数”与“不随订单筛选变化的全局空闲资源数”,并包含统一关键词对当前定制师展示名的匹配规则。
|
||||||
|
|
||||||
|
### 10.3 真实网关 API
|
||||||
|
|
||||||
|
使用独立车务和定制师测试账号,经 `https://api.test.1814.love:9443` 完成真实订单全流程回归;未使用 `admin`、`wx` 或 Mock 数据。
|
||||||
|
|
||||||
|
```text
|
||||||
|
最终回归:161/161 通过,失败 0
|
||||||
|
覆盖:看板汇总/列表/详情、统一筛选、定制师、脱敏/明文出行人、候选车辆/司机、
|
||||||
|
常驻与跨常驻、预检、派车、司机确认、取消/恢复/改派/提前完结、矩阵、价格、
|
||||||
|
车辆/司机、对账、模板,以及无大交通订单完整派车链路。
|
||||||
|
```
|
||||||
|
|
||||||
|
无大交通真实场景额外断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
- 新建真实订单并补齐 2 名出行人
|
||||||
|
- 不创建任何大交通计划
|
||||||
|
- 成功提交有效用车需求
|
||||||
|
- 详情:arrive=null、depart=null、batches=[]、transferTimeHint=暂无接送机时间
|
||||||
|
- 候选查询成功,pickupAt/dropoffAt 均省略
|
||||||
|
- 派车预检成功,conflict=false
|
||||||
|
- 直派成功并进入已派车状态
|
||||||
|
- DB:派车组逐日 6 条,pickup_at/dropoff_at 6 条均为空
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十一、相关文档
|
||||||
|
|
||||||
|
- 看板字段、统计、行程和保险事务:`57_4882_车务派单看板当前订单字段统计行程与保险事务收口-管理后台.md`
|
||||||
|
- 需求级最终完成回调:`59_4935_车务需求级派单完成回调与滚动发布契约-管理后台.md`
|
||||||
|
- 提需求和派单基础契约:`53_4871_车务提需求派单看板闭环契约-管理后台.md`
|
||||||
|
- 团号与定制师筛选:`54_4876_派单看板团号与定制师下拉筛选-管理后台.md`
|
||||||
@ -0,0 +1,213 @@
|
|||||||
|
# 【前端对接·管理后台】房务待办新增“待最终确认”派生项
|
||||||
|
|
||||||
|
> Issue: [wx/HL#5053](https://git.1814.love:8443/wx/HL/issues/5053)
|
||||||
|
>
|
||||||
|
> PR: [wx/HL#5059](https://git.1814.love:8443/wx/HL/pulls/5059)
|
||||||
|
>
|
||||||
|
> 合并提交: `989318c3925b`
|
||||||
|
>
|
||||||
|
> 服务: `hl-order-service-v3` / `hl-user-service`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-18
|
||||||
|
>
|
||||||
|
> 影响范围: 房务“待处理”列表、待办类型筛选、订单详情跳转、房务工作台待办统计
|
||||||
|
|
||||||
|
## 一、对接结论
|
||||||
|
|
||||||
|
1. 房务“待处理”页的唯一主数据源仍是 `GET /v3/admin/order/todos`,不要改用任何 `my-claims` 接单列表接口。`my-claims` 不返回完整的待办类型聚合,不能替代待办接口。
|
||||||
|
2. 待办接口新增可选类型 `PENDING_FINALIZE`,中文标签为“待最终确认”。它是查询时派生的虚拟待办,不落 `house_todo`,因此 `derived=true`、标签级 `todoId=null`。
|
||||||
|
3. 当前房务持有的 active 住宿需求处于 `status=PROCESSING`、`houseStatus=PENDING_FINALIZE` 时,接口返回该派生项;最终确认后需求进入 `DONE/CONFIRMED`,该项从列表、筛选结果和统计中自然消失。
|
||||||
|
4. `list[].todoTypes[]` 是一订单多标签的权威数据。点击 `PENDING_FINALIZE` 标签时必须使用该标签自己的 `requirementId`,不能依赖聚合行顶层 `requirementId`。
|
||||||
|
5. 派生项不可调用待办 `RESOLVE`。用户应打开 `OrderDetailModal` 完成“最终确认”,成功后刷新待办列表与工作台仪表盘。
|
||||||
|
6. 房务工作台 `GET /admin/profile/dashboard` 的 `todoSummary` 同步新增大写键 `PENDING_FINALIZE`。
|
||||||
|
7. 本次没有修改 `hl-ui`;下文列出的现有前端筛选和标签级参数问题需由前端处理。
|
||||||
|
|
||||||
|
## 二、待办接口变化
|
||||||
|
|
||||||
|
### 2.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/todos?scope=mine&status=OPEN&page=1&pageSize=20
|
||||||
|
Authorization: Bearer <room-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
只看“待最终确认”时:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/todos?scope=mine&status=OPEN&todoType=PENDING_FINALIZE&page=1&pageSize=20
|
||||||
|
Authorization: Bearer <room-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
查询参数名是 `todoType`,不是 `type`。`todoType` 支持逗号分隔多选。
|
||||||
|
|
||||||
|
### 2.2 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"orderId": "2000000000000000001",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"todoType": "PENDING_FINALIZE",
|
||||||
|
"todoTypeLabel": "待最终确认",
|
||||||
|
"title": "待最终确认",
|
||||||
|
"urgency": "normal",
|
||||||
|
"status": "OPEN",
|
||||||
|
"derived": true,
|
||||||
|
"requirementId": "2000000000000000101",
|
||||||
|
"orderTodoCount": 1,
|
||||||
|
"todoTypes": [
|
||||||
|
{
|
||||||
|
"typeCode": "PENDING_FINALIZE",
|
||||||
|
"typeLabel": "待最终确认",
|
||||||
|
"urgency": "normal",
|
||||||
|
"count": 1,
|
||||||
|
"derived": true,
|
||||||
|
"todoId": null,
|
||||||
|
"requirementId": "2000000000000000101",
|
||||||
|
"unreadCount": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"stats": {
|
||||||
|
"PENDING_FINALIZE": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段规则:
|
||||||
|
|
||||||
|
| 字段 | 前端规则 |
|
||||||
|
|---|---|
|
||||||
|
| `list[].todoTypes[]` | 一订单多待办类型的权威标签数组,必须完整渲染。 |
|
||||||
|
| `todoTypes[].typeCode` | 新增可选值 `PENDING_FINALIZE`。 |
|
||||||
|
| `todoTypes[].typeLabel` | 直接展示后端中文“待最终确认”。 |
|
||||||
|
| `todoTypes[].derived` | `true` 表示虚拟待办,由源业务状态自然消失。 |
|
||||||
|
| `todoTypes[].todoId` | `PENDING_FINALIZE` 固定为 `null`,禁止调用 `RESOLVE`。 |
|
||||||
|
| `todoTypes[].requirementId` | 打开该标签对应房务详情时使用;雪花 ID 按字符串透传。 |
|
||||||
|
| `list[].requirementId` | 只兼容聚合行主标签;一行多标签时不能替代标签级字段。 |
|
||||||
|
| `stats.PENDING_FINALIZE` | 当前 scope 内“待最终确认”需求数;无数据也返回 `0`。 |
|
||||||
|
|
||||||
|
`stats` 是当前 scope 的完整分类计数,不因本次 `todoType` facet 收窄;因此筛选结果 `total` 可以是 `1`,同时其他统计槽仍保留其真实值。
|
||||||
|
|
||||||
|
### 2.3 生命周期
|
||||||
|
|
||||||
|
```text
|
||||||
|
当前房务 + active requirement
|
||||||
|
status=PROCESSING + houseStatus=PENDING_FINALIZE
|
||||||
|
→ /todos 出现 PENDING_FINALIZE 派生标签
|
||||||
|
→ 用户从该标签进入订单详情并执行最终确认
|
||||||
|
→ status=DONE + houseStatus=CONFIRMED
|
||||||
|
→ /todos、todoType facet、stats 和 dashboard 中该项均消失/归零
|
||||||
|
```
|
||||||
|
|
||||||
|
该链路不创建 `house_todo` 记录,也不改变既有持久化待办的 RESOLVE 语义。
|
||||||
|
|
||||||
|
## 三、房务工作台统计变化
|
||||||
|
|
||||||
|
房务角色调用:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /admin/profile/dashboard?period=today
|
||||||
|
Authorization: Bearer <room-manager-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
`data.todoSummary` 新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 4,
|
||||||
|
"PENDING_FINALIZE": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 键名固定为大写 `PENDING_FINALIZE`,与待办接口 `stats`、`todoTypes[].typeCode` 共用同一常量。
|
||||||
|
- 最终确认后该值归零;前端刷新列表时应同时刷新工作台数据或使对应查询缓存失效。
|
||||||
|
|
||||||
|
## 四、前端必须处理的现有问题
|
||||||
|
|
||||||
|
### 4.1 筛选器混用了“房务状态”和“待办类型”
|
||||||
|
|
||||||
|
当前 `src/views/housekeeper/todos/index.vue:220-227` 把以下值放在同一个 `STATUS_OPTIONS` 中:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HOTEL_REPLY_TIMEOUT / PENDING_ARRANGE /
|
||||||
|
CLAIMING / IN_INQUIRY / PENDING_FINALIZE / EXCEPTION
|
||||||
|
```
|
||||||
|
|
||||||
|
但同文件 `:370` 固定请求 `status=OPEN`,`:374` 又把所有非“全部”选项都作为 `todoType`,最终由 `:386` 请求待办接口。
|
||||||
|
|
||||||
|
- `HOTEL_REPLY_TIMEOUT`、`PENDING_ARRANGE`、`PENDING_FINALIZE` 是合法 `HouseTodoType`。
|
||||||
|
- `CLAIMING`、`EXCEPTION` 是房务业务状态,不是待办类型,作为 `todoType` 请求会得到空结果。
|
||||||
|
- `IN_INQUIRY` 已不再是顶层房务状态,也不是待办类型。
|
||||||
|
|
||||||
|
前端应删除这三个无效 `todoType` 选项;如产品确实需要按房务状态筛选,应另行使用声明支持该参数的数据源,不能继续混传给 `/todos.todoType`。
|
||||||
|
|
||||||
|
同时,`src/api/housekeeper/todos.js:53` 的注释仍写 `params.type`,实际页面和后端均使用 `params.todoType`;请同步修正文档注释,API 调用本身仍是 `:66-67` 的 `/v3/admin/order/todos`。
|
||||||
|
|
||||||
|
### 4.2 标签级 `requirementId` 在映射时丢失
|
||||||
|
|
||||||
|
当前 `src/views/housekeeper/todos/index.vue:290-313` 把 `todoTypes[]` 映射成 `reasonTags` 时只保留了 `typeCode/label/derived`,未保留标签自己的 `requirementId`;`:267` 和 `:453` 只使用聚合行顶层 `requirementId`。
|
||||||
|
|
||||||
|
一订单多标签时,顶层字段属于“主标签”,不保证就是用户点击的 `PENDING_FINALIZE` 标签。建议保留标签字段并按点击项分发:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const pendingFinalizeTag = row.todoTypes.find(
|
||||||
|
(item) => item.typeCode === 'PENDING_FINALIZE'
|
||||||
|
)
|
||||||
|
|
||||||
|
openOrderDetail({
|
||||||
|
orderId: row.orderId,
|
||||||
|
requirementId: pendingFinalizeTag?.requirementId,
|
||||||
|
claimScope: 'mine',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
实际组件仍应复用现有 `OrderDetailModal`,传入:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<OrderDetailModal
|
||||||
|
:order-id="orderId"
|
||||||
|
:requirement-id="requirementId"
|
||||||
|
claim-scope="mine"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
所有 ID 均按字符串处理,禁止转为 JavaScript `Number`。
|
||||||
|
|
||||||
|
### 4.3 完成动作
|
||||||
|
|
||||||
|
`PENDING_FINALIZE` 的处理入口是订单详情内“最终确认”,不是待办 `RESOLVE`:
|
||||||
|
|
||||||
|
1. 从点击标签取得 `orderId + todoTypes[].requirementId`。
|
||||||
|
2. 以 `claimScope=mine` 打开 `OrderDetailModal`。
|
||||||
|
3. 用户执行一次“最终确认”。
|
||||||
|
4. 成功后刷新 `/v3/admin/order/todos` 和 `/admin/profile/dashboard`。
|
||||||
|
5. 列表行、筛选计数和工作台徽章应同步消失/归零。
|
||||||
|
|
||||||
|
## 五、已完成的后端与测试环境验证
|
||||||
|
|
||||||
|
- PR #5059 已合并到 `dev-v3`,合并提交为 `989318c3925b`。
|
||||||
|
- `hl-order-service-v3` TEST 部署任务 `fd8522fe` 成功,`8086/8186` 双实例健康。
|
||||||
|
- `hl-user-service` TEST 部署任务 `372a9f3e` 成功,`8081/8181` 双实例健康。
|
||||||
|
- 网关 API 实测:进入待最终确认前 `PENDING_FINALIZE=0`;测试需求进入 `PROCESSING/PENDING_FINALIZE` 后,列表、facet、`stats` 和 dashboard 均为 `1`;最终确认后均恢复为 `0`。
|
||||||
|
- TEST DB 对账:派生项出现时没有新增 `house_todo(PENDING_FINALIZE)`;最终确认后需求为 `DONE/CONFIRMED`,两晚配房仍为 `CONFIRMED`,房务归属和配房数据均保留。
|
||||||
|
- 真实调试 Chrome 已验证“待最终确认”标签可见、筛选只剩目标订单、最终确认后列表空态;浏览器验收仅作为前端对接参考,不改变上述接口契约。
|
||||||
|
|
||||||
|
## 六、前端验收清单
|
||||||
|
|
||||||
|
- [ ] “待处理”页仅以 `GET /v3/admin/order/todos` 为主数据源。
|
||||||
|
- [ ] `STATUS_OPTIONS` 不再把 `CLAIMING/IN_INQUIRY/EXCEPTION` 作为 `todoType` 发送。
|
||||||
|
- [ ] 使用 `todoType=PENDING_FINALIZE` 可筛出“待最终确认”订单。
|
||||||
|
- [ ] 完整渲染 `list[].todoTypes[]`,显示后端 `typeLabel`。
|
||||||
|
- [ ] 映射和点击事件保留 `todoTypes[].requirementId`。
|
||||||
|
- [ ] 以 `orderId + 标签级 requirementId + claimScope=mine` 打开 `OrderDetailModal`。
|
||||||
|
- [ ] 派生标签不调用待办 `RESOLVE`。
|
||||||
|
- [ ] 最终确认成功后刷新待办列表和 dashboard,标签与计数同步消失。
|
||||||
|
- [ ] 所有雪花 ID 均按字符串透传。
|
||||||
@ -0,0 +1,510 @@
|
|||||||
|
# 【#4938 前端对接·管理后台】车务创建、修改与最终确认返回订单调整基线差异
|
||||||
|
|
||||||
|
> Issue: [wx/HL#4938](https://git.1814.love:8443/wx/HL/issues/4938)
|
||||||
|
>
|
||||||
|
> PR: [wx/HL#5065](https://git.1814.love:8443/wx/HL/pulls/5065)
|
||||||
|
>
|
||||||
|
> 服务: `hl-fleet-service`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-18
|
||||||
|
>
|
||||||
|
> 影响范围: 车务直接派车、直接改派、`holding → assigned` 最终确认,以及订单调整后的日期、人数、车辆容量差异处理
|
||||||
|
>
|
||||||
|
> 状态: 后端已合并并部署测试环境;待管理后台按本文完成页面联调
|
||||||
|
|
||||||
|
## 一、前端对接结论
|
||||||
|
|
||||||
|
以下三个既有管理后台接口现在共用业务码 `605041` 返回结构化逐日差异:
|
||||||
|
|
||||||
|
| 操作 | 接口 | 触发 605041 的条件 |
|
||||||
|
|---|---|---|
|
||||||
|
| 创建派单 | `POST /admin/fleet/assignments` | `holdMode=0` 直接派车时最终基线不一致 |
|
||||||
|
| 修改派单 | `POST /admin/fleet/assignments/{assignmentId}/change` | `holdMode=0` 直接改派时最终基线不一致 |
|
||||||
|
| 最终确认 | `POST /admin/fleet/assignments/{assignmentId}/confirm` | 订单、需求、行程、逐日派单、人数或容量基线不一致 |
|
||||||
|
|
||||||
|
前端必须遵守以下判断:
|
||||||
|
|
||||||
|
1. 三个接口的 `605041` 当前均返回 HTTP 200,但统一响应体为 `code=605041`、`success=false`,不能只判断 HTTP 状态。
|
||||||
|
2. `605041` 的 `data` 不为空:
|
||||||
|
- create 返回 `AssignmentWriteRespVO`,保证 `data.dailyDifferences` 可读;
|
||||||
|
- change 返回 `ChangeAssignmentRespVO`,保证 `data.dailyDifferences` 可读;
|
||||||
|
- confirm 返回 `ConfirmRespVO`,保证 `data.confirmed=false` 和 `data.dailyDifferences` 可读。
|
||||||
|
3. `605041` 不会提交派单及其关联业务状态写入:
|
||||||
|
- create 不会新增或激活派单;
|
||||||
|
- change 不会取消旧派单,也不会生成可用的新派车版本;
|
||||||
|
- confirm 不会推进派单状态或确认时间;
|
||||||
|
- 三者都不会触发车辆/司机占用、保险、对账或订单派定结果变化。
|
||||||
|
- change 仍会按既有设计在独立事务保留一条 `CHANGE_FAILED` 操作审计;它不是有效派单版本,也不表示业务写入成功。
|
||||||
|
4. 收到 `605041` 后保持操作前页面状态,展示逐日差异,并重新拉取最新订单和车务详情。
|
||||||
|
5. 所有派单、车辆槽位、派车组、订单、车辆和司机雪花 ID 均按 JSON String 发送和读取,禁止转为 JavaScript `Number`。金额字段也按 String 读取。
|
||||||
|
|
||||||
|
## 二、605041 公共响应契约
|
||||||
|
|
||||||
|
### 2.1 统一响应外层
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||||
|
"data": {
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-22",
|
||||||
|
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": 5,
|
||||||
|
"capacityGap": 3,
|
||||||
|
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938f09a001",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- `data` 的完整类型取决于调用的是 create、change 还是 confirm,不能跨接口复用成功响应模型。
|
||||||
|
- 除本文件明确保证的失败字段外,其余成功态字段在 `605041` 时为空,前端不得用它们推断写入结果。
|
||||||
|
- `traceId` 用于反馈和日志定位,不参与业务判断。
|
||||||
|
|
||||||
|
### 2.2 `dailyDifferences[]`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `serviceDate` | String/null | 发生差异的服务日,格式 `yyyy-MM-dd`;无法定位到单日时为空 |
|
||||||
|
| `differenceType` | String | 差异类型,取值见下表 |
|
||||||
|
| `assignmentId` | String/null | 可定位到具体派单时返回;仅用于定位差异,不代表本次写入成功 |
|
||||||
|
| `assignmentSlotId` | String/null | 可定位到稳定车辆槽位时返回 |
|
||||||
|
| `passengerCount` | Integer/null | 当前比对使用的乘客人数,不含司机 |
|
||||||
|
| `passengerCapacity` | Integer/null | 当日车辆合计可载客人数,每辆车已扣除司机一座 |
|
||||||
|
| `capacityGap` | Integer/null | 缺少座位数,等于 `passengerCount - passengerCapacity` |
|
||||||
|
| `message` | String | 后端生成的差异说明,可直接辅助展示 |
|
||||||
|
|
||||||
|
差异类型:
|
||||||
|
|
||||||
|
| `differenceType` | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| `REQUIREMENT_VERSION_MISMATCH` | 当前生效用车需求已变化,或订单/需求已不可继续派单 |
|
||||||
|
| `ORDER_DATE_MISMATCH` | 订单当前日期与用车需求冻结日期不一致 |
|
||||||
|
| `ITINERARY_DATE_MISMATCH` | 逐日行程日期与用车需求冻结日期不一致,或缺少逐日行程 |
|
||||||
|
| `ASSIGNMENT_DATE_MISSING` | 某服务日缺少有效派单或车辆槽位 |
|
||||||
|
| `ASSIGNMENT_DATE_EXTRA` | 派单仍包含已不属于当前需求的服务日 |
|
||||||
|
| `HEADCOUNT_BASELINE_MISMATCH` | 订单当前人数、需求冻结人数或派单人数快照不一致 |
|
||||||
|
| `CAPACITY_INSUFFICIENT` | 当日所有车辆合计载客量不足 |
|
||||||
|
|
||||||
|
`dailyDifferences` 可能同时包含多种类型、多条服务日记录。前端应遍历数组展示,不得只取第一条,也不得自行重算人数或车辆容量。
|
||||||
|
|
||||||
|
## 三、创建派单 create
|
||||||
|
|
||||||
|
### 3.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2078001000000000101",
|
||||||
|
"orderNo": "26-0719",
|
||||||
|
"requirementId": "2078001000000000201",
|
||||||
|
"fleetItemIndex": 1,
|
||||||
|
"vehicleId": "2078001000000000301",
|
||||||
|
"driverId": "2078001000000000401",
|
||||||
|
"startDate": "2026-07-21",
|
||||||
|
"endDate": "2026-07-23",
|
||||||
|
"pickupAt": "海拉尔",
|
||||||
|
"dropoffAt": "满洲里",
|
||||||
|
"headcount": 8,
|
||||||
|
"protocolPrice": "1300.00",
|
||||||
|
"holdMode": 0,
|
||||||
|
"fromEntry": "from-board",
|
||||||
|
"skipCityJunctionException": false,
|
||||||
|
"strictSeats": true,
|
||||||
|
"confirmCrossResident": false,
|
||||||
|
"requestId": "fleet-create-4938-20260719-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`605041` 只适用于 `holdMode=0`。原有 `holdMode=1` 排车锁定流程不执行本次最终基线门禁。
|
||||||
|
|
||||||
|
### 3.2 holdMode=0 成功响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": "2078001000000000501",
|
||||||
|
"assignmentGroupId": "2078001000000000601",
|
||||||
|
"assignmentSlotId": "2078001000000000701",
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"stageCode": "assigned",
|
||||||
|
"stageLabel": "已派车",
|
||||||
|
"currentStep": 4,
|
||||||
|
"skippedStepCodes": [
|
||||||
|
"DRIVER_CONFIRMATION",
|
||||||
|
"DRIVER_CONFIRMATION_EVIDENCE"
|
||||||
|
],
|
||||||
|
"protocolPrice": "1300.00",
|
||||||
|
"holdSentAt": null,
|
||||||
|
"confirmedAt": "2026-07-19 14:20:00",
|
||||||
|
"sideEffects": {
|
||||||
|
"vehicleStatusUpdated": "busy",
|
||||||
|
"driverStatusUpdated": "busy",
|
||||||
|
"reconPrepRowsCreated": 0,
|
||||||
|
"reconPrepMarkedCanceled": null
|
||||||
|
},
|
||||||
|
"dailyDifferences": null
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938c200001",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
仅在 `code=200` 时把新派单加入页面;`id`、`assignmentGroupId`、`assignmentSlotId` 均按 String 保存。
|
||||||
|
|
||||||
|
### 3.3 605041 失败响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||||
|
"data": {
|
||||||
|
"id": null,
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"assignmentStatus": null,
|
||||||
|
"stageCode": null,
|
||||||
|
"stageLabel": null,
|
||||||
|
"currentStep": null,
|
||||||
|
"skippedStepCodes": null,
|
||||||
|
"protocolPrice": null,
|
||||||
|
"holdSentAt": null,
|
||||||
|
"confirmedAt": null,
|
||||||
|
"sideEffects": null,
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-22",
|
||||||
|
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": 5,
|
||||||
|
"capacityGap": 3,
|
||||||
|
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938c605041",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
此时不得把临时响应内容加入派单列表,不得本地占用车辆/司机;刷新后仍以服务端最新详情为准。
|
||||||
|
|
||||||
|
## 四、修改派单 change
|
||||||
|
|
||||||
|
### 4.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/2078001000000000501/change
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"effectiveDate": "2026-07-22",
|
||||||
|
"newVehicleId": "2078001000000000302",
|
||||||
|
"newDriverId": "2078001000000000402",
|
||||||
|
"holdMode": 0,
|
||||||
|
"protocolPrice": "1688.00",
|
||||||
|
"confirmCrossResident": false,
|
||||||
|
"reason": "订单调整后更换车辆和司机",
|
||||||
|
"requestId": "fleet-change-4938-20260719-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`newVehicleId`、`newDriverId` 至少传一个。`605041` 只适用于 `holdMode=0`;`holdMode=1` 仍按排车待司机确认流程处理。
|
||||||
|
|
||||||
|
### 4.2 holdMode=0 成功响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"assignmentId": "2078001000000000502",
|
||||||
|
"assignmentSlotId": "2078001000000000701",
|
||||||
|
"previousAssignmentGroupId": "2078001000000000601",
|
||||||
|
"newAssignmentGroupId": "2078001000000000602",
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"effectiveDate": "2026-07-22",
|
||||||
|
"affectedDays": 2,
|
||||||
|
"protocolPrice": "1688.00",
|
||||||
|
"otherVehicleCount": 1,
|
||||||
|
"warningCode": "ORDER_HAS_OTHER_VEHICLES",
|
||||||
|
"warningMessage": "该订单另有1个车辆槽位,当前仅修改本车辆,请核对其它车辆安排",
|
||||||
|
"otherVehicles": [
|
||||||
|
{
|
||||||
|
"assignmentSlotId": "2078001000000000702",
|
||||||
|
"vehiclePlate": "蒙B-66666",
|
||||||
|
"driverName": "李师傅",
|
||||||
|
"startDate": "2026-07-21",
|
||||||
|
"endDate": "2026-07-23"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dailyDifferences": null
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938a200001",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
change 成功响应没有 `confirmed` 和 `sideEffects` 字段。只有 `code=200` 时才能用新派车组替换页面中的旧版本;`warningCode=ORDER_HAS_OTHER_VEHICLES` 时继续保留既有强提示。
|
||||||
|
|
||||||
|
### 4.3 605041 失败响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||||
|
"data": {
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"previousAssignmentGroupId": null,
|
||||||
|
"newAssignmentGroupId": null,
|
||||||
|
"assignmentStatus": null,
|
||||||
|
"effectiveDate": null,
|
||||||
|
"affectedDays": null,
|
||||||
|
"protocolPrice": null,
|
||||||
|
"otherVehicleCount": null,
|
||||||
|
"warningCode": null,
|
||||||
|
"warningMessage": null,
|
||||||
|
"otherVehicles": null,
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": null,
|
||||||
|
"differenceType": "HEADCOUNT_BASELINE_MISMATCH",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": null,
|
||||||
|
"capacityGap": null,
|
||||||
|
"message": "订单当前人数与用车需求冻结人数不一致"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-22",
|
||||||
|
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": 5,
|
||||||
|
"capacityGap": 3,
|
||||||
|
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938a605041",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
此时旧派单和原派车组仍是有效业务状态。后端可能新增一条 `CHANGE_FAILED` 操作审计,但不会生成可用的新派车版本。不得用 `dailyDifferences[].assignmentId` 替换页面主键,也不得本地切换车辆、司机或状态。
|
||||||
|
|
||||||
|
## 五、最终确认 confirm
|
||||||
|
|
||||||
|
### 5.1 请求
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/2078001000000000501/confirm
|
||||||
|
Authorization: Bearer <admin-token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "fleet-final-confirm-4938-20260719-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `assignmentId` | Path | String | 是 | 有效派单 ID | 当前派车组中的任一派单 ID |
|
||||||
|
| `requestId` | Body | String | 是 | 非空,最长 64 | 最终确认幂等标识 |
|
||||||
|
|
||||||
|
### 5.2 成功响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"confirmed": true,
|
||||||
|
"assignmentStatus": "assigned",
|
||||||
|
"stageCode": "assigned",
|
||||||
|
"stageLabel": "已派车",
|
||||||
|
"currentStep": 4,
|
||||||
|
"assignmentGroupId": "2078001000000000601",
|
||||||
|
"confirmedAt": "2026-07-19 14:30:00",
|
||||||
|
"itineraryUrl": "https://h5.example.com/#/itinerary/<signed-token>",
|
||||||
|
"sideEffects": {
|
||||||
|
"vehicleStatusUpdated": "busy",
|
||||||
|
"driverStatusUpdated": "busy",
|
||||||
|
"reconPrepRowsCreated": 0,
|
||||||
|
"reconPrepMarkedCanceled": null
|
||||||
|
},
|
||||||
|
"dailyDifferences": null
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938f200001",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`itineraryUrl` 签发配置不可用时允许为空,不影响确认成功。前端只有在 `code=200 && data.confirmed===true` 时展示最终确认成功,并刷新派单详情、看板列表和汇总。
|
||||||
|
|
||||||
|
### 5.3 日期、人数与容量同时存在差异
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||||
|
"data": {
|
||||||
|
"confirmed": false,
|
||||||
|
"assignmentStatus": null,
|
||||||
|
"stageCode": null,
|
||||||
|
"stageLabel": null,
|
||||||
|
"currentStep": null,
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"confirmedAt": null,
|
||||||
|
"itineraryUrl": null,
|
||||||
|
"sideEffects": null,
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-21",
|
||||||
|
"differenceType": "ORDER_DATE_MISMATCH",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": null,
|
||||||
|
"passengerCapacity": null,
|
||||||
|
"capacityGap": null,
|
||||||
|
"message": "订单当前日期与用车需求冻结日期不一致"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"serviceDate": null,
|
||||||
|
"differenceType": "HEADCOUNT_BASELINE_MISMATCH",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": null,
|
||||||
|
"capacityGap": null,
|
||||||
|
"message": "订单当前人数与用车需求冻结人数不一致"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-22",
|
||||||
|
"differenceType": "CAPACITY_INSUFFICIENT",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": 8,
|
||||||
|
"passengerCapacity": 5,
|
||||||
|
"capacityGap": 3,
|
||||||
|
"message": "车辆载客量不足,已按每车司机占一座计算"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938f605041",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 缺少某日车辆槽位
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605041,
|
||||||
|
"message": "订单行程或用车派单已变化,请按逐日差异处理后重试",
|
||||||
|
"data": {
|
||||||
|
"confirmed": false,
|
||||||
|
"assignmentStatus": null,
|
||||||
|
"stageCode": null,
|
||||||
|
"stageLabel": null,
|
||||||
|
"currentStep": null,
|
||||||
|
"assignmentGroupId": null,
|
||||||
|
"confirmedAt": null,
|
||||||
|
"itineraryUrl": null,
|
||||||
|
"sideEffects": null,
|
||||||
|
"dailyDifferences": [
|
||||||
|
{
|
||||||
|
"serviceDate": "2026-07-23",
|
||||||
|
"differenceType": "ASSIGNMENT_DATE_MISSING",
|
||||||
|
"assignmentId": null,
|
||||||
|
"assignmentSlotId": null,
|
||||||
|
"passengerCount": null,
|
||||||
|
"passengerCapacity": null,
|
||||||
|
"capacityGap": null,
|
||||||
|
"message": "该服务日缺少第2个车辆槽位派单"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traceId": "7db459fd-0b8e-4f29-9c46-4938f605042",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、前端统一处理顺序
|
||||||
|
|
||||||
|
1. 先判断业务 `code`,再读取端点自己的 `data`:
|
||||||
|
- `code=200`:按对应成功模型处理;
|
||||||
|
- `code=605041`:按对应失败模型读取 `dailyDifferences`;
|
||||||
|
- 其他业务码:继续走既有错误处理。
|
||||||
|
2. `605041` 时不要乐观更新:
|
||||||
|
- create 不新增本地派单;
|
||||||
|
- change 不替换旧派单;
|
||||||
|
- confirm 不改为 `assigned` 或“已确认”。
|
||||||
|
3. 展示顶层 `message`,并按 `dailyDifferences` 列出服务日、差异类型、人数、容量和缺口;字段为空时隐藏对应展示项。
|
||||||
|
4. 重新请求最新订单和车务详情,避免继续使用操作前缓存。
|
||||||
|
5. 用户修正订单日期、行程、人数或派单后,生成新的 `requestId` 再提交新动作;仅在同一次动作网络结果不确定时复用原 `requestId`。
|
||||||
|
|
||||||
|
## 七、其他直接错误分支
|
||||||
|
|
||||||
|
| 业务码 | 常见场景 | 前端处理 |
|
||||||
|
|---|---|---|
|
||||||
|
| `605009` | 派单不存在 | 刷新详情和看板,停止操作旧记录 |
|
||||||
|
| `605020` | 当前状态不允许操作 | 刷新最新生命周期与操作能力 |
|
||||||
|
| `605025` | 最终确认前缺少司机确认或有效凭证 | 引导先完成司机确认与凭证登记 |
|
||||||
|
| `605001` / `605003` | 车辆或司机档期冲突 | 展示后端错误并重新选车/司机 |
|
||||||
|
| `605036` | 跨常驻车未显式确认 | 二次提示后携带 `confirmCrossResident=true` 重试 |
|
||||||
|
| `605041` | 订单、需求、行程、逐日派单、人数或容量基线不一致 | 使用当前端点的 `data.dailyDifferences` 展示并处理 |
|
||||||
|
|
||||||
|
请求字段为空、格式不正确或超过长度限制时走统一参数校验错误,前端应在发请求前完成同样约束。
|
||||||
|
|
||||||
|
## 八、不影响范围
|
||||||
|
|
||||||
|
- 不新增管理后台接口,三个接口的请求字段结构保持不变。
|
||||||
|
- `holdMode=1` 的排车锁定、司机确认与凭证登记流程保持不变。
|
||||||
|
- 不改变取消、司机拒接、驳回需求、撤销取消和提前完结的前端调用契约。
|
||||||
|
- 不要求前端计算订单人数、逐日服务日期或车辆载客量,这些均由后端权威校验并返回差异。
|
||||||
|
- 本文件只描述管理后台直接消费的 HTTP 契约,不包含服务间调用或发布实现细节。
|
||||||
|
|
||||||
|
## 九、验收状态与待补证据
|
||||||
|
|
||||||
|
已完成:
|
||||||
|
|
||||||
|
- 当前 worktree 中 create、change、confirm Controller 的 `605041` 强类型 `data` 静态核对。
|
||||||
|
- `AssignmentWriteRespVO`、`ChangeAssignmentRespVO`、`ConfirmRespVO` 与 `dailyDifferences` 字段静态核对。
|
||||||
|
- 三条失败路径不提交派单及其关联业务状态变化的源码顺序核对。
|
||||||
|
- 后端 PR #5065 已合并至 `dev-v3`;Fleet 双实例 `8087/8187` 已完成滚动部署并通过健康/Nacos 验证。
|
||||||
|
- 最终源码指纹下 Fleet `verify` 1909/1909、Order-v3 受影响回归 438/438、User Quartz 桥接 5/5 均通过。
|
||||||
|
|
||||||
|
前端联调仍需补充:
|
||||||
|
|
||||||
|
- 三个接口在测试环境 OpenAPI 中的请求/响应模型截图或导出差异。
|
||||||
|
- 经网关分别取得 create、change、confirm 的成功响应和 `605041` 真实响应,记录 HTTP 状态、业务码、`traceId` 与完整 `data`。
|
||||||
|
- 对 `605041` 前后做测试业务数据对照,确认派单版本/状态、车辆司机占用、保险、对账和订单派定结果未发生变化。
|
||||||
|
- 管理后台页面联调证据:差异列表展示、空字段处理、刷新行为、禁止乐观更新,以及修正后重新提交成功。
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
# 【修改接口·管理后台】订单工作台统计口径与金额字符串收口(#5062)
|
||||||
|
|
||||||
|
> Issue: [wx/HL#5062](https://git.1814.love:8443/wx/HL/issues/5062)
|
||||||
|
>
|
||||||
|
> 服务: `hl-user-service`、`hl-order-service-v3`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-18
|
||||||
|
>
|
||||||
|
> 影响入口: `GET /admin/profile/dashboard?period={today|week|month}`
|
||||||
|
|
||||||
|
## 一、前端结论
|
||||||
|
|
||||||
|
1. 路径、请求参数和角色分流不变,不需要新增接口调用。
|
||||||
|
2. GMV/收入改按真实收款时间归属:线上只统计成功支付,线下只统计未撤销收款;不再按订单创建时间归属订单累计实付。
|
||||||
|
3. 退款改按真实成功退款时间归属;财务近 30 天趋势返回真实每日收入和退款。
|
||||||
|
4. 所有金额字段固定按 JSON String 处理;比例 `gmvDiffRate` 仍为 JSON Number。
|
||||||
|
5. 雪花 ID(例如排行 `adminId`、即将出行 `orderId`)固定按 JSON String 处理,禁止转换为 JavaScript `Number`。
|
||||||
|
6. 排行订单数为期间发生有效收款的订单去重数,同一订单多笔收款只计一单、金额全部累加。
|
||||||
|
7. 权威统计源不可用时接口失败关闭,不会用部分成功数据或全零数据伪装成功。
|
||||||
|
|
||||||
|
## 二、受影响字段
|
||||||
|
|
||||||
|
### 2.1 ADMIN / CUSTOMIZER 工作台
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `overview.gmv` | String | 当前 period 内真实收款金额 |
|
||||||
|
| `overview.gmvDiffRate` | Number | 与上一等长期间相比的变化比例 |
|
||||||
|
| `trend[].gmv` | String | 对应日期的真实收款金额 |
|
||||||
|
| `ranking[].adminId` | String | 定制师雪花 ID |
|
||||||
|
| `ranking[].gmv` | String | 对应定制师期间真实收款金额 |
|
||||||
|
| `ranking[].orderCount` | Number | 发生有效收款的去重订单数 |
|
||||||
|
| `ranking[].avatar` | String/null | 定制师头像;用户信息降级时允许为空 |
|
||||||
|
| `upcomingTrips[].orderId` | String | 订单雪花 ID |
|
||||||
|
|
||||||
|
### 2.2 FINANCE 工作台
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `periodIncome` | String | 当前 period 内真实收入 |
|
||||||
|
| `periodRefund` | String | 当前 period 内成功退款 |
|
||||||
|
| `monthIncome` | String | 自然月真实收入 |
|
||||||
|
| `monthRefund` | String | 自然月成功退款 |
|
||||||
|
| `financeTrend[].date` | String | 日期,`yyyy-MM-dd` |
|
||||||
|
| `financeTrend[].income` | String | 当日真实收入 |
|
||||||
|
| `financeTrend[].refund` | String | 当日成功退款 |
|
||||||
|
|
||||||
|
## 三、响应片段
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"role": "FINANCE",
|
||||||
|
"periodIncome": "128000.00",
|
||||||
|
"periodRefund": "5600.00",
|
||||||
|
"monthIncome": "328000.00",
|
||||||
|
"monthRefund": "8600.00",
|
||||||
|
"financeTrend": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-18",
|
||||||
|
"income": "12000.00",
|
||||||
|
"refund": "600.00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、前端检查清单
|
||||||
|
|
||||||
|
- [ ] 金额展示使用字符串格式化,不执行 `Number(amount)`。
|
||||||
|
- [ ] `gmvDiffRate` 继续按 Number 计算百分比。
|
||||||
|
- [ ] 所有 Long ID 保持字符串透传到路由和请求参数。
|
||||||
|
- [ ] 不再用订单创建日解释趋势 GMV;趋势日期是支付/收款发生日。
|
||||||
|
- [ ] 财务趋势同时渲染 `income` 与 `refund`,空日后端返回 `"0.00"`。
|
||||||
|
- [ ] 接口业务失败时展示重试,不把缺失统计源当作全零成功。
|
||||||
|
|
||||||
|
## 五、后端验证
|
||||||
|
|
||||||
|
- Dashboard、User 聚合、支付/退款 Feign、序列化与失败关闭相关测试已通过。
|
||||||
|
- Issue #5062 最终五模块全量测试:14,518 个测试,0 失败、0 错误;Fleet Reactor verify:1,824 个测试,0 失败、0 错误、0 跳过。
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
# 房务 bug房务:管理后台修复清单
|
||||||
|
|
||||||
|
## 来源
|
||||||
|
|
||||||
|
桌面文件:`bug房务.docx`。
|
||||||
|
|
||||||
|
## 后端当前状态
|
||||||
|
|
||||||
|
`dev-v3` 已包含房务需求版本、改期平移、库存原子迁移、增减晚次、人数变化、作废需求保护、返工待办互斥和最终确认动作契约修复。TEST 回归数据由 Codex 生成,5 条王骁订单已进入 `PENDING / PENDING_CLAIM` 抢单池。
|
||||||
|
|
||||||
|
## 管理后台必须修复
|
||||||
|
|
||||||
|
### 1. 酒店多房型展示
|
||||||
|
|
||||||
|
当定制师选择同一酒店的多个房型时,房务卡片必须按 `hotelId + roomTypeId` 展开,禁止把多个房型合并为一个房型后叠加房间数。展示的房型名称、房间数、价格和晚次必须与需求明细逐项对应。
|
||||||
|
|
||||||
|
### 2. 待办标签颜色
|
||||||
|
|
||||||
|
按后端 `todoType` 使用统一颜色:待配房、需求变更重配、待最终确认、酒店超时、异常/取消必须视觉可区分;不能只显示文字而丢失优先级。
|
||||||
|
|
||||||
|
### 3. 指定酒店但不指定房型
|
||||||
|
|
||||||
|
酒店已指定、房型为空时,仍应允许进入房务流程;候选列表限定指定酒店,房型由房务选择。不能把“房型为空”误判为需求无效。
|
||||||
|
|
||||||
|
### 4. 最终确认后修改
|
||||||
|
|
||||||
|
已最终确认订单进入详情后,仍需显示“修改/替换酒店、调整房型、修改房间数、清空配房”入口。操作前调用重新询房/重开接口,成功后刷新详情;不能在前端用 `m.finalized` 直接隐藏或禁用所有修改入口。
|
||||||
|
|
||||||
|
重点文件:`src/views/housekeeper/components/OrderDetailModal.vue` 中 `canClearAssignments`、修改入口和 `ensureRequirementEditable` 的状态判断必须统一。
|
||||||
|
|
||||||
|
### 5. 作废需求展示
|
||||||
|
|
||||||
|
作废需求必须展示:
|
||||||
|
|
||||||
|
- 作废状态和红色视觉标记
|
||||||
|
- 准确易懂的作废原因,例如“定制师修改住宿需求,原房务需求已作废”
|
||||||
|
- 仅保留“查看”操作
|
||||||
|
- 隐藏领取、配房、替换、清空、确认、最终确认等所有写操作
|
||||||
|
|
||||||
|
### 6. 改出发日期
|
||||||
|
|
||||||
|
改期后页面必须展示新日期;当前有效晚次的配房日期由后端按 `dayNumber` 对齐。原日期库存先恢复,新日期库存全部预占成功后才提交;失败时订单、配房和库存保持原状。页面必须展示“已改期,配房需按新日期重新确认”的说明。
|
||||||
|
|
||||||
|
### 7. 增加出行人数
|
||||||
|
|
||||||
|
订单调整摘要和房务详情必须显示“增加 X 人”,同时展示调整前人数、调整后人数和新增出行人;既有酒店、房型、房间数和库存字段不得丢失。
|
||||||
|
|
||||||
|
### 8. 增加行程天数
|
||||||
|
|
||||||
|
必须展示“新增第 N 晚住宿,新增日期待配房”;原日期配房保留,新增晚次为空白候选,未完成新增晚次时禁止最终确认。
|
||||||
|
|
||||||
|
## 验收订单
|
||||||
|
|
||||||
|
使用以下 5 条 TEST 订单逐项验证:
|
||||||
|
|
||||||
|
- `HL20260719092421973`
|
||||||
|
- `HL20260719092425403`
|
||||||
|
- `HL20260719092428569`
|
||||||
|
- `HL20260719092431853`
|
||||||
|
- `HL20260719092435001`
|
||||||
|
|
||||||
|
每条订单均为定制师“王骁”,已模拟支付、补全出行人并提交住宿需求。
|
||||||
|
|
||||||
|
## 验收要求
|
||||||
|
|
||||||
|
必须同时提供:
|
||||||
|
|
||||||
|
1. 房务首页截图
|
||||||
|
2. 待办列表截图
|
||||||
|
3. 订单详情截图
|
||||||
|
4. 改期/增人/增晚前后对比截图
|
||||||
|
5. 浏览器 Network 请求确认只提交 `dayNumber`,不由前端提交 `stayDate`
|
||||||
|
6. 最终确认后订单从待办和工作台消失
|
||||||
@ -0,0 +1,244 @@
|
|||||||
|
# 【修改接口·管理后台】核团核算状态改用 `review_status`(#5066)
|
||||||
|
|
||||||
|
> **PR**: [#5067](https://git.1814.love:8443/wx/HL/pulls/5067) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-19 10:22
|
||||||
|
|
||||||
|
## 1. 关键变化
|
||||||
|
|
||||||
|
> ⚠️ 两个接口的字段名 `settlementStatus` / `settlementStatusName` 均保持不变,但字段的数据来源、可选枚举和业务语义已经变化。前端不得继续复用财务结算状态字典。
|
||||||
|
|
||||||
|
- 核团核算状态的数据来源由 `order_main.settlement_status` 改为 `order_main.review_status`。
|
||||||
|
- 页面状态统一为:
|
||||||
|
- `PENDING`:待核算
|
||||||
|
- `IN_PROGRESS`:核算中
|
||||||
|
- `COMPLETED`:已完成
|
||||||
|
- 列表查询参数名仍为 `settlementStatus`,但合法值改为 `PENDING / IN_PROGRESS / COMPLETED`。
|
||||||
|
- 旧值 `NONE` 不再是合法查询参数;历史 `review_status = NONE / NULL` 的订单统一投影为 `PENDING / 待核算`。
|
||||||
|
- 本次仅调整核团页面的查询和返回投影,不修改财务复核及结算完成所使用的 `settlement_status`。
|
||||||
|
|
||||||
|
## 2. 变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 核团核算任务列表 | GET | `/v3/admin/order-settlement/tasks` | 修改接口 | 筛选和返回状态改用 `review_status`;参数名 `settlementStatus` 保持不变 |
|
||||||
|
| 2 | 查询核团详情 | GET | `/v3/admin/order/{orderId}/settlement/return-detail` | 修改接口 | `orderInfo` 中的核算状态改用 `review_status` 投影 |
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 核团核算任务列表
|
||||||
|
|
||||||
|
`GET /v3/admin/order-settlement/tasks`
|
||||||
|
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:是,只读查询。
|
||||||
|
- **请求体**:无。
|
||||||
|
- **响应结构**:`Result<PageResult<SettlementTaskRespVO>>`。
|
||||||
|
- 分页、关键词、出发日期筛选和列表范围均保持不变。
|
||||||
|
|
||||||
|
#### Query 入参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 合法值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `settlementStatus` | string | 否 | `PENDING` / `IN_PROGRESS` / `COMPLETED` | 核团页面核算状态;字段名保留,实际筛选 `order_main.review_status` |
|
||||||
|
|
||||||
|
其他 Query 参数保持不变:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `page` | number | 否 | 当前页码,默认 `1` |
|
||||||
|
| `pageSize` | number | 否 | 每页条数,默认 `20`,范围 `1` 到 `100` |
|
||||||
|
| `keyword` | string | 否 | 按订单号、团号、产品名模糊查询 |
|
||||||
|
| `departureDateFrom` | string | 否 | 出发日期开始,格式 `yyyy-MM-dd` |
|
||||||
|
| `departureDateTo` | string | 否 | 出发日期结束,格式 `yyyy-MM-dd` |
|
||||||
|
|
||||||
|
#### 受影响的响应字段
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 修改后说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `data.records[].settlementStatus` | string | 核团核算状态:`PENDING / IN_PROGRESS / COMPLETED` |
|
||||||
|
| `data.records[].settlementStatusName` | string | 页面文案:`待核算 / 核算中 / 已完成` |
|
||||||
|
|
||||||
|
列表中的其他字段、分页结构和排序规则均保持不变。
|
||||||
|
|
||||||
|
#### 请求与响应示例
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order-settlement/tasks?page=1&pageSize=10&settlementStatus=IN_PROGRESS
|
||||||
|
Authorization: Bearer <JWT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应片段**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"orderId": "2077233886248534018",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"teamNo": "T20260718001",
|
||||||
|
"productName": "呼伦贝尔草原 5 日游",
|
||||||
|
"departureDate": "2026-07-20",
|
||||||
|
"returnDate": "2026-07-24",
|
||||||
|
"peopleCount": 3,
|
||||||
|
"peopleSummary": "2成人1婴儿",
|
||||||
|
"systemBalanceAmount": 0.00,
|
||||||
|
"settlementStatus": "IN_PROGRESS",
|
||||||
|
"settlementStatusName": "核算中"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 查询核团详情
|
||||||
|
|
||||||
|
`GET /v3/admin/order/{orderId}/settlement/return-detail`
|
||||||
|
|
||||||
|
- **认证**:需要管理后台 JWT。
|
||||||
|
- **幂等性**:是,只读查询。
|
||||||
|
- **请求体**:无。
|
||||||
|
- **路径参数和响应整体结构保持不变。**
|
||||||
|
|
||||||
|
#### 受影响的响应字段
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 修改后说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `data.orderInfo.settlementStatus` | string | 核团核算状态:`PENDING / IN_PROGRESS / COMPLETED`,来源为 `review_status` |
|
||||||
|
| `data.orderInfo.settlementStatusName` | string | 页面文案:`待核算 / 核算中 / 已完成` |
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"orderInfo": {
|
||||||
|
"orderId": "2077233886248534018",
|
||||||
|
"orderNo": "HL202607180001",
|
||||||
|
"settlementStatus": "COMPLETED",
|
||||||
|
"settlementStatusName": "已完成"
|
||||||
|
},
|
||||||
|
"travelers": [],
|
||||||
|
"driverVehicles": [],
|
||||||
|
"receivableItems": [],
|
||||||
|
"collectionRecords": []
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 示例仅展示本次相关字段;详情接口原有的订单信息、出行人、司机车辆、应收和收款字段均保持不变。
|
||||||
|
|
||||||
|
## 4. 枚举与状态映射
|
||||||
|
|
||||||
|
| `settlementStatus` | `settlementStatusName` | 核团页面语义 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PENDING` | 待核算 | 尚未开始核算 |
|
||||||
|
| `IN_PROGRESS` | 核算中 | 已开始录入或处理核算数据 |
|
||||||
|
| `COMPLETED` | 已完成 | 核单已经提交完成 |
|
||||||
|
|
||||||
|
### 历史数据兼容
|
||||||
|
|
||||||
|
| `order_main.review_status` 实际值 | 接口返回 `settlementStatus` | 接口返回 `settlementStatusName` |
|
||||||
|
|---|---|---|
|
||||||
|
| `NULL`、空值或 `NONE` | `PENDING` | 待核算 |
|
||||||
|
| `PENDING` | `PENDING` | 待核算 |
|
||||||
|
| `IN_PROGRESS` | `IN_PROGRESS` | 核算中 |
|
||||||
|
| `COMPLETED` | `COMPLETED` | 已完成 |
|
||||||
|
|
||||||
|
### 列表筛选规则
|
||||||
|
|
||||||
|
| Query 参数 | 后端筛选行为 |
|
||||||
|
|---|---|
|
||||||
|
| 不传 `settlementStatus` | 不追加核算状态过滤,返回符合其他条件的任务 |
|
||||||
|
| `PENDING` | 匹配 `review_status = PENDING / NONE / NULL`,兼容历史订单 |
|
||||||
|
| `IN_PROGRESS` | 精确匹配 `review_status = IN_PROGRESS` |
|
||||||
|
| `COMPLETED` | 精确匹配 `review_status = COMPLETED` |
|
||||||
|
| `NONE` 或其他值 | 参数校验失败,HTTP 200、业务码 `400` |
|
||||||
|
|
||||||
|
## 5. 修改前后对比
|
||||||
|
|
||||||
|
| 项目 | 修改前 | 修改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| 接口字段名 | `settlementStatus` / `settlementStatusName` | 保持不变 |
|
||||||
|
| 状态数据源 | 财务结算态 `settlement_status` | 核团核算流程态 `review_status` |
|
||||||
|
| 查询参数枚举 | `NONE / PENDING / COMPLETED` | `PENDING / IN_PROGRESS / COMPLETED` |
|
||||||
|
| `PENDING` 文案/语义 | 待财务复核 | 待核算 |
|
||||||
|
| `COMPLETED` 文案/语义 | 已结算 | 已完成 |
|
||||||
|
| 处理中状态 | 无独立值 | 新增 `IN_PROGRESS / 核算中` |
|
||||||
|
| 历史 `NONE / NULL` 返回值 | `NONE / 未结算` | 归一为 `PENDING / 待核算` |
|
||||||
|
|
||||||
|
## 6. 前端适配清单
|
||||||
|
|
||||||
|
- [ ] 核团状态下拉改为 `PENDING / IN_PROGRESS / COMPLETED`。
|
||||||
|
- [ ] 下拉文案依次使用“待核算 / 核算中 / 已完成”。
|
||||||
|
- [ ] 删除核团页面向接口传递 `NONE` 的逻辑。
|
||||||
|
- [ ] 不修改 Query 参数名,继续传 `settlementStatus`。
|
||||||
|
- [ ] 不修改响应字段名,继续读取 `settlementStatus` 和 `settlementStatusName`。
|
||||||
|
- [ ] 不再复用财务结算状态字典解释这两个核团接口。
|
||||||
|
- [ ] 若前端自行维护状态文案,必须同步更新;优先使用后端返回的 `settlementStatusName`。
|
||||||
|
- [ ] 对历史未开始核算的订单统一按 `PENDING / 待核算` 展示。
|
||||||
|
|
||||||
|
## 7. 错误与边界行为
|
||||||
|
|
||||||
|
| 场景 | 行为 |
|
||||||
|
|---|---|
|
||||||
|
| 未传 `settlementStatus` | 正常查询,不按核算状态过滤 |
|
||||||
|
| 传 `settlementStatus=NONE` | 参数校验失败,HTTP 200、业务码 `400` |
|
||||||
|
| 传其他非法状态 | 参数校验失败,HTTP 200、业务码 `400` |
|
||||||
|
| 历史 `review_status=NONE/NULL` | 列表和详情均返回 `PENDING / 待核算` |
|
||||||
|
| 房务角色访问 | 保持原权限规则,不因本次变更放开 |
|
||||||
|
| 订单不存在 | 详情接口保持原订单不存在错误 |
|
||||||
|
| 空列表 | 返回成功响应,`records=[]` |
|
||||||
|
|
||||||
|
## 8. 不影响范围
|
||||||
|
|
||||||
|
- 财务复核和财务结算完成仍使用 `order_main.settlement_status`。
|
||||||
|
- 核单提交后写入 `settlement_status=PENDING`、财务确认后写入 `settlement_status=COMPLETED` 的流程不变。
|
||||||
|
- 两个接口的 URL、HTTP 方法、认证方式、分页结构和其他字段均不变。
|
||||||
|
- 不涉及数据库表结构或数据迁移。
|
||||||
|
- 不影响核团详情中的出行人、司机车辆、应收明细和收款明细契约。
|
||||||
|
- 不影响其他财务页面对 `settlementStatus` 的既有使用;本次语义仅适用于本文列出的两个核团接口。
|
||||||
|
|
||||||
|
## 9. 影响评估与回滚
|
||||||
|
|
||||||
|
- **字段结构是否破坏兼容**:否,字段名和 JSON 类型不变。
|
||||||
|
- **业务语义是否变化**:是,同名字段的数据来源、枚举和中文含义均发生变化。
|
||||||
|
- **前端是否需要同步适配**:是,核团状态下拉和本地状态字典必须同步。
|
||||||
|
- **是否影响已有数据**:不改写已有数据;读取时兼容历史 `NONE / NULL`。
|
||||||
|
- 回滚 PR #5067 后,两个接口会重新使用旧的财务结算状态语义。
|
||||||
|
- 因 `PENDING`、`COMPLETED` 是同名但不同含义的值,前后端版本回滚必须同步,不能仅根据字段是否存在判断版本。
|
||||||
|
- 无数据库迁移,无需清理或恢复数据。
|
||||||
|
|
||||||
|
## 10. 后端验证与发布状态
|
||||||
|
|
||||||
|
- PR #5067 原定向测试:48 tests,0 failures,0 errors。
|
||||||
|
- 与审计分支融合后的核团状态/快照/Feign 定向测试:121 tests,0 failures,0 errors。
|
||||||
|
- 融合后的 `hl-order-service-v3` 全量测试:5,797 tests,0 failures,0 errors,15 条件跳过。
|
||||||
|
- PR #5067 已于 2026-07-19 10:22 合并到 `dev-v3`。
|
||||||
|
- 本文未取得测试环境部署或网关真实接口调用证据;合并完成不等同于测试环境已经生效。
|
||||||
|
|
||||||
|
## 11. 历史契约说明
|
||||||
|
|
||||||
|
| 文档/PR | 说明 | 当前有效性 |
|
||||||
|
|---|---|---|
|
||||||
|
| Changelog `18_5055_核团核算列表详情-修改接口-管理后台.md` / PR #5058 | 首次交付核团列表与详情聚合接口 | 接口结构及非状态字段仍有效 |
|
||||||
|
| 上述文档中的 `settlementStatus` 枚举和示例 | 使用 `NONE / PENDING / COMPLETED` 及“未结算 / 待财务复核 / 已结算” | 已被本文纠正,不再作为核团页面契约 |
|
||||||
|
| PR #5067 / Issue #5066 | 核团核算状态改用 `review_status` | 当前最新契约 |
|
||||||
|
|
||||||
|
## 12. 关联链接
|
||||||
|
|
||||||
|
- **Issue**: [#5066](https://git.1814.love:8443/wx/HL/issues/5066)
|
||||||
|
- **PR**: [#5067](https://git.1814.love:8443/wx/HL/pulls/5067)
|
||||||
|
- **Merge commit**: [f60241f3d](https://git.1814.love:8443/wx/HL/commit/f60241f3d6fdb2f36c091dc93d0232f2dcfe4775)
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
# 房务契约补充:作废与订单调整历史字段
|
||||||
|
|
||||||
|
前端反馈“作废、人数、改期、增晚缺少明确出参”。现按当前后端实现明确如下,禁止按页面文案猜测:
|
||||||
|
|
||||||
|
## 房务详情/需求历史
|
||||||
|
|
||||||
|
接口:`GET /admin/house/orders/{orderId}`、`GET /admin/house/orders/{orderId}/requirement-history`
|
||||||
|
|
||||||
|
- `requirement.recentHistory[].status`:`PENDING`、`PROCESSING`、`DONE`、`REJECTED_TO_CONSULTANT`、`REJECTED_TO_ADMIN`、`SUPERSEDED`。
|
||||||
|
- `requirement.recentHistory[].returnReason`:退回/驳回原因;未退回为 `null`。
|
||||||
|
- `requirement.recentHistory[].returnedBy`、`returnedAt`:退回操作人和时间;未退回为 `null`。
|
||||||
|
- 作废订单本身使用 `order.status` 及 `statusLabel`;房务流程使用 `progress.houseStatus` 及 `houseStatusLabel`,不能把二者混用。
|
||||||
|
|
||||||
|
## 人数、改期、增晚的调整记录
|
||||||
|
|
||||||
|
接口:`GET /v3/admin/order/{orderId}/adjustment-record`
|
||||||
|
|
||||||
|
`items[].type` 是稳定枚举,`label/before/after` 已由后端生成,前端直接展示:
|
||||||
|
|
||||||
|
- `HEADCOUNT`:人数变化;`before/after` 为调整前后人数摘要。
|
||||||
|
- `DEPART_DATE`:改期;`before/after` 为旧/新出发日期。
|
||||||
|
- `TRIP_DAYS`:增减行程天数;`before/after` 为旧/新“X天Y晚”摘要。
|
||||||
|
- `TRAVELER_EDIT`:仅出行人资料字段编辑,不代表人数变化。
|
||||||
|
- `HOTEL_REQ`:住宿需求调整。
|
||||||
|
|
||||||
|
调整记录同时返回 `occurredAt`、`changeCount`、`statusNote`、`balanceBefore`、`balanceAfter`。新增出行人明细不从房务详情猜测,使用既有订单出行人接口;`HEADCOUNT` 记录用于展示人数前后变化。
|
||||||
|
|
||||||
|
## 验收说明
|
||||||
|
|
||||||
|
当前后端测试环境已有登录态,后续验收使用仓库 CDP 调试浏览器和 Network 证据;不得因普通浏览器无登录态改用插件或猜测实现。
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
# 【修改接口·管理后台】核团详情出行人补充出生日期和年龄(#5068)
|
||||||
|
|
||||||
|
> **Issue**: [#5068](https://git.1814.love:8443/wx/HL/issues/5068) | **PR**: [#5069](https://git.1814.love:8443/wx/HL/pulls/5069) | **服务**: `hl-order-service-v3` | **更新时间**: 2026-07-19 11:23
|
||||||
|
|
||||||
|
## 1. 关键变化
|
||||||
|
|
||||||
|
- 核团详情 `travelers[]` 新增可空字段 `birthday` 和 `age`。
|
||||||
|
- `birthday` 为出行人出生日期,格式 `yyyy-MM-dd`。
|
||||||
|
- `age` 为按订单出发日期计算的周岁。
|
||||||
|
- 出生日期为空、订单出发日期为空,或出生日期晚于出发日期时,`age` 返回 `null`。
|
||||||
|
- 出行人手机号和证件号继续沿用原有脱敏规则。
|
||||||
|
|
||||||
|
## 2. 受影响接口
|
||||||
|
|
||||||
|
`GET /v3/admin/order/{orderId}/settlement/return-detail`
|
||||||
|
|
||||||
|
- HTTP 方法、URL、认证、路径参数及响应整体结构均不变。
|
||||||
|
- 本次只增加 `data.travelers[]` 的响应字段,不增加请求参数。
|
||||||
|
|
||||||
|
## 3. 新增响应字段
|
||||||
|
|
||||||
|
| 字段 | JSON 类型 | 是否可空 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `data.travelers[].birthday` | string | 是 | 出生日期,格式 `yyyy-MM-dd` |
|
||||||
|
| `data.travelers[].age` | number | 是 | 以订单出发日期为基准计算的周岁 |
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"travelers": [
|
||||||
|
{
|
||||||
|
"travelerId": "71001",
|
||||||
|
"travelerName": "张三",
|
||||||
|
"travelerType": "ADULT",
|
||||||
|
"travelerTypeName": "成人",
|
||||||
|
"birthday": "1990-07-20",
|
||||||
|
"age": 36,
|
||||||
|
"idType": "ID_CARD",
|
||||||
|
"idTypeName": "身份证",
|
||||||
|
"phone": "138****1234",
|
||||||
|
"idCardNo": "110***********1234"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 示例仅展示本次相关结构;核团详情中的订单、司机车辆、应收和收款等字段保持不变。
|
||||||
|
|
||||||
|
## 4. 年龄计算与空值边界
|
||||||
|
|
||||||
|
| 场景 | `birthday` | `age` |
|
||||||
|
|---|---|---|
|
||||||
|
| 出生日期和订单出发日期均有效 | 返回出生日期 | 返回两个日期之间的完整周岁 |
|
||||||
|
| 出生日期为空 | `null` | `null` |
|
||||||
|
| 订单出发日期为空 | 返回出生日期 | `null` |
|
||||||
|
| 出生日期晚于订单出发日期 | 返回出生日期 | `null` |
|
||||||
|
|
||||||
|
当前已合并实现不会在订单出发日期缺失时改用服务器当前日期。Issue #5068 初始描述中的“按当前日期兜底”尚未进入代码;若业务仍需要该口径,应另行变更后端实现和本通知。
|
||||||
|
|
||||||
|
## 5. 前端适配清单
|
||||||
|
|
||||||
|
- [ ] 在核团详情出行人列表展示 `birthday` 和 `age`。
|
||||||
|
- [ ] 对两个字段均做 `null` 兼容,不拼接 `null岁` 或展示无效日期。
|
||||||
|
- [ ] 年龄直接使用后端返回值,不在浏览器端按当前日期重新计算。
|
||||||
|
- [ ] 继续使用现有脱敏后的 `phone` 和 `idCardNo`,不要尝试恢复明文。
|
||||||
|
- [ ] 不改变接口 URL、请求参数和其他响应字段的解析逻辑。
|
||||||
|
|
||||||
|
## 6. 兼容性与发布边界
|
||||||
|
|
||||||
|
- 新增字段对忽略未知 JSON 字段的旧客户端向后兼容。
|
||||||
|
- 字段为可空值,前端不能把 `birthday` 或 `age` 设为必填。
|
||||||
|
- PR #5069 已于 2026-07-19 10:47 合并到 `dev-v3`,合并提交为 `1da9389fbbc567dfd8b98703a6b9ebbfb2ea1d69`。
|
||||||
|
- 测试环境公网网关已验证新字段返回,见下方验证证据。
|
||||||
|
|
||||||
|
## 7. 验证证据
|
||||||
|
|
||||||
|
- 后端定向测试:`mvn -pl hl-order-service-v3 -am -DfailIfNoTests=false -Dtest=SettlementReturnDetailQueryServiceTest,SettlementControllerTest test`。
|
||||||
|
- 测试环境公网网关验证:`GET https://web.test.1814.love:9443/v3/admin/order/2077233855281971202/settlement/return-detail` 连续 6 次返回 `code=200`。
|
||||||
|
- 实测订单出发日为 `2026-07-13`,首位出行人 `birthday=1991-05-27`,接口返回 `age=35`,与按订单出发日计算的周岁一致。
|
||||||
|
- 实测响应中 `phone`、`idCardNo` 仍为脱敏值。
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
# #5074 调整记录新增本次新增出行人 ID
|
||||||
|
|
||||||
|
接口:`GET /v3/admin/order/{orderId}/adjustment-record`
|
||||||
|
|
||||||
|
每条调整记录新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"addedTravelerIds": ["2078739881671921666", "2078739895240560641"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 类型:`string[]`,雪花 ID 必须按字符串处理。
|
||||||
|
- 含义:仅包含该次订单调整事务实际新增的出行人 ID。
|
||||||
|
- 多人同时新增:完整返回全部新增 ID;数组顺序不承载业务语义。
|
||||||
|
- 仅编辑、仅删除、未涉及出行人或旧历史记录:返回 `[]`。
|
||||||
|
- 前端将当前出行人列表中的 `id` 与 `addedTravelerIds` 精确匹配后标记“本次新增”;禁止按列表位置或 ID 大小推断。
|
||||||
|
|
||||||
|
后端 Issue:`wx/HL#5074`;PR:`wx/HL#5075`。
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
# #5078 住宿需求允许指定酒店暂不指定房型
|
||||||
|
|
||||||
|
影响接口:住宿需求首次提交及订单调整提交。
|
||||||
|
|
||||||
|
业务规则调整:
|
||||||
|
|
||||||
|
- 允许候选酒店 `hotelId` 有值,同时房型行 `roomTypeId=null`、`roomCategory=null`。
|
||||||
|
- 此时 `roomCount` 仍必须为正数,用于表达“酒店已指定,具体房型由房务后续确认”。
|
||||||
|
- 后端不会伪造房型 ID 或协议价;房务配房时再选择该酒店的真实房型。
|
||||||
|
- 指定真实房型时继续按原契约传 `roomTypeId`;完整房型、多房型、未指定酒店场景不变。
|
||||||
|
- `roomCount` 为 0、负数或缺失时仍按非法房数拒绝。
|
||||||
|
|
||||||
|
后端 Issue:`wx/HL#5078`;PR:`wx/HL#5079`。
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
# 【前端待处理·管理后台】管理后台消息 SSE 鉴权重连与旧会话恢复
|
||||||
|
|
||||||
|
> **模块**:管理后台全局消息 / 在线状态 / 聊天信令 | **服务**:`hl-gateway` + `hl-user-service`<br>
|
||||||
|
> **类型**:前端待处理 + 联调告知 | **更新时间**:2026-07-19<br>
|
||||||
|
> **影响范围**:管理后台全局 SSE 连接、顶部未读角标、聊天、在线状态与抢单池信令<br>
|
||||||
|
> **状态**:后端已完成根因定位;前端尚未修复;接口契约未变
|
||||||
|
|
||||||
|
## 1. 结论与处理优先级
|
||||||
|
|
||||||
|
> ⚠️ 2026-07-19 测试环境启用 SSE 连接角色一致性校验后,发布前已签发且仍在有效期内的旧登录会话可能缺少当前角色标记。此时网关能够识别 access token,但用户服务会拒绝建立 SSE,前端当前实现会持续使用同一登录会话无限重连。
|
||||||
|
|
||||||
|
- 接口 URL、HTTP 方法、事件结构均未修改。
|
||||||
|
- 这不是 `token` Query 参数名写错;当前前端 URL 拼接方式与网关读取方式一致。
|
||||||
|
- **用户立即恢复方式**:退出当前账号,重新登录并选择当前角色,再建立 SSE。
|
||||||
|
- **前端必须处理**:Token/角色变化时主动重建连接、限制连续失败重试、给出重新登录提示,并消除默认 `message` 事件的重复注册。
|
||||||
|
- 本次现象包含后端发布前旧会话兼容问题;前端改造用于正确管理连接生命周期和避免无限重试,不代表把后端兼容责任转移给前端。
|
||||||
|
|
||||||
|
## 2. 当前接口契约
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /ws/admin-msg/stream?token=<accessToken>
|
||||||
|
Accept: text/event-stream
|
||||||
|
```
|
||||||
|
|
||||||
|
- 认证:管理后台 access token。
|
||||||
|
- 当前使用原生 `EventSource`,浏览器 API 不能自定义 `Authorization` Header,因此现有实现通过 Query 参数传递 token。
|
||||||
|
- `token` 必须使用当前 Store 中的 access token,并通过 `encodeURIComponent` 做 URL 编码。
|
||||||
|
- 成功建连后,请求应长期保持 `Pending`,响应类型为 `text/event-stream`。
|
||||||
|
- 首个握手事件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
event: connected
|
||||||
|
data: ok
|
||||||
|
```
|
||||||
|
|
||||||
|
- 后续仍沿用现有具名事件,包括 `unread-count`、`im-chat`、`im-chat-read`、`presence` 和 `grab-pool-changed`;本次没有修改事件数据结构。
|
||||||
|
|
||||||
|
## 3. 已确认的问题链路
|
||||||
|
|
||||||
|
### 3.1 旧登录会话与新角色标记不兼容
|
||||||
|
|
||||||
|
测试环境运行链路已确认:
|
||||||
|
|
||||||
|
1. 网关可以从 `?token=` 读取并校验管理后台 JWT。
|
||||||
|
2. 网关向用户服务转发可信的管理员身份及角色信息。
|
||||||
|
3. 用户服务在下发任何 SSE 数据前校验“连接角色是否仍为当前登录角色”。
|
||||||
|
4. 发布前签发的旧登录会话没有初始化新角色标记时,校验按安全策略失败并关闭连接。
|
||||||
|
5. 新登录或重新选择角色会重新写入角色标记,因此重新登录后可恢复。
|
||||||
|
|
||||||
|
该校验采用 fail-closed(失败时拒绝)策略,目的是避免角色切换后旧 Token 继续接收不属于当前角色的消息。
|
||||||
|
|
||||||
|
### 3.2 前端当前会无限重试同一失败会话
|
||||||
|
|
||||||
|
当前 `src/composables/useAdminMessageSSE.js` 在 `EventSource.onerror` 后执行关闭和指数退避,但没有连续失败上限,也没有触发重新登录或鉴权恢复流程。
|
||||||
|
|
||||||
|
原生 `EventSource.onerror` 不暴露 HTTP 状态码和响应正文,前端不能仅凭 `onerror` 精确区分 401/403、服务异常和临时断网。因此不能把所有错误都直接判定为 Token 失效,但必须限制无休止重连。
|
||||||
|
|
||||||
|
### 3.3 默认 `message` 事件被重复注册
|
||||||
|
|
||||||
|
当前实现同时注册:
|
||||||
|
|
||||||
|
```js
|
||||||
|
es.onmessage = handleMessage
|
||||||
|
es.addEventListener('message', handleMessage)
|
||||||
|
```
|
||||||
|
|
||||||
|
两种写法都会监听默认 `message` 事件,并不是互斥兜底。后端发送默认 `message` 时,同一数据可能被处理两次,必须只保留一种注册方式。
|
||||||
|
|
||||||
|
## 4. 用户立即恢复步骤
|
||||||
|
|
||||||
|
1. 关闭当前页面产生的旧 SSE 连接。
|
||||||
|
2. 正常退出管理后台。
|
||||||
|
3. 重新登录,并重新选择当前需要使用的角色。
|
||||||
|
4. 进入主布局后重新建立 `/ws/admin-msg/stream`。
|
||||||
|
5. 在浏览器 Network 中确认请求保持 `Pending`,并收到一次 `connected` 事件。
|
||||||
|
|
||||||
|
不要通过手工复制、修改或在地址栏粘贴完整 Token 的方式恢复连接。
|
||||||
|
|
||||||
|
## 5. 【前端·管理后台】适配清单
|
||||||
|
|
||||||
|
### 5.1 让 SSE 生命周期跟随登录凭证和角色
|
||||||
|
|
||||||
|
- [ ] 监听 `userStore.token` 变化;值变化时先关闭旧 `EventSource`,再使用最新 Token 建立唯一的新连接。
|
||||||
|
- [ ] 角色切换成功并更新 Token 后,立即重建 SSE,不等待旧连接自行报错。
|
||||||
|
- [ ] 登出、主布局卸载或 Token 被清空时,关闭连接、清理重连定时器并禁止再次拉起。
|
||||||
|
- [ ] 保证全局最多只有一个管理后台消息 SSE 实例,避免布局重复挂载造成多连接。
|
||||||
|
- [ ] 重建连接时始终从 Store 现取 Token,不缓存旧登录会话中的 Token 字符串。
|
||||||
|
|
||||||
|
### 5.2 限制连续失败,避免无限重连
|
||||||
|
|
||||||
|
- [ ] 保留指数退避和最大间隔,但增加“连续失败次数/总时长”上限。
|
||||||
|
- [ ] **仅在收到后端 `connected` 事件后**清零连续失败计数;`EventSource.onopen` 不能作为鉴权成功依据,也不能清零计数。
|
||||||
|
- [ ] 达到上限后停止自动重试,并显示中性、可操作的提示,例如“消息连接连续失败,请检查网络或重新登录”。
|
||||||
|
- [ ] 用户完成重新登录、Token 刷新、角色切换或主动点击重试后,才开启新一轮连接。
|
||||||
|
- [ ] 临时断网恢复后仍允许重连;可结合 `online` 事件或显式重试入口恢复,而不是永久静默失效。
|
||||||
|
|
||||||
|
> 注意:由于原生 `EventSource` 无法在 `onerror` 中读取响应状态,前端不要根据一次 `onerror` 立即清空登录态。需要使用连续失败阈值,并结合普通鉴权接口结果或既有 Token 刷新状态判断。
|
||||||
|
|
||||||
|
### 5.3 消除重复消息处理
|
||||||
|
|
||||||
|
- [ ] `es.onmessage` 与 `es.addEventListener('message', ...)` 只保留一种。
|
||||||
|
- [ ] `connected`、`unread-count`、`im-chat`、`im-chat-read`、`presence`、`grab-pool-changed` 等具名事件继续分别注册。
|
||||||
|
- [ ] 验证单条默认 `message`、聊天信令和未读数信令都只被业务层消费一次。
|
||||||
|
|
||||||
|
### 5.4 失败信息与联调反馈
|
||||||
|
|
||||||
|
- [ ] 前端提示中不要展示 Token、完整 SSE URL、Cookie 或管理员标识。
|
||||||
|
- [ ] 如重新登录后仍失败,只反馈发生时间、页面、错误 `message` 和 `X-Trace-Id`。
|
||||||
|
- [ ] 若 Network 原始响应确实为“未提供有效的Token”,请附 `X-Trace-Id` 交后端继续检查路由/拦截器链;不要附 Token。
|
||||||
|
|
||||||
|
## 6. 验收场景
|
||||||
|
|
||||||
|
| 场景 | 期望结果 |
|
||||||
|
|---|---|
|
||||||
|
| 重新登录后首次进入主布局 | 只建立 1 条 SSE;请求保持 `Pending`;收到 1 次 `connected` |
|
||||||
|
| access token 刷新 | 旧连接关闭,使用新 Token 只重建 1 次 |
|
||||||
|
| 切换管理后台角色 | 旧角色连接立即关闭;新角色 Token 建立新连接;不接收旧角色后续数据 |
|
||||||
|
| 发布前旧会话无法建连 | 退避重试达到阈值后停止,并明确提示重新登录;不无限刷请求 |
|
||||||
|
| 只触发 `onopen`、未收到 `connected`、随后触发 `onerror` | 仍累计连续失败次数,不得被 `onopen` 反复清零 |
|
||||||
|
| 临时断网后恢复 | 在受控退避或用户重试后恢复连接,不产生并发 SSE |
|
||||||
|
| 收到默认 `message` | 同一事件只处理 1 次 |
|
||||||
|
| 正常登出 | SSE 和重连定时器均被清理,退出页不再发起连接 |
|
||||||
|
| 重新登录后仍失败 | 联调材料仅包含时间、页面、错误消息、`X-Trace-Id`,不包含 Token |
|
||||||
|
|
||||||
|
## 7. 后端状态与边界
|
||||||
|
|
||||||
|
- 当前接口路径、Query 参数名和 SSE 事件结构未变,不需要前端调整数据模型。
|
||||||
|
- 新登录/角色切换链路会写入当前角色标记,重新登录是当前可用的恢复手段。
|
||||||
|
- 发布前旧会话没有迁移标记是本次问题的触发条件;后端尚未交付旧会话兼容补丁。
|
||||||
|
- 角色一致性校验必须保留,不能为了兼容旧会话而允许旧角色 Token 接收消息。
|
||||||
|
- 若后续改为一次性 SSE Ticket、Fetch Streaming 或其他不在 URL 中携带 access token 的方案,将另发接口契约,不在本次前端适配范围内。
|
||||||
|
|
||||||
|
## 8. 安全要求
|
||||||
|
|
||||||
|
- 禁止把完整 Token、带 Token 的完整 SSE URL、Cookie 或真实管理员信息写入 Issue、PR、Changelog、日志和截图。
|
||||||
|
- Token 一旦通过聊天、工单或截图暴露,应立即停止使用和传播,通知后端/运维按当前鉴权策略显式吊销或拒绝该旧 Token,并验证它已无法访问;随后重新登录获取新 Token。
|
||||||
|
- 重新登录只是恢复 SSE 和获取新 Token,不等于旧 JWT 已自动吊销;尤其在仅校验 JWT 签名的环境中,必须单独完成旧 Token 的失效处置。
|
||||||
|
- 不得在前端代码中硬编码 Token,也不得把 Token 写入错误上报或埋点参数。
|
||||||
|
|
||||||
|
## 9. 影响范围
|
||||||
|
|
||||||
|
| 文件/能力 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `src/composables/useAdminMessageSSE.js` | 连接、重连、事件监听和清理逻辑 |
|
||||||
|
| `src/layouts/BasicLayout.vue` | 主布局挂载、登出和 SSE 生命周期 |
|
||||||
|
| 角色切换流程 | Token 更新后主动重建 SSE |
|
||||||
|
| 顶部未读角标、聊天、在线状态、抢单池信令 | 共用同一 SSE,需防止连接缺失或事件重复消费 |
|
||||||
|
|
||||||
|
## 10. 发布说明
|
||||||
|
|
||||||
|
- 本文是前端联调和修复通知,不代表已修改或发布前端代码。
|
||||||
|
- 本文没有包含任何真实 Token、管理员 ID、Cookie 或其他敏感信息。
|
||||||
|
- 前端完成后应在 `mmg/hl-ui` 走自身 Issue、分支、PR、测试和发布流程。
|
||||||
|
|
||||||
|
## 11. 相关历史契约
|
||||||
|
|
||||||
|
| 文档 | 当前说明 |
|
||||||
|
|---|---|
|
||||||
|
| [内部员工站内信收件箱 + SSE 实时推送](../2026-06/04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md) | SSE 路径、Query 鉴权和事件契约仍有效;其中“断线自动重连”的建议被本文补充为有上限的受控重连,鉴权持续失败时不得无限请求 |
|
||||||
|
| [切角色 / 刷新令牌原子保存](../2026-06/38_4529_切角色与刷新token原子保存_前端必改-管理后台.md) | `token` 与 `refreshToken` 原子保存要求仍有效;保存新 access token 后还必须关闭旧 SSE 并主动重建 |
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
# 房务详情混合房型逐行出参(Issue #5080)
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
同一晚存在多个房型时,旧兼容标量会把 `rooms[]` 的首行房型与所有房数相加,导致“标间 1 + 大床房 1”被错误展示为“标间 2”。
|
||||||
|
|
||||||
|
## 接口
|
||||||
|
|
||||||
|
`GET /admin/house/orders/{orderId}`
|
||||||
|
|
||||||
|
`GET /v3/admin/order/{orderId}`(订单详情中的住宿需求摘要)
|
||||||
|
|
||||||
|
## 新增字段
|
||||||
|
|
||||||
|
`data.itinerary[].expectedRooms[]`:当天逐房型预期房间列表,混合房型展示和业务判断以此字段为准。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expectedRoom": {
|
||||||
|
"roomCategory": null,
|
||||||
|
"roomCategoryLabel": null,
|
||||||
|
"roomCount": 2
|
||||||
|
},
|
||||||
|
"expectedRooms": [
|
||||||
|
{ "roomCategory": "STANDARD", "roomCategoryLabel": "标间", "roomCount": 1 },
|
||||||
|
{ "roomCategory": "KING", "roomCategoryLabel": "大床房", "roomCount": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 兼容规则
|
||||||
|
|
||||||
|
- 单一房型:`expectedRoom` 继续返回原标量,`expectedRooms[]` 同时提供逐行数据。
|
||||||
|
- 混合房型:`expectedRoom.roomCategory` 与 `roomCategoryLabel` 返回 `null`,防止形成“首房型 × 总房数”的错误含义;`roomCount` 仍为总间数。
|
||||||
|
- 未指定房型:房型字段保持 `null`,房数按需求返回。
|
||||||
|
- `requirement.current.days[].segments[].candidates[].rooms[]` 仍是候选酒店房型行的权威明细。
|
||||||
|
|
||||||
|
## 前端适配要求
|
||||||
|
|
||||||
|
1. 房务详情及“选择酒店”弹窗不得再用 `days[].roomCategory` 或首个酒店 `roomCategory` 表示混合房型。
|
||||||
|
2. 标题按 `expectedRooms[]` 渲染,例如“标间 1 间 + 大床房 1 间”。
|
||||||
|
3. 候选房型筛选与默认数量应逐条读取 `expectedRooms[]`;不得以首行房型套用总间数。
|
||||||
|
4. 兼容后端尚未部署时,可从 `segments[].candidates[0].rooms[]` 读取同等权威明细,但不得猜测列表顺序。
|
||||||
|
|
||||||
|
## 订单详情补充(Issue #5082)
|
||||||
|
|
||||||
|
- `hotelRequirement.days[].hotels[]` 与 `segments[]` 的兼容 `roomCategory/roomCategoryLabel` 仅在对应 `rooms[]` 全部属于同一房型大类时返回。
|
||||||
|
- 混合房型时,上述兼容房型字段返回 `null`,`roomCount` 仍返回总间数;页面标题必须由 `rooms[]` 逐行生成。
|
||||||
|
- 这可避免“标间 1 + 大床房 1”被标题错误展示为“标间 2”。
|
||||||
@ -0,0 +1,370 @@
|
|||||||
|
# 【前端对接·管理后台】车务派单可靠通知、取消后重派与发送状态契约
|
||||||
|
|
||||||
|
> Issue: [wx/HL#4933](https://git.1814.love:8443/wx/HL/issues/4933)
|
||||||
|
>
|
||||||
|
> PR: [wx/HL#5073](https://git.1814.love:8443/wx/HL/pulls/5073)、[wx/HL#5084](https://git.1814.love:8443/wx/HL/pulls/5084)
|
||||||
|
>
|
||||||
|
> 服务: `hl-fleet-service` / `hl-user-service` / `hl-order-service-v3` / `hl-gateway`
|
||||||
|
>
|
||||||
|
> 日期: 2026-07-19
|
||||||
|
>
|
||||||
|
> 影响范围: 派单/改派弹窗、派单详情操作记录、通知发送日志、订单详情推送记录、取消后重新派车
|
||||||
|
|
||||||
|
## 一、前端结论
|
||||||
|
|
||||||
|
- `holdMode=1` 的创建派单和改派现在会冻结本次通知模板与正文,并由后端异步执行可靠短信发送。
|
||||||
|
- 创建 HOLD 成功只表示派单和通知意图已落库;首次响应中的 `holdSentAt` 固定为 `null`。只有供应商真实受理后,派单详情的 `currentAssignment.holdSentAt` 才会回显发送时间。
|
||||||
|
- 通知日志 `status` 已从旧的少量状态扩展为 `0~6`。前端必须展示“投递中、结果不确定、授权撤销”,不得把它们归并成发送成功或失败。
|
||||||
|
- 取消派单成功后,后端会可靠地把当前生效用车需求重新打开,允许再次派车;该过程为最终一致。前端刷新看板和详情,并以最新 `canAssign`/当前需求状态决定是否开放重派,不调用内部重开接口。
|
||||||
|
- 订单详情推送记录的归一化状态枚举已调整,前端需要同步新枚举。
|
||||||
|
- `/internal/**`、`/v3/internal/**` 均为服务间接口,经网关调用返回业务码 `403`;任何 Web/小程序代码都不得调用。
|
||||||
|
|
||||||
|
## 二、前端可调用接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 本轮变化 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 创建派单 | POST | `/admin/fleet/assignments` | 新增 `messageTemplateId/customBody`;明确 `holdSentAt` 语义 |
|
||||||
|
| 修改派单 | POST | `/admin/fleet/assignments/{assignmentId}/change` | HOLD 改派新增 `messageTemplateId/customBody` |
|
||||||
|
| 派单看板详情 | GET | `/admin/fleet/board/orders/{orderId}` | 回显真实 `holdSentAt`;操作记录补齐取消/退保完整时间线 |
|
||||||
|
| 派单看板汇总 | GET | `/admin/fleet/board/summary` | 取消后刷新当前状态与能力字段 |
|
||||||
|
| 派单看板列表 | GET | `/admin/fleet/board/orders` | 取消后刷新当前状态与能力字段 |
|
||||||
|
| 通知发送日志 | GET | `/admin/notification/logs` | 状态扩展为 `0~6`,新增可靠投递审计字段 |
|
||||||
|
| 通知发送统计 | GET | `/admin/notification/logs/stats` | 新增跳过、投递中、不确定、撤销等统计 |
|
||||||
|
| 人工核对可靠短信 | PUT | `/admin/notification/logs/{id}/resolve-reliable` | 新增,仅专用权限可用 |
|
||||||
|
| 订单详情推送记录 | GET | `/v3/admin/order/{id}/push-records` | 归一化状态枚举调整 |
|
||||||
|
|
||||||
|
## 三、创建/修改 HOLD 派单
|
||||||
|
|
||||||
|
### 3.1 请求字段
|
||||||
|
|
||||||
|
两个写接口新增相同的可选字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 规则 |
|
||||||
|
|---|---|---|
|
||||||
|
| `messageTemplateId` | string | HOLD 通知模板 ID;可空,空时使用 `hold_notify` 默认模板;`holdMode=0` 时忽略 |
|
||||||
|
| `customBody` | string | 本次通知自定义正文;可空,最大 4000 字符;只冻结本次内容,不回写模板 |
|
||||||
|
|
||||||
|
所有雪花 ID 继续按字符串传递和保存,禁止转为 JavaScript `Number`。
|
||||||
|
|
||||||
|
创建 HOLD 请求示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <fleet-admin-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"orderId": "2074746808742928386",
|
||||||
|
"requirementId": "2075001000000000001",
|
||||||
|
"vehicleId": "2076001000000000001",
|
||||||
|
"driverId": "2077001000000000001",
|
||||||
|
"startDate": "2026-07-20",
|
||||||
|
"endDate": "2026-07-22",
|
||||||
|
"headcount": 4,
|
||||||
|
"holdMode": 1,
|
||||||
|
"messageTemplateId": "20260706000101",
|
||||||
|
"customBody": "王师傅您好,26-7218 团 7 月 20 日待确认。",
|
||||||
|
"fromEntry": "from-board",
|
||||||
|
"requestId": "hold-2074746808742928386-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
修改为 HOLD 请求示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /admin/fleet/assignments/2078001000000000001/change
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <fleet-admin-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"effectiveDate": "2026-07-21",
|
||||||
|
"newVehicleId": "2076001000000000002",
|
||||||
|
"newDriverId": "2077001000000000002",
|
||||||
|
"holdMode": 1,
|
||||||
|
"messageTemplateId": "20260706000101",
|
||||||
|
"customBody": "李师傅您好,本团 7 月 21 日起调整由您服务,请确认。",
|
||||||
|
"reason": "原司机临时无法执行",
|
||||||
|
"requestId": "change-2078001000000000001-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 创建响应与 `holdSentAt`
|
||||||
|
|
||||||
|
HOLD 创建成功响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "2078001000000000001",
|
||||||
|
"assignmentGroupId": "2078001000000000001",
|
||||||
|
"assignmentSlotId": "2078001000000000001",
|
||||||
|
"assignmentStatus": "holding",
|
||||||
|
"stageCode": "holding_wait_driver",
|
||||||
|
"stageLabel": "排车中·等待司机确认",
|
||||||
|
"currentStep": 3,
|
||||||
|
"skippedStepCodes": [],
|
||||||
|
"protocolPrice": "1300.00",
|
||||||
|
"holdSentAt": null,
|
||||||
|
"confirmedAt": null,
|
||||||
|
"sideEffects": null,
|
||||||
|
"dailyDifferences": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端处理规则:
|
||||||
|
|
||||||
|
1. `code=200` 且 `assignmentStatus=holding` 后立即关闭重复提交入口,并刷新详情。
|
||||||
|
2. `holdSentAt=null` 不是接口失败,也不能显示“短信已发送”;应显示“通知处理中/等待发送结果”。
|
||||||
|
3. 后续读取 `GET /admin/fleet/board/orders/{orderId}`,仅当 `currentAssignment.holdSentAt` 非空时显示真实发送时间。
|
||||||
|
4. 模板缺失、供应商失败或结果不确定时,派单仍保持 `holding`,前端通过通知日志查看真实状态,不自行改派单状态。
|
||||||
|
|
||||||
|
## 四、通知发送日志状态
|
||||||
|
|
||||||
|
### 4.1 状态枚举
|
||||||
|
|
||||||
|
`GET /admin/notification/logs` 的请求筛选参数和响应字段 `status` 统一使用:
|
||||||
|
|
||||||
|
| status | 含义 | 前端展示建议 |
|
||||||
|
|---:|---|---|
|
||||||
|
| 0 | 发送成功,供应商明确受理 | 成功 |
|
||||||
|
| 1 | 明确失败 | 失败 |
|
||||||
|
| 2 | 无收件人 | 已跳过·无收件人 |
|
||||||
|
| 3 | 无模板 | 已跳过·无模板 |
|
||||||
|
| 4 | 投递中 | 投递中 |
|
||||||
|
| 5 | 结果不确定 | 待核对 |
|
||||||
|
| 6 | 授权撤销 | 已撤销 |
|
||||||
|
|
||||||
|
前端不得把 `4/5/6` 计入成功或失败。状态 `5` 也不能自动重发,避免供应商实际已发送时重复通知司机。
|
||||||
|
|
||||||
|
单条日志新增字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 2080001000000000001,
|
||||||
|
"eventCode": "FLEET_DISPATCH_CREATED",
|
||||||
|
"channel": "SMS",
|
||||||
|
"bizId": "2078001000000000001",
|
||||||
|
"bizType": "FLEET_ASSIGNMENT_HOLD",
|
||||||
|
"status": 5,
|
||||||
|
"latestProviderAttemptAt": "2026-06-19T10:00:00",
|
||||||
|
"providerSentAt": null,
|
||||||
|
"resultTime": null,
|
||||||
|
"manualResolvedAt": null,
|
||||||
|
"manualResolvedBy": null,
|
||||||
|
"manualResolutionReason": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
新增统计字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"totalToday": 20,
|
||||||
|
"successToday": 12,
|
||||||
|
"failToday": 2,
|
||||||
|
"skippedToday": 3,
|
||||||
|
"dispatchingToday": 1,
|
||||||
|
"unknownToday": 1,
|
||||||
|
"canceledToday": 1,
|
||||||
|
"terminalAttemptToday": 14,
|
||||||
|
"successRate": 85.71,
|
||||||
|
"channelStats": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`successRate` 的分母是 `terminalAttemptToday = successToday + failToday`,前端不要再用 `totalToday` 自行计算。
|
||||||
|
|
||||||
|
## 五、人工核对结果不确定短信
|
||||||
|
|
||||||
|
该入口只处理超过供应商 29 天查询窗口、仍为 `status=5` 的车务可靠短信,并要求 `NOTIFICATION_RELIABLE_RESOLVE` 专用权限。当前后端只授予 `SUPER_ADMIN`;普通管理员即使手工构造请求也会被拒绝。
|
||||||
|
|
||||||
|
确认已发送:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PUT /admin/notification/logs/2080001000000000001/resolve-reliable
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <super-admin-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"resolution": "SUCCESS",
|
||||||
|
"reason": "阿里云控制台发送记录核对,工单 SMS-20260719-001",
|
||||||
|
"externalMessageId": "SMS-20260719-001",
|
||||||
|
"providerSentAt": "2026-06-19T10:00:30"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
确认未发送:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"resolution": "NOT_SENT",
|
||||||
|
"reason": "阿里云控制台未查到对应发送记录",
|
||||||
|
"externalMessageId": null,
|
||||||
|
"providerSentAt": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
处理规则:
|
||||||
|
|
||||||
|
- `SUCCESS` 必须传 `externalMessageId` 和 `providerSentAt`;事实时间必须位于最近一次供应商尝试时间前后 5 分钟内。
|
||||||
|
- `NOT_SENT` 不得传 `providerSentAt`。
|
||||||
|
- 请求返回 `100001` 表示参数或证据时间不合法;返回 `100003` 表示无权限、日志不符合人工核对条件或状态已变化。
|
||||||
|
- 操作成功后刷新当前日志行和统计;不要在前端直接篡改状态。
|
||||||
|
|
||||||
|
## 六、订单详情推送记录状态
|
||||||
|
|
||||||
|
`GET /v3/admin/order/{id}/push-records` 的 `records[].status` 改为:
|
||||||
|
|
||||||
|
| status | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| `SENT` | 供应商明确受理 |
|
||||||
|
| `FAILED` | 明确失败 |
|
||||||
|
| `SKIPPED_NO_RECIPIENT` | 无收件人 |
|
||||||
|
| `SKIPPED_NO_TEMPLATE` | 无模板 |
|
||||||
|
| `DISPATCHING` | 投递中 |
|
||||||
|
| `UNKNOWN` | 结果不确定 |
|
||||||
|
| `CANCELED` | 授权已撤销 |
|
||||||
|
| `UNRECOGNIZED` | 未识别的存量状态 |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 2080001000000000001,
|
||||||
|
"eventCode": "FLEET_DISPATCH_CREATED",
|
||||||
|
"channel": "SMS",
|
||||||
|
"channelName": "短信",
|
||||||
|
"kind": "sms",
|
||||||
|
"target": "王师傅",
|
||||||
|
"status": "UNKNOWN",
|
||||||
|
"statusName": "结果不确定",
|
||||||
|
"rawStatus": 5,
|
||||||
|
"failReason": null,
|
||||||
|
"bizId": "2078001000000000001",
|
||||||
|
"bizType": "FLEET_ASSIGNMENT_HOLD",
|
||||||
|
"sentAt": "2026-07-19T10:00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": {
|
||||||
|
"all": 1,
|
||||||
|
"sms": 1,
|
||||||
|
"miniapp": 0,
|
||||||
|
"officialAccount": 0,
|
||||||
|
"inapp": 0,
|
||||||
|
"internal": 0,
|
||||||
|
"wework": 0,
|
||||||
|
"other": 0,
|
||||||
|
"failed": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`summary.failed` 只统计 `rawStatus=1`,不包含 `UNKNOWN/DISPATCHING/CANCELED`。
|
||||||
|
|
||||||
|
## 七、取消后重新派车
|
||||||
|
|
||||||
|
前端仍调用既有接口取消:
|
||||||
|
|
||||||
|
```http
|
||||||
|
DELETE /admin/fleet/assignments/{assignmentId}
|
||||||
|
```
|
||||||
|
|
||||||
|
成功后的正确流程:
|
||||||
|
|
||||||
|
1. 接受取消响应中的 `assignmentStatus=canceled`。
|
||||||
|
2. 重新请求 `/admin/fleet/board/summary`、`/admin/fleet/board/orders` 和 `/admin/fleet/board/orders/{orderId}`。
|
||||||
|
3. 后端完成需求重开后,当前订单重新出现可派状态;按钮只看最新响应的 `canAssign`,不要本地强制改为可派。
|
||||||
|
4. 如果首次刷新仍未开放重派,保持处理中并短暂重试刷新;不要调用 `/v3/internal/order/**`,也不要让用户重复取消。
|
||||||
|
5. 重新派车成功后再次刷新服务端状态,不能沿用已取消派单的 `assignmentId`。
|
||||||
|
|
||||||
|
派单详情 `operationLog.records[]` 会保留不可变取消时间线,新增/强化的 `opType` 包括:
|
||||||
|
|
||||||
|
- `cancel_requested`
|
||||||
|
- `driver_notification_recorded`
|
||||||
|
- `cancel_evidence_recorded`
|
||||||
|
- `insurance_refund_pending`
|
||||||
|
- `insurance_refund_succeeded`
|
||||||
|
- `insurance_refund_failed`
|
||||||
|
- `cancel_completed`
|
||||||
|
- `cancel_restored`
|
||||||
|
- `cancel_failed`
|
||||||
|
|
||||||
|
前端优先展示后端返回的 `opTypeLabel`、`operationStatusLabel` 和 `summary`,不要另维护中文文案。`operationStatus` 允许 `pending/succeeded/failed`。
|
||||||
|
|
||||||
|
## 八、网关 internal 边界
|
||||||
|
|
||||||
|
下列路径全部禁止客户端调用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/internal
|
||||||
|
/internal/**
|
||||||
|
/v3/internal
|
||||||
|
/v3/internal/**
|
||||||
|
```
|
||||||
|
|
||||||
|
网关按项目协议返回 HTTP 200,但响应体为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 403,
|
||||||
|
"message": "接口不可访问",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请前端全仓检查是否仍有 `/v3/internal/mp/**` 等历史调用;如存在,不要自行改成另一个 internal 地址,应反馈后端补正式 BFF/admin 契约。
|
||||||
|
|
||||||
|
## 九、前端待处理清单
|
||||||
|
|
||||||
|
- [ ] 派单/改派弹窗在 HOLD 模式支持 `messageTemplateId/customBody`,DIRECT 模式不提交或忽略这两个字段。
|
||||||
|
- [ ] HOLD 创建成功时把 `holdSentAt=null` 展示为处理中,不显示“已发送”。
|
||||||
|
- [ ] 通知日志筛选、标签和统计适配 `0~6` 状态及新增字段。
|
||||||
|
- [ ] 仅对具备专用权限的账号展示“人工核对可靠短信”入口,并实现 `SUCCESS/NOT_SENT` 两种表单校验。
|
||||||
|
- [ ] 订单详情推送记录适配新的归一化状态枚举。
|
||||||
|
- [ ] 取消派单后刷新服务端状态,以 `canAssign` 控制重新派车入口。
|
||||||
|
- [ ] 确认前端不存在任何 `/internal/**` 或 `/v3/internal/**` 调用。
|
||||||
|
- [ ] 所有雪花 ID 保持字符串。
|
||||||
|
|
||||||
|
## 十、后端交付与测试环境状态
|
||||||
|
|
||||||
|
- 后端 PR #5073、#5084 已合并到 `dev-v3`。
|
||||||
|
- `hl-order-service-v3`、`hl-fleet-service`、`hl-gateway` 已按顺序部署 TEST,双实例健康;当前 OpenAPI 已公开本文全部管理端接口。
|
||||||
|
- 已用真实测试订单完成 DIRECT、取消、需求重开、再次 DIRECT、司机同步和退保时间线验收。
|
||||||
|
- TEST 当前 `hold_notify` 短信模板仍是占位配置,真实 HOLD 短信会失败关闭,`holdSentAt` 保持 `null`;这是环境配置阻塞,不应由前端伪造成发送成功。
|
||||||
|
- 本文件只做契约交接,不修改 `hl-ui`。
|
||||||
|
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
# 房务选择酒店误传 preferredHotelId 导致只显示 1 家(前端待处理)
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
订单 `2078739922130243586` 第 1 晚打开“选择酒店”弹窗,只显示定制师指定的“呼伦贝尔香格里拉大酒店”,分页显示“共 1 条”,页面提示“已限定定制师指定酒店”。
|
||||||
|
|
||||||
|
## 已确认原因
|
||||||
|
|
||||||
|
`192.168.100.160:9527` 当前 Vite 服务实际返回的 `PickHotelModal.vue` 仍在候选请求中传递:
|
||||||
|
|
||||||
|
```js
|
||||||
|
preferredHotelId:
|
||||||
|
on.specifiedHotelIds?.length === 1 ? String(on.specifiedHotelIds[0]) : undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
该参数会要求后端按指定酒店过滤,因此响应只剩 1 家。这与当前产品意图“定制师指定酒店置顶并标记,房务仍可选择其他酒店”冲突。
|
||||||
|
|
||||||
|
当前 `D:/work2/hl-ui` 源码已经不再传该参数,说明 `192.168.100.160:9527` 运行的是未同步的工作树或旧代码。
|
||||||
|
|
||||||
|
## 接口证据
|
||||||
|
|
||||||
|
接口:`GET /v3/admin/hotel-candidates`
|
||||||
|
|
||||||
|
公共参数:
|
||||||
|
|
||||||
|
- `orderId=2078739922130243586`
|
||||||
|
- `dayNumber=1`
|
||||||
|
- `stayDate=2026-07-22`
|
||||||
|
- `roomCount=2`
|
||||||
|
- `limit=50`
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- 不传 `preferredHotelId`、无关键词:返回 21 家;香格里拉为 `isConsultantRecommended=true` 且排第 1。
|
||||||
|
- 不传 `preferredHotelId`、`keyword=满洲里`:返回 3 家,包括香格里拉、满洲里凯旋大酒店、满洲里饭店(百年俄式)。
|
||||||
|
- 当前截图环境传入唯一 `preferredHotelId`:只返回指定酒店 1 家。
|
||||||
|
|
||||||
|
## 前端处理要求
|
||||||
|
|
||||||
|
1. 房务候选请求不得传 `preferredHotelId`,无论 `specifiedHotelIds` 是 1 个还是多个。
|
||||||
|
2. `specifiedHotelIds` 仅用于页面提示;推荐标记以接口 `isConsultantRecommended` 为准。
|
||||||
|
3. 不输入关键词时展示后端返回的全部候选;跨城搜索继续使用 `keyword`。
|
||||||
|
4. 确认 `192.168.100.160:9527` 的 Vite 进程工作目录与 `D:/work2/hl-ui` 当前目标分支一致,重启 Vite 后清除模块缓存并复测。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- 打开本订单第 1 晚选择酒店,不输入关键词时不再显示“共 1 条”,可看到其他酒店。
|
||||||
|
- 搜索“满洲里”返回 3 家。
|
||||||
|
- 香格里拉仍显示“定制师推荐”,但不会阻止选择其他酒店。
|
||||||
|
- Network 中 `/v3/admin/hotel-candidates` 请求不含 `preferredHotelId`。
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
# 房务最终确认后修改配房按钮被旧前端隐藏(前端待处理)
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- **端类型:管理后台(Web)**
|
||||||
|
- **目标仓库:`mmg/hl-ui`**
|
||||||
|
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||||
|
- **前端本地测试环境:`http://192.168.100.160:9527`**
|
||||||
|
- **小程序:无需处理**
|
||||||
|
|
||||||
|
本通知应由管理后台前端负责人在 `mmg/hl-ui` 处理,不属于后端仓库 `wx/HL`,也不属于小程序前端。
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
订单 `HL20260719151313174` 已最终确认、配房进度 `2/2`,房务详情配房行程只显示“询房”按钮;既有配房行未显示“替换”“改协议价”“移除”等修改入口。
|
||||||
|
|
||||||
|
业务要求:最终确认后房务仍可修改配房。发起修改时先将住宿需求从完成态解冻回配房中,再执行替换、移除、改价等操作。
|
||||||
|
|
||||||
|
## 已确认原因
|
||||||
|
|
||||||
|
`192.168.100.160:9527` 当前 Vite 服务实际返回的 `OrderDetailModal.vue` 仍包含旧门槛:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const canMutateRequirement = computed(
|
||||||
|
() =>
|
||||||
|
canEditHouseOrder.value &&
|
||||||
|
!requirementReadOnly.value &&
|
||||||
|
(merged.value?.finalized !== true || merged.value?.reopenAction?.enabled === true)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
后端详情当前有意将 `reopenAction` 设为 disabled,不再把“回配”作为单独按钮;后端各直接编辑入口会调用 `reopenIfFinalizedForDirectEdit()` 自动解冻。因此旧前端条件在 `finalized=true` 时恒为 false,连真正的修改按钮也全部隐藏。
|
||||||
|
|
||||||
|
当前 `D:/work2/hl-ui` 源码已经改为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const canMutateRequirement = computed(
|
||||||
|
() => canEditHouseOrder.value && !requirementReadOnly.value
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
并由 `ensureRequirementEditable(reqId)` 在写操作前调用 `reopenRequirement(reqId)`,与后端自动解冻语义一致。
|
||||||
|
|
||||||
|
## 前端处理要求
|
||||||
|
|
||||||
|
1. 同步当前 `D:/work2/hl-ui` 正确实现到 `192.168.100.160:9527` 实际运行工作树,重启 Vite 服务。
|
||||||
|
2. `canMutateRequirement` 不得用 `finalized` 或 `reopenAction.enabled` 隐藏配房修改入口。
|
||||||
|
3. 最终确认后,只要订单属于当前房务、需求仍生效且未驳回/作废,应继续显示:
|
||||||
|
- 当晚“替换”;
|
||||||
|
- 已确认配房行“改协议价”;
|
||||||
|
- 已确认配房行“移除”。
|
||||||
|
4. 写操作前沿用 `ensureRequirementEditable()`;不得要求用户先点击一个独立“回配”按钮。
|
||||||
|
5. 驳回需求、作废需求、非本人订单、组长只读入口仍保持只读,不得放宽权限边界。
|
||||||
|
|
||||||
|
## 后端依据
|
||||||
|
|
||||||
|
- `HouseAssignmentService.reopenIfFinalizedForDirectEdit()`:最终确认后的直接编辑自动解冻。
|
||||||
|
- 替换、移除、改协议价等多个写入口均已调用该方法。
|
||||||
|
- `HouseDetailAggregator` 不暴露独立 reopen action 属预期行为,不需要后端恢复该按钮。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- 打开订单 `HL20260719151313174`,完成态仍可看到“替换”“改协议价”“移除”。
|
||||||
|
- 点击修改后 Network 先出现 reopen 或对应写接口自动解冻,操作成功,房务状态回到配房中。
|
||||||
|
- 重新配房并逐日确认后,可再次最终确认。
|
||||||
|
- 非本人、驳回、作废及只读入口仍不显示写操作。
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
# 房务调整提醒按总人数展示(修改接口)
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- **端类型:管理后台(Web)**
|
||||||
|
- **目标仓库:`mmg/hl-ui`**
|
||||||
|
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||||
|
- **联调/验收环境:`http://192.168.100.160:9527`**
|
||||||
|
- **小程序:无需处理**
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
订单调整删除一名出行人后,房务端“订单调整提醒”曾显示人员类型变化,例如“儿童人数 2 → 1”。房务只需要核对订单总人数,因此后端统一调整为“总人数 4 → 3”。
|
||||||
|
|
||||||
|
## 接口语义变更
|
||||||
|
|
||||||
|
涉及调整记录及房务详情中复用的 `changeItems`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "HEADCOUNT",
|
||||||
|
"label": "总人数",
|
||||||
|
"before": "4",
|
||||||
|
"after": "3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 总人数发生变化时,只返回一条 `HEADCOUNT`,`label` 固定为 `总人数`。
|
||||||
|
- 不再按成人、儿童、小童、婴儿分别返回多条 `HEADCOUNT`。
|
||||||
|
- 人员类型变化但总人数不变时,不返回 `HEADCOUNT`。
|
||||||
|
- 出行人明细及 `addedTravelerIds` 契约不变。
|
||||||
|
|
||||||
|
## 管理后台处理要求
|
||||||
|
|
||||||
|
1. 房务“订单调整提醒”直接展示 `label + before → after`,不得自行按人员类型重新计算。
|
||||||
|
2. 不要依赖旧的“成人人数/儿童人数/小童人数/婴儿人数”标签。
|
||||||
|
3. 历史调整记录仍可能保留旧标签,前端需要兼容只读展示;新记录按“总人数”展示。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- 订单出行人由 4 人删除 1 人后,房务提醒显示“总人数 4 → 3”。
|
||||||
|
- 页面不显示“儿童人数 2 → 1”等人员类型变化。
|
||||||
|
- 总人数不变时不出现人数调整提醒。
|
||||||
|
|
||||||
|
后端关联:`wx/HL#5090`、PR `wx/HL#5091`。
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
# 作废房务需求只读与历史详情(修改接口)
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- **端类型:管理后台(Web)**
|
||||||
|
- **目标仓库:`mmg/hl-ui`**
|
||||||
|
- **仓库地址:`https://git.1814.love:8443/mmg/hl-ui.git`**
|
||||||
|
- **联调/验收环境:`http://192.168.100.160:9527`**
|
||||||
|
- **小程序:无需处理**
|
||||||
|
|
||||||
|
## 业务硬规则
|
||||||
|
|
||||||
|
作废房务需求只能查看。页面不得提供联系房务、联系定制师、转单、配房、询房、替换、移除、改价、清空配房、驳回、最终确认等任何业务操作。
|
||||||
|
|
||||||
|
## 问题与原因
|
||||||
|
|
||||||
|
同一订单调整后会保留旧的失活需求并生成新的生效需求。此前列表虽返回 `voided=true`,但前端未标红、未展示原因;点击旧行又只按 `orderId` 请求详情,导致打开当前生效需求,出现旧记录与当前配房串版。
|
||||||
|
|
||||||
|
## 接口变更
|
||||||
|
|
||||||
|
### 1. 我的房务订单列表
|
||||||
|
|
||||||
|
`GET /v3/admin/order/grab-pool/my-claims/hotel`
|
||||||
|
|
||||||
|
作废行新增/明确字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "2078779808241668097",
|
||||||
|
"orderId": "2078739922130243586",
|
||||||
|
"requirementVersion": 2,
|
||||||
|
"voided": true,
|
||||||
|
"voidReason": "订单调整生成新版本,原需求已作废",
|
||||||
|
"voidedAt": "2026-07-20T16:52:29",
|
||||||
|
"primaryAction": {
|
||||||
|
"type": "VIEW",
|
||||||
|
"url": "/admin/order/2078739922130243586/arrange?requirementId=2078779808241668097"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:列表字段 `id` 就是本行的房型需求 ID,打开详情时必须连同该 ID 传给详情接口,不能只传 `orderId`。
|
||||||
|
|
||||||
|
### 2. 房务详情支持指定历史需求
|
||||||
|
|
||||||
|
`GET /admin/house/orders/{orderId}?requirementId={requirementId}`
|
||||||
|
|
||||||
|
该接口使用既有房务详情命名空间 `/admin/house`,请求时必须沿用 API 模块的绝对路径配置,不得自行添加 `/v3`。错误请求 `/v3/admin/house/orders/{orderId}` 会返回“接口不存在”。
|
||||||
|
|
||||||
|
### 2026-07-20 本地测试环境 Network 复核
|
||||||
|
|
||||||
|
`http://192.168.100.160:9527` 点击作废行“查看”时实际发出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
错误:GET /v3/admin/house/orders/2078739922130243586?requirementId=2078779808241668097
|
||||||
|
正确:GET /admin/house/orders/2078739922130243586?requirementId=2078779808241668097
|
||||||
|
```
|
||||||
|
|
||||||
|
同一弹窗的需求历史请求已经使用正确命名空间:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /admin/house/orders/2078739922130243586/requirement-history
|
||||||
|
```
|
||||||
|
|
||||||
|
因此请检查详情 API 方法是否误传 `baseURL: '/v3'`、V3 request config 或再次拼接 `/v3`。只修改详情请求,`operation-log` 仍按它自己的既有 `/v3/admin/house/...` 契约处理,不得全局替换。
|
||||||
|
|
||||||
|
- 不传 `requirementId`:保持原行为,返回当前生效需求。
|
||||||
|
- 传 `requirementId`:精确返回该订单的指定历史需求;ID 不属于该订单时返回业务错误。
|
||||||
|
- 作废历史需求不会混入当前需求的配房数据。
|
||||||
|
|
||||||
|
详情新增顶层字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"viewedRequirementId": "2078779808241668097",
|
||||||
|
"historicalRequirement": true,
|
||||||
|
"voided": true,
|
||||||
|
"voidReason": "订单调整生成新版本,原需求已作废",
|
||||||
|
"voidedAt": "2026-07-20T16:52:29"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`requirement.history[]` 同步增加 `requirementId`、`voided`、`voidReason`、`voidedAt`。
|
||||||
|
|
||||||
|
历史作废详情中:
|
||||||
|
|
||||||
|
- `actions` 下全部动作的 `enabled=false`;
|
||||||
|
- `permissions.canEdit=false`;
|
||||||
|
- `permissions.canSendMessage=false`;
|
||||||
|
- `requirement.actions.canSendMessage=false`,其他写动作同样为 `false`;
|
||||||
|
- `permissions.canViewMessage=true` 只代表允许查看既有留言,不代表可回复。
|
||||||
|
|
||||||
|
## 管理后台处理要求
|
||||||
|
|
||||||
|
1. `voided=true` 的列表行和详情必须使用明确的红色作废样式,并展示“已作废”、`voidReason` 和作废时间。
|
||||||
|
2. 作废列表行只能显示“查看”;不得显示“更多”菜单或任何联系、流转、配房按钮。
|
||||||
|
3. 点击作废行必须携带本行 `id` 作为 `requirementId` 请求详情,不得复用当前有效需求详情。
|
||||||
|
4. 详情只要 `voided=true` 或 `historicalRequirement=true`,前端必须再次强制只读并隐藏全部业务操作,不能只依赖某一个按钮字段。
|
||||||
|
5. 人数调整提醒直接展示后端 `changeItems`;按通知 70,人数仅显示“总人数 4 → 3”,不显示成人/儿童等具体人员类型变化。
|
||||||
|
|
||||||
|
### 当前订单详情增加“作废记录”入口
|
||||||
|
|
||||||
|
在当前有效订单的房务详情中增加按钮:`作废记录(N)`,让房务不必返回列表寻找红色卡片。
|
||||||
|
|
||||||
|
- `N` 为该订单历史需求中 `voided=true` 的数量;没有作废记录时可隐藏按钮或显示禁用的 `作废记录(0)`。
|
||||||
|
- 按钮建议放在详情标题区或需求信息区,与普通业务写操作分开,避免误认为可以恢复作废需求。
|
||||||
|
- 点击后打开只读抽屉/弹窗,列出该订单全部作废需求,至少展示:需求版本、提交/作废时间、作废原因、原状态。
|
||||||
|
- 列表数据可使用详情响应的 `requirement.history[]`,按 `voided=true` 过滤;每项必须使用自身 `requirementId`。
|
||||||
|
- 点击某条“查看详情”时调用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /admin/house/orders/{orderId}?requirementId={该条requirementId}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 历史详情继续执行严格只读规则,只能关闭/返回,不能联系、转单、配房、清空、驳回、最终确认或执行其他业务操作。
|
||||||
|
- 作废记录列表按 `voidedAt DESC` 展示,最新作废记录在前;本入口不改变“我的订单”主列表中作废卡片统一置底的规则。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- 同一订单的作废旧行与当前有效行能明确区分,旧行标红并显示原因。
|
||||||
|
- 旧行仅有“查看”,不存在任何写操作或联系操作。
|
||||||
|
- 打开旧行后 `viewedRequirementId` 等于该行 `id`,内容为旧需求快照,不出现当前配房。
|
||||||
|
- 作废详情仅可阅读,所有动作均隐藏或禁用。
|
||||||
|
- 删除一名出行人后,调整提醒显示“总人数 4 → 3”。
|
||||||
|
- 当前有效订单详情显示“作废记录(1)”;点击可看到该订单的作废需求列表,并能打开对应只读历史详情。
|
||||||
|
|
||||||
|
后端关联:`wx/HL#5092`。
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
# 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||||
|
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||||
|
- 小程序、H5 及其他前端:无需处理
|
||||||
|
|
||||||
|
# 变更背景
|
||||||
|
|
||||||
|
订单改出发日期后,原入住日期已经配置的酒店不能静默平移到新日期。房务需要明确核对并逐条删除旧配房;旧配房未清完时禁止最终确认。
|
||||||
|
|
||||||
|
# 详情接口新增字段
|
||||||
|
|
||||||
|
`GET /admin/house/orders/{orderId}` 顶层新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pendingRescheduleAssignments": [
|
||||||
|
{
|
||||||
|
"assignmentId": "99001",
|
||||||
|
"originalStayDate": "2026-07-22",
|
||||||
|
"hotelId": "8001",
|
||||||
|
"hotelName": "示例酒店",
|
||||||
|
"roomTypeId": "9001",
|
||||||
|
"roomTypeName": "普通标间",
|
||||||
|
"roomCategory": "STANDARD",
|
||||||
|
"roomCategoryLabel": "标间",
|
||||||
|
"roomCount": 2,
|
||||||
|
"assignmentStage": "FINAL_CONFIRMED",
|
||||||
|
"assignmentStageLabel": "最终确认",
|
||||||
|
"deleteEndpoint": "DELETE /v3/admin/order/assignments/99001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`assignmentStage` 枚举:
|
||||||
|
|
||||||
|
| 值 | 中文 | 含义 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `UNCONFIRMED` | 未单日确认 | 改期前仍处于询房/候选阶段 |
|
||||||
|
| `DAY_CONFIRMED` | 单日确认 | 改期前已完成该日确认,但原需求未最终确认 |
|
||||||
|
| `FINAL_CONFIRMED` | 最终确认 | 改期前所属住宿需求已经最终确认 |
|
||||||
|
|
||||||
|
数组为空表示没有改期旧配房待清理。旧配房不会再出现在当前 `itinerary[].assignments`,也不计入当前配房进度。
|
||||||
|
|
||||||
|
# 前端交互要求
|
||||||
|
|
||||||
|
1. 在“订单调整提醒”的改期记录下展示 `pendingRescheduleAssignments`,每行至少显示:原日期、酒店、房型、数量、配房步骤。
|
||||||
|
2. 每行提供“删除旧配房”,调用返回的 `deleteEndpoint`;成功后重新拉取详情。
|
||||||
|
3. 只要数组非空,不允许用户最终确认,并显示后端 `actions.canFinalize.disabledReason`。
|
||||||
|
4. 数组清空后再按后端 `actions.canFinalize.enabled` 决定按钮状态,禁止前端自行推断。
|
||||||
|
5. 删除仍可能因领取归属、房务写权限、并发修改或库存释放链路失败而报错,直接展示后端消息并刷新详情。
|
||||||
|
|
||||||
|
# 最终确认写口门禁
|
||||||
|
|
||||||
|
`POST /admin/house/assignments/requirements/{requirementId}/finalize`
|
||||||
|
|
||||||
|
若仍有旧日期配房,返回业务错误:
|
||||||
|
|
||||||
|
- code:`808183`
|
||||||
|
- message:`改期前旧日期配房尚未清理,请逐条删除后再最终确认`
|
||||||
|
|
||||||
|
该门禁由后端强制执行,前端禁用按钮仅用于交互提示。
|
||||||
|
|
||||||
|
# 兼容说明
|
||||||
|
|
||||||
|
- 字段为 additive;旧页面忽略新增字段不会影响反序列化。
|
||||||
|
- `roomTypeName` 在资源服务降级时可为空,前端回退 `roomCategoryLabel`。
|
||||||
|
- `assignmentId`、`hotelId`、`roomTypeId` 按字符串处理,禁止转 JavaScript `number`。
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
# 作废需求详情冻结作废时配房快照
|
||||||
|
|
||||||
|
## 目标前端
|
||||||
|
|
||||||
|
- 端类型:管理后台(Web)
|
||||||
|
- 目标仓库:`mmg/hl-ui`(`https://git.1814.love:8443/mmg/hl-ui.git`)
|
||||||
|
- 联调/验收环境:`http://192.168.100.160:9527`
|
||||||
|
- 小程序、H5 及其他前端:无需处理
|
||||||
|
|
||||||
|
## 业务规则
|
||||||
|
|
||||||
|
订单调整生成新住宿需求时,旧需求详情必须展示“该需求作废当时”的配房事实,不能复用当前订单的新日期、新行程地点或当前配房。历史数据严格只读,不能恢复或执行任何业务操作。
|
||||||
|
|
||||||
|
## 接口
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /admin/house/orders/{orderId}?requirementId={作废需求ID}
|
||||||
|
```
|
||||||
|
|
||||||
|
历史详情的 `itinerary[].assignments[]` 明确返回冻结字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dayNumber": 1,
|
||||||
|
"stayDate": "2026-07-28",
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"assignmentId": "2079188886726098945",
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||||
|
"roomTypeId": "2023727403196502017",
|
||||||
|
"roomTypeName": "普通标间",
|
||||||
|
"roomCategory": "STANDARD",
|
||||||
|
"confirmStatus": "CONFIRMED",
|
||||||
|
"confirmStatusLabel": "已确认",
|
||||||
|
"roomCount": 1,
|
||||||
|
"protoPrice": "280.00",
|
||||||
|
"settlementPrice": "279.00",
|
||||||
|
"settleType": "sign",
|
||||||
|
"sellPrice": "280.00",
|
||||||
|
"deductInventory": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`stayDate`、酒店、房型、数量、价格、支付方式、库存口径和确认状态均来自作废时快照。后续删除/修改当前配房、资源酒店改名或价格调整,不影响历史详情。
|
||||||
|
|
||||||
|
## 管理后台处理要求
|
||||||
|
|
||||||
|
1. 作废详情按 `itinerary[]` 展示旧日期;每条配房至少显示酒店、`roomTypeName`、`roomCount` 和 `confirmStatusLabel`。
|
||||||
|
2. 房型名称优先使用 `roomTypeName`;部署前没有可信名称快照的旧数据才允许回退 `roomCategoryLabel`,不得按 ID 或列表位置猜测。
|
||||||
|
3. `historicalRequirement=true` 或 `voided=true` 时保持严格只读:仅允许查看、关闭、查看车务;不得出现联系、转单、配房、删除、清空、驳回、最终确认等房务写操作。
|
||||||
|
4. 禁止用当前订单出发日期推算历史 `stayDate`,禁止调用当前资源结果覆盖后端返回的历史酒店/房型快照。
|
||||||
|
5. ID 字段按字符串处理,禁止转换为 JavaScript `number`。
|
||||||
|
|
||||||
|
## 兼容与验收证据
|
||||||
|
|
||||||
|
- 变更为 additive,当前生效需求的接口结构不变。
|
||||||
|
- 测试订单:`HL20260719151313174`,`orderId=2078739922130243586`。
|
||||||
|
- 作废需求:`requirementId=2079181505287897090`。
|
||||||
|
- 测试环境返回 3 晚旧配房:2026-07-28/29/30,酒店“呼伦贝尔香格里拉大酒店”,房型“普通标间”,数量 1/2/3,状态均为“已确认”。
|
||||||
|
- 浏览器验收使用仓库 CDP 脚本完成,页面无 console error 或 failed request。
|
||||||
|
|
||||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
正在加载...
x
在新工单中引用
屏蔽一个用户