| | |
| | | applyTwoDecimalFormat(wb); // 数值显示两位小数(保留全精度);% / 日期等既有样式不改变 |
| | | int inlineFixed = normalizeStaleInlineStrings(wb); // 清掉「非 inlineStr 却残留 <is>」的非法格,否则 Excel 拒绝打开 |
| | | if (inlineFixed > 0) log.warn("汇总工作簿清掉残留 inlineStr 非法格 {} 个(母版由 openpyxl 写出时的典型问题)", inlineFixed); |
| | | int[] recalc = recalcWorkbookFormulas(wb); // 公式重算并写回缓存值:全省行(Σ市州)/累计列/排名 SUM 才能被预览与二次处理读到 |
| | | log.info("汇总工作簿公式重算完成:成功 {} 格,失败 {} 格(失败格保留母版旧缓存值,打开时按 fullCalcOnLoad 重算)", recalc[0], recalc[1]); |
| | | if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr(); |
| | | wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 还原/改写公式后打开即重算 |
| | | return toBytes(wb); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 汇总工作簿公式重算:POI 写文件不带缓存值,导致「全省行(Σ17市州)」「累计列」「排名 SUM」 |
| | | * 在预览/二次处理里只看到母版上一期的旧值(2026-09-18 用户报“8月没数据 / 全省行停在上一期”的根因之一)。 |
| | | * 逐格 evaluate 并把结果写回缓存值;个别不支持函数/外部引用格失败则跳过并计数,不阻塞导出。 |
| | | */ |
| | | private int[] recalcWorkbookFormulas(XSSFWorkbook wb) { |
| | | if (wb == null) return new int[]{0, 0}; |
| | | org.apache.poi.ss.usermodel.FormulaEvaluator ev; |
| | | try { |
| | | ev = wb.getCreationHelper().createFormulaEvaluator(); |
| | | } catch (Exception e) { |
| | | log.warn("汇总工作簿公式重算:创建求值器失败,跳过({})", e.getMessage()); |
| | | return new int[]{0, 0}; |
| | | } |
| | | int ok = 0, fail = 0; |
| | | java.util.List<String> samples = new java.util.ArrayList<>(); |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | Sheet sh = wb.getSheetAt(i); |
| | | if (sh == null) continue; |
| | | for (Row row : sh) { |
| | | if (row == null) continue; |
| | | for (Cell c : row) { |
| | | if (c == null || c.getCellType() != CellType.FORMULA) continue; |
| | | try { |
| | | ev.evaluateFormulaCell(c); // 注意:evaluate() 无副作用,必须用 evaluateFormulaCell 才会把结果写回缓存值 |
| | | ok++; |
| | | } catch (Exception ex) { |
| | | fail++; |
| | | if (samples.size() < 10) { |
| | | samples.add(sh.getSheetName() + "!" + c.getAddress() + " :: " + ex.getClass().getSimpleName()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (!samples.isEmpty()) log.warn("汇总工作簿公式重算失败样例:{}", samples); |
| | | return new int[]{ok, fail}; |
| | | } |
| | | |
| | | /** |
| | | * 清掉「非 inlineStr 却残留 <is>」的非法单元格。 |
| | | * |
| | | * 母版若由 openpyxl 写出,文本单元格用 <is>(inlineStr)存储;POI 的 setCellValue 只改写 t 与 <v>, |