p2_ready_check.py 341 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
存量 Controller「就绪度」盘点:老门户的每个 Controller,它依赖的 `clouds.api` 服务在新栈**齐不齐**。

它回答什么(这是挑下一批迁移目标前的**第一步**,见 AGENTS.md §3.1 第 0 步):

    一个老 Controller 里的每个 `import com.zteits.clouds.api.service.*`,是不是
      (a) 接口在 `parking-module-api` 里存在,且
      (b) 有一个 `class XxxImpl implements Xxx` 的实现类。

两条都满足才算"就绪"。**任一不满足 = 这个 Controller 搬过来就是 500**,
不管它的端点看上去多简单、多像"只读",所以**不要**靠读代码去猜,跑这个脚本。

全仓当前结果(2026-10-13 盘点,迁移进度同步到 **2026-10-26 第二百八十切片**,`zteits-bcp-portal` 一个 portal):老门户 **232 个 Controller**,
其中 **227 个有映射 / 5 个是死代码**(整类被注释掉,本脚本已跳过)。
有映射的 227 个里:**223 个 / 1180 端点就绪**(依赖服务齐全),**4 个 / 38 端点有缺口**。
就绪的 223 个中已迁 **223 个 / 1180 端点** → 就绪且未迁 **0 个 / 0 端点**;
**再过坑 22 一刀:✔ 0 个 / 0 端点,⚠ 0 个 / 0 端点(⚠A 0/0 + ⚠B 0/0)。**
⛔ **2026-10-20 第一百六十八切片:`OrderExamineController` 判定不可迁(半合并壳)**
  四个生产分支都只有 Controller + API 接口,**没有实现、没有 Dubbo provider、`examinePlNos` 全仓无赋值**;
  实现只在未合入的 `origin/chifeng_dev_high_video_examine` ⇒ 机械缺口仍报,但单列
  `gapAdjudicated*` / `gapOpen*` 两组数,让「可迁缺口」归零而「机械缺口」仍说真话。
  详见代码里的 `GAP_ADJUDICATED` 表。
★ **2026-10-20 第一百六十九切片:P3 ③ APP 端登录(`UserLoginAppController` 5 端点)**
  **本片不在本脚本口径内**:`DEFAULT_LEGACY` 只扫 `zteits-bcp-portal/.../web`,
  `zteits-pay-portal/.../web/app` 从来不在扫描根里(app 端 Controller 不消费 portal 的
  `clouds.api` 契约)⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**(已判 1/2、可迁 1/9)、5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十切片:P3 ④ 登录桥补 `loginCode`(零端点)**
  只改框架层(`LoginUser.info` 多一个 `username`)+ `LoginUserBridge`,**不新增也不迁移任何端点**
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
★ **2026-10-20 第一百七十一切片:APP 端只读小接口(5 类 / 7 端点)**
  搬的是 `zteits-pay-portal/.../web/app/{Activity,AppNews,CommonProblem,CustRule,AppCheck}`,
  同 §169 一样**不在 `DEFAULT_LEGACY` 里** ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十二切片:APP 端码表/组织/反馈建议(3 类 / 8 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{SysCodeValueQueryController, AppOrgController, SuggestController(只读 4/5)}`,
  同样**不在 `DEFAULT_LEGACY` 里**(这 3 个类只依赖 `AppOrgService`/`TdCCustPersonService`/
  `TdCCustSuggestService`/`TdCCustSuggestDetailQueryService` 与缓存工具,不消费 portal 契约)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十三切片:APP 端对外车场信息/停车记录/ETC(3 类 / 5 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{outinterface/ParkInfoController, outinterface/ParkRecordController,
  newversion/ETCStateController}`,同样**不在 `DEFAULT_LEGACY` 里**
  (这 3 个类只依赖 `ParkFreeBerthQueryService`/`ParkingLotQueryService`/`BerthManageService`/
  `ParkOrderQueryService`/`TdCCustPersonService`/`SysConfigCodeOrgIdService` 与 `AppTokenUtil`,
  不消费 portal 契约)⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十四切片:APP 端接口平台/积分/时域卡/模板缓存/现金找零(5 类 / 9 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{ApiController, CustScoreController, newversion/VipTimeCardRuleController,
  MessageTempletCacheRefreshController, cashchange/KeyTopCashChangeController}`,
  同样**不在 `DEFAULT_LEGACY` 里**(这 5 个类只依赖 `ApiService`/`TdCCustScoreDetailService`/
  `TdCFileService`/`TVipAreaCardService`/`TdCMsgTemplateService` + `MessageTempletCacheUtil`,
  不消费 portal 契约)⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十五切片:APP 端初始化/无感支付/特殊车牌(3 类 / 4 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{pingkang/InitController, newversion/NonSensePaymentController,
  specialcar/SpecialCarnumberBillingController}`,同样**不在 `DEFAULT_LEGACY` 里**
  (只依赖 `SysCommonService`/`TdCAcctNoSecretService`/`SpecialCarnumberBillingService`/
  `TdCAcctPersonService` + `RedisCacheUtil`/`ParkFreeBerthsCacheUtil`,不消费 portal 契约)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十六切片:APP 端用户签到/免密支付(2 类 / 9 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{UserSignController, AcctNoSecretController}`,同样**不在 `DEFAULT_LEGACY` 里**
  (只依赖 `TdCCustSignService`/`AcctNoSecretService`/`TdCCustPersonService`,
  不消费 portal 契约、不新增缓存)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十七切片:APP 端年底活动/卡劵(2 类 / 8 端点)**
  搬的是 `zteits-pay-portal/.../web/app/`
  `{YearActivityController, CouponRuleController}`,同样**不在 `DEFAULT_LEGACY` 里**
  (只依赖 `TdCCouponRuleService`/`TdCCouponPersonService`/`TdCCustScoreDetailService`/
  `TdCCustPersonService`/`AppOrgService`,不消费 portal 契约)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
★ **2026-10-20 第一百七十八切片:APP 端车主车牌(1 类 / 3 端点)**
  搬的是 `zteits-pay-portal/.../web/app/UserCarController`,同样**不在 `DEFAULT_LEGACY` 里**
  (只依赖 `TdCCarService`/`TdCCarBrandService`/`TdCCarCheckRecordQueryService`,不消费 portal 契约)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
  ★ 注意:同批原定的 `CustScoreController`(`/score/*` 4 条)新栈早已迁过
    (`controller/app/score/CustScoreAppController`)⇒ 立项先 `grep` 路由,别只 grep 类名。
★ **2026-10-20 第一百七十九切片:APP 端停车场信息查询(1 类 / 7 端点)**
  搬的是 `zteits-pay-portal/.../web/app/TabParkingLotController`(老 `/park`),同样
  **不在 `DEFAULT_LEGACY` 里**(只依赖 `ParkingLotQueryService`/`TBillingDescriptionQueryService`/
  `ParkFreeBerthQueryService`/`TpPBerthsQueryService`/`BerthManageService`/`AppOrgService`,
  全部是新栈既有 Bean;6 个门户 VO 落到 `parking-legacy-common/.../vo/app/parklot`)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
  ★ 注意:本片 7 条里只有 `query-park-berth-msg-res` 验签;带对象体的 5 条沿用老 `@Valid`,
    所以 `{}` 会被 `AppBaseReq` 的 `@NotBlank` 挡在绑定层 400(不是 500)。
★ **2026-10-20 第一百八十切片:APP 端小额免密支付(1 类 / 6 端点,4 条真写)**
  搬的是 `zteits-pay-portal/.../web/app/PettyPaymentController`(老 `/pettyPayment`,新 `/pay/petty-payment`),
  同样**不在 `DEFAULT_LEGACY` 里**(只依赖 `TdCCustPersonService`;2 个门户 VO 落在
  `parking-legacy-common/.../vo/app/pettypayment`)⇒ 就绪 **225/1199**、已迁 **221/1195**、
  未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。这里只记录口径,不改判定。
  ★ 注意:本类**一次都没调** `validateSigin`/`checkParam`,`@NoAuth`/`⚠A` 那套启发式对它不适用;
    取用户靠 `AppTokenUtil.getUserInfo` ⇒ 假 token 是 [存量错误码 -10000],不是 [4014]。
  ★ 注意:`IRainParkingController`(9)整个类已注释掉 ⇒ 死代码,没有迁移目标。
★ **2026-10-20 第一百八十一切片:反馈建议第 5 条 `saveFeedbackAndSuggest`(1 端点,真写)**
  补的是第一百七十二切片留下的那条写端点,直接加进既有 `SuggestAppController`
  (老 `/suggest/saveFeedbackAndSuggest` → 新 `/pay/suggest/save-feedback-and-suggest`)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
  ★ 注意:`orgId` 非数字时 `Long.parseLong` 抛 NFE,位置在 token 校验**之前** ⇒ 500「系统异常」。
  ★ 注意:新增分支的 `insertTdCCustSuggest` 返回值被丢弃(存量缺陷,照搬)。
★ **2026-10-20 第一百八十二切片:APP 端个人卡券 `CouponPersonController`(1 类 / 8 端点,4 条真写)**
  同样**不在 `DEFAULT_LEGACY` 里**(`zteits-pay-portal/.../web/app` 不在盘点范围)。依赖全是新栈既有 Bean:
  `TdCCouponPersonService`/`TdCCouponRuleService`/`TdCCustScoreDetailService`/`TdCAcctPersonService`/
  `WhitelistService`/`AppOrgService`/`DiscountWrapper`/`TActivityThirdDiscountService`/`PayOrderQueryService`;
  5 个门户 VO 落到 `parking-legacy-common/.../vo/app/coupon` + `.../vo/app/billquery`
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:同类里**三种老鉴权档位并存** —— `AppRsesultUtil.checkParam(String)`(回信封)与老
    `AppBaseController#checkParam(AppBaseReq)`(抛 `BizException`)的 `{}` 都是 [4014];
    裸 `AppTokenUtil.getUserCustId` 是 [-10000];`deleteCouponPerson` 的 1003 文案校验在鉴权**之前**。
  ★ 注意:本片把 `DiscountWrapper` 从 `parking-module-order` 移到 `parking-legacy-common`
    (**同 FQN `com.zteits.ipay.util`**),order 侧 import 不变、单进程仍只有一个 Bean。
★ **2026-10-20 第一百八十三切片:APP 端账号信息 `UserController`(7 端点,4 条动库)**
  默认盘点口径同样**不含** `zteits-pay-portal/.../web/app`。老类共 25 条 POST,其中 6 条**早已搬过**
  (`sendverificode`/`checkverificode`/`login`/`alias`/`logout` → §169 `/pay/user-login/*`;
  `userCarsInfoEdit` → §178 `/pay/user-car/*`)⇒ 本片只搬 `queryUserInfo`/`shareInfo`/
  `getUserPhoneByToken`/`updateCustPersonBaseInfo`/`queryCustPersonBaseInfo`/`updateUserChangeOrgId`/
  `queryUserCars` 七条到 `/app-api/pay/user-info/*`。依赖全是既有 Bean;老门户的 `AppOrgCommonService`
  复制进 `parking-legacy-common/.../pay/portal/util`(同 FQN)
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:`updateUserChangeOrgId` **没有任何签名校验**,`checkAppToken` 失败就 `success(true)` 早退
    (回成功信封但一行不写);`queryUserInfo`/`queryCustPersonBaseInfo` 在读接口里 `updateByUserPhone` 写库。
★ **2026-10-20 第一百八十四切片:APP 端注册/登录/改密一族 `UserController` 最后 10 端点(9 条动库)**
  默认盘点口径同样**不含** `zteits-pay-portal/.../web/app`;且工具是**按类**判已迁,`UserController`
  早在 §169 就计入 221/1195 ⇒ 本片把老类剩下的 `userRegistration`/`passwordLogin`/`messCodeBeforeCheck`/
  `verificationCodeLogin`/`userRecoverPasswordCheck`/`changePassword`/`setPassword`/`clearAccount`/
  `oneClickLogin`/`uploadHeadPic` 十条搬到 `/app-api/pay/user-account/*` 后,计数**一字不动**
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:**multipart 端点缺参是 500 不是 400** —— `upload-head-pic`(`@RequestParam picFile + token`)
    空 body 被全局兜底成「系统异常」⇒ 进不了 `LEGACY_WRITES` 的 400 断言,单钉在冒烟 §2fu (c)。
  ★ 注意:`oneClickLogin` **不验签**,第一道是 `operatorToken` 非空(空 ⇒ [1003]),非空值会调**外部运营商接口**
    ⇒ 冒烟只发空串;其余 8 条走老私有校验(先必填、再验签),假签名一律 [4014]。
  ★ 注意:`FastDFSClientWrapper` 在 **pay 模块**是活的 `@Component`(`com.zteits.pay.util`),
    order 模块那份整类被注释 ⇒ `uploadHeadPic` 直接注入即可,无需复制。
★ **2026-10-20 第一百八十五切片:APP 端会员卡办理 / 区域卡规则 / 时域卡使用(3 类 / 9 端点,全只读)**
  默认盘点口径同样**不含** `zteits-pay-portal/.../web/app`:`ApplyVipCardController`(2)→
  `/app-api/pay/apply-vip-card/*`、`VipAreaCardRuleController`(6)→ `/app-api/pay/vip-area-card-rule/*`、
  `VipTimeCardUseController`(1)→ `/app-api/pay/vip-time-card-use/*`。依赖全是既有 Bean
  ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:**老基类 `com.zteits.pay.portal.web.app.AppBaseController` 本片首次搬进 `parking-legacy-common`(同 FQN)**
    ⇒ 后续 APP 切片直接 `extends`,别再内联副本;同时搬了 `com.clouds.common.constants.ParkConstant`
    (parking-cloud 原先只有 `com.zteits.park.constant.ParkConstant`,pay 模块不依赖 park 模块)。
  ★ 注意:`{}` 落点分两档 —— 带 `@Valid` 的 3 条 400「请求参数不正确」,`VipAreaCardRule` 的 6 条
    **无 `@Valid`** ⇒ 进方法体被 `checkParam` 拦成 500 `[4014]`。
  ★★ 注意:`@Valid` 多约束并存时校验报文**不稳定**(`@NotNull carNumber` 与 `@NotBlank` 同时命中,
    遍历顺序随 HashMap 变)⇒ 冒烟只锁 400 + 「请求参数不正确」前缀。
★ **2026-10-20 第一百八十六切片:APP 端充电桩充电(1 类 / 4 端点,3 读 + 1 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/battery/BatteryChargeController`:
  `queryEquipChargeStatus`→`/app-api/pay/battery-charge/query-equip-charge-status`、
  `queryBatteryChargeInfo`→`/query-battery-charge-init-info`、
  `queryAccountDetailsForPage`→`/query-order-charge-info-for-page`、`stopCharge`→`/stop-charge`。
  依赖全是既有 Bean ⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。这里只记录口径,不改判定。
  ★ 注意:APP 端老控制器**还有第二种鉴权形态** —— 这 4 条**都没有 `checkParam`**,只靠裸
    `AppTokenUtil.getUserCustId(token)` ⇒ 无 token / 假签名的落点是 500 `[存量错误码 -10000]`,
    不是 4014;`query-order-charge-info-for-page` **无 `@Valid`** ⇒ `{}` 也到 [-10000]。
  ★ 注意:`stop-charge` 是**真写 + 出站**(`queryStopCharge` 下发停机 + `updateOrderChargeStatusStop`),
    冒烟只发空 body;`query-battery-charge-init-info` 会做出站设备认证,探针只用必然查不到的假 `connectorCode`。
  ★ 注意:`ChargeStateInfoDTO` 本片**补回老 api 的 3 个字段**(`feeInfoList`/`stationLng`/`stationLat`)——
    parking-cloud 早先裁剪过,纯加字段、不改 `BizResult` 与接口签名。
★ **2026-10-20 第一百八十七切片:APP 端停车场会员卡(1 类 / 12 端点,11 读 + 1 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/ParkVipCardController`(老 `/parkvip`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:本类是「三种鉴权形态并存」样本 —— 2 条 `@Valid` + `validateSigin`、2 条 `checkParam`、
    1 条 `AppRsesultUtil.validateSigin(JSONObject)`、其余 7 条**零校验** ⇒ `{}` 落点分
    400 / 500 [4014] / 老信封 1003 / 老信封 0 四档,写断言前必须逐条 grep。
  ★ 注意:`query-parking-vip-card` 本地恒 500「系统异常」—— bill 的
    `TVipCardParkSaleRelSmapper.querySaleVIPCardRuleForPage` 是存量 `GROUP BY` 写法,
    本地 MySQL 8 `only_full_group_by` 直接拒 ⇒ 非回归、红线⑥不修、冒烟不断言。
  ★ 注意:`VipCardAuthPersonBjxcService` 只有**嵌套包**(`...order.VipCardAuthPersonBjxc.`)那个接口
    有实现;用扁平包注入会启动失败。⛔ `change-car-number-for-vip-card` 是真写,冒烟只发空 body。
★ **2026-10-20 第一百八十八切片:APP 端发票申领(1 类 / 21 端点,11 读 + 10 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/InvoiceHelperController`(老 `/invoice`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。
  ★ 注意:**`@Valid` 能否把 `{}` 挡成 400 取决于 VO 的父类** ——
    `ResendInvoiceMailReq extends AppBaseReq`(app 公共五件套 `@NotBlank`)⇒ 400;
    `QueryInvoiceHeaderReq extends AppBaseRequest`(无约束)⇒ `{}` 直接成功;
    17 条 `@RequestBody String` 与 1 条**无 `@Valid`** 的 `query-rnt-pay-order-for-invoice`
    ⇒ `{}` 落到方法体里的 `checkParam`,500 `[4014]`。
  ★ 注意:`query-invoic-header` / `query-invoic-order-date` 老代码把 `checkParam` **注释掉**
    ⇒ 无鉴权公开可读(照搬)。`query-invoic-order-date` 读 `SysCodeValueCacheUtil(APP_INVOICE_QUERY_DATE)`,
    本机未配 ⇒ 空列表,别据此判"端点死了"。
  ⛔ `open-ticket-for-cust-invoice-his-info`(真插开票历史 + 航信开票队列出站)与
    `resend-invoice-mail`(真实发邮件)是真写,冒烟只发空 body;同片另有 8 条纯 CRUD 写端点。
★ **2026-10-20 第一百八十九切片:APP 端账户充值(1 类 / 19 端点,15 读 + 4 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/AppAcctRechargeController`(老 `/appAcctRecharge`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  这里只记录口径,不改判定。(`zteits-minipay-portal` 里同 FQN 的另一份不搬。)
  ★ 注意:同一类里**三种鉴权形态混排** —— 7 条 `@Valid` + VO 继承 `AppBaseReq` ⇒ `{}` 400;
    9 条无 `@Valid` 的 VO + 3 条 `@RequestBody String` ⇒ `{}` 落 `checkParam`,500 `[4014]`;
    `get-account-recharge-amount` **无入参、无鉴权**(公开可读码表,本机恒 `[{"code":"0","name":"0"}]`)。
  ★ 注意:`get-park-card-by-park-card` / `account-recharge-by-park-card` 是**死端点**,恒抛
    `[存量错误码 3017]`「该版本已经失效」(照搬,别当回归)。
  ⛔ 真写 4 条:`get-accout-balance`(`checkParam` 后 `updateByUserPhone` 刷客户操作时间)、
    `open-account`、`acct-deposit-refund`(建退款单)、`account-recharge-by-recharge-card`
    (`aliWxPayService` 建充值支付单);冒烟只发空 body。
  ★ 负向探针必须**带 token**:不带 token 时安全层先回 401「账号未登录」,看不到 404。
★ **2026-10-20 第一百九十切片:APP 端会员卡退款(1 类 / 2 端点,1 读 + 1 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/ParkVipCardRefundController`(老 `/parkviprefund`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  依赖只有既有 Bean `RefundOrderOperService`;新增 2 个门户 VO(`vo/app/vipcard/VipCardRefundReq` +
  `QueryVipCardRefundReq`)。
  ★ 注意:两条都 `@Valid` + VO 继承 `AppBaseReq` ⇒ `{}` 统一 400;假签名统一 500 `[4014]`。
  ⛔ `/refund` 是真写(建退款单,且先查重),冒烟只发空 body。
★ **2026-10-20 第一百九十一切片:APP 端消息推送开关(1 类 / 1 端点,1 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/MsgPushController`(老 `/msgPush`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  依赖只有既有 Bean `TdCCustPersonService.openMsgPush`,零新增 VO/DTO。
  ★★ 注意:该类**不继承 `AppBaseController`**、没有 `checkParam`/`validateSigin`;token 从 body JSON 取,
    直接 `AppTokenUtil.getUserInfo` ⇒ 无效 token 是 **`-10000`「登录失效」**,不是 `4014`。
  ⛔ `/open-or-close` 改客户推送状态,冒烟只发空 body。
★ **2026-10-20 第一百九十二切片:APP 端极光消息推送(1 类 / 6 端点,4 读 + 2 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/JPushController`(老 `/push`)⇒
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  依赖全是既有 Bean(`JPushService`/`ParkingLotQueryService`/`TdCMsgTemplateService`/`TdCCustPersonService`);
  新增 3 个门户 VO(`vo/app/messagepush/*`)。
  ★★ 注意:**同一类三种鉴权落点** —— 4 条 `String`+`checkParam`/`validateSigin` ⇒ `{}`/假签名 500 `[4014]`;
    `new-message-push-homepage` 假 token 抛 `[2003]`;`un-reads-total` 假 token **回 code "0" 全 0 成功**
    (存量契约,别当缺陷)。逐条看方法体,别整类套模板。
  ⛔ 真写 2 条:`delete-pushed-message-details`(按 msgType 删)、`new-message-push-homepage`(读完置已读);
    冒烟只发空 body。
★ **2026-10-20 第一百九十三切片:APP 端与华软 ETC 结算对接(1 类 / 2 端点,2 写)+ 补回 QR 进出场链**
  口径同样**不含** `zteits-pay-portal/.../web/app/etcinterface/ElectronicTollCollectionController`
  (老 `/etc/pay/`)⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、5 死代码**全部不变**。
  本片补整条链:13 个 VO(etcinterface/qrcodeinout/billquery/ETCBaseReq)、
  `portal/util/{ParkLotCommonService,DateUtils}`、`portal/config/TimedCacheFactory`、
  `portal/web/app/qrcodeinoutparking/`(接口/路由/适配器/基类 + 4 个 impl + 科拓费用查询 InnerService)。
  ★★ 注意:**返回值不是 `AppBaseRes` 信封** —— 签名失败回 `{"code":"5","message":"验证签名失败"}`
    (`QueryFeeVO`/`PayNoticeVO`),断言按 code 字符串比,别套 500/[4014] 模板。
  ⛔ 两条都真写:`/query-fee` 改支付单 + 写 Redis 防重;`/notice` 走 `etcPayService.payNotify` 真结算。
    冒烟只发空 body(两条也都进 `LEGACY_WRITES`)。
  ★ `KeyTopQRcodeInOutParkController` 仍留(下一片搬;当时误以为它「真正使用 `BillQueryCommonService`」)。
★ **2026-10-20 第一百九十四切片:科拓道闸扫码进出场(1 类 / 2 端点,2 写)**
  口径同样**不含** `zteits-pay-portal/.../web/app/qrcodeinoutparking/KeyTopQRcodeInOutParkController`
  (老 `/keyTopQrCodeInOutPark`)⇒ 就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。零新增 VO(`UseCouponQueryFeeReq/Res` 侦察阶段已补)。
  ★★ 纠正上一片的结论:`BillQueryCommonService` 在本类**是死字段**(注入后两个方法体从未引用),
    删掉即可,**不必**搬 `BillQueryService`/`BillQueryRouteService` 整条链。
  ★★ 注意:老类 `// validateSigin(request);` **是注释掉的**,全类无签名校验 ⇒ `{}`/假字段都是
    **400「请求参数不正确」**,不是 500/[4014]。别按签名失败模板写断言。
  ⛔ 两条都真写:`QrCodeInPark`/`QrCodeOutPark`(补录进场、建/改待支付单)、
    `updateWaitPayOrderFeeByPayOrderId`(改金额);冒烟只发空 body / `{}` / 假字段。
★ **2026-10-21 第一百九十五切片:共享车位收益提现(1 类 / 2 端点,1 读 + 1 写)**
  搬的是 `zteits-pay-portal/.../web/app/sharedparking/SharedParkingTransferController`
  (老 `/sharedparking/transfer`)⇒ 口径同样**不在 `DEFAULT_LEGACY` 里**,
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。新增 3 个门户 VO:
  `vo/app/sharedparking/{OperTransferReq,QueryTransferReq,QueryTransferLogRes}`。
  ★★ 同一控制器里的**两个端点假签名码不同**:`oper-transfer` 的 `OperTransferReq` 自带
    5 个 `@NotNull/@NotBlank`,`@Valid` **先于** `checkParam` 失败 ⇒ **400**;
    `query-transfer` 的 `QueryTransferReq` 无自有注解 ⇒ **500 [存量错误码 4014]**。别套模板。
  ⛔⛔ `/oper-transfer` 是全仓最危险的写端点:`aliWxPayService.transfers` **真实发起
    支付宝/微信提现**,微信分支还先调 `sns/oauth2/access_token` 换 openid。
    冒烟**只发无 body**(进 `LEGACY_WRITES`),真机联调必须沙箱/测试账号。
  ★ 存量缺陷照搬:`query-transfer` 按月**循环查库(N+1)**;`queryTransferLogResList` 在循环外创建
    ⇒ 第 n 个月响应重复带上前 n-1 个月记录;`totalFee` 无 `@NotNull`,`getTotalFee()<=0` 会 NPE。
★ **2026-10-21 第一百九十六切片:共享车位·我的共享车位(1 类 / 6 端点,全部读)**
  搬的是 `zteits-pay-portal/.../web/app/sharedparking/SharedParkingHomeController`
  (老 `sharedparking/sharedBerthHome`)⇒ 口径同样**不在 `DEFAULT_LEGACY` 里**,
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。新增 10 个门户 VO(`vo/app/sharedparking/` 9 个 +
  `vo/app/billquery/QuerySharedParkFeeRes`)。
  ★★ 与 §194 相反:`BillQueryCommonService` 在这里**不是死依赖**(3 处 `queryBillingForShared` 调用)。
    新栈在 `com.zteits.pay.portal.util` 补回**只含两个重载**的版本(不需要 `BillQueryService`/
    `BillQueryRouteService` 整条链)。
  ★★ `{}` 在同一个类里有**三种落点**:`query-shared-page` 无 `checkParam` ⇒ `code="0"`(**字符串**);
    两个走 `@Valid` 的 ⇒ 400「请求参数不正确」;其余三个 ⇒ 500 `[4014]`。
  ★★ 400 文案**不稳定**(`不能为空` 与 `不能为null` 随机,取决于 Hibernate Validator 遍历顺序)
    ⇒ 断言只钉 `请求参数不正确` 前缀。
  ★ 假签名三种:`query-shared-page` ⇒ 500 `[-10000]`(`AppTokenUtil` 认不出 token);
    `query-shared-by-berth-no` ⇒ 400(VO 自带 `@NotNull`);其余四个 ⇒ 500 `[4014]`。
  ★ 6 条**全是读** ⇒ 零 `LEGACY_WRITES` 增量、零 `@OperateLog`。存量缺陷:分页里 N+1 查订单、
    `isAllowDownLock` 查不到订单直接 NPE、`getPreActFeeForShareBerth` 的允许取消时长可能为负。
★ **2026-10-21 第一百九十七切片:共享车位·预订(1 类 / 6 端点,2 读 + 4 写)**
  搬的是 `zteits-pay-portal/.../web/app/sharedparking/SharedParkingReservationController`
  (老 `/sharedparking/reservation`)⇒ 口径同样**不在 `DEFAULT_LEGACY` 里**,
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。新增 10 个门户 VO(全部继承 `QuerySharedByPlNoAndBerthNoReq`)。
  ★★ 本类两个口径最干净的对照:6 个 VO 全**继承带 `@NotNull` 的基类** ⇒ `{}` 与假签名
    **一律 400「请求参数不正确」**,`@Valid` 先于 `validateSigin`/`checkParam`,**零 500**。
    ⇒ 判 `{}` 落点的唯一判据就是"VO 有没有自带注解",别按控制器套模板。
  ★ 4 条真写(`reservation-submit`/`cancel-pre-order`/`in-parking`/`out-parking`)进 `LEGACY_WRITES`,
    只发无 body;老类零 `@SysOpLog` ⇒ 新类零 `@OperateLog`。
  ★ 存量缺陷:`queryPreInTimes` 第一个时间点 `psPreFee` 恒 0(后 3 个才取担保费);
    `inParking` 在"现在已过预约入场时间"时把进场时间回退成预约时间;`outParking` 在
    `psInTime == null` 时 NPE;`reservationSubmit` 的 `Long.valueOf(preTotalFee)` 无 null 守卫。
★★ **2026-10-21 第一百九十八切片:共享车位·我的共享车位(Rnt)(1 类 / 6 端点,4 读 + 2 写)**
  搬的是 `zteits-pay-portal/.../web/app/sharedparking/MySharedBerthController`
  (老 `sharedparking/mySharedBerth`)⇒ 口径同样**不在 `DEFAULT_LEGACY` 里**,
  就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、机械缺口 **2/11**、
  5 死代码**全部不变**。零新 VO(全走 `JSONObject.parseObject`)。
  ★★ 本类**不继承 `AppBaseController`**,手写 `AppRsesultUtil.checkParam` ⇒ 响应
    `{code, message}`(不是 `msg`)且 `code` 是**字符串**;`save-parking-berth-share`
    把 5 个业务字段校验放在 `checkParam` 之前 ⇒ `{}` 直接回 `code="1003"`
    「共享车位详细地址为空!」,其余 5 条才是 500 `[4014]`。
  ★ 2 条真写(`save-parking-berth-share`/`update-park-share-berth-by-berth-no-and-cust-id`)
    进 `LEGACY_WRITES`,只发无 body;老类零 `@SysOpLog` ⇒ 新类零 `@OperateLog`。
  ★ 存量缺陷:`saveParkingBerthShare` 客户号为 null 时 NPE;两个费用接口逐条调计费(N+1);
    `updateParkShareBerth...` 不校验车位归属;6 条全 `catch(Exception)` 吞成业务码。
★★ **2026-10-21 第一百九十九切片:自助停车(1 类 / 2 端点,1 读 + 1 写)**
  搬的是 `zteits-pay-portal/.../web/app/AppSelfParkingController`(老 `/appSelfParking`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。新增 2 个门户 VO;补回 `SelfParkingCommonService`。
  ★★ `@Valid` 才是 `{}` 落点的开关:本类继承 `AppBaseController`、`BerthParklotReq` 也有
    `@NotNull berthNo`,但方法参数没加 `@Valid` ⇒ `{}` 与假签名**一律 500 `[4014]`**
    (落 `validateSigin`),与 §197 的 400 正好对照。
  ★ `in-park-self` 是 `private` 方法但仍是真端点(Spring 反射调用);它是本片唯一真写
    (白名单 → Redis 防重 → `insertParkOrder` 建单 + 推 PDA 消息)⇒ 只进 `LEGACY_WRITES`、只发无 body。
  ★ 存量缺陷:`getIsSelfParking() == 1` 自动拆箱 NPE;`nightChargeDesc` 误取 `dayChargeDesc`;
    `regionAreaDTO`/`parkLotByPlNo` 无 null 守卫;`Integer.valueOf(carNumberColor)` 无守卫;
    `listBizResult.getData()` 前不判 `isSuccess`。
★★ **2026-10-26 第二百八十一切片:`IncomeBerthReportController` +1(停车位日报 URL 型导出补齐 ⇒ 该类 3/3 全迁)**
  同样补在**已迁类**里 ⇒ 本脚本看见的仍是 **+0 类 / +0 端点**;重测全部不变:
  就绪 **223/1180**、已迁 **223/1180**、**就绪且未迁 0/0**、⚠A **0/0**、⚠B **0/0**、
  机械缺口 **4/38**(已判 **1/2** ⇒ 机械口径下可迁 **3/36**)、死代码 **5**、导出 **97**。
  ★ 这是本脚本的**口径边界**:它按**类**计数,"同一个类里再补端点"永远显示 +0;
    端点级差集靠 `api_smoke.py`(§2i27 等分块断言)与 `p2_audit_*` 兜住,别拿这里说"没变化 = 没干活"。
  ★ 顺带销账一条**旧判定的错因**:`billing/UploadController.uploadPic` 早在第二百一十六切片就迁完了
    (`POST /admin-api/pay/upload/upload-pic`,`pay:upload:create`);旧笔记里说的
    「`FileStorageServiceImpl.uploadFile` 整段注释、`return null`」指的是 `com.zteits.pay.biz` 的 **Dubbo 服务**,
    与本端点无关。⇒ 当前"上传类"队列只剩 4 条(`insertParkingBerthShare` / `upload-employee-head-pic` /
    `recharge-card-generate-pic` / `uploadParkLotPic`),本机都只验负向、正向走离线桩。

★★ **2026-10-26 第二百八十切片:两条 URL 型导出补迁(`ParkInOutRecordController` +1 / `IncomeChargerCheckTaskController` +1)**
  两条同样补在**已迁类**里 ⇒ 本脚本看见的仍是 **+0 类 / +0 端点**;重测:
  就绪 **223/1180**、已迁 **223/1180**、**就绪且未迁 0/0**、⚠A **0/0**、⚠B **0/0**、
  机械缺口 **4/38**(已判 **1/2** ⇒ 机械口径下可迁 **3/36** = `ParkingRecordController` 15 +
  `InvoiceOperController` 12 + `ParkingAssistantController` 9,后一个 ⛔ 红线不动)、死代码 **5**。
  ⚠️ **225/1199 → 223/1180 不是回归**:用户 2026-10-26 的三个提交(10:56 / 11:27 / 14:14)在老仓
  `zteits-api` 新增 `order.parkorder.oper.ParkRecordCashService`、`pay.invoice.InvoiceRedFlushService`、
  `pay.invoice.InvoiceFaceAuthService`,而 `parking-module-api` **都没有** ⇒ `ParkingRecordController`(15)
  与 `InvoiceOperController`(12) 从「就绪」掉进「缺口」。缺口 4 类 = 15 + 12 + `ParkingAssistantController` 9(⛔)
  + `OrderExamineController` 2(已判不可迁)。
  ★ 这两条**返回 URL 字符串而不是响应流** ⇒ 按既定口径保持 `POST`,不进「导出 97」的统计;
    改 `GET /export-excel` 是契约变更(红线⑥)。
  ★ 本脚本不受影响的真原因:它只比「老类依赖的 service 有没有实现」,URL 型导出的差别在 Controller 形状。

★★ **2026-10-26 第二百七十九切片:`ArrearageStaticController` +3(欠费统计的 ajax 导出补齐)**
  老类是「4 读 + 1 响应流导出已迁、3 条模板型 ajax 导出挂起」,本片补上后**该老类 8 端点全迁**。
  3 条同样**补在已迁类里** ⇒ 本脚本看见的仍是 **+0 类 / +0 端点**;就绪口径
  **225/1199、就绪且未迁 0/0、⚠A 0/0、⚠B 0/0、机械缺口 2/11(已判 1/2)、死代码 5** 全部不变。
  ★ 本片顺带把「模板类导出」的挂起理由也销账了一条:卡点一直是**模板文件没随迁**,
    不是存储、也不是依赖 ⇒ 打法 = 模板复制到模块 `resources` + `ClassPathResource` 同路径读 + 需要时加 `easyexcel`。
  ★ 下一批只剩 `ParkInOutRecordController` −1、`IncomeChargerCheckTaskController` −1(两条模板导出),
    见 AGENTS.md EOF §279 ⑤。

★★ **2026-10-26 第二百七十八切片:`AppVersionCheckController` +2 / `InspectionLogController` +1(multipart 上传补迁)**
  **本片搬的 3 个端点全补在「已迁类」里** ⇒ 按**类**计数的本脚本看见的是 **+0 类 / +0 端点**
  (它只数老类与端点映射,不追"某个已迁类又补了几个方法")。
  实测(⚠️ 本片在 **`beijing_master`** 上跑;用户 2026-10-26 从 `fangshan_master` 切过来 ⇒
  **口径整体变了、不是回归**):就绪 **225/1199**、已迁 **225/1199**、**就绪且未迁 0/0**、
  ⚠A **0/0**、⚠B **0/0**、机械缺口 **2/11**(已判 **1/2** ⇒ 可迁 **0/9**)、死代码 **5**。
  ⚠️ 别再拿 §277 记的 **235/1267 / 死代码 6** 跟现在比:`beijing_master` 的老仓端点总数少 68 条、
  死代码少 1 个(`fangshan_master` 口径)。换分支后**先重跑**再报数。
  ★ 本片的价值不在本脚本:它是把「等文件存储抽象」这条挂起理由**彻底销账**——
    抽象 `OssClientBaseWrapper` 一直都在,真卡点是①强制 FastDFS 无 `fdfs.*` 配置②本机 `ali` 是真 bucket。
  ★ 下一批:就绪池仍 **0/0**;剩 `ArrearageStaticController` −3(本片已备好 `uploadFileWithName` 的 wrapper)
    与几条模板类导出(见 AGENTS.md EOF §278 ⑤)。

★★ **2026-10-23 第二百七十七切片:`TdCCustBusinessParkController` 老类 3 端点全迁(商户-车场关联)★ 就绪池清零**
  老路由 `/tdCCustBusinessParkManage`(`business/`),新路由
  `/admin-api/business/td-c-cust-business-park/*`(1 读 + 2 写,无导出)。
  实测:就绪 **235/1267**、已迁 **235/1267**、**就绪且未迁 0/0**、⚠A **0/0**、⚠B **0/0**、
  缺口的 2 类 / 7 端点**已判不可迁** ⇒ 真正可迁 **0/0**、死代码 **6**。
  ★ 3 条全抛 `AUTH_TOKEN_NOT_EXISTS`(照搬)且全 `@PreAuthorize`;老类**无** `@SysOpLog` ⇒ 新类**无** `@OperateLog`。
  ★ 失败形状与 §276 相反:缺表 `td_c_cust_business_park` 结尾是 `park`(无下划线)⇒ 不命中 `park_` ⇒ **500**
     (§276 的 `…park_rel` 命中 ⇒ 501)。501/500 只取决于表名里的子串。
  ★ 分页回填 `plName`:`isSuccess()` → `ParkingLotCacheUtil` 查缓存 → 按 `plNo` 双循环,照搬。
  ⛔ `save` 服务层**先物理删除**该 businessId 下全部关联再逐车场插入(`{}` 会真删真插)。
  ★ 零写基线仍 **+248/轮**(实测 238550 → 238798,变化表 1)。
  ★ 下一批:**就绪池无待办**;只剩已判不可迁的 2 类 / 7 端点与端点级差集(见 AGENTS.md EOF §277 ⑤)。

★★ **2026-10-23 第二百七十六切片:`TdCBusinessCouponRuleParkRelController` 老类 4 端点全迁(商户卡券规则-车场关联)**
  老路由 `/tdCBusinessCouponRuleParkRelManage`(`business/`),新路由
  `/admin-api/business/td-c-business-coupon-rule-park-rel/*`(1 读 + 3 写,无导出)。
  实测:就绪 **235/1267**(不变)、已迁 **234/1264**(+1 类 / +4 端点)、就绪未迁 **1/3**、
  缺口 **2/7 已判不可迁**、真正可迁 **0/0**、死代码 **6**。
  ★ 4 条全抛 `AUTH_TOKEN_NOT_EXISTS`(照搬)且全 `@PreAuthorize`;老类**无** `@SysOpLog` ⇒ 新类**无** `@OperateLog`。
  ★★ 501 提示被**子串匹配**成「停车场模块」:缺的表名 `td_c_business_coupon_rule_park_rel` 含 `park_`,
     纯属表名撞词(§275 的 `td_c_business_acct` 不含 ⇒ 同一批里它是 500)。
  ★★ 同一 `update` 端点两种形状:只带 `id` ⇒ `<set>` 空 ⇒ SQL 语法错 **500**;带宽真实列 ⇒ **501**。
  ⛔ `save` 无校验且 `parks` 非空时**逐车场各插一行**(`{}` 会真插);`update`/`bath-delete` 的 1003 闸门照搬。
  ★ 零写基线仍 **+248/轮**(实测 236814 → 237062,变化表 1)。
  ★ 下一批只剩 1 个类 / 3 端点(`TdCCustBusinessParkController`,清单见 AGENTS.md EOF §276 ⑤)。

★★ **2026-10-23 第二百七十五切片:`TdCBusinessAcctController` 老类 4 端点全迁(商户账户)**
  老路由 `/tdCBusinessAcctManage`(`business/`),新路由
  `/admin-api/business/td-c-business-acct/*`(1 读 + 3 写,无导出)。
  实测:就绪 **235/1267**(不变)、已迁 **233/1260**(+1 类 / +4 端点)、就绪未迁 **2/7**、
  缺口 **2/7 已判不可迁**、真正可迁 **0/0**、死代码 **6**。
  ★ 4 条全抛 `AUTH_TOKEN_NOT_EXISTS`(照搬)且全 `@PreAuthorize`;老类**无** `@SysOpLog` ⇒ 新类**无** `@OperateLog`。
  ★★ **与 §273/§274 不同**:分页服务是**真实现**(`PageHelper` + `PageBeanUtil`),本机缺表 ⇒ **500**,不是恒空页。
  ★ 本 DAO **没有**表存在性守卫 ⇒ `bath-delete {死主键}` 也是 **500**(§274 同形状端点回 501)——各自照搬。
  ⛔ `save` 服务层无校验(`{}` 真插一行);`update`/`bath-delete` 的 1003 闸门、`bath` 拼写照搬。
  ★ 零写基线仍 **+248/轮**(实测 235326 → 235574,变化表 1)。
  ★ 下一批剩 2 个类 / 7 端点(清单见 AGENTS.md EOF §275 ⑤)。

★★ **2026-10-23 第二百七十四切片:`OrderBusinessCouponController` 老类 4 端点全迁(商户购券主单)+ 就绪工具修正**
  老路由 `/orderBusinessCouponManage`(`business/`),新路由
  `/admin-api/business/order-business-coupon/*`(1 读 + 3 写,无导出)。
  ★★★ **本片先修工具再搬类**:原先 `impls` 扫描只做 `"implements" in src` 的文本预过滤,
    `CustPersonCreditServiceImpl.java`(整文件注释)因此被判「有实现」,让 `CustPersonCreditController`
    长期挂在「就绪且未迁」;改为 `strip_comments` 后再套 `IMPL_PAT`。
    ⇒ 该 Controller 落回缺口并登记 `GAP_ADJUDICATED` 判 **⛔ 不可迁**;就绪基数 **236/1272 → 235/1267**。
  实测(修正后口径):就绪 **235/1267**、已迁 **232/1256**(+1 类 / +4 端点)、就绪未迁 **3/11**、
  缺口 **2/7 已判不可迁**(OrderExamine 2 + CustPersonCredit 5)、真正可迁 **0/0**、死代码 **6**。
  ★ 4 条全抛 `AUTH_TOKEN_NOT_EXISTS`(照搬)且全 `@PreAuthorize`;老类**无** `@SysOpLog` ⇒ 新类**无** `@OperateLog`。
  ★★ `query-order-business-coupon-for-page` 服务实现体**整段被注释** ⇒ **恒返回空页、一次库都不查**。
  ★ 本机缺表:`update` 死主键 **500**(MBG 直接抛)、`bath-delete` 死主键 **501**(DAO 守卫)——**不是回归**。
  ⛔ `save` 服务层无校验(`{}` 真插一行);`update`/`bath-delete` 的 1003 闸门、`bath` 拼写照搬。
  ★ 零写基线仍 **+248/轮**(实测 234086 → 234334,变化表 1)。
  ★ 下一批剩 3 个类 / 11 端点(清单见 AGENTS.md EOF §274 ⑤)。

★★ **2026-10-23 第二百七十三切片:`OrderBusinessCouponSubController` 老类 5 端点全迁(商户购券子单)**
  老路由 `/orderBusinessCouponSubManage`(`business/`),新路由
  `/admin-api/business/order-business-coupon-sub/*`(2 读 + 3 写,无导出)。
  实测:就绪 **236/1272**(不变)、已迁 **231/1252**(+1 类 / +5 端点)、就绪未迁 **5/20**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 5 条全抛 `AUTH_TOKEN_NOT_EXISTS`(照搬)且全 `@PreAuthorize`;老类**无** `@SysOpLog` ⇒ 新类**无** `@OperateLog`。
  ★★ `query-order-business-coupon-sub-for-page` 服务实现体**整段被注释** ⇒ **恒返回空页、一次库都不查**。
  ★ `query-order-business-coupon-list-for-page` 本机缺表 ⇒ **501「表结构未导入」**(DAO 有前置守卫,不是 500)。
  ★ 零写基线仍 **+248/轮**(本片零 `@OperateLog`,实测 232598 → 232846,变化表 1)。
  ⛔ `save` 服务层无校验(`{}` 真插一行);`update`/`bath-delete` 的 1003 闸门、`bath` 拼写照搬。
  ★ 下一批剩 5 个类 / 20 端点(清单见 AGENTS.md EOF §273 ⑤)。

★★ **2026-10-23 第二百七十二切片:`InspectionLogController` 老类 5 端点迁 4(巡检日志)**
  老路由 `/inspectionlog`(`operationsmanage/inspectionlog`),新路由
  `/admin-api/order/inspection-log/*`(1 读 + 3 写;第 5 个 multipart+FastDFS 上传**挂起不迁**)。
  实测:就绪 **236/1272**(不变)、已迁 **230/1247**(+1 类 / +5 端点)、就绪未迁 **6/25**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类**没有一条**抛 `AUTH_TOKEN_NOT_EXISTS`(靠门户全局拦截器)⇒ 新栈 4 条全 `@PreAuthorize`。
  ★ `@SysOpLog` 三条 → `@OperateLog(module="巡检日志")`;`query` 老代码没有 ⇒ 新代码也没有。
  ★★ **零写基线从 +244 变 +248**:`OperateLogAspect` 在 `finally` 里落库、**业务 500 也记**;
    §2i18 里 `update`/`delete` 各两次穿透控制器 ⇒ 多 4 行(变化表仍是 1)。
  ★ 坑 24 补充:全局 `spring.jackson.date-format: yyyy-MM-dd HH:mm:ss` ⇒ **请求体 `Date` 只认带时分秒**,
    `yyyy-MM-dd` 被 Jackson 拒 → 400「请求参数类型错误」。
  ⚠️ 本机缺表 `inspection_log` ⇒ 读/改/删恒 500;⛔ `save` 服务层无校验(`{}` 真插一行)。
  ★ 下一批剩 6 个类 / 25 端点(清单见 AGENTS.md EOF §272 ⑤)。

★★ **2026-10-23 第二百七十一切片:`business/TdCBusinessCouponRuleController` 整类 6 端点首迁(卡券规则)**
  老路由 `/tdCBusinessCouponRuleManage`,新路由 `/admin-api/business/business-coupon-rule/*`
  (2 读 + 1 导出 + 3 写)。
  实测:就绪 **236/1272**(不变)、已迁 **229/1242**(+1 类 / +6 端点)、就绪未迁 **7/30**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类**零** `@SysOpLog` ⇒ 新类零 `@OperateLog`(零写基线不变,+244/轮);导出数 **96 → 97**。
  ★ 新增 `com.zteits.business.util.SnowFlakeUtil`(business 模块第 5 份同源副本,生成 `cardRuleNo`);
    ⚠️ **别**换 `com.clouds.common.utils.SnowflakeIdWorker`(twepoch/datacenter 位数不同)。
  ⚠️ 老导出是 `@RequestMapping`(**不限方法**)+ `@NoAuth` **且不判登录态**,新栈收紧为 `:export`
    且**只收 GET**;`orgId`/`sysCode` 是**必填 query 参数**(缺 → 400)。
  ⚠️ **导出不分页** ⇒ `BaseInfo` 默认 0/10 ⇒ 最多 10 行;**DAO 根本不按 orgId 过滤**(全组织同一份)。
  ⚠️ 本机缺表 `td_c_business_coupon_rule` ⇒ `page`/`list`/导出/死主键写全为 500「系统异常」。
  ★ 三个写端点的闸门**都在落库前**(save 卡名称/类型/优惠值、update 卡 id+三项、delete 卡 id);
    `delete` 是逻辑删除(`data_state=0`)且 `opName` 恒 null。
  ★ 下一批剩 7 个类 / 30 端点(清单见 AGENTS.md EOF §271 ⑤)。

★★ **2026-10-23 第二百七十切片:`business/TdCBusinessCouponController` 整类 6 端点首迁(商户卡券)**
  老路由 `/tdCBusinessCouponManage`,新路由 `/admin-api/business/business-coupon/*`
  (2 读 + 1 导出 + 3 写)。
  实测:就绪 **236/1272**(不变)、已迁 **228/1236**(+1 类 / +6 端点)、就绪未迁 **8/36**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类**零** `@SysOpLog` ⇒ 新类零 `@OperateLog`(零写基线不变,+244/轮);导出数 **95 → 96**。
  ⚠️ 老导出是 `@RequestMapping`(**不限方法**)+ `@NoAuth`,新栈收紧为 `:export` 且**只收 GET**
    (POST → 405);⚠️ **导出不带 `orgId`**(跨组织全量,存量缺陷照搬)。
  ⚠️ 本机缺表 `td_c_business_coupon` ⇒ `page`/`sum-sta`/导出/`update {死主键}` 全为 500「系统异常」。
  ⚠️ 分页回填 `BusCardTypeEnum.getValue(int).desc()`:`cardType` null 拆箱 NPE、未知类型也 NPE。
  ⛔ `save` 服务层无任何校验(`{}` 真插一行)⇒ 只发无 body。
  ★ 下一批剩 8 个类 / 36 端点(清单见 AGENTS.md EOF §270 ⑤)。

★★ **2026-10-23 第二百六十九切片:`share/CouponController` 整类 7 端点首迁(对外卡券接口)**
  老路由 `share/coupon`(最江阴下发优惠券),新路由 `/admin-api/pay/share-coupon/*`
  (2 读 + 5 写,**无导出**)。
  实测:就绪 **236/1272**(不变)、已迁 **227/1230**(+1 类 / +7 端点)、就绪未迁 **9/42**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类 `@SysOpLog` **全是注释掉的** ⇒ 新类零 `@OperateLog`(零写基线不变,+244/轮)。
  ★ 两条读端点查不到用户时走 `setErrorInfo(COMMON_ERROR)` ⇒
    **500 `[存量错误码 1002] 账户不存在`**(`selectByUserPhone` 返回非 null 空 DTO,不是 NPE)。
  ⚠️ 五条写端点的 `{}` 全是**只失败不落库**:`save`→flg NPE、`batch-insert`→`@Valid` 400、
    `delete-coupon-rule`→MBG「Value for cardNo cannot be null」、
    `user-registration`→1003「手机号不能为空」、`delete-coupon-info`→`Long.valueOf(null)` NFE。
  ★ 下一批剩 9 个类 / 42 端点(清单见 AGENTS.md EOF §269 ⑤)。

★★ **2026-10-23 第二百六十八切片:`TdCBusinessAcctPayoutController` 整类 7 端点首迁**
  老路由 `/tdCBusinessAcctPayoutManage`(`business` 模块第三个),新路由
  `/admin-api/business/business-acct-payout/*`(3 读 + 1 导出 + 3 写)。
  实测:就绪 **236/1272**(不变)、已迁 **226/1223**(+1 类 / +7 端点)、就绪未迁 **10/49**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类**零 `@SysOpLog`** ⇒ 新类零 `@OperateLog`(零写基线不变,+244/轮)。
  ★ 导出老 `@NoAuth` + 手工 `x-auth-token` ⇒ 按 §3.3 第 1 类收紧为
    `business:business-acct-payout:export`,`GET /export-excel`,不再收 `token` 参数。
    ⚠️ **坑 24**:`@RequestParam Date` 只认 JS `Date.toString()`,`yyyy-MM-dd` → 400。
    ⚠️ **导出不带 `orgId` 过滤**(存量缺陷,跨组织全量,照搬)。
  ⚠️ `td-c-business-acct-payout-detailfor-card` 的服务实现是**空壳 `return null`**
    ⇒ 控制器 `getErrCode()` NPE ⇒ **任何环境都 500**(老栈同样,照搬别补实现)。
  ⚠️ **本机缺表 `td_c_business_acct_payout`** ⇒ `page`/`static`/导出/`update {死主键}`/
    `bath-delete {ids:[死主键]}` 恒 500「系统异常」,冒烟断言按双收写 ——
    **这是本机 schema 缺口,不是迁移缺陷**。
  ⛔ `save` 服务层无任何校验(`{}` 会真插一行)⇒ 冒烟只发无 body;
    ⚠️ `bathDelete` 写的 `opName` 服务读的是 `BaseRequest.sOpName`(**永为 null**,存量缺陷照搬)。
  ★ 下一批剩 10 个类 / 49 端点(清单见 AGENTS.md EOF §268 ⑤)。

★★ **2026-10-23 第二百六十七切片:`TdCBusinessAcctSourceController` 整类 7 端点首迁**
  老路由 `/tdCBusinessAcctSourceManage`(`business` 模块第二个),新路由
  `/admin-api/business/business-acct-source/*`(2 读 + 1 导出 + 4 写)。
  实测:就绪 **236/1272**(不变)、已迁 **225/1216**(+1 类 / +7 端点)、就绪未迁 **11/56**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 老类**零 `@SysOpLog`** ⇒ 新类零 `@OperateLog`(零写基线不变,+244/轮)。
  ★ 导出老 `@NoAuth` + 手工 `x-auth-token` ⇒ 按 §3.3 第 1 类收紧为 `business:business-acct-source:export`,
    `GET /export-excel`,不再收 `token` 参数。⚠️ **坑 24**:`@RequestParam Date` 只认
    JS `Date.toString()`,`yyyy-MM-dd` → 400。⚠️ **导出不带 `orgId` 过滤**(存量缺陷,跨组织全量,照搬)。
  ⚠️ **本机缺表 `td_c_business_acct_source`** ⇒ `page`/`static`/两个导出/`update {死主键}`/
    `bath-delete {ids:[死主键]}`/`examine` 恒 500「系统异常」,冒烟断言按双收写 ——
    **这是本机 schema 缺口,不是迁移缺陷**。
  ⛔ `save` 服务层无任何校验(`{}` 会真插一行)⇒ 冒烟只发无 body;
    ⚠️ `td-c-business-acct-source-examine` 审批通过会**真加余额 + 建支付单**。
  ★ 下一批剩 11 个类 / 56 端点(清单见 AGENTS.md EOF §267 ⑤)。

★★ **2026-10-23 第二百六十六切片:`TdCBusinessUserController` 整类 7 端点首迁**
  老路由 `/tdCBusinessUserManage`(`business` 模块第一个),新路由 `/admin-api/business/business-user/*`。
  实测:就绪 **236/1272**(不变)、已迁 **224/1209**(+1 类 / +7 端点)、就绪未迁 **12/63**、
  机械缺口 **1/2**(已判 1/2、可迁 **0/0**)、死代码 **6**。★ 本机仍为 **`fangshan_master`** 口径。
  ★ 坑 22 安全(只写登录态 `orgId`/`sOpId`/操作人)。⚠️ **本机缺表 `td_c_business_user`**
    ⇒ `page`/`get-business-user-name-list`/`batch-delete {ids:[死主键]}` 恒 500「系统异常」,
    冒烟断言按「500 或 0」双收 —— **这是本机 schema 缺口,不是迁移缺陷**。
  ★ 下一批剩 12 个类 / 63 端点(清单见 AGENTS.md EOF §266 ⑤)。

★★ **2026-10-23 第二百六十五切片(P3 桥回收第十二组):`BlackListRuleController` 补 `parkTree`**
  §94 只落 6/7(`parkTree` 读登录态 `plNos` 当树根,P3 搁置);P3 ① 落地后补迁 ⇒ **7/7**。
  ★ **口径提示**:本片运行时本机已从 `beijing_master` 检出到 **`fangshan_master`**
    (2026-10-23 09:29;会话开始时是 `beijing_master`),老门户树不同 ⇒ 本脚本实测
    **就绪 236/1272**、已迁 **223/1202**、就绪未迁 **13/70**、机械缺口 **1/2**
    (已判 1/2、可迁 0/0)、死代码 **6**。历史行记的 **225/1199 是 `beijing_master` 口径**,
    **两者不可直接比**;换回 `beijing_master` 后应重跑再记录。
  ★ **本次重跑暴露的 13 个就绪且未迁类(70 端点,全部 ⚠B)** —— 下一批从这里取:
    `TdCBusinessAcctSourceController`(7)、`TdCBusinessAcctPayoutController`(7)、
    `TdCBusinessUserController`(7)、`CouponController`(7)、`TdCBusinessCouponController`(6)、
    `TdCBusinessCouponRuleController`(6)、`InspectionLogController`(5)、
    `OrderBusinessCouponSubController`(5)、`CustPersonCreditController`(5)、
    `OrderBusinessCouponController`(4)、`TdCBusinessAcctController`(4)、
    `TdCBusinessCouponRuleParkRelController`(4)、`TdCCustBusinessParkController`(3)。
    抽样两个(`TdCBusinessUserController` / `TdCBusinessAcctSourceController`)只读登录态 `orgId`
    ⇒ 坑 22 安全候选(逐个还应读源码再判)。

★★ **2026-10-22 第二百六十四切片(P3 桥回收第十一组):`AnomalyStatisticsController` 补齐 2 条**
  第一百四十八切片只落 2/4(`querySealedParkingReportForPage` /
  `querySealedParkingReportForLineChart` 在 `parkIds` 为空时回落 `userInfo.getPlNos()`,
  且只留 `plType == 2`);P3 ① 落地后补迁 2 条,交付口径 2/4 → **4/4**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、
    机械缺口 **2/11**、5 死代码不变;本工具按**类**判已迁 —— §148 起它就一直算「全迁」。
  ★ 本机 `plNos=[]` ⇒ 回落得到空列表,两个存量 SQL 都是 `IN <foreach>` 且**没有空集合守卫**
    ⇒ 拼出 `IN ()` ⇒ Druid wall 判语法错误 ⇒ **500**(老门户同样 500,照搬)。
  ★ 折线图**先快照时间串**:存量 `ParkOrderQueryServiceImpl#statisticOrderFeeByDay` 会就地
    把入参拼成 `"… 00:00:00 00:00:00"`;老门户跨 JVM 远程调用不受影响,本栈 injvm 传引用
    ⇒ 不快照就 `DateTimeParseException` ⇒ 500。`Period.getDays()` 只取日分量(存量缺陷)。
  ★ 另记两个**假缺口**(不必再写代码):`GroundLockManageController` −3 已由
    `GroundLockAppController` 落、`RealTimeMonitorController` −2 已由
    `RealTimeMonitorAppController` 落(§3.3 的 `@NoAuth` 外部端点拆到 `controller/app/`)。

★★ **2026-10-22 第二百六十三切片(P3 桥回收第十组):`VipCardRuleController` 补齐 3 条**
  第九十五切片只落 4/7(`queryVIPCardRuleForPage` / `queryVIPCardRuleForPageNew` /
  `vipCardSelectParkTree` 都读登录态 `userInfo.getPlNos()`);P3 ① 落地后补迁 3 条,
  交付口径 4/7 → **7/7**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、
    机械缺口 **2/11**、5 死代码不变;本工具按**类**判已迁 —— §95 起它就一直算「全迁」。
  ★ 本机 `plNos=[]` ⇒ 两个分页回落成空 `plNos`,存量 SQL **对空 plNos 不加过滤** ⇒
    `{}` 回全量 364 条;`tree` 回空树 `[]`。★ 冒烟只发不存在的 `cardNo`:`{}` 会让
    `queryParkName` 逐条回填时把「本地 DB 快照里没有的车场」的空 DTO 写进 Redis。

★★ **2026-10-22 第二百六十二切片(P3 桥回收第九组):`IncomeCalculationNewController` 补齐 4 条**
  第九十八切片只落 5/9(`getSummationIncomeInfoforPlTypeByTime` /
  `getAllIncomeCalculationDataOfAreaByTime` / `getSummationIncomeByTimeOfArea` /
  `getSummationIncomeByTimeOfPl` 都是「`req.plNos` 为空就回落 `userInfo.getPlNos()`」);
  P3 ① 落地后补迁 4 条,交付口径 5/9 → **9/9**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §98 起它就一直算「全迁」。
  ★ `plNos=[]` ⇒ 不带 `plNos` 的 4 条全在**服务层**抛 1003(500);缓存内车场
    `A150402000` 时「按类型拆三次」的两条问 i=1 空子集 ⇒ 同样 500 + 1003,
    另两条回 `code=0` + `[]`;缓存外车场 ⇒ `getParkTypePlNos` 对 null 元素 NPE。

★★ **2026-10-22 第二百六十一切片(P3 桥回收第八组):`ParkLotManageController` 补齐 2 条**
  第九十七切片只落 14/18(`getParkLotNamesByUserId` 读登录 `plNos` 后逐车场查缓存/服务;
  `queryAreaTreeByOpIdAndOrgId` 最终用 `plNos` 过滤车场节点);P3 ① 落地后补迁
  `get-park-lot-names-by-user-id` / `get-areatree-by-op-id`,交付口径 14/18 → **16/18**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §97 起它就一直算「全迁」。
  ★ `refreshParkLotCache`(把 `plNos` 写回 `HttpSession`,JWT 栈无对应语义)与
    `uploadParkLotPic`(FastDFS/OSS 挂起)仍不迁,故是 16/18 而不是 18/18。
  ★ `plNos=[]` ⇒ 第一条回空数组(老门户「登录人无车场」同口径);第二条本机**恒 500**:
    存量服务查到 92 个块、块→区县 0 条 ⇒ 地市查询空 id 列表 ⇒ `and id in  and area_type=?`
    被 Druid WallFilter 判语法错(红线⑥照搬,不是本片引入)。

★★ **2026-10-22 第二百六十切片(P3 桥回收第七组):`SelectItemController` 补齐后 6 条**
  第一百四十七切片只落 2/8(另 6 条读登录 `plNos`:5 条经 `GetParkLotNameListByUserId()`
  查 `ParkingLotCacheUtil`,1 条直接喂 `queryVipCardParkSaleRelForListByPlNo`);
  P3 ① 落地后补迁 6 条,交付口径 2/8 → **8/8**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §147 起它就一直算「全迁」。
  ★ `plNos=[]` ⇒ 前 5 条回空集(只剩「全部」);第 6 条 DAO 空集不过滤 ⇒ 105 张在售卡
    (+ALL = 106,ALL 写 `value=ALL`/`value4=全部`、`label=null`);前 5 条缺 `noAll` 仍 NPE 500。
★★ **2026-10-22 第二百五十九切片(P3 桥回收第六组):`SpecialCarManageController` 补齐后 2 条**
  第八十八切片只落 6/8(`querySpecialCar`/`initSpecialCarTree` 读登录 `plNos`、
  后者另固定 `sourceTypes=[17,9]`);P3 ① 落地后补迁 `query-special-car`/`init-special-car-tree`,
  交付口径 6/8 → **8/8**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §88 起它就一直算「全迁」。
  ★ `plNos=[]` ⇒ DAO 不加车场过滤(老门户「登录人无车场」同口径);两条本机**恒 500**:
    `query-special-car` 存量 `GROUP BY` 撞 MySQL 8 `only_full_group_by`;
    `init-special-car-tree` 区县查空 ⇒ `and id in ()` 非法 SQL(存量无空集守卫)。
★★ **2026-10-22 第二百五十八切片(P3 桥回收第五组):`CloseCarParkController` 补齐前 2 条**
  第八十六切片只落 17/19(`QueryParkingList`/`queryParkingParkList` 读登录 `plNos` +
  固定 `sourceType=[17,9]`);P3 ① 落地后补迁 `query-parking-list`/`query-parking-park-list`,
  交付口径 17/19 → **19/19**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §86 起它就一直算「全迁」。
  ★ `plNos=[]` ⇒ DAO 不加 `pl_no` 过滤(本机 `total=2`);不分页那条 DAO 无 null 守卫,
    `{}` 让 `getPlProvinceId() != 0` 拆箱 **NPE ⇒ 500**(存量照搬,省市区显式 0 才正常)。
★★ **2026-10-22 第二百五十七切片(P3 桥回收第四组):`TdCCustInvoiceSaleController` 补齐 `/relParkTree`**
  第八十四切片只落 11/12(`/relParkTree` 用登录 `plNos` 裁掉已绑定车场);P3 ① 落地后补迁
  `POST /rel-park-tree`,交付口径 11/12 → **12/12**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码不变;本工具按**类**判已迁 —— §84 起它就一直算「全迁」。
  ★ 桥实测 `orgIds=[10003] userAreas=0 plNos=[]`(**空集合,不是 null**)⇒ `removeAll` 正常执行、
    回空树;`{}` 不给 `saleTaxids` 时服务层 `saleTaxids.get(0)` **NPE ⇒ 500**(存量照搬)。
★★ **2026-10-22 第二百五十六切片(P3 桥回收第三组):`KeyTopServiceController` 补齐 `/parkTree`**
  第一百八十五切片只落 7/8(`/parkTree` 读登录 `plNos` 构树);P3 ① 落地后补迁
  `POST /park-tree`(**无入参**、无 `@RequestBody`),交付口径 7/8 → **8/8**。
  ★ 本类**在扫描根内** ⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、5 死代码不变;
    但本工具按**类**判已迁 —— §185 起它就一直算「全迁」。
  ★ 本机 admin 经桥换算 `orgIds=[10003]`、但本地 `sys_org_area` 无该组织行 ⇒ `plNos=[]`(空集合,不是 null)⇒ 恒回空树 `[]`(老门户「无车场」同口径)。
★★ **2026-10-22 第二百五十五切片(P3 桥回收第二组):`BalanceManagementController` 补齐 2 个分页**
  第一百五十切片只落 3/5(另 2 个在 `plNos` 空/`ALL` 时回落 `userInfo.getPlNos()`);P3 ① 落地后
  补齐 `query-bill-fill-total-for-pay-type-new`/`query-blancacfor-day-detail-page`,交付口径 3/5 → **5/5**。
  ★ 本类**在扫描根内** ⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、5 死代码不变;
    但本工具按**类**判已迁 —— §150 起它就一直算「全迁」。
  ★ 服务层 `BillReconciliationForQueryServiceImpl` 的 DAO 调用**全被注释掉**(存量遗留)
    ⇒ 两条端点恒 `{total:0,list:[]}`;无 `@Valid` ⇒ `{}` 不 400。
★★ **2026-10-22 第二百五十四切片(P3 桥回收第一组):`ParkSourceController` 补齐 3 个 GET**
  第一百四十九切片只落 3/6(另 3 个读 `userInfo.getPlNos()`);第一百五十九切片的 P3 ① 给
  `LoginUserBridge` 补了懒推导 `plNos`/`userAreas` ⇒ 本片补齐 `query-park-source-statistics`/
  `query-park-source-employee-total`/`query-park-source-device-total`,该类交付口径 3/6 → **6/6**。
  ★ 本类**在扫描根内**(`bcp-portal`)⇒ 就绪 **225/1199**、已迁 **225/1199**、机械缺口 **2/11**、
    5 死代码全部不变。★ 但本工具按**类**判已迁 —— §149 起它就一直算「全迁」,
    本片只是把当初的交付口径补成名副其实。
  ★ `sysCode` 是死参数(只进日志,真正给服务的是 `BasicEnum.SYS_CODE`)但老签名
    `@RequestParam` 必填 ⇒ 缺它 **400「请求参数缺失:sysCode」**(照搬)。
  ★ 本机 admin 经桥换算 `orgIds=[10003]`、但本地 `sys_org_area` 无该组织行 ⇒ `plNos=[]`(空集合,不是 null)⇒ 三条都走空值早退(统计全 null、两个 total 回 0),
    与老门户「登录人无车场」同口径。
★★ **2026-10-22 第二百五十三切片(批 7 第二十七切片):PDA 收费员登录 9 端点**
  `PdaUserAppController`:`/app-api/pda/user/*` 9 个,**写 4 个**(login/sign-out/modify-pass/sign-in)
  全进 `LEGACY_WRITES`(`modify-pass` 还会写 `td_b_api_log`)。
  ★★ 通用口径:`PDABaseReq` 的 6 个公共参数是 `@NotBlank`,但 `data` **没有 `@Valid` 级联**
    ⇒ `{}`/仅 token 一律 400「不能为空」;齐参无 data 一律 500「系统异常」;
    齐参 + `data={}` 落 500「系统异常」/ 500「-10001」/ 400 三档。
  ★ 新增 4 个 VO(`ChargerInfoRes`/`ChargerLoginRes`/`SignInReq`/`account/ChargeAccountIsRechargeRes`)。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百五十二切片(批 7 第二十六切片):PDA 图片上传 2 端点**
  `UploadPictureAppController`:`upload-pic`、`sf-pda-upload-pic`,**都是 multipart + OSS 真写**
  ⇒ 不进 `LEGACY_WRITES`(那张表要求无 body 时 400),零写守卫写在 §2i10。
  ★ `validateToken` 在取文件名 / 加水印 / 上传之前 ⇒ 零字节 `picFile` + 假 token 必落 500「-10001」,
    绝无 OSS 写;JSON 侧(无 `@RequestBody`)一律 500「系统异常」。
  ★ 落地映射:老 `uploadCarPic`/`uploadXCPic`/`uploadFile(file,plNo)` 新栈都没有 ⇒ 统一
    `uploadFile(in,size,ext)`/`uploadFile(file)`;字体用 `reportpdf/Microsoft Yahei.ttf`。
  ★ 本片**零新增 VO**。本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百五十一切片(批 7 第二十五切片):PDA 停车记录 5 端点**
  `ParkRecordAppController`:`query-berth-info`/`query-park-record`/`query-park-record-by-order-id`
  只读;`modify-order-info`/`out-park-nullify` **写** ⇒ 进 `LEGACY_WRITES`。
  ★ 同类两种 `data={}` 落点:`query-berth-info` 的 data 是 `?` ⇒ 齐参 + `data={}` 落 **500「-10001」**;
    其余 4 个 data 类带 `@NotBlank` ⇒ 落 **400「不能为空」**。
  ★ 启动坑复现:`@Resource private CommonService` 会撞 park 模块同名 bean ⇒ 必须
    `@Resource(name = "pdaCommonService")`(§238 已记)。
  ★ 新增 5 个 VO + `pda.portal.constant.BerthConstant`(抄自老门户)。
    本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百五十切片(批 7 第二十四切片):PDA 排班服务 2 端点**
  `DutyAppController`:`query-latest-week-duty`、`query-latest-week-duty-by-charge-code`,**都只读**
  ⇒ 不进 `LEGACY_WRITES`。
  ★ 两套校验并存:前者 `@Valid` + `checkParam` ⇒ 齐参 + `data={}` 落 **500「-10001」**;
    后者 **`@RequestBody` 上没有 `@Valid`**、也不调 `checkParam`,只调
    `BeanValidatorsUtils.validateWithException(request.getData())` ⇒ **`{}` 落 500「系统异常」**
    (data 为 null 时 NPE)、齐参 + `data={}` 落 **400「不能为空」**。
  ⚠️ 后者**完全不校验 token**(假 token + 真 `chargerCode` 直接 8888 成功),存量缺陷照搬(红线⑥)。
  ★ 新增 1 个 VO:`QueryWeekDutysReq`(抄自老门户 `vo/pda`)。本工具只扫 `bcp-portal`
    ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十九切片(批 7 第二十三切片):收费员考核任务 1 端点**
  `ChargerCheckTaskAppController`:`query-charger-check-task`,**只读** ⇒ 不进 `LEGACY_WRITES`。
  ★★ **新样板:没有 `checkParam` 的端点,`data={}` 不会落 -10001** —— 只 `@Valid` 校验公共参数 +
    直接 `PdaTokenCacheUtil.getPDAInfoByToken` ⇒ 假 token 得 null ⇒ `.getChargerInfo()` NPE
    ⇒ **齐参缺 data 与齐参 + `data={}` 都是 500「系统异常」**,两者无差别。
  ★ 存量缺陷照搬:两处 `setSysCode("sysCode")` 是**字面量字符串**。
  ★ 本片**零新增 VO**。本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十八切片(批 7 第二十二切片):PDA收费员充值/支出记录 2 端点**
  `ChargerAccountAppController`:`query-acct-recharge-for-page`、`query-acct-pay-out-for-page`,
  **两个都只读** ⇒ 不进 `LEGACY_WRITES`;`custId` 取自 PDA token,不从入参取。
  ★ 类名带 `App`:`admin/chargeraccount/ChargerAccountController` 已占 bean 名。
  ★ 落点第三档:`PDABaseReq<BaseInfo>` 而 `BaseInfo` **无任何约束** ⇒ 齐参 + `data={}` 落 **500「-10001」**。
  ★ 本片**零新增 VO**。本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十七切片(批 7 第二十一切片):车牌识别激活码 2 端点**
  `CarNumberIdentifyAppController`:`get-register-code`(只读 `tp_p_pda`)、
  `report-register-result`(⚠️ 真写 `tp_p_pda`:`updatePDAByPdaNo`)⇒ 只有后者进 `LEGACY_WRITES`。
  ★ 两端点都是 `@Valid` + `checkParam`、data 类都有约束 ⇒ 齐参 + `data={}` 一律 400;
    `ReportRegisterResultRequest` 的 `@NotBlank` + `@NotNull` 并存 ⇒ 报文**不稳定** ⇒ 断言只认 400 + 二者之一。
  ★ 老方法名 `queryFreeBerths` 是复制粘贴残留(实际是「获取激活码」),新栈按语义改名 `getRegisterCode`。
  ★ 本片**零新增 VO**。本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十六切片(批 7 第二十切片):违章车辆上报 2 端点**
  `PdaCarBreakRuleAppController`(★ 类名带 `Pda`:`controller.app.carbreak.CarBreakRuleAppController`
  已占 bean 名 `carBreakRuleAppController`,同名会 `ConflictingBeanDefinitionException` 启动即死):
  `upload-pic`(multipart,OSS + `td_c_file`)、`report-car-break-rule`(真插 `tp_c_car_break_rule`)。
  ★ 只有 `report-car-break-rule` 进 `LEGACY_WRITES`;`upload-pic` 无 body 是 500「系统异常」⇒ 守卫写在 §2i4。
  ★★ **multipart 零写冒烟规矩**:缺 `picFile` part ⇒ 500;**零字节空 part + 假 token ⇒ 500「-10001」**
    (`validateToken` 在 `getSize()`/`uploadParkFile` 之前 ⇒ 既不传 OSS 也不写 `td_c_file`);
    缺 `token` part ⇒ 400「请求参数缺失:token」。⛔ 永不发真字节 + 有效 token。
  ★ `upload-pic` 的 JSON 侧(空 body / `{}` / 坏 JSON)**一律 500「系统异常」**,不是 bug。
  ★ 本片新增 1 个 VO(`CarBreakRuleReq`)。本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十五切片(批 7 第十九切片):车辆出场 8 端点**
  `CarOutParkAppController`:4 条真写进 `LEGACY_WRITES`(两个现金出场 `outParkPayedByCash`、
  `batch-out-park` 推 MQ、`set-park-order-recosting` 真重算),其余 4 个只读。
  ★ 落点三档:两个无约束 data 类 ⇒ 500「-10001」;`PayOutFeeByCashReq` ⇒ 400;
    其余 4 个 ⇒ 400「不能为空」;缺 data ⇒ 八个一致 500「系统异常」。
  ★★ `PayOutFeeByCashReq` 的 400 报文在「不能为空」/「不能为null」之间**不稳定**
    (`ConstraintViolation` 是 HashSet)⇒ 断言只认 code=400 + 二者之一。
  ★ 本片新增 8 个 VO(`QueryHaveOrderBerthsRes`/`CarOutParkReq`/`QueryOutParkInfoRes`/
    `PayOutFeeByCashReq`/`BatchOutParkReq`/`VehicleLeavingReq`/`RecostingByOrderIdReq`/
    `VehicleLeavingResForTicket`)。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十四切片(批 7 第十八切片):车辆入场 6 端点**
  `CarInParkAppController`:`in-park`(`insertParkOrder` 真建停车单)与 `pay-pre-fee-by-cash`
  (真现金预付)进 `LEGACY_WRITES`,其余 4 个只读。
  ★★ 本片**三种 `data={}` 落点并存**:`query-free-berths`(`CarInOutParkReq` 无约束)⇒ 500「-10001」;
    `get-vehicle-entering-data`(**`checkParam` 被注释** ⇒ 进业务,服务端 `@NotNull`)⇒ 400「不能为null」;
    其余 4 个 ⇒ 400「不能为空」。缺 data ⇒ 六个一致 500「系统异常」。
  ★ 本片新增 8 个 VO(`QueryFreeBerthsRes`/`CarInOutParkReq`/`CarInParkRes`/`CarInParkReq`/
    `QueryPreInParkFeeRes`/`QueryPreInParkFeeReq`/`PayPreFeeByCashReq`/`VehicleEnteringReq`)。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十三切片(批 7 第十七切片):欠费补交 6 端点**
  `ArrearageAppController`:`pay-arrearage-by-cash` 是**真现金补缴**(`LEGACY_WRITES`),其余 5 个只读。
  ★ 六个端点都 `@Valid` + `checkParam`,data 类都有 `@NotBlank` ⇒ `data={}` 一律 400;缺 data ⇒ 500。
  ★ 存量缺陷照搬:一键补缴取 orderIds 用 `getRecordInfos()` 而非局部 `recordInfos`。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十二切片(批 7 第十六切片):个人账户支付 1 端点**
  `PersonAccountPayAppController`:`pre-or-out-pay-by-account` 是**真扣个人账户余额**(`LEGACY_WRITES`)。
  ★ `@Valid` + `checkParam` + data 类 `@NotBlank/@NotNull` ⇒ `data={}` 一律 400;缺 data ⇒ 500。
  ★ `saveCallInterfaceLog` 在 `checkParam` 之后 ⇒ `data={}` 时**不写 `td_b_api_log`**。
  ★ 方法名沿老代码误写 `preOrOutPayByBarcode`(红线⑥不改)。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十一切片(批 7 第十五切片):退款 2 端点**
  `RefundAppController`:`pre-fee-refund` 是**真退款**(`LEGACY_WRITES`);`query-allow-refund-order` 只读。
  ★ 两个端点都 `@Valid` 且方法体都走 `checkParam`,两个 data 类都有 `@NotBlank`
    ⇒ `data={}` 一律 400「不能为空」(PDA 口径里最简单的情形);缺 data ⇒ 500「系统异常」。
  ★ `saveCallInterfaceLog` 在 `checkParam` 之后 ⇒ `data={}` 时**不写 `td_b_api_log`**。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百四十切片(批 7 第十四切片):条码支付 4 端点 + 基类 PDABassController**
  `BarcodePayAppController`:**3 个真扣款/充值**(`LEGACY_WRITES`),只发空 body / `{}` / `data={}`。
  ★★ 修正 §238 的过度概括:`@Valid` 打在 `PDABaseReq<T>` 上,`data` 字段没有 `@Valid`
    ⇒ **只校验公共参数,不下钻 data**;`data={}` 的落点取决于方法体走不走 `checkParam`。
  ★ 本片三种落点:checkParam ⇒ 400;只 validateSigin + 查库 ⇒ 500;只 validateSigin + 服务
    校验失败 ⇒ 8888 信封 + 4000(account-recharge,未扣款)。
  ★ 随迁基类 `PDABassController` 到 `com.zteits.pay.portal.web.pda`(扫描包内)。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百三十九切片(批 7 第十三切片):停车公共查询 13 端点**
  `ParkSourceQueryAppController`:**13 个全是纯读**(零写,`LEGACY_WRITES` 无新增)。
  ★ 都没有 `@Valid`、都走 `checkParam` ⇒ `{}`/缺 data 都是 500(HV000116);
    `data={}` 按 data 类有无约束分两组:无约束 ⇒ 500(齐参时 -10001),有约束 ⇒ 400。
  ★ 存量照搬:`getEqpIsOnline() == "0"` 字符串引用比较(onlineEqpNum 恒 0)、
    `queryNearbyParkLotInfo` 的循环内查收费员(N+1),均已注释、不批量化。
  ★ 本工具只扫 `bcp-portal` ⇒ **pda-portal 是盲区**,225/1199 不变。
★★ **2026-10-22 第二百三十八切片(批 7 第十二切片):巡查 登录/签退/改密 4 端点**
  `PatrolUserAppController`:**4 个全是真写**(Redis token / 改密码),`LEGACY_WRITES`,只发空 body。
  ★ 四个都有 `@Valid`、`data` 类也都有约束 ⇒ 空 body/`{}`/`data={}` 全是 400,**够不到写服务**。
  ★ 坑:`@Resource private CommonService commonService` 撞 park 模块同名 bean
    ⇒ 必须 `@Resource(name = "pdaCommonService")`。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-22 第二百三十七切片(批 7 第十一切片):巡查任务 6 端点**
  `PatrolTaskAppController`:4 读 + `update-task-state`/`apply-help`(⚠️写,`LEGACY_WRITES`,只发空 body)。
  ★★ 总口径:`PDARsesultUtil.checkParam` 第一句就是 HV 校验 `data`
  ⇒ `data={}` 是 400 还是 500 **取决于 `data` 类上有没有 Bean Validation 约束**,与 `@Valid` 无关。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十六切片(批 7 第十切片):巡查 设备巡查 4 端点**
  `EqpInspectAppController`:3 读 + `save-inspect-record`(⚠️写,`LEGACY_WRITES`,只发空 body)。
  ★★ 与 §235「5 端点整齐一致」相反:同类的 4 个端点 `data={}` 落点各不相同(设备类型 200+`"8888"`、设备属性/异常上报 500、工单明细 500+存量码 1003)。
  ★ 无 `@Valid` + 只 `validateSigin` ⇒ 除空 body/坏 JSON 外没有 400。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十五切片(批 7 第九切片):巡查 设备维护 5 端点**
  `EqpMaintenanceAppController`:4 读 + `update-record-detail`(⚠️写,`LEGACY_WRITES`,只发空 body)。
  ★★ **参数上没有 `@Valid`** ⇒ `{}` 不是 400 而是 500「系统异常」(进方法体 `validateSigin` 里 NPE)。
  ★ 只 `validateSigin`(不校验 token),随后 `getPDAInfoByToken` ⇒ 假 token 也是 500 而**不是** `-10001`。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十四切片(批 7 第八切片):巡查 情况上报 3 端点**
  `PatrolInspectAppController`:`save-inspect-situation`(⚠️写)+ `get-service-rule-list`(读)+ `to-save-service-record`(⚠️写)。
  ★ 三端点落点完全一致(`checkParam` **不吞异常**)⇒ 假 token 一律 500「-10001」;与同目录的巡查心跳(异常全吞)相反。
  ★ 老方法名 `updateTaskState` 与语义不符,是复制粘贴残留 ⇒ 照搬不改。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十三切片(批 7 第七切片):巡查 版本检测 + 心跳 2 端点**
  `PatrolCheckAppController`(纯读)+ `PatrolHeartBeatAppController`(⚠️ 真写,`LEGACY_WRITES`,只发空 body)。
  ★★ 同名端点、**不同老版本**:PDA 心跳给 signId 加了 loginRecordId 兜底并设 signDetailid,巡查心跳两样都没有 ⇒ 各搬各的。
  ★ 巡查版本检测同 PDA 口径:**不校验 token**,`data` 非空即真查版本表,`appType=5`。
  ★ 本工具只扫 `bcp-portal` ⇒ **巡查端是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十二切片(批 7 第六切片):PDA 推送消息 1 端点**
  `JPushMsgAppController`(老 `pda-portal` `web/pda/JPushMsgController`),纯读。
  ★ 老类 `extends PDABassController`(`@Component`,带 MQ/HttpServletRequest),但本端点没调用任何继承方法 ⇒ 新类不继承。
  ★ 鉴权同 §2hm:`checkParam`(签名 + token,不吞)⇒ 假 token 500「-10001」。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十一切片(批 7 第五切片):PDA 版本检测 1 端点**
  `PDACheckAppController`(老 `pda-portal` `web/pda/PDACheckController`),纯读。
  ★★ **PDA 鉴权强度不一致**:本端点只调 `validateSigin`、**不校验 token** ⇒ `data` 非空就真查版本表、返回真数据。
  ★ 返回老 `PDABaseRes` 信封,`code` 是**字符串 `"8888"`**(断言按字符串比)。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百三十切片(批 7 第四切片):PDA 自助停车车牌上报 1 端点**
  `CarSelfParkingAppController`(老 `pda-portal` `web/pda/CarSelfParkingController`),**真写**(文件表)⇒ 已进 `LEGACY_WRITES`。
  ★ 三种分界:`{}`/`data={}` ⇒ 400(`ConstraintViolationException` 走 400 分支);`data` **整个缺失** ⇒ 500 系统异常;假 token ⇒ 500 -10001。
  ★ 老类硬编码 `sysCode="ZTEITS-ORDER"`;hutool3 `CollectionUtil` → Spring `CollectionUtils`。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百二十九切片(批 7 第三切片):PDA 码表查询 1 端点**
  `SysMsgQueryAppController`(老 `pda-portal` `web/common/SysMsgQueryController`),纯读、走本地码表缓存。
  ★ 新坑:老方法**没有 `@Valid`** ⇒ `{}` 不落 400 而落 500「系统异常」(`validateSigin` NPE)。
  ★ 同一个 `checkParam`:带 `@Valid` 时 `{}`=400(§2hm),不带时 `{}`=500(本片)。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百二十八切片(批 7 第二切片):PDA 今日统计 1 端点 + 门户 CommonService**
  `PdaTodayStatisticsAppController`(老 `pda-portal` `web/pda/TodayStatisticsController`),纯读。
  ★ 伴迁 `web/pda/CommonService`(543 行)→ `com.zteits.pay.portal.web.pda`(**必须改包名**:basePackages 没扫 `com.zteits.pda`)。
  ★ 新坑:bean 名/字段名都必须是 `pdaCommonService`,否则撞 `park...inoutpark.CommonService`(@Resource 先按名再按类型)。
  ★ 同基座不同形态:本端点不吞异常 ⇒ 假 token 500「-10001 登录失效」;`data` 缺失 ⇒ NPE 500「系统异常」。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百二十七切片(批 7 第一切片):PDA 门户基座 + 心跳 1 端点**
  `PdaHeartBeatAppController`(老 `pda-portal` `web/pda/HeartBeatController`):收费员实时位置上报告。
  ★ 顺带把 PDA 门户基座搬进 `parking-legacy-common`(逐字):`com.zteits.pda.portal.common.PDARsesultUtil`、
    `vo/base/{PDABaseReq,PDABaseRes}`、`vo/pda/GpsResportReq`。
  ★ PDA 鉴权 = 自己的签名(`PDARsesultUtil.checkParam`)+ `PdaTokenCacheUtil` token ⇒ 全 `@PermitAll`。
  ★ 返回值是**裸 `Boolean`**(响应体 `true`),冒烟要 `raw=True`;老方法**吞掉所有异常**并 return true。
  ★ 新坑:`@Valid` 多约束同时失败时报哪条是随机的(「不能为空」/「不能为null」)⇒ 断言锁 code、文案两种都收。
  ★ 本工具只扫 `bcp-portal` ⇒ **PDA 是盲区**,225/1199 不变。
★★ **2026-10-21 第二百二十六切片(P5 第十批):违章车辆上报 3 端点**
  `CarBreakRuleAppController`(老 `app/carbreak/CarBreakRuleController`):违章照片上传(multipart+水印)、
  违章车辆上报、违章道路分页。搬了两个门户 VO(`CarBreakRuleReq`、`RoadNameReq`,都逐字)。
  ★ 三条都靠**老 APP token**(`checkAppToken` / `checkParam`)⇒ 全 `@PermitAll`;三条都 `@PostMapping` ⇒ GET 401/405。
  ⚠️ **`/upload-pic` 是 multipart + OSS 直连 ali 的真写**:空 body 落 500「系统异常」(不是 400)
     ⇒ **不能进 `LEGACY_WRITES`**(同 `/app-api/pay/file-upload/upload-pic`);冒烟只发不带文件分片 / 0 字节假分片。
  ★ `/query-...-for-page` 的 `{}` 落 500「[存量错误码 4014] appId不存在」(验签在方法体里抛)。
  ★ 水印字体类路径 `MicrosoftYahei.ttf` → `reportpdf/Microsoft Yahei.ttf`(同 md5)。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十五切片(P5 第九批):微信公众号 4 端点**
  `AliWeixinPublicAppController`(老 `weixinpublic/AliWeixinPublicController`):公众号换 openId /
  小程序 code2session / 小程序手机号登录 / JS-SDK 扫码签名。搬了两个门户 VO(`GetPhoneNumberReq`、
  `WxChatScanReq`,都逐字)。
  ★ 四个方法都没验签 ⇒ 全 `@PermitAll`;四条都 `@PostMapping` ⇒ GET 401/405。
  ⚠️ ⭐ **四条都会出站调微信,URL 里带解密后的 appSecret** ⇒ 冒烟只用**不存在的 appId**(落 1001、不出站)。
  ★ `{}` 落点不一致:前三个老信封 1003;`/get-wechat-scan` 服务层抛 ⇒ 500(存量照搬)。
  ★ `/small-routine-login` 是**真写**(建 `td_c_cust_person` + Redis 写 token)⇒ 只发空 body。
  ★ 老类 `@Value("${local.base.url}")` 字段源码零引用 ⇒ 不搬(坑 16)。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十四切片(P5 第八批):农行支付 5 端点**
  `ABCBankAppController`(老 `bank/ABCBankController`):钱包充值下单 / app 内嵌下单 / H5 下单 /
  银行异步通知 / 银行订单状态查询。搬了门户 `XMLDocument`(逐字)。
  ★ 五条老代码都写了 `method = RequestMethod.POST` ⇒ 只收 POST(GET 401/405),
    与上一片「未写 method」口径**相反**。
  ★ ★ **三个端点在老代码里是 private**(`bankH5Pay`/`bankPayForH5`/`queryBankOrderState`):
    Spring 反射注册 handler 不过滤可见性 ⇒ 真能通,迁移照搬 private。
  ⚠️ 风险最高的一批:3 条真下单(`appPay`)+ 1 条真结算(`payNotify`)⇒ 只测绑定层/第一道闸。
  ★ `/pay-notify-url` 没有 `@RequestBody` ⇒ 空 body 是 500,**不进** `LEGACY_WRITES`。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十三切片(P5 第七批):北京西城用户认证 3 端点**
  `BJXCUserAuthAppController`(老 `wxpublic4bjxc/BJXCUserAuthController`):认证状态 / 可购卡列表 /
  线下转线上专用。搬了两个门户 VO(`UserAuthRes` 逐字、`AvailableVipCardInfoVO` 仅换 import)。
  ★ 三个方法都没验签 ⇒ `@PermitAll`;★ **老 mapping 未写 method** ⇒ 任意方法可进,
    GET 无 token 也落 400(不是 401/405)。
  ★ `/auth-user` 是读接口带写(`buy_state` 10→20)⇒ 进 `LEGACY_WRITES`。
  ★ 本地认证表缺列 `examine_state` ⇒ 三个端点的 `{}` 恒 500(环境漂移)。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十二切片(P5 第六批):北京西城会员卡 3 端点**
  `BJXCVipCardAppController`(老 `wxpublic4bjxc/BJXCVipCardController`):可办车场 / 新增认证 / 下单。
  ★ 三个端点全部匿名(`save` 令牌失效只降级成「未登录支付」,不拦)⇒ `@PermitAll`。
  ★★ 坑:`...service.order` 下有两个同名 `VipCardAuthPersonBjxcService`,只有**子包**那份有实现;
    导错包 ⇒ 启动 `required a bean ... could not be found`。
  ★ 本地无 `VIP_OPEN_PARKING` 字典 ⇒ `query` 的 `{}` 恒 500(NPE);认证表缺列 `examine_state` ⇒
    `save` 落库路径 500。两者老栈同样复现 ⇒ 环境漂移,不是迁移缺陷。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十一切片(P5 第五批):北京西城会员卡开票 2 端点**
  `BJXCVipCardInvoiceAppController`(老 `wxpublic4bjxc/BJXCVipCardInvoiceController`)。
  搬了两个门户 VO(`UserAuthReq` 逐字、`HasBuyVipCardInfoVO` 仅换 `StringUtils` import)。
  ★ 老类继承 `AppBaseController` 但**两个方法都没验签** ⇒ 新类 `@PermitAll`。
  ★ 本地表 `vip_card_auth_person_bjxc` 缺列 `examine_state` ⇒ 业务路径在本地恒 500,
    **老栈同样会 500** ⇒ 环境漂移,不是迁移缺陷。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百二十切片(P5 第四批):公众号用户 2 端点**
  `WXPublicUserAppController`(老 `weixinpublic/WXPublicUserController`):openId 换 App token、
  openId 绑手机号。**整片无出站**,依赖全在新栈(无需搬类)。
  ★ 响应是第三套协议:`AppRsesultUtil` 的 `{"code":"1003","message":...}`
    —— `code` 字符串、键是 `message`,别按 `CommonResult` 的 `msg` 写断言。
  ★ 仍是本工具盲区(`pay-portal`)⇒ 225/1199 不变。
★★ **2026-10-21 第二百一十九切片(P5 第三批):电池(充电)微信下单 1 端点**
  `BatteryWeixinPayAppController#unifiedOrder`(老 `weixin/BatteryWeixinPayController`)。
  老门户 VO `BatteryAliWxPayRequest` 本片才搬进 `parking-legacy-common`
  —— 注意:**「包目录已存在」≠「类已存在」**,按类名 `find`。
  ★ 仍是本工具盲区(`pay-portal` 不在 `DEFAULT_LEGACY` 口径内)⇒ 225/1199 不变。
★★ **2026-10-21 第二百一十八切片(P5 第二批):4 个真下单端点,两个类补齐 3/3**
  `AliPayAppController` 的 `/app-pay`、`/ali-h5-pay` 与 `WeixinPayAppController` 的
  `/unified-order`、`/public-unified-order`。
  ★ 订正:上一片写的"下单端点依赖未迁的门户本地类"**是误判** ——
    `BillQueryCommonService`(pay 模块) / `EmployeeChargerValidateUtil` / `BillQueryRes` /
    `QueryFeePublicReq`(legacy-common) 早就在新栈。**判定缺口前先 `find` 一遍新栈。**
  ★ 仍是本工具盲区:`pay-portal` 不在 `DEFAULT_LEGACY` 口径内 ⇒ 225/1199 不会变。
★★ **2026-10-21 第二百一十七切片(P5 第一批):第三方支付回调 2 端点 —— 本工具的盲区**
  搬的是 `zteits-pay-portal` 的 `alipay/AliPayController#aliPayNotify` 与
  `weixin/WeixinPayController#payNotifyUrl`(各 1/3 端点;**下单端点不是缺口**,
  第二百一十八切片已补齐 3/3 —— 当时写的"依赖未迁的门户本地类"是误判)。
  ★★ **P5 的进度本工具完全看不见**:`DEFAULT_LEGACY` 写死 `zteits-bcp-portal/.../web`,
     `pay-portal` / `minipay-portal` 全在口径外 ⇒ 搬完 P5 后 225/1199、未迁 0/0 **都不会变**。
     要统计 P5,必须传 `--legacy-root` 或另写工具;**别拿本工具的数字当全站进度**。
  ★ 回调是 `@PermitAll` + 协议原文返回(支付宝 `success`、微信 XML),不是 `CommonResult`;
    冒烟绝不发 `result_code=SUCCESS`(会走真结算)。
★★ **2026-10-21 第二百一十六切片:老门户最后 4 个管理后台 Controller(4 类 / 4 端点)**
  搬的是 `zteits-bcp-portal` 的 `HomeController`、`aliyunecs/AliyuncsEcsController`、
  `billing/UploadController`、`personpc/PersonOrderBackWxPayController` ⇒
  ★★ **就绪与已迁首次对齐:就绪 225/1199、已迁 225/1199、未迁 0/0**(此前长期 4/4)。
     机械缺口 2/11 不变(`OrderExamineController` 2 已判不可迁 + `ParkingAssistantController` 9 ⛔ 不碰),
     5 个死代码不变。
  ★ `HomeController` **必须**落在 `parking-module-*` 的 `controller` 包(`controller.admin` 的同级):
    本工具只扫 `parking-module-*`,放 `parking-server` 会让本类重新变回「未迁」(本片实测踩过)。
  ★ `aliyunecs` 端点带 token 会**真出站调阿里云**(实现在无配置时回落到写死的 key)⇒ 冒烟不带 token,
    只验「匿名 401 / 带 token GET 405 / 老路由 404」;`upload` 老 `@NoAuth` 按 §3.3 第 1 类收敛为
    `pay:upload:create`(缺文件是 500,不进 `LEGACY_WRITES`);`wxpay` 老 `@SysOpLog` → `@OperateLog`。
★★ **2026-10-21 第二百一十五切片:时域月卡购买/续费(1 类 / 2 端点,全写,老类已弃用)**
  搬的是 `zteits-pay-portal/.../web/app/viparea/TimeVipCardController`(老 `/timeVipCard`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 老类注释已标**弃用**,但映射仍在门户 ⇒ 照搬;2 条全真写,进 `LEGACY_WRITES`、只发空 body。
  ★ 与 §214 的差别:**没有** `isJudgeArreage` 前置,`checkParam` 是第一道业务闸,结构完整+appId 假直接 `[4014]`。
  ★ 复用 §214 补搬的两个 VO,零新 VO、零新依赖;存量缺陷:`createVipTimeCardOrder` 调 `createVipAreaCardOrder`。
★★ **2026-10-21 第二百一十四切片:区域月卡购买/续费(1 类 / 2 端点,全写)**
  搬的是 `zteits-pay-portal/.../web/app/viparea/AreaVipCardController`(老 `/areaVipCard`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 2 条全是真写(建卡 + `order_vipcard` + 明细 / 续费同四步)⇒ 进 `LEGACY_WRITES`、只发空 body。
  ★ ⚠️ **`checkParam` 不是第一道闸**:先跑 `isJudgeArreage(carNumber, effDate, cardNo)`(只读,查不到卡 return false),
    所以要证明 `checkParam` 在链路上得给"结构完整 + appId 假 + cardNo 不存在"的 body(落 `[4014]`);
    只给 `AppBaseReq` 5 个字段的假签名会先被 `@Valid` 业务必填挡成 400。日期只认 `yyyy-MM-dd HH:mm:ss`。
  ★ 存量缺陷:`getIsJudgeArreargeVipcard()`/`getType()` 拆箱 NPE;`Long.valueOf(orderFee)`;两分支逐行重复。
★★ **2026-10-21 第二百一十三切片:错峰共享信息验证(1 类 / 3 端点,1 读 + 2 写)**
  搬的是 `zteits-pay-portal/.../web/app/newversion/SharedAuthenticationController`(老 `/sharedAuthentication`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 1 读 + 2 写(`driver-license-review`/`idcard-review` 改 `td_c_car`)⇒ 两条写进 `LEGACY_WRITES`、只发空 body。
  ★ 3 条都 `@Valid` ⇒ `{}`/空 body 都是 400;假签名**两种落点并存**:读走 `checkParam` ⇒ `[4014]`,
    两条写没有 `checkParam`、第一道闸是 `AppTokenUtil.getUserCustId` ⇒ `[-10000]`。
  ★ 存量缺陷:逐车 N+1 查审核记录;`ExamineStateEnum.of(Integer)` 拆箱 NPE;日志占位符与字符串拼接混用。
★★ **2026-10-21 第二百一十二切片:新版 APP 区域卡(1 类 / 3 端点,全读)**
  搬的是 `zteits-pay-portal/.../web/app/newversion/VipAreaCardController`(老 `/vipAreaCard`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 3 条全读;⚠️ `valid-vip-area-card-buy-or-renew` 在 `checkParam` **之前** `getType() == 1` 拆箱
    ⇒ `{}`/假签名是 500「系统异常」,带 `type` 才落 `[4014]`。复用 §211 补搬的两个 VO。
★★ **2026-10-21 第二百一十一切片:新版 APP 时域卡(1 类 / 3 端点,全读)**
  搬的是 `zteits-pay-portal/.../web/app/newversion/VipTimeCardController`(老 `/vipTimeCard`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ `query-vip-time-card-info` 带 `@Valid`(`AppBaseReq` 5 个 `@NotBlank`)⇒ `{}` 400;
    另两条老代码已标弃用、无 `@Valid` ⇒ `{}`/假签名都 500 `[4014]`。
  ★ `validVipTimeCardBuyOrRenew` **只查不写**(次数/时长校验)⇒ 3 条全读、不进 `LEGACY_WRITES`。
  ★ 补搬 `vo.app.vipareacard.QueryVipAreaCardRequest`、`web.app.viparea.query.TVipAreaCardValidRequest`。
★★ **2026-10-21 第二百一十切片:ETC 德亚追缴通知(1 类 / 1 端点,真写)**
  搬的是 `zteits-pay-portal/.../web/app/etcinterface/ETCDeYaController`(老 `/etc/deya`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 第三方回调不吃老 token;裸 VO 入参(无 `@RequestBody`)⇒ 任何空 payload 都 500「系统异常」
    (`bizParam`=null 的 NPE 在 try 之外)。★ 真写:`arrearsPayNotify` 欠费追缴结算。
  ⚠️ 空 body 是 500 ⇒ **不能进 `LEGACY_WRITES`**(那张表要求 400)。
★★ **2026-10-21 第二百零九切片:第三方车场查询(1 类 / 1 端点,纯缓存读)**
  搬的是 `zteits-pay-portal/.../web/app/thirdservice/ParkingLotForThirdController`(老 `/thirdParkQuery`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 不吃老 token:`@Valid @RequestBody String` 取 `appId` 查 `td_c_third_part_app_authorization`。
    空 body ⇒ 400;`{}`/假签名 ⇒ 500「系统异常」(appId=null 存量路径);空白串 ⇒ 200 + 空列表。
  ⚠️ 存量缺陷:该 Service 查不到也返回**空壳 DTO** ⇒ `THIRDPART_APPID_NOT_EXISTS` 不可达。
  ★ `sysCode` 传进了 `GetAuthorizationByAppIdRequest` 但实现类**只按 appId 查** ⇒ 不必另立独立键。
★★ **2026-10-21 第二百零八切片:丹阳公共自行车(1 类 / 4 端点,纯出站代理)**
  搬的是 `zteits-pay-portal/.../web/app/danyang/DanYangBikeQueryController`(老 `/danYangBike`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 4 条全是 `@RequestParam` 代理到 danyangbike.com:缺必填参数 ⇒ 400「请求参数缺失:<name>」。
  ⛔ `/baseinfo/get-batch` 参数全 optional,空参数也出站 ⇒ 冒烟只验 GET 405/401,绝不 POST。
  ★ 零落库、零写 ⇒ 不进 `LEGACY_WRITES`。
★★ **2026-10-21 第二百零七切片:商户信息(1 类 / 4 端点,2 写 + 2 读)**
  搬的是 `zteits-pay-portal/.../web/app/business/CustBusinessController`(老 `/custBusiness`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ `@Valid` 先于 `checkParam`:两条写 `{}` ⇒ 400「请求参数不正确」;假签名带 `token` ⇒ 500 `[4014]`。
    两条读无 `@Valid` ⇒ `{}`/假签名都 500 `[4014]`。
  ★ 2 条写(落 `td_c_cust_business`、改商户并重置审核状态)进 `LEGACY_WRITES`,只发无 body。
★★ **2026-10-21 第二百零六切片:车位预约(1 类 / 6 端点,2 写 + 4 读)**
  搬的是 `zteits-pay-portal/.../web/app/appointment/AppointMentBerthController`(老 `/appointmentBerth`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 6 条全 `@RequestBody`(无 `@Valid`):空 body 400;`{}`/假签名**三落点** ——
    `checkParam` ×4 ⇒ 500 `[4014]`;手写 `checkAppToken` ×1 ⇒ 500 `[-10000]`;
    不验签 ×1 ⇒ 500「系统异常」。
  ★ 2 条写(建 `order_appointment`、改预约日期)进 `LEGACY_WRITES`,只发无 body。
★★ **2026-10-21 第二百零五切片:提现(1 类 / 1 端点,存量空壳)**
  搬的是 `zteits-pay-portal/.../web/app/TransferController`(老 `/parkInfo`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ `@Valid @RequestBody String`:空 body 400;非空 body 直接 **`return null`** ⇒ HTTP 200 + 空体。
  ★ 零副作用(`orderTransferService` 从未调用)⇒ **不进 `LEGACY_WRITES`**。
  ★ 至此老 `web/app/` 顶层类全部处理完(`IRainParkingController` 整类注释掉、无需迁)。
★★ **2026-10-21 第二百零四切片:APP 图片上传(1 类 / 1 端点,写)**
  搬的是 `zteits-pay-portal/.../web/app/SysFileUploadController`(老 `/pic`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 入参是 **multipart/form**(`@RequestParam MultipartFile picFile + token + fileType`):
    空 body/`{}` 因缺必填参数被兜底成 **500「系统异常」**(不是 400)
    ⇒ **不能进 `LEGACY_WRITES`**,零写入守卫单独钉在 §2go。
  ★ 会落 FastDFS/OSS + `td_c_file` ⇒ 冒烟**永不发真文件**。
★★ **2026-10-21 第二百零三切片:会员卡购买(1 类 / 1 端点,写)**
  搬的是 `zteits-pay-portal/.../web/app/VipCardController`(老 `/vip`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ `@RequestBody String` + 手写 `checkParam` ⇒ `{}`/假签名 500 `[4014]`。
  ★ 真写 `order_vipcard` + `order_vipcard_detail` ⇒ `LEGACY_WRITES` 只发无 body。
★★ **2026-10-21 第二百零二切片:我的首页(1 类 / 2 端点,全读)**
  搬的是 `zteits-pay-portal/.../web/app/MyHomeController`(老 `/home`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 两种落点:`home-page`(`@RequestBody String` + `validateSigin`)⇒ 500 `[4014]`;
    `query-arrearage-is-exist` **不验签名** ⇒ 成功信封(`code` 字符串 `"0"`、`isLogin=0`)。
  ★ 全读,`LEGACY_WRITES` 不加。
★★ **2026-10-21 第二百零一切片:停车订单查询(1 类 / 13 端点,12 读 + 1 写)**
  搬的是 `zteits-pay-portal/.../web/app/ParkOrderQueryNewController`(老 `/queryParkOrder`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★ 补回 8 个老 VO(`parklot/` 5 个 + `billquery/` 3 个)到 legacy-common;
    jfinal `JsonUtils.toJson` 唯一一处(日志)换 fastjson `JSON.toJSONString`。
  ★ 鉴权有**三类落点**:8 条无 `@Valid` ⇒ 500 `[4014]`;4 条 `@Valid` ⇒ 400;
    `query-arrearage-sta-for-wx-publc` 因 `getOrgId() == 10003` 拆箱 null ⇒ 500「系统异常」。
  ★ 唯一写 `appDeleteParkOrder`(`delType=0` 按 custId 全部车牌批量软删)⇒ `LEGACY_WRITES` 只发无 body;
    `openOrClose` 名字像写、实际只读。
★★ **2026-10-21 第二百切片:个人账户支付(1 类 / 4 端点,全写)+ 补回 billquery 路由链**
  搬的是 `zteits-pay-portal/.../web/app/AppAccoutPayController`(老 `/appAccountPay`)⇒
  口径同样**不在 `DEFAULT_LEGACY` 里**,就绪 **225/1199**、已迁 **221/1195**、未迁 **4/4**、
  机械缺口 **2/11**、5 死代码**全部不变**。
  ★★ 补链路是本片主体:整条 `com.zteits.pay.portal.billquery`(11 文件)+ `HttpClientTutorial`
    + 5 个 billquery VO + `ZeroPayReq` + `EmployeeChargerValidateUtil`,并把 §196 精简版
    `BillQueryCommonService` 换成 964 行全量版。
  ⚠️ 这些类放在 **`parking-module-pay`**:`KeyTopBillQueryServiceImpl` 依赖 module-pay 的
    `KeyTopBillQueryInnerService`,而 `parking-legacy-common` 不能反向依赖模块。
    `DiscountWrapper` 复用 `com.zteits.ipay.util.DiscountWrapper`(同签名)。
  ★ 同一控制器两种鉴权:`account-pay` 手写 `checkParam` ⇒ `{}` 500 `[4014]`;
    其余 3 条 `@Valid` + `ZeroPayReq` ⇒ `{}` 400(文案随机)。
  ★ 4 条全是钱/账 ⇒ 全进 `LEGACY_WRITES`、只发无 body。
★★ **2026-10-20 第一百六十七切片:补回月卡合同整条链路 + 搬完车场运营·月卡合同管理 6 端点**
  (`VipCardContractController`,路由 `/admin-api/order/vip-card-contract`)
  ★ 缺口原因:`VipCardContractService` 整条链路都不在新栈(接口 + 4 个 DTO/入参 + 域对象
    + 两套 Mapper/XML + Word/Excel 模板)。本次按老仓逐字搬回,再补 3 个编排服务
    (归档 / Word 渲染 / 四川营台账导出)。
  ⇒ 就绪 **224/1193 → 225/1199**、已迁 **220/1189 → 221/1195**、未迁 **4/4**、
    缺口 **3/17 → 2/11**(剩 `OrderExamineController` 2、`ParkingAssistantController` 9[不碰])、
    死代码 5 不变。
  ★ 老类零 `@NoAuth`、零 `@SysOpLog` ⇒ 6 条全 `@PreAuthorize`、新类零 `@OperateLog`。
  ★ ⛔ `archive-now` 是真归档 + 真传 OSS:冒烟只允许**空 body**(绑定层 400),⛔ 不发 `{}`。
    每日 03:23 的 `VipCardContractTask` 定时任务**未搬**(会自行上传真实 OSS)。
  ★ 冒烟 34 条(+22 §2ff + 6 §5b + 6 `LEGACY_WRITES`),6262 → 6296;零写 `system_operate_log`
    **+244/轮**(§166 的 5 条 `@OperateLog`,本片零新增),`td_b_vip_card_contract` 恒 2 行。
  ★ POI 4.1.2:`Cell.CELL_TYPE_NUMERIC` 已删,台账导出用 `CellType.NUMERIC`。
★★ **2026-10-20 第一百六十六切片:补回 `BjxcPushService` + 搬完停车订单管理 14 端点**
  (`ParkingRecordController`,路由 `/admin-api/order/park-record`)
  ★ 缺口原因只有一条:12 个服务依赖都在,**只差 `BjxcPushService`**(接口 + 2 DTO + 太极响应 DTO
    + 实现类都不在新栈)。本次按老仓逐行搬回 `BjxcPushServiceImpl`,
    并给 `ParkOrderDTO` 补 `isUploadThird`(老 DTO 有、新栈缺,导出侧 `getParkInfo` 要回填)。
  ⇒ 就绪 **223/1179 → 224/1193**、已迁 **219/1175 → 220/1189**、未迁 **4/4**、
    缺口 **4/31 → 3/17**(剩 `ParkingAssistantController` 9[不碰]、`VipCardContractController` 6、
    `OrderExamineController` 2)、死代码 5 不变。
  ★ `@NoAuth` 两条:导出收紧为 `GET /export-excel` + `:export`;`queryParkOrderInOutImgNew`
    是上一方法的逐字副本、老前端 0 调用 ⇒ 收紧为 `:query`。两条都不进 `@PermitAll` 白名单。
  ★ 冒烟 46 条(+26 §2fe + 14 §5b + 6 `LEGACY_WRITES`);零写 `system_operate_log` **+244/轮**
    (本片 5 条 `@OperateLog`),其余 52 张表 0 变化。
  ★ 编译踩坑:`OperateLog`/`OperateTypeEnum` 在 `com.zteits.parking.framework.operatelog.core`。
  ★ `ParkOrderDTO` 加字段 ⇒ §2u 的 RSN 全键断言 46 → **47**,`RSN_KEYS` 已同步。
★★ **2026-10-19 第一百六十五切片:修第二类实现误判 + 搬完进出场上报 3 端点**
  (`InOutParkStatisticController` 2 读 + 1 导出,路由 `/admin-api/park/in-out-park-statistic`)
  ★★ **工具修正**:实现扫描此前只 glob `*Impl.java` / `*Ipml.java`,漏掉**不叫 Impl 的实现类**
    (典型:桥接空实现 `NoopDfGroundLockMsgPushService implements DfGroundLockMsgPushService`)
    ⇒ 已迁的 `GroundLockManageController` 6 端点一直被误挂在缺口档。现改为扫全部 `*.java`
    (先 `"implements" in src` 预过滤,再套 `IMPL_PAT`),彻底不依赖命名约定
    ⇒ 就绪 **221/1170 → 223/1179**、缺口 **6/40 → 4/31**。
  ★ `InOutParkStatisticService` 接口一直在 `parking-module-api`,但**无实现**(存量实现整类被注释、
    Dubbo provider 早已注释);本次按 `git show 550cba464e^` 搬回实现,
    并补回它依赖、同样被删干净的 `TdACardCouponsService`(接口 + `queryCardCouponsByCarNumbers`
    单方法 + pay 模块实现;DB 访问留在 pay 内,不在 park 跨库直连)。
  ⇒ 已迁 **217/1166 → 219/1175**、未迁 **4/4**、✔ **3/3**、⚠A **0/0**、⚠B **1/1**、死代码 5 不变。
  ★ 冒烟 17 条(+3 §5b):两个读端点空 body 停在 1003;chart 跨天横轴 19 格、同一天横轴
    **25 格(00:00 … 24:00,存量 `j <= hour + 1` 多排一格)**;导出缺参停在绑定层 400。
  ★ 本机 `parking_cloud` **没有 `inpark_log` / `outpark_log`** ⇒ list/export 的 SQL 抛
    `Table … doesn't exist`,被兜底成 501「表结构未导入」;断言写成 `0 或 501`,表导入后自然变 0。
  ★ 零写一轮:`system_operate_log` 两次计数均 101348(本片零 `@OperateLog`),其余表 0 变化。
★★ **2026-10-19 第一百六十四切片:修 DTO 误判 + 搬完会员卡购买 15 端点**
  (`TVipTimeCardCustInfoController` 6 + `TVipAreaCardCustInfoController` 9)
  ★★ **工具修正**:`RenewVipAreaCardOperReq` 位于 `api.service.pay.viparea.oper`,是**入参 DTO 类**、
    不是服务契约。旧版只看 `import com.zteits.clouds.api.service.*` 就判"接口不在新栈",
    把这两个 Controller 误挂进缺口档。新增 `API_CLASS_PAT` 收集 `api.service.*` 下的类,
    `scan_legacy` 命中即跳过 ⇒ 就绪 **219/1155 → 221/1170**、缺口 **8/55 → 6/40**。
  ★ 15 个端点全 `@PreAuthorize('bill:vip-{time,area}-card-cust-info:*')`、全 `@PostMapping`;
    7 个写端点挂 `@OperateLog`(时域卡 3 + 区域卡 4),其余读/校验端点零注解。
  ⇒ 已迁 **215/1151 → 217/1166**、未迁 **4/4**、✔ **3/3**、⚠A **0/0**、⚠B **1/1**、死代码 5 不变。
  ★ 冒烟 15 条全发空 body:create 停在 1003(时域卡编码/区域卡编码)、renew 停在存量
    `Value for cardNo cannot be null`(500「系统异常」,红线⑥照搬)、delete 停在 `@Valid`、
    update/delete-list 停在控制器 1003、push 停在 `@NotNull id`、statistic 停在参数校验。
  ★ 零写一轮:仅 `system_operate_log` +239(7 个 `@OperateLog` 里 delete 两条在绑定层就 400、
    到不了切面,其余 5 条真正写入;另 234 条来自全脚本既有端点);新增监控表
    `t_vip_area_card_cust_info` / `t_vip_area_card_cust_info_rel` 均 0 变化。
★★ **2026-10-19 第一百六十三切片落地 P3 ②(操作员 id 换算表)**
  (`LoginUserBridge` + `LegacyOpIdMappingConfig`,配置键 `parking.legacy.op-id-mapping`)
  ⇒ 本片**不搬 Controller、不加端点**,上面所有计数**逐位不变**:
  **215/1151**、未迁 **4/4**、✔ **3/3**、⚠A **0/0**、⚠B **1/1**、缺口 **8/55**、死代码 5。
  ★ `opId` 现在与 `orgId` 同一手法:`新system_users.id:老sys_op.id` 查表换算;
    **默认空表 = 恒等映射**(不配与改造前完全一致),非法格式启动即失败。
  ★ 本机 `sys_op` 0 行 ⇒ 代码里**零猜测映射**;本地 yaml 的 `1:10001` 仅为验证链路。
  ★ 端到端实测:`save-black-list-rule` 写入 `create_empid=10001`(探查行已清理);
    本片零写,基线仍 **+234/轮**。
★★ **2026-10-19 第一百六十二切片补全两个既有类的 session 端点**
  (`PersonPCIndexController` +3/3:首页卡片 / 停车记录 / 欠费记录;
    `PersonPCCarNumController` +1/1:车牌读取)
  ⇒ 两个类此前已计入"已迁",本片只是补齐端点 ⇒ 就绪/已迁数**不变**:
  **215/1151**、未迁 **4/4**、✔ **3/3**、⚠A **0/0**、⚠B **1/1**、缺口 **8/55**、死代码 5。
  ★ 4 条都经私有 `getOwnerCarNumbers()` 或直接读 `getUserInfoForPersonPC()`,
    带 token 无 session 时依次撞存量 1003(`custId为空!` / `组织id为空!` /
    `获取客户信息失败`,顺序见 §162 ⑥)。
  ★ 新增 4 条 `§5b` 无 token 断言;本片零写,基线仍 **+234/轮**。
★★ **2026-10-19 第一百六十一切片借 §160 登录桥搬走 ⚠B 的 `PersonPCCouponController` 1/1**
  (`/admin-api/pay/person-pc-coupon/get-coupon-person`,1 读、零导出、零 `@OperateLog`)
  ⇒ 已迁 **214/1150 → 215/1151**、未迁 **5/5 → 4/4**、✔ **3/3** 不变、
  ⚠B **2/2 → 1/1**(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
  ★ 唯一闸门是 `getUserInfoForPersonPC()`:session `USER_INFO.custId` 为空 ⇒ 存量 1003
    「获取客户信息失败」;带 token 但无个人客户 session 时冒烟就停在这里。
  ★ 查询条件照搬:`cardTypes=[1,2,3]`、`day=15`、`dataState` 有效;回填 `orgName` 的
    N+1 与 `getData()` 空时 NPE 是存量缺陷,照搬。
  ★ ⚠️ 本地 `td_c_coupon_person` **缺 `car_num` 列**(parking-cloud 存量 mapper 多列漂移,
    老 `zteits-pay` 同名 SQL 并不查它)⇒ 正样本探针临时加列、跑完删列;
    冒烟只走 1003,不执行 SQL,不受影响。⛔ 别为此改 mapper/schema。
  ★ 本片零写:基线仍 **+234/轮**;`td_c_coupon_person` / `td_c_coupon_rule` 已在监控内。
★★ **2026-10-19 第一百六十切片落地 P3 ③(个人客户登录桥)并搬走 `PersonPCAuthController` 4/4**
  (`/app-api/pay/person-pc-auth`,4 条 `@PermitAll`:发码 / 登录 / 取用户信息 / 登出)
  ⇒ 已迁 **213/1146 → 214/1150**、未迁 **6/9 → 5/5**、✔ **4/7 → 3/3**、
  ⚠B **2/2** 不变(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
  ★ 桥的原理:登录端点把 `LOGIN_USER_TOKEN` / `USER_INFO` 写进 **HTTP session**,
    于是 `SessionCommUtil#getUserInfoForPersonPC()` 在新栈恢复可用
    ⇒ `PersonPCCouponController` 已按原样搬走,其余「只差 session」的类仍待补。
  ★ ⛔ 冒烟**绝不**用已注册手机号打 `send-validator-code`(会真实发短信);
    正样本用 Redis 种 `person-pc:validator_code_<phone>` 单独验证(见 §160 ⑥)。
★★ **2026-10-19 第一百五十九切片落地 P3 ①(数据权限桥)并搬走 ⚠B 的 `ParkLotStatisticController` 1/1**
  ⇒ 已迁 **212/1145 → 213/1146**、未迁 **7/10 → 6/9**、✔ **4/7** 不变、
  ⚠B **3/3 → 2/2**(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
  ★ `LoginUserBridge` 现在按老登录口径**懒推导** `userAreas`/`plNos`:
    `queryAreaListByOrgIds` → `sys_org_area`;`sys_code=1001` 取 `areaType=60` 走
    `queryParkInfoByAreaBlockIds`,`sys_code=1002` 取 `areaType=40` 走 `queryParkListByPark`;
    没有区域时 `plNos` 为空集合(不回退「空即可见」);`resIds`/`roleIds` 仍 null。
  ★ 该类唯一端点只读登录 `plNos`,为空抛 `AUTH_TOKEN_NOT_EXISTS`(存量 2003),
    本机 org 10003 无区域 ⇒ 冒烟断言 500/2003;正样本另用 `LEGACY_ORG_ID_MAPPING=1:10038`
    验证(恰好 2 个车场,与 SQL 一致)。
  ★ 本片零写:基线仍 **+234/轮**;新增只读表 `sys_org_area`、`sys_org` 已进零写监控。
★★ **2026-10-19 第一百五十八切片搬走 ⚠B 的 1 个类 / 1 端点**
  (`RemoteControlDzController` 1/1)
  ⇒ 已迁 **211/1144 → 212/1145**、未迁 **8/11 → 7/10**、✔ **4/7** 不变、
  ⚠B **4/4 → 3/3**(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
  ★ 同片判定:`PersonPCCouponController` 读 `getUserInfoForPersonPC().getCustId()`
    ⇒ 个人客户登录桥(P3 ③)未落地,**不迁**;`AliyuncsEcsController` 虽只读 `orgId`,
    但会带存量 AK/SK **真实外呼阿里云**(且 service 会把配置对象打进 info 日志)
    ⇒ 继续挂起,不做正测。
★★ **2026-10-19 第一百五十七切片搬走 ⚠B 的 2 个类 / 8 端点**
  (`StoreCarManageController` 7 / `AppointmentManageController` 1)
  ⇒ 已迁 **209/1136 → 211/1144**、未迁 **10/19 → 8/11**、✔ **4/7** 不变、
  ⚠B **6/12 → 4/4**(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
  ★ 同片判定:`ParkLotStatisticController` 读登录 `plNos`(**不是** `orgId`)⇒ P3 未落地前不可搬。
★★ **2026-10-19 第一百五十六切片搬走 ⚠B 的 2 个类 / 12 端点**
  (`OrderRefundController` 5 / `AccountRechargeStatisController` 7)
  ⇒ 已迁 **207/1124 → 209/1136**、未迁 **12/31 → 10/19**、✔ **4/7** 不变、
  ⚠B **8/24 → 6/12**(⚠A 仍 0/0)、缺口 **8/55**、死代码 5。
★ **2026-10-19 第一百五十四切片修正实现识别**(`extends Base implements X` 与 `RefundServiceIpml.java`)
  ⇒ 就绪 211/1118 → **219/1155**、缺口 16/92 → **8/55**、未迁 8/11 → **15/42**、
  ✔ 4/7 → **6/15**、⚠ 4/4 → **⚠A 1/3 + ⚠B 8/24**。总数 232/227/5 不变。
★ **2026-10-19 第一百六十五切片修正实现识别(第二刀)**:上一条只解决了「文件名拼错」,
  仍按 `*Impl.java`/`*Ipml.java` 命名约定找实现 ⇒ **不叫 Impl 的实现类**一律漏判。
  实证:`NoopDfGroundLockMsgPushService implements DfGroundLockMsgPushService`(桥接空实现)
  被当成「无实现」,让**早已迁完**的 `GroundLockManageController` 6 端点一直挂在缺口队列。
  现改为 glob 全部 `*.java`(`"implements" in src` 预过滤后再套 `IMPL_PAT`),与命名约定解耦
  ⇒ 就绪 **221/1170 → 223/1179**、缺口 **6/40 → 4/31**、已迁 **217/1166 → 219/1175**。
★★ **2026-10-19 第一百五十五切片把上片新进 ✔ 池/⚠A 池的 3 个类一次搬完**
  (`PersonPCAcctController` 3 / `UserRechargeRefundController` 5 / `AppUserAccountController` 3)
  ⇒ 已迁 **204/1113 → 207/1124**、未迁 **15/42 → 12/31**、✔ **6/15 → 4/7**、
  ⚠A **1/3 → 0/0(再次清零)**、⚠B **8/24** 不变、缺口 **8/55**、死代码 5。
★ **2026-10-16 第三十四切片:✔ 那 5 个已逐个读源码判定**(人工结论见下面 ADJUDICATED 表,脚本只做标注、不改机械判定)
   ⇒ ⏸ 4 个 / 10 端点(待定:共享密钥来源 1 个、个人客户登录桥 2 个、文件存储 1 个)、
   ⛔ 1 个 / 1 端点不适用(HomeController 不是 JSON 接口) ⇒ **✔ 池当前 0 个可直接开搬,别按 4/7 排期。**
   ★ 2026-10-17 第四十六切片:`RefreshCacheController` 已按 (B) 配置化方案搬走 ⇒ ✔ 池从 5/11 降到 **4/7**。
★★ **2026-10-16 第三十五切片:⚠ 那 124 个再切一刀**(`LEGACY_ID_PAT` 不知道该 `get*()` 打在谁身上,
   把前端**请求体**一起抓了)⇒ **⚠A 12 个 / 61 端点**整类**没有一处**读登录态 = **纯机械误报**、
   **⚠B 113 个 / 700 端点**才是真读了登录态的那批。A 组逐类核完命中行(全落在
   `request.getPlNos()` / 局部入参 `req.getOrgId()` / 下游 DTO 上),结论见 `IDSPACE_FALSE_POSITIVE` 表;
   其中 `EqpIncomeStatisticController`(3 端点)本片已搬 ⇒ **⚠A 余 11 个 / 58 端点**。
★★ **2026-10-16 第三十六切片:⚠A 里再搬 2 个**(`BerthStatisticController` 2 端点、
   `WxcpMessageController` 2 端点)⇒ **⚠A 余 9 个 / 54 端点**。本片的价值在于把 §3.3
   「老 `@NoAuth` 分两类」**在同一批次里两侧都钉住**:berth 落 `/admin-api` + `@PreAuthorize`
   (无 token **401**),wxcp 落 `/app-api` + `@PermitAll` + 白名单登记(无 token **进业务**)。
★★ **2026-10-16 第三十七切片:⚠A 里的 `IndexParkSituationController`(6 端点)搬走**
   ⇒ **⚠A 余 8 个 / 48 端点**。它一搬走,⚠A 的 6 端点整档就**自动从「未迁」里消失**
   (下面的 85/359 与 ⚠A 8/48 是**同一次移动的两个投影**,别当两件事重复计数)。
   ★ 本片是「云平台首页」两个 tab 的**收尾**:兄弟类 `IndexParkIncomeController` 早在
   批 6 第三十X 切片就搬了,本片把 `IndexParkSituationController` 补上 ⇒ 该页面在
   `/admin-api` 下已无遗留。★ 坑 22 侧:老类 5 处命中**全是请求体**(3 处
   `CollectionUtils.isEmpty(request.getPlNos())` + 2 处 `setPlNos(request.getPlNos())` 转投下游),
   全类 **0 处** `getUserInfo(` / `SessionCommUtil` ⇒ ⚠A 机械误报,逐行核销见 `IDSPACE_FALSE_POSITIVE`。
★★ **2026-10-16 第三十八切片:⚠A 里的 `IncomeCalculationController`(5 端点,4 读 + 1 导出)搬走**
   ⇒ **⚠A 余 7 个 / 43 端点**(`86/364` 与 `7/43` 同一次移动的两个投影)。
   ★ 导出的 `@NoAuth` 按 §3.3 **第 1 类**(管理页面)收紧;★★ **本片是「坑 25」的第二处修点**——
   两条走 `PageHelper.startPage` 的 select 原是 `statementType="STATEMENT"`,老 PageHelper 5.0.0
   拼字面量 `LIMIT 3` 能跑、新栈 6.1.1 拼 `LIMIT ?` 绑不上 ⇒ 真分页必 500;修法与回归守卫
   见 §2ah 抬头 ① 与 `TIncomeCalculationStaticNumSMapper.xml` 的 `⚠️ 手改` 注释。
★★ **2026-10-16 第三十九切片:⚠A 里的 `VehicleFlowController`(5 端点,3 读 + 2 导出)搬走**
   ⇒ **⚠A 余 6 个 / 38 端点**(`87/369` 与 `6/38` 同一次移动的两个投影)。
   ★ 两条老 `@NoAuth` 都按 §3.3 **第 1 类**收紧(一条 `GET /export-excel`、一条 `GET /export-pdf`,
   老 `carFlowPrint` 的 `@RequestMapping` **没写 method**);★ 本片**新建了 1 个老门户独有 VO**
   (`com.zteits.bcp.portal.vo.report.runtime.VehicleFlowVO`,只喂 PDF、不进响应体)——
   ⚠️ 上两片那条"先看返回类型在不在新栈"的省力规律**要连 VO 一起数**,本片就漏了一个。
★★ **2026-10-16 第四十切片:⚠A 里的 `IncomeAllStatisticController`(5 端点,5 读)搬走**
   ⇒ **⚠A 余 5 个 / 33 端点**(`88/374` 与 `5/33` 同一次移动的两个投影)。
   ★ 本片**又新建了 3 个老门户独有 VO**(`com.zteits.bcp.portal.vo.report.income` 下
   `IncomeStatisticTrentReportVO` / `IncomeStatistic2MonthFeeVO` / `IncomeStatisticDayFeeVO`)——
   第三十九切片留下那条"返回类型要连 VO 一起数"的教训本片照做了,**先数再开搬**。
   ⚠️ **本片 5 个端点里 4 个在本机恒 500**,原因**不在迁移**:老 SQL(`IncomeFinanceBillSMapper.xml`
   3 处**在本片路径上** + 同族的 `IncomeStatisticDaySMapper.xml` 1 处)写的是 `GROUP BY x DESC`,**这个语法在 MySQL 8.0.13 已被移除**,
   本机 MySQL 是 8.x ⇒ 老栈同样 500。按红线⑥(存量缺陷照搬不修)**不动 SQL**,改为**离线桩测**证明Controller 逻辑等价
   (剥 fat jar + `Proxy` 桩 + 反射注入,见 AGENTS.md 第四十切片 ④)。⇒ 这 4 条端点的"可交付"以
   **目标库若不是 MySQL 8 才成立**为准,是**非阻塞决策项**,已上报。
★★ **2026-10-16 第四十一切片:⚠A 里的 `OrderParkStatisticController`(4 端点,4 读)搬走**
   ⇒ **⚠A 余 4 个 / 29 端点**(`89/378` 与 `4/29` 同一次移动的两个投影)。
   ★ 本片**一个新类都没新建**(4 个返回类型全在新栈)——"返回类型要连 VO 一起数"这条第 3 次生效。
   ★ 老类 **3 条端点是大屏用 `@NoAuth`** ⇒ 按 §3.3 **第 1 类**(管理页面)收紧成
   `/admin-api` + `@PreAuthorize('park:order-park-statistic:query')` ⇒ 4 条无 token 全 **401**。
   ⚠️ **本片两条端点在本机恒空、但都不是迁移回归**:EP2 走的 SQL 打 `order_park.modfiy_date`
   (该表 `max = 2018-10-16`、`> yesterday` 的行 0 条);EP3/EP4 的表 `parking_running_statistic`
   全量 500 行停在 `2026-01-31`,而窗口由代码**按今天算** ⇒ 恒空。
   ★ 判定手法:**同一条 SQL 直连、手动换窗口**(`2026-01-24~01-31` 回 1 组 15188/15073)
   ⇒ 证明 SQL 活着,"空"只来自窗口。**先怀疑样本、再怀疑代码**。
★★ **2026-10-16 第四十二切片:⚠A 里的 `ReconciliationManagementController`(7 端点,5 读 + 2 导出)搬走**
   ⇒ **⚠A 余 3 个 / 22 端点**(`90/385` 与 `3/22` 同一次移动的两个投影)。
   ★ 本片**一个新类都没新建**(7 个返回类型全在新栈)——"返回类型要连 VO 一起数"第 4 次生效。
   ★ 老类 `extends ReconciliationBaseController`(140 行、只有一个 `getPayOrderTypeName(Integer,Integer)`、自己零映射)⇒ 它躺在本脚本的"5 个死代码"清单里,但**它是活基类,不是死代码**;新栈没有基类可继承 ⇒ 那段 switch **原地抄进新控制器**。
   ★ 两条导出老 `@NoAuth` 按 §3.3 **第 1 类**(管理页面)收紧:一个类两条导出 ⇒ 主导出占 `GET /export-excel`、另一条落 `GET /export-detail-excel`(§3.3 第 3 条)。
   ⚠️ **本片两条源表样本都停在过去、且采到的列组正好错开**:`td_b_pay_blncac_result` 的 `report_date` ∈ 2018-01-01~01-16 而 `pl_no` **500 行全是空串**(⇒ 传真 plNo 恒空);`pay_order` 全表停在 `pay_finish_time = 2017-09-25` 且 `pay_type = 6`(⇒ 写死 `pay_type IN (1,2,4,5)` 的 EP2 **恒全 0**)。**又一条「先怀疑样本」**。
   ⚠️ 导出端点的日期是 `@RequestParam Date` ⇒ **只吃 JS `Date.toString()`**(`yyyy-MM-dd HH:mm:ss` → 400)——**第 4 种日期契约**,⛔ 别顺手统一。
★★ **2026-10-16 第四十三切片:⚠A 里的 `BerthsStatisticController`(9 端点,5 读 + 4 导出)搬走**
   ⇒ **⚠A 余 2 个 / 13 端点**(`91/394` 与 `2/13` 同一次移动的两个投影)。
   ⚠️ **别与 `BerthStatisticController`(第三十六切片已搬)混** —— 只差一个 `s`,
   那个在新路由 `/park/berth-statistic`,本片这个在 `/park/berths-statistic`。
   ★ 本片**一个新类都没新建**(9 个返回类型全在新栈)——"返回类型要连 VO 一起数"第 5 次生效。
   ★ 4 条导出**全是老 `@NoAuth`**(老前端 `unit.js:877~890` 逐条有入口)⇒ 按 §3.3 **第 1 类**收紧
   ⇒ 9 条端点无 token 全 **401**;一个类 4 条导出 ⇒ 第 3 条命名:主导出占 `GET /export-excel`,
   另三条落 `-detail` / `-month` / `-month-detail` 后缀。
   ★★ **本片新增一条样本判据**:`parking_lot_use_statistic` 500 行**只有一行车场**
   (`A150402003`,且它**不在** `parking_lot` 里)⇒ 多车场断言全废;要验 N+1 回填车场名,
   得另找 `parking_lot` 里有、统计表里没有的 `B1504020C7`。挑正样本前先
   `select count(distinct <业务键>)`。
   ★★ **两条"全小写连写"的老路由值 `getstatisticsummary` / `getstatisticlist` 逐字保留**——
   kebab 只对驼峰切词,对全小写连写是恒等映射,加了连字符就回推不出来。
★★ **2026-10-17 第四十四切片:⚠A 里的 `RealTimeMonitorController`(10 端点,8 读 + 1 导出 + 2 免认证读)搬走**
   ⇒ **⚠A 余 1 个 / 3 端点**(`92/404` 与 `1/3` 同一次移动的两个投影)。
   ★★ **本片第一次把「一个老类 = 两个新类」落成事实**:本栈 URL 前缀由包路径决定
   (`**.controller.admin.**` → `/admin-api`、`**.controller.app.**` → `/app-api`),
   一个类不能两栖 ⇒ 管理端 8 条进 `park/real-time-monitor`(全 `@PreAuthorize`),
   外部网站 2 条进 `/app-api/park/real-time-monitor` + `@PermitAll`(先例 `AppVersionCheck*`)。
   ★ 老类 3 条 `@NoAuth` **不是同一类**:导出是第 1 类(老前端三处 `unit.js` 调用)、
   两条按组织查车场是第 2 类(`git grep` 两个前端分支**都搜不到** + 老注释写明"黄石官网")
   ⇒ **口径更正**:第四十三切片 ⑫ 里"只有黄石那条才是第 2 类"的预判是错的,两条都是。
   ★ 本片**零新建 VO**(10 个返回类型全在新栈)——"返回类型要连 VO 一起数"第 6 次生效。
   ★ 第五种日期契约:导出是 `@RequestParam String` + hutool `DateUtil.parseDateTime`(受检异常)。
   ★★ **本片新增一条缓存判据**:`getParkLotByPlNo` 会把"查不到"的空 DTO 写进 Redis hash
   `zteits-park:parking_lot:plNo_parkLotInfo`,之后 `QueryParkingLotByPkNos` 在
   `BerthManageServiceImpl:123` 对 null `plNo` **NPE** ⇒ 500。本地 `tp_p_berths` 的 6 个 plNo
   **全部**在 `query-park-berth-occupancy-rate` 的 `{}` 返回里 ⇒ 冒烟里 ep5 只发带 `plNo` 的过滤调用,
   ep3 断言写成"缓存干净/被污染"两支(红线⑥ 不修)。
★★ **2026-10-17 第四十五切片:⚠A 最后一个类 `VipCardQueryController`(3 端点)搬走 ⇒ ⚠A 清零(0 个 / 0 端点)**
   (`93/407` 与 `0/0` 同一次移动的两个投影)。★ 本片推翻了两条预测:
   ① 第四十三切片 ⑫ 说「§3.2 要另建 2 个老门户独有 VO」——**不用**:`VipCardStatisticIndexDTO`
      / `AuthCarNumberDTO` 本来就已在 `parking-module-api`,`VipCardStatisticFeeVO` 也早已随
      `parking-legacy-common` 整包搬入(只是用不上);
   ② 更要紧的是**第 3 条端点的签名在说谎**:声明 `BizResultVO<VipCardStatisticFeeVO>`(6 字段),
      方法体却是裸 `new BizResultVO(incomeStatisticFeeVipCardDTOBizResult)` ⇒ 实际线格式是
      **21 字段**的 `IncomeStatisticFeeVipCardDTO`,那个 VO **一次都没被 new 过**。
      ⇒ 新契约按实际线格式声明;照签名搬会**静默丢 15 个字段**(编译期不报)。
      **新判据:§3.2 数 VO 之前先看方法体 new 的是谁,别只看泛型声明。**
   ★ 本片**零新建 VO/DTO**("返回类型要连 VO 一起数"第 7 次生效,且这次连"要不要建"都省了)。
   ★ 老类 3 条端点**都没有** `@NoAuth`、没有写端点、没有 `@SysOpLog` ⇒ `LEGACY_WRITES` 与
     `system_operate_log` 的 +16 常量都不动。
   ⚠️ 第 2 条端点老 `@RequestMapping` **没写 method**(老栈 GET/POST 都进),新栈吃 `@RequestBody`
     一律 `@PostMapping` ⇒ 带 token GET 是 405(§2ao (d) 钉住)。
★★ **2026-10-17 第四十六切片:✔ 池里的 `RefreshCacheController`(4 端点)搬走 ⇒ ✔ 池 5/11 → 4/7**
   (`94/411` 与 `4/7` 同一次移动的两个投影)。★ 本片按第三十四切片 ④ 的 **(B) 配置化**方案落地:
   老类 4 条 `@NoAuth` + **写死的 32 位共享密钥**不搬(红线 11),改成 `parking.refresh-cache.token`
   配置项(本地 profile 给测试值,生产不配即 fail-closed);调用方不登录 ⇒ §3.3 **第 2 类**:
   包 `**.controller.app.**` → `/app-api` + `@PermitAll` + 注解自动进白名单。
   ★ 一个**新行为**:`refresh-sys-cache?type=1` 会把 `sys_code_value` 缓存**热起来**(跨轮次生效),
   于是 §2ak ep1 的首条图片会从 null 变成 `LZ_DZSP_*` 配置的 URL ⇒ 那条断言已改成「只有首条可能
   非 null、值只能是 null 或 http URL」,两种缓存态都合法。
   ★ 零新建 VO(4 个返回类型全是 `CommonResult<String>`);`LEGACY_WRITES` 不加(4 条只写 Redis、
   不写 DB,零写复查扫 51 张表只有 `system_operate_log` +16)。
★★ **2026-10-17 第四十七切片:⚠B 里的 `ApiCallLogController`(1 端点)搬走,同时落地 P3 组织 id 换算表**
   ⇒ **⚠B 113/700 → 112/699**(`95/412` 与 `112/699` 同一次移动的两个投影)。
   ★ 本片**不再「等 P3」**:`LoginUserBridge` 新增 `parking.legacy.org-id-mapping`
   (`新id:老id,...`,**默认空 = 恒等映射**、行为不变;本地 profile 配 `1:10003`),
   把登录人的新 `system_org.id` 换算成存量业务表认的老 `sys_org.id`。
   ★ 判据是**数据量而不是形状**:本接口在本地从 `total=0` 变成 `total=498`
   (`td_b_api_log` 里 org=10003 的行数);同时 `AreaController` 的区域树从 494 行变成
   586 行(省市区 494 + 区块 92)——后者是**已迁端点**,说明这张表是全局生效的,
   不是给单个 Controller 打补丁。
   ⚠️ 仍然没打通的是 `plNos` / `userAreas` / `resIds`(桥依旧留 null),所以 ⚠B 里
   **按这些字段过滤的类还不能直接开搬**;本片只解锁了「只读 orgId/orgIds」的那批。
★★ **2026-10-17 第四十八切片:⚠B 里的 `SmsTemplateConfigController`(5 端点)搬走**
   ⇒ **⚠B 112/699 → 111/694、已迁 95/412 → 96/417、未迁 116/706 → 115/701**。
   ★ 它是换算表落地后的**第二个**消费者(第一个是第四十七切片的 `ApiCallLogController`),
     证明「只读 orgId/orgIds」这条解禁路径可复制:本类 5 条端点只读 `userInfo.getOrgId()`,
     没有任何 `plNos`/`userAreas`/`resIds` 读取。
   ★ 判据依旧是**数据量**:`/page {}` 在本地 `sms_template_config` 上回 **total=1**
     (org=10003 且 `data_status=1` 的正好 1 行);映射不生效时是 `org_id = 1` ⇒ total=0(不报错)。
   ⚠️ 本类还有 4 个写端点,其中 `/delete-list` 的老 DAO 是 `return 0;` **桩**(点了从没删掉过任何行)
     ——红线⑥照搬,别说它「坏了」。
★★ **2026-10-17 第四十九切片:⚠B 里的 `ParkGroupController`(6 端点)搬走**
   ⇒ **⚠B 111/694 → 110/688、已迁 96/417 → 97/423、未迁 115/701 → 114/695**。
   ★ 它是换算表落地后的**第三个**消费者。`/page`、`/list` 只读 `userInfo.getOrgId()`,
     4 个写端点只读 `userInfo.getOpId()`(写 `create_empid`/`modfiy_empid`),
     没有任何 `plNos`/`userAreas`/`resIds` 读取。
   ★ 判据依旧是**数据量**:`/page {}` 在本地 `parking_lot_group` 上回 **total=13**
     (org=10003 且 `data_status=1`、`park_num>0` 的正好 13 行);映射不生效时是 `org_id = 1` ⇒ total=0。
   ⚠️ 老 smapper 的 `plGroupName` 分支**少一个 `and`** ⇒ 一传过滤条件就 SQL 语法错、恒 500
     ——红线⑥照搬,别说它「坏了」。
★★ **2026-10-17 第五十切片:⚠B 里的 `DiscountController`(7 端点)搬走**
   ⇒ **⚠B 110/688 → 109/681、已迁 97/423 → 98/430、未迁 114/695 → 113/688**。
   ★ 它是换算表落地后的**第四个**消费者,也是**最新的一类解禁形态**:
     **整类不读 `orgId`**(按请求体 `plNos` 过滤),三个写端点只拿 `userInfo.getOpId()`/`getName()`
     写审计列 ⇒ 连组织换算都不涉及。别再认为"⚠B 命中 = 必须等 P3 orgId 换算"。
   ★ 判据是**过滤后的数据量**:`/page {}` 在本地 `t_activity_discount` 上回 **total=21**
     (表里 500 行,smapper 硬编码 `data_state=1` 只剩 21 行)——拿表行数当期望值会一跑就红。
   ⚠️ 老 SQL 的 `disc_name like '%${discName}%'` 是**拼串**(注入面)、`queryParkName` 是
     **N+1 且不判空**、`/create` 的规则列表**不判空就是 NPE** —— 红线⑥全部照搬。
★★ **2026-10-17 第五十一切片:⚠B 里的 `TActivityAbcbankDiscountController`(5 端点)搬走**
   ⇒ **⚠B 109/681 → 108/676、已迁 98/430 → 99/435、未迁 113/688 → 112/683**。
   ★ 它是换算表落地后的**第五个**消费者,也是最"轻"的一类:`/page`、`/query` 只读
     `userInfo.getOrgId()`,三个写端点只拿 `userInfo.getOpId()`/`getName()` 写审计列,
     没有 `plNos`/`userAreas`/`resIds` 读取。
   ★ 判据同样是**过滤后的数据量**:`/page {}` 在本地 `t_activity_abcbank_discount` 上回 **total=1**
     (表里 15 行,`data_state=1 AND org_id=10003` 只剩 1 行 id=68);映射不生效是 `org_id=1` ⇒ 0 行。
   ⚠️⚠️ 本片最危险的一条:`/create` 在调服务层**之前**先调 `setTActivityAbcbankDiscountFalse`,
     那条 SQL **不带 `org_id`** ⇒ 会把**全库所有组织**的生效中配置(`disc_state in (0,1)`)
     一起下架。冒烟**只能发空 body**(绑定层 400),⛔ 绝不能发 `{}`(会先执行这条 update、之后才 NPE)。
   ⚠️ 另三处存量缺陷照搬:`disc_name like ?` **绑定不过滤通配符** ⇒ `discName=农行` 查不到
     `农行充值`(用户得自己传 `%`)、`/query` 查不到时**服务层 NPE**、`/delete-list` **逐 id 先查后删**
     (N+1)且 id 不存在时先 NPE。
★★ **2026-10-17 第五十二切片:⚠B 里的 `TActivityThirdDiscountController`(5 端点)搬走**
   ⇒ **⚠B 108/676 → 107/671、已迁 99/435 → 100/440、未迁 112/683 → 111/678**。
   ★ 它与第五十一切片是同一包(`discount/`)的孪生类,也是最"轻"的一类:`/page`、`/query`
     只读 `orgId`,`/update` **连 `orgId` 都不 set**,三个写端点只拿 `opId`/`getName()` 写审计列。
   ★ 判据同样是**过滤后的数据量**:`/page {}` 在本地 `t_activity_third_discount` 上回 **total=3**
     (表里 9 行,`data_state=1 AND org_id=10003` 正好 3 行 id=7/8/9);映射不生效是 `org_id=1` ⇒ 0 行。
   ★ 与孪生类**行为不同的两处**(别按上个类抄断言):`/query` 查不到回 **code=0、data=null**
     (服务层把 null 装进 BizResult,`getDataOrThrow` 允许透传,**不 NPE**)、`/delete-list`
     的 id 不存在回 **data=false**(逻辑删 0 行,**不 NPE**)。
   ⚠️ `/create` 的 `{}` 会**真插一行**(`disc_no` 由 `SequenceUtil` 生成;本类**没有**全库下架那一步)
     ⇒ 冒烟只能发空 body(绑定层 400),⛔ 绝不能发 `{}`。
   ⚠️ 与孪生类相同的坑:`andDiscNameLike` **不补 `%`**(`农行` → 0 行、`农行%` → 2 行)、
     `PageUtil.pageSizeValidate` 校验 1..1000。
★★ **2026-10-17 第五十三切片:⚠B 里的 `TpPNetworkCardController`(11 端点)搬走**
   ⇒ **⚠B 107/671 → 106/660、已迁 100/440 → 101/451、未迁 111/678 → 110/667**。
   ★ 本类落在 `park` 域(`POST /admin-api/park/network-card/*`),7 读 + 4 写、**无导出**;
     读端点只读 `orgId` **且带超管豁免**(超管不覆盖请求体 `orgId`),写端点只拿 `opId`/`getName()`。
     `/list` **完全不读登录态**(老 DAO 是 `//TODO` + `return null` ⇒ 恒空)。
   ★ 判据:`/page {}` 在本地 `tp_p_network_card` 上回 **total=500**
     (表里 500 行,全部 `data_state=1 AND org_id=10003`);映射不生效是 `org_id=1` ⇒ 0 行。
   ★ **本类没有 `PageUtil.pageSizeValidate`** ⇒ `pageSize=0` 不分页(回全量 500 行)、`1001` 照过 ——
     别把折扣类的「0 → 1002 / 1001 → 500」断言抄过来。
   ★ 存量缺陷照搬:`/delete` 与 `/delete-list` **都不置 `data_state=0`**(只改审计列);
     `/statistic` 查不到回 **code=0、data=null**;`/query-eqp-for-page {}` 与
     `/query-pda-for-page {eqpType:…}` 都因 tk `and*EqualTo(null)` 抛异常 ⇒ **500**。
   ⚠️ `/create` 的 `{}` 会**真插一行**(`insertSelective`,`create_date` 由 DAO 补 now)
     ⇒ 冒烟只能发空 body(绑定层 400),⛔ 绝不能发 `{}`。
★★ **2026-10-17 第五十四切片:⚠B 里的 `ParkingBindingChargingController`(10 端点)搬走**
   ⇒ **⚠B 106/660 → 105/650、已迁 101/451 → 102/461、未迁 110/667 → 109/657**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/parking-binding-charging/*`),7 读 + 3 写、**无导出**、
     **无 `@NoAuth`、无 `@SysOpLog`** ⇒ 新类没有 `@OperateLog`。
   ★ 整类只有 2 处读登录态(`queryParkingBindingChargingRules`、`initCloseParkingBindTree`),
     都是**无条件覆写 `orgId`、没有超管豁免**(与第五十三切片网络卡类**相反**);
     `orgId` 经 `parking.legacy.org-id-mapping` 换算 ⇒ `areas {plNo=P15040214Z}` 回 1 行即映射生效。
   ★ **本类两个分页端点都有 `PageUtil.pageSizeValidate`**(min=1/max=1000)⇒ `pageSize=0`/`1001` 都 500。
   ★ 本地恒 500 的四条端点**不是回归**:`query-parking-binding-charging-rules` 与
     `query-binding-rules-list` 撞 MySQL 8 `only_full_group_by`(老 SQL `GROUP BY r.pl_no` 却 select 非聚合列);
     `areas {}` 撞 Druid wall `where 1=1`;`areas {plNo=不存在}` 空结果拼出 `in ()` 撞 wall。
   ★ 空结果语义两分:`areas {plNo=NOPE}` **500**、`query-binding-area-rules-list {plNo=NOPE}` 回 **`[]`**。
   ★ `initCloseParkingBindTree` 必须调 **park 模块**的同名服务(bill 服务接口上那个实现 `return null`)。
   ⚠️ 3 个写端点:`/save` 的 `{}` NPE、`/update` 缺 `areaId`、`/delete` 缺 `plNo` 都在 DAO 前 500;
     **`/delete` 是物理删除**(`deleteByExample`)、`/update` 不存在则 `insertSelective` ⇒
     ⛔ 冒烟只能发空 body(400),绝不能发带真实 `areaId`/`plNo`/`parkDtoList` 的 body。
★★ **2026-10-17 第五十五切片:⚠B 里的 `TbAreaController`(2 端点)搬走**
   ⇒ **⚠B 105/650 → 104/648、已迁 102/461 → 103/463、未迁 109/657 → 108/655**。
   ★ 落在 `park` 域(`POST /admin-api/park/tb-area/*`),**全只读**、无导出、无 `@NoAuth`、
     无 `@SysOpLog` ⇒ 新类没有 `@OperateLog`。同目录 `AreaManageController`/`RegionAreaController`
     早已迁完,本类是「区域管理」页的**最后一块**。
   ★ `/query-next-lower-lever-by-id` **不查库**:数据源是 Redis 字典
     (`GOV_CLOUDS_LBS_AREA_` + cityId,`SysCodeValueCacheUtil`),`cityId` 为空先抛
     `PARAMM_NULL「区域ID」`;字典里没有该城市 ⇒ **code=0 + `[]`**(不是 500)。
   ★ `/get-berth-area-tree` 读 `orgId` 且**无条件覆写、没有超管豁免**;`orgId` 经
     `parking.legacy.org-id-mapping` 换算 ⇒ 本地回 588 行(32 省 + 342 市 + 120 区 +
     **92 区块** + 2 停车场),92 个区块即映射生效的判据。本地这 2 个车场没有泊位区间样本
     ⇒ **无 areaType=1000 行**(数据事实,不是代码丢段)。
   ★ `TpPRegionAreaService#queryTpPRegionAreaForPage` 接口声明 `throws Exception`
     ⇒ 控制器方法必须保留 `throws Exception`(同 `RegionAreaController`)。
★★ **2026-10-17 第五十六切片:⚠B 里的 `CarBreakRuleController`(3 端点)搬走**
   ⇒ **⚠B 104/648 → 103/645、已迁 103/463 → 104/466、未迁 108/655 → 107/652**。
   ★ 落在 `park` 域(`POST /admin-api/park/car-break-rule/*`),1 读 + 2 写、**无导出**;
     读端点**无条件覆写 `orgId`、没有超管豁免**(同第五十四/五十五切片),写端点只拿
     `opId`/`name` 写审计列;两个写端点老类都有 `@SysOpLog` ⇒ 新类有 `@OperateLog`。
   ★ 读端点 **不带 `plNos` 时老 DAO 走 `andPlNoIsNull()`** ⇒ `{}` 只回「未挂车场」的记录
     (本地 `org_id=10003` 有效行 52、其中 `pl_no IS NULL` **35** 行;显式 `plNos` 才查得到另 17 行)。
   ★ 两条写端点在控制器校验关口就 500(「主键信息」/「审核状态」),**服务层都碰不到**;
     ⛔ 冒烟只能发空 body 或这些炸关口样本,真 `id`/`ids` 会插审核记录或把 `upload_type` 标成 1。
★★ **2026-10-17 第五十七切片:⚠B 里的 `TBillingDescriptionController`(3 端点)搬走**
   ⇒ **⚠B 103/645 → 102/642、已迁 104/466 → 105/469、未迁 107/652 → 106/649**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/billing-description/*`),2 读 + 1 写、**无导出**;
     两个读端点**完全不读登录态**,写端点只拿 `opId`/`name`/`orgId` 写审计/组织列、不参与过滤;
     只有写端点老类有 `@SysOpLog` ⇒ 新类只在该端点带 `@OperateLog`。
   ★ 按计费集合查不到时返回**空 DTO 且 code=0**(不是 null、不是 500)。
   ★ 按车场查在本机**恒 500「系统异常」**:存量 join SQL 撞两表 **collation 不一致**
     (`t_billing_rule_collect` = utf8mb4_unicode_ci,另两张 = utf8mb4_general_ci),
     MySQL 回 **1267**;直连同一条 SQL 同样报错 ⇒ 非迁移回归,按红线⑥不改 SQL/库。
   ★ ⛔ `/save-or-update` 只能发空 body 或炸服务层校验关口的样本(缺 `ruleCollectNo` / 缺 `descText`);
     真发两者会把同集合旧描述置失效、插新描述,并清空两个 Redis key。
★★ **2026-10-17 第五十八切片:⚠B 里的 `DutyRosterGroupController`(4 端点)搬走**
   ⇒ **⚠B 102/642 → 101/638、已迁 105/469 → 106/473、未迁 106/649 → 105/645**。
   ★ 落在 `park` 域(`POST /admin-api/park/duty-roster-group/*`),1 读 + 3 写、**无导出**;
     查询/修改/删除三个端点**完全不读登录态**(`orgId`/`sOpId` 都来自请求体),
     只有保存端点读 `opId`(审计列)与 `orgId`(新行组织列 + 按计划物理删的过滤条件)。
   ★ 保存端点的顺序是「校验列表非空 → 按 `orgId+planId+dutyType` **物理删** → 逐条插」;
     列表为空时 1003 在删除**之前**抛出 ⇒ 冒烟只发列表为空的样本。
   ★ 修改 `{}` 返回 `code=0 + false`(`where id = null` 命中 0 行);
     ⛔ 本库**真有一行 `id=0`**,冒烟绝不能发 `id=0`(会改它的 `modify_time`)。
   ★ 删除是逻辑删(`data_status=0`),`ids` 为空 → 1003「值班班次ids」。
★★ **2026-10-18 第五十九切片:⚠B 里的 `ChargerAssessTargetController`(4 端点)搬走**
   ⇒ **⚠B 101/638 → 100/634、已迁 106/473 → 107/477、未迁 105/645 → 104/641**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-assess-target/*`),1 读 + 3 写、**无导出**;
     **四个端点全部读登录态**:查询把登录 `orgId` **无条件**覆盖请求体并作为 SQL 过滤条件
     (`andOrgIdEqualTo`,**无超管豁免**),保存/修改/删除只拿 `opId`/`name` 写审计列。
   ★ ★ 删除端点的老拼写是 **`bath-`**(少一个 c),路由照抄,别"修正"成 `batch-`。
   ★ `targetNo` 的自动生成在 DAO 里是**注释掉的**(「考核指标不生成,在码表里配置」);
     修改缺 id → 控制器 1003「id为空!」;删除 `ids` 为空 → 1003「id为空!」。
   ★ ⛔ 冒烟写样本只用「空白 targetNo / 不存在 id / 空 ids」;非空 targetNo 会真插行。
★★ **2026-10-18 第六十切片:⚠B 里的 `ChargerAssessRuleController`(4 端点)搬走**
   ⇒ **⚠B 100/634 → 99/630、已迁 107/477 → 108/481、未迁 104/641 → 103/637**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-assess-rule/*`),1 读 + 3 写、**无导出**;
     查询把登录 `orgId` **无条件**覆盖请求体并作为 SQL 过滤条件(**无超管豁免**),
     保存/修改/删除只拿 `opId`/`name` 写审计列。
   ★ 与兄弟类 `ChargerAssessTargetController` 的三处不同:查询**有 `order by id asc`**;
     请求体是**列表包装类**(`chargerAssessRuleRequests`,空/null 被 `ListCopyUtil` 安全吞掉 ⇒ `false`);
     修改的 id 判空在**服务层循环内**(`{}`→false、`[{}]`→1003)。
   ★ `ruleNo` 由 DAO `queryNextRuleNoByOrgId` **自动生成**(默认 `R1000001`,存量 N+1);
     删除端点老拼写是 **`bath-`**(单数 rule);删除是逻辑删(`data_state=0`)。
   ★ ⛔ 冒烟写样本只用「空列表 / 缺 id / 不存在 id / 空 ids」;非空列表会真插行。
★★ **2026-10-18 第六十一切片:⚠B 里的 `ChargerAssessTemplateController`(5 端点)搬走**
   ⇒ **⚠B 99/630 → 98/625、已迁 108/481 → 109/486、未迁 103/637 → 102/632**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-assess-template/*`),2 读 + 3 写、**无导出**;
     老类里 `/saveChargerAssessTemplateRel` **整段被注释** ⇒ 只迁 5 条。
   ★ 两个读端点把登录 `orgId` 用于主表 WHERE 与配置过滤(**无超管豁免**);
     `by-id` **不**按组织/`data_state` 过滤主表,查不到回全 null DTO(不是 null、不是 500)。
   ★ 保存先插主表再校验配置列表(空列表 1003;`@Transactional` 实测回滚);
     修改 id 判空在**控制器**;不存在的 id 回 1002「保存模板信息错误!」。
   ★ 删除的 `modify_emp_name` 因控制器 `setOpName` 与服务层 `getsOpName` **字段错位恒为 null** —— 存量缺陷照搬。
   ★ ⛔ 冒烟写样本只用「空 body / `{}`(缺 id)/ 不存在 id / 空 ids」;非空配置列表会真插行。
★★ **2026-10-18 第六十二切片:⚠B 里的 `ChargerAssessController`(5 端点)搬走**
   ⇒ **⚠B 98/625 → 97/620、已迁 109/486 → 110/491、未迁 102/632 → 101/627**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-assess/*`),**5 端点全部只读**、无导出;
     老类无 `@NoAuth`、无 `@SysOpLog` ⇒ 新类**不挂** `@OperateLog`,冒烟跑一轮审计仍只 +59。
   ★ 五个端点都把登录 `orgId` **无条件覆盖**进请求体并用于 WHERE,**无超管豁免**
     (请求体传 `orgId=10007`,结果仍是登录组织 `10003` 的)。
   ★ 结果分页的必填项:`beginTime`/`endTime` 缺 → 1003;`templateNo` 缺 →
     tk.mybatis Example 抛 `Value for templateNo cannot be null` → 500「系统异常」;
     `chargerCodes` 缺 → 服务层 for 循环 **NPE** → 500「系统异常」(存量缺陷照搬)。
   ★ 模板配置查询**只看配置表自身 `data_state=1`**,不看模板主表 `data_state`
     (主表 `data_state=0` 的 `M100000001` 仍出 1 条配置)。
   ★ ⚠ 本机 MySQL 8 上结果分页**恒 500**:存量聚合 SQL `GROUP BY charger_code,org_id`
     却 select `charger_name`/`person_type`,撞 `only_full_group_by`;**老栈同 SQL 同样 500**
     ⇒ 不是迁移回归,按红线⑥不改。
★★ **2026-10-18 第六十三切片:⚠B 里的 `ChargerAssessResultController`(7 端点)搬走**
   ⇒ **⚠B 97/620 → 96/613、已迁 110/491 → 111/498、未迁 101/627 → 100/620**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-assess-result/*` + `GET /export-excel`),
     **2 读 + 4 写 + 1 导出**;老类 6 条 POST 都有 `@SysOpLog` ⇒ 4 个写端点带 `@OperateLog`。
   ★ 两个读端点把登录 `orgId` **无条件覆盖**进请求体并用于 WHERE,**无超管豁免**
     (传 `orgId=10007` 仍回登录组织 `10003` 的 18 / 206 条)。
   ★★ **导出是唯一例外**:老 `@NoAuth`,`orgId` 由前端 query 参数传入并原样使用,
     **不覆盖**;缺 `orgId` → 500「系统异常」(Example 拒 null),`orgId=10007` → 1002,
     证明真按传入值查。按 §3.3 第 1 类收敛为需登录 + `park:charger-assess-result:export`。
   ★ 明细分页 `recordNo` 必填(服务层 1003「记录编码」),`order_num` 升序;
     结果分页 `id desc`、`pageSize=0` 回全部(18 行)。
   ★ 写端点校验顺序:`save` 先插主表再校验(缺开始时间 1003、`@Transactional` 回滚);
     `update` 控制器先判 id(1003「id为空!」)、服务层判审核状态/记录编码后**先插日志再改主表**;
     `bathDelete` 不存在的 id 回 **`code=0 + data=false`**(不是错误码);
     `updateDetail` 不存在 id 回 1002「…**为**查询到相关记录」(错别字照搬)。
   ★ ⚠ 本机没有 `charger_assess_result_log` 表 ⇒ `update` 恒 500「系统异常」
     (本地 schema 漂移、老栈同依赖);`save` 的聚合仍撞 MySQL 8 `only_full_group_by`。
   ★ ⛔ 冒烟写样本只用「空 body / `{}`(缺必填)/ 不存在 id / 空 ids」。
★★ **2026-10-18 第六十四切片:⚠B 里的 `TdCCustSuggestDetailController`(7 端点)搬走**
   ⇒ **⚠B 96/613 → 95/606、已迁 111/498 → 112/505、未迁 100/620 → 99/613**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/cust-suggest-detail/*`),**3 读 + 4 写**、无导出;
     老类 4 条写都有 `@SysOpLog` ⇒ 新类带 `@OperateLog`,3 条读不带。
   ★ **只有建议分页读登录组织,且有超管豁免**(`SUPER_ORG_ID` 时**不**覆盖、看全部);
     两个回复读端点**完全不读登录态**。本机 `td_c_cust_suggest.org_id` 全 NULL +
     登录组织覆盖 ⇒ 建议分页 6 种入参都 `total=0`。
   ★ **回复分页是存量死读**(服务层直接返回空 `PageBean`、DAO 实现整段注释)⇒ 恒 `total=0`,照搬。
   ★ `save` 先插回复再置主表 `is_answer=1` 再推极光;`suggestId` 空的 1003 在写审计字段**之后**,
     且 `@Transactional` 内抛 `BizException` ⇒ `@OperateLog` **不记账**。
   ★ `update` 只写 `sOpId`,空 SET 段被 druid 判语法错 ⇒ 空 body/id 不存在都 500。
   ★ `bath-delete` 两条 500:`{}` → `Value for id cannot be null`、`{ids:[]}` → `id in ()` 语法错;
     `{ids:[不存在]}` → `code=0 + data=false`。路由拼写是 **`bath-`**(照抄)。
   ★ ⛔ 冒烟写样本只用「空 body / `{}`(缺必填)/ 不存在 id / 空 ids」。
★★ **2026-10-18 第六十五切片:⚠B 里的 `PdaManageMentController`(12 端点)搬走**
   ⇒ **⚠B 95/606 → 94/594、已迁 112/505 → 113/517、未迁 99/613 → 98/601**。
   ★ 落在 `park` 域(`POST /admin-api/park/pda/*` + 3 条 `GET ...excel`),
     **4 读 + 5 写 + 3 导出**;3 条导出与 1 条导入老类 `@NoAuth`。
   ★ 老类 4 条写有 `@SysOpLog` ⇒ 新类带 `@OperateLog`;`import` 不带。
   ★ 两个分页读端点的登录 `orgId` **无条件覆盖**进请求体并用于 WHERE、**无超管豁免**;
     本机按 `orgId` 过滤不缩小结果(229 → 229)。统计端点登录 null 时回退 `orgId=99999`。
   ★ 存量缺陷照搬:`/export-berth-info-excel` 判的是刚 `new` 出来的空 `ArrayList`
     ⇒ **恒空文件**;`/query-passedca4pabe` 在本地因 `NID`(32 位 hex)映射 `Integer`
     抛异常被 `catch` 吞掉 ⇒ `data=null`;`/import-pda` 的 `list` 初值为 null 后直接 `for` ⇒ NPE。
   ★ `insert-pda` 的 `{}` 会**真插一行**(全列可空)⇒ 冒烟只能用已存在 `pdaNo`;
     `update {}` → 1007 / 空 SET、`delete {}`/`{ids:[]}`、`batch-delete {}`/`{padList:[]}` 均 500。
   ★ `updateByPrimaryKeySelective` 空 SET 段、`andIdIn(null)`、空 `IN ()` 都是存量行为,不修。
   ★ ⛔ 冒烟写样本只用「已存在 `pdaNo`(回 3003)/ 空 body / 不存在 id / 空 ids」。
★★ **2026-10-18 第六十六切片:⚠B 里的 `ChargerManageController`(32 端点)搬走**
   ⇒ **⚠B 94/594 → 93/561、已迁 113/517 → 114/550、未迁 98/601 → 97/568**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger/*` + 3 条 `GET ...export...`),
     **16 读 + 13 写 + 3 导出**;3 条导出老类 `@NoAuth` 全按 §3.3 第 1 类收紧。
   ★ 老类 33 个端点,本片只迁 32:`upload-employee-head-pic` 走 FastDFS/存储后端,
     等 `FileClient` 抽象拍板后再补。脚本按整类计 ⇒ 机械计数 **+33** 而实际迁 **32**。
   ★ 11 个写端点老类有 `@SysOpLog` ⇒ 新类带 `@OperateLog`;
     `update-bind-regions`、`get-employee-tail` 不带。
   ★ 分页/签到类读端点的登录 `orgId` **无条件覆盖**进请求体并用于 WHERE、**无超管豁免**;
     `queryEmployerByPlNo`/`queryInspectSituationForPage`/`querySignRecordByChargerCode`/
     3 导出/`getEmployeeTail` **完全不读登录态**。
   ★ 存量缺陷照搬:老 GPS 查询/导出**恒 500**(存量 SQL 末尾 `WHERE 1=1` 撞新栈 druid
     `wall`;老 portal 无 wall);`/query-lot-region-tree` 无「事」节点时
     `rootDTO.getChildren()` 回 **null** ⇒ `data=null` 不是 `[]`;`resetpwd {}` 与
     `acct-status {不存在编码}` 均回 `true`(0 行也算成功);`getEmployeeTail` 只回最后一次
     循环的 `BizResult`。
   ★ `insert {}`/`update {}` 是 **400**(`@Valid`),不写审计;其余写端点的 `{}` 多落在
     服务层 NPE / tk.mapper null 守卫 ⇒ 500。
   ★ ⛔ 冒烟写样本只用「空 body / `{}`(缺必填)/ 不存在编码 / 空列表」。
★★ **2026-10-18 第六十七切片:⚠B 里的 `DutyRosterController`(25 端点)搬走**
   ⇒ **⚠B 93/561 → 92/536、已迁 114/550 → 115/575、未迁 97/568 → 96/543**。
   ★ 落在 `park` 域(`POST /admin-api/park/duty-roster/*` + 1 条 `GET ...-list`),
     **12 读 + 12 写 + 1 导出**;导出老类 `@NoAuth` 按 §3.3 第 1 类收紧。
   ★ 12 个读端点把登录 `orgId` **无条件覆盖**进请求体并用于 WHERE、**无超管豁免**;
     `queryDutyRosterPlanForPage` 覆盖的是 `List<Long> orgId`。
     `updateDutyRoster`/`exchangeDutyRoster`/两个验证码端点/导出**完全不读登录态**。
   ★ 4 个写端点老类有 `@SysOpLog` ⇒ 新类带 `@OperateLog`
     (保存调休记录 / 删除排班计划 / 正式提交排班计划 / 调班)。
   ★ 存量缺陷照搬:`queryNoDutyRosterParkTree` 老实现只 set `orgId/sysCode/plType`、
     **从不 set `plNos`**,而底层 `queryParkLotByOrgIdAndPlNos(orgId, plNos, plType)`
     三参版要求 `plNos` 非空 ⇒ **恒 1003「停车场编号」**(老栈同样炸);
     `deleteParkByPlanIdAndPlNos {}` 未判空,直落服务层 ⇒ **500「系统异常」**。
   ★ `queryDutyPersonNumAndRecessNum` 老类 `@Validated` ⇒ 新类 `@Valid`,`{}` 是 **400**。
   ★ `generatorDutyRosterPlanId` 不收 body(空 body 回 code=0),**不能**进 `LEGACY_WRITES`。
   ★ ⛔ 冒烟写样本只用「空 body / `{}`(缺必填)/ 假验证码」:**绝不发真实手机号**
     (`sendVerifyCodeByPhoneNumber` 会真发短信)。
   ★ POI 4.1.2 已移除 `XSSFCellStyle.ALIGN_CENTER`/`XSSFCell.CELL_TYPE_STRING` 等 int 常量,
     照搬存量导出必须先换 `HorizontalAlignment`/`VerticalAlignment`/`BorderStyle`/`CellType`。
★★ **2026-10-18 第六十八切片:⚠B 里的 `TemporarySettlementController`(18 端点)搬走**
   ⇒ **⚠B 92/536 → 91/518、已迁 115/575 → 116/593、未迁 96/543 → 95/525**。
   ★ 落在 `order` 域(`POST /admin-api/order/temporary-settlement/*` + 2 条 `GET …excel`),
     **12 读 + 4 写 + 2 导出**;老路由 `/TemporarySettlement`,
     老类在 `financialmanagement/clearsettlement/`。
   ★ 登录态只在 8 处把**当前登录人组织**写进 `fromOrgId`/`toOrgIds`(打款方/收款方),
     **无 `plNos`/`userAreas`/`resIds`、无超管豁免**;`querySettlementRecordByStatusForOrgId`/
     `querySettlementRecordListBySettleNO`/`querySettlementInfoByNo` 与 2 条导出**完全不读登录态**。
   ★ 3 个写端点老类有 `@SysOpLog` ⇒ `@OperateLog`(`confirmReceiveMoney` 没有);
     第 4 处 `@SysOpLog` 挂在**读端点** `querySettlementRecordSumforOrgId` 上,
     按第六十三切片口径新类**不挂**。
   ★ 两条导出老 `@NoAuth` 按 §3.3 第 1 类收紧为 `GET` + `order:temporary-settlement:export`。
   ★ 存量缺陷照搬:`orgIds` 为空时把 `new ArrayList<>()` 塞回请求体(**不是**加 orgId,恒查空集合);
     `Boolean flag` 直接拆箱 ⇒ 缺 `flag` 是 **500 NPE**(`fee-summary`/`list-page`);
     导出 `getData().getDataList()` 无判空;`fenChangeToyuan` 用 `Double.toString()`
     (`100`→`"1.0"`);主导出 `new BaseInfo(1, 0)` 吃全局 `pagehelper.page-size-zero: true`。
   ★ ⛔ `update-settlement-record-check-status-by-org-id-and-time` 只要过校验就**必插一行
     `td_b_settlement_serial`**(0 行可改也插)⇒ 冒烟只发 `{}` 与空 body。
   ★ 导出日期走 `com.clouds.common.utils.DateUtil.strDate`(严格 `yyyy-MM-dd HH:mm:ss`),
     只给 `yyyy-MM-dd` 是 **500「系统异常」**,不是 400。
★★ **2026-10-18 第六十九切片:⚠B 里的 `ChargerAuditController`(13 端点)搬走**
   ⇒ **⚠B 91/518 → 90/505、已迁 116/593 → 117/606、未迁 95/525 → 94/512**。
   ★ 落在 `park` 域(`POST /admin-api/park/charger-audit/*` + 4 条 `GET …excel`),
     **9 读 + 0 写 + 4 导出**;老路由 `/pda/input`,
     老类在 `operationsmanage/chargemanage/`。**零写**、无 `@SysOpLog` ⇒ 无 `@OperateLog`。
   ★ 登录态只出现在 4 个收入报表端点:把**当前登录人组织** `orgId` **无条件覆盖**进请求体
     (无超管豁免);前 5 个稽核读端点与 4 条导出**完全不读登录态**。
   ★ 4 条导出老 `@NoAuth`(任何人可下载全部收费员/收益数据)按 §3.3 第 1 类收紧为
     需登录 + `park:charger-audit:export`;一个 Controller 四个导出 ⇒ 主导出
     `/export-excel`(两张 sheet:收费员收益稽核 + 停车场收益稽核),其余
     `/export-input-nums-excel`、`/export-report-time-excel`、`/export-dy-excel`。
   ★ 5 个稽核读端点在 DAO 层**无条件拆箱** `inOutState`(`queryEqpAndPdaTimesForLine`
     另拆 `timeType`)⇒ 带时间但缺这两个字段是 **500「系统异常」**;冒烟正样本都带上。
   ★ 存量缺陷照搬:收入报表 3 条列表 + 主导出/大元导出在本机撞
     **MySQL 8 `only_full_group_by`**(smapper `GROUP BY charger_code, pl_no, ...`
     却 SELECT `charger_name`),**两栈同坏、不改 SQL**(红线⑥),冒烟断言 500;
     `queryChargerIncomeStatisticTotal` 的 SQL 不分组 ⇒ 本机 code=0 可正向。
   ★ `getAreaName` 在停车场 sheet 里是**每行一次查询的 N+1**,按红线⑥照搬并加注释。
★★ **2026-10-18 第七十切片:⚠B 里的 `SubCardInfoManageController`(9 端点)搬走**
   ⇒ **⚠B 90/505 → 89/496、已迁 117/606 → 118/615、未迁 94/512 → 93/503**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/sub-card-info-manage/*`),
     **5 读 + 4 写 + 0 导出**;老路由 `/subCardInfoManage`,
     老类在 `parkinglotcloudplatform/closecarpark/`。
     **无 `@NoAuth`、无导出、无 `@SysOpLog`** ⇒ 新类无 `@OperateLog`。
   ★ 登录态只出现在 `query` / `save` / `check-sub-custmoer-bind` 三条:
     「`userInfo != null && userInfo.getOrgId() != null` 才覆盖」、**无超管豁免**;
     其余 6 条**完全不读登录态**(无 `plNos`/`userAreas`/`resIds`)。
   ★ 存量缺陷照搬(新栈 DAO 原本就这样):`{}` 打在 update/delete/check/view/
     viewCar/flow 全是 **500「系统异常」**(tk.mybatis 条件值 null 抛
     IllegalArgumentException / 查回 null 后继续解引用);只有 `query {}` 回空页;
     `query` 的 `pageSize` 走 `PageUtil.pageSizeValidate` ⇒ 0/1001 是 500 存量错误码 1002。
   ★ ⛔ **两个写端点绝不能用 `{}`**:`save {}` 会在事务内**真插 3 张表**;
     `save-sub-flow-record {}` **先插一行 `td_cl_sub_flow_record`**、再在
     `chargeType` 拆箱处 NPE,而该 inner 方法**没有 `@Transactional`** ⇒ 不回滚。
     冒烟只用**无 body**(绑定层 400),四张表行数已实测不变。
★★ **2026-10-18 第七十一切片:⚠B 里的 `VipCardAuthPersonBjxcController`(12 端点)搬走**
   ⇒ **⚠B 89/496 → 88/484、已迁 118/615 → 119/627、未迁 93/503 → 92/491**。
   ★ 落在 `order` 域(`POST /admin-api/order/vip-card-auth-person-bjxc/*` + 2 条 `GET …excel`),
     **4 读/导入(3 读 + 1 导入)+ 6 写 + 2 导出**;老路由 `/vipCardAuthPersonBjxcManage`,
     老类在 `vipCardAuthPersonBjxc/`(北京西城「一位多车卡」授权人)。
   ★ 登录态出现在 8 条端点(9 处调用,`batch-inset` 里两处),全部只覆盖 `orgId` 或写审计列
     (`sOpId`/`createEmpid`/`modfiyEmpid`/`opId`/`opName`),
     **无 `plNos`/`userAreas`/`resIds`**;另 4 条(`query-cust-invoice-info-by-id`、两个导出、导入)完全不读登录态
     (`export-excel` 连组织过滤都不加,导出的是全部授权人)。
   ★ 三个老 `@NoAuth` 端点按 §3.3 收紧:两个导出 → `:export`(主 `GET /export-excel`、
     次 `GET /export-invoice-excel`,26 列)、导入 → `:import`(multipart,
     `POST /batch-import-bjxc-vipcard-info`,与 `OrderModifyController#read-excel` 同款)。
   ★ 唯一的 `@SysOpLog`(`batchInsetBjxcVIPcardList`「优惠月卡-批量保存」)
     ⇒ `@OperateLog(create, 优惠月卡, 批量保存)`。
   ★ **本地 schema 缺口(新旧两栈 mapper XML 逐字相同,不是迁移引入)**:
     `VipCardAuthPersonBjxcMapper.xml` select 带 `examine_state`/`examine_date`/`picture_path`,
     而本机 `vip_card_auth_person_bjxc` 只建到 `invoice_apply_time` ⇒ `selectByExample`/
     `selectByPrimaryKey` 恒 `BadSqlGrammarException` ⇒ `query-pay-order-for-vip-card-invoicebjxc`、
     `export-invoice-excel`、`examine` 三条**本机恒 500「系统异常」**;分页查询走另一条 SQL,正常。
   ★ 存量缺陷照搬:`save {}` 的 `vipAuthType` 拆箱 NPE;`examine {}` 的
     `getExamineState()+""` 得到字符串 `"null"`(非空串)⇒ 校验放行后拆箱 NPE;
     `batchInsetBjxcVIPcardList` 的 `list.size()` 在空判**之前**(`{}` NPE、`[]` 才是 1003「数据为空」);
     `ExcelReadUtil.readUploadExl` 对假文件**不抛 IOException** 而是回空表 ⇒ 导入的
     `BIZ_ERROR` 分支实际走不到(冒烟断言 `code=0 data=null`)。
   ★ ⛔ **写端点绝不能发业务形状**:`save {vipAuthType:1}` 且手机号不存在时会
     **真插一条 `td_c_cust_person`**。冒烟只用**无 body**(绑定层 400)与上述炸关口样本,
     `vip_card_auth_person_bjxc`、`td_c_cust_invoiceinfo` 行数已实测不变。
★★ **2026-10-18 第七十二切片:⚠B 里的 `GeomagneticManagementController`(10 端点)搬走**
   ⇒ **⚠B 88/484 → 87/474、已迁 119/627 → 120/637、未迁 92/491 → 91/481**。
   ★ 落在 `park` 域(`POST /admin-api/park/geomagnetic/*` + 1 条 `GET …export-excel`),
     老路由 `/geomagnetic`(老类在 `devicemanagement/`,地磁设备管理);
     **3 读 + 1 导入 + 1 导出 + 5 写**。
   ★ 登录态只在 4 条端点(`statistic` / `records` / `insertone` / `batch-inset`),
     都是把当前登录人 `orgId` 写进查询条件或库,**无 `plNos`/`userAreas`/`resIds`**;
     其余 6 条完全不读登录态。
   ★ 两个老 `@NoAuth` 端点按 §3.3 收紧:导出 → `:export`(`GET /export-excel`,11 列,
     `sysCode` 硬编码 `"1001"`、`new BaseInfo(1, 10000)` 照搬,空结果也出文件);
     导入 → `:import`(`POST /batch-import-eqp-info`,multipart `excelFile`)。
   ★ **本类 `@Valid` 是真生效的**(老类逐字相同):`DeleteEqpsRequest.eqps` @NotEmpty、
     `NewEqpRequest` 的 eqpNo/createOp/usedType 必填、`EqpHostRequest.ids` @NotEmpty、
     `QueryParkLotInfoByPkNoRequest.pklNo` @NotNull ⇒ 这些端点的 `{}` / 空集合是 **400**;
     `QueryEqpRequest` 无约束 ⇒ `statistic` / `records` 的 `{}` 能到 service(空条件查询)。
   ★ 存量缺陷照搬:`insertone` 缺 `joinMethod` 在拆箱处 NPE(发生在 insert 之前,安全)、
     `batch-inset-excle-list` 的 `list.size()` 在空判**之前**(`{}` NPE、`[]` 才是 1003「数据为空」)、
     `export-excel` 的 `SimpleDateFormat.parse` 遇到坏日期抛 ParseException ⇒ 500,
     `readUploadExl` 对假文件回空表而不抛 IOException ⇒ 导入断言 `code=0 data=null`。
   ★ ⛔ **`insertone` 带 `joinMethod=1`(设备直连)+ 不存在的 `eqpNo` 会真插一行 `tp_p_eqp`**;
     `deleteall` 会逻辑删除 `eqps` 里列出的设备、`updateone` 改主数据、
     `bind-eqp-host-by-ids` 改 `gateway_no`。冒烟一律用**无 body**(绑定层 400)
     或带 `id:0` 的 0 行 no-op 样本,`tp_p_eqp` 行数已实测不变。
★★ **2026-10-18 第七十三切片:⚠B 里的 `TdCRechargeCardController`(10/11 端点)搬走**
   ⇒ **⚠B 87/474 → 86/463、已迁 120/637 → 121/648、未迁 91/481 → 90/470**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/recharge-card/*` + 1 条 `GET …export-excel`),
     老路由 `/tdCRechargeCardManage`(老类在 `appservice/`,充值卡管理);
     **4 读 + 5 写 + 1 导出**。
   ★ **唯一未迁端点 `rechargeCardGeneratePic`**:本地 `./cardpic` 生成二维码 zip +
     `FastDFSClientWrapper.uploadFile` + 落 `td_c_recharge_card_pic_zip`,
     卡在「文件存储收敛到 `FileClient` 抽象」的独立决策(与 `/upload-employee-head-pic`、
     停车场车位共享同款)。
   ★ 登录态在 7 条端点(page / zip-page / save / update / delete / bath-delete /
     batch-update),全部只碰 `orgId` 或 `opId`/审计列,**无 `plNos`/`userAreas`/`resIds`**;
     list / statistic / 导出完全不读登录态。
   ★ 导出老 `@NoAuth` 按 §3.3 收紧为 `:export`:13 列、`sysCode` 硬编码 `"1001"`、
     入参全 `required=false`、`orgId` 取**请求参数**不取登录组织、空表也出文件。
   ★ 存量死读:`queryTdCRechargeCardList` 的 DAO 方法整段注释、`return null` ⇒ 恒 `data=[]`。
   ★ 存量缺陷照搬:`save {}` 的 `cardNum` 拆箱 NPE、`save cardNum=0` 空 `<foreach>` 坏 SQL、
     `bath-delete {}` / `batch-update {}` 的 `andIdIn(null)` 坏 SQL(均 500「系统异常」);
     `delete` 只写 `modfiy_empid`/`modfiy_date`、**不改 `data_state`**;
     `zip-page` 的 `andCreateDateBetween(null,null)` 抛 RuntimeException ⇒ 两个日期是事实必填;
     `export` 的 `getCardType()==1` / `getEndTime().getTime()` 拆箱,坏日期走 hutool `parse`。
   ★ `delete`/`bath-delete`/`batch-update` 里的 `setsOpId(...)` 是存量死赋值
     (DTO 自己声明 `String opId` 并覆盖 `getOpId()`,服务读的是那个 String)。
   ★ ⛔ **`save` 带 `cardNum>0` 会真插 N 行 `td_c_recharge_card`**;冒烟只发 `{}` /
     `{cardNum:0}` / `id:0` / `ids:[0]`,`td_c_recharge_card` 与
     `td_c_recharge_card_pic_zip` 行数已实测不变。
★★ **2026-10-18 第七十四切片:⚠B 里的 `VipCardCustInfoController`(迁 14/15 端点)搬走**
   ⇒ **⚠B 86/463 → 85/448、已迁 121/648 → 122/663、未迁 90/470 → 89/455**。
   ★ 落在 `order` 域(`POST /admin-api/order/vip-card-cust-info/*` + 1 条
     `GET …export-excel` + 1 条 multipart 导入),老路由 `/vipCardCustInfo`
     (老类在 `operationsmanage/vipcardmanage/`,个人购买会员卡记录);
     **6 读 + 7 写 + 1 导入 + 1 导出**。
   ★ **唯一未迁端点 `exportVipCardWordDoc`**:输出目录硬编码
     `/Users/wangbiao/Downloads/四川营协议/`、模板用 `ClassPathResource.getFile()`
     (打 jar 后不可用)、`XWPFTemplate`(deepoove poi-tl)新栈**没有依赖**
     ⇒ 与 `rechargeCardGeneratePic` 同归「文件存储/外部模板」决策。
   ★ 登录态只碰 `orgId`/`createEmpid`/`modifyEmpid`/`name`,
     **无 `plNos`/`userAreas`/`resIds`**;读端点与导出完全不读登录态。
   ★ 老 `@NoAuth` **三处**,三种处置:multipart 导入 → `:import`;导出 → `:export`
     (`GET /export-excel`);`checkPhoneAndCarNumber`(老前端只有登录后的
     `cardrule.js`/`xcvipcard.js` 在调)→ `:query`,老 `@SysOpLog` 保留成
     `@OperateLog(QUERY, …)`。
   ★ 存量缺陷照搬:`update-*-by-id` 服务层 `detailList.iterator()` NPE、
     `update-*-by-cust-card-no` 空指针、`change-*-car-number`/`delete-*` 的 1003 判空、
     `check {}` 的 `DateUtil.parse(null)` NPE;`check` 里「每个别人的车牌都覆盖一次结果」
     的循环与 `getCustId().equals(...)` 裸调用一律保留。
   ★ 导出 `@RequestParam Date beginTime/endTime` 走 §3c 契约(坏日期 400,
     与充值卡导出的 hutool 解析 500 不同);`pageSize=0` 靠全局 `page-size-zero`。
   ★ ⛔ **`save-vip-card-cust-info-and-person` 会建客户/车牌/订单/授权多表插入**;
     `batch-save-vip-card-buy` 批量建卡订单;`check-phone-and-car-number` 在
     「手机号存在、车牌不存在」时**真插一条 `td_c_car`**。冒烟只发**无 body** 与
     `checkFlg=2` + 假车场样本,`t_vip_card_cust_info` 等表行数已实测不变。
★★ **2026-10-18 第七十五切片:⚠B 里的 `WorkOrderManageController`(迁 8/8 端点)搬走**
   ⇒ **⚠B 85/448 → 84/440、已迁 122/663 → 123/671、未迁 89/455 → 88/447**。
   ★ 落在 `order` 域(`POST /admin-api/order/work-order/*`),老路由 `/workorder`
     (老类在 `operationsmanage/workordermanage/`,工单管理);**4 读 + 4 写、无导出**。
   ★ 登录态只在 `save-order-work` / `save-order-work-for-inspection` 两处读,
     且**只取 `orgId`**(`userInfo != null && orgId != null` 才覆盖);
     其余 6 条完全不读登录态,**无 `plNos`/`userAreas`/`resIds`**。
   ★ `query-order-work-track-by-work-id` 的 `workId` 为空抛 1003「入参为空」;
     轨迹列表走老 `returnJqGridDataByList` 口径(`total = list.size()`)→
     `toPageResultOfList`(同 `ParkingFlowController` 的写法)。
   ★ 存量错拼 `Histoty` 保留在路由里(`…-for-page-for-histoty`)。
   ★ 老 `@SysOpLog` 5 处 → `@OperateLog`(CREATE×3、UPDATE×1、DELETE×1);
     `save-order-work-for-publish` 底层是 `updateOrderWorkAndOrderWorkTrack` ⇒ 记 UPDATE。
   ★ ⛔ **`save-order-work-for-inspection` 与 `save-order-work-for-publish` 传 `{}`
     会真插 `order_work`/`order_work_track`**;冒烟只发无 body,两张表行数已实测不变。
★★ **2026-10-18 第七十六切片:⚠B 里的 `TdBPayProductController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 84/440 → 83/433、已迁 123/671 → 124/678、未迁 88/447 → 87/440**。
   ★ 落在 `order` 域(`POST /admin-api/order/pay-product/*`),老路由
     `/tdBPayProductManage`(支付产品管理);**4 读 + 3 写 + 1 批量删、无导出**。
   ★ 6 处读登录态但**只碰 `orgId`/`opId`/`name`**:page/save/appNameList 在
     `userInfo == null || orgId == null` 时抛 `AUTH_TOKEN_NOT_EXISTS`,
     非超管(`orgId != -1`)才用登录组织覆盖请求;`opId` 只作审计字段写库,
     **无 `plNos`/`userAreas`/`resIds`**。
   ★ `bath` 是存量错拼,路由保留(`/bath-delete-td-b-pay-product`)。
   ★ 存量缺陷照搬:`delete-td-b-pay-product` 写 `request.setsOpId(...)`
     (`BaseRequest` 字段),服务层却读 `getOpId()`(子类字段)⇒ 操作人恒 null;
     `batchDelete` 才写 `setOpId`。`update` 的请求 DTO 没有 id,`{}` 直接撞空主键 500。
   ★ ⛔ **`save-td-b-pay-product` 传 `channelEnum` 会真插 `td_b_pay_product` +
     渠道配置 + `sys_org_app_id`**;冒烟只发 `{}` 与无 body,三张表行数已实测不变。
★★ **2026-10-18 第七十八切片:⚠B 里的 `IncomeChargerCheckTaskSettingController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 82/426 → 81/419、已迁 125/685 → 126/692、未迁 86/433 → 85/426**。
   ★ 落在 `order` 域(`POST /admin-api/order/charger-check-task-setting/*`),老路由
     `/incomeChargerCheckTaskSettingManage`(收费员考核任务设置);
     **2 读 + 1 导出 + 1 导入 + 3 写**;导出数 64 → 65。
   ★ 导出按 §3.3 第 1 类收紧:老 `@NoAuth` → 登录 + `:export`,路由固定
     `GET /export-excel`;导出里 `orgId` 硬编码 10003、`plBusiType` 硬编码 `"1"`,照搬。
   ★ 5 处读登录态但**只碰 `orgId`/`sOpId`/`name`**;**无 `plNos`/`userAreas`/`resIds`**。
   ★ `bath` 是存量错拼,路由保留(`/bath-delete-income-charger-check-task-setting`)。
   ★ page/sumFee 的 `staticDate` 判空 + beginOfDay/beginOfMonth 是存量 Controller
     层处理,照搬;`{}` 是 500「统计时间不能为空」。
   ★ `update {"id":-1}` 其余字段全 null ⇒ tk mapper 空 SET ⇒ Druid 报
     `expect SET, actual WHERE` ⇒ 500(存量同样炸)。
   ★ 导入 multipart 先解析 `statisticDateStr`:缺月份是 400「请求参数缺失」,
     只发月份缺文件才是 `MissingServletRequestPartException` ⇒ 500。
   ★ ⛔ **`save-income-charger-check-task-setting` 有 body 就会真插
     `income_charger_check_task_setting`**(无任何必填校验);冒烟只发无 body。
   ★ ⛔ 导入**合法 Excel** 会走 `batchSaveChargerCheckTask`(按月份先删后插)——
     冒烟只发缺参 multipart,服务层未被调用,该表行数已实测不变。
★★ **2026-10-18 第七十九切片:⚠B 里的 `CustomerManagementController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 81/419 → 80/412、已迁 126/692 → 127/699、未迁 85/426 → 84/419**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/customer-manage/*`),老路由
     `customerManage`(★ 老 `@RequestMapping` **没有前导斜杠**);
     **4 读 + 3 写、无导出**。
   ★ 3 处读登录态但**只碰 `orgId`/`orgName`**,老写法是宽松守卫
     `if (userInfo != null && null != userInfo.getOrgId())`(不抛
     `AUTH_TOKEN_NOT_EXISTS`);**无 `plNos`/`userAreas`/`resIds`**。
   ★ 老类无 `@SysOpLog` ⇒ 不加 `@OperateLog`。
   ★ 存量差异照搬:`nameByNo {}` 对 null 列表 for-each NPE ⇒ 500;
     `view {}` 撞 `BeanUtils.copyProperties(null, dto)` ⇒ 500;
     `update {}` 的 DAO 是 `updateByPrimaryKey`(非 selective)⇒ SET 全列、
     `where id = null` 命中 0 行(code=0 false)。
   ★ ⛔ **`save-customer-manage-info` 带 `carInfoDTOList` 会真插
     `td_cl_customer_management` + `td_cl_customer_car_info`**;
     冒烟只发无 body 与 `update {}`,两张表行数已实测不变。
★★ **2026-10-18 第八十切片:⚠B 里的 `TdCPresentRuleController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 80/412 → 79/405、已迁 127/699 → 128/706、未迁 84/419 → 83/412**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/present-rule/*`),老路由
     `/tdCPresentRuleManage`;**3 读 + 4 写、无导出**。
   ★ 类路由按「去掉表前缀 `TdC` 再 kebab」定为 `present-rule`
     (先例 `TdCRechargeCardController` → `/pay/recharge-card`);
     方法段 kebab 老 mapping 逐字,`bath` 存量错拼保留。
   ★ 5 处读登录态但**只碰 `orgId`/`sOpId`/`createEmpid`/操作人**;
     **无 `plNos`/`userAreas`/`resIds`**。老类 4 处 `@SysOpLog` ⇒ 新类 4 处 `@OperateLog`。
   ★ 存量差异照搬:`save {}` 撞 `getPayType().contains(...)` 的 NPE ⇒ 500
     (**在任何 DB 调用之前**);`update {}` 的全 null selective update 在 SQL 层炸 ⇒ 500;
     `bath-delete {}` 对 null ids 直接 `.size()` NPE ⇒ 500;`delete {}` 命中 0 行 ⇒ code=0 false。
   ★ ⛔ **`save-td-c-present-rule` 只要 `payType` 有值就会真插 1~2 行
     `td_c_present_rule`**(`3`/`1,3`/`2,3` 还会先查农行 5 折活动);
     冒烟只发无 body 与炸关口样本,该表行数已实测不变。
★★ **2026-10-18 第九十六切片:⚠B 里的 `ArrearageStaticController`(迁 5/8 端点,3 个「模板 + FastDFS 回 URL」导出挂起)搬走**
   ⇒ **⚠B 64/281 → 63/273、已迁 143/830 → 144/838、未迁 68/288 → 67/280**。
   ★ 落在 `order` 域(`POST /admin-api/order/arrearage-static/*` + `GET /export-excel`),
     老路由 `/arrearageStatic`(欠费统计);**4 读 + 1 响应流导出 + 3 挂起**。
   ★ 坑 22 安全:4 读 + 导出的登录态**只读 `orgId`**(并无条件覆盖进请求体),
     请求体 `plNos` 是前端下发的,**不读** `userInfo.getPlNos()`/`userAreas`/`resIds`。
   ★ 老类 8 个端点**一条 `@SysOpLog` 都没有** ⇒ 新类无 `@OperateLog`。
   ★ 老 `@NoAuth` 的响应流导出按 §3.3 第 1 类**收紧**为
     `order:arrearage-static:export`(无 token 401)。
   ★ ⏸ 3 个 `...ForAjax[New]`/`...ForParkAjax` 导出挂起:模板
     `ArrearageReportTemplate.xlsx` / `ArrearageReportTemplate4Park.xlsx` 未随迁,
     形状是「写临时文件 → FastDFS 上传 → 回 URL」,属 §4.3 待定端点;
     **别硬搬成响应流**(会改掉老契约)。
   ★ 存量语义照搬:两个列表端点(含 New 的非 vip/非来源分支)走
     `queryArrearListForParkOrCarNumber`,其 SQL 撞本地 MySQL 8
     `only_full_group_by` ⇒ **500「系统异常」**(老栈同 SQL 同样 500,红线⑥不修);
     New 的 vip/parkSource 分支走 `...WithVipCard`,本地活着(窗口内 0 行)。
   ★ ⛔ 全类只读/导出,无写端点;`system_operate_log` 零写基线不变。
★★ **2026-10-19 第一百三十二切片:⚠B 里的 `TVipTimeCardController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 28/102 → 27/99、已迁 179/1009 → 180/1012、未迁 32/109 → 31/106**。
   ★ 落在 `bill` 域(`/admin-api/bill/vip-time-card/*`),老类在 `portal.web.viptime` 包、
     老路由 `/tVipTimeCardManage`;**2 读 + 1 写**、无导出、无 `@NoAuth`。
   ★ 与已迁的 `/bill/vip-area-card` 共用 `TVipAreaCardService`,本类强制 `type=2`。
   ★ 坑 22 安全:只写 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 真写风险:`create` 服务层无校验、`{}` 会真插一行 `t_vip_area_card`
     ⇒ 冒烟只发无 body(400)。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百五十五切片:上片新进池的 3 个类一次搬完(11 端点)**
   ⇒ **已迁 204/1113 → 207/1124、未迁 15/42 → 12/31、✔ 6/15 → 4/7、
     ⚠A 1/3 → 0/0(再次清零)、⚠B 8/24 不变、缺口 8/55、死代码 5。**
   ★ `PersonPCAcctController` 3/3(`/admin-api/pay/person-pc-acct/*`):
     ⚠A **机械误报核销** —— 第 93 行 `acctRequest.setOrgId(request.getOrgId())`
     打在**请求体**上,全类不读登录态;新类不注入 `SessionCommUtil`。
   ★ `AppUserAccountController` 3/3(`/admin-api/pay/app-user-account/*`):
     1 读 + 2 写;老 `@SysOpLog` ×2 ⇒ `@OperateLog` ×2。`delectUserCar`(老拼写)
     → `delete-user-car`,按主键软删;`updateAccountFreezeState` 的冻结流水插入
     **无条件**(不看 update 结果)⇒ 冒烟只能发零字节/坏 JSON/GET,**不能发 `{}`**。
   ★ `UserRechargeRefundController` 5/5(`/admin-api/order/user-recharge-refund/*`):
     3 读 + 1 写 + 1 导出;导出老方法名误称 `excleImport…`、老 `@NoAuth` 按 §3.3
     第 1 类收紧为 `:export`;`sysCode` 硬编码 `"1001"`、`pageSize=0`、
     空对象兜底、provider 空 data 会 NPE 等存量缺陷照搬。
   ★ 坑 22:读端点/导出零登录态;写端点只拿 `opId`/`name` 写审计列。
   ★ 零写基线上移 **+224/轮 → +226/轮**(新增 2 条 `@OperateLog` 留痕);
     新增监控表 `order_refund` / `td_c_cust_person_freeze`(均一行不动)。
★★ **2026-10-19 第一百五十六切片:⚠B 的 2 个类一次搬完(12 端点)**
   ⇒ **已迁 207/1124 → 209/1136、未迁 12/31 → 10/19、✔ 4/7 不变、
     ⚠B 8/24 → 6/12(⚠A 仍 0/0)、缺口 8/55、死代码 5。**
   ★ `OrderRefundController` 5/5(`/admin-api/order/order-refund/*`):
     3 读 + 1 写 + 1 导出;两个读端点把登录 `orgId` 写进查询(null 抛
     `AUTH_TOKEN_NOT_EXISTS`),`plNos` 只出现在导出 query 参数里(不是登录态);
     导出老 `@NoAuth` 按 §3.3 第 1 类收紧为 `:export`。
     存量缺陷照搬:补缴分支 `split("-")[1]` AIOOBE、`payOrderid == null` 时
     `.toString()` NPE;`{}` 打写端点先炸 1003。
   ★ `AccountRechargeStatisController` 7/7(`/admin-api/pay/account-recharge-statis/*`):
     6 读 + 1 导出、**零写**、老类零 `@SysOpLog`;前两个读端点 `null != userInfo`
     才设 `orgId`(null 不抛错,与 OrderRefund 相反);导出老 `@NoAuth` 收紧。
     本机两条端点恒 500 = 存量 `group by ... desc` 撞 MySQL 8(老栈同坏)。
   ★ 坑 22:两类命中都只读登录 `orgId`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 零写基线上移 **+226/轮 → +228/轮**(新增 1 个写端点 `@OperateLog` ×2 条留痕);
     本片没有新增被写表。
★★ **2026-10-19 第一百五十七切片:⚠B 的 2 个类一次搬完(8 端点)**
   ⇒ **已迁 209/1136 → 211/1144、未迁 10/19 → 8/11、✔ 4/7 不变、
     ⚠B 6/12 → 4/4(⚠A 仍 0/0)、缺口 8/55、死代码 5。**
   ★ `StoreCarManageController` 7/7(`/admin-api/pay/store-car-manage/*`):
     4 读 + 3 写(开通/销户/现金充值)、零导出;老类 3 处 `@SysOpLog` ⇒ 3 条
     `@OperateLog`(CREATE / DELETE / UPDATE)。
     读端点 `null == userInfo` 抛 `AUTH_TOKEN_NOT_EXISTS`;开通/销户/充值只写
     `orgId`/`sOpId`/`sOpName` 等审计列。
   ★ 真写风险:`open-store-car-acctount {}` 会**真插**个人账户(custId=null)⇒
     探针/冒烟只发无 body/坏 JSON/GET;`close-store-car-account` 只发不存在的
     `custId`;`account-store-car-recharge-by-xj {}` 在取值处 NPE,早于任何写入。
   ★ 本机 `query-acct-person-for-page` 恒 500 = 存量
     `TdCAcctPersonSMapper.queryAcctPersonStoreCarForPage` 的 `GROUP BY a.cust_id`
     却 SELECT 非聚合列 `b.acct_id` 等,撞 MySQL 8 `only_full_group_by`(老栈同坏,
     照红线⑥不修)⇒ 冒烟断言 500 并留注释。
   ★ `AppointmentManageController` 1/1(`/admin-api/order/appointment-manage/*`):
     1 读、零写零导出;老方法名 `queryAcctPersonForPage`(复制残留)、mapping 才是
     `queryOrderAppointmentForBcpPage` ⇒ 新方法名按 mapping 恢复。
   ★ 坑 22:两类只读登录 `orgId`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 同片判定 `ParkLotStatisticController` **不可搬**:它读登录 `plNos` 且为空抛
     `AUTH_TOKEN_NOT_EXISTS`,属 P3 未落地 ⇒ 继续挂起。
   ★ 零写基线 **+228/轮 → +231/轮**(新增 3 个写端点埋点,`system_operate_log`
     每轮各多 1 行);本片没有新增被写表。
★★ **2026-10-19 第一百五十八切片:⚠B 的 `RemoteControlDzController` 1/1 搬走**
   ⇒ **已迁 211/1144 → 212/1145、未迁 8/11 → 7/10、✔ 4/7 不变、
     ⚠B 4/4 → 3/3(⚠A 仍 0/0)、缺口 8/55、死代码 5。**
   ★ 老类 `dzcontroller/RemoteControlDzController`(老路由 `/dzcontroller`),
     新路由 `/admin-api/park/remote-control-dz/*`;老类 1 处 `@SysOpLog`
     ⇒ 新类 1 条 `@OperateLog`(UPDATE)。
   ★ 坑 22:只读登录 `orgId`/`loginCode`/`opId` 写 `api_log_request`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ ⛔ 真副作用:`sourceType == 7`(红门)会**真实开闸 + 插 API 调用日志**;
     安全样本 `P15040214Z` 在本机 Redis 缓存里 `sourceType=17` ⇒ 走 else,
     在**外呼与写库之前**抛「该车场暂不支持远程开闸」(存量错误码 1007)。
   ★ 存量 NPE 照搬:`getParkLotByPlNo(null/不存在)` 返回 null 后直接
     `parkLot.getSourceType()` ⇒ `{}` / 假 plNo 都是 500「系统异常」(红线⑥)。
   ★ HTTP 工具改用 park 模块已有的 `com.zteits.park.util.HttpClientTutorial`
     (与老门户同名同签名);两份 portal VO 原样搬进
     `controller/admin/remotecontroldz/vo/`。
   ★ 零写基线 **+231/轮 → +234/轮**(3 条带 token 的 §2ex 调用触发 `@OperateLog`);
     本片没有新增被写表(红门分支会写 `td_b_api_log`,该表历批已在监控内,本片不可测)。
★★ **2026-10-19 第一百五十四切片:先修实现识别,再搬 `ChargerAccountController` 6 端点**
   ⇒ **已迁 203/1107 → 204/1113、未迁 8/11 → 15/42、✔ 4/7 → 6/15、
     ⚠A 0/0 → 1/3、⚠B 4/4 → 8/24、缺口 16/92 → 8/55、就绪 211/1118 → 219/1155。**
   ★ 本片**先修工具再搬类**:`IMPL_PAT` 只认 `class X implements Y`、漏掉
     `class X extends Base implements Y`;新版实现类只 glob `*Impl.java`、漏掉
     `RefundServiceIpml.java`(存量把 Impl 拼成 Ipml)⇒ 8 个 Controller / 37 端点
     被误判成「无实现」挡在缺口档:StoreCarManage(7)、AccountRechargeStatis(7)、
     ChargerAccount(6)、UserRechargeRefund(5)、OrderRefund(5)、PersonPCAcct(3)、
     AppUserAccount(3)、AppointmentManage(1)。总数 232/227/5 不变。
   ★ `ChargerAccountController` 落在 `pay` 域(`/admin-api/pay/charger-account/*`),
     老类在 `operationsmanage/chargemanage` 包、老路由 `/chargerAccount`;
     **4 读 + 2 导出**,零写、零 `@SysOpLog`。
   ★ 坑 22 安全:6 个端点**零登录态**(`custIds` 由前端下发);新类不注入 `SessionCommUtil`。
   ★ 两条导出老 `@NoAuth` 按 §3.3 第 1 类收紧为 `:export`;一个类两个导出 ⇒
     `/export-excel` + `/export-pay-out-excel`。
   ★ 照搬:`acctTypeList` 无条件覆写、双重循环回填收费员名且不判空、
     支出回填两次且退费单命中时 `berthNo=""`、停车查询用 `0001_SYS` 而退费/收费员用请求 `sysCode`、
     导出 `BaseInfo(1, MAX)` + `DateUtil.to_date` + `rechargeType` 1/2/5/6/未知 + `/100` ROUND_HALF_UP。
   ★ 本机样本:`acct_type=6` 充值 2 行(统计 110000/2/0/0)、支出 0 行、
     `tp_e_employee_charge` 0 行 ⇒ `custName` 恒 null;支出两次回填无正样本。
   ★ 零写库;零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百五十三切片:⚠B 里的 `RealTimeInfoController`(3 端点只落 2 个)搬走**
   ⇒ **⚠B 5/7 → 4/4、已迁 202/1104 → 203/1107、未迁 9/14 → 8/11**。
   ★ 落在 `park` 域(`/admin-api/park/real-time-info/*`),老类在
     `parkinglotcloudplatform/monitor` 包、老路由 `/rtinfo`;**2 读**、零导出、零 `@SysOpLog`。
   ★ 未迁 `berthinfo`:真读 `userInfo.getUserAreas()` 做数据权限过滤(P3)。
   ★ 已迁两条**零登录态**:`escapePaymentInfo` 把请求原样透传;`warninginfo` 里的
     `//userInfo.getUserAreas()` 在存量源码里就是注释掉的(「权宜之策构造」按 `cityId` 现造
     `UserArea`)⇒ 不算登录态读取,别"顺手恢复"。
   ★ `escape-payment-info` 本机**恒 500**:`RealtimeInfoSMapper.xml` 的 namespace 写成
     `com.zteits.park.mapper.smapper...`、接口在 `com.zteits.pay.mapper.smapper...`
     ⇒ `Invalid bound statement`;**老仓同一份 XML 同样写错**(两栈同坏、红线⑥照搬不修),
     `p2_audit_mapper_bind.py` 归 B 类「存量就断」。
   ★ `warning-info` 本机 `free_berth=0` 与 `parking_lot_geo(geo_type=1)` 的连接结果为 0
     ⇒ 恒返回空数组(样本缺口,不是过滤错)。
   ★ 零写库;零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百五十二切片:⚠B 里的 `ChargerTicketController` 3 端点全迁**
   ⇒ **⚠B 6/10 → 5/7、已迁 201/1101 → 202/1104、未迁 10/17 → 9/14**。
   ★ 落在 `pay` 域(`/admin-api/pay/charger-ticket/*`),老类在 `web.ticket` 包、
     老路由 `/chargerTicket`;**1 读 + 2 写**、零导出、零 `@SysOpLog`。
   ★ 坑 22 安全:只写 `orgId`/操作人,不读 `plNos`/`userAreas`/`resIds`;
     老代码三处**都不判** `userInfo == null`,新栈由安全过滤器先挡(照搬不补判空)。
   ★ 两处预览都用**写死的样例 DTO**(收费员 0050001/张三、京A88888 等),
     渲染后 `replaceAll("null","-")` 并把 `&&&&` 切成 `ticketTextUp/Down` + `hasPic`,
     但正文**仍保留 `&&&&`**(只切不删)。
   ★ 照搬顺序:`save` 先雪花号(内存)再五项判空;`open` 先按条件把旧模板
     `ticket_state=0`、再启用当前模板(返回第二步结果)。
   ★ 真写风险:`save` 带齐 `configRequests` 会真插配置+预览模板;
     `open` 带真实 `ticketNo` 会真改两批行(本地 `charger_ticket_template` 81 行)。
     冒烟只发无 body / `{}` / 标题-only / 不存在模板号;零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百五十一切片:⚠B 里的 `ArrearageCallConfigController` 4 端点全迁**
   ⇒ **⚠B 7/14 → 6/10、已迁 200/1097 → 201/1101、未迁 11/21 → 10/17**。
   ★ 落在 `order` 域(`/admin-api/order/arrearage-call-config/*`),老类在
     `web.arrearangecall` 包、老路由 `/arrearageCallConfig`;
     **1 读 + 3 写**、零导出、零 `@SysOpLog`。
   ★ 坑 22 安全:分页条件用的是**请求体** `plNos`(`sql.andPlNoIn(request.getPlNos())`),
     控制器写的 `orgId` 在 DAO 里**根本没被使用**;save/update 只写操作人,delete 老代码不读登录态。
   ★ 存量形状:`plNos=null` ⇒ Example `addCriterion` 抛 RuntimeException;
     `plNos=[]` ⇒ `and pl_no in ()` SQL 语法错 ⇒ 两条都是 500「系统异常」。
     分页**必须显式带非空 plNos**,别顺手改成"不传就查全部"。
   ★ 其它照搬:`callMaxValue` 条件错用 `getCallMinValue`;update 先写操作人/时间再判 id;
     delete 是**物理删除**(不是 `data_state=0`)。
   ★ 真写风险:`save` 带非空 `parkLontDTOs` 会真插行(本地 239 行)
     ⇒ 冒烟只发无 body / `{}` / `{"parkLontDTOs":[]}`;零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百五十切片:⚠B 里的 `BalanceManagementController`(5 端点只落 3 个)搬走**
   ⇒ **⚠B 8/19 → 7/14、已迁 199/1092 → 200/1097、未迁 12/26 → 11/21**。
   ★ 落在 `order` 域(`/admin-api/order/balance-management/*`),老类在
     `web.financialmanagement.clearsettlement` 包、老路由 `/BalanceManagement`;
     **1 写 + 2 导出**;另 2 个分页端点**有意不迁**
     (`plNos` 空或 `"ALL"` 时回落 `userInfo.getPlNos()` = P3 数据权限)。
   ★ 坑 22:已迁 3 条**都不读登录态**(写只吃 body、导出只吃 7 个 query 参数)⇒ 可开搬。
   ★ 两条导出老 `@NoAuth`,按 §3.3 第 1 类收紧为需登录;
     权限 `order:balance-management:update` / `order:balance-management:export`。
   ★ **本地服务层是空壳**(`BillReconciliationFor*ServiceImpl` 的 DAO 调用全注释)
     ⇒ 导出只有表头、写恒回 `false`;换环境接回 DAO 后冒烟 `dataRows == []` 会红,
     那是功能恢复、不是回归。
   ★ 写端点冒烟只发空 body / 坏 JSON(绑定层 400),`@OperateLog` 不触发;
     零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十九切片:⚠B 里的 `ParkSourceController`(6 端点只落 3 个)搬走**
   ⇒ **⚠B 9/25 → 8/19、已迁 198/1086 → 199/1092、未迁 13/32 → 12/26**。
   ★ 落在 `park` 域(`/admin-api/park/park-source/*`),老类在
     `parkinglotcloudplatform.parklotmanage` 包、老路由 `parkSource`;
     **3 读、零写、零导出、零 `@SysOpLog`**;另 3 个端点**有意不迁**
     (统计 / 员工总数 / 设备总数都读 `userInfo.getPlNos()` = P3 数据权限)。
   ★ 坑 22:三个已迁端点**都不读登录态**(两条分页只吃请求体 `plNos`、
     单车场只吃 `plNo`)⇒ 可开搬。
   ★ 三个端点统一挂 `park:park-source:query`;老类无 `@NoAuth`。
   ★ `plNos=null` 时存量 SQL 的 `<if>` 全不生效 ⇒ 车场扫全部 `data_status=1` 行
     (本机 4 行);每行 `deviceList` 恒 3 项(PDA/地磁/地锁)、`deviceTotal` 走
     `Integer` 拆箱;单车场端点查不到车场时服务层回**非 null 空 DTO**。
   ★ 单车场端点 `{}` → **500「系统异常」**:`plNo=null` 时缓存工具直接炸
     (存量缺陷照搬);员工统计本机 `tp_e_employee_charge` 0 行 ⇒ 恒空页。
   ★ ⚠️ 盘点脚本按**类**判已迁 ⇒ 本片 6 个端点都记进"已迁"、⚠B 整类核销,
     但**交付口径是 3/6**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十八切片:⚠B 里的 `AnomalyStatisticsController`(4 端点只落 2 个)搬走**
   ⇒ **⚠B 10/29 → 9/25、已迁 197/1082 → 198/1086、未迁 14/36 → 13/32**。
   ★ 落在 `order` 域(`/admin-api/order/anomaly-statistics/*`),老类在
     `portal.web.report.income` 包、老路由 `income/anomalyStatistics`;
     **2 读/导出、零写、零 `@SysOpLog`**;另 2 个端点**有意不迁**
     (`parkIds` 为空时回落 `userInfo.getPlNos()` = P3 数据权限)。
   ★ 坑 22:两个已迁端点**都不读登录态**(明细只吃请求体;导出只吃 4 个 `@RequestParam`)
     ⇒ 可开搬。
   ★ 老导出 `@NoAuth` 按 §3.3 第 1 类收紧为 `GET /export-excel` + `:export`;
     明细分页 `POST /query-sealed-parking-detail-report-for-page` + `:query`。
   ★ 明细 `QuerySealedParkDetailReportRequest` 自带 `@NotBlank parkId` /
     `@NotBlank paymentTime` / `@NotNull queryType` ⇒ `{}` 是绑定层 400。
   ★ 老导出整个方法体包在 `try/catch` → 只 `printStackTrace`(新栈 `log.error`):
     `queryType=0` 撞本机 MySQL 8 `only_full_group_by` 时是 **HTTP 200 + 空 body**,
     不是新栈 500;文件名/sheet/10 列表头逐字照搬,`sysCode="1001"`、
     `BaseInfo(1,0)`、金额 ÷100、null → `""`、「封顶放行(次)」恒空串。
   ★ 月口径入参必须是 `yyyy-MM`(服务层补 `-01`);本机 `P1101020Z7` + `2018-02-01`
     有 10 单、汇总 9400 分 ⇒ xlsx 行 `94.0/94.0`。
   ★ ⚠️ 盘点脚本按**类**判已迁 ⇒ 本片 4 个端点都记进"已迁"、⚠B 整类核销,
     但**交付口径是 2/4**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十七切片:⚠B 里的 `SelectItemController`(8 端点只落 2 个)搬走**
   ⇒ **⚠B 11/37 → 10/29、已迁 196/1074 → 197/1082、未迁 15/44 → 14/36**。
   ★ 落在 `bill` 域(`/admin-api/bill/select-item/*`),老类在
     `parkinglotcloudplatform.datastatistic` 包、老路由 `/selectItem`;
     **2 读、零写、零导出**;另 6 个端点**有意不迁**(都读 `userInfo.getPlNos()`)。
   ★ 坑 22:两个已迁端点**都不读登录态**(会员卡全量分页 + 前端参数 `noAll`)⇒ 可开搬。
   ★ 老接口是 `void` + `response.getOutputStream().write(JSONArray…)` 裸数组;
     新栈统一 `CommonResult<List<SelectItemVO>>`,`SelectItemVO` 与老
     `com.zteits.bcp.portal.common.SelectItem` 字段一一对应,`equals`/`hashCode`
     只比 `code`/`label`/`userDefined`/`value`(老代码 `list.contains` 去重)。
   ★ `noAll` 缺失时照搬老代码 `noAll.equals("yes")` 的 **NPE → 500**;
     `pageSize=0` 取全量靠 `page-size-zero: true`;`sysCode` 是字面量 `"1001"`。
   ★ 本机 `t_vip_card where data_state=1` **427 行**(card_type 1~7 全非空)⇒
     `noAll=yes` 返回 8 条类型 / 428 张卡;`noAll=no` 返回 7 / 427。
   ★ ⚠️ 盘点脚本按**类**判已迁 ⇒ 本片 8 个端点都记进"已迁"、⚠B 整类核销,
     但**交付口径是 2/8**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十六切片:⚠B 里的 `PersonPCIndexController`(5 端点只落 2 个)搬走**
   ⇒ **⚠B 12/42 → 11/37、已迁 195/1069 → 196/1074、未迁 16/49 → 15/44**。
   ★ 落在 `pay` 域(`/admin-api/pay/person-pc-index/*`),老类在 `portal.web.personpc` 包、
     老路由 `/personPc/index`;**2 读、零写、零导出**;另 3 个端点**有意不迁**。
   ★ 坑 22:两个已迁端点**都不读登录态**(`custId` 来自请求体、组织查询不读身份)⇒ 可开搬。
   ★ 不迁的 3 个(`getCustIndexBaseInfo` / `queryOwnerParkRecord` /
     `pageQueryArrearRecordByCondition`)都走 `getUserInfoForPersonPC()` 读 HTTP session,
     新栈不建 session ⇒ 恒 1003,记为「等个人客户登录桥」
     (与 `PersonPCCarNumController` 同口径)。
   ★ `query-owner-pay-out-by-month`:控制器不校验 `custId`(服务层抛 1003);
     `beginTime`/`endTime` 缺一则改「去年今天 ~ 本月月末」并补满 13 个月零值。
   ★ `select-all-app-org`:**无 `@RequestBody`**,DAO 过滤 `data_state=1`
     (本机 14 行 → 12 行),服务层 Redis 缓存优先(读端点里的 Redis 写)。
   ★ ⚠️ 盘点脚本按**类**判已迁 ⇒ 本片 5 个端点都记进"已迁"、⚠B 整类核销,
     但**交付口径是 2/5**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十五切片:⚠B 里的 `IndexDeviceController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 13/45 → 12/42、已迁 194/1066 → 195/1069、未迁 17/52 → 16/49**。
   ★ 落在 `park` 域(`/admin-api/park/index-device/*`),老类在 `portal.web.index` 包、
     老路由 `/index/device`;**3 读、零写、零导出**。
   ★ 坑 22:**只有第 1 个端点**读登录态回填 `orgId`(`getUserInfo()` 为空抛
     `AUTH_TOKEN_NOT_EXISTS`);第 2、3 个老代码不读登录态,照搬。
     `plNos` 全来自前端,`userAreas`/`resIds` 不碰 ⇒ 可开搬。
   ★ 第 1 个 `eqpTypes=[ALL(-1)]` 由服务层展开成 GEO+PDA,每类型回 3 个 VO
     (在线 0 / 离线 1 / 故障 2),`map` 空回**空列表**。
   ★ 第 2 个 `eqpType` **无校验**(`{}` ⇒ `eqpTypes=[null]` 空结果),4 个计数 `Integer` 拆箱累加。
   ★ 第 3 个 `@Valid`(`plNos` `@NotEmpty`、`eqpType` `@NotNull` ⇒ 400;两字段同违时
     Bean Validation **不保证先报哪个**);`PageResult.total` = 地磁分页 total + PDA 分页 total,
     `id` 左补零 10 位;两个分支各自 `pageBean==null` 提前返回空页(PDA 分支会丢弃已收集的地磁行)。
   ★ **本地样本**:`tp_p_eqp` 与 `parking_lot` **零 JOIN** ⇒ GEO 分支统计/分页恒空(样本缺口,
     已直连 SQL 复核);PDA 正样本 `P150402254`(34 行),登录人会话 `orgId=10003`。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十四切片:⚠B 里的 `IncomeBerthReportController`(3 端点,2 迁 + 1 挂起)搬走**
   ⇒ **⚠B 14/48 → 13/45、已迁 193/1063 → 194/1066、未迁 18/55 → 17/52**。
   ★ 落在 `order` 域(`/admin-api/order/income-berth-report/*`),老类在
     `portal.web.berthreport` 包、老路由 `/incomeBerthReport`;**2 读 + 1 导出**。
   ★ 坑 22 安全:两个读端点都照搬 `getUserInfo().getOrgId()` 且**不判空**
     (新栈 `@PreAuthorize` 先挡无 token),`plNos` 全来自前端,`userAreas`/`resIds` 不碰。
   ★ 请求 DTO **无 `@Valid`** ⇒ 三条手工校验:空 `plNos` → 1003、开始/结束时间缺失 → 1003、
     跨度 >31 天 → 1003;日期字段只吃 `yyyy-MM-dd HH:mm:ss`(`yyyy-MM-dd` 是 400)。
   ★ 分页老 `returnJqGridData` → `PageResult`;汇总 SQL 全列 `ifnull(sum(...),0)`
     ⇒ 假车场回**全 0 DTO**。
   ★ **DAO 除零风险照搬**:守卫只看 `parkDurationPay==0`,`0<parkDurationPay<3600`
     时 `totalFee / (parkDurationPay/3600)` 除零 ⇒ 500(存量缺陷,红线⑥不修)。
   ★ ⏸ `exportIncomeBerthReport` **挂起**:EasyExcel 写临时文件 → FastDFS 上传 → 回 URL,
     新栈运行期无存储后端;`handleData`/`getInitDTO`/`getExcelHeader`/`fen2yuan` 一并挂起。
   ★ **本地样本**:`income_berth_report` 500 行全部停在 `2020-07-07`,唯一非零行
     `A320211000`(`berthNum=2800`、`totalFee=716200`)。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十三切片:⚠B 里的 `DurationParkController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 15/52 → 14/48、已迁 192/1059 → 193/1063、未迁 19/59 → 18/55**。
   ★ 落在 `order` 域(`/admin-api/order/duration-park/*`),老类在
     `portal.web.report.duration` 包、老路由 `/duration/durationPark`;
     **3 读 + 1 PDF 导出、零写端点**。
   ★ 坑 22 安全:汇总读登录态**只读 `orgId`** 回填 `orgName`,且取不到登录态时
     **静默跳过**(老代码没有 `else` 抛 401 —— 别照抄收费员收入那一类);
     `plNos` 全来自前端,`userAreas`/`resIds` 不碰。
   ★ `@Valid` **不是空转**:`DurationParkReq.reportDate`/`reportTabTag` 有 `@NotNull`
     ⇒ `{}` 是 400;空 `plNos` 在汇总/分布是 1003,在分页是空 `in ()` 的 500。
   ★ 未知 tab:汇总返回**长度 1 的 orgName 空 Bean**(空结果被 `add`)、分布 `[]`、
     分页空页;`tag=2`(周)仍查日表(存量注释掉周表分支)。
   ★ 老 `@NoAuth` 的 PDF 导出按 §3.3 收紧为 `GET /export-pdf` + `order:duration-park:export`;
     方法体整个包在 try/catch ⇒ 日期错是 **200 + 0 字节**;假车场仍出 `%PDF-`;
     `plNos=` 空列表的 1003 在 try 之外 ⇒ 500 JSON。
   ★ **本地样本**:日表 500 行最大 `2018-06-06`(`A150402003` 路侧 430 泊位 /
     `P1101020EG` 封闭 335 泊位),月表 500 行最大 `2018-05`。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十二切片:⚠B 里的 `IncomeParkSourceController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 16/56 → 15/52、已迁 191/1055 → 192/1059、未迁 20/63 → 19/59**。
   ★ 落在 `order` 域(`/admin-api/order/income-park-source/*`),老类在
     `portal.web.report.income` 包、老路由 `/income/incomeParkSource`;
     **2 读 + 2 导出(PDF + xlsx)、零写端点**。
   ★ 坑 22 安全:汇总端点读登录态**只读 `orgId`** 回填 `orgName`(P3 已解禁),
     拿不到登录态抛 401(与 `IncomeParkAndVipController` 的"跳过"不同,照搬老代码);
     分页与两条导出完全不读登录态,`plNos`/`sourceCodes`/`orgId`/`userName` 全来自前端。
   ★ 两条老 `@NoAuth` 导出按 §3.3 收紧:xlsx 占 `GET /export-excel`、
     PDF 落 `GET /export-pdf`(先例 `IncomeStatisticController`)。
   ★ **同一类里两条导出的失败形态不同**:PDF 整个方法体 `try/catch(Exception)` 吞异常
     ⇒ 坏日期 = **200 + 0 字节 + `application/pdf`**;xlsx 无 try/catch ⇒ **500 JSON**。
   ★ 汇总端点返回的是**服务结果本身**(不是方法体里回填用的两元素 `list`);
     Service 对空结果会塞 `plType=1/2` 两行 0 值 ⇒ `tag=1` 恒 2 行;未知 tab ⇒ `code=0 + []`。
   ★ **本地样本事实**:`income_statistic_park_source_day` 的 `pl_no` 与 `parking_lot`
     **完全不重叠**(日表最大 `2018-06-04`)⇒ `A150402003` 验"有收入无车场"、
     `B1504020C7` 验"有车场无收入",不能合并断言。
   ★ `reportTabTag=3` 传 `yyyy-MM-dd` 不报错(hutool3 容错),别写 500 断言;
     `reportDateEnd` 日/周/月分支用不到但老签名必填(缺 ⇒ 400)。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十一切片:⚠B 里的 `IndexRealTimeController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 17/61 → 16/56、已迁 190/1050 → 191/1055、未迁 21/68 → 20/63**。
   ★ 落在 `park` 域(`/admin-api/park/index-real-time/*`),老类在 `portal.web.index` 包、
     老路由 `/index/new`;**4 读 + 1 导出、零写端点**。
   ★ 坑 22:只有 `query-employee-num-by-pl-no` / `query-parkinglot-basic-facts-by-pl-nos`
     从登录态回填 `orgId`(**只读 orgId,P3 已解禁**);另两条老代码不读登录态;
     `plNos` 全来自请求体 ⇒ 不碰 `plNos`/`userAreas`/`resIds`。
   ★ 实时汇总/列表空 `plNos` ⇒ 1003;本机缓存冷 ⇒ 汇总全 0 Bean(`plNo=null`)、列表 `[]`。
   ★★ **踩到跨切片环境坑**:列表端点传**真实车场**会异步写 `PARK_REAL_TIME_STATUS<plNo>`
     到本机 Redis,**无 TTL、跨轮次常驻**,会把 §2ak「DTO 字段全 null」与
     `park-detail-info-by-org-ids → 500` 两条老断言永久打红。冒烟只准用不存在的 plNo
     (本片 `ZZZNOPE`,预热在 `getParkLotByPlNo` 返回 null 处 NPE 被 catch)。
   ★ 车场概况:`@Valid @NotEmpty` 先于老手写判空 ⇒ 空 body **400**(1003 不可达);
     有效 ⇒ `total=plNos.size()`;不存在车场 ⇒ `total=0, list=[]`(老 `data=null` 口径)。
   ★ 老导出 `@NoAuth`(`/exportParkinglotBasicFactsByPlNos`,不限方法)按 §3.3 收紧为
     `GET /export-excel` + `park:index-real-time:export`。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百四十切片:⚠B 里的 `InvoiceController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 18/66 → 17/61、已迁 189/1045 → 190/1050、未迁 22/73 → 21/68**。
   ★ 落在 `pay` 域(`/admin-api/pay/invoice/*`),老类在 `portal.web.invoice` 包、老路由 `/invoice`;
     **4 读 + 1 导出、零写端点**。
   ★ 坑 22 安全:4 读 + 导出只写 `orgId`(统计端点保留「非超管才覆盖」);
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ ★ `query-invioce-by-vo`(老 `queryInvioceByVo` 的 `Invioce` 是存量错拼,路由逐字 kebab)
     与 `query-cust-invoice-his-for-page` 都是真分页:`data_state=1` + 登录 orgId=10003 ⇒ 本机 406 行;
     `custName=赤峰` → 168。
   ★ ★ 分页**逐行 N+1 照搬**(`selectByCustId` + `queryCustInvoiceHisDetailList`):
     本机 `td_c_cust_person` 0 行 ⇒ `recipientsPhone=""`;明细未挂 org=10003 且 `car_number` 全 NULL
     ⇒ `carNumbers=null`。别改批量。
   ★ 详情死主键 / `id=null` ⇒ `code=0 + data=null`。
   ★ 老导出 `@NoAuth`(`/exportCustInvoiceHisList`,不限方法)按 §3.3 收紧为
     `GET /export-excel` + `pay:invoice:export`;坏日期 `ParseException` ⇒ 500(存量无包装)。
   ★ 老统计端点 `@NoAuth` 但方法内自校验登录 + 超管组织不覆盖 orgId;新栈外层加 `:query`,
     保留老 bean 绑定(不加 `@RequestBody`);custName/saleName 空 ⇒ 1003;
     两者都给 ⇒ 存量 `selectRateData` 撞 MySQL 8 `only_full_group_by` ⇒ 500(红线⑥不修)。
   ★ 补齐 `parking-module-api` 的 `TdCCustInvoiceHisQueryRequest`:老仓有 `carNumbers`/`custPhone`/
     `custEmail` 三字段,新栈漏搬,本片补回(纯追加)。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十九切片:⚠B 里的 3 个类一次搬完**
   (`TdBSmsRecordController` 4 + `OrderParkBjxcController` 4 + `OrderParkBjxcSuccessController` 4)
   ⇒ **⚠B 21/78 → 18/66、已迁 186/1033 → 189/1045、未迁 25/85 → 22/73**。
   ★ 全部落在 `order` 域:
     `/admin-api/order/sms-record/*`(老 `/tdBSmsRecordManage`,类路由去 `TdB` 前缀,先例 `TdBPayApp`)、
     `/admin-api/order/order-park-bjxc/*`(老 `/orderParkBjxcManage`)、
     `/admin-api/order/order-park-bjxc-success/*`(老 `/orderParkBjxcSuccessManage`);
     三个类都是 **1 读 + 3 写**、无导出、无 `@NoAuth`、**零** `@SysOpLog` ⇒ 零 `@OperateLog`。
   ★ 坑 22 安全:只写 `orgId`/`sOpId`/`createEmp*`/`modifyEmp*`/`opId`/`opName`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ `TdBSmsRecord` 分页**真查询**:DAO 按登录组织(admin 会话 orgId=10003)+ `data_state=1`
     过滤 ⇒ 本机 total=500;`carNumber=蒙D7Y077` → 1、`sendType=10,sendState=20` → 466。
   ★ 北西城两个类的分页服务层把 DAO 调用整段注释 ⇒ **恒空页**(存量 TODO,别补 SQL);
     本机也**没有** `order_park_bjxc_success` 表(delete → 501)。
   ★ ★ `OrderParkBjxc` 批量删除 DAO 的 `record.set*` 全被注释 ⇒ `updateByExampleSelective`
     生成空 SET ⇒ **删除恒 500**;`OrderParkBjxcRequest` 无 `id` 字段 ⇒ 按主键改恒 `where id=null`。
     `OrderParkBjxcSuccessRequest` 与 domain 只共享 `id` ⇒ update 恒空 SET(500)。均照搬。
   ★ 真写风险:三个 save 都无校验 ⇒ 冒烟只发无 body(400);其余只发手写 1003 / 0 行分支。
   ★ 零写基线 **+224/轮** 不变(`td_b_sms_record` 500、`order_park_bjxc` 0 实测不动)。
★★ **2026-10-19 第一百三十八切片:⚠B 里的 `TdCAcctPersonChangeController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 22/82 → 21/78、已迁 185/1029 → 186/1033、未迁 26/89 → 25/85**。
   ★ 落在 `pay` 域(`/admin-api/pay/acct-person-change/*`),老类在 `portal.web.appservice` 包、
     老路由 `/tdCAcctPersonChangeManage`;**1 读 + 3 写**、无导出、无 `@NoAuth`。
   ★ 坑 22 安全:只写 `orgId`/`sOpId`/`createEmp*`/`modifyEmp*`/`opId`/`opName`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ ★★ 分页**恒空页**:老 DAO 的 `queryTdCAcctPersonChangeForPage` 整段被注释
     (`//TODO 编写smapper实现`),服务层直接 `return new PageBean<>()` ⇒
     任何入参都是 `total=0` + `list=[]`,本机表里有 5 行也看不到(存量形状,别补 SQL)。
   ★ ★ `update` 入参 DTO 只有 `id/orgId/create*/modify*`;拷到 domain 后能进 `<set>` 的
     只有 `orgId` ⇒ `{"id":999999999}` 生成空 SET(500 系统异常),
     `{"id":999999999,"orgId":10003}` 才返回 `0 + false`。存量缺陷照搬。
   ★ 真写风险:`save` 无校验、`{}` 会走 insert(本机三列 NOT NULL 兜底)⇒ 冒烟只发无 body。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十七切片:⚠B 里的 `TdCStoreCarController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 23/86 → 22/82、已迁 184/1025 → 185/1029、未迁 27/93 → 26/89**。
   ★ 落在 `pay` 域(`/admin-api/pay/store-car/*`),老类在 `portal.web.appservice` 包、
     老路由 `/tdCStoreCarManage`;**1 读 + 3 写**、无导出、无 `@NoAuth`。
     类路由按「去掉表前缀 `TdC` 再 kebab」定(先例 `TdCPresentRule` → `/pay/present-rule`)。
   ★ 坑 22 安全:只写 `orgId`/`sOpId`/`modfiyEmpid`/`opId`/`opName`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ ★ 分页 DAO **不按 `orgId` 过滤**(只认 `carNumber`/`custId`/`data_state`)——
     控制器里的 `setOrgId` 不生效,本机 total=3、能看到 org=10005 的行;存量事实照搬。
   ★ 真写风险:`save` 无校验、`{}` 会真走 insert(本机 `car_number NOT NULL` 兜底)
     ⇒ 冒烟只发无 body;update/delete 只发 0 行 / 手写 1003 分支。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十六切片:⚠B 里的 `TbWorkOrderController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 24/90 → 23/86、已迁 183/1021 → 184/1025、未迁 28/97 → 27/93**。
   ★ 落在 `system` 域(`/admin-api/system/tb-work-order/*`),老类在
     `portal.web.workorder` 包、老路由 `/tbWorkOrderManage`;**1 读 + 3 写**
     (分页 / 新增 / 按主键改 / 批量删),无导出、无 `@NoAuth`。
   ★ 坑 22 安全:只写登录态 `orgId`/`sOpId`/`createEmpId`/`modifyEmpId`/`opId`/`opName`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ ⛔ 真写风险:`saveTbWorkOrder` 服务层无任何校验 ⇒ `{}` 也真插一行 `tb_work_order`
     ⇒ 冒烟只发无 body(400)。update/delete 的 1003「id为空!」是 Controller 手写闸门 ⇒ 真零写。
   ★ 本机**没有** `tb_work_order` 表:走到 SQL 的分支都是 500「系统异常」
     (表名不含 `order_` ⇒ 不吃 `tb_work_order_process` 的 501 兜底);冒烟断言写成 0/500/501
     三选一,表导进来后自动转绿。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十五切片:⚠B 里的 `ReconciliationController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 25/93 → 24/90、已迁 182/1018 → 183/1021、未迁 29/100 → 28/97**。
   ★ 落在 `order` 域(`/admin-api/order/reconciliation/*`),老类在
     `portal.web.financialmanagement.reconciliation` 包、老路由 `/new/reconciliation`;
     **2 读 + 1 导出(xlsx)**、无写、导出老 `@NoAuth` 已收紧为需登录 + `:export`。
   ★ 坑 22 安全:汇总/分页只判登录后写 `orgId`,超管再扩 `SUPER_OP_ID` 配置组织;
     导出沿用老入参 `opId`/`orgId`/`sysCode`,**不读** `plNos`/`userAreas`/`resIds`。
     基类 `ReconciliationBaseController` 仅 `getPayOrderTypeName`,整段照搬进新类。
   ★ 存量缺陷照搬:缺起止时间 ⇒ 200 + code=500 + `[存量错误码 1003]`;
     只给时间不给 `dateType` ⇒ `request.getDateType()` 拆箱 NPE ⇒ 500;
     导出不传 `opId` ⇒ `isSuperOpId` 内 `opId.longValue()` NPE ⇒ 500。
   ★ 导出是程序化 13 列 `SXSSFWorkbook`,无模板依赖;导出端点数 **79 → 80**。
     日期参数保持存量 `@RequestParam Date`(坑 24),只吃 JS `Date.toString()` 格式。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十四切片:⚠B 里的 `EqpOnlineRateController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 26/96 → 25/93、已迁 181/1015 → 182/1018、未迁 30/103 → 29/100**。
   ★ 落在 `park` 域(`/admin-api/park/eqp-online-rate/*`),老类在
     `portal.web.report.equipment` 包、老路由 `/eqpOnlineRate`;
     **2 读 + 1 导出(PDF)**、无写、导出老 `@NoAuth` 已收紧为需登录 + `:export`。
   ★ 坑 22 安全:分页/合计只判登录(合计另用 `sysOrg` 回填 `orgName`),
     导出沿用老入参 `orgId`/`orgName`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量缺陷照搬:`pl_no in (<foreach>)` 在 `plNos` 为 null/空列表时拼成 `in ()`
     ⇒ MySQL 语法错 ⇒ 500;导出 `plNos=` 空串更早抛 `BizException(1003)`
     ⇒ 200 + code=500 + [存量错误码 1003](取参校验在 try/catch 外)。
   ★ 分页/合计按 `reportTabTag` 1/2/3 走日/周/月,未知 tab 回空页 / 数值全 null(只填 `orgName`)。
   ★ 导出是程序化 PDF(`PdfCreatTableUtil` 双子报表),无模板依赖;导出端点数 **78 → 79**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十三切片:⚠B 里的 `IncomeOnlineBillController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 27/99 → 26/96、已迁 180/1012 → 181/1015、未迁 31/106 → 30/103**。
   ★ 落在 `order` 域(`/admin-api/order/income-online-bill/*`),老类在
     `portal.web.financialmanagement.clearsettlement` 包、老路由 `/incomeOnline`;
     **2 读 + 1 导出**、无写、无 `@NoAuth`(导出老门户是 `@NoAuth`,已收紧为需登录 + `:export`)。
   ★ 坑 22 安全:两读判登录后写 `orgId`,导出沿用老入参 `orgId`/`sysCode`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量 SQL 两种面貌:汇总带聚合却无 `GROUP BY`、又 select `DATE_FORMAT(check_date)`
     ⇒ 给时间窗口在 MySQL 8 恒 500(`only_full_group_by`,红线⑥不修),`{}` 命中空集合回全 0;
     分页 `GROUP BY check_date, org_id` 正常(本机 total=121)。
   ★ 导出是程序化 15 列(`SXSSFWorkbook` + `Layouter` + `ExcleFillDateManager`),无模板依赖;
     导出端点数 **77 → 78**。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十一切片:⚠B 里的 `TpPChargingPileController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 29/105 → 28/102、已迁 178/1006 → 179/1009、未迁 33/112 → 32/109**。
   ★ 落在 `park` 域(`/admin-api/park/charging-pile/*`),老类在 `portal.web.chargingpile` 包、
     老路由 `/chargingPile`;**1 读 + 2 写**、无导出、无 `@NoAuth`。
   ★ 坑 22 安全:三端点判登录后写 `orgId`/`opId`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 真写风险:保存是 tk `insert`(`charging_pile_no` NOT NULL)⇒ `{}` 500 不落行;
     更新 `{}` 主键为空 500、`{id:999999999}` 0 行 false。
   ★ 老类**零** `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线 **+224/轮** 不变。
★★ **2026-10-19 第一百三十切片:⚠B 里的 `TdCCouponPersonController`(1 端点整类迁完)搬走**
   ⇒ **⚠B 30/106 → 29/105、已迁 177/1005 → 178/1006、未迁 34/113 → 33/112**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/coupon-person/batch-insert-coupon-person`),
     老类在 `portal.web.coupon` 包、老路由 `/couponPerson`;**1 写**、无导出、无 `@NoAuth`。
   ★ 坑 22 安全:判登录后写 `opId`/`orgId`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 真写风险:`custIds` 非空会逐条插 `td_c_coupon_person`;冒烟只发
     「无 body / `{}` ⇒ 400」与「必填齐全 + `custIds` 缺省/`[]`/`[""]` ⇒ 零插入 false」。
   ★ 老类 1 处 `@SysOpLog` ⇒ 1 个 `@OperateLog` ⇒ 零写基线 **+221 → +224/轮**。
★★ **2026-10-19 第一百二十九切片:⚠B 里的 `OrderRechargerController`(2 端点整类迁完)搬走**
   ⇒ **⚠B 31/108 → 30/106、已迁 176/1003 → 177/1005、未迁 35/115 → 34/113**。
   ★ 落在 `order` 域(`POST /admin-api/order/order-recharger/...`),老类在
     `portal.web.acctsource` 包、老路由 `/orderRecharger`;**1 读 + 1 写**、无导出、无 `@NoAuth`。
   ★ 坑 22 安全:读端点判登录并写 `orgId` + **字面量** `sysCode="10001"`;
     写端点老代码不判登录(新栈过滤器兜住),只取 `opId`/`name`/`orgId`。
   ★ 存量语义照搬:读端点的 `...SMapper` 聚合 SQL `group by` 选非聚合列
     ⇒ 本地 MySQL 8 `only_full_group_by` **恒 500**(红线⑥不修);
     写端点是**真充值**(充值单 + 支付单 + 加余额),查不到账户还会**自动建户**。
   ★ 冒烟只发零写入样本:无 body **400**、已存在 `custId` 缺 `acctGiveFee` **NPE 500**(落库前)。
   ★ 老类 1 处 `@SysOpLog` ⇒ 1 个 `@OperateLog` ⇒ 零写基线 **+220 → +221/轮**。
★★ **2026-10-19 第一百二十八切片:⚠B 里的 `StationInfoController`(1 个有效端点整类迁完)搬走**
   ⇒ **⚠B 32/109 → 31/108、已迁 175/1002 → 176/1003、未迁 36/116 → 35/115**。
   ★ 落在 `charge` 域(`POST /admin-api/charge/station-info/query-station-info-for-page`),
     老类在 `portal.web.battery` 包、老路由 `/stationInfoManage`;
     老类 4 个 mapping 里 save/update/bathDelete **整段被注释** ⇒ **1 读、零写**、无导出。
   ★ 坑 22 安全:page 无条件写登录 `orgId` + 注入 `${project.syscode}`;
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:分页是 `station_info ⋈ operator_info` 两表内连接,
     本地 `operator_info` **0 行** ⇒ 恒空页(`station_info` 3 行都不起作用)。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十七切片:⚠B 里的 `EquipmentInfoController`(1 个有效端点整类迁完)搬走**
   ⇒ **⚠B 33/110 → 32/109、已迁 174/1001 → 175/1002、未迁 37/117 → 36/116**。
   ★ 落在 `charge` 域(`POST /admin-api/charge/equipment-info/query-equipment-info-for-page`),
     老类在 `portal.web.battery` 包、老路由 `/equipmentInfoManage`;
     老类 4 个 mapping 里 save/update/bathDelete **整段被注释** ⇒ **1 读、零写**、无导出。
   ★ 坑 22 安全:page 无条件写登录 `orgId` + 注入 `${project.syscode}`;
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:分页是 `equipment_info ⋈ operator_info ⋈ station_info` 三表内连接,
     本地 `operator_info` **0 行** ⇒ 恒空页(`equipment_info` 7 行、`station_info` 3 行都进不来)。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十六切片:⚠B 里的 `ConnectorInfoController`(2 个有效端点整类迁完)搬走**
   ⇒ **⚠B 34/112 → 33/110、已迁 173/999 → 174/1001、未迁 38/119 → 37/117**。
   ★ 落在 `charge` 域(`POST /admin-api/charge/connector-info/*`),
     老类在 `portal.web.battery` 包、老路由 `/connectorInfoManage`;
     老类 5 个 mapping 里 save/update/bathDelete **整段被注释** ⇒ **1 读 + 1 写**、无导出。
   ★ 坑 22 安全:page 无条件写登录 `orgId` + 注入 `${project.syscode}`,
     generator 写 `sysCode`/`orgId`;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:分页是四表内连接,本地 `operator_info` **0 行** ⇒ 恒空页;
     `queryConnectorInfoByPrimaryKey` 查不到 ⇒ 服务层 `copyProperties(null)` 500,
     「设备id不存在」分支是死代码;本地 7 行都有 `qr_code_url` ⇒
     `generator {id:1}` 停在 1002「太阳码已经存在」,不调微信、不写库。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十五切片:⚠B 里的 `OperatorInfoController`(2 个有效端点整类迁完)搬走**
   ⇒ **⚠B 35/114 → 34/112、已迁 172/997 → 173/999、未迁 39/121 → 38/119**。
   ★ 落在 `charge` 域(`POST /admin-api/charge/operator-info/*`),
     老类在 `portal.web.battery` 包、老路由 `/operatorInfoManage`;
     老类 4 个 mapping 里 save/bathDelete **整段被注释** ⇒ **1 读 + 1 写**、无导出。
   ★ 坑 22 安全:page 无条件写登录 `orgId` + 注入 `${project.syscode}`,
     update 写 `sOpId`/`sOpName`/`modifyEmpId`/`modifyEmpName`/`modifyTime`/`sysCode`;
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:`PageBeanUtil` 只在 list 非空时 copy,本地 `operator_info` **0 行**
     ⇒ page 恒空页;update 走 selective,不存在的 id 命中 0 行回 `false`;
     `sOpId`/`sOpName` 域对象没有对应字段 ⇒ 拷不过去。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十四切片:⚠B 里的 `ChargeRecordController`(2 端点整类迁完)搬走**
   ⇒ **⚠B 36/116 → 35/114、已迁 171/995 → 172/997、未迁 40/123 → 39/121**。
   ★ 落在 `order` 域(`POST /admin-api/order/charge-record/*`),
     老类在 `financialmanagement/chargerecord` 包、老路由 `/ChargeRecord`,类级 `@NoAuth`
     ⇒ 按 §3.3 第 1 类收紧为登录 + `order:charge-record:query`。
     **2 端点 = 2 读、零写、无导出**,整类 2/2 迁完。
   ★ 坑 22 安全:page 判 `userInfo`/`orgId` 并有超管豁免(非超管强制覆盖 orgId);
     pay-detail **完全不读登录态**(照搬);只碰 `orgId`。
   ★ 存量语义照搬:日期只收 `yyyy-MM-dd HH:mm:ss` / 毫秒时间戳,纯 `yyyy-MM-dd` 是 400;
     `parkOutTime=null` ⇒ hutool `beginOfDay` NPE 500;`orderState` 拆箱 ⇒ `{}` 500;
     `orderId` 为 null ⇒ tk mapper `Value for rltOrderId cannot be null` 500。
   ★ 本地 org 10003 `2017-09-25` 真读 **41 行**(首行 id=2048609 / state=50)、
     `2017-09-28` **1 行**、9/2 归一末页首行 2048479;`orgId=10007` 被强制回 10003 仍 41 行。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十三切片:⚠B 里的 `TdCCommonProblemController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 37/120 → 36/116、已迁 170/991 → 171/995、未迁 41/127 → 40/123**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/common-problem/*`),
     老类在 `portal.web.commonproblem` 包、老路由 `/tdCCommonProblemManage`;
     **1 读 + 3 写**,无导出,整类 4/4 迁完。
   ★ 老类 `@Controller` + 每方法 `@ResponseBody`、`extends BizController` ⇒ 新栈
     `@RestController` + `CommonResult.success` + `BizResultAdapter`。
   ★ 坑 22 安全:4 个端点**全部**判登录;page/save 写 `orgId`、save 写
     `sOpId`/`createEmpId`/`createEmpName`、update 写 `sOpId`/`modifyEmpId`/`modifyEmpName`、
     batchDelete 写 `opId`/`opName`;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:**分页服务层 `PageInfo` 整段被注释 ⇒ page 恒空页**(本地 13 行活数据也不读);
     save 无校验、任何 body(含 `{}`)都真插一行;`update {}` ⇒ 1003「id为空!」、
     `update id=不存在` 空 SET 被 Druid 拒 500;bathDelete 是软删(`data_state=0`)、
     `{}`/`ids=[]` ⇒ 1003、不存在 id 回 false。
   ★ `TdCCommonProblemRequest` 无 `question`/`answer` 字段、`createEmpId` 与域对象
     `createEmpid` 大小写不同 ⇒ 拷不过去,save 实际只落 `org_id`+`create_date`。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+220/轮)。
★★ **2026-10-19 第一百二十二切片:⚠B 里的 `TdCouponRuleController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 38/125 → 37/120、已迁 169/986 → 170/991、未迁 42/132 → 41/127**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/coupon-rule/*` + `GET /export-excel`),
     老类在 `pay.controller.coupon` 包、老路由 `/tdCoupon`;**1 读 + 3 写 + 1 导出**,整类 5/5 迁完。
   ★ 老类 `@Controller` + 每方法 `@ResponseBody`、`extends BizController` ⇒ 新栈
     `@RestController` + `CommonResult.success` + `BizResultAdapter`。
   ★ 坑 22 安全:save/query 读 `orgId`/`opId`/`name`,delete/deleteBatch **完全不读登录态**;
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:save 由 `flg` 分支(`Integer` 拆箱 ⇒ `flg=null` NPE 500),
     服务层另按 `cardType==2` 分叉;delete 只在 `id != null` 时软删 + 按 `card_no` 删关系表;
     deleteBatch `andIdIn(ids)` 空 ⇒ tk mapper 500;导出 `BaseInfo(1, MAX)` 一次取全量,
     `couponsType`/`isUesed` 拆箱、时间列判空缺失 ⇒ 存量缺陷。
   ★ 本地 org 10003 真读 **46 行**(首行 id=10169),`pageNum=9&pageSize=2`
     被 reasonable 归一成末页 1 行(id=10132)。
   ★ 导出老 `@NoAuth` → 登录 + `:export`,10 列、参数全可选,真出 20KB+ xlsx。
   ★ 老类 3 个写端点带 `@SysOpLog` ⇒ 3 个 `@OperateLog`
     ⇒ 零写基线 **+214 → +220/轮**(冒烟发不落库样本,只多记 3 条日志)。
★★ **2026-10-19 第一百二十一切片:⚠B 里的 `ParkBerthAppointmentConfigRelController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 39/128 → 38/125、已迁 168/983 → 169/986、未迁 43/135 → 42/132**。
   ★ 落在 `park` 域(`POST /admin-api/park/park-berth-appointment-config-rel/*`),
     老类在 `parkberthappointmentconfig` 包、老路由 `/parkBerthAppointmentConfigRle`
     (★ 存量拼写 `Rle`);**1 读 + 2 写**,整类 3/3 迁完。
   ★ 老类 `@Controller` + 每方法 `@ResponseBody`、`extends BizController` ⇒ 新栈
     `@RestController` + `CommonResult.success` + `BizResultAdapter`。
   ★ 坑 22 安全:3 个端点**全部**判登录;page 写 `orgId`、save 写 `sOpId`/`createEmpId`/
     `createEmpName`、batchDelete 什么都不写;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:page 内联 `park_berth_appointment_config`、两边 `data_state=1`、
     按 `b.org_id` 过滤、`order by a.id desc`;本地 org 10003 真读 **3 行**(首行 id=7),
     plName 由 Dubbo 回填;不传 `baseRequest` 不炸(默认 10 行);
     `pageNum=9&pageSize=2` 被 reasonable **归一成末页 1 行(id=2)**;
     `appointmentConfigName` 的 like 过滤「医院」命中 2 行;
     ⛔ save **先按 `pl_noList` 物理删再逐条插**(空列表不删不插回 false);
     ⛔ batchDelete 物理删、`ids` 空 ⇒ 1003「id为空!」。
   ★ ⛔ 两个写端点:save 传真 `plNoList` 会真删真插、batchDelete 会真删,
     冒烟只发无 body(绑定层 400)+ `plNoList=[]`;本地 3 行一行不动。
   ★ 老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变(+214/轮)。
★★ **2026-10-19 第一百二十切片:⚠B 里的 `EtcSettlementController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 40/132 → 39/128、已迁 167/979 → 168/983、未迁 44/139 → 43/135**。
   ★ 落在 `order` 域(`POST /admin-api/order/etc-settlement/*` + `GET /export-excel`),
     老类在 `financialmanagement/clearsettlement` 包、老路由 `/EtcSettlement`;
     **2 读 + 1 分页 + 1 导出**,整类 4/4 迁完。
   ★ 老类 `extends BizController` ⇒ 只借道 `BizResultVO`/`returnJqGridData`,
     新栈改 `CommonResult.success` + `BizResultAdapter.getDataOrThrow/toPageResult`。
   ★ 坑 22 安全:3 个 POST 判 `userInfo == null`、写 `orgIds` = 登录 `orgId`、
     `opId` 只用于 `isSuperOpId` 超管判定;导出**不读登录态**(老方法里的登录判定
     整段被注释),读**请求参数** `optId`/`orgId`;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:**`queryMchInfoList` 完全忽略入参**(服务层全表查 `td_b_etc_config`
     + 回填 appId/mchName),本地 0 行 ⇒ `code=0 + []`;`appIds` 的补齐在**服务层**
     (空则按 `orgIds + payType=32` 查 `sys_org_app_id`,查不到 ⇒ `BIZ_ERROR "appId为空!"`
     ⇒ 500/1002);**`baseRequest` 不会 NPE**(字段初始化 + 懒 getter + `reasonable=true`)
     ⇒ 不传 `baseRequest` 实测 total 500 / 默认 10 行;导出 `getCheckResultType()==1`、
     `getSettlementState()==0` 是 `Integer` 拆箱,`DateUtil.format` 不判空;
     `isSuperOpId` 对 null map `forEach` ⇒ NPE。照搬。
   ★ 本地 org 10003 绑 ETC 商户 1504005 ⇒ `td_b_etc_pay_order_check_result` 500 行
     (2020 年)是真读:2020 窗口 `totalFee=217134、totalCount=500、已结算 494、未结算 6`。
   ★ 导出老 `@NoAuth` → 登录 + `:export`,5 必填 / `appId` 可选;先查一次拿 pages
     再把 pageSize 改成 **1000** 逐页取,18 列、起始行 `pageSize*(num-1)+2`。
   ★ ⛔ 全类只读/导出,无写端点;老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变。
★★ **2026-10-19 第一百一十九切片:⚠B 里的 `EtcReconciliationController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 41/137 → 40/132、已迁 166/974 → 167/979、未迁 45/144 → 44/139**。
   ★ 落在 `order` 域(`POST /admin-api/order/etc-reconciliation/*` + `GET /export-excel`),
     老类在 `financialmanagement/reconciliation` 包、老路由 `/etc/reconciliation`;
     **2 对账读 + 2 异常流水动作 + 1 导出**,整类 5/5 迁完。
   ★ 老类 `extends ReconciliationBaseController`(基类只有一个 `getPayOrderTypeName`、
     类上无 mapping)⇒ 新类**整段照搬**那一个方法,不继承、不抽公共类。
   ★ 坑 22 安全:total/page 读 `orgId`(进 `orgIds`)+ `opId`(只用于超管判定);
     feedBack 读 `opId`/`name`(**不判空**);异常流水查询**完全不读登录态**;
     导出读**请求参数** `optId`/`orgId`;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 存量语义照搬:`dateType=null` 时 page 控制器拆箱 NPE、total 服务层 `.equals(2)` NPE,
     都是 **500**;page **不传 `baseRequest` 不炸**(服务层 `PageBeanUtil.copyProperties`
     补默认 BaseInfo ⇒ total 500 / 默认 10 行);异常流水 `{}` 500、假 appId 4014,
     均停在外部调用/回写之前;导出老 `@NoAuth` → 登录 + `:export`,6 个必填 `@RequestParam`。
   ★ 本地 org 10003 绑 ETC 商户 1504005 ⇒ `td_b_etc_pay_order_check_result` 500 行
     (2020 年)是真读:日口径汇总 217134、8 月月口径 35680/124 行。
   ★ ⛔ 两个异常流水端点**可能落库**(回写 `check_state` / 改平账),
     冒烟只发炸关口样本;老类零 `@SysOpLog` ⇒ 零 `@OperateLog`,零写基线不变。
★★ **2026-10-19 第一百一十八切片:⚠B 里的 `OrderChargeInfoController`(3 端点整类迁完)搬走**
   ⇒ **⚠B 42/140 → 41/137、已迁 165/971 → 166/974、未迁 46/147 → 45/144**。
   ★ 落在 `order` 域(`POST /admin-api/order/order-charge-info/*`),老类在 `battery` 包、
     老路由 `/orderChargeInfoManage`;**2 读 + 1 退款动作**,整类 3/3 迁完。
   ★ 类级去 `Manage` 后 kebab;方法段按老 mapping 逐字 kebab。
   ★ 坑 22 安全:3 个端点**全部**判 `userInfo == null`;page 写 `orgId`(★ DAO 的
     smapper **确实用它过滤**)与写死 `sysCode=10001`;退款/日志查询写 `sOpId`/`sOpName`
     (日志查询其实没用这两列,照搬);**不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+214/轮 不变**。
   ★ 存量语义照搬:page 固定 `data_state=1 and order_state > 30` + 登录人 `orgId`
     ⇒ 本地 org 10003 **0 行**(有数据时还会经 Dubbo 查充电站位置,走不到);
     `platformManualRefund` 先 `queryByOrderId(operatorId, orderId)` ⇒ `{}` 因 null
     条件在 tk-mapper 直接抛异常 **500**,给不存在编号 ⇒ **5010 没有找到充电订单**,
     两条都在任何退款/外部调用之前;`queryOperatorLog` 的 DAO 直接
     `andOrderIdEqualTo(null)` ⇒ `{}` **500「系统异常」**,条件齐了回 `[]`。
   ★ ⛔ `platformManualRefund` 带**真实已支付 orderId + operatorId** 会真发起退款并调
     「停止充电」外部接口 ⇒ 冒烟只发 `{}`/不存在编号/无 body,业务表行数已实测不变。
   ★ 该三张表(`order_charge_info`/`order_charge_log`/`order_charge_operate_log`)已并入
     零写计数脚本(`/tmp/zwcount66.py`)。
★★ **2026-10-19 第一百一十七切片:⚠B 里的 `InvoiceOperController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 43/145 → 42/140、已迁 164/966 → 165/971、未迁 47/152 → 46/147**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/invoice-oper/*`),老类在 `invoice` 包、
     老路由 `/invoice/oper`;**1 改状态 + 3 开票/下载动作 + 1 个 `@NoAuth` 测试端点**,
     整类 5/5 迁完。
   ★ 坑 22 安全:`updateInvoiceStateById` **不判空**直接取 `opId`/`name`(照搬);
     `openInvoiceRecord` **完全不读登录态**;另两条写 `createEmpid`/`modifiyEmpid`
     (`openInvoiceListByOrderIds` 另写 `orgId`,`downTheInvoice` **不写**);
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 3 处 `@SysOpLog` ⇒ 新类 3 处 `@OperateLog` ⇒ 零写基线 **+209 → +214/轮**。
   ★ 存量语义照搬:`updateInvoiceStateById` 的 `@Valid`(id/invoiceState)挡成 400;
     给不存在 id ⇒ 0 行但**无条件回 true**;`openRecord {}` 在 `saleTaxid.substring`
     处 NPE 500;`openList` 先判 orderIds(1003)再判 orgId,订单不存在 1002;
     `downInvoice` **不设 orgId** ⇒ 销方查询 null ⇒ 1002。
   ★ `openInvoiceRecordTest` 老 `@NoAuth` 按 §3.3 收紧,且服务层会调**外部金税
     WebService** ⇒ ⛔ 冒烟只发无 body。
   ★ ⛔ `openRecord` 带 `saleTaxid` 会真插历史+条目、`openList`/`downInvoice` 会调
     外部发票服务并改状态 ⇒ 冒烟只发炸关口样本,业务表行数已实测不变。
★★ **2026-10-19 第一百一十六切片:⚠B 里的 `ParkingChargingRulesController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 44/150 → 43/145、已迁 163/961 → 164/966、未迁 48/157 → 47/152**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/parking-charging-rules/*`),老类在
     `parkinglotcloudplatform/closecarpark` 包、老路由 `/parkingChargingRules`;
     **1 读 + 4 写**,整类 5/5 迁完。
   ★ 类级与方法段均按老名字 kebab 逐字搬。
   ★ 坑 22 安全:query/save/update **判** `userInfo == null`;delete/view
     **老代码根本不取登录态**(照搬,不补校验);只碰 `orgId`/`createBy`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**分页参数在 `baseRequest` 里**(直接传 pageNum/pageSize 被忽略,
     默认 0/10);page 按 `orgId`+`data_flg='1'`+`fee_type=1` 过滤(本地 8 行);
     `save` **先插主表再遍历 detailList** ⇒ `{}` 会先落一行再 NPE ⇒ ⛔ 绝不发;
     `update` 的 `andRuleCollectIdEqualTo(null)` 直接抛异常 ⇒ **500,异常在写之前**;
     `delete` 先 `checkBindPark`(空 ruleCollectId ⇒ 500),不存在 id ⇒ 0 行返回 0;
     `view` 对结果 `.get(0)` ⇒ 查不到 **IndexOutOfBounds 500**。
   ★ ⛔ `save`/`update` 带 detailList 会真写 `td_cl_rule_collect`/`td_cl_rule`/
     `td_cl_rule_detail` ⇒ 冒烟只发无 body 与炸关口样本,三表行数已实测不变。
★★ **2026-10-19 第一百一十五切片:⚠B 里的 `SpecialCarnumberBillingController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 45/155 → 44/150、已迁 162/956 → 163/961、未迁 49/162 → 48/157**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/special-carnumber-billing/*`),老类在 `specialcar` 包、
     老路由 `/specialCarnumberBillingManage`;**1 读 + 4 写**,整类 5/5 迁完。
   ★ 类级去 `Manage` 后 kebab;方法段按老 mapping 逐字 kebab(`bath-delete` 保留拼写)。
   ★ 坑 22 安全:5 个端点都判 `userInfo == null`;page 只写 `orgId`(★ DAO 没用它),
     save 另写 `createEmpId`/`createEmpName`,update 写 `modifyEmpId`/`modifyEmpName`,
     batchDelete/del 写 `opId`/`opName`;**不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**page 不按组织过滤**(本地 220 行活数据全可见,默认 0/10 ⇒
     `page {}` = total 220 / 10 条);`save` 手写判手机/车牌非空再查重;
     `update`/`del` 先 `queryByPrimaryKey().get(0)` ⇒ 不存在 id **IndexOutOfBounds 500**
     (异常在写之前);`del` 还会关闭 `acct_type=5` 的免密;`bathDelete` 逻辑删,
     传 `getsOpName()` 的存量错位照搬。
   ★ ⛔ `save {phoneNum,carNumber}` 会真插主表并可能建客户/账户/车牌
     ⇒ 冒烟只发 `{}`(判空 1003)与无 body。
★★ **2026-10-19 第一百一十四切片:⚠B 里的 `TdCCustBusinessController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 46/160 → 45/155、已迁 161/951 → 162/956、未迁 50/167 → 49/162**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/cust-business/*`),老类在 `custperson` 包、
     老路由 `/tdCCustBusinessManage`;**1 读 + 3 写 + 1 审核动作**,整类 5/5 迁完。
   ★ 类级与方法段都去 `TdC` 前缀后按 kebab 逐字搬;`examine` 审核动作复用 `:update` 权限。
   ★ 坑 22 安全:5 个端点都判 `userInfo == null`;page 只写 `orgId`,
     save/update 另写 `opId`/`name`,batchDelete 写 `opId`/`opName`,examine 写
     `createEmpid`/`createEmpname`/`createDate`/`examineDate`;**不读**
     `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:本地三表全 **0 行** ⇒ page 恒 `total=0 / list=[]`;
     `update {}` / `bathDelete {}` ⇒ 控制器 `PARAMM_NULL`;域对象无 `sOpId`
     ⇒ `update {id}` 空 SET 被 Druid 拒 ⇒ **500 且不改行**;`examine {}` 在
     `getExamineState().equals(2)` 处 **NPE 500**(DAO 之前,不落库);
     `bathDelete` 是逻辑删;`batchDelete` 传 `getsOpName()` 的存量错位照搬。
   ★ ⛔ `save {}` 会真插一行、⛔ `examine {examineState}` 会改主表 + 插审核记录
     ⇒ 冒烟只发无 body 与 `examine {}`(NPE)。
★★ **2026-10-19 第一百一十三切片:⚠B 里的 `TdCCustBusinessCheckTypeController`(4 端点整类迁完)搬走**
   ⇒ **⚠B 47/164 → 46/160、已迁 160/947 → 161/951、未迁 51/171 → 50/167**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/cust-business-check-type/*`),
     老类在 `custperson` 包、老路由 `/tdCCustBusinessCheckTypeManage`;
     **1 读 + 3 写**,**整类 4/4 全部迁完,无 P3 尾巴**。
   ★ 类级与方法段都去 `TdC` 前缀后按 kebab 逐字搬(沿同包
     `TdCCustBusinessCheckRecordController` → `/pay/cust-business-check-record` 的风格)。
   ★ 坑 22 安全:4 个端点都判 `userInfo == null`;page 只写 `orgId`,
     save/update 另写 `opId`/`name`,batchDelete 写 `opId`/`opName`;**不读**
     `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**page 服务层整段被注释 ⇒ 恒 `total=0 / list=[]`**(DAO 也没有分页方法);
     `queryTdCCustBusinessCheckType` 的 DAO 死方法 `return null`(老门户没调用);
     Request 的 `createEmpId`/`modifyEmpId` 与域对象 `createEmpid`/`modfiyEmpid`
     拼写不同 ⇒ `update {id}` 空 SET 被 Druid 拒 ⇒ **500「系统异常」且不改行**;
     `bathDelete` 是**逻辑删除**(`data_state=0`,`where id in (?)`);`batchDelete`
     传 `getsOpName()` 的存量错位照搬。
   ★ 本地 `td_c_cust_business_check_type` **0 行** ⇒ 读断言钉 `total=0 / []`;
     ⛔ `save {}` 会真插一行(DAO 补 `createDate`),冒烟只发无 body(绑定层 400)。
★★ **2026-10-19 第一百一十二切片:⚠B 里的 `ParkBerthAppointmentConfigController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 48/169 → 47/164、已迁 159/942 → 160/947、未迁 52/176 → 51/171**。
   ★ 落在 `park` 域(`POST /admin-api/park/park-berth-appointment-config/*`),
     老类在 `parkberthappointmentconfig` 包、老路由 `/parkBerthAppointmentConfigManage`;
     **2 读 + 3 写**,**整类 5/5 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab(`/bath-delete-…` 保留存量拼写);类级按类名 kebab。
   ★ 坑 22 安全:5 个端点都判 `userInfo == null`;page/list 只写 `orgId`,
     save/update 另写 `opId`/`name`,batchDelete 什么都不写;**不读**
     `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**page** 过滤 `data_state=1` + `org_id`;**list** 额外叠
     `rule_state=1`;`update {}` / `bathDelete {}` ⇒ 控制器 `PARAMM_NULL`;
     ⛔ **`bathDelete` 是物理删除**(先删主表、再删 `park_berth_appointment_park_rel`,
     影响行数相加),不是逻辑删。
   ★ ★ 本地两表都有活数据:`park_berth_appointment_config` **2 行**(id=2、3 / org 10003)、
     `park_berth_appointment_park_rel` **3 行** ⇒ page/list 是**活读**,冒烟按真实数据钉
     `total=2 / 2 条`;⛔ 绝不发真实 id、save 只发无 body。实测两表前后不变(2→2、3→3)。
★★ **2026-10-19 第一百一十一切片:⚠B 里的 `ArrearageSmsAutoSendConfigController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 49/174 → 48/169、已迁 158/937 → 159/942、未迁 53/181 → 52/176**。
   ★ 落在 `order` 域(`POST /admin-api/order/arrearage-sms-auto-send-config/*`),
     老类在 `arrautosendconfig` 包、老路由 `/arrearageSmsAutoSendConfigManage`;**2 读 + 3 写**,
     **整类 5/5 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab(`/bath-delete-…` 保留存量拼写);类级按类名 kebab。
   ★ 坑 22 安全:5 个端点都判 `userInfo == null`,但只写 `orgId`/`opId`/`name`,
     **不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有一处 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:`queryArrearageSmsAutoSendConfig` 只取 list `get(0)`;
     `handelCronStr` 在判空之前执行(空 ⇒ "";不足 6 段 ⇒
     `BIZ_ERROR「cron 表达式错误!」` ⇒ code 500);老 Request 的 `modifyEmpId` 与域对象
     `modfiyEmpId` **拼写不同**、`BeanUtils.copyProperties` 拷不过去 ⇒ selective update
     的 SET 只剩 `cron_str`;`bathDelete` 是逻辑删除(`data_state=0`)。
   ★ ★ 与最近几片不同:本地 `arrearage_sms_auto_send_config` **有 1 行活数据**
     (id=1 / org 10003 / data_state=1,id=2 已删)⇒ page/query 是**活读**,
     冒烟按真实数据钉 `total=1`、`id=1`;⛔ 别给 ids 传 1、别给 save 传 `{}`。
   ★ 实测冒烟前后该表 **2 → 2** 未变,零写差分只动 `system_operate_log`。
★★ **2026-10-19 第一百一十切片:⚠B 里的 `TdCCustBusinessCheckRecordController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 50/179 → 49/174、已迁 157/932 → 158/937、未迁 54/186 → 53/181**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/cust-business-check-record/*`),
     老类在 `custperson` 包、老路由 `/tdCCustBusinessCheckRecordManage`;**2 读 + 3 写**,
     **整类 5/5 全部迁完,无 P3 尾巴**。
   ★ 类级与方法段**都把 `TdC` 前缀去掉后按 kebab 逐字搬**(沿
     `TdCCustSuggestDetailController` / `TdCCustBusinessPicController` 的既有风格)。
   ★ 坑 22 安全:`query-cust-business-check-record` **完全不读登录态**;其余 4 个端点
     只写 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类 **没有 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**page 服务层整段被注释** ⇒ 恒空页;list 的
     `andBusinessIdEqualTo(null)` 抛 ⇒ **500**;`save {}` / `update id=1` 的 selective
     SET 为空被 Druid 拒(`expect SET, actual WHERE`)⇒ **500 且不落库**;
     `update {}` / `bathDelete {}` ⇒ 控制器 `PARAMM_NULL`;`bathDelete ids=[999999999]`
     命中 0 行 ⇒ false(逻辑删 `data_state=0`)。
   ★ ⛔ `save` 仍只发无 body(绑定层 400);本地 `td_c_cust_business_check_record` 0 行,
     实测前后不变。
★★ **2026-10-19 第一百零九切片:⚠B 里的 `SmsTemplateConfigRltParkingController`(5 端点整类迁完)搬走**
   ⇒ **⚠B 51/184 → 50/179、已迁 156/927 → 157/932、未迁 55/191 → 54/186**。
   ★ 落在 `park` 域(`POST /admin-api/park/sms-template-config-rlt-parking/*`),
     老类在 `msg` 包、老路由 `/smsTemplateConfigRltParkingManage`;**2 读 + 3 写**,
     **整类 5/5 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab(`/query-…-for-page`、`/query-…-for-list`、
     `/save-…`、`/update-…-by-id`、`/bath-delete-…`);类级按类名 kebab。
   ★ 坑 22 安全:5 个端点**全部**读登录态判空,但只碰 `orgId`/`opId`/`name`,
     **不读** `plNos`/`userAreas`/`resIds`;page/list 的 `orgId` **不判空直接覆盖**。
   ★ 老类 **没有 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:**page 服务层整段被注释** ⇒ 恒空页(`total=0 / list=[]`);
     list 按登录 org=10003 查,本地 46 行全 `org_id=NULL` ⇒ 恒 `[]`;
     `save` 缺 smsId / `update` 缺 id / `bathDelete` 缺 ids ⇒ 控制器抛 `PARAMM_NULL`
     (HTTP 200 + `code=500` + 存量错误码 1003);
     `update id=1` / `bathDelete ids=[1]` 因 DAO 是 TODO 空实现 ⇒ 恒 `false`。
   ★ ⛔ `save` 给**已存在的 smsId** 会先 `deleteByExample` **真删**该 smsId 下所有关联、
     再按 `templateConfigRltParkList` 插新 ⇒ 冒烟只发无 body / `{}` / 空 smsId,
     `sms_template_config_rlt_parking` 行数实测不变(46 → 46)。
★★ **2026-10-18 第一百零八切片:⚠B 里的 `CardRuleManageController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 52/190 → 51/184、已迁 155/921 → 156/927、未迁 56/197 → 55/191**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/card-rule-manage/*`),老类在 `closecarpark`
     包、老路由 `/cardRuleManage`;**2 读 + 3 写 + 1 查看**,
     **整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab;类级按类名 kebab,沿同目录
     `SpecialCarManageController` / `SubCardInfoManageController` 的风格。
   ★ 坑 22 安全:只读 `orgId`,**不读** `plNos`/`userAreas`/`resIds`;
     `query-card-rule-list-manage` / `update` / `delete` / `view` **完全不读登录态**。
   ★ 老类 **没有 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:本地 `td_cl_sub_card_rule` 0 行 ⇒ page total 0 / list [];
     page 按 `data_status = 1 + org_id`,list **只按 data_status = 1、不按组织**(两条口径不同);
     `view {}` / `view id=1` 都查不到 ⇒ `BeanUtils.copyProperties(null, dto)` 抛
     `Source must not be null` ⇒ **500**;`update {}` / `delete {}` 主键 null ⇒ 更新 0 行、
     回 false(delete 是逻辑删除 `data_status=0`)。
   ★ ⛔ `save` 服务层无校验、给 `{}` 会真插一行 `td_cl_sub_card_rule`
     ⇒ 冒烟只发无 body(绑定层 400);该表行数实测不变。
★★ **2026-10-18 第一百零七切片:⚠B 里的 `CompanyStoreAccountController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 53/196 → 52/190、已迁 154/915 → 155/921、未迁 57/203 → 56/197**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/company-store-account/*`),老类在
     `closecarpark` 包、老路由 `/companyAccount`;**3 读 + 3 写**,
     **整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab(`/query-company-account`、`/save-company-account-info`、
     `/update-company-account-info`、`/view-company-account`、`/operate-company-account`、
     `/query-company-account-flow`);类级按类名 kebab。
   ★ 坑 22 安全:登录态只读 `orgId`/`name`,**不读** `plNos`/`userAreas`/`resIds`;
     `view` / `query-company-account-flow` **完全不读登录态**。
   ★ 老类 **没有 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 存量语义照搬:分页 SMapper 的 SQL 以 `where 1=1` 结尾,本地 Druid
     `filters=stat,wall` 直接拒(`select alway true condition not allow`)⇒ **page 恒 500**;
     老 `zteits-pay` 的 `druid.filters=stat,wall` 是同一份配置 ⇒ 老栈同样 500,不是迁移回归。
     `view {}` 同一条 SQL ⇒ 500;`view accCode=不存在` 查询回 null、
     `BeanUtils.copyProperties(null, dto)` 抛 `Source must not be null` ⇒ 500。
     `save {}` 撞 `andCustomerNoEqualTo(null)` ⇒ 500;`update {}` 因空 `parkList`
     的 `record.get(0)` 越界 ⇒ 500;flow 本地 0 行 ⇒ total 0 / list []。
   ★ ⛔ `operate` 给 `{}` 仍会调 `operateCompanyAccountFlow` **真插一行**
     `td_cl_company_acc_flow` ⇒ 冒烟只发无 body(绑定层 400);三张 `td_cl_company_acc*`
     表行数实测不变。
★★ **2026-10-18 第一百零六切片:⚠B 里的 `ParkingSegmentChargingController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 54/202 → 53/196、已迁 153/909 → 154/915、未迁 58/209 → 57/203**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/parking-segment-charging/*`),老类在
     `closecarpark` 包、老路由 `/parkingSegmentCharging`(**无前导斜杠**);
     **2 读 + 3 写 + 1 查看**,**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 方法段按老 mapping 逐字 kebab(`/query-parking-segment-charging`、
     `/query-parking-charging-rules-list`、`/save-…`、`/update-…`、`/delete-…`、`/view-…`),
     沿同目录 `SpecialCarManageController` / `SubCardInfoManageController` 的风格。
   ★ 坑 22 安全:登录态只读 `orgId`/`name`,**不读** `plNos`/`userAreas`/`resIds`;
     `delete` / `view` **完全不读登录态**。
   ★ 登录 `orgId` 是「非 null 才覆盖」、**没有超管分支**(`-1` 原样写入)——与其它类不同,照搬。
   ★ 老类 **没有 `@SysOpLog`** ⇒ 新类无 `@OperateLog` ⇒ 零写基线 **+209/轮 不变**。
   ★ 两条读端点口径不同:分页 SQL 只查 `fee_type = 2 and data_flg = 1`(本地 6 行);
     规则列表走 `selectByExample`,只按 org + `data_flg = 1`、**不过滤 fee_type**(本地 14 行)。
   ★ 存量语义照搬:`update {}` / `delete {}` 均因 Example criteria
     `Value for ruleCollectId cannot be null` ⇒ **500**;`view {}` 的 id 是原始类型 `long`
     默认 0 ⇒ 查不到回 null;`save` 返回**最后一条详情**的插入行数,无详情恒 `false`。
   ★ ⛔ `save` 给 `{}` 会真插一行 `td_cl_rule_collect`(列全可空、ID 雪花生成)却仍回 false
     ⇒ 冒烟只发无 body(绑定层 400);三张 `td_cl_*` 表行数实测不变。
★★ **2026-10-18 第一百零五切片:⚠B 里的 `TBlackRuleListController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 55/208 → 54/202、已迁 152/903 → 153/909、未迁 59/215 → 58/209**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/black-rule-list/*`),老路由 `/tBlackRuleListManage`
     (黑名单规则列表管理);**2 读 + 4 写**,**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ ★ 与已迁的 `BlackListRuleController`(`/bill/black-list-rule`,表 `t_black_list_rule`)
     **不是同一个类**:本类操作 `t_black_rule_list`,路由/权限前缀都不同,别混。
   ★ CRUD 段沿同模块 `TVipAreaCardController` 的语义命名(`/list`、`/page`、`/create`、
     `/update`、`/delete`、`/delete-list`);老方法名 `bathDelete…` 的错拼只留在方法名。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
     两个读端点老代码直接 `getUserInfo().getOrgId()`、**不判空**(无 token 先被过滤器拦);
     save 判 `userInfo == null` 后写 `sOpId`/`sOpName`/`orgId`;其余三个只判 `userInfo == null`。
   ★ 老类 **4 处 `@SysOpLog`** ⇒ 4 个 `@OperateLog`(create/update/delete/delete-list);
     冒烟每轮有 3 条带 `{}` 的写请求进控制器 ⇒ 零写基线 **+206 → +209/轮**。
   ★ 存量语义照搬:SMapper 真 SQL 是 `data_state = 1 and org_id = #{orgId}`(可加 carNum/
     ruleType/ruleState/carNums),本地 org=10003 有 **3 行**;`update {}` 动态 SQL 无 SET 段
     ⇒ **500**;`delete-list {}` 因 `andIdIn(null)` ⇒ **500**;`delete {}` 主键 null ⇒ 更新 0 行、
     回 false;两个删除端点写 `setsOpId`、服务读 `getOpId()` ⇒ 操作人 ID 恒 null(照搬)。
   ★ ⛔ `create` 服务层只强制 `auditorStatus=2`/`ruleState=1`、`ruleNo` 由 SequenceUtil 生成,
     给合法 body 会真插一行 ⇒ 冒烟只发无 body(绑定层 400)。
★★ **2026-10-18 第一百零四切片:⚠B 里的 `TdBPayAppController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 56/214 → 55/208、已迁 151/897 → 152/903、未迁 60/221 → 59/215**。
   ★ 落在 `order` 域(`POST /admin-api/order/pay-app/*`),老路由 `/tdBPayAppManage`
     (支付APP应用配置管理);**2 读 + 4 写**,**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 路由风格照同模块同族 `TdBPayProductController`(`/order/pay-product`):
     方法段逐字 kebab(`/query-td-b-pay-app-list` 等),不改成语义 CRUD 段。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
     `queryTdBPayAppList` **完全不读登录态**;page/save 判 `userInfo == null || orgId == null`,
     非超管(`orgId != -1`)才覆盖 `orgId`;其余三个写端点只判 `userInfo == null`。
   ★ 老类 **4 处 `@SysOpLog`** ⇒ 4 个 `@OperateLog`(save/update/delete/bathDelete);
     冒烟每轮有 3 条带 `{}` 的写请求进控制器 ⇒ 零写基线 **+203 → +206/轮**。
   ★ 存量语义照搬:列表 DAO 是 **TODO 空实现、恒 return null** ⇒ 端点**永远 `[]`**;
     分页按登录 `orgId` + `data_state=1` 过滤(本地样本那行属 10079)⇒ 0 行;
     `update {}` / `delete {}` 主键 null ⇒ 更新 0 行、回 `code=0 + data=false`;
     `bath {}` 因 `andPayAppIdIn(null)` 抛 RuntimeException ⇒ **500**(老栈同);
     两个删除端点写 `setsOpId`、服务读 `getOpId()` ⇒ 操作人 ID 恒 null;
     DAO 的 `save` 第二处判空仍判 `createTime`(本意 `modifyTime`)——全部照搬。
   ★ ⛔ `save` 服务层无校验,给 body 会真插一行 `td_b_pay_app` ⇒ 冒烟只发无 body(绑定层 400)。
★★ **2026-10-18 第一百零三切片:⚠B 里的 `TdBPayChannelController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 57/220 → 56/214、已迁 150/891 → 151/897、未迁 61/227 → 60/221**。
   ★ 落在 `order` 域(`POST /admin-api/order/pay-channel/*`),老路由 `/tdBPayChannelManage`
     (支付渠道管理);**2 读 + 4 写**,**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 路由风格照同模块同族 `TdBPayProductController`(`/order/pay-product`):
     方法段逐字 kebab(`/query-td-b-pay-channel-list` 等),不改成语义 CRUD 段。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
     `queryTdBPayChannelList` **完全不读登录态**;page/save 判 `userInfo == null || orgId == null`,
     非超管(`orgId != -1`)才覆盖 `orgId`;其余三个写端点只判 `userInfo == null`。
   ★ 老类 **4 处 `@SysOpLog`** ⇒ 4 个 `@OperateLog`(save/update/delete/bathDelete);
     冒烟每轮有 3 条带 `{}` 的写请求进控制器 ⇒ 零写基线 **+200 → +203/轮**。
   ★ 存量语义照搬:列表 DAO 是 **TODO 空实现、恒 return null** ⇒ 端点**永远 `[]`**;
     分页只按登录 `orgId` 过滤;`update {}` 动态 SQL 无 SET 段 ⇒ **500**;
     `bath {}` 的 `andChannelIdIn(null)` ⇒ **500**;`delete {}` 更新 0 行;
     两个删除端点写 `setsOpId`、服务读 `getOpId()` ⇒ 操作人恒 null。
   ★ ⛔ 写样本红线:`saveTdBPayChannel` 服务层**无任何校验**、`{}` 会真插一行
     `td_b_pay_channel` ⇒ 冒烟只发无 body(绑定层 400);该表已纳入零写计数表做守卫。
★★ **2026-10-18 第一百零二切片:⚠B 里的 `TdCPoliceCarInfoController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 58/226 → 57/220、已迁 149/885 → 150/891、未迁 62/233 → 61/227**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/police-car-info/*`),老路由
     `/tdCPoliceCarInfoManage`(公安车辆信息);**2 读 + 4 写**(含 1 条 multipart Excel
     解析),**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 类名以老表前缀 `TdC` 开头,按本仓既有做法(`TdCActivityController` → `/pay/activity`、
     `TdCCarCheckRecordController` → `/pay/car-check-record`)**去掉 `TdC`** 落
     `/pay/police-car-info`;CRUD 段语义命名,非 CRUD 保留原方法 kebab(`4` 原样保留)。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ 老类**零 `@SysOpLog`** ⇒ 新类零 `@OperateLog`,零写基线 **+200/轮 不变**。
   ★ 存量语义照搬:两个 `parse` 端点**不判** `userInfo == null`(有 token 不受影响);
     `parsePoliceCarInfoByCarNumber` 外部失败逐条吞掉仍 `code=0`,服务层拿到图片会往
     `td_c_police_car_info` **插一行**(`@Async` 副作用);`parsePoliceCarInfo4Excel`
     逐行落库(循环内 N+1 存量违例)、`sysCode` 写字面量、`>200` 行 500、
     列数不足 500、车牌空/手机号非数字静默跳过;`update {}` / `delete-list {}` 停在
     控制器 1003。
   ★ ⛔ 写样本红线:`saveTdCPoliceCarInfo` 服务层**无任何校验**、`{}` 会真插一行
     `td_c_police_car_info` ⇒ 冒烟只发无 body(绑定层 400);该表已纳入零写计数表做守卫。
★★ **2026-10-18 第一百零一切片:⚠B 里的 `TVipAreaCardController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 59/232 → 58/226、已迁 148/879 → 149/885、未迁 63/239 → 62/233**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/vip-area-card/*`),老路由 `/tVipAreaCardManage`
     (区域卡管理);**3 读 + 3 写**,**整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 类名以老表前缀 `T` 开头,按本仓既有做法(`TBillingDescriptionController` →
     `/bill/billing-description`、`TBlackRuleParkRelController` → `/bill/black-rule-park-rel`)
     **去掉 `T`** 落 `/bill/vip-area-card`;CRUD 段沿用语义命名
     `/list`、`/page`、`/create`、`/update`、`/delete-list`。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`;
     `queryParkListByAreaCardNo` **完全不读登录态**。
   ★ 老类**零 `@SysOpLog`** ⇒ 新类零 `@OperateLog`,零写基线 **+200/轮 不变**
     (`update {}` / `delete-list {}` 停在控制器 1003;`create` 冒烟只发无 body)。
   ★ 存量语义照搬:列表走 `getDataOrThrow`、分页走 `toPageResult`;
     `updateTVipAreaCardById` **先写操作人再判 id**;`bathDelete` 的 `ids` 判空在写 opId 之前;
     `queryParkListByAreaCardNo` 查不到卡规则回 `code=0 + []`(不报错)、查到取第一条的
     `plGroupNo` 再查组下车场;`@Valid` 保留。
   ★ ⛔ 写样本红线:`saveTVipAreaCard` 服务层**无任何校验**、`{}` 会**真插一行**
     `t_vip_area_card` ⇒ 冒烟只发无 body(绑定层 400);`t_vip_area_card` 已纳入零写计数表做守卫。
★★ **2026-10-18 第一百切片:⚠B 里的 `BlackListController`(6 端点整类迁完)搬走**
   ⇒ **⚠B 60/238 → 59/232、已迁 147/873 → 148/879、未迁 64/245 → 63/239**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/black-list/*` + `GET /export-excel`),
     老路由 `/blackListMng`(黑名单车辆管理);**3 读 + 3 写**(含 1 响应流导出 + 1 multipart 导入),
     **整类 6/6 全部迁完,无 P3 尾巴**。
   ★ 与已迁的 `/admin-api/bill/black-list-rule`(第九十四切片)前缀不同,**不冲突、不合并**。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`,不读 `plNos`/`userAreas`/`resIds`。
     两处例外照搬:导出老代码把登录态判断整段注释掉、`orgId` 由前端下发(缺了 1003);
     导入 `sOpId` 取前端 query `userId`。
   ★ 老类**零 `@SysOpLog`** ⇒ 新类零 `@OperateLog`,零写基线 **+200/轮 不变**
     (`addArr {}`/`save {}` 在服务层前抛 500;`invalid {}` 主键 null ⇒ 0 行)。
   ★ 老 `@NoAuth` 导出按 §3.3 第 1 类收紧为 `bill:black-list:export` 且方法收紧为 GET;
     导入挂 `bill:black-list:import`。
   ★ 存量语义照搬:导出**不判** `beanBizResult.getData()` ⇒ data 为 null 时 NPE 500;
     缺 `orgId` 是 **500 + 1003**(POJO 绑定无必填,不是 400);`invalidBlackListCarForPage`
     无前置校验、`QueryBlackCarLimitRequest` 是死变量;导入 `HttpServletRequest` 是死参数、
     不校验列数(列少即 IndexOutOfBounds 500)、忽略服务返回值恒回 true。
★★ **2026-10-18 第九十九切片:⚠B 里的 `WhitelistController`(8 端点整类迁完)搬走**
   ⇒ **⚠B 61/246 → 60/238、已迁 146/865 → 147/873、未迁 65/253 → 64/245**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/whitelist/*` + `GET /export-excel`),
     老路由 `/whitelist`(白名单规则);**3 读 + 3 写 + 1 响应流导出 + 1 导入解析**,
     **整类 8/8 全部迁完,无 P3 尾巴**。
   ★ 与已有的 `/admin-api/bill/whitelist-rule-car-number` 前缀不同,**不冲突、不合并**。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`;
     `orgId` 用于分页过滤与新增组织列,`opId`/`name` 只写审计列。
   ★ 老类 3 处 `@SysOpLog` ⇒ 3 个 `@OperateLog`(add/update/delete,module=白名单规则);
     `OperateLogAspect` 抛异常也记账 ⇒ 冒烟 5 次写请求 ⇒ 零写基线 +195 → **+200/轮**。
   ★ 老 `@NoAuth` 两条按 §3.3 第 1 类收紧:导出 → `bill:whitelist:export`、
     multipart 导入 → `bill:whitelist:import`,**不进 `@PermitAll`**。
   ★ 存量语义照搬:`queryWhiteRuleById` 的 DAO 对不存在/`data_state=0` 的 id
     返回**非 null 全 null DTO** ⇒ `code=0` 而不是 404;`checkWhiteUnique {}`
     无必填校验直接进 SQL ⇒ 500;导出对 `result.getData()` 不判空 ⇒ NPE 500;
     `delete` 的 `setModifyDate` 写在 `userInfo != null` 判断之外;导入保留死变量
     `parkingLotList`、`whiteRNo` 取两次、严格 4 列校验,
     四条失败分支 = 500 / `1019` / `1003` / `1020`。
★★ **2026-10-18 第九十八切片:⚠B 里的 `IncomeCalculationNewController`(迁 5/9 端点,4 个读 `plNos` 留 P3)搬走**
   ⇒ **⚠B 62/255 → 61/246、已迁 145/856 → 146/865、未迁 66/262 → 65/253**。
   ★ 落在 `order` 域(`POST /admin-api/order/income-calculation-new/*` + `GET /export-excel` +
     `GET /export-calculation-excel`),老路由 `/incomeCalculation`(收益测算·新);
     **3 读 + 2 响应流导出 + 4 未迁**。
   ★ 与第三十八切片的 `IncomeCalculationController`(老 `/income/incomeCalculation`)**同类不同文件**,
     新路由按类名 kebab 落到 `income-calculation-new`,**不合并**。
   ★ 坑 22:4 个未迁端点都读登录态 `userInfo.getPlNos()`(`getSummationIncomeInfoforPlTypeByTime` /
     `getAllIncomeCalculationDataOfAreaByTime` / `getSummationIncomeByTimeOfArea` /
     `getSummationIncomeByTimeOfPl`)⇒ **P3 阻塞**;已迁 5 条只读前端 `plNos`。
   ★ 老类 9 个端点**一条 `@SysOpLog` 都没有** ⇒ 新类无 `@OperateLog`,**零写**。
   ★ 老 `@NoAuth` 两条导出按 §3.3 第 1 类收紧为 `order:income-calculation-new:export`;
     一个类两条导出 ⇒ 主导出(三 sheet)占 `/export-excel`,另一条(两 sheet)落
     `/export-calculation-excel`(§3.3 第 3 条)。
   ★ 存量缺陷照搬:缺 8 个金额参数(`dayMinAmount`…`nightMaxAmountAfter`)时
     XML 的 IF 不成立 ⇒ SQL 渲染成 `t.day_1_hour_num *  +`(空表达式)⇒ 500「系统异常」;
     `getInitDateList` 的 `timeType`、`queryIncomeCalculationGroupByPlNo` 的 `billType`
     都是 `Integer` 拆箱 ⇒ 不传即 500;`getInitDateList` 按周分支写成 end−end,
     差值恒 0 ⇒ 永远只出 1 行;导出 ① 先按 `plType` 拆 `plNos`:
     缓存里「存在但全 null」的车场让两个子集都空 ⇒ 服务层 1003,
     **完全不在缓存**的车场让 `getParkTypePlNos` 撞 null 元素 ⇒ NPE 500;
     导出 ② 整体 `try/catch(Exception)` ⇒ 失败是 **200 + 空 body**。
   ★ ⛔ 全类只读/导出,无写端点;`system_operate_log` 零写基线不变。
★★ **2026-10-18 第九十七切片:⚠B 里的 `ParkLotManageController`(迁 14/18 端点,3 个读 `plNos` 留 P3 + 1 个上传挂起)搬走**
   ⇒ **⚠B 63/273 → 62/255、已迁 144/838 → 145/856、未迁 67/280 → 66/262**。
   ★ 落在 `park` 域(`POST /admin-api/park/park-lot-manage/*` + `GET /export-excel`),
     老路由 `/parkLotManage`(停车场管理);**5 读 + 7 写 + 1 响应流导出 + 1 导入解析 + 4 未迁**。
   ★ 坑 22 安全:登录态只读 `orgId`/`opId`;`plNos` 相关 3 条未迁
     (`getParkLotNamesByUserId`/`refreshParkLotCache`/`getAreatreeByOpId`)⇒ **P3 阻塞**。
   ★ 老类 5 处 `@SysOpLog` ⇒ 新类 5 个 `@OperateLog`;`updateBlockForParkLot` 与
     `deleteParkLotPic` 老类**没有** `@SysOpLog` ⇒ 新类也不带(照搬,不"补上")。
   ★ 老 `@NoAuth` 三条按 §3.3 收紧:响应流导出 → `:export`、JSON 读 → `:query`、
     multipart 导入解析 → `:import`(无 token 401)。
   ★ ⏸ `uploadParkLotPic` 挂起:`fastDFSClientWrapper.uploadFile(...)` 运行期依赖
     阿里云 OSS/FastDFS 后端,属文件存储抽象(与 `ParkShareController` 同因)。
   ★ `queryParkingLotByPkNo` / `queryParkLotByCountryAndParkName` 与 `ParkingLbsController`
     (老 `monitor/ParkingLBSController` 的迁移)同义,**按本类老路由另挂一份**;
     两边去重留 P6 门户对账时统一决定,双跑期不先删。
   ★ ⛔ 7 个 `@RequestBody` 写端点:冒烟只发无 body(绑定层 400);
     本片 5 个 `@OperateLog` ⇒ `system_operate_log` **+195/轮**(原 +190 + 5)。
★★ **2026-10-18 第九十五切片:⚠B 里的 `VipCardRuleController`(迁 4/7 端点,3 个读 `plNos` 留 P3)搬走**
   ⇒ **⚠B 65/288 → 64/281、已迁 142/823 → 143/830、未迁 69/295 → 68/288**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/vip-card-rule/*`),老路由 `/vipCardRule`
     (会员卡规则管理);**1 读 + 3 写 + 3 未迁**。
   ★ 3 个未迁端点都读登录态 `plNos`:两个分页在请求体 `plNos` 为空时回填、
     `vipCardSelectParkTree` 直接用 `plNos` 当树根 ⇒ **P3 阻塞**;
     两个分页还带 `queryParkName` 回填(缓存优先 + 循环 N+1)。
   ★ 写端点只写 `opId`/`name`/`orgId`,**不读** `plNos`/`userAreas`/`resIds`
     ⇒ 坑 22 安全;老代码**不判** `userInfo == null`(无 token 时框架层已拦)
     ⇒ 新类照搬不判,不补 `AUTH_TOKEN_NOT_EXISTS`。
   ★ 3 处老 `@SysOpLog` ⇒ 3 个 `@OperateLog`(module `会员卡规则`);
     `checkVipCardPark` 老类无 `@SysOpLog` ⇒ 不加。
   ★ 存量语义照搬:`insert {}` 撞服务层 `BeanValidatorsUtils` ⇒
     **code=400「请求参数不正确:xxx必填」**;`update` 的 `id`/`cardNo` 不全
     ⇒ DAO 提前 `return 0`(0 行、不碰库);`delete` 是**下架**(改
     `card_status`/`data_state`),不存在 id 回 `0 + false`;
     `check` 缺 `plNos` ⇒ `<foreach>` 展成空 `in` ⇒ **500**。
   ★ ⛔ 冒烟对写端点只用 `{}`、不存在 id(`999999`/`NOPE_95`)与无 body(400),
     `t_vip_card` / `t_vip_card_park_sale_rel` / `t_vip_card_cust_info` 行数已实测不变。
★★ **2026-10-18 第九十四切片:⚠B 里的 `BlackListRuleController`(迁 6/7 端点,`parkTree` 留 P3)搬走**
   ⇒ **⚠B 66/295 → 65/288、已迁 141/816 → 142/823、未迁 70/302 → 69/295**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/black-list-rule/*`),老路由 `/blackListRuleMng`
     (黑名单规则管理);**3 读 + 3 写 + 1 未迁**(`parkTree`)。
   ★ 登录态只读 `orgId`/`opId`,**不读** `plNos`/`userAreas`/`resIds` ⇒ 坑 22 安全;
     `parkTree` 从登录态取 `plNos` 当车场树根 ⇒ **P3 阻塞、整条不迁**(本机路由 404)。
   ★ 老类**没有** `@SysOpLog` ⇒ 新类**不加** `@OperateLog`。
   ★ `@Value("${project.syscode}")` 照搬(老门户值 `zteits-bcp-portal`),
     **不要**硬编码 `"1001"`(那是另一个类的老写法)。
   ★ 控制器闸门照搬:`invalid` 的 `ruleNo` 判空(1030)在写操作人**之前**;
     `update` 的 `ruleNo`/`ruleName`/`actionType` 三字段判空(1030);
     `actionType` 在 update 是 **String**、save 是 Integer(存量不一致,照搬)。
   ★ 存量缺陷照搬:`queryBlackListRule` 查不到规则时 `BeanUtils.copyProperties(null, dto)`
     后继续 `getStartTime()` ⇒ **NPE 500**;`save {}` 先撞服务层
     1003「结束时间不能早于当前时间」,`save` 只给 `endTime` 撞
     `now.after(startTime)` 拆箱 **NPE 500**;`invalid` 不存在的 `ruleNo`
     是 0 行更新 `code=0 + true`(存量不校验命中行)。
   ★ ⛔ 冒烟对三个写端点只用「无 body(绑定层 400)」与「不存在 ruleNo(0 行)」样本,
     `t_black_list_rule` / `t_black_list_park_rel` / `t_black_list` 行数已实测不变。
★★ **2026-10-18 第九十三切片:⚠B 里的 `IncomeParkAndVipController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 67/302 → 66/295、已迁 140/809 → 141/816、未迁 71/309 → 70/302**。
   ★ 落在 `order` 域(`POST|GET /admin-api/order/income-park-and-vip/*`),
     老路由 `/income/incomeParkAndVip`(统计报表·临停车、会员卡收入);
     **5 读 + 2 响应流导出**(xlsx + PDF),无写端点、无挂起。
   ★ 登录态只读 `orgId`(仅 total 端点回填组织名),**不读** `plNos`/`userAreas`/`resIds`
     ⇒ 坑 22 安全,整类可迁、不涉及 P3。
   ★ 老类**没有** `@SysOpLog` ⇒ 新类**不加** `@OperateLog`。
   ★ 两条导出都是**直接写 `HttpServletResponse`**(不落临时文件、不过 FastDFS/模板)
     ⇒ 可直接迁;但异常语义相反:`/export-excel` 无 try/catch ⇒ 500 JSON、
     `/export-pdf` 整段 try/catch ⇒ 200 + 0 字节。`days` 只在 PDF 必填。
   ★ pay-type 两个端点的 DTO 无必填校验,`{}` 撞 `1 == null` 拆箱 NPE ⇒ 500;
     `reportTabTag` 只有 1/2/3 分支,4/9 是静默空结果。
   ★ 本机 `ParkingLotCacheUtil` 未命中这三个车场 ⇒ pay-type 三行全 null,
     冒烟只断言 code=0 与条数 3。
★★ **2026-10-18 第九十二切片:⚠B 里的 `IncomeChargerCheckTaskController`(迁 5/6 端点,导出挂起)搬走**
   ⇒ **⚠B 68/308 → 67/302、已迁 139/803 → 140/809、未迁 72/315 → 71/309**。
   ★ 落在 `order` 域(`POST /admin-api/order/charger-check-task/*`),
     老路由 `/incomeChargerCheckTaskManage`(收费员任务完成情况);**2 读 + 3 写 + 1 导出**。
   ★ 登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`
     ⇒ 坑 22 安全,整类可迁、不涉及 P3。
   ★ 老类**没有** `@SysOpLog` ⇒ 新类**不加** `@OperateLog`。
   ★ 本脚本按老类计数 ⇒ 挂起的导出也计入「已迁」的 6 个端点(同 AppVersionCheck 先例)。
   ★ ⏸ `exportIncomeChargerCheckTask` **挂起**:EasyExcel 模板写临时文件 →
     `fastDFSClientWrapper.uploadFileWithName(...)` → 回**文件 URL 字符串**;
     本机运行期无存储后端,且模板 `excelTemplate/ChargerCheckTask.xlsx` 未随迁。
   ★ 日期契约只吃 `yyyy-MM-dd HH:mm:ss`;`page` 缺起止日 → DAO generator
     空值守卫抛 RuntimeException ⇒ 500(存量缺陷,红线⑥不修)。
   ★ ⛔ `save` 的 DTO 无必填校验 ⇒ 冒烟绝不 POST;`bathDelete` 的 DAO 是
     `return 0` 死代码,回 false 不是安全保证。
★★ **2026-10-18 第九十一切片:⚠B 里的 `TdCActivityController`(迁 6/6 端点)搬走**
   ⇒ **⚠B 69/314 → 68/308、已迁 138/797 → 139/803、未迁 73/321 → 72/315**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/activity/*`),老路由 `/tdCActivityManage`;
     **2 读 + 4 写**,无导出;4 处 `@SysOpLog` → 4 个 `@OperateLog`。
   ★ 登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`。
   ★ `save`/`update` 是 **multipart/form**(不是 `@RequestBody`):空 body 会真进业务,
     `save` 无必填校验会真插 `td_c_activity` ⇒ 冒烟**只发 GET 验 405**;
     `update`/`bath` 停在服务层 1003「id不能為空!」,`delete {不存在}` 是 0 行更新。
★★ **2026-10-18 第九十切片:⚠B 里的 `TpPEqpErrorRecordController`(迁 6/6 端点)搬走**
   ⇒ **⚠B 70/320 → 69/314、已迁 137/791 → 138/797、未迁 74/327 → 73/321**。
   ★ 落在 `park` 域(`POST /admin-api/park/eqp-error-record/*`),
     老路由 `/tpPEqpErrorRecordManage`(设备异常记录管理);**2 读 + 4 写**,无导出。
   ★ 登录态只读 `orgId`(读端点查询条件)/`opId`(写端点操作人),
     **不读** `plNos`/`userAreas`/`resIds` ⇒ 坑 22 安全。
   ★ 老类**没有** `@SysOpLog` ⇒ 新类**没有** `@OperateLog`。
   ★ `save {}` 走到 `insertSelective` 被 `error_name` NOT NULL 挡回;
     `update {}` / `delete {id}` / `bath {ids:[]}` 生成无 SET 或 `id in ()` 的 SQL
     ⇒ 本机 500 但**一行不落**;`bath {ids:[不存在]}` 是 0 行 ⇒ `data=false`。
★★ **2026-10-18 第八十九切片:⚠B 里的 `BillingRuleController`(迁 9/9 端点)搬走**
   ⇒ **⚠B 71/329 → 70/320、已迁 136/782 → 137/791、未迁 75/336 → 74/327**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/billing-rule/*` + `GET /export-excel`),
     老路由 `/billingRule`(计费规则服务);**5 读 + 3 写 + 1 导出**。
   ★ 登录态只读 `orgId`/`opId`/`name`,**不读** `plNos`/`userAreas`/`resIds`
     ⇒ 坑 22 安全,整类可迁、不涉及 P3。
   ★ 3 处 `@SysOpLog` → 3 个 `@OperateLog`(`module = "计费规则服务"`);
     老导出 `@NoAuth` 按 §3.3 收敛为 `bill:billing-rule:export`。
   ★ 老方法名 `queryBilingRuleShowInfoByCollectNo` 少个 `l`,新路由段照抄
     `query-biling-rule-show-info-by-collect-no`。
   ★ ⚠️ 存量库排序规则冲突:`t_billing_rule_collect`(unicode_ci)与
     `t_billing_rule` / `t_billing_park_rule_rel`(general_ci)JOIN ⇒ MySQL 1267
     `Illegal mix of collations` ⇒ 本机 5 读 + 分页 + 导出**恒 500**
     (`sql/.dump/zt_bill.sql` 确认是源库真实形态;红线⑥不改 SQL、不改库)。
   ★ ⛔ 写端点冒烟只发无 body / 停在闸门或服务前置校验的样本;
     `t_billing_rule` / `t_billing_rule_config` / `t_billing_rule_collect` /
     `t_billing_park_rule_rel` / `t_billing_description` 实测一行不动。
★★ **2026-10-18 第八十八切片:⚠B 里的 `SpecialCarManageController`(迁 6/8 端点)搬走**
   ⇒ **⚠B 72/337 → 71/329、已迁 135/774 → 136/782、未迁 76/344 → 75/336**。
   ★ 落在 `bill` 域(`POST /admin-api/bill/special-car-manage/*`),老路由
     `/specialCarManage`(特殊车辆管理);**4 读 + 2 写、无导出**。
   ★ ⛔ 前 2 条 `/querySpecialCar`、`/initSpecialCarTree` **未迁**:
     老代码 `request.setPlNos(userInfo.getPlNos())` + 固定 `sourceTypes=[17,9]`
     ⇒ P3 `plNos` 数据权限模型。其余 4 条只写 `orgId`、2 条完全不读登录态
     ⇒ 坑 22 安全。
   ★ 老类**无 `@SysOpLog`** ⇒ 新类**无 `@OperateLog`**(照搬,不补)。
   ★ `saveSpecialCar` 按 `plNo` 逗号拆行 insert;`updateSpecialCar` 按 `carNumber`
     先删后插;`deleteSpecialCar` 是软删(`data_state='0'`)。
   ★ ⚠️ 存量 SQL 撞 MySQL 8:`viewSpecialCar` 的 `GROUP BY car_number` 选了
     非聚合列 `remarks` ⇒ `only_full_group_by` ⇒ 本机**恒 500**
     (老栈 5.7 正常;红线⑥不修,冒烟钉成已知行为)。
   ★ ⛔ 冒烟只用零写入样本:`save {specialCarList:[]}` 循环 0 次、
     `update {carNumber:ZZZ_NO_88}` 删 0 行不插、`delete {id:999999999}` 软删 0 行;
     `td_cl_special_car` 实测 500 行不变。
★★ **2026-10-18 第八十七切片:⚠B 里的 `ParkIllegalAreaBaseController`(整类 8/8 端点)搬走**
   ⇒ **⚠B 73/345 → 72/337、已迁 134/766 → 135/774、未迁 77/352 → 76/344**。
   ★ 落在 `park` 域(`POST /admin-api/park/park-illegal-area-base/*`),老路由
     `/parkIllegalAreaBaseManage`(停车违章区域基础信息管理);**3 读 + 5 写、无导出**。
   ★ 坑 22 **全类安全**:8 个端点只从登录态读 `orgId`/`opId`/`name`;唯一一处
     `getPlNos()` 是 `request.getPlNos()`(`saveParkIllegalAreaRel` 的**请求参数**,
     不是登录数据权限)⇒ 不需要等 P3。
   ★ 老类 `@NoAuth` 0 处;5 处 `@SysOpLog` ⇒ 5 处 `@OperateLog`。
   ★ `saveParkIllegalAreaRel` 的两道控制器前置校验照搬(`parkIllegalBaseId` 为空、
     `plNos` 为空 ⇒ `BizException(PARAMM_NULL, …)`),⛔ 都在 Service「先删后插」之前。
   ★ ⛔ 5 个写端点会**真插/真删主数据**(`save-base` 无校验注解;`save-rel` 先删旧关系)
     ⇒ 冒烟只发**无 body**(绑定层 400),`save-rel` 另发两道校验样本(控制器内 500)。
     零写实测 `parking_lot`/`parking_lot_areas`/`parking_lot_enclosed`/
     `parking_lot_geo`/`park_free_berth` 500/53/46/500/500 不变。
★★ **2026-10-18 第八十六切片:⚠B 里的 `CloseCarParkController`(迁 17/19 端点)搬走**
   ⇒ **⚠B 74/364 → 73/345、已迁 133/747 → 134/766、未迁 78/371 → 77/352**。
   ★ 落在 `park` 域(`POST /admin-api/park/close-car-park/*`),老路由
     `/parkListManage`(封闭车场管理);**10 读 + 7 写、无导出**。
   ★ ⛔ 前 2 条 `/QueryParkingList`、`/queryParkingParkList` **未迁**:
     老代码 `request.setPlNos(userInfo.getPlNos())` + 固定 `sourceType=[17,9]`
     ⇒ P3 `plNos` 数据权限模型。其余 17 条中只有 `areaBlockByAreaId`
     读登录态且只写 `orgId` ⇒ 坑 22 安全。
   ★ 老类 `@NoAuth` 0 处;3 处 `@SysOpLog` ⇒ 3 处 `@OperateLog`
     (`saveParkingListInfo` / `saveParkingAreaInfo` / `updateFreeBerthEnclosed`;
     另有 4 处老代码本就是注释状态,不补)。
   ★ 存量缺陷照搬(红线⑥不修):`view-parking-area {id:0}`(BeanUtils copy null)、
     `view-parking-area-by-id {ids:[]}`(空 `in ()`)、`init-area {}`(id 拆箱 NPE)、
     `view-free-berth-enclosed` 无封闭扩展行(`list.get(0)` 越界)、
     `view-parking-list-info` 车场不存在 均 500。
   ★ ⛔ 7 个写端点会**真插/真改/真软删车场与区域**(`saveParkingListInfo`
     无校验注解,`{}` 会生成新编码并真插一行)⇒ 冒烟只发 **无 body**(400);
     零写实测 `parking_lot`/`parking_lot_areas`/`parking_lot_enclosed`/
     `parking_lot_geo`/`park_free_berth` 500/53/46/500/500 不变,
     只有 `system_operate_log` +169/轮。
★★ **2026-10-18 第八十五切片:⚠B 里的 `KeyTopServiceController`(迁 7/8 端点)搬走**
   ⇒ **⚠B 75/372 → 74/364、已迁 132/739 → 133/747、未迁 79/379 → 78/371**。
   ★ 落在 `order` 域(`POST /admin-api/order/key-top-service/*`),老路由
     `/keyTopService`(科拓小管家);**4 读 + 3 写、无导出**。
   ★ ⛔ 第 8 条 `/parkTree` **未迁**:老 `parkTree()` 直接
     `plNos = userInfo.getPlNos();` 再构树(只留 KEYTOP/CLOUD_BILL)
     ⇒ P3 `plNos` 数据权限模型。其余 7 条只写 `orgId`/`opId`/`opName` ⇒ 坑 22 安全。
   ★ 老类 `@NoAuth` 0 处;4 处 `@SysOpLog` ⇒ 4 处 `@OperateLog`;
     `sysCode` 走 `@Value("${project.syscode}")`(已配 `zteits-bcp-portal`)。
   ★ `queryOrder` 按 §3.2 直接透传 `ParkOrderDTO`(老栈多拷一层
     `OrderParkForPayTypeDTO`,多出的 21 字段本就恒 null);分页参数照搬。
   ★ ⚠️ 存量服务层缺陷照搬:`int carType = request.getCarType()` 对 null
     拆箱 NPE ⇒ `query {}` 恒 500(老栈同款,红线⑥不修);冒烟正样本必须带 carType。
   ★ ⛔ 4 个写端点会**真出场 / 真补录并进 MQ / 真控闸机** ⇒ 冒烟只发
     **无 body**(400)与炸关口样本;零写实测 `order_park` / `park_in_out_record` /
     `order_modify_record` 500/500/0 不变,只有 `system_operate_log` +169/轮。
★★ **2026-10-18 第八十四切片:⚠B 里的 `TdCCustInvoiceSaleController`(迁 11/12 端点)搬走**
   ⇒ **⚠B 76/384 → 75/372、已迁 131/727 → 132/739、未迁 80/391 → 79/379**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/invoice-sale/*`),老路由
     `/invoiceSaleManage`(发票-销方管理);**6 读 + 5 写、无导出**。
   ★ ⛔ 第 12 条 `/relParkTree` **未迁**:老 `vipCardSelectParkTree` 直接
     `plNos = userInfo.getPlNos(); plNos.removeAll(relPlNos);` 裁剪可选车场树
     ⇒ P3 `plNos` 数据权限模型(先例 `VipCardCustInfoController` 14/15)。
   ★ 其余 11 条只写 `orgId`/`opId`/`createEmpid`/`modfiyEmpid` ⇒ 坑 22 安全。
   ★ 老类 `@NoAuth` 0 处;4 处 `@SysOpLog` ⇒ 4 处 `@OperateLog`;
     `saveOrUpdateInvoiceRelPlNo` 老类没有 ⇒ 不加。
   ★ 照搬:`queryRelParks` 空 `sysCode` 补 `"1001"`、`relplno` **无条件**
     `"1001"`;`bath-delete` 老 mapping 错拼逐字保留;
     `delete`/`bath-delete` 同时写父类 `sOpId(Long)` 与 `opId(String)`。
   ★ ⛔ **`save {}` 会真插一行销方**(服务层无任何必填校验)⇒ 冒烟只发
     **无 body**(绑定层 400);`relplno` 只用不存在的税号(删 0 行、不插)。
     零写实测:只有 `system_operate_log` +161/轮,两张发票表 4/4/4 不变。
★★ **2026-10-18 第八十三切片:⚠B 里的 `ManualInvoiceController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 77/391 → 76/384、已迁 130/720 → 131/727、未迁 81/398 → 80/391**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/manual-invoice/*`),老路由
     `/manualInvoice`(人工开具电子发票);**4 读 + 3 写、无导出**。
   ★ 登录态只碰 `orgId`/`loginCode`;`queryInvoiceSaleInfo` 的 `plNos`
     从结果集 `PayOrderParkingInvoiceDTO::getPlNo` 现算,不是数据权限字段。
   ★ 老类 `@NoAuth` 1 处(`queryPayOrderForParkingInvoice`)——★ 方法体自己就抛
     `AUTH_TOKEN_NOT_EXISTS`,调用方 `fillinvoice.js` 走 `sysAjax`(带 token)
     ⇒ §3.3 第一类,收敛为需登录;老类无 `@SysOpLog` ⇒ 无 `@OperateLog`。
   ★ 存量差异照搬:`park {}` 撞本机 MySQL 8 `only_full_group_by` ⇒ 500
     (存量 SQL,老栈 5.7 正常,红线⑥不修);`ticket` 缺 `invoiceType`
     拆箱 NPE ⇒ 500;`resend {}` 模板缺失 ⇒ `-100020`;`open {}` ⇒
     「BizResult 为 null」(老类 catch 里塞 null)。
   ★ ⛔ 三个写端点有 body 会**真发邮件 / 真落开票历史 / 真调航信开票队列**;
     冒烟只发无 body 与炸关口样本,`td_c_cust_invoice_his` / `_sale` /
     `_sale_park_rel` 行数已实测不变。
★★ **2026-10-18 第八十二切片:⚠B 里的 `GuideScreenController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 78/398 → 77/391、已迁 129/713 → 130/720、未迁 82/405 → 81/398**。
   ★ 落在 `park` 域(`POST /admin-api/park/guide-screen/*`),老路由
     `/guideScreen`(诱导屏管理);**5 读 + 2 写、无导出**。
   ★ 4 处读登录态但**只碰 `orgId`/`opId`/`opName`**;`plNos` 来自
     `guideScreenQueryService.getGuideScreenInfo` 的结果集,不是数据权限字段。
   ★ 老类 `@NoAuth` 1 处(`updateGuideScreenStates`)——★ 有**管理页面**调用方
     (`screenmanage.js:415` 走 `sysAjax`,带 token)⇒ §3.3 第一类,收敛为需登录;
     老类 `@SysOpLog` 1 处 ⇒ `@OperateLog` 1 处(UPDATE)。
   ★ 存量差异照搬:`statistics {}` 的 `WHERE 1=1` 被本机 Druid wall 拦 ⇒ 500;
     `get-info` 不存在的 guideNo 拆箱 NPE ⇒ 500;`update-states {}`
     撞 Example `andGuideNoEqualTo(null)` ⇒ 500。
   ★ ⛔ **`syn-all-guide-info` 有 body(连 `{}` 都算)会先打青岛三组 HTTP,
     再进 `@Transactional` 的先删后插**;冒烟只发无 body
     (`td_g_guide_screen` 97 行、`td_g_guide_park_rel` 320 行已实测不变)。
★★ **2026-10-18 第八十一切片:⚠B 里的 `BatteryRechargeCardController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 79/405 → 78/398、已迁 128/706 → 129/713、未迁 83/412 → 82/405**。
   ★ 落在 `pay` 域(`POST /admin-api/pay/battery-recharge-card/*`),老路由
     `/batteryRechargeCardManage`;**3 读 + 4 写、无导出**。
   ★ 6 处读登录态但**只碰 `orgId`/`sOpId`/操作人**;**无 `plNos`/`userAreas`/`resIds`**。
   ★ 老类 `@NoAuth` 1 处(分页查询)——但方法体自己就抛
     `AUTH_TOKEN_NOT_EXISTS`,新栈收敛为需登录;老类无 `@SysOpLog` ⇒ 不加 `@OperateLog`。
   ★ `save` 与 `account-charge-wx-pay` 保留 `@Valid`(`{}` → 400);
     `update {}` / `bath-delete {}` ⇒ 500「存量错误码 1003 / id为空!」。
   ★ `query-account-details-for-page` 撞本机 MySQL 8 `only_full_group_by` ⇒ 500
     (存量 SQL,老栈同样炸,红线⑥不修)。
   ★ ⛔ **`account-charge-wx-pay` 有完整 body 会真建充值单并调微信下单**;
     冒烟只发无 body 与 `{}`,两张电池卡表行数已实测不变。
★★ **2026-10-18 第七十七切片:⚠B 里的 `TpPEqpGatewayController`(迁 7/7 端点)搬走**
   ⇒ **⚠B 83/433 → 82/426、已迁 124/678 → 125/685、未迁 87/440 → 86/433**。
   ★ 落在 `park` 域(`POST /admin-api/park/eqp-gateway/*`),老路由
     `/tpPEqpGatewayManage`(道闸网关管理);**3 读 + 4 写、无导出**。
   ★ 类路由按「去掉表前缀 `TpP` 再 kebab」定为 `eqp-gateway`
     (先例 `TpPNetworkCardController` → `/park/network-card`);
     方法段 kebab 老 mapping 逐字(同第七十五/七十六切片),`bath` 存量错拼保留。
   ★ 7 处读登录态但**只碰 `orgId`/`sOpId`/`sOpName`**:statistic/list/page/save/update
     写 `orgId`,delete/bathDelete 只写操作人;**无 `plNos`/`userAreas`/`resIds`**。
   ★ 存量差异照搬:`delete` 同时写父类 `setsOpId` 与子类 `setOpId`(服务读 `getOpId()`);
     `batchDelete` 只写 `setsOpId` ⇒ 操作人名称恒 null;
     `bathDelete {}` 撞 `TpPEqpGatewayExample.andIdIn(null)` 的
     `Value for id cannot be null` ⇒ 500。
   ★ ⛔ **`save-tp-p-eqp-gateway` 有 body(哪怕 `{}`)就会真插 `tp_p_eqp_gateway`**
     (`TpPEqpGatewayRequest` 无任何校验注解);冒烟只发无 body,该表行数已实测不变。
⚠️ **排期用「✔ 3/3(0 个可直接开搬,全是外部副作用/文件存储前置)+ ⚠A 0/0(已清零)+ ⚠B 1/1(orgId 部分已解禁,剩余均带硬避让)」,别用 4/4**——
"就绪"只回答了"服务齐不齐",不回答"搬过来数据对不对"。端点数**不能**当"好迁"的信号:
`ChargerManageController` 与 `DutyRosterController` 是端点最多的两个,也是坑 22 命中最多的两个(各 22 处),
且这两个都在 **⚠B**(真读登录态)——**两者已分别在第六十六/第六十七切片搬走**;
当前队列里端点最多的是 `ParkLotManageController`(18,⛔ 读登录 `plNos` 三处 = P3 数据权限),其后 `VipCardCustInfoController`(15,**第七十四切片已搬走 14 个**,余 1 条「下载月卡购买协议」⛔ 依赖 poi-tl + classpath 下 docx 模板 + 本机硬编码输出目录)、
`VipCardAuthPersonBjxcController`(12,**已于第七十一切片搬走**)、`TdCCustInvoiceSaleController`(12,⛔ 读登录 `plNos` = P3 数据权限)。
"ids 大"才是危险信号,"endpoints 大"只是工量大。

退出码:
  0 = 盘点本身是可信的(扫到了 Controller、也扫到了新栈的接口与实现)
  1 = **盘点不可信**:老门户目录不存在、或新栈一个接口/一个实现都没扫到。
      ⚠️ 退出码**不是**"有没有缺口"——缺口是这份报告的**结论**,不是它的失败。
     (把"有缺口"当失败的话,这个脚本会恒返回 1,等于没有退出码。)

⚠️ 四个必须保留的做法,别"顺手简化":

1. **统计端点前先剥注释 —— 而且必须把老写法 `@RequestMapping` 一起算进来。**
   两个独立的坑,**第一版两个都栽了**,数字差一倍:
   - *剥注释*:`invoice/InvoiceController` 看着 18 端点 / 0 会话 / 0 `@NoAuth`,
     实际整个类是注释掉的(`//@RequestMapping("/invoice")`)。不剥就会把死代码排进待迁清单。
   - *算 `@RequestMapping`*:老工程的**主流写法**是
     `@RequestMapping(value = "/getFeedbackType", method = RequestMethod.POST)`,
     **不是** `@PostMapping`。只匹配 `@(Get|Post|Put|Delete)Mapping` 会漏掉一大半端点,
     并把**活着的控制器误判成死代码**(当时把整个 `personpc/**` 判成死代码,
     实际 `PersonPCSuggestController:57`、`PersonPCAcctController:79` 都是活的)。
   - *别把类级映射算成端点*:`@RequestMapping("equipmentManagement")` 和
     `@PostMapping("/saveChannelInfo")` 在正则眼里是一个东西,于是**每个带类级映射的
     Controller 都多算 1 个端点**(`EquipmentManagementController` 数出 11,实际 10)。
     现在 `count_endpoints()` 按"类声明之前 = 类级、之后 = 方法级"分开数。
   - 教训:**"剥注释"只解决"假活",还要再解决"假死"和"数多了"**。任何结论性的数字
     ("X 个是死代码"、"某类有 N 个端点")**写进文档前一定要拿一个真实文件手数核对**
     —— 这三个 bug 全是这么发现的:`personpc` 是随手 grep 一个方法签名发现的,
     端点数是对着 `closecarpark` 那 5 个类逐行数出来的。
2. **判定"某个 Controller 是否已迁"不靠文件名,也不靠新类名。** 两条都不可靠:
   - 新类普遍按路由约定**改名**(老 `TpPInspectRecordController` → 新 `InspectRecordController`),
     按新类名反查会把已迁的**全判成未迁**(只认出 13 个,实际 23 个);
   - 老工程大小写不统一(老 `monitor/ParkingLBSController` → 新 `ParkingLbsController`),
     逐字符比对同样漏。
   本脚本用「新类 javadoc 抬头里提到老类名」+「类名忽略大小写相同」两路合并
   (见 `provenance_mentions` / `PROVENANCE_PAT`,含"必须在小标题之前截断"的原因——
   不截断会把 `ParkLotExtendController` 里那句"与老 `ParkLotManageController` 的端点不混在一起"
   当成已迁证据,把 19 个端点的 `ParkLotManageController` 从待办队列里藏掉)。
   **但这是启发式**:真要确认某个类迁没迁,`grep` 那个老名字,别信计数。
3. **"接口不在新栈"和"无实现"要分开报。** 前者要补 `parking-module-api` 的契约,
   后者要搬一个 ServiceImpl 进某个 module——是两种完全不同的工。
4. **实现类是按简单类名匹配的**(`class XxxImpl implements Xxx`)。
   老工程存在同名不同包的重复类,所以这是**宽松匹配**:它只证明"有一个实现",
   **不证明**实现落在正确的 module 里、也不证明它被 Spring 扫到了。
   就绪度是**必要不充分**条件——真迁的时候仍然要起 Jar 打端点(`tools/api_smoke.py`)。
5. **"服务就绪"之外必须再报坑 22**(`idSpaceHits`)。只报就绪会给人一个 189 个的"待办队列",
   实际能开搬的只有三分之一——**把半个队列说成可做,比不说更糟**(会被当成排期依据)。
   判据是 `LEGACY_ID_PAT`(从登录态读 `getOrgId()/getPlNos()/getUserAreas()`),
   保守但机械;`setOpId(...)` 这类只写审计列的**不**算命中(见 §3.1 第 1 步的判据)。
6. **坑 22 必须切两刀,只切一刀会漏掉 12 个可搬的类。** 第一刀(`LEGACY_ID_PAT`)的
   正则**没有接收者概念**:`request.getPlNos()`(前端请求体,与登录态无关)与
   `userInfo.getPlNos()`(登录态)命中一模一样。于是 ⚠ 档里混进了 12 个 / 61 端点
   "整类一处都没读登录态"的**纯误报**(第三十五切片实测)。第二刀 `IDENT_READ_PAT`
   (`getUserInfo\\s*\\(|SessionCommUtil|getUserInfoForPersonPC\\s*\\(`)把它们摘出来成 ⚠A。
   ⚠️ ⚠A **不等于"已核销"**:脚本只证明"整类没读登录态",仍要人肉看一眼命中行落在谁身上
   (`idSpaceLines` 已经把行号+原文打出来了,几秒的事),核完登记进 `IDSPACE_FALSE_POSITIVE`。

用法:
    python3 tools/p2_ready_check.py                 # 从仓库根目录跑(parking-cloud/ 的父目录)
    python3 tools/p2_ready_check.py --json          # 机器可读,供挑选下一批用
    python3 tools/p2_ready_check.py --top 40        # 只看端点最多的前 40 个就绪项
    python3 tools/p2_ready_check.py --legacy-root /path/to/zteits-clouds \\
        --new-root /path/to/parking-cloud
"""
import argparse
import glob
import json
import os
import re
import sys

DEFAULT_LEGACY = "zteits-clouds/zteits-bcp-portal/src/main/java/com/zteits/bcp/portal/web"
DEFAULT_NEW = "parking-cloud"

# ⚠️ 第一百五十四切片修正(2026-10-19):老正则只认 `class Xxx implements Yyy`,
# 漏掉「`class Xxx extends Base implements Yyy`」这种**先继承再实现**的形状。
# 新栈里有 6 个接口就是因此被误判成「无实现」(`PubParkOrderQueryService`、
# `RefundService` 等,共 8 个 Controller / 37 端点被错挡在「缺口」档)。
# 另外老新版扫描只 glob `*Impl.java`,漏掉 `RefundServiceIpml.java`(文件名把 Impl 拼成 Ipml)。
IMPL_PAT = re.compile(r'class\s+(\w+)(?:\s+extends[^{]*?)?\s+implements[^{]*?(\w+)')
IFACE_PAT = re.compile(r'public\s+interface\s+(\w+)')
# 新栈 `api.service.*` 包下的**类**(不是接口)。存量 Controller 会 import 它们当入参
# (典型:`RenewVipAreaCardOperReq` 放在 `api.service.pay.viparea.oper` 下但 `extends BaseRequest`)。
# 这些 import 只说明"传参用到这个 DTO",不构成"缺服务",不能算进 gaps。
API_CLASS_PAT = re.compile(r'\b(?:public\s+|final\s+|abstract\s+)*class\s+(\w+)')
SVC_IMPORT_PAT = re.compile(r'import\s+(com\.zteits\.clouds\.api\.service\.[\w.]+);')
ENDPOINT_PAT = re.compile(r'@(?:Get|Post|Put|Delete|Request)Mapping\(')
# 坑 22 的机械信号:老 Controller **从登录态读**这三个身份字段。它们在新栈里一个是新 id
# (`system_org.id`,seed 只有 1 行「总公司」)、一个是空集合,拿去**过滤结果集**就静默少数据。
# 只认 `get*`(读登录态)——`setOpId(...)` 写审计列不在此列,而
# `request.setOrgId(userInfo.getOrgId())` 这种"读完就喂给下游当过滤条件"的写法因为含
# `getOrgId()` 仍会被抓到。== 0 才认为"坑 22 安全"(见 §3.1 第 1 步)。
LEGACY_ID_PAT = re.compile(r'get(?:OrgId|OrgIdList|PlNos|UserAreas|UserAreaList)\s*\(')
# ⚠️⚠️ `LEGACY_ID_PAT` **不知道 `get*()` 是打在谁身上的**——它连**请求体**一起抓。
# 老 Controller 里 `request.getPlNos()`(`request` 是前端传进来的请求体)、
# `dto.getOrgId()`(下游返回的 DTO)都会命中,**与登录态毫无关系**。
# ⇒ 它是个"宁可多报"的**筛子**,不是判据。第三十五切片实测:⚠ 档 124 个里有
#   **12 个 / 61 端点**整类**没有任何一处**读登录身份(`IDENT_READ_PAT` 命中 0),
#   全部是机械误报——所以本脚本在 ⚠ 档内部再切一刀成 ⚠A / ⚠B(见 main()):
#     ⚠A(`hasIdentitySource == False`)= **机械误报候选**,可开搬,但每个类仍要
#        人肉核对命中行落在谁身上(`idSpaceLines` 已经把行号和原文打出来了,几秒的事);
#     ⚠B(`hasIdentitySource == True`)= 真读了登录态,还要再往下追"读到的值有没有进 SQL"
#        (第三十三切片:`ParkInOutRecordController` 的 `setTopOrgId` 是**注释掉的** INERT)。
IDENT_READ_PAT = re.compile(r'getUserInfo\s*\(|SessionCommUtil|getUserInfoForPersonPC\s*\(')
# 新类注释里对老类的指认:反引号或 `/` 紧跟的 `XxxController`(见 collect_new_stack 注释②)。
# 只在**类 javadoc 的第一段**里找(`provenance_mentions` 负责切段),因为约定是
# 「管理后台 - XXX Controller(老门户 `path/XxxController`,路由 …)」。
# ⚠️ **不切段会误判**:`ParkLotExtendController` 在后面的说明里写了
#    「与老 `ParkLotManageController` 的端点不混在一起」——那是在**划清界限**,
#    结果把 19 个端点的 `ParkLotManageController` 误标成"已迁"从待办队列里藏掉了
#    (而它恰恰是 §4.3 高风险批次里那个不能漏的)。
# ⚠️ 它仍是启发式:切段后仍可能在某类的第一段里提到**另一个**老类名。
#    反过来的错(把已迁的算进待办)只浪费时间,这个方向的错(把未迁的藏起来)会**漏活**,
#    所以确认某个类迁没迁,一律 `grep` 那个老名字,别信这里的计数。
PROVENANCE_PAT = re.compile(r'[`/]([A-Za-z0-9_]+Controller)\b')


def strip_comments(src):
    """剥掉块注释与整行行注释——见模块注释第 1 条。"""
    s = re.sub(r'/\*.*?\*/', '', src, flags=re.S)
    return re.sub(r'^\s*//.*$', '', s, flags=re.M)


def strip_comments_keep_lines(src):
    """同 `strip_comments`,但块注释**只清空字符、保留换行**。

    为什么需要它:上面那个 `re.S` 替换会把多行注释整段删掉、**连带删掉里面的换行**,
    于是"剥注释后的第 N 行"和**文件里真正的第 N 行**就不是一回事了。剥注释后打行号
    (`idSpaceLines`,给人核对 `getPlNos()` 打在谁身上用)必须对得上编辑器里看到的行号,
    否则人肉核对要重新数一遍——那这一步就白做了。
    两者**非换行字符完全一致**,所以 `LEGACY_ID_PAT` 在两个视图上命中数相同。
    """
    s = re.sub(r'/\*.*?\*/', lambda m: re.sub(r'[^\n]', '', m.group(0)), src, flags=re.S)
    return re.sub(r'^\s*//.*$', '', s, flags=re.M)


CLASS_PAT = re.compile(r'\b(?:public\s+|final\s+|abstract\s+)*class\s+\w+')
# 类级 `@RequestMapping("channelManagement")` 的路径。把它也当"指认",因为**第三种**
# provenance 写法认的是**路由**不是类名:`(老门户 \`channelManagement\`)`。
ROUTE_PAT = re.compile(r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?"([^"]+)"')
# 注释抬头里的反引号词(`` `channelManagement` `` 这种)。见 new_stack_mentions。
BACKTICK_PAT = re.compile(r'`([A-Za-z0-9_./\-]+)`')


# ── 已判定表(★ 人工读源码的结论,按**老类名**索引;脚本只做标注,不改 ✔ 的机械判定) ──
# 为什么要有这张表:✔ 的判据只有「服务齐 + 没读身份字段」两条,**不回答能不能交付**
#   (第三十四切片实测:✔ 5 个 / 11 端点**没有一个是能直接开搬的**)。
# 规矩:**每判定一个类就登记在这里**,否则下一批还会把同一批类当「可开搬」重走一遍。
ADJUDICATED = {
    "RefreshCacheController": (
        u"✅ 已搬(第四十六切片)",
        u"4 个端点全 `@NoAuth` + 老代码写死的 32 位共享密钥(照搬违红线 11)⇒ 按第三十四切片 ④ 的"
        u" **(B) 配置化**落地:期望值走 `parking.refresh-cache.token`(本地 profile 给测试值、生产不配即"
        u" fail-closed),代码里零密钥;调用方不登录 ⇒ §3.3 第 2 类:`**.controller.app.**` → `/app-api`"
        u" + `@PermitAll`(注解扫描自动进白名单)。落 `parking-module-system`,路由 `/system/refresh-cache`。"
        u"★ 4 条只写 Redis、不写 DB;token 失败的「成功码 + data=token不正确」存量形状照搬"),
    "PersonPCAuthController": (
        u"⏸ 等个人客户登录桥",
        u"类级 `@NoAuth`;`login` 把 `request.getSession().getId()` 当 token 写 `PersonSessionEnum`,"
        u"`getUserInfo`/`logout` 读同一 session —— 新栈 Redis Token + Spring Security **不建 session**,"
        u"且全仓**没有任何地方写** `PersonSessionEnum.USER_INFO`(只有 3 处读)⇒ 照搬即恒失败;"
        u"`sendValidatorCode` 还会走 `MessageCodeUtil.sendSms` **真发短信**;"
        u"`uploadUserPic` 的 `@RequestMapping` **被注释掉** ⇒ 它不是端点"),
    "PersonOrderBackWxPayController": (
        u"⏸ 等个人客户登录桥",
        u"`sessionCommUtil.getUserInfoForPersonPC()` **只读 HTTP session、没有 LoginBridge 兜底** "
        u"⇒ 照搬恒抛 1003「获取客户信息失败」(先例 `PersonPCCarNumController#getPersonCarPC` 已如此记账);"
        u"且它会**真建微信支付订单**(`aliWxPayService.wxJSAPIPay`)"),
    "UploadController": (
        u"⏸ 等文件存储抽象",
        u"`/upload/uploadPic` 是 `@NoAuth` + `FastDFSClientWrapper.uploadFile`;新栈里 "
        u"`FileStorageServiceImpl.uploadFile` **整段被注释掉、直接 return null** "
        u"(先例 `ParkShareController#insertParkingBerthShare`、`AppVersionCheckController#saveAppVersion`)。"
        u"⚠️ 同栈里那些 `MultipartFile` 的 excel 导入端点**不算**这个卡点——卡的是上传到存储后端"),
    "HomeController": (
        u"⛔ 不适用",
        u"14 行 `@Controller`:`@RequestMapping(根路径)` → `return redirect:swagger-ui.html`,"
        u"**不是 JSON 接口**;新栈 springdoc 的 UI 在 `/swagger-ui`、knife4j 在 `/doc.html`,"
        u"根路径也没有别的映射 ⇒ 判定「不迁」,不是「等」(要么落成文档页,要么不交)"),
}


# ── ⚠A 档(机械误报)核销表 ──
# 与 `ADJUDICATED` 分开两张表,因为**回答的是不同问题**:
#   `ADJUDICATED` 管 ✔ 档("没读身份字段"的类到底能不能交付);
#   `IDSPACE_FALSE_POSITIVE` 管 ⚠A 档("`LEGACY_ID_PAT` 命中了,但整类没读登录态"的类,
#     命中行究竟打在谁身上)。规矩同样是**每核销一个类就登记在这里**,否则下一批会重走一遍。
# ★ 2026-10-16 第三十五切片:⚠A 候选 12 个 / 61 端点逐类读完命中行,**结论全是误报**
#   (命中行无一例外落在请求体 `request.getPlNos()` / 局部变量 `req.getOrgId()` /
#    下游 DTO `dto.getOrgId()` 上);其中 `EqpIncomeStatisticController` 本片已搬走 ⇒ 余 11 个 / 58 端点。
IDSPACE_FALSE_POSITIVE = {
    "EqpIncomeStatisticController": (
        u"✅ 已搬(第三十五切片)",
        u"3 端点搬进 `park/eqp-income-statistic`;命中行 `recordRequest.setPlNos(request.getPlNos())`"
        u"(`request` = 前端请求体 `IncomeRecentStatisticRequest`)"),
    "RealTimeMonitorController": (
        u"✅ 已搬(第四十四切片)",
        u"2 处全是 `null == req.getOrgId()` —— `req` 是**方法入参**(前端请求体 `ParkDutyInfoQueryReq`),"
        u"只做「没传就不查」的守卫,不是登录态;全类 **0 处** `getUserInfo(`/`SessionCommUtil`。"
        u"10 端点拆成**两个新类**(本栈前缀由包路径决定,一个类不能两栖):管理端 8 条进 "
        u"`park/real-time-monitor`(+ 导出收紧为 `park:real-time-monitor:export`);"
        u"`queryParkLotInfoByOrgId` / `...ForHS` 两条**老 `@NoAuth` 是第 2 类**(外部黄石官网,"
        u"`parkingcloud` 的两个分支都搜不到调用方)⇒ `/app-api` + `@PermitAll` + 白名单。"
        u"★ 精确口径更正第四十三切片 ⑫ 的预判:**两条都是第 2 类**,不是只有 `ForHS`。"),
    "BerthsStatisticController": (
        u"✅ 已搬(第四十三切片)",
        u"9 端点进 `park/berths-statistic`(⚠️ 不是 `BerthStatisticController`);"
        u"`request.getPlNos()` 两处(老文件 `:362`/`:363`)里 `request` 是**方法入参**"
        u"`ParkingLotUseStatisticForPageRequest` = 前端请求体,一处比 size、一处 for 遍历,"
        u"全类 **0 处** `getUserInfo(`/`SessionCommUtil`;"
        u"4 条导出全是老 `@NoAuth` ⇒ 按 §3.3 **第 1 类**收紧(一个类 4 条导出 ⇒ 主导出占 "
        u"`GET /export-excel`,余三条给 `-detail`/`-month`/`-month-detail` 后缀)"),
    "ReconciliationManagementController": (
        u"✅ 已搬(第四十二切片)",
        u"两处 `CollectionUtils.isEmpty(request.getPlNos())` / `req.getPlNos()`,均为请求体空判"
        u"(`request`/`req` 是**方法入参**),全类 **0 处** `getUserInfo(`/`SessionCommUtil`;"
        u"7 端点(5 读 + 2 导出)搬进 `order/reconciliation-management`。"
        u"★ 老类 `extends ReconciliationBaseController`(那个只有 `getPayOrderTypeName(Integer,Integer)`"
        u"的 140 行基类)——新栈没有基类可继承,那段 switch **原地抄进新控制器**(⛔ 别改成枚举/工具类)。"
        u"★ 两条导出在老门户上都是 `@NoAuth` ⇒ 按 §3.3 **第 1 类**(管理页面导出)收紧成 "
        u"`GET /export-excel` + `GET /export-detail-excel` + `@PreAuthorize('order:reconciliation-management:export')`;"
        u"5 条读用 `order:reconciliation-management:query` ⇒ **7 条无 token 全 401**。"
        u"⚠️ 两条源表样本都停在过去(`td_b_pay_blncac_result` 2018-01 且 `pl_no` 全空串、"
        u"`pay_order` 停在 2017-09-25 且 `pay_type=6`)⇒ 正样本窗口得手动指定,**不是迁移回归**"),
    "IndexParkSituationController": (
        u"✅ 已搬(第三十七切片)",
        u"5 处全打在 `request.getPlNos()` 上:3 处空判(老 `:100`/`:230`/`:286`)+ "
        u"2 处 `setPlNos(request.getPlNos())` 转投下游请求(老 `:111`/`:117`)——"
        u"`request` = 前端请求体,全类 **0 处** `getUserInfo(`/`SessionCommUtil`;"
        u"6 端点搬进 `order/index-park-situation`(老类**没有** `@NoAuth` ⇒ 一律 `@PreAuthorize`)"),
    "VehicleFlowController": (
        u"✅ 已搬(第三十九切片)",
        u"3 处 `request/parkingRunningStatisticForPageRequest.getPlNos()`,均为请求体;"
        u"⚠️ 本类**两条端点在老门户上是 `@NoAuth`**(`exportCarFlowForList` 与 "
        u"`carFlowPrint`——后者 `@RequestMapping` 连 `method` 都没写)⇒ 已按 §3.3 **第 1 类**"
        u"(管理页面导出)收紧成 `GET /export-excel` + `GET /export-pdf`,同一权限点 "
        u"`park:vehicle-flow:export`;5 端点搬进 `park/vehicle-flow`。★ 本片**新建 1 个老门户独有 VO**"
        u"(`com.zteits.bcp.portal.vo.report.runtime.VehicleFlowVO`,只喂 PDF、不进响应体)"),
    "IncomeCalculationController": (
        u"✅ 已搬(第三十八切片)",
        u"1 处 `CollectionUtils.isEmpty(req.getPlNos())`(请求体,控制器里那条是服务层同句的复制粘贴);"
        u"⚠️ 本类另带 `@NoAuth` 导出端点(`/exportToIncomeCalculationSummaryNewOfllwTwo`,"
        u"`@RequestMapping` 无 `method`)⇒ 已按 §3.3 **第 1 类**(管理页面导出)收紧成 "
        u"`GET /export-excel` + `@PreAuthorize('order:income-calculation:export')`;"
        u"5 端点搬进 `order/income-calculation`。★ 本片同时是**坑 25 的第二处修点**"
        u"(两条走 `PageHelper.startPage` 的 select 去掉 `statementType=\"STATEMENT\"`)"),
    "IncomeAllStatisticController": (
        u"✅ 已搬(第四十切片)",
        u"5 处 `request/incomeStatisticRequest.getPlNos()==null`,全是请求体空判(⚠A 机械误报);"
        u"5 端点(5 读,无 `@NoAuth`、无 `@SysOpLog`)搬进 `order/income-all-statistic`。"
        u"★ 本片**新建 3 个老门户独有 VO**(`IncomeStatisticTrentReportVO` / "
        u"`IncomeStatistic2MonthFeeVO` / `IncomeStatisticDayFeeVO`)。"
        u"⚠️ 其中 4 条端点的老 SQL 用 `GROUP BY x DESC`(MySQL 8.0.13 已移除的语法)⇒ "
        u"本机(MySQL 8.x)恒 500,**老栈同样 500**,按红线⑥ **不改 SQL**,"
        u"改以离线桩测(剥 jar + `Proxy` 桩 + 反射注入,66 断言)证明控制器逻辑等价"),
    "OrderParkStatisticController": (
        u"✅ 已搬(第四十一切片)",
        u"1 处 `request.getOrgIds()/getPlNos()` 空判(请求体,⚠A 机械误报);"
        u"4 端点(4 读、无导出、无 `@SysOpLog`)搬进 `park/order-park-statistic`。"
        u"★ 老类 3 条大屏 `@NoAuth`(`queryLastOrderPark` / `queryLastOrderParkByPlNos` / "
        u"`queryOrderParkNumForLastWeek`)按 §3.3 **第 1 类**(老门户自己的管理页面)收紧成 "
        u"`/admin-api` + `@PreAuthorize('park:order-park-statistic:query')`(第 4 条原本就没有 "
        u"`@NoAuth`,也纳入同一个安全档)⇒ **4 条端点无 token 全 401**。"
        u"⚠️ 别与 `RealTimeMonitorController` 里**外部黄石官网**那条 `@NoAuth` 互推——"
        u"那个才是第 2 类(`/app-api` + `@PermitAll` + 白名单)。"
        u"⚠️ EP2 / EP3 / EP4 在本机**恒空**(样本表停在过去 + 窗口按今天算),"
        u"**不是迁移回归**:判定靠「同一条 SQL 直连换窗口」,见 AGENTS.md 第四十一切片 ⑤"),
    "VipCardQueryController": (
        u"✅ 已搬(第四十五切片)",
        u"1 处 `whitelistRuleForPageRequest.setPlNos(request.getPlNos())`(请求体转投下游;"
        u"全类 0 处 `getUserInfo(`/`SessionCommUtil`)⇒ 3 端点(3 读、无导出、无 `@NoAuth`、无写)"
        u"搬进 `order/vip-card-query`。★ 更正旧结论:**没有**「要另建 2 个老门户独有 VO」这回事——"
        u"`VipCardStatisticIndexDTO`/`AuthCarNumberDTO` 早已在 `parking-module-api`,"
        u"`VipCardStatisticFeeVO` 早已随 `parking-legacy-common` 搬入且**从未被 new 过**"
        u"(第 3 条端点的真实线格式是 21 字段的 `IncomeStatisticFeeVipCardDTO`,老签名是假的)"),
    "WxcpMessageController": (
        u"✅ 已搬(第三十六切片)",
        u"2 处 `request.getOrgId() == null` 空判(请求体)⇒ 落 `/app-api` + `@PermitAll`"
        u"(§3.3 **第 2 类**:企微推送 H5 真免认证,已进白名单登记表 + §2af (b) 正面钉住)"),
    "BerthStatisticController": (
        u"✅ 已搬(第三十六切片)",
        u"1 处 `appOrgRequest.setOrgId(dto.getOrgId() + \"\")` —— `dto` 是**上游服务返回的 DTO**"
        u"(全类 0 处登录态读取);两个端点的 `@NoAuth` 已按 §3.3 **第 1 类**收紧成 `@PreAuthorize`"),
}


# 缺口档的人工结论(**不可迁**):机械判定仍是「缺实现」,但人读完源码判定**补不了**。
# 与 ADJUDICATED 的区别:那张表管"就绪且未迁",这张表管"缺实现",两者都只做**标注**。
GAP_ADJUDICATED = {
    "OrderExamineController": (
        u"⛔ 不可迁:半合并壳(实现只在未合入的特性分支)",
        u"老门户 `orderexamine/OrderExamineController`(2021-08 高位订单数据检查,2 端点)在"
        u" `beijing_master` / `beijing_dev` / `chifeng_master` / `chifeng_dev` **四个生产分支上只有"
        u" Controller + `clouds.api` 接口与 DTO,没有任何实现**:"
        u"① 全仓(HEAD)`implements OrderExamineService` = 0;② Dubbo 只有 consumer,**没有 provider**;"
        u"③ `UserInfo.examinePlNos` **全仓无一处 set**(登录时不填)⇒ `queryLists` 恒抛 `AUTH_PL_NOS_EMPTY`,"
        u"`updateStatus` 恒「no provider available」;④ 仓库里也没有 `order_examine_data` / `sys_op_examine_rel` 的 DDL。"
        u"实现(`OrderExamineServiceImpl` + `OrderExamineData(+Example)` + 两套 Mapper/XML + "
        u"`SysOpExamineRelService`)**只存在于未合入的 `origin/chifeng_dev_high_video_examine`**(最后提交 2021-08)。"
        u"⇒ 照搬 Controller 在新栈必因缺 Bean 启动失败;补实现 = 把这个未发布特性整块搬进新栈(约 3000 行生成代码 +"
        u" `examinePlNos` 登录桥 + 两张表),**属 P4 特性移植,不是 P2 Controller 迁移**。"
        u"→ 若要上线「高位视频审核」,先由产品确认,再从该分支整体移植。"),
    "CustPersonCreditController": (
        u"⛔ 不可迁:服务实现整文件被注释(两栈同款)",
        u"老门户 `custperson/CustPersonCreditController`(5 端点:分页/新增/审核/修改/批量删)。"
        u"`CustPersonCreditServiceImpl.java` 在**老仓和新栈里都是从第 1 行 `//package` 起整文件注释**,"
        u"全仓 `implements CustPersonCreditService` 的有效代码 = 0(包 `com.zteits.pay.biz.custperson.oper.impl`);"
        u"`CustCreDitDicJob` 里对它的注入同样被注释。⇒ 老栈走 Dubbo **没有 provider**(端点本来就恒失败),"
        u"新栈若照搬 `@Resource CustPersonCreditService` 会**没有任何 Bean**,整个应用启动即失败。"
        u"补实现 = 从被注释的 200 行里恢复 Service + DAO + `td_c_cust_person_credit` 表 + 征信 WebService 调用,"
        u"**属功能复活,不是 P2 Controller 迁移**(红线⑥:存量缺陷照搬不修)。"
        u"★ 同一分支下 `custperson/TdCCustBusinessPicController` / `TdCCustBusinessController` 等**有活实现**,"
        u"只有这一类是死的。"),
}


def gap_adjudicated(controller):
    """返回缺口档的人工结论 (标记, 依据);没登记过就是 (None, None)。"""
    hit = GAP_ADJUDICATED.get(controller)
    if hit is None:
        return None, None
    return hit


def idspace_adjudicated(controller):
    """返回 ⚠A 档的核销结论 (标记, 依据);没登记过就是 (None, None)。"""
    hit = IDSPACE_FALSE_POSITIVE.get(controller)
    if hit is None:
        return None, None
    return hit


def adjudicated(controller):
    """返回 (标记, 依据);没登记过就是 (None, None)。"""
    hit = ADJUDICATED.get(controller)
    if hit is None:
        return None, None
    return hit


def count_endpoints(src):
    """数**方法级**映射。⚠️ 类级 `@RequestMapping("equipmentManagement")` 不算端点,
    但 `ENDPOINT_PAT` 认不出两者——它就是一个 `@RequestMapping(`。
    第一版因此把**每个带类级映射的 Controller 都多算 1 个端点**
    (`EquipmentManagementController` 数出 11,实际 10——对着文件数一遍才发现)。
    做法:类声明之前的映射是类级的,之后的才是方法级的。
    """
    m = CLASS_PAT.search(src)
    return len(ENDPOINT_PAT.findall(src[m.end():] if m else src))


def javadoc_head(src):
    """类 javadoc 的**抬头**:首行标题 + 之后的普通段落,
    到第一个 `<h2>`/`<h3>` 小标题或 `@author` 为止。

    `<p>` 不算分界——三种 provenance 写法并存(反引号里是实际内容):
      ① 标题行里:老门户 `inspect/TpPInspectRecordController`,路由 ...
         → 反引号里是**类名**(前面还可能带老包路径)
      ② 第二段:来源:老门户 `zteits-bcp-portal` 的 `AreaManageController`
         → 反引号里是**类名**
      ③ 标题行里:老门户 `channelManagement`
         → 反引号里是**路由**,不是类名!这一种光看类名认不出来(见 scan_legacy 第二遍)
    但 `<h2>`/`<h3>` 里全是"与老代码的差异"「不要碰」之类的**对照性**提及,必须排除——
    否则 `ParkLotExtendController` 里那句"与老 `ParkLotManageController` 的端点不混在一起"
    会把 19 个端点的 `ParkLotManageController` 当成已迁(见 PROVENANCE_PAT 注释)。
    """
    m = re.search(r'/\*\*(.*?)\*/', src, flags=re.S)
    if not m:
        return ""
    para = []
    for line in m.group(1).splitlines():
        body = line.strip().lstrip('*').strip()
        if body.startswith('<h') or body.startswith('@'):
            break
        para.append(body)
    return "\n".join(para)


def provenance_mentions(src):
    """抬头里指认的老**类名**(写法①②)。"""
    return PROVENANCE_PAT.findall(javadoc_head(src))


def backtick_mentions(src):
    """抬头里所有反引号词,小写(含写法③的**路由**,也可能含 `BizResult` 这类噪声)。"""
    return {t.lower().lstrip('/') for t in BACKTICK_PAT.findall(javadoc_head(src))}


def collect_new_stack(new_root):
    """扫新栈:接口简单名集合 + 实现类的 `implements` 目标集合 + 已迁控制器的类名集合。"""
    api_dir = os.path.join(new_root, "parking-module-api/src/main/java")
    ifaces = set()
    api_classes = set()
    for f in glob.glob(api_dir + "/com/zteits/clouds/api/service/**/*.java", recursive=True):
        with open(f, encoding="utf-8", errors="replace") as fh:
            src = fh.read()
        m = IFACE_PAT.search(src)
        if m:
            ifaces.add(m.group(1))
        else:
            for cm in API_CLASS_PAT.finditer(strip_comments(src)):
                api_classes.add(cm.group(1))

    impls = set()
    # ⚠️ 第一百五十四切片修正:`*Impl.java` 漏了 `RefundServiceIpml.java`(存量文件名把
    # Impl 拼成 Ipml),补一条 `*Ipml.java`;两条都扫,其余逻辑不变。
    # ⚠️ 第一百六十五切片修正:按文件名 glob 还会漏掉**不叫 Impl 的实现类**——桥接空实现
    # `NoopDfGroundLockMsgPushService implements DfGroundLockMsgPushService` 就因此被判
    # 「无实现」,让已迁的 GroundLockManageController 一直挂在缺口队列里。改为扫全部
    # `*.java`(`implements` 预过滤后再套 IMPL_PAT),彻底不依赖命名约定。
    # ⚠️ 第二百七十四切片修正:**注释里的 `implements` 不算实现**。
    #    `CustPersonCreditServiceImpl.java` 整个文件(第 1 行就是 `//package`)都被注释掉,
    #    旧逻辑只做 `"implements" in src` 的文本预过滤 ⇒ 把它当成「有实现」,于是
    #    `CustPersonCreditController` 长期挂在「就绪且未迁」队列里;真照搬控制器会因
    #    **没有 Bean** 让整个应用启动失败(@Resource 注入不到)。改为先 `strip_comments`
    #    再匹配,与上面 API 接口那段做法保持一致。
    for f in glob.glob(new_root + "/parking-module-*/src/main/java/**/*.java", recursive=True):
        with open(f, encoding="utf-8", errors="replace") as fh:
            src = fh.read()
        stripped = strip_comments(src)
        if "implements" not in stripped:
            continue
        for m in IMPL_PAT.finditer(stripped):
            impls.add(m.group(2))

    # 已迁集合。**不能只比类名**——新类普遍按路由约定改名(老 `TpPInspectRecordController`
    # → 新 `InspectRecordController`),逐名比对会把已迁的都判成未迁(第一版就栽在这,
    # 只认出 13 个,实际 40+)。两路合并:
    #   ① 新类**注释里提到老类名**(约定形如「老门户 `inspect/TpPInspectRecordController`」)
    #      —— 取反引号或 `/` 紧跟的 `*Controller` 词;
    #   ② 类名小写相同(兼容没有 provenance 注释的老迁移,如 `ParkingLBSController`
    #      ↔ `ParkingLbsController`,老工程大小写不统一)。
    migrated, route_mentions = set(), set()
    for f in glob.glob(new_root + "/parking-module-*/src/main/java/**/*Controller.java", recursive=True):
        migrated.add(os.path.basename(f)[:-len(".java")].lower())
        with open(f, encoding="utf-8", errors="replace") as fh:
            src = fh.read()
        for name in provenance_mentions(src):
            migrated.add(name.lower())
        route_mentions |= backtick_mentions(src)
    return ifaces, impls, migrated, route_mentions, api_classes


def scan_legacy(legacy_root, ifaces, impls, migrated, route_mentions, api_classes):
    rows, dead = [], []
    for f in glob.glob(legacy_root + "/**/*Controller.java", recursive=True):
        with open(f, encoding="utf-8", errors="replace") as fh:
            raw = fh.read()
        src = strip_comments(raw)
        # 整类被注释掉 / 没有任何**方法级**映射 → 死代码,不计入待迁(见模块注释第 1 条)。
        # 顺手记下来,好让文档里"死代码 N 个"那句话是可复现的,而不是手数出来的。
        if not count_endpoints(src):
            dead.append({"controller": os.path.basename(f)[:-len(".java")],
                         "path": os.path.relpath(f, legacy_root)})
            continue
        svc = sorted(set(SVC_IMPORT_PAT.findall(src)))
        gaps = []
        for fqn in svc:
            simple = fqn.rsplit(".", 1)[1]
            # `api.service.*` 下的**类**(入参 DTO)不是服务契约,import 它不算缺口。
            # 修复前 `RenewVipAreaCardOperReq` 把 TVipTimeCardCustInfo / TVipAreaCardCustInfo
            # 两个类误挡在缺口档(共 15 端点)。
            if simple in api_classes:
                continue
            if simple not in ifaces:
                gaps.append((simple, "接口不在新栈"))
            elif simple not in impls:
                gaps.append((simple, "无实现"))
        cm = CLASS_PAT.search(src)
        head = src[:cm.start()] if cm else ""
        routes = ROUTE_PAT.findall(head)
        name = os.path.basename(f)[:-len(".java")]
        ids_hits = len(LEGACY_ID_PAT.findall(src))
        # 命中行的**证据**(行号 + 原文),供人肉核对 `getPlNos()` 打在谁身上——见 ⚠A/⚠B 切分。
        # 只在有命中时才算(绝大部分类是 0),行号取自 `strip_comments_keep_lines`(保换行)。
        id_lines = []
        if ids_hits:
            id_lines = [(i + 1, l.strip()) for i, l in
                        enumerate(strip_comments_keep_lines(raw).split("\n"))
                        if LEGACY_ID_PAT.search(l)]
        rows.append({
            "controller": name,
            "path": os.path.relpath(f, legacy_root),
            "endpoints": count_endpoints(src),
            "services": len(svc),
            "migrated": name.lower() in migrated,
            "route": routes[-1].lstrip("/").lower() if routes else "",
            "idSpaceHits": ids_hits,
            # 坑 22 的第二刀:整类有没有**任何一处**读登录身份。False ⇒ `ids_hits` 全是
            # 打在请求体/DTO 上的机械误报(见 IDENT_READ_PAT 注释)。用剥注释后的 src 判。
            "hasIdentitySource": bool(IDENT_READ_PAT.search(src)),
            "idSpaceLines": id_lines,
            "gaps": [{"service": s, "reason": r} for s, r in gaps],
        })

    # 第二遍:用**类级路由**认"已迁"(provenance 写法③只给了路由,没给类名)。
    # ⚠️ 只有当这个路由在**老门户里只对应一个 Controller**时才敢认——`parklotmanage` 一个路由
    # 下挂了 3 个类(`ParkLotManageController`/`ParkLotExtendController`/`ParkingAssistantController`),
    # 新类注释里的 `parkLotManage` 只证明**其中一个**迁了,拿它认全组就会把 19 个端点的
    # `ParkLotManageController` 从待办队列里藏掉。共享路由的组进 `ambiguous`,**宁可报不确定,不猜**。
    groups = {}
    for r in rows:
        if r["route"]:
            groups.setdefault(r["route"], []).append(r)
    ambiguous = []
    for route, members in groups.items():
        if len(members) != 1:
            if route in route_mentions or any(m["migrated"] for m in members):
                ambiguous.append({"route": route,
                                  "controllers": [m["controller"] for m in members]})
            continue
        if route in route_mentions:
            members[0]["migrated"] = True
    return rows, dead, ambiguous


def main():
    ap = argparse.ArgumentParser(description="存量 Controller 就绪度盘点(挑下一批迁移目标的第一步)")
    ap.add_argument("--legacy-root", default=DEFAULT_LEGACY,
                    help="老门户 Controller 根目录(默认 %s)" % DEFAULT_LEGACY)
    ap.add_argument("--new-root", default=DEFAULT_NEW,
                    help="新工程根目录(默认 %s)" % DEFAULT_NEW)
    ap.add_argument("--json", action="store_true", help="输出 JSON")
    ap.add_argument("--top", type=int, default=70, help="纯文本模式下列出多少个就绪项(默认 70)")
    args = ap.parse_args()

    if not os.path.isdir(args.legacy_root):
        print("✘ 找不到老门户目录:%s" % args.legacy_root)
        print("  请从 parking-cloud/ 的父目录运行,或用 --legacy-root 指定。")
        return 1
    if not os.path.isdir(args.new_root):
        print("✘ 找不到新工程目录:%s" % args.new_root)
        return 1

    ifaces, impls, migrated, route_mentions, api_classes = collect_new_stack(args.new_root)
    if not ifaces or not impls:
        print("✘ 新栈扫到 接口 %d 个 / 实现 %d 个——至少一边是 0,说明路径错了,报告不可信。"
              % (len(ifaces), len(impls)))
        return 1

    rows, dead, ambiguous = scan_legacy(args.legacy_root, ifaces, impls, migrated,
                                        route_mentions, api_classes)
    if not rows:
        print("✘ 老门户目录下没扫到任何带映射的 Controller:%s" % args.legacy_root)
        return 1

    ready = [r for r in rows if not r["gaps"]]
    gap = [r for r in rows if r["gaps"]]
    ready.sort(key=lambda r: -r["endpoints"])

    todo = [r for r in ready if not r["migrated"]]   # 就绪 **且** 还没迁 —— 这才是待办队列
    done = [r for r in ready if r["migrated"]]
    # 再按坑 22 切一刀:碰了 orgId/plNos/userAreas 的**不能先迁**(等 P3 打通 id 空间)
    clean = [r for r in todo if not r["idSpaceHits"]]
    haz = [r for r in todo if r["idSpaceHits"]]
    # 坑 22 的**第二刀**:⚠ 档内部再按"整类读没读登录身份"一分为二。
    #   ⚠A `hasIdentitySource == False` —— 机械误报候选(`getPlNos()` 全打在请求体/DTO 上),
    #      不阻塞迁移,但**每个类仍要人肉核对一次**(`idSpaceLines` 已经把行号+原文打出来了)。
    #   ⚠B `hasIdentitySource == True`  —— 真读了登录态,还要追"读到的值有没有进 SQL"
    #      (第三十三切片的 INERT 先例),这才是真正等 P3 的那批。
    hazA = [r for r in haz if not r["hasIdentitySource"]]
    hazB = [r for r in haz if r["hasIdentitySource"]]
    clean.sort(key=lambda r: -r["endpoints"])
    haz.sort(key=lambda r: -r["endpoints"])
    hazA.sort(key=lambda r: -r["endpoints"])
    hazB.sort(key=lambda r: -r["endpoints"])
    _adj_c = [r for r in clean if r["controller"] in ADJUDICATED]
    # 缺口档也要有人工结论:有些"缺实现"不是能补的(实现只存在于未合入的特性分支)⇒ 单独标注。
    _gap_adj = [r for r in gap if r["controller"] in GAP_ADJUDICATED]
    _gap_open = [r for r in gap if r["controller"] not in GAP_ADJUDICATED]

    if args.json:
        print(json.dumps({
            "ready": ready, "withGaps": gap, "dead": dead, "ambiguous": ambiguous,
            "summary": {
                "readyControllers": len(ready),
                "readyEndpoints": sum(r["endpoints"] for r in ready),
                "readyAndMigratedControllers": len(done),
                "readyAndMigratedEndpoints": sum(r["endpoints"] for r in done),
                "readyAndUnmigratedControllers": len(todo),
                "readyAndUnmigratedEndpoints": sum(r["endpoints"] for r in todo),
                "goodToGoControllers": len(clean),
                "goodToGoEndpoints": sum(r["endpoints"] for r in clean),
                "idSpaceHazardControllers": len(haz),
                "idSpaceHazardEndpoints": sum(r["endpoints"] for r in haz),
                # 第二刀的两半:A = 机械误报候选(无登录态读取),B = 真读了。A ∪ B = 上面的 haz。
                "idSpaceFalsePositiveControllers": len(hazA),
                "idSpaceFalsePositiveEndpoints": sum(r["endpoints"] for r in hazA),
                "idSpaceRealControllers": len(hazB),
                "idSpaceRealEndpoints": sum(r["endpoints"] for r in hazB),
                "gapControllers": len(gap),
                "gapEndpoints": sum(r["endpoints"] for r in gap),
                # ⛔ 已判「不可迁」的缺口(半合并壳/上游特性未合入本分支)——**不参与**上面的机械计数,
                #    单独报一份,让"可迁缺口"能归零而"机械缺口"仍说真话。
                "gapAdjudicatedControllers": len(_gap_adj),
                "gapAdjudicatedEndpoints": sum(r["endpoints"] for r in _gap_adj),
                "gapOpenControllers": len(_gap_open),
                "gapOpenEndpoints": sum(r["endpoints"] for r in _gap_open),
                "deadControllers": len(dead),
                "adjudicatedInClean": len(_adj_c),
                "adjudicatedEndpoints": sum(r["endpoints"] for r in _adj_c),
                "totalControllers": len(rows) + len(dead),
            },
        }, ensure_ascii=False, indent=2))
        return 0

    print("=" * 72)
    print("就绪(依赖服务在新栈全都有接口+实现):**%d 个 Controller / %d 个端点**"
          % (len(ready), sum(r["endpoints"] for r in ready)))
    print("  其中已迁 %d 个 / %d 端点;就绪且未迁 %d 个 / %d 端点 ← 待办队列"
          % (len(done), sum(r["endpoints"] for r in done),
             len(todo), sum(r["endpoints"] for r in todo)))
    print("=" * 72)
    print("就绪且未迁的待办队列按**坑 22**再切一刀(数值=从登录态读 orgId/plNos/userAreas 的次数):")
    _adj_eps = sum(r["endpoints"] for r in _adj_c)
    _unjudged = [r for r in clean if r["controller"] not in ADJUDICATED]
    print("  ✔ %d 个 / %d 端点  **坑 22 安全**" % (len(clean), sum(r["endpoints"] for r in clean)))
    if _unjudged:
        print("       └ 其中已逐个判定 %d 个 / %d 端点;⚠ **还有 %d 个未判定**(先读源码再开搬)"
              % (len(_adj_c), _adj_eps, len(_unjudged)))
    else:
        print("       └ 其中 %d 个 / %d 端点**已全部判定**(见下表)⇒ **当前 0 个可直接开搬**"
              % (len(_adj_c), _adj_eps))
    print("  ⚠ %d 个 / %d 端点  碰了身份字段(`LEGACY_ID_PAT` 机械命中)——**但这一档里有两拨人**:"
          % (len(haz), sum(r["endpoints"] for r in haz)))
    print("       ├ ⚠A %d 个 / %d 端点  **整类没有一处读登录态** ⇒ 机械误报候选"
          "(`getPlNos()` 打在请求体/DTO 上),逐类核对命中行即可开搬"
          % (len(hazA), sum(r["endpoints"] for r in hazA)))
    print("       └ ⚠B %d 个 / %d 端点  **真读了登录态**(`getUserInfo`/`SessionCommUtil`)"
          "⇒ 等 P3(迁了就是静默少数据)"
          % (len(hazB), sum(r["endpoints"] for r in hazB)))
    print()

    print("=" * 72)
    print("✔ 可以开搬(就绪 + 未迁 + 坑 22 安全),按端点数列:**%d 个 / %d 端点**"
          % (len(clean), sum(r["endpoints"] for r in clean)))
    print("=" * 72)
    for r in clean[:args.top]:
        _mark, _why = adjudicated(r["controller"])
        print("  eps=%-3d %-44s %s%s"
              % (r["endpoints"], r["controller"], r["path"],
                 ("   [%s]" % _mark) if _mark else "   [未判定]"))
    print()
    print("  注:本脚本的 ✔ 只答「服务齐 + 没读身份字段」,**不答「能不能交付」**;下面是人工判定结论:")
    for r in clean[:args.top]:
        _mark, _why = adjudicated(r["controller"])
        if _mark:
            print("     %-24s %s" % (_mark, r["controller"]))
            print("        └ %s" % _why)
        else:
            print("     %-24s %s   ⚠ 未判定(只能算「有竞争者」,不能当可开搬)" % ("❓", r["controller"]))
    if len(clean) > args.top:
        print("  … 另有 %d 个(用 --top 调整,或 --json 取全量)" % (len(clean) - args.top))

    print()
    print("=" * 72)
    print("⚠A 机械误报候选(没读登录态)——**可开搬,但逐类核对下面这几行**:**%d 个 / %d 端点**"
          % (len(hazA), sum(r["endpoints"] for r in hazA)))
    print("=" * 72)
    _ida = [r for r in hazA if r["controller"] in IDSPACE_FALSE_POSITIVE]
    print("  (已核销 %d / %d;核销结论见代码里的 `IDSPACE_FALSE_POSITIVE` 表)"
          % (len(_ida), len(hazA)))
    for r in hazA[:args.top]:
        _mark, _why = idspace_adjudicated(r["controller"])
        print("  eps=%-3d ids=%-3d %-40s %s   [%s]"
              % (r["endpoints"], r["idSpaceHits"], r["controller"], r["path"],
                 _mark or u"❓ 未核销"))
        for n, line in r["idSpaceLines"][:6]:
            print("        %5d| %s" % (n, line[:110]))
        if len(r["idSpaceLines"]) > 6:
            print("        …  另有 %d 行命中" % (len(r["idSpaceLines"]) - 6))
        if _why:
            print("        └ %s" % _why)
    if len(hazA) > args.top:
        print("  … 另有 %d 个(用 --top 调整,或 --json 取全量)" % (len(hazA) - args.top))

    print()
    print("=" * 72)
    print("⚠B 真读了登录态 → 等 P3:**%d 个 / %d 端点**" % (len(hazB), sum(r["endpoints"] for r in hazB)))
    print("=" * 72)
    for r in hazB[:args.top]:
        print("  eps=%-3d ids=%-3d %-40s %s"
              % (r["endpoints"], r["idSpaceHits"], r["controller"], r["path"]))

    print()
    print("=" * 72)
    print("有缺口(搬过来就是 500,先补服务):**%d 个 Controller / %d 个端点**"
          % (len(gap), sum(r["endpoints"] for r in gap)))
    print("=" * 72)
    for r in sorted(gap, key=lambda r: -r["endpoints"]):
        detail = ", ".join("%s/%s" % (g["service"], g["reason"]) for g in r["gaps"])
        _mark, _why = gap_adjudicated(r["controller"])
        print("  eps=%-3d %-40s 缺: %s%s"
              % (r["endpoints"], r["controller"], detail,
                 ("   [%s]" % _mark) if _mark else ""))
        if _why:
            print("        └ %s" % _why)
    if _gap_adj:
        print("  ★ 其中 %d 个 / %d 端点**已判定不可迁**(见代码里的 `GAP_ADJUDICATED` 表)"
              "⇒ 真正可迁的缺口是 **%d 个 / %d 端点**"
              % (len(_gap_adj), sum(r["endpoints"] for r in _gap_adj),
                 len(_gap_open), sum(r["endpoints"] for r in _gap_open)))

    print()
    print("=" * 72)
    print("死代码(整个类被注释掉 / 零映射,**不迁**):**%d 个** "
          "(老门户 Controller 合计 %d 个,其中 %d 个有映射)"
          % (len(dead), len(rows) + len(dead), len(rows)))
    print("=" * 72)
    for d in sorted(dead, key=lambda d: d["controller"]):
        print("  %-44s %s" % (d["controller"], d["path"]))

    if ambiguous:
        print()
        print("=" * 72)
        print("⚠ **一个类级路由下挂了多个 Controller**,仅凭路由认不出谁迁了——"
              "**别把没迁的当已迁,去 grep**:")
        print("=" * 72)
        for a in sorted(ambiguous, key=lambda a: a["route"]):
            print("  老路由 %s:" % a["route"])
            for c in a["controllers"]:
                print("      %s" % c)

    print()
    print("⚠️ 就绪 ≠ 可迁。**还要过坑 22**(拿 `orgId`/`plNos`/`userAreas` 过滤数据的不能先迁)"
          "与死代码判定——本脚本只回答「服务齐不齐」这一个问题。")
    print("⚠️ 而且坑 22 的判据是**机械的**,两个方向都会错:"
          "① 它**认不出接收者**——`request.getPlNos()`(前端请求体)和 `userInfo.getPlNos()`(登录态)"
          "在正则眼里一样(第三十五切片:⚠ 档里 12 个类整类没读登录态,是**纯误报**);"
          "② 就算命中的是登录态,它也**不说那行读到的值有没有真的进 SQL**"
          "(第三十三切片就栽在这:`ParkInOutRecordController` 的 `setTopOrgId` 在老代码里本来就是注释掉的)"
          "⇒ 每个候选仍要读源码/smapper(⚠A 档看 `idSpaceLines`,⚠B 档看 smapper 的 `WHERE`)。")
    print("★ ✔ 池的人工结论在上面 `ADJUDICATED` 表里(脚本只标注、不改判定);**每判定一个类都要登记进去**。")
    return 0


if __name__ == "__main__":
    sys.exit(main())