zhizhijie
2026-09-19 60b8347dcc5d8e2c47133fe19739374125340103
fix(导入): 投资市州单表表头取列定位修复 + 补齐2026-08投资源数据

根因(两类):
1) 表头文本扫描窗口固定「表头行+3」,把首行数据纳入列名匹配:
宜昌客运首行数据的「竣工收尾/建成投运」先命中「竣工/建成」分支,
导致 L=建设阶段、M=形象进度 被判成竣工时间列(两列全空);
咸宁客运的 L/M 标题写在表头下第 4 行,超出 +3 窗口取不到。
2) 「合计」判定用 nt.contains("合计"),不识别「合   计」这类内部空格,
咸宁客运的合计行被当成项目入库。

修复:
- 表头扫描上界改为「表头块」:上界 = 首个真实数据行 − 1(硬上限 +12 行),
数据行不再参与列名关键字匹配;
- 新增 findFirstInvestDataRow(),与解析循环的跳过规则(空行/表头续行/合计小计/
章节行/填报行/市州节行)保持一致;
- 形象进度列新增「建设进展」关键词,并新增 desc = stage + 1 兜底
(省标「项目建设阶段、进度情况」为 L/M 合并表头,孝感/恩施/随州等只写 L 标题);
- 合计/总计/小计判定统一改为去空白后匹配(解析循环、行数统计、首数据行探测三处同步)。

数据:补齐 docs/投资/输入/{客运(客运站),物流(物流园区)}/8月(9 + 16 个文件)并重导,
客运 39 行、物流 128 行;孝感/恩施市州小计与源文件逐项一致。

