zhizhijie
2026-09-20 b80bb7253a87931fea5a1a788fc4c6ffb204d245
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -226,6 +226,36 @@
                    }
                    break;
                }
                case "investPassengerDetail":
                case "investLogisticsDetail": {
                    boolean paxDetail = "investPassengerDetail".equals(t);
                    String catName = paxDetail ? "客运站场" : "物流园区";
                    String catLabel = paxDetail ? "客运站场投资月报" : "物流园区投资月报";
                    List<InvestmentProject> catProjects = investProjectMapper.selectList(
                        new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, catName));
                    final List<Long> catIds = new ArrayList<>();
                    for (InvestmentProject cp : catProjects) catIds.add(cp.getId());
                    if (catIds.isEmpty()) {
                        rows = 0;
                        notes.add("缺数据:" + catLabel);
                    } else {
                        rows = addReadyCount(() -> investMonthlyMapper.selectCount(
                            periodQw(period, mode, InvestmentMonthly::getReportPeriod)
                                .in(InvestmentMonthly::getProjectId, catIds)), catLabel, notes);
                    }
                    String detTpl = paxDetail ? "模板_X月客运站投资明细表.xlsx" : "模板_X月物流站场投资明细.xls";
                    try {
                        if (resolveAnyTemplate(investTemplateDir, detTpl) == null) {
                            rows = 0;
                            notes.add("docs/投资/模板 下缺少《" + detTpl + "》");
                        }
                    } catch (Exception ex) {
                        rows = 0;
                        notes.add("母版定位失败:" + ex.getMessage());
                    }
                    auditTypes.add("INVEST");
                    break;
                }
                default:
                    rows = 0;
                    notes.add("未知报表类型:" + t);
@@ -381,7 +411,9 @@
                coverage.addAll(investCover);
                // 投资为当月报表(无 1..N 累计口径),只提示当月缺失
                if (!investCover.contains(month)) missing.add(month);
                investPreview(period, mode, summary, problems);
                if ("investPassengerDetail".equals(t)) investPreview(period, mode, "客运站场", summary, problems);
                else if ("investLogisticsDetail".equals(t)) investPreview(period, mode, "物流园区", summary, problems);
                else investPreview(period, mode, summary, problems);
            } else if ("energySummary".equals(t)) {
                Long ec = energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod));
                if (ec != null && ec > 0) coverage.add(month);
