From 8900dcb1016964438c8d8e17094f9e7605632d49 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 21 Sep 2026 17:06:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BB=93=E8=AE=AD=EF=BC=9A?= =?UTF-8?q?=E9=98=BB=E6=AD=A2=E6=9C=AA=E6=8A=A5=E5=88=B0=E4=B8=8E=E8=AF=B7?= =?UTF-8?q?=E5=81=87=E5=AD=A6=E5=91=98=E8=A2=AB=E9=94=99=E8=AF=AF=E5=BD=92?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/EnrollmentPlanCompletionAdapter.java | 72 +++++++++++++++++++ .../service/impl/PlanAppServiceImpl.java | 6 ++ .../port/PlanEnrollmentCompletionPort.java | 8 +++ 3 files changed, 86 insertions(+) diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentPlanCompletionAdapter.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentPlanCompletionAdapter.java index 26f3680a5..7ffbf910e 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentPlanCompletionAdapter.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentPlanCompletionAdapter.java @@ -38,10 +38,82 @@ public class EnrollmentPlanCompletionAdapter implements PlanEnrollmentCompletion @Autowired(required = false) private AccessAuthorizationOrchestrator accessAuthorizationOrchestrator; + @Override + public void precheckCompletion(String planId) + { + List> stats = trainEnrollmentService.selectEnrollmentCompletionStatsByPlanId(planId); + int status0Waiting = 0; + int status1Reported = 0; + int status2Leave = 0; + int status3Abandoned = 0; + int status4Completed = 0; + int integrityErrorCount = 0; + + if (stats != null) + { + for (java.util.Map map : stats) + { + Object stObj = map.get("enrollmentStatus") != null ? map.get("enrollmentStatus") : map.get("enrollment_status"); + int st = stObj instanceof Number ? ((Number) stObj).intValue() : 0; + Object repObj = map.get("hasReport") != null ? map.get("hasReport") : map.get("has_report"); + int hasRep = repObj instanceof Number ? ((Number) repObj).intValue() : 0; + + if (st == 0) + { + status0Waiting++; + } + else if (st == 1) + { + status1Reported++; + if (hasRep == 0) + { + integrityErrorCount++; + } + } + else if (st == 2) + { + status2Leave++; + } + else if (st == 3) + { + status3Abandoned++; + } + else if (st == 4) + { + status4Completed++; + } + } + } + + if (status0Waiting > 0 || status2Leave > 0 || integrityErrorCount > 0) + { + StringBuilder msg = new StringBuilder("计划暂不能结训:"); + List reasons = new java.util.ArrayList<>(); + if (status0Waiting > 0) + { + reasons.add("待报到 " + status0Waiting + " 人 (WAITING_REPORT_ENROLLMENTS_EXIST)"); + } + if (status2Leave > 0) + { + reasons.add("请假 " + status2Leave + " 人 (LEAVE_ENROLLMENTS_REQUIRE_RESOLUTION)"); + } + if (integrityErrorCount > 0) + { + reasons.add("已报到缺失报到单异常 " + integrityErrorCount + " 人 (REPORTED_WITHOUT_REPORT_INTEGRITY_ERROR)"); + } + msg.append(String.join(",", reasons)).append("。请先处理后再结训。"); + log.warn("计划结训预检拦截: planId={}, 原因={}", planId, msg.toString()); + throw new com.ruoyi.common.exception.ServiceException(msg.toString()); + } + } + @Override @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public void completeEnrollments(String planId, String operator, Date completeTime) { + // 结训预检 (All-or-Nothing 守卫) + precheckCompletion(planId); + log.info("执行计划结训学员状态联动归档: planId={}, operator={}, completeTime={}", planId, operator, completeTime); int updatedRows = trainEnrollmentService.completeActiveEnrollmentsByPlanId(planId, operator, completeTime); log.info("计划结训学员归档完成: planId={}, 更新记录数={}", planId, updatedRows); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java index 6ef4af0e0..d60236095 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java @@ -158,6 +158,12 @@ public class PlanAppServiceImpl implements PlanAppService throw error(PlanErrorCode.PLAN_STATUS_LOCKED_MESSAGE, PlanErrorCode.PLAN_STATUS_LOCKED); } + // 结训预检 (All-or-Nothing 守卫:存在待报到、请假或报到数据异常学员时阻止结训) + for (PlanEnrollmentCompletionPort port : completionPorts) + { + port.precheckCompletion(planId); + } + Date completeTime = DateUtils.getNowDate(); TrainClassPlan statusUpdate = new TrainClassPlan(); statusUpdate.setPlanId(planId); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java index 5c81c4689..43788e48e 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java @@ -8,5 +8,13 @@ import java.util.Date; */ public interface PlanEnrollmentCompletionPort { + /** + * 结训前预检参训人员状态完整性 (All-or-Nothing Precheck)。 + * 若存在 0:待报到、2:请假、或 1:已报到但无报到记录异常,抛出异常阻止结训。 + * + * @param planId 培训班ID + */ + default void precheckCompletion(String planId) {} + void completeEnrollments(String planId, String operator, Date completeTime); }