修复分析:适配Kingbase日期统计并校准已开课考勤口径
This commit is contained in:
1 parent
956ad40663
commit
b815264fbd
2 files changed
+119
-23
No files matched your search
@@ -66,7 +66,7 @@
|
||||
SUM(CASE WHEN c.attendance_status != 1 THEN 1 ELSE 0 END) AS abnormal_count
|
||||
FROM train_student_checkin c
|
||||
WHERE c.sign_in_time IS NOT NULL
|
||||
AND c.sign_in_time >= CURRENT_DATE - INTERVAL '30 days'
|
||||
AND c.sign_in_time >= CURRENT_DATE - INTERVAL '30' DAY
|
||||
GROUP BY TO_CHAR(c.sign_in_time, 'YYYY-MM-DD')
|
||||
ORDER BY stat_date ASC
|
||||
</select>
|
||||
@@ -107,7 +107,7 @@
|
||||
SELECT
|
||||
COALESCE(cr.room_name, ts.site_name, '未知场地') AS venue_name,
|
||||
count(1) AS usage_count,
|
||||
ROUND(SUM(EXTRACT(EPOCH FROM (l.end_time - l.start_time)) / 3600.0)::numeric, 1) AS total_hours
|
||||
ROUND(SUM((EXTRACT(EPOCH FROM l.end_time) - EXTRACT(EPOCH FROM l.start_time)) / 3600.0)::numeric, 1) AS total_hours
|
||||
FROM train_lesson l
|
||||
LEFT JOIN train_classroom_info cr ON cr.class_room_id = l.class_room_id
|
||||
LEFT JOIN train_training_site ts ON ts.site_id = l.site_id
|
||||
@@ -123,10 +123,10 @@
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE 1 = 1
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -143,10 +143,10 @@
|
||||
JOIN train_enrollment e ON e.plan_id = pc.plan_id
|
||||
WHERE l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -162,11 +162,12 @@
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE c.attendance_status = 1
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -182,11 +183,12 @@
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE c.attendance_status != 1
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -205,11 +207,12 @@
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE c.sign_in_time IS NOT NULL
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -229,11 +232,12 @@
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE 1 = 1
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -256,11 +260,12 @@
|
||||
JOIN train_lesson l ON l.plan_course_id = pc.plan_course_id
|
||||
LEFT JOIN train_student_checkin c ON c.lesson_id = l.lesson_id
|
||||
WHERE p.del_flag = '0'
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND p.plan_id = #{dto.planId}
|
||||
@@ -284,11 +289,12 @@
|
||||
JOIN train_lesson l ON l.plan_course_id = pc.plan_course_id
|
||||
LEFT JOIN train_student_checkin c ON c.lesson_id = l.lesson_id
|
||||
WHERE crs.del_flag = '0'
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND pc.plan_id = #{dto.planId}
|
||||
@@ -319,11 +325,12 @@
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
JOIN train_plan_course pc ON pc.plan_course_id = l.plan_course_id
|
||||
WHERE c.attendance_status IN (0, 2, 3, 4)
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
<if test="dto.startDate != null and dto.startDate != ''">
|
||||
AND l.start_time >= TO_TIMESTAMP(#{dto.startDate} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time >= TO_DATE(#{dto.startDate}, 'YYYY-MM-DD')
|
||||
</if>
|
||||
<if test="dto.endDate != null and dto.endDate != ''">
|
||||
AND l.start_time <= TO_TIMESTAMP(#{dto.endDate} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND l.start_time < TO_DATE(#{dto.endDate}, 'YYYY-MM-DD') + INTERVAL '1' DAY
|
||||
</if>
|
||||
<if test="dto.planId != null and dto.planId != ''">
|
||||
AND p.plan_id = #{dto.planId}
|
||||
@@ -376,7 +383,9 @@
|
||||
COUNT(1) AS count_num
|
||||
FROM train_student_checkin c
|
||||
JOIN train_enrollment e ON e.enrollment_id = c.enrollment_id
|
||||
JOIN train_lesson l ON l.lesson_id = c.lesson_id
|
||||
WHERE e.student_id = #{studentId}
|
||||
AND l.start_time <= CURRENT_TIMESTAMP
|
||||
GROUP BY c.attendance_status
|
||||
</select>
|
||||
|
||||
@@ -393,7 +402,9 @@
|
||||
SELECT
|
||||
a.accommodation_id AS accommodation_id,
|
||||
a.dorm_id AS dorm_id,
|
||||
d.dorm_name AS room_name,
|
||||
d.dorm_building AS dorm_building,
|
||||
d.dorm_floor AS dorm_floor,
|
||||
d.dorm_room AS dorm_room,
|
||||
a.bed_no AS bed_no,
|
||||
a.accommodation_status AS accommodation_status,
|
||||
auth.auth_status AS auth_status
|
||||
|
||||
+87
-2
@@ -169,12 +169,95 @@ public class TrainAnalysisServiceTest
|
||||
|
||||
// 住宿与门禁
|
||||
assertTrue(vo.getHasAccommodation());
|
||||
assertEquals("101", vo.getRoomName());
|
||||
assertEquals("学员1号楼", vo.getDormBuilding());
|
||||
assertEquals("1层", vo.getDormFloor());
|
||||
assertEquals("101", vo.getDormRoom());
|
||||
assertEquals("学员1号楼 / 1层 / 101室", vo.getRoomName());
|
||||
assertEquals("A床", vo.getBedNo());
|
||||
assertEquals("已入住", vo.getAccommodationStatusName());
|
||||
assertEquals("生效中", vo.getAuthStatusName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttendanceDateFilterNormalization_SingleDayMultiDayCrossMonthFuture()
|
||||
{
|
||||
// 1. 空日期 -> 默认最近 30 天
|
||||
AttendanceFilterDto emptyFilter = new AttendanceFilterDto();
|
||||
AttendanceSummaryVo voEmpty = service.getAttendanceSummary(emptyFilter);
|
||||
assertNotNull(voEmpty);
|
||||
|
||||
// 2. 单日 (startDate == endDate)
|
||||
AttendanceFilterDto singleDay = new AttendanceFilterDto();
|
||||
singleDay.setStartDate("2026-09-20");
|
||||
singleDay.setEndDate("2026-09-20");
|
||||
AttendanceSummaryVo voSingle = service.getAttendanceSummary(singleDay);
|
||||
assertNotNull(voSingle);
|
||||
|
||||
// 3. 跨月范围
|
||||
AttendanceFilterDto crossMonth = new AttendanceFilterDto();
|
||||
crossMonth.setStartDate("2026-08-15");
|
||||
crossMonth.setEndDate("2026-09-20");
|
||||
AttendanceSummaryVo voCross = service.getAttendanceSummary(crossMonth);
|
||||
assertNotNull(voCross);
|
||||
|
||||
// 4. 未来日期范围
|
||||
AttendanceFilterDto future = new AttendanceFilterDto();
|
||||
future.setStartDate("2026-10-01");
|
||||
future.setEndDate("2026-10-10");
|
||||
AttendanceSummaryVo voFuture = service.getAttendanceSummary(future);
|
||||
assertNotNull(voFuture);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttendanceExpectedAttendanceCalculation_ExcludesFutureLessons()
|
||||
{
|
||||
// 当筛选未来日期或无开课时,应到人次为 0
|
||||
mockMapper.expectedTotal = 0L;
|
||||
mockMapper.normalTotal = 0L;
|
||||
mockMapper.abnormalTotal = 0L;
|
||||
|
||||
AttendanceFilterDto filter = new AttendanceFilterDto();
|
||||
filter.setStartDate("2026-09-21");
|
||||
filter.setEndDate("2026-09-25");
|
||||
|
||||
AttendanceSummaryVo summary = service.getAttendanceSummary(filter);
|
||||
assertEquals(Long.valueOf(0), summary.getExpectedAttendTotal());
|
||||
assertEquals(Long.valueOf(0), summary.getNormalAttendTotal());
|
||||
assertEquals(Double.valueOf(0.0), summary.getAttendanceRate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCheckinAndExplicitAbsenceCalculation_SeparationSafe()
|
||||
{
|
||||
// 验证已开课场景:应到 100,正常签到 73,明确异常流水(迟到/缺勤等)4,无签到 = 100 - (73 + 4) = 23
|
||||
mockMapper.expectedTotal = 100L;
|
||||
mockMapper.normalTotal = 73L;
|
||||
mockMapper.abnormalTotal = 4L;
|
||||
|
||||
AttendanceFilterDto filter = new AttendanceFilterDto();
|
||||
filter.setPlanId("1631c0e1edcc47eea1760584b09f63b5");
|
||||
filter.setStartDate("2026-09-15");
|
||||
filter.setEndDate("2026-09-20");
|
||||
|
||||
AttendanceSummaryVo summary = service.getAttendanceSummary(filter);
|
||||
assertEquals(Long.valueOf(100), summary.getExpectedAttendTotal());
|
||||
assertEquals(Long.valueOf(73), summary.getNormalAttendTotal());
|
||||
// 异常人次合并统计:明确异常流水 (4) + 无签到 (23) = 27
|
||||
assertEquals(Long.valueOf(27), summary.getAbnormalAttendTotal());
|
||||
assertEquals(Double.valueOf(73.0), summary.getAttendanceRate());
|
||||
|
||||
// 验证学生 360 视图中明确缺勤与无签到的分离
|
||||
Student360Vo s360 = service.getStudent360("S2026001");
|
||||
assertNotNull(s360);
|
||||
assertEquals(Long.valueOf(10), s360.getExpectedAttendCount());
|
||||
assertEquals(Long.valueOf(8), s360.getNormalCount());
|
||||
assertEquals(Long.valueOf(1), s360.getAbsentCount()); // 明确缺勤流水
|
||||
assertEquals(Long.valueOf(1), s360.getNoCheckinCount()); // 无签到未打卡
|
||||
// 8 正常 + 1 明确缺勤 + 1 无签到 = 10 应到
|
||||
assertEquals(s360.getExpectedAttendCount().longValue(),
|
||||
s360.getNormalCount() + s360.getAbsentCount() + s360.getNoCheckinCount());
|
||||
}
|
||||
|
||||
// ==================== Mock Mapper Implementation ====================
|
||||
private static class MockTrainAnalysisMapper implements TrainAnalysisMapper
|
||||
{
|
||||
@@ -384,7 +467,9 @@ public class TrainAnalysisServiceTest
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("accommodation_id", 801L);
|
||||
map.put("dorm_id", "DORM01");
|
||||
map.put("room_name", "101");
|
||||
map.put("dorm_building", "学员1号楼");
|
||||
map.put("dorm_floor", "1层");
|
||||
map.put("dorm_room", "101");
|
||||
map.put("bed_no", "A床");
|
||||
map.put("accommodation_status", 1);
|
||||
map.put("auth_status", "ACTIVE");
|
||||
|
||||
Reference in new issue
Block a user