\ feat: 汇总大表生成支持toMonth裁剪(1-6月生成+1-7月扩列)及验证产物与测试报告\
3个文件已修改
3个文件已添加
141 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_汇总大表1-6月生成与1-7月扩列验证_2026-09-04.md 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-04.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/输出/道路运输量汇总表_2026-06.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/输出/道路运输量汇总表_2026-07.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»ã×Ü´ó±í1-6ÔÂÉú³ÉÓë1-7ÔÂÀ©ÁÐÑéÖ¤_2026-09-04.md
New file
@@ -0,0 +1,46 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šæ±‡æ€»å¤§è¡¨ 1-6 æœˆç”Ÿæˆ + 1-7 æœˆæ‰©åˆ—(2026-09-04)
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
- ç›®æ ‡ï¼šéªŒè¯ã€Šé“路运输量汇总表》生成程序可产出两本产物——
  1. **1-7 æœˆæ‰©åˆ—**(`period=2026-07`):以“1-4 æœˆéª¨æž¶â€æ¯ç‰ˆä¸ºåº•、按同期备份母版还原月度块,得到 1-7 æœˆå®Œæ•´å·¥ä½œç°¿ï¼›
  2. **1-6 æœˆç”Ÿæˆ**(`period=2026-07&toMonth=6`):在上述 1-7 å†…容基础上,把 8 ä¸ªå°è´¦/合成页裁剪到 6 æœˆâ€”—
     - 7 æœˆå½“月值列(O)、7 æœˆåŒæ¯”列(P)的**数据格清空,表头保留**(用户拍板口径 â‘ ï¼‰ï¼›
     - ç´¯è®¡å…¬å¼ï¼ˆQ)去掉 7 æœˆé¡¹ï¼Œç´¯è®¡åŒæ¯”公式(R)分母去掉 2025 å¹´ 7 æœˆåˆ—(=2025 å¹´ 1-6 æœˆï¼Œç”¨æˆ·æ‹æ¿å£å¾„ â‘¡ï¼‰ã€‚
- ä»£ç æ”¹åŠ¨ï¼š
  - `ReportExportService.exportSummaryWorkbook(period, mode, toMonth)`:新增可选 `toMonth` è£å‰ªï¼›
  - æ–°å¢ž `trimSummaryMonthlySheets(wb, keepMonths)`:只处理 8 ä¸ªå°è´¦/合成页(` è´§è¿`、班线包车、城市客运、公交、出租、网约车、轨道、轮渡、中口径明细),按“含累计公式且任一 2026 æœˆåº¦å€¼åˆ—非空”识别数据行;
  - `ReportExportController /export/summaryWorkbook` å¢žåŠ å¯é€‰å‚æ•° `toMonth`。
- äº§ç‰©ï¼ˆç¨‹åºå®žé™… HTTP ç”Ÿæˆï¼Œéžæ‰‹å·¥æ”¹è¡¨ï¼‰ï¼š
  - `docs/生成汇总大表/输出/道路运输量汇总表_2026-07.xlsx`(921,070 B)
  - `docs/生成汇总大表/输出/道路运输量汇总表_2026-06.xlsx`(912,980 B)
## äºŒã€æµ‹è¯•环境
- åŽç«¯ Spring Boot 8090(`mvn spring-boot:run` å®žè·‘,本机 MySQL 127.0.0.1:3308/traffic_audit);
- æ¯ç‰ˆï¼š`docs/生成汇总大表/2026å¹´7月道路运输量汇总表.xlsx`(1-4 æœˆéª¨æž¶ï¼Œå·¥ä½œåŒºæœªæäº¤ç‰ˆï¼‰ï¼›
- å¤‡ä»½æ¯ç‰ˆï¼ˆé€æ ¼å¯¹ç…§åŸºå‡†ï¼‰ï¼š`docs/生成汇总大表/_备份_2026å¹´7月道路运输量汇总表_清理前_20260904.xlsx`;
- æ ¡éªŒè„šæœ¬ï¼šé€æ ¼æ¯”è¾ƒå•å…ƒæ ¼åŽŸæ–‡ï¼ˆå…¬å¼å« `=`、值、文本),数值按 int/float ç­‰ä»·å½’一化。
## ä¸‰ã€ç”¨ä¾‹ä¸Žç»“æžœ
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | 1-7 æœˆæ‰©åˆ— | GET `/api/report/export/summaryWorkbook?period=2026-07&mode=month` | ä¸Žå¤‡ä»½æ¯ç‰ˆå…¨ç°¿é€æ ¼ä¸€è‡´ | çœŸå·®å¼‚=0 | é€šè¿‡ |
| 2 | 1-6 æœˆç”Ÿæˆï¼ˆæ­£å‘) | åŒä¸Šè¿½åŠ  `&toMonth=6` | éžè£å‰ªèŒƒå›´ï¼ˆéž 8 å°è´¦é¡µ + 8 å°è´¦é¡µ A..N ç­‰ï¼‰ä¸Žå¤‡ä»½ä¸€è‡´ï¼›ä»… O/P/Q/R æŒ‰è§„则变化 | éžç™½åå•真差异=0 | é€šè¿‡ |
| 3 | 7 æœˆä¸¤åˆ—清空/表头保留 | 8 å°è´¦é¡µæ•°æ®è¡Œ O/P | æ•°æ®æ ¼ç©ºã€è¡¨å¤´è¡Œ O/P ä¿ç•™ | æ•°æ®æ ¼æ¸…空 1132,表头保留 24,表头异常 0 | é€šè¿‡ |
| 4 | ç´¯è®¡æŒ‰ 1-6 æœˆ | æ•°æ®è¡Œ Q åˆ—公式 | åŽ»æŽ‰ `+O{行}` | æ”¹å†™ 566 å¤„,例:`=C5+E5+...+O5`→`=C5+...+M5` | é€šè¿‡ |
| 5 | ç´¯è®¡åŒæ¯”分母=2025 1-6 æœˆ | æ•°æ®è¡Œ R åˆ—公式 | åŽ»æŽ‰ `,AF{行}`/`+AF{行}` | æ”¹å†™ 566 å¤„,例:`=Q5/SUM(T5..AF5)-1`→`=Q5/SUM(T5..AD5)-1` | é€šè¿‡ |
| 6 | æŠ½æŸ¥å…³é”®æ ¼ | è´§è¿/中口径明细 O/P/Q/R | è¡¨å¤´è§æ–‡ã€æ•°æ®è¡Œ O/P ç©º | 07 æ•°æ®è¡Œ O=3754.7823(7 æœˆå€¼ï¼‰ï¼Œ06 åŒæ ¼ä¸ºç©ºï¼›è¡¨å¤´ä¸€è‡´ | é€šè¿‡ |
| 7 | è´Ÿå‘/边界 | `toMonth` ç¼ºçœæˆ– â‰¥7 | è¡Œä¸ºä¸Žæ—§ç‰ˆä¸€è‡´ï¼ˆå…¨ç°¿è¿˜åŽŸï¼‰ | åŒç”¨ä¾‹ 1,真差异 0 | é€šè¿‡ |
## å››ã€å®žçŽ°ä¸­å‘çŽ°å¹¶å¤„ç½®çš„é—®é¢˜
1. **POI è¡Œå· 0-based vs å…¬å¼ 1-based**:初版按行索引拼接 `+O4`,匹配不上公式里的 `O5`,导致 Q/R æœªæ”¹å†™ï¼ˆä»…清空生效)。处置:token è¡Œå·æ”¹ `excelRow = r + 1`。
2. **2025 åŒæœˆåˆ— 0-based ç´¢å¼•é”™ 1**:分母列 T..AF çš„ 0-based èµ·ç‚¹æ˜¯ 19(T),误用 20 ä¼šç®—出 AG(列号越 1 åˆ—),R å…¬å¼åŒ¹é…ä¸åˆ°ã€‚处置:`lastCol = 19 + 2*(m-1)`。
3. ä¸¤å¤„修复后均重新 `mvn compile` + é‡å¯åŽç«¯å¤æµ‹é€šè¿‡ï¼ˆæœ€ç»ˆäº§ç‰©ä¸ºä¿®å¤åŽä»£ç ç”Ÿæˆï¼‰ã€‚
## äº”、边界与已知项(本次未纳入)
- **成品聚合页**(货运量排名、货运周转量排名、中口径排名、中口径分析、城市汇总、城市分市州、道路运输周转量)的单元格为**人工母版静态值(1-7 æœˆç´¯è®¡å£å¾„)**,本次裁剪不触及;要随“1-6 æœˆâ€è‡ªåŠ¨é‡ç®—éœ€èµ° P2-P5 æ•°æ®é©±åŠ¨å¡«å……ï¼ˆå¦æŽ’æœŸï¼‰ã€‚
- 2025/2024 å‚考列保留母版缓存值,未改(已确认口径)。
- éšè—é¡µï¼ˆå…¬è·¯æ€»å®¢è¿ã€2025年城市客运量、中口径客运量、中口径计算过程)未纳入裁剪,保持原样。
## å…­ã€ç»“论
“先生成 1-6 æœˆã€1-7 æœˆæ‰©åˆ—”两本产物经程序实际生成并通过逐格对照:1-7 æœˆäº§ç‰©ä¸Žå¤‡ä»½æ¯ç‰ˆé›¶å·®å¼‚ï¼›1-6 æœˆäº§ç‰©åœ¨ 8 ä¸ªå°è´¦/合成页正确执行“7 æœˆä¸¤åˆ—留空(表头保留)、累计/同比按 1-6 æœˆâ€çš„口径,其余范围零差异。P0 é¡¹å¯å…³é—­ï¼Œå‰©ä½™æˆå“èšåˆé¡µæ•°æ®é©±åŠ¨é‡ç®—ï¼ˆP2-P5)单列跟进。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-04.md
@@ -13,7 +13,7 @@
3. æ–‡æ¡£ï¼šæ•°æ®å¯¼å…¥çŽ°çŠ¶ä¸Žç¼ºå£æ¸…å•ã€æ¯ç‰ˆä½“æ£€æ¸…å•è¿½åŠ æ‹æ¿è®°å½•ã€åŠŸèƒ½æµ‹è¯•æŠ¥å‘ŠÃ—3。
## é—留问题与待办(明早优先)
- P0 æ±‡æ€»å¤§è¡¨â€œ1-6 æœˆç”Ÿæˆ + 1-7 æœˆæ‰©åˆ—”程序改造与对照验证(用户已确认 â‘¡ ç´¯è®¡/同比按 1-6 æœˆã€åˆ†æ¯ 2025 1-6 æœˆï¼›â‘  7 æœˆä¸¤åˆ—保留表头、数据留空;如效果不佳再改整列去掉)。
- ~~P0 æ±‡æ€»å¤§è¡¨â€œ1-6 æœˆç”Ÿæˆ + 1-7 æœˆæ‰©åˆ—”程序改造与对照验证~~(已完成,见文末“晚间追加”,2026-09-04 å¤œï¼‰ã€‚
- P1 åŸŽå¸‚客运公交/出租“同事版模板”列位兼容(等同事文件或表头)。
- P1 æ¯ç‰ˆä¿®æ­£ï¼ˆæ–¹æ¡ˆ A:当月块错用累计值、右侧 #REF! æ¸…理)后再作为母版;docs/生成汇总大表 ä¸‹æœªæäº¤çš„ xlsx æ”¹åŠ¨å¾…ä¸€å¹¶å¤„ç†ã€‚
- P2 ç½‘约车拆分/汇总工作簿生成接入前端选项(如尚未完成);生成产物命名与输出目录确认。
@@ -22,3 +22,12 @@
## å¤‡æ³¨
- å®¡æ ¸ç»“果已按新口径重算(2026-07 H2032);如之后继续重导 07 æ‹†åˆ†/全口径,需再点一次“审核”刷新。
- ä»Šæ—¥æœªå®Œæˆé¡¹ä¸åˆ ç”¨æˆ·ä¸´æ—¶æ–‡ä»¶ï¼›ä¸´æ—¶è„šæœ¬ç•™åœ¨ä»“åº“æ ¹ç›®å½•ï¼ˆ_*.py/_*.sql ç­‰ï¼‰æœªæ¸…理。
## æ™šé—´è¿½åŠ ï¼ˆ2026-09-04 20:3x,加班完成 P0)
- P0「1-6 æœˆç”Ÿæˆ + 1-7 æœˆæ‰©åˆ—」程序改造与对照验证已完成:
  - ä»£ç ï¼š`ReportExportService.exportSummaryWorkbook` å¢žåŠ å¯é€‰ `toMonth` è£å‰ª + `trimSummaryMonthlySheets`(8 å°è´¦/合成页:7 æœˆå½“月值/同比数据格清空、表头保留,Q ç´¯è®¡åŽ» 7 æœˆé¡¹ã€R ç´¯è®¡åŒæ¯”分母去 2025 å¹´ 7 æœˆåˆ—);控制器 `/export/summaryWorkbook` å¢žåŠ  `toMonth` å‚数。
  - äº§ç‰©ï¼ˆç¨‹åº HTTP å®žè·‘生成,已落盘):`docs/生成汇总大表/输出/道路运输量汇总表_2026-07.xlsx`、`_2026-06.xlsx`。
  - éªŒè¯ï¼š07 ä¸Žå¤‡ä»½æ¯ç‰ˆå…¨ç°¿é€æ ¼ 0 å·®å¼‚ï¼›06 éžç™½åå• 0 å·®å¼‚、O/P æ•°æ®æ¸…空 1132 æ ¼ã€Q/R å…¬å¼å„改写 566 å¤„、表头保留 24 æ ¼å…¨å¯¹ã€‚报告:`docs/功能测试报告/功能测试报告_汇总大表1-6月生成与1-7月扩列验证_2026-09-04.md`。
  - é—留(新):成品聚合页(排名/汇总/分析/总周转量)为母版静态 1-7 å£å¾„,需 P2-P5 æ•°æ®é©±åŠ¨å¡«å……åŽå†çº³å…¥ 1-6 éªŒæ”¶ã€‚
