From 2087d3a68dda9d21705969dd882c9a9de82d1fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E9=94=90=E6=B3=A2?= <10549902+zhuangruibo@user.noreply.gitee.com> Date: Sun, 31 May 2026 17:13:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=A7=E6=B3=95=E7=AB=AF-=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/IEfcodeCpNoticeService.java | 8 + .../service/IEfcodeCpPlanService.java | 9 + .../impl/EfcodeCpNoticeServiceImpl.java | 29 ++++ .../service/impl/EfcodeCpPlanServiceImpl.java | 157 ++++++++++++++++++ .../enforcement/my/plan/cp_plan_mine.vue | 4 +- 5 files changed, 205 insertions(+), 2 deletions(-) diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpNoticeService.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpNoticeService.java index babee848..9c20a917 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpNoticeService.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpNoticeService.java @@ -85,4 +85,12 @@ public interface IEfcodeCpNoticeService { * 删除预告(仅草稿可删除) */ String deleteNotice(Collection ids); + + /** + * 预告到期自动标记已执行 + *

enforce_date 23:59:59 < 当前时间 且 status=1(已发布) 的预告,状态更新为 3=已执行

+ * + * @return 处理的记录数 + */ + int markExpiredNoticesAsExecuted(); } diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java index a96a3bde..b5f486e2 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java @@ -103,4 +103,13 @@ public interface IEfcodeCpPlanService { * @return 按 planGroupId 分组的预约记录列表 */ Map> autoMatchReserveV2(boolean forceMatch, boolean updatePlan, boolean insertReserve); + + /** + * 计划到期自动生成任务 + *

距计划执行日期 <= CP_SETTING_MANUAL_MATCH_DAYS 天时, + * 系统自动取消剩余未处理的预约(含系统匹配与手动预约),并为计划生成任务。

+ * + * @return 处理的计划数量 + */ + int processPlanDeadlineAutoGenTask(); } diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpNoticeServiceImpl.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpNoticeServiceImpl.java index 0fbdb3dc..2ee6eac8 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpNoticeServiceImpl.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpNoticeServiceImpl.java @@ -275,4 +275,33 @@ public class EfcodeCpNoticeServiceImpl implements IEfcodeCpNoticeService { } return baseMapper.deleteByIds(ids) > 0; } + + /** + * 预告到期自动标记已执行 + *

将 enforce_date 23:59:59 < 当前时间 且 status=1(已发布) 的预告,状态更新为 3=已执行

+ */ + @Override + public int markExpiredNoticesAsExecuted() { + // 查找已发布(status=1)且执法日期已过期的预告 + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(EfcodeCpNotice::getStatus, 1L); + lqw.eq(EfcodeCpNotice::getIsDelete, 0L); + lqw.apply("CONCAT(enforce_date, ' 23:59:59') < NOW()"); + List expiredList = baseMapper.selectList(lqw); + + if (expiredList.isEmpty()) { + return 0; + } + + // 批量更新状态为 3=已执行 + for (EfcodeCpNotice notice : expiredList) { + EfcodeCpNotice update = new EfcodeCpNotice(); + update.setId(notice.getId()); + update.setStatus(3L); + baseMapper.updateById(update); + } + + log.info("[markExpiredNoticesAsExecuted] 共标记{}条预告为已执行", expiredList.size()); + return expiredList.size(); + } } 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 4d2dbe5a..2cfcf7d8 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 @@ -26,6 +26,7 @@ import java.time.temporal.ChronoUnit; import java.time.DayOfWeek; import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -1107,4 +1108,160 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService { log.info("[CpPlan-V2] 计划自动匹配预约执行完毕,共匹配{}组联合预约", matchedCount); return resultMap; } + + /** + * 计划到期自动生成任务 + *

距计划执行日期 <= CP_SETTING_MANUAL_MATCH_DAYS 天时, + * 系统自动取消剩余未处理的预约(含系统匹配与手动预约),并为计划生成任务。

+ *
    + *
  • 部分联合(有已同意的预约):取消剩余待确认预约,已同意的计划共享生成任务
  • + *
  • 完全没有联合记录:直接为计划生成任务
  • + *
  • 全部待确认(无人同意):取消全部待确认预约,为每个计划各自生成任务
  • + *
+ */ + @Transactional(rollbackFor = Exception.class) + @Override + public int processPlanDeadlineAutoGenTask() { + int manualMatchDays = cpSettingService.getManualMatchDays(); + LocalDate today = LocalDate.now(); + LocalDate deadlineDate = today.plusDays(manualMatchDays); + + // 1. 查找即将到期的计划:plan_date <= today + manualMatchDays,且未生成任务 + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.le(EfcodeCpPlan::getPlanDate, java.sql.Date.valueOf(deadlineDate)); + lqw.ge(EfcodeCpPlan::getPlanDate, java.sql.Date.valueOf(today)); + lqw.eq(EfcodeCpPlan::getIsDelete, 0L); + lqw.and(w -> w.isNull(EfcodeCpPlan::getTaskId).or().eq(EfcodeCpPlan::getTaskId, "")); + lqw.in(EfcodeCpPlan::getReserveStatus, Arrays.asList(1L, 2L, 4L, 5L, 6L)); + List plans = baseMapper.selectList(lqw); + + if (plans.isEmpty()) { + log.info("[processPlanDeadlineAutoGenTask] 无即将到期的计划需要处理"); + return 0; + } + log.info("[processPlanDeadlineAutoGenTask] 找到{}条即将到期的计划(截止日期={})", plans.size(), deadlineDate); + + // 2. 查找这些计划的预约记录 + List planIds = plans.stream().map(EfcodeCpPlan::getId).collect(Collectors.toList()); + List reserves = efcodeCpReserveMapper.selectList( + Wrappers.lambdaQuery().in(EfcodeCpReserve::getPlanId, planIds) + ); + + // 建立 planId → reserves 映射 + Map> planReserveMap = reserves.stream() + .collect(Collectors.groupingBy(EfcodeCpReserve::getPlanId)); + + // 记录已处理的 plan_group_id,避免重复处理同一分组 + Set processedGroups = new HashSet<>(); + int processedCount = 0; + + for (EfcodeCpPlan plan : plans) { + // 跳过已经在分组处理中被赋予了 taskId 的计划 + if (StringUtils.isNotBlank(plan.getTaskId())) { + continue; + } + + List planReserves = planReserveMap.get(plan.getId()); + + if (planReserves == null || planReserves.isEmpty()) { + // Case B: 完全没有联合记录 → 直接生成任务 + log.info("[processPlanDeadlineAutoGenTask] 计划[{}]无联合记录,直接生成任务, 企业={}", + plan.getId(), plan.getEnterpriseName()); + cpTaskService.forceGenerateTask(plan); + processedCount++; + } else { + // Case A: 有联合记录 → 按 plan_group_id 处理 + for (EfcodeCpReserve reserve : planReserves) { + String groupId = reserve.getPlanGroupId(); + if (StringUtils.isBlank(groupId) || processedGroups.contains(groupId)) { + continue; + } + processedGroups.add(groupId); + processedCount += processDeadlineGroup(groupId); + } + } + } + + log.info("[processPlanDeadlineAutoGenTask] 处理完毕,共处理{}条计划", processedCount); + return processedCount; + } + + /** + * 处理一个预约分组的截止逻辑: + * 1. 取消所有待确认(opStatus=3)的预约 → 设为超期取消(4) + * 2. 为已同意(opStatus=1)的计划生成共享任务 + * 3. 为被取消的计划各自生成独立任务 + * + * @param groupId 预约分组ID + * @return 处理的计划数量 + */ + private int processDeadlineGroup(String groupId) { + // 查询分组内所有预约记录 + List groupReserves = efcodeCpReserveMapper.selectList( + Wrappers.lambdaQuery().eq(EfcodeCpReserve::getPlanGroupId, groupId) + ); + if (groupReserves.isEmpty()) { + return 0; + } + + // 1. 取消所有待确认的预约 (opStatus=3 → 4) + List pendingReserves = groupReserves.stream() + .filter(r -> Long.valueOf(3L).equals(r.getOpStatus())) + .collect(Collectors.toList()); + for (EfcodeCpReserve pending : pendingReserves) { + EfcodeCpReserve update = new EfcodeCpReserve(); + update.setId(pending.getId()); + update.setOpStatus(4L); // 超期取消 + update.setOpTime(new Date()); + efcodeCpReserveMapper.updateById(update); + log.info("[processDeadlineGroup] 取消待确认预约: reserveId={}, planId={}, groupId={}", + pending.getId(), pending.getPlanId(), groupId); + } + + // 2. 找出已同意的预约 (opStatus=1) + List approvedReserves = groupReserves.stream() + .filter(r -> Long.valueOf(1L).equals(r.getOpStatus())) + .collect(Collectors.toList()); + + int processedCount = 0; + + if (!approvedReserves.isEmpty()) { + // 有部分或全部同意 → 为同意的计划生成共享任务 + String sharedTaskId = null; + for (EfcodeCpReserve approved : approvedReserves) { + EfcodeCpPlan approvedPlan = baseMapper.selectById(approved.getPlanId()); + if (approvedPlan == null || StringUtils.isNotBlank(approvedPlan.getTaskId())) { + continue; + } + if (sharedTaskId == null) { + sharedTaskId = cpTaskService.forceGenerateTask(approvedPlan); + log.info("[processDeadlineGroup] 已同意计划[{}]生成共享任务taskId={}", + approvedPlan.getId(), sharedTaskId); + } else { + EfcodeCpPlan planUpdate = new EfcodeCpPlan(); + planUpdate.setId(approvedPlan.getId()); + planUpdate.setTaskId(sharedTaskId); + planUpdate.setReserveStatus(3L); + baseMapper.updateById(planUpdate); + log.info("[processDeadlineGroup] 已同意计划[{}]加入共享任务taskId={}", + approvedPlan.getId(), sharedTaskId); + } + processedCount++; + } + } + + // 3. 为被取消的计划各自生成独立任务 + for (EfcodeCpReserve pending : pendingReserves) { + EfcodeCpPlan pendingPlan = baseMapper.selectById(pending.getPlanId()); + if (pendingPlan == null || StringUtils.isNotBlank(pendingPlan.getTaskId())) { + continue; + } + cpTaskService.forceGenerateTask(pendingPlan); + log.info("[processDeadlineGroup] 被取消计划[{}]生成独立任务, 企业={}", + pendingPlan.getId(), pendingPlan.getEnterpriseName()); + processedCount++; + } + + return processedCount; + } } 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 f2cce183..9da30cb8 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 @@ -216,9 +216,9 @@ const emit = defineEmits(['edit']); const filterStatus = ref(null); const filterOptions = [ { label: '全部', value: null }, - { label: '待预约', value: '1,4,5' }, + { label: '待预约', value: '1,4,5,6' }, { label: '预约中', value: '2' }, - { label: '预约完成', value: '3,6,7' }, + { label: '预约完成', value: '3,7' }, ]; const onFilterChange = (val) => {