Add class numberOfSessions and fix attendance completion detection.
Sessions with full student attendance are now excluded from the pending list reliably by loading class rosters from the database.
This commit is contained in:
@@ -111,6 +111,22 @@ const getSessionEndDateTime = (session) => {
|
||||
return end;
|
||||
};
|
||||
|
||||
const normalizeId = (value) => {
|
||||
if (value == null) return '';
|
||||
if (typeof value === 'object' && value._id != null) return String(value._id);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const buildStudentsByClassId = async (sessions) => {
|
||||
const classIds = [...new Set(
|
||||
sessions.map((session) => normalizeId(session.class?._id || session.class)).filter(Boolean)
|
||||
)];
|
||||
if (!classIds.length) return new Map();
|
||||
|
||||
const classes = await Class.find({ _id: { $in: classIds } }).select('students').lean();
|
||||
return new Map(classes.map((cls) => [String(cls._id), cls.students || []]));
|
||||
};
|
||||
|
||||
const isSessionDue = (session) => {
|
||||
if (session.status === 'cancelled') return false;
|
||||
const end = getSessionEndDateTime(session);
|
||||
@@ -118,38 +134,42 @@ const isSessionDue = (session) => {
|
||||
return end.getTime() <= Date.now();
|
||||
};
|
||||
|
||||
const getClassStudentIds = (session) => {
|
||||
const getClassStudentIds = (session, studentsByClassId = null) => {
|
||||
const classId = normalizeId(session.class?._id || session.class);
|
||||
if (studentsByClassId && classId && studentsByClassId.has(classId)) {
|
||||
return (studentsByClassId.get(classId) || []).map(normalizeId).filter(Boolean);
|
||||
}
|
||||
const students = session.class?.students || [];
|
||||
return students.map((student) => String(student._id || student)).filter(Boolean);
|
||||
return students.map(normalizeId).filter(Boolean);
|
||||
};
|
||||
|
||||
const getRecordedUserIds = (session) =>
|
||||
(session.attendanceList || []).map((record) => String(record.user?._id || record.user)).filter(Boolean);
|
||||
(session.attendanceList || []).map((record) => normalizeId(record.user)).filter(Boolean);
|
||||
|
||||
const hasCompleteAttendance = (session) => {
|
||||
const studentIds = getClassStudentIds(session);
|
||||
const hasCompleteAttendance = (session, studentsByClassId = null) => {
|
||||
const studentIds = getClassStudentIds(session, studentsByClassId);
|
||||
if (!studentIds.length) return true;
|
||||
const recorded = new Set(getRecordedUserIds(session));
|
||||
return studentIds.every((id) => recorded.has(id));
|
||||
};
|
||||
|
||||
const isAttendancePending = (session) =>
|
||||
session.status === 'held' && isSessionDue(session) && !hasCompleteAttendance(session);
|
||||
const isAttendancePending = (session, studentsByClassId = null) =>
|
||||
isSessionDue(session) && !hasCompleteAttendance(session, studentsByClassId);
|
||||
|
||||
const enrichSessionAttendance = (session) => ({
|
||||
const enrichSessionAttendance = (session, studentsByClassId = null) => ({
|
||||
...session,
|
||||
isDue: isSessionDue(session),
|
||||
attendanceComplete: hasCompleteAttendance(session),
|
||||
attendancePending: isAttendancePending(session)
|
||||
attendanceComplete: hasCompleteAttendance(session, studentsByClassId),
|
||||
attendancePending: isAttendancePending(session, studentsByClassId)
|
||||
});
|
||||
|
||||
const filterByAttendanceScope = (sessions, scope) => {
|
||||
const filterByAttendanceScope = (sessions, scope, studentsByClassId = null) => {
|
||||
const dueSessions = sessions.filter(isSessionDue);
|
||||
if (scope === 'pending') {
|
||||
return dueSessions.filter((session) => !hasCompleteAttendance(session));
|
||||
return dueSessions.filter((session) => !hasCompleteAttendance(session, studentsByClassId));
|
||||
}
|
||||
if (scope === 'recorded') {
|
||||
return dueSessions.filter((session) => hasCompleteAttendance(session));
|
||||
return dueSessions.filter((session) => hasCompleteAttendance(session, studentsByClassId));
|
||||
}
|
||||
if (scope === 'due') {
|
||||
return dueSessions;
|
||||
@@ -213,7 +233,9 @@ const getAllSessions = async (queryParams) => {
|
||||
|
||||
let sessions = sortByClosestAttendance(matched);
|
||||
if (attendanceScope) {
|
||||
sessions = filterByAttendanceScope(sessions, attendanceScope).map(enrichSessionAttendance);
|
||||
const studentsByClassId = await buildStudentsByClassId(matched);
|
||||
sessions = filterByAttendanceScope(sessions, attendanceScope, studentsByClassId)
|
||||
.map((session) => enrichSessionAttendance(session, studentsByClassId));
|
||||
const totalCount = sessions.length;
|
||||
sessions = sessions.slice(skip, skip + limit);
|
||||
const meta = calculateMeta(totalCount, page, limit);
|
||||
@@ -328,6 +350,12 @@ const updateSessionAttendance = async (sessionId, attendanceList, recordedBy = n
|
||||
|
||||
await session.save();
|
||||
|
||||
const classItem = await Class.findById(session.class).select('students').lean();
|
||||
if (classItem && hasCompleteAttendance({ ...session.toObject(), class: classItem }) && session.status !== 'cancelled') {
|
||||
session.status = 'held';
|
||||
await session.save();
|
||||
}
|
||||
|
||||
eventEmitter.emit(EVENT_NAMES.ATTENDANCE_RECORDED, {
|
||||
sessionId: session._id,
|
||||
recordCount: list.length,
|
||||
|
||||
Reference in New Issue
Block a user