| | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.auditengine.entity.AuditResult; |
| | | import com.trafficaudit.auditengine.entity.AuditRun; |
| | | import com.trafficaudit.auditengine.mapper.AuditResultMapper; |
| | | import com.trafficaudit.auditengine.mapper.AuditRunMapper; |
| | | import com.trafficaudit.common.util.RegionUtil; |
| | | import com.trafficaudit.dataimport.entity.EnergyAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly; |
| | |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.usermodel.WorkbookFactory; |
| | | import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| | | import org.apache.poi.xwpf.usermodel.XWPFParagraph; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTable; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTableRow; |
| | | import cn.hutool.core.io.IoUtil; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | private FreightTurnoverImportMapper freightTurnoverMapper; |
| | | @Resource |
| | | private AuditResultMapper auditResultMapper; |
| | | @Resource |
| | | private AuditRunMapper auditRunMapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | |
| | | } |
| | | } |
| | | |
| | | /** 公路旅客运政车辆数(独立模板:企业代码/企业名称/车辆数/载客位数) */ |
| | | /** 公路旅客运政车辆数(兼容两种表头:① 企业代码/企业名称/车辆数/载客位数;② 运政库导出 OWNERNAME/总车辆数/客位数,无企业代码列) */ |
| | | public ImportResult importPassengerAuth(MultipartFile file, String period) throws Exception { |
| | | List<PassengerAuthVehicle> authList = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | requireHeaderCols(sheet, "旅客运政车辆数", "企业名称", "车辆数", "载客位数"); |
| | | Map<String, Integer> colMap = buildHeaderMapCompat(sheet.getRow(0)); |
| | | Integer nameCol = firstColIdx(colMap, "企业名称", "OWNERNAME"); |
| | | Integer vehicleCol = firstColIdx(colMap, "车辆数", "总车辆数"); |
| | | Integer seatCol = firstColIdx(colMap, "载客位数", "客位数"); |
| | | Integer codeCol = firstColIdx(colMap, "企业代码"); |
| | | if (nameCol == null || vehicleCol == null || seatCol == null) { |
| | | throw new RuntimeException("所选数据类型【旅客运政车辆数】与文件内容不符:表头需含【企业名称/车辆数/载客位数】或【OWNERNAME/总车辆数/客位数】,请确认是否选错了数据类型或使用了旧模板"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = gs(row, colMap, "企业名称"); |
| | | String name = getString(row, nameCol); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | PassengerAuthVehicle auth = new PassengerAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setEnterpriseCode(gs(row, colMap, "企业代码")); |
| | | auth.setEnterpriseCode(codeCol == null ? null : getString(row, codeCol)); |
| | | auth.setEnterpriseName(name.trim()); |
| | | auth.setVehicleCount(gi(row, colMap, "车辆数")); |
| | | auth.setSeatCount(gi(row, colMap, "载客位数")); |
| | | auth.setVehicleCount(getInt(row, vehicleCol)); |
| | | auth.setSeatCount(getInt(row, seatCol)); |
| | | authList.add(auth); |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | List<ScaleSplitTransport> list = new ArrayList<>(); |
| | | int failRows = 0; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | int targetMonth = Integer.parseInt(period.split("-")[1]); // 仅导入目标期所在月的页签(1-N月文件按目标期取当月/累计) |
| | | boolean hasPeriodSheet = false; |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | String sn = wb.getSheetAt(i).getSheetName(); |
| | |
| | | Sheet sheet = wb.getSheetAt(i); |
| | | String sheetName = sheet.getSheetName(); |
| | | String periodType = null; |
| | | if (sheetName.contains("当月") || sheetName.contains("月份")) periodType = "MONTH"; |
| | | else if (sheetName.contains("累计")) periodType = "CUMULATIVE"; |
| | | java.util.regex.Matcher mSheetMonth = java.util.regex.Pattern.compile("(\\d{1,2})\\u6708(\\u4efd|\\u7d2f\\u8ba1)").matcher(sheetName); |
| | | if (mSheetMonth.find()) { |
| | | int sheetMonth = Integer.parseInt(mSheetMonth.group(1)); |
| | | if (sheetMonth == targetMonth) { |
| | | periodType = "份".equals(mSheetMonth.group(2)) ? "MONTH" : "CUMULATIVE"; |
| | | } |
| | | } else { |
| | | if (sheetName.contains("当月")) periodType = "MONTH"; |
| | | else if (sheetName.contains("累计")) periodType = "CUMULATIVE"; |
| | | } |
| | | if (periodType == null) continue; |
| | | |
| | | // 校验 sheet 布局:第2行应含「市州/规上企业运输量」,第3行子表头应含「周转量」或「货运量」(扩展布局) |
| | |
| | | } |
| | | } |
| | | |
| | | /** 支持“按类型清空”的数据类型 -> 数据表操作键(同表同类别归并,避免重复删除) */ |
| | | private static final Map<String, String> CLEAR_TYPE_TO_OP = new LinkedHashMap<>(); |
| | | /** 数据类型 -> 关联审核规则类型(同步清除审核结果与已审核标记) */ |
| | | private static final Map<String, String[]> CLEAR_TYPE_TO_AUDIT = new HashMap<>(); |
| | | /** 数据类型 -> 关联导入批次 importType(同步清除导入批次记录) */ |
| | | private static final Map<String, String[]> CLEAR_TYPE_TO_BATCH = new HashMap<>(); |
| | | |
| | | static { |
| | | CLEAR_TYPE_TO_OP.put("h2032", "H2032"); |
| | | CLEAR_TYPE_TO_OP.put("transportAuth", "TRANSPORT_AUTH"); |
| | | CLEAR_TYPE_TO_OP.put("trackMileage", "TRACK_MILEAGE"); |
| | | CLEAR_TYPE_TO_OP.put("scaleSplit", "SCALE_SPLIT"); |
| | | CLEAR_TYPE_TO_OP.put("freightTurnover", "FREIGHT_TURNOVER"); |
| | | CLEAR_TYPE_TO_OP.put("passengerMonthly", "H2031"); |
| | | CLEAR_TYPE_TO_OP.put("passengerAuth", "PASSENGER_AUTH"); |
| | | CLEAR_TYPE_TO_OP.put("passengerIndividual", "PASSENGER_INDIVIDUAL"); |
| | | CLEAR_TYPE_TO_OP.put("energyMonthly", "H204"); |
| | | CLEAR_TYPE_TO_OP.put("energyAuth", "ENERGY_AUTH"); |
| | | CLEAR_TYPE_TO_OP.put("investment", "INVEST_MONTHLY_PASSENGER"); |
| | | CLEAR_TYPE_TO_OP.put("investCityPassenger", "INVEST_MONTHLY_PASSENGER"); |
| | | CLEAR_TYPE_TO_OP.put("investmentLogistics", "INVEST_MONTHLY_LOGISTICS"); |
| | | CLEAR_TYPE_TO_OP.put("investCityLogistics", "INVEST_MONTHLY_LOGISTICS"); |
| | | CLEAR_TYPE_TO_OP.put("investmentSystem", "INVEST_SYSTEM"); |
| | | CLEAR_TYPE_TO_OP.put("cityBus", "CITY_BUS"); |
| | | CLEAR_TYPE_TO_OP.put("cityTaxi", "CITY_TAXI"); |
| | | CLEAR_TYPE_TO_OP.put("cityTaxiAuth", "CITY_TAXI_AUTH"); |
| | | CLEAR_TYPE_TO_OP.put("wycTotal", "WYC_TOTAL"); |
| | | CLEAR_TYPE_TO_OP.put("wycOrder", "WYC_ORDER"); |
| | | CLEAR_TYPE_TO_OP.put("wycOrderReport", "WYC_ORDER"); |
| | | |
| | | CLEAR_TYPE_TO_AUDIT.put("h2032", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("transportAuth", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("trackMileage", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("scaleSplit", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("freightTurnover", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("passengerMonthly", new String[]{"H2031"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("passengerAuth", new String[]{"H2031"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("passengerIndividual", new String[]{"H2031"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("energyMonthly", new String[]{"H204"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("energyAuth", new String[]{"H204"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("investment", new String[]{"INVEST"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("investmentLogistics", new String[]{"INVEST"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("investCityPassenger", new String[]{"INVEST"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("investCityLogistics", new String[]{"INVEST"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("investmentSystem", new String[]{"INVEST"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("cityBus", new String[]{"CITY_BUS"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("cityTaxi", new String[]{"CITY_TAXI"}); |
| | | CLEAR_TYPE_TO_AUDIT.put("cityTaxiAuth", new String[]{"CITY_TAXI"}); |
| | | |
| | | String[] investPassengerBatch = {"INVEST_客运站场", "INVEST_客运站场_CITY"}; |
| | | String[] investLogisticsBatch = {"INVEST_物流园区", "INVEST_物流园区_CITY"}; |
| | | CLEAR_TYPE_TO_BATCH.put("h2032", new String[]{"H2032"}); |
| | | CLEAR_TYPE_TO_BATCH.put("transportAuth", new String[]{"TRANSPORT_AUTH"}); |
| | | CLEAR_TYPE_TO_BATCH.put("trackMileage", new String[]{"TRACK_MILEAGE"}); |
| | | CLEAR_TYPE_TO_BATCH.put("scaleSplit", new String[]{"SCALE_SPLIT"}); |
| | | CLEAR_TYPE_TO_BATCH.put("freightTurnover", new String[]{"FREIGHT_TURNOVER"}); |
| | | CLEAR_TYPE_TO_BATCH.put("passengerMonthly", new String[]{"H2031"}); |
| | | CLEAR_TYPE_TO_BATCH.put("passengerAuth", new String[]{"PASSENGER_AUTH"}); |
| | | CLEAR_TYPE_TO_BATCH.put("passengerIndividual", new String[]{"PASSENGER_INDIVIDUAL"}); |
| | | CLEAR_TYPE_TO_BATCH.put("energyMonthly", new String[]{"H204"}); |
| | | CLEAR_TYPE_TO_BATCH.put("energyAuth", new String[]{"ENERGY_AUTH"}); |
| | | CLEAR_TYPE_TO_BATCH.put("investment", investPassengerBatch); |
| | | CLEAR_TYPE_TO_BATCH.put("investCityPassenger", investPassengerBatch); |
| | | CLEAR_TYPE_TO_BATCH.put("investmentLogistics", investLogisticsBatch); |
| | | CLEAR_TYPE_TO_BATCH.put("investCityLogistics", investLogisticsBatch); |
| | | CLEAR_TYPE_TO_BATCH.put("investmentSystem", new String[]{"INVEST_SYSTEM"}); |
| | | CLEAR_TYPE_TO_BATCH.put("cityBus", new String[]{"CITY_BUS"}); |
| | | CLEAR_TYPE_TO_BATCH.put("cityTaxi", new String[]{"CITY_TAXI"}); |
| | | CLEAR_TYPE_TO_BATCH.put("cityTaxiAuth", new String[]{"CITY_TAXI_AUTH"}); |
| | | CLEAR_TYPE_TO_BATCH.put("wycTotal", new String[]{"WYC_TOTAL"}); |
| | | CLEAR_TYPE_TO_BATCH.put("wycOrder", new String[]{"WYC_ORDER", "WYC_ORDER_REPORT"}); |
| | | CLEAR_TYPE_TO_BATCH.put("wycOrderReport", new String[]{"WYC_ORDER_REPORT", "WYC_ORDER"}); |
| | | } |
| | | |
| | | /** |
| | | * 清空指定报表期已导入数据与审核结果,用于整月重导;投资项目主档保留。 |
| | | * |
| | | * @param types 需清空的数据类型(前端类型键,如 h2032/cityBus);为空则清空该报表期全部类型 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> clearPeriod(String period, List<String> types) { |
| | | if (period == null || !period.matches("\\d{4}-\\d{2}")) { |
| | | throw new IllegalArgumentException("报表期格式不正确,应为 yyyy-MM"); |
| | | } |
| | | Set<String> selected = new LinkedHashSet<>(); |
| | | if (types != null) { |
| | | for (String t : types) { |
| | | if (t != null && !t.trim().isEmpty()) selected.add(t.trim()); |
| | | } |
| | | } |
| | | boolean clearAll = selected.isEmpty(); |
| | | int dataRows = 0; |
| | | int auditRows = 0; |
| | | int auditRunRows = 0; |
| | | int batchRows = 0; |
| | | if (clearAll) { |
| | | dataRows += h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, period)); |
| | | dataRows += transportAuthMapper.delete(new LambdaQueryWrapper<TransportAuthVehicle>() |
| | | .eq(TransportAuthVehicle::getReportPeriod, period)); |
| | | dataRows += trackMileageMapper.delete(new LambdaQueryWrapper<VehicleTrackMileage>() |
| | | .eq(VehicleTrackMileage::getReportPeriod, period)); |
| | | dataRows += scaleSplitMapper.delete(new LambdaQueryWrapper<ScaleSplitTransport>() |
| | | .eq(ScaleSplitTransport::getReportPeriod, period)); |
| | | dataRows += freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>() |
| | | .eq(FreightTurnoverImport::getReportPeriod, period)); |
| | | dataRows += passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, period)); |
| | | dataRows += passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>() |
| | | .eq(PassengerIndividualMonthly::getReportPeriod, period)); |
| | | dataRows += passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, period)); |
| | | dataRows += energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, period)); |
| | | dataRows += energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, period)); |
| | | dataRows += investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period)); |
| | | dataRows += investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, period)); |
| | | dataRows += cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, period)); |
| | | dataRows += cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, period)); |
| | | dataRows += cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(CityTaxiAuth::getReportPeriod, period)); |
| | | dataRows += wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>() |
| | | .eq(WycTotalMonthly::getReportPeriod, period)); |
| | | dataRows += wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>() |
| | | .eq(WycOrderMonthly::getReportPeriod, period)); |
| | | auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period)); |
| | | auditRunRows = auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>() |
| | | .eq(AuditRun::getReportPeriod, period)); |
| | | batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>() |
| | | .eq(ImportBatch::getReportPeriod, period)); |
| | | } else { |
| | | Set<String> ops = new LinkedHashSet<>(); |
| | | Set<String> auditTypes = new LinkedHashSet<>(); |
| | | Set<String> batchTypes = new LinkedHashSet<>(); |
| | | for (String t : selected) { |
| | | String op = CLEAR_TYPE_TO_OP.get(t); |
| | | if (op == null) { |
| | | log.warn("清空数据:未知数据类型 {},已忽略", t); |
| | | continue; |
| | | } |
| | | ops.add(op); |
| | | String[] at = CLEAR_TYPE_TO_AUDIT.get(t); |
| | | if (at != null) java.util.Collections.addAll(auditTypes, at); |
| | | String[] bt = CLEAR_TYPE_TO_BATCH.get(t); |
| | | if (bt != null) java.util.Collections.addAll(batchTypes, bt); |
| | | } |
| | | if (ops.isEmpty()) { |
| | | throw new IllegalArgumentException("未指定有效的数据类型"); |
| | | } |
| | | for (String op : ops) { |
| | | dataRows += clearTypeData(op, period); |
| | | } |
| | | if (!auditTypes.isEmpty()) { |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if (rule.getReportType() != null && auditTypes.contains(rule.getReportType())) { |
| | | ruleIds.add(rule.getId()); |
| | | } |
| | | } |
| | | if (!ruleIds.isEmpty()) { |
| | | auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | for (String rt : auditTypes) { |
| | | auditRunRows += auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>() |
| | | .eq(AuditRun::getReportPeriod, period) |
| | | .eq(AuditRun::getReportType, rt)); |
| | | } |
| | | } |
| | | if (!batchTypes.isEmpty()) { |
| | | batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>() |
| | | .eq(ImportBatch::getReportPeriod, period) |
| | | .in(ImportBatch::getImportType, batchTypes)); |
| | | } |
| | | } |
| | | Map<String, Object> stats = new LinkedHashMap<>(); |
| | | stats.put("period", period); |
| | | stats.put("scope", clearAll ? "all" : "types"); |
| | | if (!clearAll) stats.put("types", new ArrayList<>(selected)); |
| | | stats.put("dataRows", dataRows); |
| | | stats.put("auditRows", auditRows); |
| | | stats.put("auditRunRows", auditRunRows); |
| | | stats.put("batchRows", batchRows); |
| | | log.info("清空报表期 {}({})完成:业务数据 {} 行、审核结果 {} 行、审核标记 {} 行、导入批次 {} 行", |
| | | period, clearAll ? "全部类型" : String.join(",", selected), dataRows, auditRows, auditRunRows, batchRows); |
| | | return stats; |
| | | } |
| | | |
| | | /** 按“数据表操作键”清空对应业务表(投资月度按项目类别过滤),返回删除行数 */ |
| | | private int clearTypeData(String op, String period) { |
| | | switch (op) { |
| | | case "H2032": |
| | | return h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, period)); |
| | | case "TRANSPORT_AUTH": |
| | | return transportAuthMapper.delete(new LambdaQueryWrapper<TransportAuthVehicle>() |
| | | .eq(TransportAuthVehicle::getReportPeriod, period)); |
| | | case "TRACK_MILEAGE": |
| | | return trackMileageMapper.delete(new LambdaQueryWrapper<VehicleTrackMileage>() |
| | | .eq(VehicleTrackMileage::getReportPeriod, period)); |
| | | case "SCALE_SPLIT": |
| | | return scaleSplitMapper.delete(new LambdaQueryWrapper<ScaleSplitTransport>() |
| | | .eq(ScaleSplitTransport::getReportPeriod, period)); |
| | | case "FREIGHT_TURNOVER": |
| | | return freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>() |
| | | .eq(FreightTurnoverImport::getReportPeriod, period)); |
| | | case "H2031": |
| | | return passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, period)); |
| | | case "PASSENGER_AUTH": |
| | | return passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, period)); |
| | | case "PASSENGER_INDIVIDUAL": |
| | | return passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>() |
| | | .eq(PassengerIndividualMonthly::getReportPeriod, period)); |
| | | case "H204": |
| | | return energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, period)); |
| | | case "ENERGY_AUTH": |
| | | return energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, period)); |
| | | case "INVEST_MONTHLY_PASSENGER": |
| | | return deleteInvestmentMonthly(period, "客运站场"); |
| | | case "INVEST_MONTHLY_LOGISTICS": |
| | | return deleteInvestmentMonthly(period, "物流园区"); |
| | | case "INVEST_SYSTEM": |
| | | return investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, period)); |
| | | case "CITY_BUS": |
| | | return cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, period)); |
| | | case "CITY_TAXI": |
| | | return cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, period)); |
| | | case "CITY_TAXI_AUTH": |
| | | return cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(CityTaxiAuth::getReportPeriod, period)); |
| | | case "WYC_TOTAL": |
| | | return wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>() |
| | | .eq(WycTotalMonthly::getReportPeriod, period)); |
| | | case "WYC_ORDER": |
| | | return wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>() |
| | | .eq(WycOrderMonthly::getReportPeriod, period)); |
| | | default: |
| | | log.warn("清空数据:未知数据表操作 {}", op); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** 投资月度按项目类别清除(投资项目主档保留) */ |
| | | private int deleteInvestmentMonthly(String period, String category) { |
| | | List<Long> projectIds = new ArrayList<>(); |
| | | for (InvestmentProject p : investProjectMapper.selectList(new LambdaQueryWrapper<InvestmentProject>() |
| | | .eq(InvestmentProject::getCategory, category))) { |
| | | projectIds.add(p.getId()); |
| | | } |
| | | if (projectIds.isEmpty()) return 0; |
| | | return investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period) |
| | | .in(InvestmentMonthly::getProjectId, projectIds)); |
| | | } |
| | | |
| | | /** 数据变化后删除指定报表类型指定报表期的审核结果(需重新执行审核) */ |
| | | private void deleteRuleResults(String reportType, String period) { |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | |
| | | } |
| | | } |
| | | return colMap; |
| | | } |
| | | |
| | | /** 兼容表头映射:去引号/去空白;纯英文表头统一大写(如 'OWNERNAME' → OWNERNAME),便于新旧模板混用 */ |
| | | private Map<String, Integer> buildHeaderMapCompat(Row header) { |
| | | Map<String, Integer> colMap = new HashMap<>(); |
| | | if (header == null) return colMap; |
| | | for (Cell cell : header) { |
| | | if (cell == null) continue; |
| | | String h = FORMATTER.formatCellValue(cell).replace("'", "").replace("\"", "").trim(); |
| | | if (h.isEmpty()) continue; |
| | | boolean ascii = true; |
| | | for (int i = 0; i < h.length(); i++) { |
| | | if (h.charAt(i) > 127) { ascii = false; break; } |
| | | } |
| | | if (ascii) h = h.toUpperCase(); |
| | | if (!colMap.containsKey(h)) colMap.put(h, cell.getColumnIndex()); |
| | | } |
| | | return colMap; |
| | | } |
| | | |
| | | /** 按别名顺序取第一个存在的列号,全部缺失返回 null */ |
| | | private Integer firstColIdx(Map<String, Integer> colMap, String... aliases) { |
| | | for (String a : aliases) { |
| | | Integer idx = colMap.get(a); |
| | | if (idx != null) return idx; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private int colIdx(Map<String, Integer> colMap, String header) { |
| | |
| | | return ok; |
| | | } |
| | | |
| | | // ========== 网约车订单运行监测分析报告(docx)导入 ========== |
| | | |
| | | /** 网约车订单报告导入结果(报表期/订单合计/覆盖市州数/缺失市州) */ |
| | | public static class WycOrderReportResult { |
| | | private final String period; |
| | | private final double orderSum; |
| | | private final int cityCount; |
| | | private final List<String> missingCities; |
| | | |
| | | public WycOrderReportResult(String period, double orderSum, int cityCount, List<String> missingCities) { |
| | | this.period = period; |
| | | this.orderSum = orderSum; |
| | | this.cityCount = cityCount; |
| | | this.missingCities = missingCities; |
| | | } |
| | | |
| | | public String getPeriod() { return period; } |
| | | public double getOrderSum() { return orderSum; } |
| | | public int getCityCount() { return cityCount; } |
| | | public List<String> getMissingCities() { return missingCities; } |
| | | } |
| | | |
| | | /** |
| | | * 《网约车行业YYYY年M月运行监测情况分析报告》(docx)导入: |
| | | * 解析报告附件中「地市名称 | … | 订单数总数」表的 17 市州订单数,写入 wyc_order_monthly 的 |
| | | * F 订单合计与 G-W 17 市州订单列;全省 pin(B-E)沿用该期已有行,若无该期行则从 |
| | | * wyc_total_monthly 同报表期复制。报表期优先取报告标题年月,取不到时用页面所选报表期。 |
| | | */ |
| | | public synchronized WycOrderReportResult importWycOrderReport(MultipartFile file, String period) throws Exception { |
| | | Map<String, Double> orders = new LinkedHashMap<>(); |
| | | double[] sumHolder = new double[]{0.0}; |
| | | String docPeriod; |
| | | try (java.io.InputStream in = file.getInputStream(); |
| | | XWPFDocument doc = new XWPFDocument(in)) { |
| | | docPeriod = periodOfDocx(doc); |
| | | parseWycOrderReportTables(doc, orders, sumHolder); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("无法读取 Word 文档,请上传 .docx 格式的《网约车行业YYYY年M月运行监测情况分析报告》;" |
| | | + "若上传的其实是 Excel/PDF,请改用对应模板导入。(原始错误:" + e.getMessage() + ")"); |
| | | } |
| | | if (orders.isEmpty()) { |
| | | throw new RuntimeException("未解析到任何市州订单数,请确认上传的是《网约车行业YYYY年M月运行监测情况分析报告》"); |
| | | } |
| | | String rp = null; |
| | | if (docPeriod != null && docPeriod.matches("\\d{4}-\\d{2}")) rp = docPeriod; |
| | | else if (period != null && period.matches("\\d{4}-\\d{2}")) rp = period; |
| | | if (rp == null) { |
| | | throw new RuntimeException("无法确定报表期:报告标题未含「YYYY年M月」,且页面未选择报表期"); |
| | | } |
| | | |
| | | List<String> missing = new ArrayList<>(); |
| | | double citySum = 0.0; |
| | | for (String city : RegionUtil.cityList()) { |
| | | Double v = orders.get(city); |
| | | if (v == null) missing.add(city); |
| | | else citySum += v; |
| | | } |
| | | double sum = sumHolder[0] > 0 ? sumHolder[0] : citySum; |
| | | |
| | | WycOrderMonthly it = wycOrderMapper.selectOne(new LambdaQueryWrapper<WycOrderMonthly>() |
| | | .eq(WycOrderMonthly::getReportPeriod, rp).last("LIMIT 1")); |
| | | if (it == null) it = new WycOrderMonthly(); |
| | | it.setReportPeriod(rp); |
| | | it.setOrderSum(sum > 0 ? sum : null); |
| | | it.setOrderWuhan(orderOf(orders, "武汉市")); |
| | | it.setOrderHuangshi(orderOf(orders, "黄石市")); |
| | | it.setOrderShiyan(orderOf(orders, "十堰市")); |
| | | it.setOrderYichang(orderOf(orders, "宜昌市")); |
| | | it.setOrderXiangyang(orderOf(orders, "襄阳市")); |
| | | it.setOrderEzhou(orderOf(orders, "鄂州市")); |
| | | it.setOrderJingmen(orderOf(orders, "荆门市")); |
| | | it.setOrderXiaogan(orderOf(orders, "孝感市")); |
| | | it.setOrderJingzhou(orderOf(orders, "荆州市")); |
| | | it.setOrderHuanggang(orderOf(orders, "黄冈市")); |
| | | it.setOrderXianning(orderOf(orders, "咸宁市")); |
| | | it.setOrderSuizhou(orderOf(orders, "随州市")); |
| | | it.setOrderEnshi(orderOf(orders, "恩施州")); |
| | | it.setOrderXiantao(orderOf(orders, "仙桃市")); |
| | | it.setOrderQianjiang(orderOf(orders, "潜江市")); |
| | | it.setOrderTianmen(orderOf(orders, "天门市")); |
| | | it.setOrderShennong(orderOf(orders, "神农架林区")); |
| | | |
| | | if (it.getPinK() == null || it.getPinCK() == null || it.getPinZ() == null || it.getPinCZ() == null) { |
| | | WycTotalMonthly tot = wycTotalMapper.selectOne(new LambdaQueryWrapper<WycTotalMonthly>() |
| | | .eq(WycTotalMonthly::getReportPeriod, rp).last("LIMIT 1")); |
| | | if (tot != null) { |
| | | if (it.getPinK() == null) it.setPinK(tot.getPinK()); |
| | | if (it.getPinCK() == null) it.setPinCK(tot.getPinCK()); |
| | | if (it.getPinZ() == null) it.setPinZ(tot.getPinZ()); |
| | | if (it.getPinCZ() == null) it.setPinCZ(tot.getPinCZ()); |
| | | } |
| | | } |
| | | |
| | | wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>().eq(WycOrderMonthly::getReportPeriod, rp)); |
| | | wycOrderMapper.insert(it); |
| | | recordBatch(file.getOriginalFilename(), "WYC_ORDER_REPORT", rp, 1, 1, 0, null); |
| | | log.info("WycOrderReport imported: period={} sum={} cities={} missing={}", rp, sum, orders.size(), missing); |
| | | return new WycOrderReportResult(rp, sum, orders.size(), missing); |
| | | } |
| | | |
| | | /** 报告标题中的报表期:网约车行业2026年7月 -> 2026-07;取不到返回 null */ |
| | | private String periodOfDocx(XWPFDocument doc) { |
| | | java.util.regex.Pattern titlePat = java.util.regex.Pattern.compile("网约车行业\\s*(\\d{4})\\s*年\\s*(\\d{1,2})\\s*月"); |
| | | java.util.regex.Pattern anyPat = java.util.regex.Pattern.compile("(\\d{4})\\s*年\\s*(\\d{1,2})\\s*月"); |
| | | String fallback = null; |
| | | int scanned = 0; |
| | | for (XWPFParagraph para : doc.getParagraphs()) { |
| | | String text = para.getText(); |
| | | if (text == null) continue; |
| | | String flat = text.replaceAll("\\s+", ""); |
| | | if (flat.isEmpty()) continue; |
| | | java.util.regex.Matcher m = titlePat.matcher(flat); |
| | | if (m.find()) return ymd(m.group(1), m.group(2)); |
| | | if (fallback == null && scanned < 10) { |
| | | java.util.regex.Matcher m2 = anyPat.matcher(flat); |
| | | if (m2.find()) fallback = ymd(m2.group(1), m2.group(2)); |
| | | } |
| | | scanned++; |
| | | } |
| | | return fallback; |
| | | } |
| | | |
| | | private String ymd(String y, String mo) { |
| | | try { |
| | | int month = Integer.parseInt(mo); |
| | | if (month < 1 || month > 12) return null; |
| | | return String.format("%04d-%02d", Integer.parseInt(y), month); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** 解析报告中的「地市名称 | … | 订单数总数」表,orders=市州订单,sumHolder[0]=合计行订单数 */ |
| | | private void parseWycOrderReportTables(XWPFDocument doc, Map<String, Double> orders, double[] sumHolder) { |
| | | for (XWPFTable table : doc.getTables()) { |
| | | List<XWPFTableRow> rows = table.getRows(); |
| | | if (rows.size() < 3) continue; |
| | | int headerRow = -1; |
| | | int orderCol = -1; |
| | | for (int r = 0; r < Math.min(rows.size(), 3); r++) { |
| | | List<org.apache.poi.xwpf.usermodel.XWPFTableCell> cells = rows.get(r).getTableCells(); |
| | | for (int c = 0; c < cells.size(); c++) { |
| | | String norm = flat(cells.get(c).getText()); |
| | | if (norm.contains("订单数总数") || "订单数".equals(norm)) { |
| | | headerRow = r; |
| | | orderCol = c; |
| | | break; |
| | | } |
| | | } |
| | | if (headerRow >= 0) break; |
| | | } |
| | | if (headerRow < 0 || orderCol < 0) continue; |
| | | |
| | | Map<String, Double> found = new LinkedHashMap<>(); |
| | | double citySum = 0.0; |
| | | boolean hasTotal = false; |
| | | for (int r = headerRow + 1; r < rows.size(); r++) { |
| | | List<org.apache.poi.xwpf.usermodel.XWPFTableCell> cells = rows.get(r).getTableCells(); |
| | | if (orderCol >= cells.size()) continue; |
| | | String name = flat(cells.get(0).getText()); |
| | | if (name.isEmpty()) continue; |
| | | Double val = parsePlainNumber(cells.get(orderCol).getText()); |
| | | if (val == null) continue; |
| | | String city = RegionUtil.normalizeCityName(name); |
| | | if (city != null && RegionUtil.cityList().contains(city)) { |
| | | found.put(city, val); |
| | | citySum += val; |
| | | } else if (name.contains("合计") || name.contains("总计") || name.contains("全省") || name.contains("湖北")) { |
| | | hasTotal = true; |
| | | sumHolder[0] = val; |
| | | } |
| | | } |
| | | if (!found.isEmpty()) { |
| | | orders.clear(); |
| | | orders.putAll(found); |
| | | if (!hasTotal) sumHolder[0] = citySum; |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private String flat(String s) { |
| | | return s == null ? "" : s.replaceAll("\\s+", "").trim(); |
| | | } |
| | | |
| | | /** 「11,183,267 单」这类文本取数值 */ |
| | | private Double parsePlainNumber(String s) { |
| | | if (s == null) return null; |
| | | String t = s.replaceAll("[,\\s]", "").trim(); |
| | | if (t.isEmpty()) return null; |
| | | java.util.regex.Matcher m = java.util.regex.Pattern.compile("-?\\d+(?:\\.\\d+)?").matcher(t); |
| | | if (!m.find()) return null; |
| | | try { |
| | | return Double.parseDouble(m.group()); |
| | | } catch (NumberFormatException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Double orderOf(Map<String, Double> orders, String city) { |
| | | Double v = orders.get(city); |
| | | return (v == null || v <= 0) ? null : v; |
| | | } |
| | | private String periodOfCell(Cell c) { |
| | | if (c == null) return null; |
| | | if (c.getCellType() == CellType.NUMERIC) { |
| | |
| | | case "scaleSplit": return new ImportResult(importScaleSplit(file, period), 0, new ArrayList<>()); |
| | | case "wycTotal": return new ImportResult(importWycTotal(file, period), 0, new ArrayList<>()); |
| | | case "wycOrder": return new ImportResult(importWycOrder(file, period), 0, new ArrayList<>()); |
| | | case "wycOrderReport": return new ImportResult(importWycOrderReport(file, period).getCityCount(), 0, new ArrayList<>()); |
| | | default: throw new RuntimeException("不支持的导入类型:" + type); |
| | | } |
| | | } |