@@ -412,7 +444,8 @@
    }
    private boolean isInvestType(String t) {
        return "investPlan".equals(t) || "investFiveYearLogistics".equals(t) || "investBillion".equals(t) || "investCounty".equals(t);
        return "investPlan".equals(t) || "investFiveYearLogistics".equals(t) || "investBillion".equals(t) || "investCounty".equals(t)
            || "investPassengerDetail".equals(t) || "investLogisticsDetail".equals(t);
    }
    private boolean isEnergyType(String t) {
@@ -668,9 +701,23 @@
        if (allZero) problems.add("全省累计值全为 0(公交/出租月报可能未导入)");
    }
    /** 投资摘要:项目数 + 本月完成/自年初/自开始累计 */
    /** 投资摘要:项目数 + 本月完成/自年初/自开始累计(全投资月报合计,不分类别) */
    private void investPreview(String period, String mode, List<Map<String, Object>> summary, List<String> problems) {
        List<InvestmentMonthly> list = investMonthlyMapper.selectList(periodQw(period, mode, InvestmentMonthly::getReportPeriod));
        investPreview(period, mode, null, summary, problems);
    }
    /** 投资摘要(category 非空时只统计该类别,如客运站场/物流园区投资明细表) */
    private void investPreview(String period, String mode, String category, List<Map<String, Object>> summary, List<String> problems) {
        LambdaQueryWrapper<InvestmentMonthly> qw = periodQw(period, mode, InvestmentMonthly::getReportPeriod);
        if (category != null) {
            List<InvestmentProject> ps = investProjectMapper.selectList(
                new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category));
            List<Long> ids = new ArrayList<>();
            for (InvestmentProject p : ps) ids.add(p.getId());
            if (ids.isEmpty()) { problems.add("缺数据:库内没有「" + category + "」项目主档"); return; }
            qw.in(InvestmentMonthly::getProjectId, ids);
        }
        List<InvestmentMonthly> list = investMonthlyMapper.selectList(qw);
        Set<Long> proj = new HashSet<>();
        double monthDone = 0.0, yearCum = 0.0, startCum = 0.0;
        for (InvestmentMonthly m : list) {
@@ -840,36 +887,36 @@
        for (Double v : h2032FreightCum.values()) {
            if (v != null) provAbove += v;
        }
        Double provAboveWan = round(provAbove / 10000.0, 4);
        Double provAboveWan = provAbove / 10000.0;
        Double provTotal = freightCum(ftMap.get("湖北省"), month);
        Double provBelow = provTotal == null ? null : round(provTotal - provAboveWan, 4);
        Double provBelow = provTotal == null ? null : provTotal - provAboveWan;
        Double provTotalYoy = freightCumYoy(ftMap.get("湖北省"), month);
        double provAboveLast = 0.0;
        for (Double v : h2032FreightCumLast.values()) {
            if (v != null) provAboveLast += v;
        }
        Double provAboveYoy = provAboveLast == 0.0 ? null : round((provAbove - provAboveLast) / provAboveLast, 4);
        Double provAboveYoy = provAboveLast == 0.0 ? null : (provAbove - provAboveLast) / provAboveLast;
        Double provTotalLast = lastFreightCum(ftMap.get("湖北省"), month);
        Double provBelowLast = provTotalLast == null ? null : round(provTotalLast - provAboveLast / 10000.0, 4);
        Double provBelowLast = provTotalLast == null ? null : provTotalLast - provAboveLast / 10000.0;
        Double provBelowYoy = provBelowLast == null || provBelowLast == 0.0 ? null
            : round((provBelow - provBelowLast) / provBelowLast, 4);
            : (provBelow - provBelowLast) / provBelowLast;
        for (String city : RegionUtil.cityList()) {
            Double a = h2032FreightCum.get(city);
            double aWan = a == null ? 0.0 : round(a / 10000.0, 4);
            double aWan = a == null ? 0.0 : a / 10000.0;
            Double t = freightCum(ftMap.get(city), month);
            above.put(city, aWan);
            total.put(city, t);
            below.put(city, t == null ? null : round(t - aWan, 4));
            below.put(city, t == null ? null : t - aWan);
            aboveYoy.put(city, h2032FreightYoy.get(city));
            // 规下同比 = (规下今年 - 规下去年) / 规下去年;规下去年 = 模板去年合计 - H2032去年规上
            Double tLast = lastFreightCum(ftMap.get(city), month);
            Double aLast = h2032FreightCumLast.get(city);
            double aLastWan = aLast == null ? 0.0 : round(aLast / 10000.0, 4);
            Double belowLast = tLast == null ? null : round(tLast - aLastWan, 4);
            double aLastWan = aLast == null ? 0.0 : aLast / 10000.0;
            Double belowLast = tLast == null ? null : tLast - aLastWan;
            Double b = below.get(city);
            belowYoy.put(city, belowLast == null || belowLast == 0.0 || b == null
                ? null : round((b - belowLast) / belowLast, 4));
                ? null : (b - belowLast) / belowLast);
            totalYoy.put(city, freightCumYoy(ftMap.get(city), month));
        }
@@ -894,20 +941,20 @@
            if (maLast != null) provMonthAboveLast += maLast;
            monthAbove.put(city, ma);
            monthTotal.put(city, mt);
            monthBelow.put(city, mt == null ? null : round(mt - (ma == null ? 0.0 : ma), 4));
            monthBelow.put(city, mt == null ? null : mt - (ma == null ? 0.0 : ma));
            monthAboveYoy.put(city, growthYoy(ma, maLast));
            Double mb = monthBelow.get(city);
            Double mbLast = mtLast == null ? null : round(mtLast - (maLast == null ? 0.0 : maLast), 4);
            Double mbLast = mtLast == null ? null : mtLast - (maLast == null ? 0.0 : maLast);
            monthBelowYoy.put(city, growthYoy(mb, mbLast));
            monthTotalYoy.put(city, freightYoy(ftMap.get(city), month));
        }
        Double provMonthAboveWan = provMonthAbove == 0.0 ? null : round(provMonthAbove, 4);
        Double provMonthAboveWan = provMonthAbove == 0.0 ? null : provMonthAbove;
        Double provMonthTotal = freightMonth(ftMap.get("湖北省"), month);
        Double provMonthBelow = provMonthTotal == null ? null : round(provMonthTotal - provMonthAbove, 4);
        Double provMonthBelow = provMonthTotal == null ? null : provMonthTotal - provMonthAbove;
        Double provMonthAboveYoy = growthYoy(provMonthAbove == 0.0 ? null : provMonthAbove, provMonthAboveLast);
        Double provMonthTotalLast = lastFreightMonth(ftMap.get("湖北省"), month);
        Double provMonthBelowLast = provMonthTotalLast == null ? null
            : round(provMonthTotalLast - provMonthAboveLast, 4);
            : provMonthTotalLast - provMonthAboveLast;
        Double provMonthBelowYoy = growthYoy(provMonthBelow, provMonthBelowLast);
        Double provMonthTotalYoy = freightYoy(ftMap.get("湖北省"), month);
@@ -1244,7 +1291,7 @@
    private Double monthFreightWan(Map<String, Map<Integer, Double>> byCityMonth, String city, int month) {
        Map<Integer, Double> byMonth = byCityMonth == null ? null : byCityMonth.get(city);
        Double v = byMonth == null ? null : byMonth.get(month);
        return v == null ? null : round(v / 10000.0, 4);
        return v == null ? null : v / 10000.0;
    }
    /** 同比 = (本期-基期)/基期;任一期缺失或基期为 0 返回 null */
@@ -1346,12 +1393,12 @@
        }
    }
    /** 周转量排名 Q/R 占比:模板为缓存值,按新数据重算(规上占比 1 位小数) */
    /** 周转量排名 Q/R 占比:模板为缓存值,按新数据重算(全精度写入,两位小数展示交给单元格格式) */
    private void setFreightShare(Row row, Double above, Double total) {
        if (total != null && total > 0 && above != null) {
            double share = round(above * 10.0 / total, 1);
            double share = above * 10.0 / total;
            setFreightCell(row, 16, share);
            setFreightCell(row, 17, round(10.0 - share, 1));
            setFreightCell(row, 17, 10.0 - share);
        } else {
            setFreightCell(row, 16, (Double) null);
            setFreightCell(row, 17, (Double) null);
@@ -1561,9 +1608,9 @@
        setNumeric(row, 15, isProvince ? null : yoyRankOfMap(totalYoy, city));
        if (t != null && t > 0 && a != null) {
            double share = round(a * 10.0 / t, 1);
            double share = a * 10.0 / t;
            setNumeric(row, 16, share);
            setNumeric(row, 17, round(10.0 - share, 1));
            setNumeric(row, 17, 10.0 - share);
        }
        return rowIdx + 1;
    }
@@ -1604,9 +1651,9 @@
        setNumeric(row, 15, isProvince || cum == null ? null : yoyRankOfTurnover(cumMap, city, 2));
        if (total != null && total > 0 && above != null) {
            double share = round(above * 10.0 / total, 1);
            double share = above * 10.0 / total;
            setNumeric(row, 16, share);
            setNumeric(row, 17, round(10.0 - share, 1));
            setNumeric(row, 17, 10.0 - share);
        }
        return rowIdx + 1;
    }
@@ -1626,7 +1673,7 @@
                Double v = byMonth == null ? null : byMonth.get(month);
                if (v != null) sum += v;
            }
            return round(sum / 10000.0, 4);
            return sum / 10000.0;
        }
        Double total = freightMonth(ft, month);
        Double above = getProvinceMonthValue(provinceMonthMap, ft, month, "freight", "above", h2032Freight);
