修复参训:收紧报到状态与报到记录一致性
This commit is contained in:
1 parent
481c998e82
commit
1c395b7827
5 files changed
+153
-13
No files matched your search
@@ -124,6 +124,14 @@ public interface TrainEnrollmentMapper
|
||||
@Param("operator") String operator,
|
||||
@Param("completeTime") Date completeTime);
|
||||
|
||||
/**
|
||||
* 查询指定计划学员的结训预检统计事实(参训状态与报到单存在性)。
|
||||
*
|
||||
* @param planId 培训班ID
|
||||
* @return 参训学员状态事实列表
|
||||
*/
|
||||
java.util.List<java.util.Map<String, Object>> selectEnrollmentCompletionStatsByPlanId(@Param("planId") String planId);
|
||||
|
||||
/**
|
||||
* 物理删除参训记录。
|
||||
*
|
||||
|
||||
+1
@@ -44,4 +44,5 @@ public interface ITrainEnrollmentService
|
||||
int countAccommodationByEnrollmentId(Long enrollmentId);
|
||||
|
||||
int countStudyDurationByEnrollmentId(Long enrollmentId);
|
||||
List<java.util.Map<String, Object>> selectEnrollmentCompletionStatsByPlanId(String planId);
|
||||
}
|
||||
+112
-10
@@ -371,17 +371,121 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService
|
||||
throw new ServiceException(EnrollmentErrorCode.ENROLLMENT_STATUS_INVALID_MESSAGE);
|
||||
}
|
||||
|
||||
// 普通状态接口严格禁止直接设置为已完成 (4),结训必须走开班计划结训流程
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_COMPLETED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("普通状态接口禁止直接设置为已完成状态,结训必须走开班计划结训流程");
|
||||
}
|
||||
|
||||
TrainEnrollment enrollment = requireEnrollment(enrollmentId);
|
||||
// 计划已结训或取消时,禁止状态变更
|
||||
TrainClassPlan plan = requirePlan(enrollment.getPlanId());
|
||||
checkPlanStatusNotLocked(plan);
|
||||
|
||||
// 如果已经结训(4),禁止任意更改状态
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_COMPLETED).equals(enrollment.getEnrollmentStatus()))
|
||||
Integer currentStatus = enrollment.getEnrollmentStatus() != null ? enrollment.getEnrollmentStatus() : EnrollmentErrorCode.STATUS_WAITING_REPORT;
|
||||
|
||||
// 如果已经结训(4),终态,任何 4 -> X 全部禁止
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_COMPLETED).equals(currentStatus))
|
||||
{
|
||||
throw new ServiceException("该学员已完成结训归档,不可修改参训状态");
|
||||
}
|
||||
|
||||
if (currentStatus.equals(newStatus))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询是否存在报到确认单事实
|
||||
boolean reportExists = trainEnrollmentService.countReportByEnrollmentId(enrollmentId) > 0;
|
||||
|
||||
// 状态迁移规则守卫 (V1 Invariant Matrix)
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(currentStatus)) // 0: 待报到
|
||||
{
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(newStatus))
|
||||
{
|
||||
// INVARIANT R1: 禁止普通状态接口 0 -> 1,首次进入 1 必须通过 confirmReport
|
||||
throw new ServiceException("待报到学员禁止通过普通接口直接变更为已报到,必须通过报到确认流程进入");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_LEAVE).equals(newStatus) &&
|
||||
!Integer.valueOf(EnrollmentErrorCode.STATUS_ABANDONED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("待报到学员仅允许变更为请假或退训状态");
|
||||
}
|
||||
}
|
||||
else if (Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(currentStatus)) // 1: 已报到
|
||||
{
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(newStatus))
|
||||
{
|
||||
// INVARIANT R4: 已有报到单,不得通过普通状态修改形成 status=0
|
||||
throw new ServiceException("已报到学员已存在报到事实,禁止变更为待报到状态");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_LEAVE).equals(newStatus) &&
|
||||
!Integer.valueOf(EnrollmentErrorCode.STATUS_ABANDONED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("已报到学员仅允许变更为请假或退训状态");
|
||||
}
|
||||
}
|
||||
else if (Integer.valueOf(EnrollmentErrorCode.STATUS_LEAVE).equals(currentStatus)) // 2: 请假
|
||||
{
|
||||
if (!reportExists)
|
||||
{
|
||||
// 报到前请假:允许 2 -> 0 (恢复为待报到);禁止 2 -> 1 (必须先回到 0 后走 confirmReport);允许 2 -> 3
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("未报到学员请假后禁止直接变更为已报到,必须先恢复为待报到并执行报到确认");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(newStatus) &&
|
||||
!Integer.valueOf(EnrollmentErrorCode.STATUS_ABANDONED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("未报到请假学员仅允许恢复为待报到或变更为退训");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 报到后请假:允许 2 -> 1 (归队);禁止 2 -> 0 (已有报到事实);允许 2 -> 3
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("已报到学员请假后存在报到事实,禁止恢复为待报到状态");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(newStatus) &&
|
||||
!Integer.valueOf(EnrollmentErrorCode.STATUS_ABANDONED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("已报到请假学员仅允许销假归队为已报到或变更为退训");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Integer.valueOf(EnrollmentErrorCode.STATUS_ABANDONED).equals(currentStatus)) // 3: 放弃/退训
|
||||
{
|
||||
if (!reportExists)
|
||||
{
|
||||
// 报到前放弃:允许 3 -> 0;禁止 3 -> 1
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("未报到退训学员禁止直接恢复为已报到,必须先恢复为待报到");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("未报到退训学员仅允许恢复为待报到状态");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 报到后放弃:允许 3 -> 1;禁止 3 -> 0
|
||||
if (Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("已报到退训学员存在既有报到事实,禁止恢复为待报到状态");
|
||||
}
|
||||
if (!Integer.valueOf(EnrollmentErrorCode.STATUS_REPORTED).equals(newStatus))
|
||||
{
|
||||
throw new ServiceException("已报到退训学员仅允许受控恢复为已报到状态");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ServiceException("当前参训状态不支持此状态流转");
|
||||
}
|
||||
|
||||
trainEnrollmentService.updateEnrollmentStatus(
|
||||
enrollmentId,
|
||||
newStatus,
|
||||
@@ -621,14 +725,6 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService
|
||||
throw new ServiceException(EnrollmentErrorCode.ENROLLMENT_NOT_FOUND_MESSAGE, EnrollmentErrorCode.ENROLLMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 状态流转保护:退训(3)或已完成(4)的历史状态禁止报到
|
||||
if (enrollment.getEnrollmentStatus() != null &&
|
||||
(enrollment.getEnrollmentStatus() == EnrollmentErrorCode.STATUS_ABANDONED ||
|
||||
enrollment.getEnrollmentStatus() == EnrollmentErrorCode.STATUS_COMPLETED))
|
||||
{
|
||||
throw new ServiceException("当前参训状态不允许执行报到确认", EnrollmentErrorCode.ENROLLMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 幂等与查重:若已报到(status==1)或已有报到记录
|
||||
TrainClassReport existingReport = trainClassReportMapper.selectTrainClassReportByEnrollmentId(enrollmentId);
|
||||
if (existingReport != null || (enrollment.getEnrollmentStatus() != null && enrollment.getEnrollmentStatus() == EnrollmentErrorCode.STATUS_REPORTED))
|
||||
@@ -636,6 +732,12 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService
|
||||
throw new ServiceException(AttendanceErrorCode.REPORT_ALREADY_CONFIRMED_MESSAGE, AttendanceErrorCode.REPORT_ALREADY_CONFIRMED);
|
||||
}
|
||||
|
||||
// 状态流转保护:只有待报到(0)状态允许执行报到确认 (首次进入 1 必须通过 confirmReport)
|
||||
if (enrollment.getEnrollmentStatus() == null || !Integer.valueOf(EnrollmentErrorCode.STATUS_WAITING_REPORT).equals(enrollment.getEnrollmentStatus()))
|
||||
{
|
||||
throw new ServiceException("当前参训状态不允许执行报到确认", EnrollmentErrorCode.ENROLLMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 校验计划报到截止时间生命周期策略 (Report Lifecycle Policy)
|
||||
TrainClassPlan plan = planMapper.selectTrainClassPlanByPlanId(enrollment.getPlanId());
|
||||
Date now = new Date();
|
||||
|
||||
+19
@@ -49,6 +49,11 @@ public class TrainEnrollmentServiceImpl implements ITrainEnrollmentService
|
||||
@Override
|
||||
public int insertTrainEnrollment(TrainEnrollment trainEnrollment)
|
||||
{
|
||||
if (trainEnrollment != null)
|
||||
{
|
||||
// 固定初始参训状态为 0:待报到,防止外部传入非法状态
|
||||
trainEnrollment.setEnrollmentStatus(0);
|
||||
}
|
||||
return trainEnrollmentMapper.insertTrainEnrollment(trainEnrollment);
|
||||
}
|
||||
|
||||
@@ -59,6 +64,14 @@ public class TrainEnrollmentServiceImpl implements ITrainEnrollmentService
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// 严格固定每条新创建的参训记录状态为 0:待报到
|
||||
for (TrainEnrollment item : list)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
item.setEnrollmentStatus(0);
|
||||
}
|
||||
}
|
||||
return trainEnrollmentMapper.batchInsertTrainEnrollment(list);
|
||||
}
|
||||
|
||||
@@ -115,4 +128,10 @@ public class TrainEnrollmentServiceImpl implements ITrainEnrollmentService
|
||||
{
|
||||
return trainEnrollmentMapper.countStudyDurationByEnrollmentId(enrollmentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> selectEnrollmentCompletionStatsByPlanId(String planId)
|
||||
{
|
||||
return trainEnrollmentMapper.selectEnrollmentCompletionStatsByPlanId(planId);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(
|
||||
#{item.planId}, #{item.studentId}, #{item.sourceType}, #{item.enrollmentStatus},
|
||||
#{item.planId}, #{item.studentId}, #{item.sourceType}, 0,
|
||||
#{item.accommodationRequired}, #{item.joinTime}, #{item.completeTime},
|
||||
#{item.createBy}, #{item.createTime}, #{item.remark}
|
||||
)
|
||||
@@ -116,7 +116,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
update train_enrollment
|
||||
<set>
|
||||
<if test="sourceType != null">source_type = #{sourceType},</if>
|
||||
<if test="enrollmentStatus != null">enrollment_status = #{enrollmentStatus},</if>
|
||||
<if test="accommodationRequired != null">accommodation_required = #{accommodationRequired},</if>
|
||||
<if test="joinTime != null">join_time = #{joinTime},</if>
|
||||
<if test="completeTime != null">complete_time = #{completeTime},</if>
|
||||
@@ -152,9 +151,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
update_by = #{operator},
|
||||
update_time = #{completeTime}
|
||||
where plan_id = #{planId}
|
||||
and enrollment_status in (0, 1)
|
||||
and enrollment_status = 1
|
||||
and exists (
|
||||
select 1 from train_class_report r where r.enrollment_id = train_enrollment.enrollment_id
|
||||
)
|
||||
</update>
|
||||
|
||||
<select id="selectEnrollmentCompletionStatsByPlanId" parameterType="String" resultType="java.util.Map">
|
||||
select e.enrollment_id as "enrollmentId",
|
||||
e.enrollment_status as "enrollmentStatus",
|
||||
(case when exists (select 1 from train_class_report r where r.enrollment_id = e.enrollment_id) then 1 else 0 end) as "hasReport"
|
||||
from train_enrollment e
|
||||
where e.plan_id = #{planId}
|
||||
</select>
|
||||
|
||||
<delete id="deleteTrainEnrollmentById" parameterType="Long">
|
||||
delete from train_enrollment where enrollment_id = #{enrollmentId}
|
||||
</delete>
|
||||
|
||||
Reference in new issue
Block a user