| HANDOFF.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| docs/生成汇总大表/2026年9月道路运输量汇总表.xlsx | 补丁 | 查看 | 原始文档 | blame | 历史 | |
| docs/生成计算参照与验收口径_2026-10-13.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| docs/确认回执汇总与剩余疑问_汇总大表数据驱动改造_2026-09-07.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| docs/要求与验收基线清单.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
HANDOFF.md
@@ -4,6 +4,25 @@ > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã > **åä½çº¦å®ï¼2026-08-31ï¼**ï¼è¯´ãæäº¤ã= æ¬å° commit + æ¨éå°è¿ç«¯ gitï¼origin mainï¼Gitblitï¼ï¼ä¸ååªåæ¬å° commitã ### 10-13ï¼1ï¼ï¼æ±æ»å¤§è¡¨ãè´§è¿å¨è½¬éæåãåéä½ä¿®å¤ï¼æ¯çå å¤ä½æ é¢è¡ + ä»£ç æãå ¨çãè¡éç¹å¯¹é½ + ååèªæ£ï¼ãå¾ éªæ¶ã - **ç°è±¡ï¼ç¨æ·æ¥å + æ ¸å¯¹ï¼**ï¼9 æ â¡â¢ å ¥åºåï¼çæä»¶ãè´§è¿å¨è½¬éæåãæ´åéä½ä¸è¡ââå ¨çå¼è½å°**表头è¡**ï¼è¦çãç´¯è®¡å®æ(ä¸å¨å ¬éï¼ãæåï¼ãå¸å·å¼ä¸å¸å·å**éå¼ä¸è¡**ï¼æ¦æ±è¡è£ é»ç³çæ°â¦æåºè¡æ¯æ¯çæ§å¼ï¼ã**æ¦æ±çå¼ä¸¢å¤±**ï¼`RANK/å æ¯/Q/R` ééä½ä¸å¹¶éã - **æ ¹å ï¼ä¸¤å±ï¼**ï¼â æ¯ç该页æ¯åæ¯æºåºç¨¿ `docs/è´§è¿/çæ_å¨è½¬éæå.xlsx` **å¤ 1 è¡**ï¼æ é¢åå¹¶å 两è¡ï¼`A1:R2`ã`A23:R24`ï¼å ¨çè¡å¨ç¬¬ 5/27 è¡ï¼ï¼â¡ `ReportExportService.copyRankValues` æãåæ é¢è¡ + æºè¡åç§»ãéè¡ç §æ¬ï¼ä¸ `:4334` éå°ç®æ æ ¼æ¯å ¬å¼å°±è·³è¿ â å½¢æ"å¼åä¿ç SUMãæåå被åæä¸ä¸è¡å¸å·"çåéä½ã该缺é·**ä¸ç´åå¨**ï¼7/8/9 ææ¯çåç»æï¼ï¼å è¿é¡µæ¤åèµ°"æ´å缺æ°ç空"忝ï¼â¡â¢æªå¯¼ï¼èæªæ´é²ã - **ä¿®å¤**ï¼ 1. **æ¯ç**ï¼ç¨æ· 19:47 æ§è¡ï¼ï¼`docs/çææ±æ»å¤§è¡¨/2026å¹´9æéè·¯è¿è¾éæ±æ»è¡¨.xlsx`ãè´§è¿å¨è½¬éæåãå æä¸¤åå¤åºçæ é¢è¡ â å ¨çè¡åå°ç¬¬ **4/25** è¡ï¼ä¸ãè´§è¿éæåãåæï¼Excel å·²èªå¨ä¿®å¥½åå¹¶åºä¸ `SUM(B5:B21)`/`SUM(B26:B42)` çå ¬å¼åºé´ã 2. **代ç **ï¼`ReportExportService.copyRankValues` â ä¼ å `List<String> problems`ï¼ï¼æ¹æç®æ åãå ¨çãè¡åéç¹ï¼`rowOff = ç®æ å ¨çè¡ â æºå ¨çè¡`ï¼è¯å«ä¸å°éç¹åéæ é¢è¡å移并 WARNï¼æ°å¢**ååèªæ£**ï¼ç®æ å ¨çè¡ä¸æºä¾§ä¸ä¸è´ â¥2 å â WARN + åå ¥ãæ°æ®ç¼ºå¤±æ¸ åãï¼ãä¸ä¸ªæå页ï¼è´§è¿é/å¨è½¬é/ä¸å£å¾ï¼ç»ä¸çæã - **éªè¯ï¼20:06 éæ°çæ 2026-09ï¼dev 8090/8080 å·²éå¯å è½½æ°ä»£ç ï¼**ï¼ãè´§è¿å¨è½¬éæåã累计å/彿å Ã å ¨ç + 17 å¸å· à 16 åï¼è§ä¸/è§ä¸/å计å¼ã忬¡ãå æ¯ã忝ãå¢é忬¡ãQ/Rï¼ä¸åºå `scale_split_transport` 2026-09ï¼CUMULATIVE/MONTHï¼**éæ ¼ä¸è´ï¼0 å¤ä¸ä¸è´**ï¼ç¼ºæ°æ¸ å 4â2 æ¡ï¼â¡â¢å·²å ¥åºï¼ä» å©ç½çº¦è½¦ 2 æ¡ï¼ï¼é¡µç¾å®ä½ 20 页ã累计é建 0 æ ¼ãå ¬å¼éç® 39969 æ ¼æåã - **æ¥åï¼æ¬å°ãä¸å ¥åºï¼**ï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_æ±æ»å¤§è¡¨å¨è½¬éæåéç¹å¯¹é½_2026-10-13.md`ã产ç©ï¼`_tmp/_fix_1013/çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-09_éªè¯.xlsx`ï¼æªå ¥åºï¼ã - **ãå¾ éªæ¶/å¾ å®å¤º/å¾ å¤çã**ï¼ 1. ãå¾ éªæ¶ããè´§è¿å¨è½¬éæåãæ¯çå è¡ + 代ç éç¹å¯¹é½ä¸èªæ£ï¼é¡µé¢éæ°çæå³å¯å¤ç°éªè¯ï¼ã 2. ãå¾ ç¨æ·ææ¿ããè´§è¿éæåã累计åãè§ä¸å¢é / å¢éæåã**30 æ ¼**å£å¾ï¼çæä»¶ï¼å ¨ç 0.052525ï¼vs æ¡é¢ã2026å¹´9æå®¢è¿éãè´§è¿é颿µ.xlsxãï¼å ¨ç -0.224015ï¼ï¼éå®ä»¥åªè¾¹ä¸ºåã 3. ãå¾ ç¨æ·ææ¿ãç弿æï¼å顺åºï¼çæä»¶=累计å¨ä¸ï¼é¢æµä»¶=彿å¨ä¸ï¼ãå¸å·å带ä¸å¸¦"å¸"ã 4. ãå¾ å¤çã7/8 ææ¯çå为"å¤ 1 è¡"ç»æï¼å¨ `彿¡£/`ï¼ââéç¹å¯¹é½åæ£ç¡®æ§å·²ä¸åå½±åï¼ä¸æ¬¡åååºè¡¨æ¶ç¡®è®¤å³å¯ï¼æä¸æ¹å½æ¡£ä»¶ã 5. ã12 æåã跨年æ¯çéæ°å¼ä¸æ¬ï¼æ©å `month>=12` æç¡®æç»ï¼ï¼å»ºè®®åæåèªå¨ï¼è§åå¾ å®ï¼ä¿çå å¹´åå²å / æ°ä¸å¹´æåº¦å叿¾ / 2027-01 æ¯çæ¯å¦çæ¡£ï¼ã 6. ã缺æ°/缺è¡ãç½çº¦è½¦ 9 æï¼è®¢åãå ¨ç pinãæºæä»¶ï¼ï¼å ¬äº¤ 9 æ 141 è¡ vs 142 è¡ï¼æºæä»¶ç¼º `éå·å¸Â·å¹¿æ°´å¸å®é¡ºå ¬äº¤æéå ¬å¸`ï¼ã - **ç¯å¢**ï¼dev åå端 20:00 éå¯ï¼`backend.log` è§ `Started TrafficAuditApplication`ã`Tomcat started on port(s): 8090`ï¼ï¼å¯å¨æ¥å¿å¦æä¸æ¡ä¸æ¬æ¬¡æ å ³ç `traffic_audit.holiday_calendar` 表ä¸åå¨åè¦ã - **æ°æ®è¾¹ç**ï¼æ¬è½®åªåçæä¸åªè¯»æ ¸å¯¹ï¼åºå æ¥è¯¢åªè¯»ï¼ï¼**æªå¯¼å ¥ãæªè¦çä»»ä½ä¸å¡æ°æ®**ã ### 10-12ï¼14ï¼ï¼æ±æ»å¤§è¡¨ãç¼ºæ°æ¸ åå¼¹çª + çæä»¶ãæ°æ®ç¼ºå¤±æ¸ åã页ã+ 使£å鳿§ä¿®å¤ + å ¬äº¤ 9æç¼ºè¡æ ¸æ¥ãå¾ éªæ¶ã - **å¨å **ï¼ç¨æ·è¦æ±æç¼ºæ°æå°å°é¢ä¸ï¼P0-3 å©ä½ï¼ï¼å¹¶å¨çæä»¶éç»ä¸é¡µãæ°æ®ç¼ºå¤±æ¸ åãã docs/Éú³É»ã×Ü´ó±í/2026Äê9ÔµÀ·ÔËÊäÁ¿»ã×ܱí.xlsxBinary files differ
docs/Éú³É¼ÆËã²ÎÕÕÓëÑéÊÕ¿Ú¾¶_2026-10-13.md
New file @@ -0,0 +1,103 @@ # çæè®¡ç®åç §ä¸éªæ¶å£å¾ï¼2026-10-13 æ±æ»ï¼ > ä¸é¡µåç两件äºï¼â æ±æ»å¤§è¡¨ç"çæè®¡ç®"**åç §äºåäºä¸é¡¹ç»è®¡çåªäºåæ³**ï¼â¡ **æ¯æ¬¡åºè¡¨æä¹éªæ¶**ï¼åå±éªæ¶ï¼å·²èæ¬åï¼ã > æ¥æºï¼`docs/æ±æ»å¤§è¡¨M0çå¼é建å¨è§æ ¼_2026-10-06.md` 第åä¸èï¼2026-10-08 对é½è¡¨ï¼ã`docs/æ±æ»å¤§è¡¨çææ°æ®éªè¯æ¹æ³_2026-10-12.md`ã`docs/è¦æ±ä¸éªæ¶åºçº¿æ¸ å.md` R6ã`docs/ç¡®è®¤åæ§æ±æ»â¦_2026-09-07.md` ç¬¬å «èã ## ä¸ãåç §åäºãä¸é¡¹ç»è®¡ãçæ å°ï¼2026-10-08 åæå¯¹é½è¡¨ï¼ | åäºä¸é¡¹ç»è®¡çåæ³ | ä»ä¸ºä»ä¹è½æç« | æ±æ»å¤§è¡¨ç对åºåæ³ | ç¶æ | |---|---|---|---| | ä¸ä»½æ¯çï¼`2026å¹´å䏿°æ®.xlsx`ï¼ï¼**天为è¡ã年为å** | å¢é¿ç»´åº¦æ¯ãå¹´ãã天æ°åºå® 8 | æ **F10 æææéæ©å**ï¼è¯» N ææ¯çäº§åº N+1 æçå¼ï¼ä¸é¢ç空åï¼ï¼æ¯çåªå½ç弿¥æº | â å·²å®ç°ï¼æ©åå¨ï¼ | | æ¯ç**åæ¶æ¯æ°æ®å®¹å¨**ï¼åå²å¹´ä»½å¼å°±å¨å¹¶æå¹´åéï¼ | åå²å¼é模æ¿èµ° | **åè¿æ¥å**ï¼åå²åç §å¼å ¥åºãæ¯çåªççå¼ãåç §ç»ç± DB åå¡« | ⬠æªå®æ½ï¼10-08 宿¹åï¼ | | 表头/è¡æ ç¾**è¯ä¹å®ä½**ï¼`findYearColumn`/`parseDayIndex`ï¼ | ååºåäºä¹è½å¯¼ | 页è§å表 `SummarySheetRule`ï¼**æè¡¨å¤´è®¤æä»½ç»ãæè¡æ ç¾è®¤å¸å·/ææ ï¼ä¸ç¡¬ç¼ç åéç¹** | ð§ é¨åï¼æåº¦åè¿ä¾èµæ¯çå ¬å¼ï¼2026-09 æ¼æå³æºäºæ¤ï¼ | | ææ¼ç§»å£å¾**å¤ç½®æè¡¨**ï¼`holiday_calendar`/`passenger_coefficient`/`vehicle_adjustment`ï¼ | è§å䏿£å¨ä»£ç ä¸åå æ ¼é | å¤ç½®ï¼åç §å¹´ä¿çç»æ°ãæåå£å¾ï¼å ¨ç䏿åï¼ã`B=G+L/2+Q/10`ã缺æ°ç½®ç©ºè§å | ð§ é¨å | | `DECIMAL(30,15)` + `BigDecimal` + æ Excel æ¾ç¤ºç²¾åº¦åå¼ | 精度å£å¾ç»ä¸ãæ æµ®ç¹å°¾å·® | æ¿æ¥å¾ åã16 å¼ è¡¨ 228 个 double åæ¹ DECIMAL+BigDecimalãï¼å·²å F1 å ¨ç²¾åº¦åå¨ï¼ä¸¤ä½å°æ°æ¾ç¤ºãæ¯çææ¬æ°åå½ä¸ | ð§ é¨å | | **å¯¼å ¥ä¸¤é¶æ®µç¡®è®¤** + å ¨ç¨åªè¯»æ ¸å¯¹ï¼å岿°æ®ä¸è¢«è¯¯è¦çï¼ | å åºå·®å¼æ¸ åãç¨æ·ç¡®è®¤åæå | **çæåå åºç¼ºæ°/差弿¸ åã人工确认åååºè¡¨**ï¼B4 åºæ°ä½æ£ + ãæ°æ®ç¼ºå¤±æ¸ åãé¡µï¼ | â å·²å®ç° | | è¾åºèµ°æ¨¡æ¿æä»¶ï¼docx/PNGï¼ | ç弿¥æºåä¸ | ä¿çç°æåæ³ï¼å¿«æ¥æ¹ `word/document.xml`ãJFreeChart åº PNGï¼ | â | **ä¸å¥è¯**ï¼åçæ¯**æ¹æ³ä¸çºªå¾**ï¼è¯ä¹å®ä½ãå£å¾å¤ç½®ã䏤鶿®µç¡®è®¤ã精度ç»ä¸ï¼ï¼**ä¸ç §æ¬**ä»"æ¯çå ¼æ°æ®å®¹å¨"çåæ³ââæä»¬åè¿æ¥è®©åå²å¼å ¥åºã ## äºãçæè®¡ç®çåæ®µä¸åèªåç §ï¼2026-10-12ãçææ°æ®éªè¯æ¹æ³ãï¼ ååï¼**æ¯ä¸æ®µé½è¦æä¸ä¸ªç¬ç«äºè¯¥æ®µçåç §**ï¼ä¸¤è¾¹åæºå°±éªä¸åºä¸è¥¿ã | 段 | é®é¢ | åç § | è°è´è´£ | ç¨åºè½èªè¯ | |---|---|---|---|---| | L0 æºæä»¶âåº | æºæ°æ®å¯¹ä¸å¯¹ | æºæä»¶/ä¸åè¡¨æ¨¡æ¿ | æ°æ®ãä¸å¡ | ä¸è½ | | L1 åºâå£å¾å¼ | ç®æ³å£å¾å¯¹ä¸å¯¹ | **æå SQL å¤ç®**ï¼ä¸çæ¯ç/å®ç¨¿ï¼ | ç¨åº | è½ | | L2 å£å¾å¼âåå æ ¼ | åå对ä¸å¯¹ | ä¸ä¸ææ¯ç/çæä»¶ï¼"ä¸è¯¥å¨çæ ¼æªå"ï¼ | ç¨åº | è½ | | L3 åå æ ¼âæ´æ¬ | ä¸äººå·¥å®ç¨¿ä¸è´å | æå·¥å®ç¨¿ï¼**è®°çæ¬ + sha256**ï¼ | 忹夿 ¸ | é¨å | | L4 跨页åæé¡µ | å¾ç¨½å¯¹ä¸å¯¹ | å¾ç¨½å¼æ¬èº«ï¼å¦ `éè·¯è¿è¾å¨è½¬é B=G+L/2+Q/10`ï¼ | ç¨åº | è½ | å ¸å失败模å¼ï¼åæï¼ï¼L0 æ¼å¯¼/åéä½/åä½å·® 10000/éå¤å¯¼å ¥å å ï¼L1 è§ä¸åå为è´ãΣå¸å·â çè¡ï¼L2 彿忲¡åã䏿å被éç®å²æãå ¬å¼æé¾ï¼L3 æ"å®ç¨¿æ»å"å½"ç¨åºåå½"ï¼L4 æºé¡µå¯¹èåæé¡µå¼ç¨éåã ## ä¸ãæ¯æ¬¡åºè¡¨çåå±éªæ¶ï¼èæ¬åï¼ä¸ç¬¬äºèçåæ®µæ¯ä¸¤å¥ååï¼å«æ··ï¼ å·¥å ·ï¼`æ±æ»è¡¨åå±éªæ¶.py`ï¼L1âL4ï¼ï¼ `æ±æ»è¡¨å ¨ç使£.py`ï¼å¢éçï¼å« A/B/C/Dï¼ãå½ä»¤ï¼ ``` python æ±æ»è¡¨åå±éªæ¶.py <çæè¡¨.xlsx> --æºè¡¨ <彿æºè¡¨.xlsx> --䏿 <䏿件.xlsx> --æ¥å éªæ¶æ¥å.txt ``` | å± | æ¥ä»ä¹ | çå¼/便® | éè¿æ å | |---|---|---|---| | éªæ¶ L1 ç»ææè¨ | å ¨ç°¿å ¬å¼ä¸è¡¨å¤´ | 表头æ¬èº« | 累计=å½å¹´ 1..M æä¹åï¼ç´¯è®¡åæ¯åæ¯=å»å¹´ 1..M æï¼**åå²åé¶åå**ï¼æ æé¦æ¥æä¸²ï¼`#REF!`=0 | | éªæ¶ L2 䏿ºè¡¨ | 8 个å¯è§å°è´¦é¡µ | **彿ä¸å¡æºè¡¨** | éæ ¼ diff=0ï¼å·®å¼é¡»éæ¡ç»è®ºå¹¶ç¾æ ¸ | | éªæ¶ L3 å é¨å¾ç¨½ | å ¨ç°¿ | ç± L2 æ¨å¯¼ | å ¨ç=Σå¸å·ï¼ç´¯è®¡=Σåæï¼æå/å æ¯èªæ´½ | | éªæ¶ L4 è·¨ææ¥è¦ | å ¨ç°¿ | 䏿件 | åå£å¾ç¯æ¯è¶ éå¼ï¼é»è®¤ ±30%ãåºæ°â¥100ï¼å³æç¤º | **ç½ååæºå¶**ï¼å·²ç¥ä¸å·²è§£éç差弿¾å¼ç»è®°ï¼è½®æ¸¡åå½¢æãæ¯ç模æ¿éçæ¥æå¼ãæºè¡¨èªå¸¦èå¼è¢«æ¸ 空â¦ï¼ï¼**ç½ååä¹å¤ä»»ä½å·®å¼ï¼ä¸éè¿**ï¼ä¸åé äººå·¥éæ¡è§£éã **两å¥ååç对åºå ³ç³»**ï¼éè¦ï¼ï¼ | éªæ¶åå± | è¦ç第äºèçåªä¸æ®µ | |---|---| | éªæ¶ L1 ç»ææè¨ | 龿¡ L2ï¼ååå±ï¼ä¸ºä¸» | | éªæ¶ L2 䏿ºè¡¨/å®ç¨¿ | 龿¡ L3ï¼åºè¡¨å±ï¼ï¼æºè¡¨å¯ç¨æ¶ä¹è¦ç L0 çç»æ | | éªæ¶ L3 å é¨å¾ç¨½ | 龿¡ L4ï¼åæé¡µï¼ | | éªæ¶ L4 è·¨ææ¥è¦ | 龿¡ L1 ç"ç¯æ¯å¼å¸¸"使£ | | **æªè¦ç** | **龿¡ L1 ç"SQL éç¹å¤ç®"å°æªèæ¬åï¼å¾ åï¼** | ## åãéè¿æ åä¸ä¸æ¡çºªå¾ - **éè¿**ï¼åå±å ¨è¿ + ç½ååå¤é¶å·®å¼ï¼**任䏿ªè¦ç â ç»è®ºåªè½å"æªè¦ç"ï¼ä¸å¾å"å·²éªè¯"**ã - çºªå¾ â **ç¬ç«åç §**ï¼åæºäºæ¯çäºæ²¡éªï¼ï¼â¡**䏿¬¡åªæ¢ä¸ä¸ªåé**ï¼æ¢ä»£ç /æ¢æ°æ®/æ¢æ¯çåå¼éªï¼ï¼â¢**å·®å¼å åç±»åç»è®º**ï¼ç»æ/æ ¼å¼/æµ®ç¹å°¾å·®åªè®¡æ°ï¼å®è´¨æ°å¼å·®å¼è¿æ¸ åï¼ã - 责任边çï¼ç¨åºä¿è¯"**䏿¬é + 䏿ºè¡¨ä¸è´ + å¯è¿½æº¯**"ï¼**æ°æ®æ¬èº«çæ£ç¡®æ§**ç±å¯¼å ¥å®¡æ ¸ + ä¸å¡å£å¾ç¡®è®¤ + æ¯æç¾æ ¸è´è´£ã ## äºãå¾ åï¼æä¼å çº§ï¼ 1. 龿¡ L1 ç **SQL éç¹å¤ç®èæ¬å**ï¼å ¨ç + 2 å¸å·ï¼è¦ç è§ä¸/è§ä¸/å计/å¨è½¬é/ä¸å£å¾/åå¸å ï¼ã 2. **åå²åç §å¼å ¥åº**ãæ¯çåªççå¼ï¼10-08 已宿¹åï¼ã 3. 轮渡å 8 æ ¼å£å¾ç¡®è®¤ï¼å½ååç½ååï¼ï¼è·¨å¹´ï¼2027-01ï¼æ©åè§åï¼"ç¼ºå½ææ¯çæ¶èªå¨æ©å"æ¥ä¸»é¾ã ## å ããçº¦å® 8 æ¡ãvs ç°ç¶ vs ç°åæ³ï¼2026-10-13 æ ¸å®ï¼ | # | 约å®å¯ä»¥åç §ç | åäºå | ç°å¨å®é æä¹å | |---|---|---|---| | 1 | æéå¢é¿å â F10 æææéæ©å | â | æ©åå¨å·²å®ç°ï¼åºç¨¿å `_cache\summaryWorkbook_<æ>_month_v*.xlsx`ï¼**æªæ¥ä¸»é¾**ï¼`resolveSummaryMother` åªç²¾ç¡®å¹é ï¼â æ¯æä»éäººå·¥ä¸æ¥ï¼æ©åâ确认è½çâçæï¼ | | 2 | 表头/è¡æ ç¾è¯ä¹å®ä½ | ð§ | å·²åï¼è¡¨å¤´å¨æå®ä½ãæãA å¸å·å + B ææ ææ¡ãå®ä½è¡ãæ©åæåºåæä»½åå¤è¡¨å¤´ã累计/ç´¯è®¡åæ¯æè¡¨å¤´é建ï¼2026-10-13 æ°å¢ï¼ãæªåï¼é¡µè§å表 `SummarySheetRule`ï¼ç¨å¯å弿¿ä»£ï¼ | | 3 | è¾åºèµ°æ¨¡æ¿æä»¶ | â | å¤å¶æ¯ç â åå¡«/è¿å â æåé¡µææ¯çæ é¢æ ¼å¯¹é½æ¬å â 7 æ é¢é¡µæ period ç°å | | 4 | å£å¾å¤ç½®æè¡¨ | ⬠| åæ»å¨ä»£ç /æ¯çï¼ç³»æ° `2ã10`ãæåå£å¾ã缺æ°ç½®ç©ºè§å | | 5 | 精度ç»ä¸ï¼DECIMAL+BigDecimalï¼ | ⬠| ä»èµ° doubleï¼å·²å F1 å ¨ç²¾åº¦ï¼ä¸¤ä½å°æ°æ¾ç¤ºãæ¯çææ¬æ°åå½ä¸ | | 6 | 䏤鶿®µç¡®è®¤ï¼åªè¯»æ ¸å¯¹ | â ï¼ç¯èä¸åï¼ | åäºå¨å¯¼å ¥ä¾§åï¼æä»¬å¨**çæä¾§**åï¼ç¼ºæ°æ¸ åï¼è´§è¿ä½æ£ï¼å¯¼åºåå¼¹çªï¼ãæ°æ®ç¼ºå¤±æ¸ åã页ï¼ã**å¯¼å ¥ä¾§ä»æ¯éå¤å¯¼å ¥ç´æ¥è¦ç** | | 7 | äºç»´è¡¨ä¸å±èè´£ | ð§ | åå§å¼ â ï¼å¯¼å ¥è¡¨é½å ¨ï¼ï¼é ç½®å¼ãäººå·¥è°æ´å¼**åæ è¡¨**ï¼**Excel 仿¿æ åå²å¼åå¨**ï¼2025 åç §å¼å¨æ¯çéï¼ | | 8 | äººå·¥è°æ´å¼åç¬å»ºè¡¨ï¼è¦çå±ï¼ | ⬠| é `_å¤ä»½_`/`_bak_` æ¯ç + äººå·¥æ¹æ¯çå | **ä¸å¯ç §æ¬ 3 æ¡çæ§è¡**ï¼ä¸æ Excel 彿°æ®æº â ï¼ä¸å¡æ°æ®èµ° DBï¼ä½åå²åç §å¼ä»å¨æ¯ç â ï¸ï¼ï¼æ¯çåªççå¼ â¬ï¼ç¬ç«ãæ°å¹´ç»ãé»è¾ âï¼è·¨å¹´ `month>=12` æéï¼ã ## ä¸ã任塿¸ åï¼å 3 æ¡ + èªå»º 3 æ¡ï¼ **ååäºç**ï¼â è¯ä¹å®ä½ï¼æ¯å¯¹ä¸å®ä½é½ä¸ç¡¬ç¼ç åéç¹ï¼ï¼â¡ åç §å¼å ¥åºãæ¯çåªççå¼ï¼é¶æ®µ 2 æ ¸å¿ï¼ï¼â¢ å£å¾ä¸äººå·¥è°æ´å¤ç½®æè¡¨ + è¦çå±ççï¼è¦çå¼+åå +æ¶é´ï¼ã **èªå»º**ï¼â£ **SQL éç¹å¤ç®**ï¼é¾æ¡ L1ï¼å¯ä¸è½åç°"æºè¡¨èªèº«é"çå±ï¼ï¼â¤ **L2 æ¯å¯¹è¦çè¡¥é½**ï¼ç线å 车/ç½çº¦è½¦/è´§è¿ï¼ç¨è¯ä¹æ å°ï¼ï¼â¥ **L4 è·¨æé弿¥è¦**ï¼å·²å®ç°ï¼ã ## å «ã2026-10-13 è¯ä¹æ¯å¯¹ç»æï¼æ°å¢èæ¬ `æ±æ»è¡¨è¯ä¹æ¯å¯¹.py`ï¼ æãè¡=å¸å·+ææ ãå=å¹´+æ+ç±»åãå½ä½åæ¯å¯¹ 2026-09 çæä»¶ ä¸ æ¡é¢æºè¡¨ï¼ | é¡µç¾ | 坿¯è¡ | å·®å¼ | ç»è®º | |---|---|---|---| | ç线å 车 | 54 | **0** | â å被"表头å¸å±ä¸å"è·³è¿ï¼ç°å¯æ¯ä¸å®å ¨ä¸è´ | | å ¬äº¤ / åºç§ / åå¸å®¢è¿ / å ¬è·¯æ»å®¢è¿ / 轨éã轮渡 | 72/69/36/36/10 | **0** | â | | ç½çº¦è½¦ | 72 | 8 | â ï¸ å ¨ç 4 ä¸ªææ ç 2026-09 å½æå¼ = çæ 0ãæºè¡¨æå¼ï¼4997.74 / 29166.70 / 4571.18 / 23367.09ï¼ï¼åæ¯ = **-100%**ãå±**åºå 9 æç½çº¦è½¦ç¼ºæ°**ï¼è®¢å/pin æªå¯¼ï¼ï¼éç®é â **ä½"ç¼ºæ°æåæ¯æ¾ç¤º -100%"åºæ¹ä¸ºç空**ï¼å¾ å¤çï¼ | | ä¸å£å¾æç» / ä¸å£å¾å®¢è¿é | 181 | 62 / 22 | å·®å¼å ¨ä¸º `çæ=None / æº='#DIV/0!'` â **æºè¡¨èå¼è¢«æ¸ 空**ï¼çæä»¶æ´å¹²åï¼ | **ç»è®º**ï¼æè¯ä¹æ¯å¯¹è¡¥ä¸åï¼`ç线å 车` å¯è¯å®ä¸è´ï¼`ç½çº¦è½¦` çå·®å¼å¯å½å å°"缺æ°"ï¼ä¸å±äºç®éã docs/È·ÈÏ»ØÖ´»ã×ÜÓëÊ£ÓàÒÉÎÊ_»ã×Ü´ó±íÊý¾ÝÇý¶¯¸ÄÔì_2026-09-07.md
@@ -28,7 +28,7 @@ | D3 | æ¯å¹´æ°å¼ä¸æ¬ï¼å«ä¸ä¸å¹´åç §ç»ï¼ï¼2026 æ±æ»è¡¨åªå° 2026-12 | | D4 | ç¨æ·æå® periodï¼ç¼ºæ°æä»½åæç¤ºæ¸ å | | D5 | ç¬ç«å°è¡¨ä¸ç»§ç»çæï¼è§å©ä½çé® Q2ï¼ | | E1 | éªæ¶ï¼å åå½âçæ 2026-07 == å·²ä¿®æ£æ¯ç diff=0âï¼åè¿ 8 æ | | E1 | éªæ¶ï¼å åå½âçæ 2026-07 == å·²ä¿®æ£æ¯ç diff=0âï¼åè¿ 8 æ **ã2026-10-13 å·²ä¿®è®¢ï¼æ¯ç已骨æ¶åï¼ä¸åä½çä»·åºçº¿ï¼éªæ¶æ¹æåå±å£å¾ï¼è§ææ«ç¬¬å «èã** | | E2 | 8 æéªè¯ï¼ç³»ç»èªå¨çæ vs 人工æ¯å¯¹ï¼éå¸å·å æ»=å ¨çï¼ä¸æºææ¥éé¡¹å¯¹é½ | | E3 | 缺æ°âæ±æ»è¡¨ç空+æç¤ºæ¸ åï¼è½¨è¿¹æµç®çå®¡æ ¸èæ°æ®ä¸è¿å ¥å¤§è¡¨ | @@ -74,3 +74,24 @@ - 以æ¬ãç¡®è®¤åæ§æ±æ»â¦ãææ¡£ä¸ºå¯ä¸å³çåºçº¿ï¼å¡å·²å¨ A1-E3 / Q1-Q3 / æ¬è¡¨ç¡®è®¤è¿çå£å¾ï¼ç´æ¥æ§è¡ï¼ä¸åé夿é®ã - åªå°±ææ¡£æªè¦ççæ°å³çåç¨æ·æé®ã ## å «ã2026-10-13 修订ï¼éªæ¶å£å¾ï¼æ¿ä»£ E1 åæªè¾ï¼ **修订åå **ï¼E1 åäºæ¹é åæï¼å½æ¶ãæ¯ç â æå·¥å®ç¨¿ä»¶ãï¼æ ãçæ == æ¯ç diff=0ãæç«ãæ¤åæ¯ç被骨æ¶å ï¼å½æåç空ãæå页ä¸åå¼ åæé¡µåçæ¯ä¸ä¸æéæå¼ï¼ï¼ç¸çå ³ç³»ä¸åæç«ï¼å®æµï¼çæ 2026-09 䏿¯çç¸æ¯ï¼ å·®å¼å ¨é¨è½å¨ãæ¯çç空ç±çæåå¡«ãä¸ãæå/åæé¡µæå½æéç®ã两类ï¼**çæç©ºãæ¯çæå¼ = 0 å¤ï¼æ 丢æ°**ï¼ã **æ°å£å¾ï¼éªæ¶ï¼åå±ï¼å ¨é¨éè¿æå¯äº¤ä»ï¼** | å± | æ¥ä»ä¹ | çå¼/便® | éè¿æ å | |---|---|---|---| | L1 ç»ææè¨ | å ¨ç°¿å ¬å¼ä¸è¡¨å¤´ | 表头æ¬èº« | 累计=å½å¹´ 1..M æä¹åï¼ç´¯è®¡åæ¯åæ¯=å»å¹´ 1..M æï¼**åå²åï¼å»å¹´å以åï¼é¶åå**ï¼æ æé¦æ¥æä¸²åå ¥ï¼`#REF!`=0 | | L2 å°è´¦æ°å¼ | 8 个å¯è§å°è´¦é¡µ | **彿ä¸å¡æºè¡¨** | éæ ¼ diff=0ï¼æå·®å¼é¡»éæ¡ç»åºç»è®ºå¹¶ç¾æ ¸ | | L3 å é¨å¾ç¨½ | å ¨ç°¿ | ç± L2 æ¨å¯¼ | å ¨ç=Σå¸å·ï¼ç´¯è®¡=Σåæï¼æå/å æ¯èªæ´½ | | L4 è·¨ææ¥è¦ | å ¨ç°¿ | 䏿件 | åå£å¾ç¯æ¯è¶ éå¼ï¼é»è®¤ ±30%ï¼åºæ°â¥100ï¼å³æç¤º | - **æ¯çåªä½ç弿¥æºï¼ä¸ä½éªæ¶åºçº¿**ï¼éªæ¶åºçº¿ = 彿ä¸å¡æºè¡¨ + ä¸å¡ç¡®è®¤åçå®ç¨¿ä»¶ï¼å»ºè®®å ¥åºä¸º `docs/çææ±æ»å¤§è¡¨/åºçº¿/golden_YYYY-MM.xlsx`ï¼ã - **ç½ååæºå¶**ï¼å·²ç¥ä¸å·²è§£éçå·®å¼ï¼å¦è½®æ¸¡åå½¢æãæ¯ç模æ¿éçæ¥æå¼ãæºè¡¨èªå¸¦èå¼è¢«æ¸ ç©ºï¼æ¾å¼åå ¥ç½ååï¼ **ç½ååä¹å¤çä»»ä½å·®å¼ï¼ä¸éè¿**ï¼ä¸åé äººå·¥éæ¡è§£éã - å·¥å ·ï¼`æ±æ»è¡¨åå±éªæ¶.py`ï¼ç¨æ³è§æä»¶å¤´ï¼ï¼2026-09 宿µï¼L1/L2/L3/L4 å ¨éè¿ï¼ç½åå 36 æ¡ã - 说æï¼æ¬å£å¾ä¿è¯ãç¨åºä¸æå¯¹çæ°æ®æé + 䏿ºè¡¨ä¸è´ + å¯è¿½æº¯ãï¼**æ°æ®æ¬èº«çæ£ç¡®æ§**ä»ç±å¯¼å ¥å®¡æ ¸ + ä¸å¡å£å¾ç¡®è®¤ + æ¯æç¾æ ¸è´è´£ã docs/ÒªÇóÓëÑéÊÕ»ùÏßÇåµ¥.md
@@ -33,9 +33,10 @@ |---|---|---|---| | R1 | éææ©å±çæï¼ä¿çæ¯çç¼å䏿¯çç¨åºå¤å¶ï¼ç´¯è®¡å ¬å¼å»ææªå°æä»½ã忝忝èå¨ | â M0/M1 | | | R2 | çæâéè·¯è¿è¾éæ±æ»è¡¨ï¼æ´æ¬ï¼â= å½æè¡¨ + 累计ï¼1-Xæï¼| â | | | R3 | çæç©ä¸æ¯ç/åº 0 å·®å¼ä¸ºéªæ¶æ åï¼å«ä¸å£å¾åæé¡µãæå页累计åï¼ | â 7æåå½ | | | R3 | çæç©ä¸æ¯ç/åº 0 å·®å¼ä¸ºéªæ¶æ åï¼å«ä¸å£å¾åæé¡µãæå页累计åï¼ | â 7æåå½ï¼**2026-10-13 已修订**ï¼æ¯çå·²ã骨æ¶åãï¼å½æåçç©ºï¼æå/åæé¡µå䏿éæå¼ï¼ä¸è½åå½çä»·åºçº¿ï¼éªæ¶æ¹æ R6ï¼ | | | R4 | ç¬ç«å°è¡¨ä¿çä½å端éèï¼ç大表è·éåå¯åæ¶ | â ææ¿ | | | R5 | æå/åæ/æ±æ»ç**å½ææ°æ®**è¦ä¸ç´¯è®¡æ°æ®åå«è®¡ç®ï¼ç¦æ¢æç´¯è®¡å¼å½å½æå¼ | ð§ M2/M3 å®å¼¹ | | | R6 | **éªæ¶ï¼åå±**ï¼2026-10-13 å®ï¼æ¿ä»£å E1/R3 çâ䏿¯ç diff=0âï¼ï¼L1 ç»ææè¨ï¼ç´¯è®¡=å½å¹´1..Mæä¹åãç´¯è®¡åæ¯åæ¯=å»å¹´1..Mæãåå²åé¶ååãæ æé¦æ¥æä¸²/æ é误å¼ï¼ï¼L2 ä¸**彿ä¸å¡æºè¡¨**éæ ¼æ¯å¯¹ï¼0 å·®å¼ï¼å·®å¼é¡»éæ¡ç»è®ºå¹¶ç¾æ ¸ï¼ï¼L3 å é¨å¾ç¨½ï¼å ¨ç=Σå¸å·ã累计=Σåæãæå/å æ¯èªæ´½ï¼ï¼L4 è·¨ææ¥è¦ï¼ä¸ä¸æä»¶åå£å¾ç¯æ¯ï¼è¶ éå¼æç¤ºââå¯ä¸è½å âæ²¡è§è¿çæ°é误âçä¸å±ï¼ãå·¥å ·ï¼`æ±æ»è¡¨åå±éªæ¶.py`ï¼**ç½ååä¹å¤çä»»ä½å·®å¼ï¼ä¸éè¿**ï¼æ¯çåªä½ç弿¥æºãä¸ä½çä»·åºçº¿ | ð§ å·¥å ·å·²å°±ç»ªï¼E1 æªè¾å·²åæ¥ä¿®è®¢ | | ## åãå®¡æ ¸è§å | ç¼å· | è¦æ± | ç¶æ | 夿³¨ | traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/SummaryWorkbookFiller.java
@@ -50,6 +50,9 @@ /** 2026 å¹´ 1 æå½æå¼åï¼0 åºï¼å³ C åï¼ */ private static final int FIRST_MONTH_COL = 2; /** è¡¨å¤´è¡æ«æèå´ï¼å ¼å®¹âä¸é¡µä¸¤åâç轨éã轮渡ï¼ç¬¬ 2 å表头å¨ç¬¬ 11 è¡ï¼ */ private static final int HDR_SCAN_ROWS = 15; /** åå¡« 1..keepMonths æå°è´¦é¡µï¼è¿ååå ¥æ ¼æ°ï¼ç¼ºæ°æç¤ºåå ¥ problems */ public int fillMonthlyLedger(XSSFWorkbook wb, String period, int year, int keepMonths, List<String> problems) { if (wb == null || keepMonths <= 0) return 0; @@ -837,6 +840,186 @@ return false; } /** * ã累计 / ç´¯è®¡åæ¯ãæè¡¨å¤´é建ï¼2026-10-13 æ°å¢ï¼æ ¹æ²»âç´¯è®¡æ¼æâï¼ã * * <p>èæ¯ï¼å½æåç±âæææ©åå¨âæå ¥æ¶åªå¨æ§å ¬å¼æ«å°¾è¿½å 彿项ãä¸é建ï¼ä¸æ¦åå²ä¸æ¼è¿ææ * ï¼æ©å卿©æçæ¬æå«å·§åæ¥æå¼çæ°æ®è¡è¯¯å¤æè¡¨å¤´è¡ä»èæ´è¡è·³è¿ââ2026-09 ç * å ¬äº¤!U8ãåºç§!U6ãä¸å£å¾æç»!U5 峿¤ï¼ï¼æ¼é¡¹ä¼æ°¸ä¹ ä¿çå¹¶éæç´¯ç§¯ã * * <p>æ¬æ¹æ³å¨çææ¶å°¾æ¶æè¡¨å¤´éå»ºå ¬å¼ï¼åºç¨¿éçå岿¬ è´¦èªå¨è¡¥é½ï¼ä¸ä¸åä¾èµåºç¨¿å ¬å¼æ¬èº«æ£ç¡®ï¼ * <ul> * <li>ã累计ãï¼å ¬å¼å¼ç¨å ¨é¨è½å¨æ¬æåº¦åçâå½å¹´å½æå¼åâ䏿¶ï¼éå为å½å¹´ 1..month åæå¼ä¹åï¼</li> * <li>ãç´¯è®¡åæ¯ãï¼å ¬å¼å¼ç¨åªç¨å°æ¬æåº¦åçâå»å¹´æä»½å + æ¬è¡ç´¯è®¡åâæ¶ï¼åæ¯éå为å»å¹´ 1..month åæå¼ä¹åï¼</li> * <li>å·²ç»å®æ´çå ¬å¼é¶æ¹å¨ï¼è·¨é¡µå¼ç¨çå ¶å®å½¢æä¸å¨ï¼åªè®¡æ°åè¦ï¼é¿å 误伤ã</li> * </ul> * å¤å页ï¼è½¨éã轮渡ä¸é¡µä¸¤åï¼æâ该è¡ä¸æ¹æè¿è¡¨å¤´è¡âç¡®å®æå±æä»½åã */ public int repairCumulativeFormulas(XSSFWorkbook wb, int year, int month) { if (wb == null || month < 1) return 0; int fixed = 0, skipped = 0; for (int i = 0; i < wb.getNumberOfSheets(); i++) { Sheet sh = wb.getSheetAt(i); if (sh == null || !isMonthLedgerSheet(sh.getSheetName())) continue; List<Integer> hdrs = headerRowsOf(sh, year); for (int hi = 0; hi < hdrs.size(); hi++) { int h = hdrs.get(hi); int blockEnd = (hi + 1 < hdrs.size()) ? hdrs.get(hi + 1) - 1 : sh.getLastRowNum(); Map<Integer, Integer> cur = new LinkedHashMap<>(); Map<Integer, Integer> lastY = new LinkedHashMap<>(); monthColsOfRow(sh, h, year, month, cur, lastY); if (cur.isEmpty() || lastY.isEmpty()) continue; int lastMonthCol = 0; for (int c : cur.values()) lastMonthCol = Math.max(lastMonthCol, c); Integer cumCol = cumulativeColOfRow(sh, h, lastMonthCol); if (cumCol == null) continue; Integer cumYoyCol = cumYoyColOfRow(sh, h, cumCol); java.util.Set<Integer> curSet = new java.util.HashSet<>(cur.values()); java.util.Set<Integer> lastSet = new java.util.HashSet<>(lastY.values()); for (int r = h + 1; r <= blockEnd; r++) { Row row = sh.getRow(r); if (row == null) continue; Cell cum = row.getCell(cumCol); if (cum != null && cum.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) { java.util.Set<Integer> used = sameRowRefCols(cum.getCellFormula(), r + 1); if (!used.isEmpty()) { if (used.equals(curSet)) { // 已宿´ï¼é¶æ¹å¨ } else if (curSet.containsAll(used)) { String nf = refsJoined(cur, r + 1); if (nf != null && !nf.equals(cum.getCellFormula())) { cum.setCellFormula(nf); fixed++; } } else { skipped++; } } } if (cumYoyCol != null) { Cell cy = row.getCell(cumYoyCol); if (cy != null && cy.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) { java.util.Set<Integer> used = sameRowRefCols(cy.getCellFormula(), r + 1); used.remove(cumCol); if (!used.isEmpty()) { if (used.equals(lastSet)) { // 已宿´ï¼é¶æ¹å¨ } else if (lastSet.containsAll(used)) { String den = refsJoined(lastY, r + 1); if (den != null) { String nf = cellRef(cumCol, r + 1) + "/SUM(" + den + ")-1"; if (!nf.equals(cy.getCellFormula())) { cy.setCellFormula(nf); fixed++; } } } else { skipped++; } } } } } } } if (fixed > 0 && log != null) { log.info("æ±æ»å·¥ä½ç°¿ï¼ç´¯è®¡/ç´¯è®¡åæ¯æè¡¨å¤´é建 {} æ ¼ï¼ç¬¬ {} æï¼å½¢æä¸ç¬¦è·³è¿ {} æ ¼ï¼", fixed, month, skipped); } else if (skipped > 0 && log != null) { log.warn("æ±æ»å·¥ä½ç°¿ï¼{} æ ¼ç´¯è®¡/ç´¯è®¡åæ¯å ¬å¼å½¢æä¸ç¬¦ï¼æªèªå¨é建ï¼é人工确认ï¼", skipped); } return fixed; } /** 表头è¡ï¼0 åºï¼ï¼å åâæä»½æ ¼æå¤çé£ä¸è¡â为åºåï¼åªæè¿äºåä¸è¿åæ¯æä»½æ ¼çè¡æç®è¡¨å¤´è¡ï¼åæ©åå¨å£å¾ï¼ */ private List<Integer> headerRowsOf(Sheet sh, int year) { int last = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + HDR_SCAN_ROWS); List<Integer> base = new ArrayList<>(); for (int r = sh.getFirstRowNum(); r <= last; r++) { List<Integer> cols = monthHeaderCols(sh.getRow(r), year); if (cols.size() > base.size()) base = cols; } List<Integer> rows = new ArrayList<>(); if (base.size() < 2) return rows; int need = Math.max(2, (base.size() + 1) / 2); for (int r = sh.getFirstRowNum(); r <= last; r++) { Row row = sh.getRow(r); if (row == null) continue; int hit = 0; for (int c : base) if (yearMonthOf(row.getCell(c), year) != null) hit++; if (hit >= need) rows.add(r); } return rows; } private List<Integer> monthHeaderCols(Row row, int year) { List<Integer> cols = new ArrayList<>(); if (row == null) return cols; for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { if (yearMonthOf(row.getCell(c), year) != null) cols.add(c); } return cols; } /** è¡¨å¤´è¡ -> å½å¹´ 1..month æå¼å / å»å¹´ 1..month æå¼åï¼åæåæå·¦ä¸åï¼ */ private void monthColsOfRow(Sheet sh, int hdrRow, int year, int month, Map<Integer, Integer> cur, Map<Integer, Integer> lastY) { Row row = sh.getRow(hdrRow); if (row == null) return; for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { Integer ym = yearMonthOf(row.getCell(c), year); if (ym == null) continue; int y = ym / 100, m = ym % 100; if (m < 1 || m > month) continue; if (y == year && !cur.containsKey(m)) cur.put(m, c); else if (y == year - 1 && !lastY.containsKey(m)) lastY.put(m, c); } } /** 该表头è¡ã累计ãåï¼0 åºï¼ï¼æåä¸ä¸ªæä»½åå³ä¾§ 3 åå å«â累计âä¸ä¸å«â忝âçè¡¨å¤´æ ¼ */ private Integer cumulativeColOfRow(Sheet sh, int hdrRow, int lastMonthCol) { Row row = sh.getRow(hdrRow); if (row == null) return null; int end = Math.min(row.getLastCellNum() - 1, lastMonthCol + 3); for (int c = lastMonthCol + 1; c <= end; c++) { String t = text(row.getCell(c)); if (t == null) continue; t = t.trim(); if (t.contains("累计") && !t.contains("忝") && !YM_RANGE_CUM.matcher(t).matches()) return c; } return null; } /** ã累计ãå³é»ï¼æåå³ä¸æ ¼ï¼æ¯å¦ä¸ºãç´¯è®¡åæ¯ã */ private Integer cumYoyColOfRow(Sheet sh, int hdrRow, int cumCol) { Row row = sh.getRow(hdrRow); if (row == null) return null; String t = text(row.getCell(cumCol + 1)); if (t != null && t.trim().contains("忝")) return cumCol + 1; String t2 = text(row.getCell(cumCol + 2)); return (t2 != null && t2.trim().contains("忝")) ? cumCol + 2 : null; } /** å ¬å¼ä¸âå¼ç¨åä¸è¡âçåå·éåï¼0 åºï¼ï¼SUM ç彿°ååæ æ°åï¼ä¸ä¼è¢«è¯¯å¹é */ private java.util.Set<Integer> sameRowRefCols(String formula, int row1) { java.util.Set<Integer> cols = new java.util.HashSet<>(); if (formula == null) return cols; java.util.regex.Matcher m = A1_REF.matcher(formula); while (m.find()) { if (Integer.parseInt(m.group(4)) == row1) cols.add(colIndexOf(m.group(2))); } return cols; } /** æ {æ->å} ææä»½é¡ºåºæ¼æ "C5+E5+G5..." */ private String refsJoined(Map<Integer, Integer> monthToCol, int row1) { StringBuilder sb = new StringBuilder(); for (Map.Entry<Integer, Integer> e : new java.util.TreeMap<>(monthToCol).entrySet()) { if (sb.length() > 0) sb.append('+'); sb.append(cellRef(e.getValue(), row1)); } return sb.length() == 0 ? null : sb.toString(); } /** 该页ã累计 / ç´¯è®¡åæ¯ã两åï¼0 åºï¼ï¼æè¡¨å¤´å 5 è¡æ¾ âç´¯è®¡â æ â{year}年累计âï¼ä¸å ¶å³åå« â忝â */ private int[] cumulativeCols(Sheet sh, int year) { int last = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + 4); traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -3767,30 +3767,218 @@ int monthFormulaFilled = summaryWorkbookFiller.ensureMonthFormulas(wb, year, effMonth); // ç´¯è®¡åæ¯åæ¯ä¿®æ£ï¼æ¯çæãå»å¹´åæãä¸ãå»å¹´1-Mæç´¯è®¡ãè¾ å©ååæ¶è®¡å ¥ï¼éå¤è®¡/æ¼å½æï¼ int cumYoyFixed = summaryWorkbookFiller.repairCumulativeYoyFormulas(wb, year, effMonth); // 2026-10-13ï¼ç´¯è®¡å / ç´¯è®¡åæ¯åæ¯æè¡¨å¤´éå»ºï¼æ ¹æ²»æ©å卿©æâç´¯è®¡æ¼æâçå岿¬ è´¦ï¼ int cumRebuilt = summaryWorkbookFiller.repairCumulativeFormulas(wb, year, effMonth); reportProgress(progress, 45, "å·æ°æå页ä¸è·¨é¡µåæé¡µ"); int rankRefreshed = refreshSummaryRankSheets(wb, effPeriod, "month"); int rankRefreshed = refreshSummaryRankSheets(wb, effPeriod, "month", dbProblems); int cumulativeRefreshed = refreshSummaryCumulativeSheets(wb, year, effMonth); // åå¼ è·¨é¡µåæé¡µéæ¥è¡¨æå·æ° // 2026-10-12ï¼åç¨ãå¥ç¨å¤é¨åç §ä»¶ãçæ_éè·¯è¿è¾éæ±æ»è¡¨.xlsxãå宽ãââçå¼ä¸å¾ä»¥å½ææ¯ç为åï¼ // é¿å æ¯çä¸åç §ä»¶ä¸¤å¤ç»´æ¤ï¼æ¯çå·²å¹¶å ¥åç §ä»¶çå宽/éèï¼ã int widthApplied = 0; log.info("æ±æ»å·¥ä½ç°¿æ°æ®é©±å¨å填宿ï¼åå ¥å½æå¼æ ¼æ°={}ï¼è¡¥åå½æåæ¯å ¬å¼æ ¼æ°={}ï¼è¡¥å彿åè®¡å ¬å¼æ ¼æ°={}ï¼" + "ä¿®æ£ç´¯è®¡åæ¯åæ¯æ ¼æ°={}ï¼å·æ°æå页æ°å¼æ ¼æ°={}ï¼å·æ°å页ï¼å叿±æ»/åå¸åå¸å·/ä¸å£å¾åæ/éè·¯è¿è¾å¨è½¬éï¼æ°å¼æ ¼æ°={}ï¼" + "ä¿®æ£ç´¯è®¡åæ¯åæ¯æ ¼æ°={}ï¼é建累计/ç´¯è®¡åæ¯æ ¼æ°={}ï¼å·æ°æå页æ°å¼æ ¼æ°={}ï¼" + "å·æ°å页ï¼å叿±æ»/åå¸åå¸å·/ä¸å£å¾åæ/éè·¯è¿è¾å¨è½¬éï¼æ°å¼æ ¼æ°={}ï¼" + "æãçæ_éè·¯è¿è¾éæ±æ»è¡¨ã对é½å宽忰={}ï¼ç¼ºæ°æç¤º={}", dbFilled, yoyFilled, monthFormulaFilled, cumYoyFixed, rankRefreshed, cumulativeRefreshed, widthApplied, dbProblems); dbFilled, yoyFilled, monthFormulaFilled, cumYoyFixed, cumRebuilt, rankRefreshed, cumulativeRefreshed, widthApplied, dbProblems); applyTwoDecimalFormat(wb); // æ°å¼æ¾ç¤ºä¸¤ä½å°æ°ï¼ä¿çå ¨ç²¾åº¦ï¼ï¼% / æ¥æçæ¢ææ ·å¼ä¸æ¹å int inlineFixed = normalizeStaleInlineStrings(wb); // æ¸ æãé inlineStr å´æ®ç <is>ãçéæ³æ ¼ï¼å¦å Excel æç»æå¼ if (inlineFixed > 0) log.warn("æ±æ»å·¥ä½ç°¿æ¸ ææ®ç inlineStr éæ³æ ¼ {} ä¸ªï¼æ¯çç± openpyxl ååºæ¶çå ¸åé®é¢ï¼", inlineFixed); reportProgress(progress, 70, "éç®æ´æ¬å ¬å¼ï¼çº¦ 4 䏿 ¼ï¼"); int[] recalc = recalcWorkbookFormulas(wb); // å ¬å¼éç®å¹¶ååç¼åå¼ï¼å ¨çè¡(Σå¸å·)/累计å/æå SUM æè½è¢«é¢è§ä¸äºæ¬¡å¤çè¯»å° setSheetViewsToCurrentMonth(wb, year, effMonth); // æå¼æ¶å®ä½å°æ¬ææ°æ®åï¼ä¸å½±åæ°æ®ä¸æ ¼å¼ï¼ log.info("æ±æ»å·¥ä½ç°¿å ¬å¼éç®å®æï¼æå {} æ ¼ï¼å¤±è´¥ {} æ ¼ï¼å¤±è´¥æ ¼ä¿çæ¯çæ§ç¼åå¼ï¼æå¼æ¶æ fullCalcOnLoad éç®ï¼", recalc[0], recalc[1]); if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr(); wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // è¿å/æ¹åå ¬å¼åæå¼å³éç® appendMissingDataSheet(wb, effPeriod, dbProblems); // 2026-10-12ï¼ç¼ºæ°/使£ç»æåè¿çæä»¶ï¼ç¨æ·è¦æ±ï¼ reportProgress(progress, 95, "ååºæä»¶"); return toBytes(wb); return normalizeSheetViews(toBytes(wb), wb, year, effMonth); } finally { ZipSecureFile.setMinInflateRatio(savedZipRatio); } } /** * æå¼å·¥ä½ç°¿æ¶çå®ä½ï¼2026-10-13ï¼ï¼ææ¯ä¸ªé¡µç¾çæ»å¨/éä¸ä½ç½®å½ä¸å°ãæ¬ææ°æ®åãï¼ * å¹¶æ¸ ææ¯çï¼è¢« Excel/WPS åè¿ï¼æ®ççæ§ topLeftCell/activeCell ââ å¦åæ¯ä¸ªé¡µç¾ä¼åå¨ * 䏿¬¡ç¼è¾æ¶ç奿ªä½ç½®ï¼å®æµæ¯çé åå¸å®¢è¿ BQ38ãä¸å£å¾æç» CC103ãè´§è¿æå A40â¦ï¼ã * å»ç»æåï¼xSplit/ySplit/stateï¼åæ ·ä¿çï¼åªæéç¹æªå°æ¬ææä»½åã */ private byte[] normalizeSheetViews(byte[] xlsx, XSSFWorkbook wb, int year, int month) { try { java.util.Map<String, String> targets = sheetViewTargets(wb, year, month); if (targets.isEmpty()) return xlsx; java.util.Map<String, byte[]> entries = new java.util.LinkedHashMap<>(); try (java.util.zip.ZipInputStream zin = new java.util.zip.ZipInputStream(new java.io.ByteArrayInputStream(xlsx))) { java.util.zip.ZipEntry e; byte[] buf = new byte[8192]; while ((e = zin.getNextEntry()) != null) { java.io.ByteArrayOutputStream b = new java.io.ByteArrayOutputStream(); int n; while ((n = zin.read(buf)) > 0) b.write(buf, 0, n); entries.put(e.getName(), b.toByteArray()); } } byte[] wbx = entries.get("xl/workbook.xml"); byte[] rlx = entries.get("xl/_rels/workbook.xml.rels"); if (wbx == null || rlx == null) return xlsx; String wbs = new String(wbx, java.nio.charset.StandardCharsets.UTF_8); String rls = new String(rlx, java.nio.charset.StandardCharsets.UTF_8); java.util.Map<String, String> rid2t = new java.util.HashMap<>(); java.util.regex.Matcher rm = java.util.regex.Pattern.compile("Id=\"([^\"]+)\"[^>]*Target=\"([^\"]+)\"").matcher(rls); while (rm.find()) rid2t.put(rm.group(1), rm.group(2)); java.util.Map<String, String> name2part = new java.util.LinkedHashMap<>(); java.util.regex.Matcher sm = java.util.regex.Pattern.compile("<sheet[^>]*name=\"([^\"]+)\"[^>]*r:id=\"([^\"]+)\"").matcher(wbs); while (sm.find()) { String t = rid2t.get(sm.group(2)); if (t == null) continue; if (t.startsWith("/")) t = t.substring(1); else if (!t.startsWith("xl/")) t = "xl/" + t; name2part.put(sm.group(1), t); } int patched = 0; for (java.util.Map.Entry<String, String> en : name2part.entrySet()) { String target = targets.get(en.getKey()); byte[] part = entries.get(en.getValue()); if (target == null || part == null) continue; String xml = new String(part, java.nio.charset.StandardCharsets.UTF_8); String np = patchSheetView(xml, target); if (!np.equals(xml)) { entries.put(en.getValue(), np.getBytes(java.nio.charset.StandardCharsets.UTF_8)); patched++; } } try (java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(xlsx.length); java.util.zip.ZipOutputStream zout = new java.util.zip.ZipOutputStream(bos)) { for (java.util.Map.Entry<String, byte[]> en : entries.entrySet()) { zout.putNextEntry(new java.util.zip.ZipEntry(en.getKey())); zout.write(en.getValue()); zout.closeEntry(); } zout.finish(); log.info("æ±æ»å·¥ä½ç°¿ï¼é¡µç¾æå¼ä½ç½®å·²å½ä¸å°æ¬æï¼{}å¹´{}æï¼ï¼å¤ç {} 个页ç¾", year, month, patched); return bos.toByteArray(); } } catch (Exception ex) { log.warn("æ±æ»å·¥ä½ç°¿ï¼é¡µç¾æå¼ä½ç½®å½ä¸å¤±è´¥ï¼å¿½ç¥ï¼ä¿çåæ ·ï¼ï¼{}", ex.toString()); return xlsx; } } /** æ¯é¡µçå®ä½ç®æ ï¼ææ¬ææä»½å â 该æè¡¨å¤´æ ¼ï¼æ²¡æï¼æå/åæé¡µï¼â A1ï¼é¡ºå¸¦æ¸ ææ®çä½ç½®ï¼ */ private java.util.Map<String, String> sheetViewTargets(XSSFWorkbook wb, int year, int month) { java.util.Map<String, String> m = new java.util.LinkedHashMap<>(); for (int i = 0; i < wb.getNumberOfSheets(); i++) { XSSFSheet sh = wb.getSheetAt(i); if (sh == null) continue; int headerRow = -1, col = -1; int scan = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + 4); for (int r = sh.getFirstRowNum(); r <= scan && col < 0; r++) { Row row = sh.getRow(r); if (row == null) continue; for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { Integer ym = smYearMonthOf(row.getCell(c)); if (ym != null && ym / 100 == year && ym % 100 == month) { headerRow = r; col = c; break; } } } m.put(sh.getSheetName(), col >= 0 ? a1Ref(headerRow, col) : "A1"); } return m; } /** 0 åºè¡å -> "S3" */ private String a1Ref(int row0, int col0) { StringBuilder sb = new StringBuilder(); int n = col0 + 1; while (n > 0) { int rem = (n - 1) % 26; sb.insert(0, (char) ('A' + rem)); n = (n - 1) / 26; } return sb.toString() + (row0 + 1); } /** 廿屿§ */ private String stripAttr(String attrs, String name) { int i = attrs.indexOf(name + "=\""); if (i < 0) return attrs; int e = attrs.indexOf('"', i + name.length() + 2); if (e < 0) return attrs; return attrs.substring(0, i) + attrs.substring(e + 1); } /** æ¾æ ç¾èµ·ç¹ï¼è¦æ±æ ç¾ååé¢ç´§è· ç©ºæ ¼/>ï¼é¿å "<sheetView" å½ä¸ "<sheetViews" */ private int findTag(String xml, String tag) { int from = 0; while (true) { int i = xml.indexOf(tag, from); if (i < 0) return -1; int nx = i + tag.length(); char ch = nx < xml.length() ? xml.charAt(nx) : '>'; if (ch == ' ' || ch == '>' || ch == '/') return i; from = i + 1; } } /** å屿§å¼ï¼ä¸åå¨è¿å nullï¼ */ private String attrValue(String attrs, String name) { int i = attrs.indexOf(name + "=\""); if (i < 0) return null; int e = attrs.indexOf('"', i + name.length() + 2); return e < 0 ? null : attrs.substring(i + name.length() + 2, e); } /** 追å 屿§ï¼å·²åå¨åä¸éå¤å ï¼ */ private String addAttr(String attrs, String name, String v) { if (attrValue(attrs, name) != null) return attrs; String sep = (attrs.isEmpty() || attrs.endsWith(" ")) ? "" : " "; return attrs + sep + name + "=\"" + v + "\""; } /** * å页 XML å½ä¸ï¼sheetView / pane ç topLeftCellãæ´»å¨ pane ç selection å ¨é¨æå targetã * å»ç»æåï¼xSplit/ySplit/stateï¼åæ ·ä¿çï¼ç ´åç»æåè¿ååæï¼failed-safeï¼ã */ private String patchSheetView(String xml, String target) { String orig = xml; int i = findTag(xml, "<sheetView"); if (i >= 0) { int j = xml.indexOf('>', i); if (j > 0) { String attrs = addAttr(stripAttr(xml.substring(i + 10, j), "topLeftCell"), "topLeftCell", target); xml = xml.substring(0, i) + "<sheetView" + attrs + ">" + xml.substring(j + 1); } } String activePane = null; int p = findTag(xml, "<pane"); if (p >= 0) { int q = xml.indexOf("/>", p); if (q > 0) { String raw = xml.substring(p + 5, q); activePane = attrValue(raw, "activePane"); String attrs = addAttr(stripAttr(raw, "topLeftCell"), "topLeftCell", target); xml = xml.substring(0, p) + "<pane" + attrs + "/>" + xml.substring(q + 2); } } StringBuilder sb = new StringBuilder(); int last = 0, pos = 0; boolean done = false, any = false; while (true) { int s0 = xml.indexOf("<selection", pos); if (s0 < 0) break; int s1 = xml.indexOf("/>", s0); if (s1 < 0) break; String raw = xml.substring(s0 + 10, s1); String attrs = stripAttr(stripAttr(raw, "activeCell"), "sqref"); String pane = attrValue(raw, "pane"); boolean hit = (activePane != null) ? (pane != null && pane.equals(activePane)) : !done; if (hit) { attrs = addAttr(addAttr(attrs, "activeCell", target), "sqref", target); done = true; } sb.append(xml, last, s0).append("<selection").append(attrs).append("/>"); last = s1 + 2; pos = s1 + 2; any = true; } if (!any) { int close = xml.indexOf("</sheetView>"); if (close > 0) xml = xml.substring(0, close) + "<selection activeCell=\"" + target + "\" sqref=\"" + target + "\"/>" + xml.substring(close); } else { sb.append(xml, last, xml.length()); xml = sb.toString(); } if (!xml.contains("<sheetViews>") || !xml.contains("</sheetViews>")) return orig; return xml; } /** @@ -4062,11 +4250,11 @@ * çç»æå·æ°æ°å¼ï¼é¿å æ±æ»è¡¨éçæå页åç卿§æä»½ã * æ°å¼æ ¼æ¬å¼ãå ¬å¼æ ¼æåç§»å¹³ç§»å ¬å¼ååå ¥ï¼ç®æ æ ¼æ¬èº«å·²æ¯å ¬å¼çï¼SUM/RANK/å æ¯/è¡¥æ°ï¼ä¿çä¸å¨ï¼æå¼æ¶ææ°æ°æ®éç®ã */ private int refreshSummaryRankSheets(XSSFWorkbook wb, String period, String mode) throws Exception { private int refreshSummaryRankSheets(XSSFWorkbook wb, String period, String mode, List<String> problems) throws Exception { int n = 0; n += copyRankValues(wb, "è´§è¿éæå", exportFreightRank(period, mode), new int[][]{{0, 0}, {22, 0}}); n += copyRankValues(wb, "è´§è¿å¨è½¬éæå", exportTurnoverRank(period, mode), new int[][]{{0, 0}, {22, 0}}); n += copyRankValues(wb, "ä¸å£å¾æå", exportPassengerMidRank(period, mode, wb), new int[][]{{0, 0}, {0, 10}}); n += copyRankValues(wb, "è´§è¿éæå", exportFreightRank(period, mode), new int[][]{{0, 0}, {22, 0}}, problems); n += copyRankValues(wb, "è´§è¿å¨è½¬éæå", exportTurnoverRank(period, mode), new int[][]{{0, 0}, {22, 0}}, problems); n += copyRankValues(wb, "ä¸å£å¾æå", exportPassengerMidRank(period, mode, wb), new int[][]{{0, 0}, {0, 10}}, problems); return n; } @@ -4075,13 +4263,15 @@ * srcBlocks[i] = æºå i çèµ·å§ {è¡, å}ï¼0 åºï¼ï¼ç®æ åææ é¢é¡ºåºï¼ç´¯è®¡åå¨åã彿åå¨åï¼å¯¹åºã * æ°å¼æ ¼ç´æ¥æ¬å¼ï¼æºä¾§æ¯å ¬å¼çï¼æå/å æ¯çï¼æç®æ ååç§»æ´ä½å¹³ç§»ååå ¥å ¬å¼ï¼æå¼æ¶ææ±æ»è¡¨èªèº«æ°æ®éç®ã */ private int copyRankValues(XSSFWorkbook dstWb, String dstSheetName, byte[] srcBytes, int[][] srcBlocks) throws Exception { private int copyRankValues(XSSFWorkbook dstWb, String dstSheetName, byte[] srcBytes, int[][] srcBlocks, List<String> problems) throws Exception { XSSFSheet dst = dstWb.getSheet(dstSheetName); if (dst == null) return 0; List<XSSFCell> dstTitles = rankTitleCells(dst); if (dstTitles.isEmpty()) return 0; int copied = 0; FormulaEvaluator dstEv = dstWb.getCreationHelper().createFormulaEvaluator(); List<int[]> anchors = new ArrayList<>(); // {æºå ¨çè¡, ç®æ å ¨çè¡, åé¦å, ååç§», å宽} try (InputStream sin = new ByteArrayInputStream(srcBytes); XSSFWorkbook srcWb = new XSSFWorkbook(sin)) { XSSFSheet src = srcWb.getSheetAt(0); @@ -4091,10 +4281,27 @@ int sr0 = srcBlocks[b][0]; int sc0 = srcBlocks[b][1]; XSSFCell dstTitle = dstTitles.get(b); int rowOff = dstTitle.getRowIndex() - sr0; int colOff = dstTitle.getColumnIndex() - sc0; int rowOff = dstTitle.getRowIndex() - sr0; // 2026-10-13ï¼ãè´§è¿å¨è½¬éæåãæ¯çåæ é¢æ¾å 两è¡ï¼åå¹¶ï¼ï¼"æ é¢è¡+åºå®åç§»"伿æºè¡ // æ´ä½åéä½ä¸è¡ï¼å ¨çå¼è½å°è¡¨å¤´è¡ãå¸å·å¼ä¸å¸å·åéå¼ï¼ãæ¹ä¸ºç¨ãå ¨çãè¡åéç¹å¯¹é½ï¼ // rowOff = ç®æ å ¨çè¡ - æºå ¨çè¡ï¼è¯å«ä¸å°éç¹ååéæ é¢è¡å移并åè¦ã Integer srcAnchor = rankAnchorRow(src, sr0, sc0); Integer dstAnchor = rankAnchorRow(dst, dstTitle.getRowIndex(), dstTitle.getColumnIndex()); if (srcAnchor != null && dstAnchor != null) { int anchored = dstAnchor - srcAnchor; if (anchored != rowOff) { log.info("æå页ã{}ã第 {} åï¼æ é¢è¡åç§» {} ä¸ãå ¨çãéç¹åç§» {} ä¸ä¸è´ï¼æéç¹å¯¹é½ï¼æºå ¨çè¡={}ï¼ç®æ å ¨çè¡={}ï¼", dstSheetName, b + 1, rowOff, anchored, srcAnchor + 1, dstAnchor + 1); } rowOff = anchored; } else { log.warn("æå页ã{}ã第 {} åï¼æªè¯å«å°ãå ¨çãéç¹ï¼æº={}ï¼ç®æ ={}ï¼ï¼åéææ é¢è¡åç§» {} 对é½", dstSheetName, b + 1, srcAnchor, dstAnchor, rowOff); } int h = rankBlockHeight(src, sr0, sc0); int w = rankBlockWidth(src, sr0, sc0); anchors.add(new int[]{srcAnchor == null ? -1 : srcAnchor, dstAnchor == null ? -1 : dstAnchor, sc0, colOff, w}); // 2026-10-12ï¼æ´å缺æ°ï¼æºä¾§ä¸ä¸ªæ°å¼é½æ²¡æï¼å¦ 9 æç¼ºâ¡â¢æ¶çå¨è½¬éæåï¼â ç®æ ååªçæ é¢ä¸æ ç¾ï¼ // æ°æ®/æå/å æ¯ä¸å¾ç空ï¼å¦åæ¯çç RANK/å æ¯å ¬å¼ä¼å¨ç©ºåä¸ç®åºãå ¨ 1 + #DIV/0!ãã boolean blockHasData = false; @@ -4173,6 +4380,34 @@ } } } // 2026-10-13 èªæ£ï¼ç®æ ãå ¨çãè¡åºä¸æºä¾§ä¸è´ï¼åç§»éä½ãæ±æ»åºé´ä¸å¯¹æ¶æ¥è¦å¹¶åè¿ç¼ºæ°æ¸ åï¼ for (int[] an : anchors) { if (an[0] < 0 || an[1] < 0) continue; Row srr = src.getRow(an[0]); Row drr = dst.getRow(an[1]); if (srr == null || drr == null) continue; int badCols = 0; StringBuilder ex = new StringBuilder(); for (int c = 0; c < an[4]; c++) { Cell sCell = srr.getCell(an[2] + c); Cell dCell = drr.getCell(an[2] + c + an[3]); Double sv = sCell == null ? null : rankNumeric(sCell, ev); Double dv = dCell == null ? null : rankNumeric(dCell, dstEv); if (sv == null && dv == null) continue; if (sv == null || dv == null || Math.abs(sv - dv) > Math.max(1.0, Math.abs(sv) * 1e-4)) { badCols++; if (badCols <= 3) { if (ex.length() > 0) ex.append("ï¼"); ex.append(colLetter(an[2] + c)).append(" æº=").append(sv).append(" ç®æ =").append(dv); } } } if (badCols >= 2) { String msg = "æå页ã" + dstSheetName + "ããå ¨çãè¡ä¸æºä¾§ä¸ä¸è´ï¼" + badCols + " åï¼ï¼" + ex; log.warn(msg); if (problems != null) problems.add("çææ ¡éªï¼" + msg); } } } return copied; } @@ -4239,6 +4474,23 @@ } /** æå页éçâåæ é¢âåå æ ¼ï¼å«âå ¨çåå¸å·âç说ææåï¼ï¼æè¡ãå顺åºè¿å */ /** å¨ [fromRow, fromRow+40) èå´å ãåèµ·å§ä¸¤åéæ¾æ´æ ¼ææ¬ä¸ºãå ¨çãçè¡å·ï¼0 åºï¼ï¼æ¾ä¸å°è¿å nullã * 2026-10-13ï¼æå页åç»æå¯è½å æ¯ççå¼ä¸åèæ´ä½ä¸/ä¸ç§»ä¸è¡ï¼ç¨ãå ¨çãè¡åéç¹å¯èªå¨å¯¹é½ã */ private Integer rankAnchorRow(Sheet sh, int fromRow, int col0) { int last = Math.min(sh.getLastRowNum(), fromRow + 40); for (int r = fromRow; r <= last; r++) { Row row = sh.getRow(r); if (row == null) continue; for (int c = Math.max(0, col0); c <= col0 + 1; c++) { Cell cell = row.getCell(c); if (cell == null || cell.getCellType() != CellType.STRING) continue; String v = cell.getStringCellValue(); if (v != null && v.replace(" ", "").equals("å ¨ç")) return r; } } return null; } private List<XSSFCell> rankTitleCells(Sheet sh) { List<XSSFCell> out = new ArrayList<>(); for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) { @@ -4563,6 +4815,34 @@ return cols; } /** * æå¼å·¥ä½ç°¿æ¶ææ¯ä¸ªé¡µç¾å®ä½å°ãæ¬ææ°æ®ãä½ç½®ï¼2026-10-13 ç¨æ·è¦æ±ï¼ã * åæ³ï¼å¡è½æè¡¨å¤´è¯å«åºãå½å¹´å½æå¼åãçé¡µï¼ææ´»å¨åå æ ¼è®¾å°è¯¥æè¡¨å¤´æ ¼ï¼å ¶ä½é¡µä¿æé»è®¤ã * åªå½±åæå¼æ¶çæ»å¨/éä¸ä½ç½®ï¼ä¸æ¹ä»»ä½æ°æ®ä¸æ ¼å¼ã */ private int setSheetViewsToCurrentMonth(XSSFWorkbook wb, int year, int month) { int n = 0; for (int i = 0; i < wb.getNumberOfSheets(); i++) { XSSFSheet sh = wb.getSheetAt(i); if (sh == null) continue; int headerRow = -1, col = -1; int scan = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + 4); for (int r = sh.getFirstRowNum(); r <= scan && col < 0; r++) { Row row = sh.getRow(r); if (row == null) continue; for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { Integer ym = smYearMonthOf(row.getCell(c)); if (ym != null && ym / 100 == year && ym % 100 == month) { headerRow = r; col = c; break; } } } if (col < 0) continue; sh.setActiveCell(new org.apache.poi.ss.util.CellAddress(headerRow, col)); n++; } if (n > 0) log.info("æ±æ»å·¥ä½ç°¿ï¼{} 个页ç¾å·²å®ä½å°æ¬æï¼{}å¹´{}æï¼æ°æ®å", n, year, month); return n; } /** 表头åå æ ¼ -> å¹´*100+æï¼åªè®¤ãYYYYå¹´Mæãææ¬æãææ 1 å·ãçæ¥æåºåå·ï¼æé¤ç´¯è®¡/忝/ç¯æ¯ */ private Integer smYearMonthOf(Cell c) { if (c == null) return null;