@@ -1657,7 +1704,7 @@
            for (Double v : h2032FreightCum.values()) {
                if (v != null) sum += v;
            }
            return round(sum / 10000.0, 4);
            return sum / 10000.0;
        }
        Double total = freightCum(ft, monthCount);
        Double above = getProvinceCumValue(provinceCum, ft, "freight", "above", h2032FreightCum, monthCount);
@@ -1687,7 +1734,7 @@
        if ("above".equals(scale)) {
            Map<Integer, Double> byMonth = h2032Freight.get(city);
            Double v = byMonth == null ? null : byMonth.get(month);
            return v == null ? 0.0 : round(v / 10000.0, 4);
            return v == null ? 0.0 : v / 10000.0;
        }
        Double total = freightMonth(ft, month);
        Double above = getCityMonthValue(monthData, ft, city, month, "freight", "above", h2032Freight);
@@ -1716,7 +1763,7 @@
        if ("total".equals(scale)) return freightCum(ft, monthCount);
        if ("above".equals(scale)) {
            Double v = h2032FreightCum.get(city);
            return v == null ? 0.0 : round(v / 10000.0, 4);
            return v == null ? 0.0 : v / 10000.0;
        }
        Double total = freightCum(ft, monthCount);
        Double above = getCityCumValue(cum, ft, "freight", "above", h2032FreightCum, city, monthCount);
@@ -1953,20 +2000,20 @@
                    double v = passengerMonthVal(data, indi, currentYear, m, region, metricIdx);
                    if (!keepFormula) {
                        if (c == null) c = row.createCell(col);
                        if (v == 0.0) c.setBlank(); else c.setCellValue(round(v, 4));
                        if (v == 0.0) c.setBlank(); else c.setCellValue(v);
                    }
                    // 同比列:库内去年同月同比(模板 #REF! 公式替换为数值,公式行也覆写)
                    double lv = passengerMonthVal(data, indi, currentYear - 1, m, region, metricIdx);
                    Cell yc = row.getCell(col + 1);
                    if (yc == null) yc = row.createCell(col + 1);
                    if (v == 0.0 || lv == 0.0) yc.setBlank(); else yc.setCellValue(round((v - lv) / lv, 4));
                    if (v == 0.0 || lv == 0.0) yc.setBlank(); else yc.setCellValue((v - lv) / lv);
                }
                // 累计同比列(模板 #REF! → 库内去年 1..N 月累计同比数值)
                int cumCol = extended ? 26 : 14;
                Double cumYoy = passengerCumYoy(data, indi, currentYear, fillMonths, region, metricIdx);
                Cell yc2 = row.getCell(cumCol + 1);
                if (yc2 == null) yc2 = row.createCell(cumCol + 1);
                if (cumYoy == null) yc2.setBlank(); else yc2.setCellValue(round(cumYoy, 4));
                if (cumYoy == null) yc2.setBlank(); else yc2.setCellValue(cumYoy);
            }
            recalc(wb);
            return toBytes(wb);
@@ -2047,7 +2094,7 @@
    private Double yoy(Double cur, Double base) {
        if (cur == null || base == null || base == 0.0) return null;
        return round((cur - base) / base, 4);
        return (cur - base) / base;
    }
    /** 指标取值(已换算为万人/万人公里/公里;无数据返回 null) */
@@ -2286,7 +2333,7 @@
        if (v == 0.0) return; // 库内无源:保留母版/定稿原值,缺数据不覆盖
        // POI setCellValue(double) 对公式单元格只更新缓存不移除公式,必须先 setBlank 再写值
        c.setBlank();
        c.setCellValue(round(v, 4));
        c.setCellValue(v);
    }
    /** 显式写入 0(POI setCellValue(0.0) 会转 blank,需操作底层 XML) */
