| | |
| | | |
| | | |
| | | |
| | | /** 公路旅客个体客运量/周转量(企业线下汇报数据,按所属地区导入,供分市州表个体行填充) */ |
| | | /** 公路旅客个体客运量/周转量(导入后按市州汇总填充分市州表个体行),支持两种模板: |
| | | * ① 企业线下汇报明细(表头含「企业名称」):逐企业入库; |
| | | * ② 省厅市州汇总表《湖北省公路旅客运输个体户月度生产情况(YYYY.M)》: |
| | | * sheet 内为「全省N月个体客运量汇总表」标题块 + 「市州/个体车辆数/…/个体客运量/个体旅客周转量」表头, |
| | | * 按市州逐行入库(同一市州多行如 县内班线+跨县班线 均入库,报表侧按市州自动汇总)。 */ |
| | | public ImportResult importPassengerIndividual(MultipartFile file, String period) throws Exception { |
| | | List<PassengerIndividualMonthly> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | Integer codeCol = colMap.get("所属地区"); |
| | | Integer entCol = colMap.get("企业代码"); |
| | | Integer nameCol = colMap.get("企业名称"); |
| | | Integer passCol = null; |
| | | Integer turnCol = null; |
| | | for (Map.Entry<String, Integer> e : colMap.entrySet()) { |
| | | String k = e.getKey(); |
| | | if (k != null && k.contains("个体") && k.contains("客运量")) passCol = e.getValue(); |
| | | if (k != null && k.contains("个体") && (k.contains("周转量") || k.contains("周转"))) turnCol = e.getValue(); |
| | | } |
| | | if (passCol == null && turnCol == null) { |
| | | throw new RuntimeException("未找到「个体客运量/个体旅客周转量」列,请检查模板(列名需包含“个体客运量”“个体周转量”)"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = nameCol != null ? getString(row, nameCol) : null; |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null; |
| | | Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null; |
| | | if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue; |
| | | PassengerIndividualMonthly entity = new PassengerIndividualMonthly(); |
| | | entity.setReportPeriod(period); |
| | | entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null); |
| | | entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null); |
| | | entity.setEnterpriseName(name.trim()); |
| | | entity.setPassengerCount(pass); |
| | | entity.setTurnover(turn); |
| | | rows.add(entity); |
| | | if (colMap.containsKey("企业名称")) { |
| | | parsePassengerIndividualEnterprise(sheet, colMap, period, rows); |
| | | } else { |
| | | parsePassengerIndividualCitySheet(sheet, period, rows); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Passenger individual parse error", e); |
| | |
| | | recordBatch(file.getOriginalFilename(), "PASSENGER_INDIVIDUAL", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | log.info("Passenger individual imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** 旧版个体客运量模板(企业线下汇报明细:所属地区/企业代码/企业名称/…个体客运量/…个体旅客周转量) */ |
| | | private void parsePassengerIndividualEnterprise(Sheet sheet, Map<String, Integer> colMap, String period, |
| | | List<PassengerIndividualMonthly> rows) { |
| | | Integer codeCol = colMap.get("所属地区"); |
| | | Integer entCol = colMap.get("企业代码"); |
| | | Integer nameCol = colMap.get("企业名称"); |
| | | Integer passCol = null; |
| | | Integer turnCol = null; |
| | | for (Map.Entry<String, Integer> e : colMap.entrySet()) { |
| | | String k = e.getKey(); |
| | | if (k != null && k.contains("个体") && k.contains("客运量")) passCol = e.getValue(); |
| | | if (k != null && k.contains("个体") && (k.contains("周转量") || k.contains("周转"))) turnCol = e.getValue(); |
| | | } |
| | | if (passCol == null && turnCol == null) { |
| | | throw new RuntimeException("未找到「个体客运量/个体旅客周转量」列,请检查模板(列名需包含“个体客运量”“个体周转量”)"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = nameCol != null ? getString(row, nameCol) : null; |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null; |
| | | Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null; |
| | | if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue; |
| | | PassengerIndividualMonthly entity = new PassengerIndividualMonthly(); |
| | | entity.setReportPeriod(period); |
| | | entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null); |
| | | entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null); |
| | | entity.setEnterpriseName(name.trim()); |
| | | entity.setPassengerCount(pass); |
| | | entity.setTurnover(turn); |
| | | rows.add(entity); |
| | | } |
| | | } |
| | | |
| | | /** 新版市州汇总模板:《全省N月个体客运量汇总表》标题行 → 表头行(市州/…/个体客运量/个体旅客周转量)→ 市州数据行 */ |
| | | private void parsePassengerIndividualCitySheet(Sheet sheet, String period, List<PassengerIndividualMonthly> rows) { |
| | | int targetMonth; |
| | | try { |
| | | targetMonth = Integer.parseInt(period.split("-")[1]); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("报表期格式应为 yyyy-MM:" + period); |
| | | } |
| | | int titleRow = -1; |
| | | for (int r = 0; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String t = getString(row, 0); |
| | | if (t == null || !t.contains("全省") || !t.contains("月")) continue; |
| | | java.util.regex.Matcher m = java.util.regex.Pattern.compile("全省(\\d+)月").matcher(t); |
| | | if (!m.find()) continue; |
| | | if (Integer.parseInt(m.group(1)) == targetMonth) { titleRow = r; break; } |
| | | if (titleRow < 0) titleRow = r; // 标题不含目标月份时暂记第一个标题块兜底 |
| | | } |
| | | if (titleRow < 0) { |
| | | throw new RuntimeException("未找到「全省N月个体客运量汇总表」标题块,请使用《模板_湖北省公路旅客运输个体户月度生产情况(YYYY.M)》整理后导入"); |
| | | } |
| | | Row header = sheet.getRow(titleRow + 1); |
| | | if (header == null) { |
| | | throw new RuntimeException("「全省" + targetMonth + "月…汇总表」标题后缺少表头行(市州/个体客运量/个体旅客周转量)"); |
| | | } |
| | | int cityCol = -1, passCol = -1, turnCol = -1; |
| | | for (Cell c : header) { |
| | | String h = FORMATTER.formatCellValue(c).trim(); |
| | | if (h.isEmpty()) continue; |
| | | if (h.contains("市州")) cityCol = c.getColumnIndex(); |
| | | else if (h.contains("个体") && h.contains("客运量")) passCol = c.getColumnIndex(); |
| | | else if (h.contains("个体") && (h.contains("周转量") || h.contains("周转"))) turnCol = c.getColumnIndex(); |
| | | } |
| | | if (cityCol < 0 || (passCol < 0 && turnCol < 0)) { |
| | | throw new RuntimeException("未找到「市州 / 个体客运量 / 个体旅客周转量」列,请使用系统模板整理后导入"); |
| | | } |
| | | int lastRow = Math.min(sheet.getLastRowNum(), titleRow + 40); |
| | | for (int r = titleRow + 2; r <= lastRow; r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String first = getString(row, cityCol); |
| | | if (first == null) break; |
| | | String t = first.trim(); |
| | | if (t.isEmpty() || t.contains("合计") || t.contains("环比") || t.contains("全省")) break; |
| | | String city = RegionUtil.normalizeCityName(t); |
| | | if (!RegionUtil.cityList().contains(city)) continue; |
| | | Double pass = passCol >= 0 ? getDecimalOrNull(row, passCol) : null; |
| | | Double turn = turnCol >= 0 ? getDecimalOrNull(row, turnCol) : null; |
| | | if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue; |
| | | PassengerIndividualMonthly entity = new PassengerIndividualMonthly(); |
| | | entity.setReportPeriod(period); |
| | | entity.setRegionCode(RegionUtil.codeOfCity(city)); |
| | | entity.setEnterpriseName(city); |
| | | entity.setPassengerCount(pass); |
| | | entity.setTurnover(turn); |
| | | rows.add(entity); |
| | | } |
| | | } |
| | | |
| | | /** 公路旅客运政车辆数(独立模板:企业代码/企业名称/车辆数/载客位数) */ |
| | |
| | | /** 能耗明细每组固定18列,车牌号起始列(0-based):3,21,39,57,75 */ |
| | | private static final int[] ENERGY_GROUP_COLS = {3, 21, 39, 57, 75}; |
| | | |
| | | /** 能耗明细模板表头关键列(0-based):程序按固定列位解析,解析前校验表头,错位直接拒绝 */ |
| | | private static final int[] ENERGY_HEADER_COLS = {3, 4, 5, 21, 39, 57, 75}; |
| | | private static final String[] ENERGY_HEADER_KEYWORDS = {"车牌号", "车辆类型", "燃料类型", "车牌号", "车牌号", "车牌号", "车牌号"}; |
| | | |
| | | /** 能耗导入按报表期互斥锁(H204 明细与运政信息共用,避免同报表期并发重导互相覆盖) */ |
| | | private Object energyLock(String period) { |
| | | return energyImportLocks.computeIfAbsent(period == null ? "" : period, k -> new Object()); |
| | |
| | | // sheet0: 第二季度货车能耗明细(每行左右各5组车辆,每组固定18列) |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | requireHeaderKeyword(sheet, "道路货运车辆能源消耗情况", "企业名称", "车牌号"); |
| | | requireEnergyHeaderLayout(sheet); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | requireHeaderKeyword(sheet, "能耗车辆运政信息", "车牌号"); |
| | | Row authHeader = sheet.getRow(0); |
| | | String authH0 = authHeader == null ? null : FORMATTER.formatCellValue(authHeader.getCell(0)).trim(); |
| | | if (authH0 == null || authH0.isEmpty() || !authH0.contains("车牌号")) { |
| | | throw new RuntimeException("能耗车辆运政信息表头与系统模板不一致:第 1 列应为「车牌号」,实际为「" |
| | | + (authH0 == null || authH0.isEmpty() ? "空" : authH0) + "」。请使用系统模板《模板_能耗车辆运政信息.xlsx》整理后再导入"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | |
| | | |
| | | // ========== 运政车辆 ========== |
| | | |
| | | /** 运政车辆导入:与 H203-2 月报一致,按表头名称定位列,列顺序变化也能正确导入 */ |
| | | public int importTransportAuth(MultipartFile file, String period) throws Exception { |
| | | List<TransportAuthVehicle> list = new ArrayList<>(); |
| | | int failRows = 0; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | requireHeaderKeyword(sheet, "运政车辆", "企业名称"); |
| | | requireHeaderCols(sheet, "运政车辆", "企业名称", "总牵引车数", "总挂车数", "其它车辆数", "挂车总吨位(吨)", "其它车辆总吨位(吨)"); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 0); |
| | | String name = gs(row, colMap, "企业名称"); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | TransportAuthVehicle entity = new TransportAuthVehicle(); |
| | | entity.setReportPeriod(period); |
| | | entity.setEnterpriseName(name); |
| | | entity.setTractorCount(getInt(row, 1)); |
| | | entity.setTrailerCount(getInt(row, 2)); |
| | | entity.setOtherCount(getInt(row, 3)); |
| | | entity.setTrailerTons(getDecimal(row, 4)); |
| | | entity.setOtherTons(getDecimal(row, 5)); |
| | | entity.setTractorCount(gi(row, colMap, "总牵引车数")); |
| | | entity.setTrailerCount(gi(row, colMap, "总挂车数")); |
| | | entity.setOtherCount(gi(row, colMap, "其它车辆数")); |
| | | entity.setTrailerTons(gd(row, colMap, "挂车总吨位(吨)")); |
| | | entity.setOtherTons(gd(row, colMap, "其它车辆总吨位(吨)")); |
| | | list.add(entity); |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | |
| | | // ========== 轨迹里程 ========== |
| | | |
| | | /** 轨迹里程导入:与 H203-2 月报一致,按表头名称定位列,列顺序变化也能正确导入 */ |
| | | public int importTrackMileage(MultipartFile file, String period) throws Exception { |
| | | List<VehicleTrackMileage> list = new ArrayList<>(); |
| | | int failRows = 0; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | requireHeaderKeyword(sheet, "轨迹里程", "企业名称"); |
| | | requireHeaderCols(sheet, "轨迹里程", "企业名称", "月度总里程(公里)", "有里程的车辆数"); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 1); |
| | | String name = gs(row, colMap, "企业名称"); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | VehicleTrackMileage entity = new VehicleTrackMileage(); |
| | | entity.setEnterpriseName(name); |
| | | entity.setMonthlyMileage(getDecimal(row, 2)); |
| | | entity.setTrackedVehicles(getInt(row, 3)); |
| | | entity.setMonthlyMileage(gd(row, colMap, "月度总里程(公里)")); |
| | | entity.setTrackedVehicles(gi(row, colMap, "有里程的车辆数")); |
| | | entity.setReportPeriod(period); |
| | | list.add(entity); |
| | | } |
| | |
| | | if (sheetName.contains("当月")) periodType = "MONTH"; |
| | | else if (sheetName.contains("累计")) periodType = "CUMULATIVE"; |
| | | if (periodType == null) continue; |
| | | |
| | | // 校验 sheet 布局:第2行应含「市州/规上企业运输量」,第3行子表头应含「周转量」或「货运量」(扩展布局) |
| | | Row headRow = sheet.getRow(1); |
| | | Row subHeadRow = sheet.getRow(2); |
| | | boolean layoutOk = headRow != null |
| | | && getString(headRow, 0) != null && getString(headRow, 0).contains("市州") |
| | | && getString(headRow, 1) != null && getString(headRow, 1).contains("规上") |
| | | && subHeadRow != null |
| | | && ("货运量".equals(getString(subHeadRow, 0)) |
| | | || (getString(subHeadRow, 1) != null && getString(subHeadRow, 1).contains("周转量"))); |
| | | if (!layoutOk) { |
| | | throw new RuntimeException("所选数据类型【规上规下拆分】与文件内容不符:sheet「" + sheetName |
| | | + "」未找到「市州/规上企业运输量/周转量」表头,请确认是否选错了数据类型"); |
| | | } |
| | | |
| | | // 检测子表头:是否包含货运量列(扩展布局) |
| | | Row subHeader = sheet.getRow(2); |
| | |
| | | int monthCount = Integer.parseInt(period.split("-")[1]); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Row header = sheet.getRow(0); |
| | | String h0 = header == null ? null : getString(header, 0); |
| | | String h1 = header == null ? null : getString(header, 1); |
| | | if (header == null || h0 == null || !h0.contains("货运量") || h1 == null || !h1.contains("月")) { |
| | | throw new RuntimeException("所选数据类型【货运量周转量】与文件内容不符:表头应为「货运量(万吨)/1月…12月」布局,请确认是否选错了数据类型"); |
| | | } |
| | | boolean cityRowFound = false; |
| | | for (int r = 1; r <= Math.min(sheet.getLastRowNum(), 30); r++) { |
| | | Row rr = sheet.getRow(r); |
| | |
| | | } |
| | | } |
| | | throw new RuntimeException("所选数据类型【" + typeLabel + "】与文件内容不符:表头未包含【" + String.join("】【", keywords) + "】等列,请确认是否选错了数据类型"); |
| | | } |
| | | |
| | | /** 能耗明细表头列位校验:系统模板为“前3列 + 5组×18列”,程序按固定列位解析,表头错位会导致整表错乱,错位时直接拒绝 */ |
| | | private void requireEnergyHeaderLayout(Sheet sheet) { |
| | | Row header = sheet == null ? null : sheet.getRow(0); |
| | | if (header == null) { |
| | | throw new RuntimeException("能耗明细表头缺失,请使用系统模板《模板_道路货运车辆能源消耗情况(2026).xlsx》整理后再导入"); |
| | | } |
| | | for (int i = 0; i < ENERGY_HEADER_COLS.length; i++) { |
| | | int col = ENERGY_HEADER_COLS[i]; |
| | | String keyword = ENERGY_HEADER_KEYWORDS[i]; |
| | | Cell cell = header.getCell(col); |
| | | String hv = cell == null ? null : FORMATTER.formatCellValue(cell).trim(); |
| | | if (hv == null || hv.isEmpty() || !hv.contains(keyword)) { |
| | | throw new RuntimeException("表头与系统模板不一致:第 " + (col + 1) + " 列应为「" + keyword + "」,实际为「" |
| | | + (hv == null || hv.isEmpty() ? "空" : hv) + "」。请使用系统模板《模板_道路货运车辆能源消耗情况(2026).xlsx》整理后再导入"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** 通用表头列位校验:程序按固定列位解析,表头错位会导致整表错乱,错位时直接拒绝导入 */ |
| | | private void requireHeaderLayout(Sheet sheet, String typeLabel, String templateHint, |
| | | int[] cols, String[] keywords) { |
| | | Row header = sheet == null ? null : sheet.getRow(0); |
| | | if (header == null) { |
| | | throw new RuntimeException("所选数据类型【" + typeLabel + "】文件表头缺失,请使用系统模板《" + templateHint + "》整理后再导入"); |
| | | } |
| | | for (int i = 0; i < cols.length; i++) { |
| | | int col = cols[i]; |
| | | String keyword = keywords[i]; |
| | | Cell cell = header.getCell(col); |
| | | String hv = cell == null ? null : FORMATTER.formatCellValue(cell).trim(); |
| | | if (hv == null || hv.isEmpty() || !hv.contains(keyword)) { |
| | | throw new RuntimeException("表头与系统模板不一致:第 " + (col + 1) + " 列应为「" + keyword + "」,实际为「" |
| | | + (hv == null || hv.isEmpty() ? "空" : hv) + "」。所选数据类型【" + typeLabel + "】请使用系统模板《" + templateHint + "》整理后再导入"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** 按表头名称建立列号映射,兼容新旧模板列位差异 */ |
| | |
| | | if (name == null || name.isEmpty()) return false; |
| | | String norm = name.replace(" ", ""); |
| | | int month = Integer.parseInt(period.substring(5, 7)); |
| | | String normDash = norm.replace('.', '-'); // "2026.8"/"2026.08" -> "2026-8"/"2026-08" |
| | | return norm.contains(month + "月") || norm.contains(String.format("%02d", month) + "月") |
| | | || name.contains(period) || name.contains(period.replace("-0", "-")); |
| | | || name.contains(period) || name.contains(period.replace("-0", "-")) |
| | | || normDash.contains(period) || normDash.contains(period.replace("-0", "-")); |
| | | } |
| | | |
| | | /** 解析单个市州报表:选 sheet(报表期名优先 + 数据行数),按列名映射提取项目行 */ |
| | |
| | | if (col == null) continue; |
| | | int cnt = countInvestRows(sh, col); |
| | | int score = cnt + (cnt > 0 && investSheetMatchesPeriod(sh, period) ? 1000000 : 0); |
| | | if (cnt > 0 && investSheetMatchesPeriod(sh, period)) { |
| | | // 当期真实报表 sheet 名通常带年份(如 2026.8/2026年8月),历史sheet多为纯月份名(8月):带年份的优先 |
| | | String norm = sh.getSheetName() == null ? "" : sh.getSheetName().replace(" ", ""); |
| | | if (norm.contains(period.substring(0, 4))) score += 100000; |
| | | } |
| | | if (score > bestScore) { |
| | | best = sh; |
| | | bestCol = col; |
| | |
| | | if (seq == null || seq.trim().isEmpty()) { |
| | | // 市州小节行(如 "武汉" "天 门"),更新当前市州 |
| | | String c = investmentCity(nt); |
| | | if (c != null) currentCity = c; |
| | | if (c != null) { currentCity = c; continue; } |
| | | // 序号为空的真实项目行(部分市州月报不填序号,如神农架物流园)也按项目处理 |
| | | if (!isLikelyInvestProjectName(nt)) continue; |
| | | } else if (!seq.trim().matches("\\d+(\\.\\d+)?")) { |
| | | continue; |
| | | } |
| | | if (!seq.trim().matches("\\d+(\\.\\d+)?")) continue; |
| | | } else { |
| | | // 无序号列(随州等):项目名非空即项目行,市州小节行用 investmentCity 识别 |
| | | String c = investmentCity(nt); |
| | |
| | | if (nt.length() > 4) count++; |
| | | } else { |
| | | String seq = cellStr(row, seqCol); |
| | | if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) count++; |
| | | if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) { |
| | | count++; |
| | | } else if ((seq == null || seq.trim().isEmpty()) && isLikelyInvestProjectName(nt)) { |
| | | count++; |
| | | } |
| | | } |
| | | } |
| | | return count; |
| | |
| | | log.error("load five year project names error", e); |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | /** 序号为空的非市州行是否像项目行:长度>4 且非备注/说明等杂项 */ |
| | | private boolean isLikelyInvestProjectName(String nt) { |
| | | if (nt == null || nt.length() <= 4) return false; |
| | | if (investmentCity(nt) != null) return false; |
| | | return !nt.startsWith("备注") && !nt.startsWith("说明") && !nt.startsWith("注:") |
| | | && !nt.startsWith("填表") && !nt.startsWith("审核") && !nt.startsWith("统计") |
| | | && !nt.startsWith("负责"); |
| | | } |
| | | |
| | | /** 项目名规范化:去空白/换行/括号内容/标点/「项目」后缀,用于十五五清单匹配 */ |
| | |
| | | } |
| | | } |
| | | /** 投资系统导出导入(审核比对基准),固定8列:行号/单位/时期/计划总投资/自开始累计/本年计划/自年初累计/当月完成 */ |
| | | private static final int[] INVEST_SYSTEM_HEADER_COLS = {0, 1, 2, 3, 4, 5, 6, 7}; |
| | | private static final String[] INVEST_SYSTEM_HEADER_KEYWORDS = |
| | | {"行号", "单位", "时期", "计划总投资", "自开始建设", "本年计划投资", "自年初", "当月完成投资"}; |
| | | |
| | | public int importInvestmentSystem(MultipartFile file, String period) throws Exception { |
| | | List<InvestmentSystem> list = new ArrayList<>(); |
| | | int failRows = 0; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | requireHeaderKeyword(sheet, "投资系统导出", "项目名称", "计划总投资", "单位"); |
| | | requireHeaderLayout(sheet, "投资系统导出", "模板_查询结果(投资系统-客运+物流 2026.7).xls", |
| | | INVEST_SYSTEM_HEADER_COLS, INVEST_SYSTEM_HEADER_KEYWORDS); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | |
| | | } |
| | | |
| | | /** 城市公交月报导入(城市公共交通月度运营情况,企业级明细,取公共汽电车字段) */ |
| | | /** 城市公交(U203)模板关键列位(0-based):所属地区/企业代码/企业名称/运营车数/客运量/轨道客运量/轮渡客运量/平均运距/填表人/联系电话/报出日期 */ |
| | | private static final int[] CITY_BUS_HEADER_COLS = {0, 1, 2, 5, 8, 13, 17, 19, 24, 25, 26}; |
| | | private static final String[] CITY_BUS_HEADER_KEYWORDS = |
| | | {"所属地区", "企业代码", "企业名称", "公共汽电车_运营车数", "公共汽电车_客运量", "轨道交通_客运量", |
| | | "城市客运轮渡_客运量", "公共汽电车平均运距", "填表人", "联系电话", "报出日期"}; |
| | | |
| | | public ImportResult importCityBus(MultipartFile file, String period) throws Exception { |
| | | List<CityBusRow> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Row header = sheet.getRow(0); |
| | | if (header == null || getString(header, 2) == null |
| | | || !getString(header, 2).contains("企业名称")) { |
| | | throw new RuntimeException("未找到表头行(需包含 企业名称 列)"); |
| | | } |
| | | requireHeaderLayout(sheet, "城市公交", "模板_交企统U203表_城市公共交通月度运营情况(2026年7月).xls", |
| | | CITY_BUS_HEADER_COLS, CITY_BUS_HEADER_KEYWORDS); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | |
| | | } |
| | | |
| | | /** 巡游出租月报导入(市州级,17 行;含核实说明/上报说明/修改记录) */ |
| | | /** 巡游出租(U402)模板关键列位(0-based):所属地区/企业代码/企业名称/载客车次总数/客运量/旅客周转量/运营车辆数/平均运距/填表人/联系电话/报出日期 */ |
| | | private static final int[] CITY_TAXI_HEADER_COLS = {0, 1, 2, 4, 6, 8, 10, 11, 15, 16, 17}; |
| | | private static final String[] CITY_TAXI_HEADER_KEYWORDS = |
| | | {"所属地区", "企业代码", "企业名称", "载客车次总数", "客运量", "旅客周转量", "运营车辆数", |
| | | "巡游出租汽车平均运距", "填表人", "联系电话", "报出日期"}; |
| | | |
| | | public ImportResult importCityTaxi(MultipartFile file, String period) throws Exception { |
| | | List<TaxiRow> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Row header = sheet.getRow(0); |
| | | if (header == null || getString(header, 2) == null |
| | | || !getString(header, 2).contains("企业名称")) { |
| | | throw new RuntimeException("未找到表头行(需包含 企业名称 列)"); |
| | | } |
| | | requireHeaderLayout(sheet, "巡游出租", "模板_交行统U402表-巡游出租汽车运营服务情况月报(2026).xls", |
| | | CITY_TAXI_HEADER_COLS, CITY_TAXI_HEADER_KEYWORDS); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |