| | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.FormulaEvaluator; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.util.CellRangeAddress; |
| | |
| | | if (sh == null || expanded.contains(sh.getSheetName())) continue; |
| | | shiftQualifiedRefs(sh, insertColOf); |
| | | } |
| | | recalcAndCache(wb); |
| | | int titleFixed = applyTitles(wb, year, targetMonth); |
| | | if (titleFixed > 0) { |
| | | log.info("扩列:标题按 {}年{}月 重写 {} 格", year, targetMonth, titleFixed); |
| | |
| | | /** 只把 7 个标题页的标题按给定期次重写(用于修正母版里滞后的标题)。 */ |
| | | public byte[] fixTitles(byte[] source, int year, int month) { |
| | | try (XSSFWorkbook wb = new XSSFWorkbook(new ByteArrayInputStream(source))) { |
| | | recalcAndCache(wb); |
| | | int n = applyTitles(wb, year, month); |
| | | wb.setForceFormulaRecalculation(true); |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | |
| | | return out.toByteArray(); |
| | | } catch (Exception e) { |
| | | throw new IllegalStateException("汇总大表标题修正失败: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | /** 整本重算并把结果写入缓存值:否则母版里只有公式没有值,后续读取会把空值当 0(2026-10-12 修) */ |
| | | private void recalcAndCache(XSSFWorkbook wb) { |
| | | try { |
| | | FormulaEvaluator ev = wb.getCreationHelper().createFormulaEvaluator(); |
| | | ev.clearAllCachedResultValues(); |
| | | ev.evaluateAll(); |
| | | } catch (Exception e) { |
| | | log.warn("扩列:整本重算未完成(公式仍在,Excel 打开会再算):{}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | |
| | | return insertCol; |
| | | } |
| | | |
| | | /** 表头行(0 基):含「年+月」表头单元格的行;轨道、轮渡有两个块 → 两行。 */ |
| | | /** |
| | | * 表头行(0 基):轨道、轮渡这类一页两个块的有两行。 |
| | | * |
| | | * <p>2026-10-12 二次修正:先取「月份表头格最多的那一行」作基准,把它的月份列当作标准列, |
| | | * 只有这些列上大半是月份格的行才算表头行。前版只要求「整行 ≥2 个月份格」, |
| | | * 会把数据行里恰好落成日期序列的数值当成表头 → 扩列时把月份日期写进数据行 |
| | | * (2026-09 母版 ` 货运!S11`、`出租!S6` 等 13 格被写成 46266)。 |
| | | */ |
| | | private List<Integer> headerRowsOf(Sheet sh) { |
| | | List<Integer> rows = new ArrayList<>(); |
| | | int lastRow = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + HEADER_SCAN_ROWS); |
| | | for (int r = sh.getFirstRowNum(); r <= lastRow; r++) { |
| | | int first = sh.getFirstRowNum(); |
| | | int lastRow = Math.min(sh.getLastRowNum(), first + HEADER_SCAN_ROWS); |
| | | List<Integer> base = new ArrayList<>(); |
| | | for (int r = first; r <= lastRow; r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { |
| | | if (yearMonthOf(row.getCell(c)) != null) { |
| | | rows.add(r); |
| | | break; |
| | | } |
| | | List<Integer> cols = monthColsOf(row); |
| | | if (cols.size() > base.size()) base = cols; |
| | | } |
| | | List<Integer> rows = new ArrayList<>(); |
| | | if (base.size() < 2) return rows; |
| | | int need = Math.max(2, (base.size() + 1) / 2); |
| | | for (int r = first; r <= lastRow; r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | int hit = 0; |
| | | for (int c : base) { |
| | | if (yearMonthOf(row.getCell(c)) != null) hit++; |
| | | } |
| | | if (hit >= need) rows.add(r); |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | /** 行内所有「年+月」表头格的列号(0 基) */ |
| | | private List<Integer> monthColsOf(Row row) { |
| | | List<Integer> cols = new ArrayList<>(); |
| | | for (int c = row.getFirstCellNum(); c >= 0 && c < row.getLastCellNum(); c++) { |
| | | if (yearMonthOf(row.getCell(c)) != null) cols.add(c); |
| | | } |
| | | return cols; |
| | | } |
| | | |
| | | private Map<Integer, Integer> monthCols(Sheet sh, int year) { |
| | | Map<Integer, Integer> cols = new LinkedHashMap<>(); |
| | | int lastRow = Math.min(sh.getLastRowNum(), sh.getFirstRowNum() + HEADER_SCAN_ROWS); |