测试考勤:覆盖首次签到与未签到展示

This commit is contained in:
wangzhibiao committed 2026-09-21 23:21:35 +08:00
1 parent dfd0133df0
commit 38d4851a18
3 files changed
+336

No files matched your search

@@ -6,6 +6,7 @@ import static org.junit.Assert.assertNotNull;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import com.ruoyi.train.domain.dto.AttendanceAdjustDTO;
@@ -56,5 +57,13 @@ public class AttendanceAndReportApiControllerContractTest
assertNotNull(putAdjust);
assertEquals("/{checkinId}", putAdjust.value()[0]);
assertEquals("@ss.hasPermi('train:attendance:edit')", adjust.getAnnotation(PreAuthorize.class).value());
// 4. 课次学员考勤名册 (纯只读,供管理员点名签到)
Method getRoster = AttendanceController.class.getMethod("getLessonRoster", Long.class);
assertNotNull(getRoster);
GetMapping getRosterAnnotation = getRoster.getAnnotation(GetMapping.class);
assertNotNull(getRosterAnnotation);
assertEquals("/lessons/{lessonId}/roster", getRosterAnnotation.value()[0]);
assertEquals("@ss.hasPermi('train:attendance:list')", getRoster.getAnnotation(PreAuthorize.class).value());
}
}
@@ -1,15 +1,20 @@
package com.ruoyi.train.service.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -94,6 +99,19 @@ public class AttendanceActionContractTest
checkinsByEnrollLesson.put(c.getEnrollmentId() + "_" + c.getLessonId(), c);
return 1;
}
if ("selectCheckinsByLessonId".equals(name))
{
Long lid = (Long) args[0];
List<TrainStudentCheckin> list = new ArrayList<>();
for (TrainStudentCheckin c : checkinsById.values())
{
if (lid.equals(c.getLessonId()))
{
list.add(c);
}
}
return list;
}
return defaultReturn(method);
}
});
@@ -108,6 +126,28 @@ public class AttendanceActionContractTest
{
return enrollments.get((Long) args[0]);
}
if ("selectEnrollmentItemList".equals(name))
{
com.ruoyi.train.domain.query.EnrollmentQuery q = (com.ruoyi.train.domain.query.EnrollmentQuery) args[0];
List<com.ruoyi.train.domain.vo.EnrollmentItemVO> list = new ArrayList<>();
for (TrainEnrollment e : enrollments.values())
{
if (q.getPlanId() != null && q.getPlanId().equals(e.getPlanId()))
{
com.ruoyi.train.domain.vo.EnrollmentItemVO vo = new com.ruoyi.train.domain.vo.EnrollmentItemVO();
vo.setEnrollmentId(e.getEnrollmentId());
vo.setPlanId(e.getPlanId());
vo.setStudentId(e.getStudentId() != null ? e.getStudentId() : "S_" + e.getEnrollmentId());
vo.setStudentName("学员_" + e.getEnrollmentId());
vo.setGender("男");
vo.setOrgName("电务段");
vo.setPhone("13812345678");
vo.setEnrollmentStatus(e.getEnrollmentStatus() != null ? e.getEnrollmentStatus() : 1);
list.add(vo);
}
}
return list;
}
return defaultReturn(method);
}
});
@@ -457,4 +497,101 @@ public class AttendanceActionContractTest
assertEquals(AttendanceErrorCode.ATTENDANCE_STATUS_INVALID, (int) e.getCode());
}
}
@Test
public void testGetLessonRoster_DistinguishesUncheckedFromAbsent_AndEnablesSignIn()
{
// 场景:E_A(201)未打卡,新增学员 E_A2(203)属于PLAN_A且有缺勤打卡记录(status=0),学员 E_A3(204)已放弃(status=3)
TrainEnrollment ea2 = new TrainEnrollment();
ea2.setEnrollmentId(203L);
ea2.setPlanId("PLAN_A");
ea2.setEnrollmentStatus(1);
enrollments.put(203L, ea2);
TrainEnrollment ea3 = new TrainEnrollment();
ea3.setEnrollmentId(204L);
ea3.setPlanId("PLAN_A");
ea3.setEnrollmentStatus(3); // 已放弃
enrollments.put(204L, ea3);
// 为 ea2 创建一条显式缺勤(attendance_status=0, 无signInTime)
TrainStudentCheckin absentCheckin = new TrainStudentCheckin();
absentCheckin.setCheckinId(888L);
absentCheckin.setEnrollmentId(203L);
absentCheckin.setLessonId(1001L);
absentCheckin.setAttendanceStatus(0); // 0: 缺勤
absentCheckin.setSignInTime(null);
checkinsById.put(888L, absentCheckin);
checkinsByEnrollLesson.put("203_1001", absentCheckin);
List<com.ruoyi.train.domain.vo.AttendanceLessonRosterVO> roster = appService.getLessonRoster(1001L);
assertNotNull(roster);
// 应包含 201 和 203,排除已放弃的 204
assertEquals(2, roster.size());
// 验证学员 201 (无 checkin 记录):
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r201 = roster.stream()
.filter(r -> r.getEnrollmentId().equals(201L)).findFirst().orElse(null);
assertNotNull(r201);
assertNull("无checkin记录的学员,checkinId必须为null", r201.getCheckinId());
assertNull("未签到(NO_CHECKIN)绝不能被渲染为0缺勤,必须为null", r201.getAttendanceStatus());
assertTrue("未打卡学员必须允许执行首次签到", Boolean.TRUE.equals(r201.getCanSignIn()));
assertFalse(Boolean.TRUE.equals(r201.getCanSignOut()));
assertFalse(Boolean.TRUE.equals(r201.getCanAdjust()));
// 验证学员 203 (有缺勤记录):
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO r203 = roster.stream()
.filter(r -> r.getEnrollmentId().equals(203L)).findFirst().orElse(null);
assertNotNull(r203);
assertEquals(Long.valueOf(888L), r203.getCheckinId());
assertEquals(Integer.valueOf(0), r203.getAttendanceStatus()); // 显式缺勤
assertTrue(Boolean.TRUE.equals(r203.getCanSignIn())); // 无signInTime可补签
assertTrue(Boolean.TRUE.equals(r203.getCanAdjust())); // 已有记录可调整
}
@Test
public void testSignInThenRosterUpdatesFact_DisablesSignIn()
{
// 初始未签到
List<com.ruoyi.train.domain.vo.AttendanceLessonRosterVO> rosterBefore = appService.getLessonRoster(1001L);
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO rBefore = rosterBefore.stream()
.filter(r -> r.getEnrollmentId().equals(201L)).findFirst().orElse(null);
assertNotNull(rBefore);
assertTrue(Boolean.TRUE.equals(rBefore.getCanSignIn()));
// 执行首次签到
AttendanceSignInDTO signInDto = new AttendanceSignInDTO();
signInDto.setEnrollmentId(201L);
signInDto.setLessonId(1001L);
signInDto.setCheckinWay(4); // 代签
TrainStudentCheckin created = appService.signIn(signInDto, "train_admin");
assertNotNull(created);
assertNotNull(created.getCheckinId());
// 刷新查询 roster
List<com.ruoyi.train.domain.vo.AttendanceLessonRosterVO> rosterAfter = appService.getLessonRoster(1001L);
com.ruoyi.train.domain.vo.AttendanceLessonRosterVO rAfter = rosterAfter.stream()
.filter(r -> r.getEnrollmentId().equals(201L)).findFirst().orElse(null);
assertNotNull(rAfter);
assertEquals(created.getCheckinId(), rAfter.getCheckinId());
assertEquals(Integer.valueOf(1), rAfter.getAttendanceStatus()); // 正常出勤
assertNotNull(rAfter.getSignInTime());
assertFalse("已成功签到的学员禁止重复执行签到", Boolean.TRUE.equals(rAfter.getCanSignIn()));
assertTrue("已签到未签退的学员允许签退", Boolean.TRUE.equals(rAfter.getCanSignOut()));
assertTrue(Boolean.TRUE.equals(rAfter.getCanAdjust()));
}
@Test
public void testGetLessonRoster_LessonNotFound_ThrowsException()
{
try
{
appService.getLessonRoster(99999L);
fail("不存在的课次应当抛出异常");
}
catch (ServiceException e)
{
assertEquals(AttendanceErrorCode.LESSON_NOT_FOUND, (int) e.getCode());
}
}
}