traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -1317,13 +1317,100 @@
        }
    }
    /** 清空指定报表期全部已导入数据与审核结果(含导入批次、审核通过标记),用于整月重导;投资项目主档保留 */
    /** 支持“按类型清空”的数据类型 -> 数据表操作键(同表同类别归并,避免重复删除) */
    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_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"});
    }
    /**
     * 清空指定报表期已导入数据与审核结果,用于整月重导;投资项目主档保留。
     *
     * @param types 需清空的数据类型(前端类型键,如 h2032/cityBus);为空则清空该报表期全部类型
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> clearPeriod(String period) {
    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>()
@@ -1358,23 +1445,145 @@
            .eq(WycTotalMonthly::getReportPeriod, period));
        dataRows += wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>()
            .eq(WycOrderMonthly::getReportPeriod, period));
        int auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
        auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
            .eq(AuditResult::getReportPeriod, period));
        int auditRunRows = auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>()
        auditRunRows = auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>()
            .eq(AuditRun::getReportPeriod, period));
        int batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>()
        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, dataRows, auditRows, auditRunRows, 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<>();