diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java index 581cbad0..b1e6b98c 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.Collection; +import java.util.Collections; import java.util.stream.Collectors; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.satoken.utils.LoginHelper; @@ -758,7 +759,7 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { } // 2. 获取最大联合单位数量 - int maxUnionUnit = cpSettingService.getMaxUnionUnit(); + int maxUnionUnit = cpSettingService.getMaxAutoMatchUnionUnit(); // 2.1 获取系统匹配预约周数范围 int systemMatchWeeks = cpSettingService.getSystemMatchWeeks(); @@ -769,8 +770,8 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.gt(EfcodeCpPlan::getPlanDate, now); lqw.eq(EfcodeCpPlan::getSystemReserved, 0L); - lqw.eq(EfcodeCpPlan::getManualReserved, 0L); - lqw.eq(EfcodeCpPlan::getReserveStatus, 1L); + //lqw.eq(EfcodeCpPlan::getManualReserved, 0L); + lqw.in(EfcodeCpPlan::getReserveStatus, 1L, 4L, 5L); lqw.eq(EfcodeCpPlan::getIsPublic, 1L); lqw.eq(EfcodeCpPlan::getIsUnion, 1L); lqw.eq(EfcodeCpPlan::getIsDelete, 0L); @@ -785,6 +786,33 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { log.info("[CpPlan-V2] 共查询到{}条待匹配计划", pendingPlans.size()); + // 预查询所有待匹配计划的手动联合记录,构建互斥关系映射(已手动联合的计划对不应再被放入同一系统组) + Set pendingPlanIdSet = pendingPlans.stream() + .map(EfcodeCpPlan::getId) + .collect(Collectors.toSet()); + List allManualReserves = efcodeCpReserveMapper.selectList( + Wrappers.lambdaQuery() + .in(EfcodeCpReserve::getPlanId, pendingPlanIdSet) + .eq(EfcodeCpReserve::getResFrom, 0L) + ); + // 按planGroupId分组,找出同组内的计划(即已手动联合过的计划对) + Map> manualGroupToPlanIds = allManualReserves.stream() + .filter(r -> StringUtils.isNotBlank(r.getPlanGroupId())) + .collect(Collectors.groupingBy( + EfcodeCpReserve::getPlanGroupId, + Collectors.mapping(EfcodeCpReserve::getPlanId, Collectors.toSet()) + )); + // 构建每个计划的手动联合伙伴映射: planId -> 已与其手动联合过的其他planId集合 + Map> manualUnionPeers = new HashMap<>(); + for (Set groupPlanIds : manualGroupToPlanIds.values()) { + if (groupPlanIds.size() < 2) continue; + for (String pid : groupPlanIds) { + manualUnionPeers.computeIfAbsent(pid, k -> new HashSet<>()).addAll(groupPlanIds); + } + } + manualUnionPeers.forEach((pid, peers) -> peers.remove(pid)); + log.info("[CpPlan-V2] 存在手动联合互斥关系的计划数量: {}", manualUnionPeers.size()); + // 存储将要保存的联合记录列表 List unionReserveList = new ArrayList<>(); @@ -809,7 +837,10 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { LocalDate weekEndDate = weekStartDate.plusWeeks(systemMatchWeeks).minusDays(1); // 扩展N周后的周日 log.info("[CpPlan-V2] 计划ID={}的时间范围是:{} ~ {}", plan.getId(), weekStartDate, weekEndDate); - // 6. 从待匹配计划中筛选:同企业 + 时间范围内 + 不是当前计划创建者发布的 + 未被处理过 + // 6. 获取当前计划的手动联合伙伴(与当前计划已手动联合过的计划ID) + Set currentPlanPeers = manualUnionPeers.getOrDefault(plan.getId(), Collections.emptySet()); + + // 7. 从待匹配计划中筛选:同企业 + 时间范围内 + 不是当前计划创建者发布的 + 未被处理过 + 未与当前计划手动联合过 List matchedPlans = pendingPlans.stream() .filter(p -> { // 排除自身 @@ -828,19 +859,31 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { if (plan.getCreateBy() != null && plan.getCreateBy().equals(p.getCreateBy())) { return false; } + // 排除与当前计划已手动联合过的计划 + if (currentPlanPeers.contains(p.getId())) { + return false; + } // 在时间范围内 LocalDate pDate = p.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); return !pDate.isBefore(weekStartDate) && !pDate.isAfter(weekEndDate); }) .collect(Collectors.toList()); - log.info("[CpPlan-V2] 计划ID={}, 在时间范围内找到{}条非本人发布的同企业计划", plan.getId(), matchedPlans.size()); + log.info("[CpPlan-V2] 计划ID={}, 在时间范围内找到{}条非本人发布的同企业候选计划", plan.getId(), matchedPlans.size()); - // 7. 从matchedPlans中取出不超过 maxUnionUnit-1 条记录(当前计划本身占1个名额) + // 8. 从matchedPlans中选取计划,确保同组内不含已手动联合过的计划对 int limit = maxUnionUnit - 1; - List selectedPlans = matchedPlans.stream() - .limit(limit) - .collect(Collectors.toList()); + List selectedPlans = new ArrayList<>(); + for (EfcodeCpPlan candidate : matchedPlans) { + if (selectedPlans.size() >= limit) break; + // 检查候选计划与已选中计划之间是否存在手动联合互斥关系 + Set candidatePeers = manualUnionPeers.getOrDefault(candidate.getId(), Collections.emptySet()); + boolean hasConflict = selectedPlans.stream() + .anyMatch(sp -> candidatePeers.contains(sp.getId())); + if (!hasConflict) { + selectedPlans.add(candidate); + } + } log.info("[CpPlan-V2] 计划ID={}, 最终选取{}条计划参与联合(最大允许{}条)", plan.getId(), selectedPlans.size(), limit); @@ -917,6 +960,40 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { return resultMap; } + /** + * 获取指定计划之前通过手动匹配已联合过的其他计划ID集合 + * + * @param planId 当前计划ID + * @return 已手动联合过的计划ID集合,无则返回空集合 + */ + private Set getManualUnionPlanIds(String planId) { + // 查询当前plan在efcode_cp_reserve中手动匹配(res_from=0)的记录 + List manualReserves = efcodeCpReserveMapper.selectList( + Wrappers.lambdaQuery() + .eq(EfcodeCpReserve::getPlanId, planId) + .eq(EfcodeCpReserve::getResFrom, 0L) + ); + if (manualReserves.isEmpty()) { + return Collections.emptySet(); + } + Set manualGroupIds = manualReserves.stream() + .map(EfcodeCpReserve::getPlanGroupId) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + if (manualGroupIds.isEmpty()) { + return Collections.emptySet(); + } + // 查询同组中其他计划的planId + List groupedReserves = efcodeCpReserveMapper.selectList( + Wrappers.lambdaQuery() + .in(EfcodeCpReserve::getPlanGroupId, manualGroupIds) + .ne(EfcodeCpReserve::getPlanId, planId) + ); + return groupedReserves.stream() + .map(EfcodeCpReserve::getPlanId) + .collect(Collectors.toSet()); + } + /** * 计划自动匹配预约V2(定时任务调用) *

扫描待预约状态的计划,按自然周分组匹配,超出最大联合数量时拆分多组

diff --git a/zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue b/zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue index f3677efe..90791b76 100644 --- a/zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue +++ b/zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue @@ -92,7 +92,7 @@ -
+