@@ -2582,12 +2629,12 @@
                    int monthCount = (y == currentYear) ? maxMonth : 12;
                    for (int m = 1; m <= monthCount; m++) {
                        Double cur = midMetricValue(aggOf(data, y, m, region), i, volume);
                        setNumeric(row, col++, cur == null ? null : round(cur, 4));
                        setNumeric(row, col++, cur);
                        Double base = midMetricValue(aggOf(data, y - 1, m, region), i, volume);
                        setNumeric(row, col++, yoy(cur, base));
                    }
                    Double cum = midCumOf(data, y, region, monthCount, i, volume);
                    setNumeric(row, col++, cum == null ? null : round(cum, 4));
                    setNumeric(row, col++, cum);
                    Double cumBase = midCumOf(data, y - 1, region, monthCount, i, volume);
                    setNumeric(row, col++, yoy(cum, cumBase));
                }
@@ -2670,18 +2717,18 @@
                double turn = midCumClass(mid, currentYear, city, maxMonth, 0, false);
                double lastPass = midCumClass(mid, currentYear - 1, city, maxMonth, 0, true);
                double lastTurn = midCumClass(mid, currentYear - 1, city, maxMonth, 0, false);
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : pass);
                setValOrBlank(sheet, r0, 3, midCumYoy(mid, currentYear, city, maxMonth, 0, true));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : turn);
                setValOrBlank(sheet, r0, 7, midCumYoy(mid, currentYear, city, maxMonth, 0, false));
                // 右块当月:L/P 值、N/R 当月同比(M/O/Q/S 排名公式保留)
                double mPass = midClassVal(mid, currentYear, maxMonth, city, 0, true);
                double mTurn = midClassVal(mid, currentYear, maxMonth, city, 0, false);
                double mLastPass = midClassVal(mid, currentYear - 1, maxMonth, city, 0, true);
                double mLastTurn = midClassVal(mid, currentYear - 1, maxMonth, city, 0, false);
                setValOrBlank(sheet, r0, 11, mPass == 0 ? null : round(mPass, 4));
                setValOrBlank(sheet, r0, 11, mPass == 0 ? null : mPass);
                setValOrBlank(sheet, r0, 13, mPass == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, city, 0, true));
                setValOrBlank(sheet, r0, 15, mTurn == 0 ? null : round(mTurn, 4));
                setValOrBlank(sheet, r0, 15, mTurn == 0 ? null : mTurn);
                setValOrBlank(sheet, r0, 17, mTurn == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, city, 0, false));
            }
            recalc(wb);
@@ -2701,7 +2748,7 @@
    private Double yoyOf(double cur, double base) {
        if (base == 0.0) return null;
        return round((cur - base) / base, 4);
        return (cur - base) / base;
    }
    /** 中口径分类累计(1..monthCount 月求和) */
@@ -2743,9 +2790,9 @@
                double turn = midCumClass(mid, currentYear, "湖北省", maxMonth, dims[i], false);
                double lastPass = midCumClass(mid, currentYear - 1, "湖北省", maxMonth, dims[i], true);
                double lastTurn = midCumClass(mid, currentYear - 1, "湖北省", maxMonth, dims[i], false);
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : pass);
                setValOrBlank(sheet, r0, 2, midCumYoy(mid, currentYear, "湖北省", maxMonth, dims[i], true));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : turn);
                setValOrBlank(sheet, r0, 6, midCumYoy(mid, currentYear, "湖北省", maxMonth, dims[i], false));
            }
            // 下半块(当月):标题行9、数据行11-15(0-based 8、10-14)
@@ -2759,9 +2806,9 @@
                double turn = midClassVal(mid, currentYear, maxMonth, "湖北省", dims[i], false);
                double lastPass = midClassVal(mid, currentYear - 1, maxMonth, "湖北省", dims[i], true);
                double lastTurn = midClassVal(mid, currentYear - 1, maxMonth, "湖北省", dims[i], false);
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
                setValOrBlank(sheet, r0, 1, pass == 0 ? null : pass);
                setValOrBlank(sheet, r0, 2, pass == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, "湖北省", dims[i], true));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
                setValOrBlank(sheet, r0, 5, turn == 0 ? null : turn);
                setValOrBlank(sheet, r0, 6, turn == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, "湖北省", dims[i], false));
            }
            // 下半块占比公式由样例指向上半块(B$3/F$3),改写为本块总行(B$11/F$11),行12-15