详见 docs/功能测试报告/功能测试报告_投资明细表与导入取列定位修复_2026-09-19.md
1个文件已修改
25个文件已添加
59 ■■■■■ 已修改文件
docs/投资/输入/客运(客运站)/8月/2026年8月咸宁市客运站场投资统计月报表(1)省表.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/2026年8月林区道路客运站场投资统计报表(月、年报).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/2026年8月站场固投(南山、汉口站).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/2026湖北省道路客运站场投资统计报8月(恩施州) (1).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/十堰市2026年8月固定资产月报电子版投资表格.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/湖北省宜昌市道路客运站场投资报表(月、年报)08月.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/荆门市客运站场投资统计8月.xls(1).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/襄阳市道路运输服务中心2026年8月投资.et 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/客运(客运站)/8月/(孝感市道运中心)202608孝感市道路客运站场投资统计月报表.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/202608---黄冈物流站场投资表(最终版).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月份孝感物流项目投资统计表.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月份物流(货运)投资项目明细表(黄石市)(1).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月企业汇总(鄂州).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月恩施州物流(货运)项目投资明细.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月湖北省襄阳市交通物流基础设施投资统计报表.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/2026年8月物流基础设施建设投资月报(随州).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/8月份物流园中心建设项目(天门).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/仙桃市物流园投资统计报表(202608).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/咸宁市2026年8月份物流(货运)项目投资明细 (1).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/湖北宜昌市物流基础设施投资统计报表(月、年报)08月.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/湖北省十堰交通物流基础设施投资统计汇总表(月报)(2026年8月).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/潜江2026.08.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/神农架林区物流投资月报2026.08.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/荆州物流投资(2026年8月报省) (1).xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/投资/输入/物流(物流园区)/8月/荆门2026年08月项目投资总报表(物流).xls 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/2026Äê8ÔÂÏÌÄþÊпÍÔËÕ¾³¡Í¶×Êͳ¼ÆÔ±¨±í(1)Ê¡±í.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/2026Äê8ÔÂÁÖÇøµÀ·¿ÍÔËÕ¾³¡Í¶×Êͳ¼Æ±¨±í£¨Ô¡¢Ä걨£©.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/2026Äê8ÔÂÕ¾³¡¹ÌͶ£¨ÄÏɽ¡¢ºº¿ÚÕ¾£©.xlsx
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/2026ºþ±±Ê¡µÀ·¿ÍÔËÕ¾³¡Í¶×Êͳ¼Æ±¨8ÔÂ(¶÷Ê©ÖÝ) (1).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/Ê®ÑßÊÐ2026Äê8Ô¹̶¨×ʲúÔ±¨µç×Ó°æÍ¶×ʱí¸ñ.xlsx
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/ºþ±±Ê¡Ò˲ýÊеÀ·¿ÍÔËÕ¾³¡Í¶×ʱ¨±í£¨Ô¡¢Ä걨£©08ÔÂ.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/¾£ÃÅÊпÍÔËÕ¾³¡Í¶×Êͳ¼Æ8ÔÂ.xls(1).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/ÏåÑôÊеÀ·ÔËÊä·þÎñÖÐÐÄ2026Äê8ÔÂͶ×Ê.et
Binary files differ
docs/Ͷ×Ê/ÊäÈë/¿ÍÔË£¨¿ÍÔËÕ¾£©/8ÔÂ/£¨Ð¢¸ÐÊеÀÔËÖÐÐÄ£©202608Т¸ÐÊеÀ·¿ÍÔËÕ¾³¡Í¶×Êͳ¼ÆÔ±¨±í.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/202608---»Æ¸ÔÎïÁ÷Õ¾³¡Í¶×Ê±í£¨×îÖհ棩.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8Ô·ÝТ¸ÐÎïÁ÷ÏîĿͶ×Êͳ¼Æ±í.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8Ô·ÝÎïÁ÷£¨»õÔË£©Í¶×ÊÏîÄ¿Ã÷ϸ±í£¨»ÆÊ¯ÊУ©(1).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8ÔÂÆóÒµ»ã×Ü£¨¶õÖÝ£©.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8Ô¶÷Ê©ÖÝÎïÁ÷£¨»õÔË£©ÏîĿͶ×ÊÃ÷ϸ.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8Ôºþ±±Ê¡ÏåÑôÊн»Í¨ÎïÁ÷»ù´¡ÉèʩͶ×Êͳ¼Æ±¨±í.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/2026Äê8ÔÂÎïÁ÷»ù´¡ÉèÊ©½¨ÉèͶ×ÊÔ±¨(ËæÖÝ).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/8Ô·ÝÎïÁ÷Ô°ÖÐÐĽ¨ÉèÏîÄ¿(ÌìÃÅ).xlsx
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/ÏÉÌÒÊÐÎïÁ÷԰Ͷ×Êͳ¼Æ±¨±í£¨202608£©.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/ÏÌÄþÊÐ2026Äê8Ô·ÝÎïÁ÷£¨»õÔË£©ÏîĿͶ×ÊÃ÷ϸ (1).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/ºþ±±Ò˲ýÊÐÎïÁ÷»ù´¡ÉèʩͶ×Êͳ¼Æ±¨±í£¨Ô¡¢Ä걨£©08ÔÂ.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/ºþ±±Ê¡Ê®Ñß½»Í¨ÎïÁ÷»ù´¡ÉèʩͶ×Êͳ¼Æ»ã×Ü±í£¨Ô±¨£©£¨2026Äê8Ô£©.xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/DZ½­2026.08.xlsx
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/ÉñÅ©¼ÜÁÖÇøÎïÁ÷Ͷ×ÊÔ±¨2026.08.xlsx
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/¾£ÖÝÎïÁ÷Ͷ×Ê£¨2026Äê8Ô±¨Ê¡£© (1).xls
Binary files differ
docs/Ͷ×Ê/ÊäÈë/ÎïÁ÷£¨ÎïÁ÷Ô°Çø£©/8ÔÂ/¾£ÃÅ2026Äê08ÔÂÏîĿͶ×Ê×ܱ¨±í£¨ÎïÁ÷£©.xls
Binary files differ
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -2465,10 +2465,10 @@
            }
            if (name == null || name.trim().isEmpty()) continue;
            String nt = name.trim().replaceAll("[\r\n]+", "");
            if (nt.contains("合计") || nt.contains("总计")) continue;
            String ntFlat = nt.replaceAll("\\s+", "");
            if (ntFlat.contains("合计") || ntFlat.contains("总计") || ntFlat.contains("小计")) continue;
            if (nt.matches("^[一二三四五六七八九十]+、.*")) continue;
            if (nt.matches("^([一二三四五六七八九十]+).*")) continue;
            if (nt.replace(" ", "").matches(".*小\\s*计.*")) continue;
            if (nt.startsWith("填报") || nt.startsWith("单位负责人") || nt.matches("^\\d+、.*")) continue;
            if (hasSeq) {
                if (seq == null || seq.trim().isEmpty()) {
@@ -2530,7 +2530,8 @@
            }
            if (!hasName) continue;
            Map<Integer, String> colTexts = new HashMap<>();
            for (int rr = r; rr <= Math.min(r + 3, sh.getLastRowNum()); rr++) {
            int scanEnd = investHeaderScanEnd(sh, r, row);
            for (int rr = r; rr <= scanEnd; rr++) {
                Row rr2 = sh.getRow(rr);
                if (rr2 == null) continue;
                for (int c = 0; c <= rr2.getLastCellNum(); c++) {
@@ -2554,7 +2555,7 @@
                else if (v.contains("自年初累计")) col.putIfAbsent("yearcum", c);
                else if (v.contains("本月完成")) col.putIfAbsent("monthdone", c);
                else if (v.contains("建设阶段")) col.putIfAbsent("stage", c);
                else if (v.contains("形象进度")) col.putIfAbsent("desc", c);
                else if (v.contains("形象进度") || v.contains("建设进展")) col.putIfAbsent("desc", c);
                else if (v.contains("工可") && !v.contains("项目名称")) col.putIfAbsent("gk", c);
                else if (v.contains("初设") && !v.contains("项目名称")) col.putIfAbsent("cs", c);
                else if (v.contains("建筑面积") && !v.contains("新增")) col.putIfAbsent("area", c);
@@ -2574,6 +2575,12 @@
                int md = col.get("yearcum") + 1;
                if (md <= row.getLastCellNum()) col.put("monthdone", md);
            }
            // desc ç¼ºå¤±æ—¶æŒ‰ stage+1 å…œåº•:省标「项目建设阶段、进度情况」是 L/M åˆå¹¶è¡¨å¤´ï¼Œ
            // å­æ„Ÿ/恩施/林区/随州等市州只在 L å†™ã€Œå»ºè®¾é˜¶æ®µã€ã€M ç›´æŽ¥å†™è¿›åº¦æ–‡å­—而不写标题
            if (!col.containsKey("desc") && col.containsKey("stage") && col.get("stage") >= 0) {
                int dc = col.get("stage") + 1;
                if (dc <= row.getLastCellNum()) col.put("desc", dc);
            }
            for (String k : new String[]{"builder", "nature", "start", "end", "total", "startcum",
                "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area"}) {
                col.putIfAbsent(k, -1);
@@ -2582,6 +2589,46 @@
            return col;
        }
        return null;
    }
    /** è¡¨å¤´æ–‡æœ¬æ‰«æä¸‹ç•Œï¼šé»˜è®¤è¡¨å¤´è¡Œ +3;若「项目名称」列首个数据行更靠下,则扩展到数据行前一行(上限 +12)。
     *  èƒŒæ™¯ï¼šå’¸å®/十堰等市州把「建设阶段/形象进度」标签写在表头下第 4~8 è¡Œï¼Œå›ºå®š +3 ä¼šæ¼åˆ—(形象进度读不到)。 */
    private int investHeaderScanEnd(Sheet sh, int headerRow, Row headerRowObj) {
        int hardEnd = Math.min(headerRow + 12, sh.getLastRowNum());
        int nameCol = -1;
        if (headerRowObj != null) {
            for (int c = 0; c <= headerRowObj.getLastCellNum(); c++) {
                String v = cellStr(headerRowObj, c);
                if (v != null && v.contains("项目名称")) { nameCol = c; break; }
            }
        }
        int firstData = findFirstInvestDataRow(sh, headerRow, nameCol);
        int scanEnd = (firstData > headerRow)
            ? Math.min(hardEnd, firstData - 1)
            : Math.min(headerRow + 3, sh.getLastRowNum());
        if (scanEnd < headerRow) scanEnd = headerRow;
        return scanEnd;
    }
    /** ã€Œé¡¹ç›®åç§°ã€åˆ—首个数据行下标(跳过表头续行与空行);找不到返回 -1 */
    private int findFirstInvestDataRow(Sheet sh, int headerRow, int nameCol) {
        if (nameCol < 0) return -1;
        for (int r = headerRow + 1; r <= sh.getLastRowNum(); r++) {
            Row row = sh.getRow(r);
            if (row == null) continue;
            String name = cellStr(row, nameCol);
            if (name == null || name.trim().isEmpty()) continue;
            String nt = name.trim().replaceAll("[\r\n]+", "");
            if (nt.contains("项目名称")) continue;
            String flat = nt.replaceAll("\\s+", "");
            if (flat.contains("合计") || flat.contains("总计") || flat.contains("小计")) continue;
            if (nt.matches("^[一二三四五六七八九十]+、.*")) continue;
            if (nt.matches("^([一二三四五六七八九十]+).*")) continue;
            if (nt.startsWith("填报") || nt.startsWith("单位负责人") || nt.matches("^\\d+、.*")) continue;
            if (investmentCity(nt) != null) continue;
            return r;
        }
        return -1;
    }
    /** ç»Ÿè®¡ sheet å†…项目数据行数(用于多 sheet åŽ†å²æ–‡ä»¶é€‰å½“æœˆ sheet) */
@@ -2596,9 +2643,9 @@
            String name = cellStr(row, nameCol);
            if (name == null || name.trim().isEmpty()) continue;
            String nt = name.trim().replaceAll("[\r\n]+", "");
            if (nt.contains("合计") || nt.contains("总计")) continue;
            String ntFlat = nt.replaceAll("\\s+", "");
            if (ntFlat.contains("合计") || ntFlat.contains("总计") || ntFlat.contains("小计")) continue;
            if (nt.matches("^[一二三四五六七八九十]+、.*")) continue;
            if (nt.replace(" ", "").matches(".*小\\s*计.*")) continue;
            if (nt.startsWith("填报") || nt.startsWith("单位负责人") || nt.matches("^\\d+、.*")) continue;
            if (!hasSeq) {
                if (nt.length() > 4) count++;