- æ˜Žæ—¥å¾…办更新:P0 å·²ä»Žæ¸…单移除;P1(同事版公交/出租模板列位兼容、母版方案 A ä¿®æ­£åŽæäº¤éª¨æž¶æ¯ç‰ˆä¸Žä¸¤æœ¬ xlsx å˜æ›´ï¼‰ä¿æŒï¼›P2(成品聚合页重算、汇总工作簿前端接入)跟进。
docs/Éú³É»ã×Ü´ó±í/Êä³ö/µÀ·ÔËÊäÁ¿»ã×ܱí_2026-06.xlsx
Binary files differ
docs/Éú³É»ã×Ü´ó±í/Êä³ö/µÀ·ÔËÊäÁ¿»ã×ܱí_2026-07.xlsx
Binary files differ
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
@@ -87,9 +87,10 @@
    /** ç”Ÿæˆ_道路运输量汇总表.xlsx(汇总工作簿 P0-P1 éª¨æž¶ï¼šæ•´æœ¬å¤åˆ¶æ¯ç‰ˆï¼Œé¡µç­¾/公式/2025 ç¼“存保留) */
    @GetMapping("/export/summaryWorkbook")
    public ResponseEntity<byte[]> exportSummaryWorkbook(@RequestParam("period") String period,
                                                        @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
                                                        @RequestParam(value = "mode", defaultValue = "month") String mode,
                                                        @RequestParam(value = "toMonth", required = false) Integer toMonth) throws Exception {
        logExport("道路运输量汇总表.xlsx", period);
        return buildResponse(reportService.exportSummaryWorkbook(period, mode), String.format("生成_道路运输量汇总表_%s.xlsx", period));
        return buildResponse(reportService.exportSummaryWorkbook(period, mode, toMonth), String.format("生成_道路运输量汇总表_%s.xlsx", period));
    }
    /** ç”Ÿæˆ_网约车客运量拆分表.xlsx(月度:订单附件1 + å…¨çœ pin + å·¡æ¸¸å‡ºç§ŸæœˆæŠ¥ï¼‰ */
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -3199,6 +3199,14 @@
     * ä¿ç•™å…¨éƒ¨é¡µç­¾/版式/公式/2025 å¹´ç¼“存值,仅把表头年份动态化为目标年;数据填充按 P2 èµ·é€æ­¥æŽ¥å…¥ã€‚
     */
    public byte[] exportSummaryWorkbook(String period, String mode) throws Exception {
        return exportSummaryWorkbook(period, mode, null);
    }
    /** ç”Ÿæˆ_道路运输量汇总表.xlsx(P0-P1 éª¨æž¶ï¼Œæ”¯æŒ toMonth è£å‰ªï¼‰ï¼š
     *  ä»¥ã€ŠYYYYå¹´M月道路运输量汇总表.xlsx》(M ç”± period å†³å®šï¼‰ä¸ºæ¯ç‰ˆï¼ŒæŒ‰åŒæœŸå¤‡ä»½æ¯ç‰ˆè¿˜åŽŸæœˆåº¦å—ï¼›
     *  toMonth éžç©ºä¸”小于 M æ—¶ï¼ŒæŠŠ 8 ä¸ªå°è´¦/合成页裁到 toMonth æœˆï¼ˆtoMonth+1..M çš„当月值/同比列清空、保留表头,
     *  ç´¯è®¡ä¸Žç´¯è®¡åŒæ¯”公式改为按 toMonth æœˆé‡ç®—),用于“先生成 1-6 æœˆã€1-7 æœˆæ‰©åˆ—”验证。 */
    public byte[] exportSummaryWorkbook(String period, String mode, Integer toMonth) throws Exception {
        int year = Integer.parseInt(period.contains("-") ? period.split("-")[0] : period);
        int month = period.contains("-") ? monthOf(period, mode) : 12;
        File template = resolveSummaryMother(period, mode);
@@ -3215,6 +3223,10 @@
                }
            } else {
                log.warn("汇总工作簿未找到同期备份母版(_备份_{}å¹´{}月…),输出将保持母版现状(可能为 1-4 æœˆéª¨æž¶ï¼‰", year, month);
            }
            if (toMonth != null && toMonth > 0 && toMonth < month) {
                int trimmed = trimSummaryMonthlySheets(wb, toMonth);
                log.info("汇总工作簿裁剪至 {} æœˆå®Œæˆï¼Œæ¸…空/改写单元格数={}", toMonth, trimmed);
            }
            dynamicSummaryYear(wb, year);
            applyTwoDecimalFormat(wb); // æ±‡æ€»å¤§è¡¨ä¿ç•™äººå·¥æ¯ç‰ˆåˆ—宽/版式:不做 autoFit(长公式会把列宽顶爆)