@@ -3179,7 +3226,7 @@
        Cell c = row.getCell(colIdx);
        if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return;
        if (c == null) c = row.createCell(colIdx);
        if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
        if (v == 0) c.setBlank(); else c.setCellValue(v);
    }
    /** 显式写入 0(setDataCell 对 0 置空;汇总表网约车行需要显示 0) */
@@ -4643,8 +4690,8 @@
            if (r.getAvgDistanceCity() != null && r.getAvgDistanceCity() > 0) a[5] = r.getAvgDistanceCity();
        }
        for (double[] a : map.values()) {
            if (a[4] <= 0 && a[0] > 0) a[4] = round(a[1] / a[0], 2);
            if (a[5] <= 0 && a[2] > 0) a[5] = round(a[3] / a[2], 2);
            if (a[4] <= 0 && a[0] > 0) a[4] = a[1] / a[0];
            if (a[5] <= 0 && a[2] > 0) a[5] = a[3] / a[2];
        }
        return map;
    }
@@ -4960,19 +5007,19 @@
                    Row row = sheet.getRow(rowIdx);
                    if (row == null) row = sheet.createRow(rowIdx);
                    if (a > 0) {
                        // 2026年 1..N 月:库内有值则填(2位小数),无值清空模板样例
                        // 2026年 1..N 月:库内有值则填(全精度写入,两位小数由单元格格式展示),无值清空模板样例
                        for (int m = 1; m <= month; m++) {
                            double v = cityVal(monthCity, area, m, k);
                            Cell c = row.getCell(2 + (m - 1) * 2);
                            if (c == null) c = row.createCell(2 + (m - 1) * 2);
                            if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
                            if (v == 0) c.setBlank(); else c.setCellValue(v);
                        }
                        // 2025年 1..12 月:库内有去年数据则填,否则清空模板样例
                        for (int m = 1; m <= 12; m++) {
                            double v = cityVal(lastYearMonthCity, area, m, k);
                            Cell c = row.getCell(19 + (m - 1) * 2);
                            if (c == null) c = row.createCell(19 + (m - 1) * 2);
                            if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2));
                            if (v == 0) c.setBlank(); else c.setCellValue(v);
                        }
                    }
                    rowIdx++;
@@ -5465,7 +5512,7 @@
        hssfNum(row, 8, agg.startCum == 0 ? null : agg.startCum);
        hssfNum(row, 9, agg.yearPlan == 0 ? null : agg.yearPlan);
        hssfNum(row, 22, agg.yearCum == 0 ? null : agg.yearCum);
        if (agg.yearPlan > 0) hssfNum(row, 23, round(agg.yearCum / agg.yearPlan, 4));
        if (agg.yearPlan > 0) hssfNum(row, 23, agg.yearCum / agg.yearPlan);
    }
@@ -5797,7 +5844,7 @@
                putNum(row, 2, s[3] == 0 ? null : s[3]);
                putNum(row, 3, s[4] == 0 ? null : s[4]);
                double b = numOf(row.getCell(1));
                putNum(row, 4, b > 0 ? round(s[3] / b * 100, 4) : null);
                putNum(row, 4, b > 0 ? s[3] / b * 100 : null);
                cT += s[3];
                dT += s[4];
            }
