const db = require('../db/models'); const ForbiddenError = require('./notifications/errors/forbidden'); const ValidationError = require('./notifications/errors/validation'); const { STAFF_ATTENDANCE_DEFAULT_LIMIT, STAFF_ATTENDANCE_MAX_LIMIT, STAFF_ATTENDANCE_REPORT_ROLE_NAMES, STAFF_ATTENDANCE_STATUSES, STAFF_ATTENDANCE_TENANT_WIDE_ROLE_NAMES, } = require('../constants/staff-attendance'); function getOrganizationId(currentUser) { return currentUser?.organizations?.id || currentUser?.organization?.id || currentUser?.organizationsId || currentUser?.organizationId || null; } function getCampusId(currentUser) { if (Array.isArray(currentUser?.staff_user) && currentUser.staff_user[0]?.campusId) { return currentUser.staff_user[0].campusId; } return currentUser?.campusId || null; } function getRoleName(currentUser) { return currentUser?.app_role?.name; } function assertAuthenticatedTenantUser(currentUser) { if (currentUser?.id && getOrganizationId(currentUser)) { return; } throw new ForbiddenError(); } function canViewReports(currentUser) { return currentUser?.app_role?.globalAccess === true || STAFF_ATTENDANCE_REPORT_ROLE_NAMES.includes(getRoleName(currentUser)); } function hasTenantWideAccess(currentUser) { return currentUser?.app_role?.globalAccess === true || STAFF_ATTENDANCE_TENANT_WIDE_ROLE_NAMES.includes(getRoleName(currentUser)); } function parseLimit(value) { if (value === undefined) { return STAFF_ATTENDANCE_DEFAULT_LIMIT; } const limit = Number(value); if (!Number.isInteger(limit) || limit <= 0) { throw new ValidationError(); } return Math.min(limit, STAFF_ATTENDANCE_MAX_LIMIT); } function requiredDate(value) { if (value === undefined) { return null; } if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/u.test(value)) { throw new ValidationError(); } return value; } function applyVisibilityScope(where, currentUser) { if (!canViewReports(currentUser)) { where.userId = currentUser.id; return; } if (hasTenantWideAccess(currentUser)) { return; } const campusId = getCampusId(currentUser); if (campusId) { where.campusId = campusId; } } function applyDateFilter(where, filter) { const startDate = requiredDate(filter.startDate); const endDate = requiredDate(filter.endDate); if (startDate) { where.attendance_date = { ...(where.attendance_date || {}), [db.Sequelize.Op.gte]: startDate, }; } if (endDate) { where.attendance_date = { ...(where.attendance_date || {}), [db.Sequelize.Op.lte]: endDate, }; } } function applyStaffScope(where, currentUser) { where.organizationId = getOrganizationId(currentUser); if (!hasTenantWideAccess(currentUser)) { const campusId = getCampusId(currentUser); if (campusId) { where.campusId = campusId; } } } function toRecordDto(record) { const plainRecord = typeof record.get === 'function' ? record.get({ plain: true }) : record; return { id: plainRecord.id, date: plainRecord.attendance_date, status: plainRecord.status, note: plainRecord.note, user_name: plainRecord.user_name, user_role: plainRecord.user_role, organizationId: plainRecord.organizationId, campusId: plainRecord.campusId, userId: plainRecord.userId, createdAt: plainRecord.createdAt, updatedAt: plainRecord.updatedAt, }; } function countStatus(records, status) { return records.filter((record) => record.status === status).length; } module.exports = class StaffAttendanceService { static async listRecords(filter, currentUser) { assertAuthenticatedTenantUser(currentUser); const where = { organizationId: getOrganizationId(currentUser), }; applyVisibilityScope(where, currentUser); applyDateFilter(where, filter); const result = await db.staff_attendance_records.findAndCountAll({ where, limit: parseLimit(filter.limit), order: [['attendance_date', 'desc'], ['user_name', 'asc']], }); return { rows: result.rows.map(toRecordDto), count: result.count, }; } static async summary(filter, currentUser) { assertAuthenticatedTenantUser(currentUser); const recordsPayload = await this.listRecords(filter, currentUser); const staffWhere = {}; applyStaffScope(staffWhere, currentUser); staffWhere.status = 'active'; const staffCount = await db.staff.count({ where: staffWhere }); const records = recordsPayload.rows; const present = countStatus(records, STAFF_ATTENDANCE_STATUSES.PRESENT); const late = countStatus(records, STAFF_ATTENDANCE_STATUSES.LATE); const absent = countStatus(records, STAFF_ATTENDANCE_STATUSES.ABSENT); return { staffCount, recordsCount: recordsPayload.count, present, late, absent, }; } };