@@ -3566,6 +3578,73 @@
                return "";
        }
    }
    /** æŠŠ 8 ä¸ªå°è´¦/合成页裁剪到 keepMonths æœˆï¼š
     *  æœˆåº¦å€¼åˆ— 2026 å¹´ m æœˆ = ç¬¬ 3+2*(m-1) åˆ—(C/E/G/I/K/M/O),同比列为其后一列;
     *  ç´¯è®¡ Q åˆ— = C+E+...+O,累计同比 R åˆ—分母 = 2025 å¹´åŒæœˆåˆ— T/V/X/Z/AB/AD/AF。
     *  è£å‰ª = æ•°æ®è¡Œï¼ˆå«ç´¯è®¡å…¬å¼ä¸”任一 2026 æœˆåº¦åˆ—有内容)中 keepMonths+1..7 æœˆçš„当月值/同比单元格清空(表头保留),
     *  å¹¶æŠŠè¯¥è¡Œç´¯è®¡å…¬å¼åŽ»æŽ‰å¯¹åº” +列,累计同比分母去掉对应 2025 åˆ—。 */
    private int trimSummaryMonthlySheets(XSSFWorkbook wb, int keepMonths) {
        String[] scope = {" è´§è¿", "班线包车", "城市客运", "公交", "出租", "网约车", "轨道、轮渡", "中口径明细"};
        java.util.Set<String> scopeSet = new java.util.HashSet<>();
        for (String s : scope) scopeSet.add(s);
        int touched = 0;
        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
            Sheet sh = wb.getSheetAt(i);
            if (sh == null || !scopeSet.contains(sh.getSheetName())) continue;
            for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
                Row row = sh.getRow(r);
                if (row == null) continue;
                Cell q = row.getCell(16); // Q åˆ— = ç´¯è®¡
                if (q == null || q.getCellType() != CellType.FORMULA) continue;
                boolean hasMonthly = false;
                for (int m = 1; m <= 7 && !hasMonthly; m++) {
                    Cell v = row.getCell(2 + 2 * (m - 1)); // C,E,G,I,K,M,O
                    if (v != null && v.getCellType() != CellType.BLANK) hasMonthly = true;
                }
                if (!hasMonthly) continue; // æŽ’除页首/页中跨页签校验公式行(无月度值)
                int excelRow = r + 1; // å…¬å¼å†…引用为 Excel 1-based è¡Œå·
                for (int m = keepMonths + 1; m <= 7; m++) {
                    int valueCol = 2 + 2 * (m - 1);     // 2026 å½“月值列
                    int yoyCol = valueCol + 1;          // å½“月同比列
                    String vLetter = colLetter(valueCol);
                    Cell vc = row.getCell(valueCol);
                    if (vc != null) {
                        vc.setBlank();
                        touched++;
                    }
                    Cell yc = row.getCell(yoyCol);
                    if (yc != null) {
                        yc.setBlank();
                        touched++;
                    }
                    String fq = q.getCellFormula();
                    if (fq != null && fq.contains("+" + vLetter + excelRow)) {
                        q.setCellFormula(fq.replace("+" + vLetter + excelRow, ""));
                        touched++;
                    }
                    Cell rc = row.getCell(17); // R åˆ— = ç´¯è®¡åŒæ¯”
                    if (rc != null && rc.getCellType() == CellType.FORMULA) {
                        String fr = rc.getCellFormula();
                        int lastCol = 19 + 2 * (m - 1); // 2025 åŒæœˆåˆ— T..AF(0-based:T=19,AF=31)
                        String lLetter = colLetter(lastCol);
                        String txt = fr;
                        if (txt != null && txt.contains("," + lLetter + excelRow)) {
                            txt = txt.replace("," + lLetter + excelRow, "");
                            touched++;
                        }
                        if (txt != null && txt.contains("+" + lLetter + excelRow)) {
                            txt = txt.replace("+" + lLetter + excelRow, "");
                            touched++;
                        }
                        if (txt != null && !txt.equals(fr)) rc.setCellFormula(txt);
                    }
                }
            }
        }
        return touched;
    }
    /** æ±‡æ€»å·¥ä½œç°¿å¹´ä»½åŠ¨æ€åŒ–ï¼šä»…æŠŠ 2026å¹´ å‰ç¼€æ›¿æ¢ä¸ºç›®æ ‡å¹´ï¼ˆ2025/2024 å‚考列不动) */
    private void dynamicSummaryYear(XSSFWorkbook wb, int year) {
        String oldPrefix = "2026å¹´";