@@ -5806,7 +5853,7 @@
                putNum(tot, 2, cT == 0 ? null : cT);
                putNum(tot, 3, dT == 0 ? null : dT);
                double b = numOf(tot.getCell(1));
                putNum(tot, 4, b > 0 ? round(cT / b * 100, 4) : null);
                putNum(tot, 4, b > 0 ? cT / b * 100 : null);
            }
        }
        try {
@@ -6027,7 +6074,7 @@
        for (InvestmentMonthly m : monthlies) provinceYearPlan += nz(m.getYearPlan());
        HSSFRow rowOther = s1.getRow(9);
        for (int c = 1; c <= 5; c++) hssfNum(rowOther, c, null);
        hssfNum(rowOther, 5, provinceYearPlan == 0 ? null : round(provinceYearPlan / 10000, 2));
        hssfNum(rowOther, 5, provinceYearPlan == 0 ? null : provinceYearPlan / 10000);
        // ---- sheet2 规上项目表:表头年份 + 合计 + 项目行 ----
        HSSFSheet s2 = wb.getSheetAt(1);
        hssfText(s2.getRow(0), 0, year + "年交通建设项目储备情况(规模以上项目)");
@@ -6259,6 +6306,8 @@
    private double nz(Double v) {
        return v == null ? 0.0 : v;
    }
    /** 数值格式里 3 位及以上的小数部分(如 0.0000 / 0.000_ ) */
    private static final java.util.regex.Pattern DEC_MORE_THAN_TWO = java.util.regex.Pattern.compile("\\.0{3,}");
    /** 生成表数值列统一样式:保留全精度数值,显示两位小数;整数值与 %/日期/科学计数等既有样式不改变 */
    private void applyTwoDecimalFormat(org.apache.poi.ss.usermodel.Workbook wb) {
        if (wb == null) return;
@@ -6275,24 +6324,32 @@
                        CellType ct = cell.getCellType();
                        if (ct != CellType.NUMERIC && ct != CellType.FORMULA) continue;
                        if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) continue;
                        boolean integral = false;
                        if (ct == CellType.NUMERIC) {
                            integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
                        } else if (cell.getCachedFormulaResultType() == CellType.NUMERIC) {
                            integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
                        }
                        if (integral) continue; // 整数不显示 .00(含公式结果为整数的格)
                        org.apache.poi.ss.usermodel.CellStyle cs = cell.getCellStyle();
                        if (cs == null) continue;
                        String f = cs.getDataFormatString();
                        if (f == null) f = "";
                        if (f.contains("%") || f.contains("E") || f.contains("0.00")) continue;
                        boolean plain = f.isEmpty() || "General".equals(f) || "general".equals(f)
                            || "0".equals(f) || "#,##0".equals(f) || "0.0".equals(f) || "#,##0.0".equals(f);
                        if (!plain) continue;
                        if (f.contains("%") || f.contains("E") || f.contains("@")) continue;
                        String normalized = null;
                        if (DEC_MORE_THAN_TWO.matcher(f).find()) {
                            // 母版个别格带 3~4 位小数格式(如 0.000_ 、0.0000_);[Red]\(0.0000\)):数值仍全精度,
                            // 展示统一收敛到两位。这类格本就按数值格式展示,故不受「整数不显示 .00」保护影响。
                            normalized = DEC_MORE_THAN_TWO.matcher(f).replaceAll(".00");
                        } else {
                            boolean integral = false;
                            if (ct == CellType.NUMERIC) {
                                integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
                            } else if (cell.getCachedFormulaResultType() == CellType.NUMERIC) {
                                integral = Math.rint(cell.getNumericCellValue()) == cell.getNumericCellValue();
                            }
                            if (integral) continue; // 整数不显示 .00(含公式结果为整数的格)
                            if (f.contains("0.00")) continue;
                            boolean plain = f.isEmpty() || "General".equals(f) || "general".equals(f)
                                || "0".equals(f) || "#,##0".equals(f) || "0.0".equals(f) || "#,##0.0".equals(f);
                            if (!plain) continue;
                        }
                        org.apache.poi.ss.usermodel.CellStyle ns = wb.createCellStyle();
                        ns.cloneStyleFrom(cs);
                        ns.setDataFormat(fmtTwo);
                        ns.setDataFormat(normalized == null ? fmtTwo : df.getFormat(normalized));
                        cell.setCellStyle(ns);
                    } catch (Exception ignore) {
                        // 个别单元格格式异常不阻塞导出