| | |
| | | .eq(InvestmentSystem::getReportPeriod, reportPeriod))) { |
| | | sysMap.computeIfAbsent(normInvestName(s.getProjectName()), k -> new ArrayList<>()).add(s); |
| | | } |
| | | // 同报表期投资系统基准整期缺失时,不做"项目缺失核实"(缺失=基准未导入,不等于项目不存在),避免整表误报 |
| | | boolean benchmarkMissing = sysMap.isEmpty(); |
| | | |
| | | // 幂等:只清除 INVEST 规则的旧审核结果 |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | |
| | | String projectName = project == null ? "未知项目" : project.getProjectName(); |
| | | InvestmentSystem sys = findInvestSystem(sysMap, projectName); |
| | | for (AuditRule rule : rules) { |
| | | if (benchmarkMissing && "EXISTENCE_SYS".equals(rule.getCompareType())) continue; |
| | | AuditResult result = checkInvestRule(rule, monthly, projectName, sys); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | |
| | | |
| | | public List<AuditResult> queryResults(String reportPeriod, String reportType) { |
| | | String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim(); |
| | | List<AuditRule> rules = ruleMapper.selectList(new LambdaQueryWrapper<AuditRule>() |
| | | .eq(AuditRule::getReportType, type)); |
| | | if (rules.isEmpty()) return new java.util.ArrayList<>(); |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds) |
| | | .orderByAsc(AuditResult::getId)); |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : ruleMapper.selectList(null)) { |
| | | if (type.equals(rule.getReportType())) ruleIds.add(rule.getId()); |
| | | } |
| | | List<AuditResult> filtered = new ArrayList<>(); |
| | | for (AuditResult result : results) { |
| | | if (ruleIds.contains(result.getRuleId())) filtered.add(result); |
| | | } |
| | | attachInfo(filtered); |
| | | return filtered; |
| | | attachInfo(results, type, reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | public void reviewResult(Long id, String status, String comment) { |
| | |
| | | resultMapper.updateById(result); |
| | | } |
| | | |
| | | private void attachInfo(List<AuditResult> results) { |
| | | private void attachInfo(List<AuditResult> results, String type, String period) { |
| | | if (results.isEmpty()) return; |
| | | Map<Long, AuditRule> ruleMap = new HashMap<>(); |
| | | for (AuditRule rule : ruleMapper.selectList(null)) ruleMap.put(rule.getId(), rule); |
| | | Map<Long, String> h2032Name = new HashMap<>(); |
| | | Map<Long, String> h2032Explain = new HashMap<>(); |
| | | Map<Long, String> passengerName = new HashMap<>(); |
| | | Map<Long, String> passengerExplain = new HashMap<>(); |
| | | Map<Long, String> energyName = new HashMap<>(); |
| | | Map<Long, String> investName = new HashMap<>(); |
| | | Map<Long, String> busName = new HashMap<>(); |
| | | Map<Long, String> busExplain = new HashMap<>(); |
| | | Map<Long, String> taxiName = new HashMap<>(); |
| | | Map<Long, String> taxiExplain = new HashMap<>(); |
| | | for (H2032EnterpriseMonthly r : h2032Mapper.selectList(null)) { |
| | | h2032Name.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | h2032Explain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | for (AuditRule rule : ruleMapper.selectList(new LambdaQueryWrapper<AuditRule>() |
| | | .eq(AuditRule::getReportType, type))) { |
| | | ruleMap.put(rule.getId(), rule); |
| | | } |
| | | for (PassengerEnterpriseMonthly r : passengerMapper.selectList(null)) { |
| | | passengerName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | passengerExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | for (EnergyVehicleQuarterly r : energyMapper.selectList(null)) { |
| | | energyName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | } |
| | | for (CityBusMonthly r : cityBusMapper.selectList(null)) { |
| | | busName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | busExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | for (CityTaxiMonthly r : cityTaxiMapper.selectList(null)) { |
| | | taxiName.putIfAbsent(r.getId(), r.getCity()); |
| | | taxiExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | Map<Long, Long> investProjectId = new HashMap<>(); |
| | | for (InvestmentMonthly r : investMonthlyMapper.selectList(null)) { |
| | | investProjectId.putIfAbsent(r.getId(), r.getProjectId()); |
| | | } |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | for (Map.Entry<Long, Long> e : investProjectId.entrySet()) { |
| | | if (e.getValue().equals(p.getId())) investName.putIfAbsent(e.getKey(), p.getProjectName()); |
| | | Map<Long, String> nameMap = new HashMap<>(); |
| | | Map<Long, String> explainMap = new HashMap<>(); |
| | | if ("INVEST".equals(type)) { |
| | | Map<Long, Long> monthlyProject = new HashMap<>(); |
| | | for (InvestmentMonthly m : investMonthlyMapper.selectList(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period))) { |
| | | monthlyProject.put(m.getId(), m.getProjectId()); |
| | | } |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | for (Map.Entry<Long, Long> e : monthlyProject.entrySet()) { |
| | | if (e.getValue() != null && e.getValue().equals(p.getId())) { |
| | | nameMap.putIfAbsent(e.getKey(), p.getProjectName()); |
| | | } |
| | | } |
| | | } |
| | | } else if ("H204".equals(type)) { |
| | | for (EnergyVehicleQuarterly m : energyMapper.selectList(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, period))) { |
| | | nameMap.putIfAbsent(m.getId(), m.getEnterpriseName()); |
| | | } |
| | | } else if ("H2031".equals(type)) { |
| | | for (PassengerEnterpriseMonthly m : passengerMapper.selectList(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, period))) { |
| | | nameMap.putIfAbsent(m.getId(), m.getEnterpriseName()); |
| | | explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation()); |
| | | } |
| | | } else if ("CITY_BUS".equals(type)) { |
| | | for (CityBusMonthly m : cityBusMapper.selectList(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, period))) { |
| | | nameMap.putIfAbsent(m.getId(), m.getEnterpriseName()); |
| | | explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation()); |
| | | } |
| | | } else if ("CITY_TAXI".equals(type)) { |
| | | for (CityTaxiMonthly m : cityTaxiMapper.selectList(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, period))) { |
| | | nameMap.putIfAbsent(m.getId(), m.getCity()); |
| | | explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation()); |
| | | } |
| | | } else { |
| | | for (H2032EnterpriseMonthly m : h2032Mapper.selectList(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, period))) { |
| | | nameMap.putIfAbsent(m.getId(), m.getEnterpriseName()); |
| | | explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation()); |
| | | } |
| | | } |
| | | for (AuditResult result : results) { |
| | |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | } |
| | | // 各报表表 id 各自独立自增,需按规则所属报表类型分表回填企业名称 |
| | | String type = rule == null ? null : rule.getReportType(); |
| | | Map<Long, String> nameMap = "INVEST".equals(type) ? investName |
| | | : "H204".equals(type) ? energyName |
| | | : "H2031".equals(type) ? passengerName |
| | | : "CITY_BUS".equals(type) ? busName |
| | | : "CITY_TAXI".equals(type) ? taxiName : h2032Name; |
| | | Map<Long, String> explainMap = "H2031".equals(type) ? passengerExplain |
| | | : "CITY_BUS".equals(type) ? busExplain |
| | | : "CITY_TAXI".equals(type) ? taxiExplain : h2032Explain; |
| | | String enterpriseName = nameMap.get(result.getReportId()); |
| | | if (enterpriseName == null) { |
| | | // 上报数据被重新导入后旧审核结果已失效,给出提示而非空白 |
| | | enterpriseName = "\uFF08\u539F\u6570\u636E\u5DF2\u91CD\u65B0\u5BFC\u5165\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u5BA1\u6838\uFF09"; |
| | | enterpriseName = "(原数据已重新导入,请重新执行审核)"; |
| | | } |
| | | result.setEnterpriseName(enterpriseName); |
| | | result.setVerifyExplanation(explainMap.get(result.getReportId())); |
| | | } |
| | | } |
| | | |
| | | /** 最近有数据的报表期(用于前端默认选中) */ |
| | | public String latestPeriod(String reportType) { |
| | | String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim(); |
| | | String max = null; |
| | | switch (type) { |
| | | case "H2031": |
| | | for (PassengerEnterpriseMonthly m : passengerMapper.selectList( |
| | | new LambdaQueryWrapper<PassengerEnterpriseMonthly>().select(PassengerEnterpriseMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(passengerMapper.selectObjs(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .select(PassengerEnterpriseMonthly::getReportPeriod) |
| | | .orderByDesc(PassengerEnterpriseMonthly::getReportPeriod).last("LIMIT 1"))); |
| | | case "H204": |
| | | for (EnergyVehicleQuarterly m : energyMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyVehicleQuarterly>().select(EnergyVehicleQuarterly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(energyMapper.selectObjs(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .select(EnergyVehicleQuarterly::getReportPeriod) |
| | | .orderByDesc(EnergyVehicleQuarterly::getReportPeriod).last("LIMIT 1"))); |
| | | case "INVEST": |
| | | for (InvestmentMonthly m : investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>().select(InvestmentMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(investMonthlyMapper.selectObjs(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .select(InvestmentMonthly::getReportPeriod) |
| | | .orderByDesc(InvestmentMonthly::getReportPeriod).last("LIMIT 1"))); |
| | | case "CITY_BUS": |
| | | for (CityBusMonthly m : cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>().select(CityBusMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(cityBusMapper.selectObjs(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .select(CityBusMonthly::getReportPeriod) |
| | | .orderByDesc(CityBusMonthly::getReportPeriod).last("LIMIT 1"))); |
| | | case "CITY_TAXI": |
| | | for (CityTaxiMonthly m : cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>().select(CityTaxiMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(cityTaxiMapper.selectObjs(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .select(CityTaxiMonthly::getReportPeriod) |
| | | .orderByDesc(CityTaxiMonthly::getReportPeriod).last("LIMIT 1"))); |
| | | default: |
| | | for (H2032EnterpriseMonthly m : h2032Mapper.selectList( |
| | | new LambdaQueryWrapper<H2032EnterpriseMonthly>().select(H2032EnterpriseMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | return firstOf(h2032Mapper.selectObjs(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .select(H2032EnterpriseMonthly::getReportPeriod) |
| | | .orderByDesc(H2032EnterpriseMonthly::getReportPeriod).last("LIMIT 1"))); |
| | | } |
| | | return max; |
| | | } |
| | | |
| | | private String firstOf(List<Object> objs) { |
| | | if (objs == null || objs.isEmpty() || objs.get(0) == null) return null; |
| | | String v = String.valueOf(objs.get(0)); |
| | | return v.trim().isEmpty() ? null : v.trim(); |
| | | } |
| | | |
| | | private String maxPeriod(String a, String b) { |
| | |
| | | return a; |
| | | } |
| | | |
| | | /** 审核结果导出 Excel(含企业名/规则名/状态/审核意见) */ |
| | | /** 审核结果导出 Excel(两 Sheet,按业务模板):Sheet1 审核结果(逐条明细);Sheet2 企业合并(按企业汇总 + 问题列) */ |
| | | public byte[] exportResults(String reportPeriod, String reportType) throws Exception { |
| | | return exportResults(reportPeriod, reportType, true, "all"); |
| | | } |
| | | |
| | | /** onlyVerify=true 时剔除“提醒型”规则(如 RULE_004 环比提醒),只保留核实型问题;status=all/pending/handled 与界面“状态”筛选一致 */ |
| | | public byte[] exportResults(String reportPeriod, String reportType, boolean onlyVerify, String status) throws Exception { |
| | | List<AuditResult> results = queryResults(reportPeriod, reportType); |
| | | java.util.List<AuditResult> filtered = new java.util.ArrayList<>(); |
| | | String st = status == null ? "all" : status.trim(); |
| | | for (AuditResult res : results) { |
| | | if (onlyVerify && isReminderRule(res.getRuleCode())) continue; |
| | | if ("pending".equals(st) && !"PENDING".equals(res.getStatus())) continue; |
| | | if ("handled".equals(st) && (res.getStatus() == null || "PENDING".equals(res.getStatus()))) continue; |
| | | filtered.add(res); |
| | | } |
| | | try (XSSFWorkbook wb = new XSSFWorkbook()) { |
| | | // ---------- Sheet1 审核结果(逐条,列与业务模板一致) ---------- |
| | | Sheet sheet = wb.createSheet("审核结果"); |
| | | String[] headers = {"报表期", "企业/市州", "规则编码", "审核规则", "等级", "实际值", "阈值/基准", "偏差说明", "状态", "审核意见"}; |
| | | Row head = sheet.createRow(0); |
| | | for (int i = 0; i < headers.length; i++) { |
| | | Cell cell = head.createCell(i); |
| | | cell.setCellValue(headers[i]); |
| | | org.apache.poi.ss.usermodel.CellStyle style = wb.createCellStyle(); |
| | | org.apache.poi.ss.usermodel.Font font = wb.createFont(); |
| | | font.setBold(true); |
| | | style.setFont(font); |
| | | cell.setCellStyle(style); |
| | | } |
| | | writeAuditHeader(sheet, headers); |
| | | int r = 1; |
| | | for (AuditResult res : results) { |
| | | for (AuditResult res : filtered) { |
| | | Row row = sheet.createRow(r++); |
| | | row.createCell(0).setCellValue(valueOf(res.getReportPeriod())); |
| | | row.createCell(1).setCellValue(valueOf(res.getEnterpriseName())); |
| | |
| | | for (int i = 0; i < widths.length; i++) { |
| | | sheet.setColumnWidth(i, widths[i] * 256); |
| | | } |
| | | |
| | | // ---------- Sheet2 企业合并(同一企业多条规则并排列出 + 问题汇总列) ---------- |
| | | Sheet ms = wb.createSheet("企业合并"); |
| | | String[] h2 = {"报表期", "企业/市州", "审核规则", "实际值", "阈值/基准", "偏差说明", "问题"}; |
| | | writeAuditHeader(ms, h2); |
| | | java.util.LinkedHashMap<String, java.util.List<AuditResult>> groups = new java.util.LinkedHashMap<>(); |
| | | for (AuditResult res : filtered) { |
| | | String key = valueOf(res.getEnterpriseName()); |
| | | if (key.isEmpty()) key = valueOf(res.getReportPeriod()) + "#" + valueOf(res.getActualValue()); |
| | | groups.computeIfAbsent(key, k -> new java.util.ArrayList<>()).add(res); |
| | | } |
| | | int rowCursor = 1; |
| | | for (java.util.Map.Entry<String, java.util.List<AuditResult>> e : groups.entrySet()) { |
| | | java.util.List<AuditResult> items = e.getValue(); |
| | | int groupStart = rowCursor; |
| | | StringBuilder problems = new StringBuilder(); |
| | | int seq = 0; |
| | | for (AuditResult it : items) { |
| | | Row row = ms.createRow(rowCursor++); |
| | | if (seq == 0) { |
| | | row.createCell(0).setCellValue(valueOf(it.getReportPeriod())); |
| | | row.createCell(1).setCellValue(valueOf(it.getEnterpriseName()).isEmpty() ? e.getKey() : valueOf(it.getEnterpriseName())); |
| | | } |
| | | row.createCell(2).setCellValue(valueOf(it.getRuleName())); |
| | | row.createCell(3).setCellValue(valueOf(it.getActualValue())); |
| | | row.createCell(4).setCellValue(valueOf(it.getThresholdValue())); |
| | | row.createCell(5).setCellValue(valueOf(it.getDeviation())); |
| | | seq++; |
| | | if (problems.length() > 0) problems.append("\n"); |
| | | problems.append(seq).append("、").append(issueSentence(it)); |
| | | } |
| | | if (rowCursor - groupStart > 1) { |
| | | ms.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(groupStart, rowCursor - 1, 1, 1)); |
| | | ms.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(groupStart, rowCursor - 1, 6, 6)); |
| | | } |
| | | Row first = ms.getRow(groupStart); |
| | | if (first.getCell(6) == null) first.createCell(6); |
| | | first.getCell(6).setCellValue(problems.toString()); |
| | | } |
| | | int[] widths2 = {12, 30, 22, 34, 22, 34, 60}; |
| | | for (int i = 0; i < widths2.length; i++) { |
| | | ms.setColumnWidth(i, widths2[i] * 256); |
| | | } |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | wb.write(out); |
| | | return out.toByteArray(); |
| | | } |
| | | } |
| | | |
| | | private void writeAuditHeader(Sheet sheet, String[] headers) { |
| | | Row head = sheet.createRow(0); |
| | | for (int i = 0; i < headers.length; i++) { |
| | | Cell cell = head.createCell(i); |
| | | cell.setCellValue(headers[i]); |
| | | org.apache.poi.ss.usermodel.CellStyle style = sheet.getWorkbook().createCellStyle(); |
| | | org.apache.poi.ss.usermodel.Font font = sheet.getWorkbook().createFont(); |
| | | font.setBold(true); |
| | | style.setFont(font); |
| | | cell.setCellStyle(style); |
| | | } |
| | | } |
| | | |
| | | /** 提醒型规则:仅提示变化、不需人工核实(如 RULE_004 货运类型环比提醒) */ |
| | | private boolean isReminderRule(String ruleCode) { |
| | | return "RULE_004".equals(ruleCode); |
| | | } |
| | | |
| | | /** 企业合并“问题”列话术:按规则类型生成可读的核实意见 */ |
| | | private String issueSentence(AuditResult res) { |
| | | String code = res.getRuleCode(); |
| | | String name = valueOf(res.getRuleName()); |
| | | String act = valueOf(res.getActualValue()); |
| | | if ("RULE_001".equals(code) || "RULE_002".equals(code)) { |
| | | return "企业上报车辆/吨位与运政数据对比存在差异(" + act + "),请核实"; |
| | | } |
| | | if ("RULE_003".equals(code)) { |
| | | return "上报周转量与轨迹测算周转量差异明显(" + act + "),请参考轨迹核实企业周转量"; |
| | | } |
| | | if ("RULE_004".equals(code)) { |
| | | return "货运类型环比发生变化" + (act.isEmpty() ? "" : "(" + act + ")") + ",请关注是否填报正常"; |
| | | } |
| | | if ("RULE_005".equals(code)) { |
| | | return "无集装箱车但存在集装箱货运量(" + act + "),请核实车辆类型与货运量填报"; |
| | | } |
| | | if ("RULE_006".equals(code)) { |
| | | return "整车平均吨位超出合理范围(" + act + "),请核实平均吨位填报"; |
| | | } |
| | | if (code != null && code.startsWith("RULE_P")) { |
| | | String special = passengerIssueSentence(res); |
| | | if (special != null) return special; |
| | | } |
| | | return name + "异常(" + act + "),请核实"; |
| | | } |
| | | |
| | | /** 客运(H2031)规则问题话术:贴合模板示例“核实异常(上期指标=,本期指标=)请核实” */ |
| | | private String passengerIssueSentence(AuditResult res) { |
| | | String code = res.getRuleCode(); |
| | | String act = valueOf(res.getActualValue()); |
| | | if ("RULE_P003".equals(code) || "RULE_P004".equals(code)) { |
| | | boolean turnover = "RULE_P004".equals(code); |
| | | String inner = act; |
| | | if (inner.startsWith("本月与上月相同: ")) { |
| | | inner = inner.substring("本月与上月相同: ".length()); |
| | | } |
| | | java.util.List<String> clauses = new java.util.ArrayList<>(); |
| | | for (String item : inner.split("、")) { |
| | | int eq = item.indexOf('='); |
| | | if (eq <= 0) { clauses.add(item); continue; } |
| | | String label = item.substring(0, eq).trim(); |
| | | String v = item.substring(eq + 1).trim(); |
| | | clauses.add("上月" + (turnover ? "周转量" : "客运量") + "=" + v + ",本月" + label + "=" + v); |
| | | } |
| | | return valueOf(res.getRuleName()) + "异常(" + String.join(";", clauses) + ")请核实"; |
| | | } |
| | | if ("RULE_P005".equals(code)) { |
| | | String pct = extractPct(valueOf(res.getThresholdValue())); |
| | | String prefix = pct.isEmpty() ? "班线平均运距环比超限" : "班线平均运距环比超±" + pct + "%"; |
| | | return prefix + ",正常班线运距在线路未发生重大变化前较为稳定(" + act + "),请核实"; |
| | | } |
| | | if ("RULE_P006".equals(code)) { |
| | | String inner = act; |
| | | if (inner.startsWith("等于上月: ")) { |
| | | inner = inner.substring("等于上月: ".length()); |
| | | } |
| | | java.util.List<String> clauses = new java.util.ArrayList<>(); |
| | | for (String item : inner.split("、")) { |
| | | int eq = item.indexOf('='); |
| | | if (eq <= 0) { clauses.add(item); continue; } |
| | | String v = item.substring(eq + 1).trim(); |
| | | clauses.add("上月运距=" + v + ",本月运距=" + v); |
| | | } |
| | | return "包车平均运距等于上月(" + String.join(";", clauses) |
| | | + ")请核实企业是否是固定线路旅游或者通勤业务"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String extractPct(String thresholdText) { |
| | | if (thresholdText == null || thresholdText.isEmpty()) return ""; |
| | | int idx = thresholdText.indexOf('\u00b1'); |
| | | if (idx < 0) return ""; |
| | | StringBuilder num = new StringBuilder(); |
| | | for (int i = idx + 1; i < thresholdText.length(); i++) { |
| | | char c = thresholdText.charAt(i); |
| | | if (Character.isDigit(c) || c == '.') num.append(c); |
| | | else if (!Character.isWhitespace(c)) break; |
| | | } |
| | | return num.toString(); |
| | | } |
| | | |
| | | private String auditStatusCn(String status) { |
| | |
| | | case "RULE_I001": |
| | | case "RULE_I002": |
| | | case "RULE_I003": |
| | | case "RULE_I004": |
| | | return investSystemMapper.selectCount(new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, period)) == 0; |
| | | default: |
| | |
| | | switch (rule.getCompareType()) { |
| | | case "DIFF": |
| | | return checkDiff(rule, report, authMap); |
| | | case "DIFF_FIELDS": |
| | | return checkDiffFields(rule, report, authMap); |
| | | case "RATIO": |
| | | return checkRatio(rule, report, authMap, trackMap); |
| | | case "MOM": |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** RULE_001 分项运政对比:牵引车/挂车/整车车辆数与挂车吨位/整车吨位/总吨位分别与运政对比,任一分项超限即提醒 */ |
| | | private AuditResult checkDiffFields(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, TransportAuthVehicle> authMap) { |
| | | TransportAuthVehicle auth = findAuth(authMap, report.getEnterpriseName()); |
| | | if (auth == null) return null; |
| | | String[] fields = rule.getCheckField() == null ? new String[0] : rule.getCheckField().split(","); |
| | | String[] limits = rule.getThreshold() == null ? new String[0] : rule.getThreshold().split("[,;|]"); |
| | | if (fields.length == 0 || limits.length != fields.length) return null; |
| | | java.util.List<String> hits = new java.util.ArrayList<>(); |
| | | double maxAbs = 0; |
| | | for (int i = 0; i < fields.length; i++) { |
| | | String field = fields[i].trim(); |
| | | double limit; |
| | | try { |
| | | limit = Double.parseDouble(limits[i].trim()); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | double actual; |
| | | double reference; |
| | | String unit; |
| | | switch (field) { |
| | | case "vehicleTractor": |
| | | actual = num(report.getVehicleTractor()); |
| | | reference = num(auth.getTractorCount()); |
| | | unit = "台"; |
| | | break; |
| | | case "vehicleTrailer": |
| | | actual = num(report.getVehicleTrailer()); |
| | | reference = num(auth.getTrailerCount()); |
| | | unit = "台"; |
| | | break; |
| | | case "tonsTrailer": |
| | | actual = nvl(report.getTonsTrailer()); |
| | | reference = nvl(auth.getTrailerTons()); |
| | | unit = "吨"; |
| | | break; |
| | | case "vehicleWhole": |
| | | actual = num(report.getVehicleWhole()); |
| | | reference = num(auth.getOtherCount()); |
| | | unit = "台"; |
| | | break; |
| | | case "tonsWhole": |
| | | actual = nvl(report.getTonsWhole()); |
| | | reference = nvl(auth.getOtherTons()); |
| | | unit = "吨"; |
| | | break; |
| | | case "tonsTotal": |
| | | actual = nvl(report.getTonsTotal()); |
| | | reference = nvl(auth.getTrailerTons()) + nvl(auth.getOtherTons()); |
| | | unit = "吨"; |
| | | break; |
| | | default: |
| | | return null; |
| | | } |
| | | double diff = Math.abs(actual - reference); |
| | | maxAbs = Math.max(maxAbs, diff); |
| | | if (diff > limit) { |
| | | hits.add(h2032FieldLabel(field) + ":上报=" + fmt(actual) + " " + unit + ",运政=" + fmt(reference) + " " + unit |
| | | + ",差" + fmt(diff) + " " + unit + "(限" + fmt(limit) + " " + unit + ")"); |
| | | } |
| | | } |
| | | if (hits.isEmpty()) return null; |
| | | StringBuilder detail = new StringBuilder(); |
| | | for (int i = 0; i < hits.size(); i++) { |
| | | String one = hits.get(i); |
| | | if (detail.length() == 0) { |
| | | detail.append(one); |
| | | } else if (detail.length() + one.length() + 1 <= 180) { |
| | | detail.append(";").append(one); |
| | | } else { |
| | | detail.append(";等").append(hits.size() - i).append("项超限"); |
| | | break; |
| | | } |
| | | } |
| | | return buildResult(rule, report, detail.toString(), |
| | | "牵引车/挂车/整车车辆差≤5台;挂车吨位/整车吨位差≤50吨;总吨位差≤50吨", |
| | | "超出项数=" + hits.size() + ",最大差=" + fmt(maxAbs)); |
| | | } |
| | | |
| | | /** 上报周转量与轨迹测算周转量对比,高出阈值比例核实 */ |
| | | private AuditResult checkRatio(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, TransportAuthVehicle> authMap, |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** 货运类型环比:本月新增(上月为0、本月>0)需核实,少的不管 */ |
| | | /** 货运类型环比(RULE_004):上月无→本月新增 或 上月有→本月无,均提醒核实 */ |
| | | private AuditResult checkMom(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, H2032EnterpriseMonthly> lastMonthMap) { |
| | | H2032EnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode())); |
| | | if (lastMonth == null) return null; |
| | | List<String> hits = new ArrayList<>(); |
| | | List<String> addHits = new ArrayList<>(); |
| | | List<String> delHits = new ArrayList<>(); |
| | | for (String field : rule.getCheckField().split(",")) { |
| | | double current = getH2032FieldValue(report, field); |
| | | double last = getH2032FieldValue(lastMonth, field); |
| | | if (current > 0 && last == 0) { |
| | | hits.add(h2032FieldLabel(field) + "=" + fmt(current)); |
| | | addHits.add(h2032FieldLabel(field) + "=" + fmt(current)); |
| | | } else if (last > 0 && current == 0) { |
| | | delHits.add(h2032FieldLabel(field) + "(上月=" + fmt(last) + ",本月无)"); |
| | | } |
| | | } |
| | | if (!hits.isEmpty()) { |
| | | return buildResult(rule, report, "本月新增: " + String.join("、", hits), |
| | | "上月该类型为0", "本月新增货运类型(上月无)"); |
| | | if (!addHits.isEmpty() || !delHits.isEmpty()) { |
| | | StringBuilder msg = new StringBuilder(); |
| | | if (!addHits.isEmpty()) msg.append("本月新增: ").append(String.join("、", addHits)); |
| | | if (!delHits.isEmpty()) { |
| | | if (msg.length() > 0) msg.append(";"); |
| | | msg.append("本月消失: ").append(String.join("、", delHits)); |
| | | } |
| | | return buildResult(rule, report, msg.toString(), |
| | | "上月无→本月新增 或 上月有→本月无", "货运类型环比新增/消失需核实"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** 有货运量但对应车辆为0 需核实(如 有集装箱货运量无集装箱车辆) */ |
| | | private AuditResult checkNull(AuditRule rule, H2032EnterpriseMonthly report) { |
| | | String[] fields = rule.getCheckField().split(","); |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** 数值范围检查(如 整车吨位 4≤且≤40) */ |
| | | /** 数值范围检查;整车吨位按“平均吨位=整车吨位/整车数”口径判定 4~40 */ |
| | | private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report) { |
| | | String[] bounds = rule.getThreshold().split(","); |
| | | if (bounds.length < 2) return null; |
| | | double value = getH2032FieldValue(report, rule.getCheckField()); |
| | | String field = rule.getCheckField(); |
| | | String label = h2032FieldLabel(field); |
| | | double value; |
| | | if ("tonsWhole".equals(field)) { |
| | | double vehicles = report.getVehicleWhole() == null ? 0.0 : report.getVehicleWhole().doubleValue(); |
| | | double tons = nvl(report.getTonsWhole()); |
| | | if (vehicles > 0) { |
| | | value = tons / vehicles; |
| | | label = "整车平均吨位(整车吨位/整车数)"; |
| | | } else { |
| | | value = tons; // 无整车数但有吨位时仍按原吨位判定 |
| | | } |
| | | } else { |
| | | value = getH2032FieldValue(report, field); |
| | | } |
| | | double low = Double.parseDouble(bounds[0].trim()); |
| | | double high = Double.parseDouble(bounds[1].trim()); |
| | | if (value > 0 && (value < low || value > high)) { |
| | | return buildResult(rule, report, h2032FieldLabel(rule.getCheckField()) + "=" + fmt(value), |
| | | return buildResult(rule, report, label + "=" + fmt(value), |
| | | "范围 " + fmt(low) + "~" + fmt(high), "超出范围"); |
| | | } |
| | | return null; |
| | |
| | | labels.put("tonsWhole", "整车吨位"); |
| | | labels.put("turnoverTotal", "周转量"); |
| | | labels.put("vehicleWhole", "整车车辆数"); |
| | | labels.put("vehicleTractor", "牵引车数"); |
| | | labels.put("vehicleTrailer", "挂车数"); |
| | | labels.put("tonsTrailer", "挂车吨位"); |
| | | labels.put("tonsWhole", "整车吨位"); |
| | | String v = labels.get(field); |
| | | return v == null ? field : v; |
| | | } |
| | |
| | | } |
| | | |
| | | private String fmt(double v) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v)) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v) && Math.abs(v) < 1e15) { |
| | | return String.valueOf((long) v); |
| | | } |
| | | return String.valueOf(Math.round(v * 100.0) / 100.0); |
| | | return java.math.BigDecimal.valueOf(v).setScale(2, java.math.RoundingMode.HALF_UP) |
| | | .stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | /** 占比/小数值:保留 4 位小数 */ |
| | | private String fmt4(double v) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v)) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v) && Math.abs(v) < 1e15) { |
| | | return String.valueOf((long) v); |
| | | } |
| | | return String.valueOf(Math.round(v * 10000.0) / 10000.0); |
| | | return java.math.BigDecimal.valueOf(v).setScale(4, java.math.RoundingMode.HALF_UP) |
| | | .stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | // ========== H2031 旅客审核 ========== |
| | |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | } |
| | | } |