From 18b8a07f55d05db0f6d5661ec8934610a392163b Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 21 Sep 2026 23:21:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=80=83=E5=8B=A4=EF=BC=9A?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=BE=E6=AC=A1=E5=AD=A6=E5=91=98=E8=80=83?= =?UTF-8?q?=E5=8B=A4=E5=90=8D=E5=86=8C=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../train/AttendanceController.java | 11 + .../domain/vo/AttendanceLessonRosterVO.java | 227 ++++++++++++++++++ .../train/service/AttendanceAppService.java | 8 + .../impl/AttendanceAppServiceImpl.java | 108 +++++++++ 4 files changed, 354 insertions(+) create mode 100644 RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/AttendanceLessonRosterVO.java diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/AttendanceController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/AttendanceController.java index 633269451..48944052b 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/AttendanceController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/AttendanceController.java @@ -3,6 +3,7 @@ package com.ruoyi.web.controller.train; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -70,4 +71,14 @@ public class AttendanceController extends BaseController attendanceAppService.adjust(checkinId, dto, SecurityUtils.getUsername()); return success(); } + + /** + * 查询课次学员考勤名册 (纯只读,供培训管理员点名签到)。 + */ + @PreAuthorize("@ss.hasPermi('train:attendance:list')") + @GetMapping("/lessons/{lessonId}/roster") + public AjaxResult getLessonRoster(@PathVariable Long lessonId) + { + return success(attendanceAppService.getLessonRoster(lessonId)); + } } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/AttendanceLessonRosterVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/AttendanceLessonRosterVO.java new file mode 100644 index 000000000..11f7ce3bf --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/AttendanceLessonRosterVO.java @@ -0,0 +1,227 @@ +package com.ruoyi.train.domain.vo; + +import java.io.Serializable; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 课次学员考勤名册对象 (Attendance Lesson Roster VO) + * 供培训管理员在业务端查看课次应到学员并完成首次签到。 + * + * @author ruoyi + * @date 2026-09-21 + */ +public class AttendanceLessonRosterVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 参训档案ID */ + private Long enrollmentId; + + /** 课次ID */ + private Long lessonId; + + /** 学员ID/工号 */ + private String studentId; + + /** 学员姓名 */ + private String studentName; + + /** 性别 */ + private String gender; + + /** 所属部门/机构 */ + private String deptName; + + /** 脱敏手机号 */ + private String phoneMasked; + + /** 参训状态 (0:待报到, 1:已报到, 2:请假, 3:放弃, 4:已完成) */ + private Integer enrollmentStatus; + + /** 考勤打卡记录ID (未打卡为null) */ + private Long checkinId; + + /** 签到方式 (1:人脸, 2:刷卡, 3:扫码, 4:教师/管理代签,未打卡为null) */ + private Integer checkinWay; + + /** 考勤状态 (0:缺勤, 1:正常, 2:迟到, 3:早退, 4:请假,未打卡为null) */ + private Integer attendanceStatus; + + /** 签到时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date signInTime; + + /** 签退时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date signOutTime; + + /** 是否允许签到 (尚未产生签到事实且参训有效) */ + private Boolean canSignIn; + + /** 是否允许签退 (已签到且未签退) */ + private Boolean canSignOut; + + /** 是否允许调整 (已有考勤记录) */ + private Boolean canAdjust; + + public Long getEnrollmentId() + { + return enrollmentId; + } + + public void setEnrollmentId(Long enrollmentId) + { + this.enrollmentId = enrollmentId; + } + + public Long getLessonId() + { + return lessonId; + } + + public void setLessonId(Long lessonId) + { + this.lessonId = lessonId; + } + + public String getStudentId() + { + return studentId; + } + + public void setStudentId(String studentId) + { + this.studentId = studentId; + } + + public String getStudentName() + { + return studentName; + } + + public void setStudentName(String studentName) + { + this.studentName = studentName; + } + + public String getGender() + { + return gender; + } + + public void setGender(String gender) + { + this.gender = gender; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getPhoneMasked() + { + return phoneMasked; + } + + public void setPhoneMasked(String phoneMasked) + { + this.phoneMasked = phoneMasked; + } + + public Integer getEnrollmentStatus() + { + return enrollmentStatus; + } + + public void setEnrollmentStatus(Integer enrollmentStatus) + { + this.enrollmentStatus = enrollmentStatus; + } + + public Long getCheckinId() + { + return checkinId; + } + + public void setCheckinId(Long checkinId) + { + this.checkinId = checkinId; + } + + public Integer getCheckinWay() + { + return checkinWay; + } + + public void setCheckinWay(Integer checkinWay) + { + this.checkinWay = checkinWay; + } + + public Integer getAttendanceStatus() + { + return attendanceStatus; + } + + public void setAttendanceStatus(Integer attendanceStatus) + { + this.attendanceStatus = attendanceStatus; + } + + public Date getSignInTime() + { + return signInTime; + } + + public void setSignInTime(Date signInTime) + { + this.signInTime = signInTime; + } + + public Date getSignOutTime() + { + return signOutTime; + } + + public void setSignOutTime(Date signOutTime) + { + this.signOutTime = signOutTime; + } + + public Boolean getCanSignIn() + { + return canSignIn; + } + + public void setCanSignIn(Boolean canSignIn) + { + this.canSignIn = canSignIn; + } + + public Boolean getCanSignOut() + { + return canSignOut; + } + + public void setCanSignOut(Boolean canSignOut) + { + this.canSignOut = canSignOut; + } + + public Boolean getCanAdjust() + { + return canAdjust; + } + + public void setCanAdjust(Boolean canAdjust) + { + this.canAdjust = canAdjust; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/AttendanceAppService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/AttendanceAppService.java index 0c13a282a..35866c3a1 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/AttendanceAppService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/AttendanceAppService.java @@ -37,4 +37,12 @@ public interface AttendanceAppService * @param operator 操作人 */ void adjust(Long checkinId, AttendanceAdjustDTO dto, String operator); + + /** + * 查询课次学员考勤名册 (纯只读,供培训管理员点名签到)。 + * + * @param lessonId 课次ID + * @return 考勤名册列表 + */ + java.util.List getLessonRoster(Long lessonId); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java index 275cfcbb3..f47039c7a 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java @@ -1,6 +1,10 @@ package com.ruoyi.train.service.impl; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +21,9 @@ import com.ruoyi.train.domain.TrainStudentCheckin; import com.ruoyi.train.domain.dto.AttendanceAdjustDTO; import com.ruoyi.train.domain.dto.AttendanceSignInDTO; import com.ruoyi.train.domain.dto.AttendanceSignOutDTO; +import com.ruoyi.train.domain.query.EnrollmentQuery; +import com.ruoyi.train.domain.vo.AttendanceLessonRosterVO; +import com.ruoyi.train.domain.vo.EnrollmentItemVO; import com.ruoyi.train.mapper.TrainEnrollmentMapper; import com.ruoyi.train.mapper.TrainLessonMapper; import com.ruoyi.train.mapper.TrainPlanCourseMapper; @@ -231,4 +238,105 @@ public class AttendanceAppServiceImpl implements AttendanceAppService trainStudentCheckinMapper.updateTrainStudentCheckin(checkin); } + + @Override + public List getLessonRoster(Long lessonId) + { + if (lessonId == null) + { + throw new ServiceException("课次ID不能为空", 400); + } + + TrainLesson lesson = trainLessonMapper.selectTrainLessonById(lessonId); + if (lesson == null) + { + throw new ServiceException(AttendanceErrorCode.LESSON_NOT_FOUND_MESSAGE, AttendanceErrorCode.LESSON_NOT_FOUND); + } + + TrainPlanCourse planCourse = trainPlanCourseMapper.selectTrainPlanCourseById(lesson.getPlanCourseId()); + if (planCourse == null || planCourse.getPlanId() == null) + { + throw new ServiceException("课次未关联有效的培训课程计划", 400); + } + + EnrollmentQuery query = new EnrollmentQuery(); + query.setPlanId(planCourse.getPlanId()); + List enrollments = trainEnrollmentMapper.selectEnrollmentItemList(query); + + List checkins = trainStudentCheckinMapper.selectCheckinsByLessonId(lessonId); + Map checkinMap = new HashMap<>(); + if (checkins != null) + { + for (TrainStudentCheckin c : checkins) + { + if (c.getEnrollmentId() != null) + { + checkinMap.put(c.getEnrollmentId(), c); + } + } + } + + List roster = new ArrayList<>(); + if (enrollments != null) + { + for (EnrollmentItemVO item : enrollments) + { + // 排除已放弃参训学员 (STATUS_ABANDONED = 3) + if (item.getEnrollmentStatus() != null && item.getEnrollmentStatus() == EnrollmentErrorCode.STATUS_ABANDONED) + { + continue; + } + + AttendanceLessonRosterVO vo = new AttendanceLessonRosterVO(); + vo.setEnrollmentId(item.getEnrollmentId()); + vo.setLessonId(lessonId); + vo.setStudentId(item.getStudentId()); + vo.setStudentName(item.getStudentName()); + vo.setGender(item.getGender()); + vo.setDeptName(item.getOrgName()); + vo.setPhoneMasked(maskPhone(item.getPhone())); + vo.setEnrollmentStatus(item.getEnrollmentStatus()); + + TrainStudentCheckin c = checkinMap.get(item.getEnrollmentId()); + if (c != null) + { + vo.setCheckinId(c.getCheckinId()); + vo.setCheckinWay(c.getCheckinWay()); + vo.setAttendanceStatus(c.getAttendanceStatus()); + vo.setSignInTime(c.getSignInTime()); + vo.setSignOutTime(c.getSignOutTime()); + // 仅当已有记录但未打签到时间时允许补签;已有签到时间则禁止重复签到 + vo.setCanSignIn(c.getSignInTime() == null); + vo.setCanSignOut(c.getSignInTime() != null && c.getSignOutTime() == null); + vo.setCanAdjust(true); + } + else + { + vo.setCheckinId(null); + vo.setCheckinWay(null); + vo.setAttendanceStatus(null); + vo.setSignInTime(null); + vo.setSignOutTime(null); + vo.setCanSignIn(true); + vo.setCanSignOut(false); + vo.setCanAdjust(false); + } + roster.add(vo); + } + } + return roster; + } + + private String maskPhone(String phone) + { + if (phone == null || phone.length() < 7) + { + return phone; + } + if (phone.length() == 11) + { + return phone.substring(0, 3) + "****" + phone.substring(7); + } + return phone.substring(0, 2) + "****" + phone.substring(phone.length() - 2); + } }