| | |
| | | if (blockOffset % 5 == 0) { |
| | | if (isYoy) { |
| | | fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, -1, volume); |
| | | } else if (monthOfCol(col) > 6) { |
| | | // 扩列出来的 7~12 月总行值列:模板里本无此列,克隆后既没有公式也没有缓存值, |
| | | // 直接写库内四维合计(维度0)。1~6 月保留模板缓存值,避免丢掉模板里的定稿数。 |
| | | double total = midClassVal(mid, currentYear, monthOfCol(col), area, 0, volume); |
| | | c.setBlank(); |
| | | if (total != 0.0) c.setCellValue(round(total, 4)); |
| | | } else if (c.getCellType() == CellType.FORMULA) { |
| | | keepCached(c); // 1~6 月总行值列 = 模板缓存值,保留 |
| | | } |
| | | // 总行值列:一律保留模板公式(1~6 月原公式 + 扩列克隆出来的公式), |
| | | // 由生成收尾的 recalcWorkbookFormulas 求值写回缓存。早期版本这里 keepCached |
| | | // 会把刚克隆出来的公式清成空格,导致 7 月及以后总行永远空白。 |
| | | continue; |
| | | } |
| | | int k = blockOffset % 5; // 1=班线 2=公交 3=出租城乡 4=网约车城乡 |
| | |
| | | int[] recalc = recalcWorkbookFormulas(wb); |
| | | log.info("中口径明细公式重算完成:成功 {} 格,失败 {} 格", recalc[0], recalc[1]); |
| | | clearFormulaErrorsAll(wb); // 去年同期明细页残留 #REF! 同比公式 → 清空 |
| | | blankFutureMonthColumns(sheet, fillMonths); // 报表期之后的月份列不保留克隆公式/0 值 |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清空「报表期之后」的月份列(扩列克隆出来的 7~12 月里、尚未到期的月份)。 |
| | | * 不清的话,克隆出来的总行公式会算出 0,报表上会出现"9~12 月全是 0"的假数据; |
| | | * 累计列(AA)的公式/缓存值已在重算阶段算好,清空格子不影响它(空 = 0)。 |
| | | */ |
| | | private void blankFutureMonthColumns(Sheet sheet, int filledMonths) { |
| | | for (int m = filledMonths + 1; m <= 12; m++) { |
| | | int col = 2 * m; // 0 基:1 月=2(C)、7 月=14(O)、12 月=24(Y) |
| | | for (int r = 4; r <= sheet.getLastRowNum(); r++) { // 0 基第 4 行 = 第 5 行(首个数据行),表头不动 |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | Cell val = row.getCell(col); |
| | | if (val != null) val.setBlank(); |
| | | Cell yoy = row.getCell(col + 1); |
| | | if (yoy != null) yoy.setBlank(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | return (col - 1) / 2; |
| | | } |
| | | |
| | | /** 填中口径数据单元格:班线/公交/出租城乡/网约车城乡 四维统一取库内值(无值清空) */ |
| | | /** |
| | | * 填中口径数据单元格:班线/公交/出租城乡/网约车城乡 四维统一取库内值。 |
| | | * 库内该月该维无源(取值为 0)时**保留母版/模板原值**——绝不写空、也不写 0。 |
| | | * 依据:模板的城市级城乡行是「台账页 总量 − 城市内」的引用、并已业务定稿; |
| | | * 用库内缺失值覆盖会把定稿数清掉(如 2026-01~06 网约车订单缺失时, |
| | | * 全省网约车城乡 333.39 与各市州值会被清空)。 |
| | | */ |
| | | private void fillMidCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) { |
| | | double v = midClassVal(mid, year, month, area, k, volume); |
| | | if (v == 0.0) return; // 库内无源:保留母版/定稿原值,缺数据不覆盖 |
| | | // POI setCellValue(double) 对公式单元格只更新缓存不移除公式,必须先 setBlank 再写值 |
| | | c.setBlank(); |
| | | if (v != 0.0) c.setCellValue(round(v, 4)); |
| | | c.setCellValue(round(v, 4)); |
| | | } |
| | | |
| | | /** 显式写入 0(POI setCellValue(0.0) 会转 blank,需操作底层 XML) */ |
| | |
| | | log.warn("汇总工作簿公式重算:创建求值器失败,跳过({})", e.getMessage()); |
| | | return new int[]{0, 0}; |
| | | } |
| | | try { |
| | | // 跨簿公式([1]出租!C13 之类)在外部工作簿缺失时默认抛异常,会让引用它的 |
| | | // 总行/累计/派生公式连锁失败(表现为「公式还在但没有缓存值」)。 |
| | | // 置 true 后改用公式格自身的缓存值(即定稿值)参与计算。 |
| | | ev.setIgnoreMissingWorkbooks(true); |
| | | } catch (Exception ignore) { |
| | | // 个别实现不支持该开关,忽略即可 |
| | | } |
| | | int ok = 0, fail = 0; |
| | | java.util.List<String> samples = new java.util.ArrayList<>(); |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |