import { Op, type Includeable, type InferAttributes, type InferCreationAttributes, type WhereAttributeHash, } from 'sequelize'; import db from '@/db/models'; import { removeRecord, deleteRecordsByIds, autocompleteByField, findOwnedByPk, tenantWhere, } from '@/db/api/shared/repository'; import { BULK_IMPORT_TIMESTAMP_STEP_MS } from '@/shared/constants/database'; import { resolvePagination } from '@/shared/constants/pagination'; import Utils from '@/db/utils'; import type { ClassSubjects } from '@/db/models/class_subjects'; import type { CurrentUser, DbApiOptions } from '@/db/api/types'; type ClassSubjectsData = Partial> & { organization?: string | null; class?: string | null; subject?: string | null; teacher?: string | null; }; interface ClassSubjectsFilter { limit?: number | string; page?: number | string; id?: string; class?: string; subject?: string; teacher?: string; active?: boolean | string; status?: string; organization?: string; createdAtRange?: Array; field?: string; sort?: string; } const NO_USER: CurrentUser = { id: null }; class Class_subjectsDBApi { static async create( data: ClassSubjectsData, options?: DbApiOptions, ): Promise { const currentUser = options?.currentUser ?? NO_USER; const transaction = options?.transaction; const class_subjects = await db.class_subjects.create( { id: data.id || undefined, status: data.status || null, importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, }, { transaction }, ); await class_subjects.setOrganization( currentUser.organizationId ?? undefined, { transaction }, ); await class_subjects.setClass(data.class ?? undefined, { transaction }); await class_subjects.setSubject(data.subject ?? undefined, { transaction }); await class_subjects.setTeacher(data.teacher ?? undefined, { transaction }); return class_subjects; } static async bulkImport( data: ClassSubjectsData[], options?: DbApiOptions, ): Promise { const currentUser = options?.currentUser ?? NO_USER; const transaction = options?.transaction; const class_subjectsData = data.map((item, index) => ({ id: item.id || undefined, status: item.status || null, importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, createdAt: new Date(Date.now() + index * BULK_IMPORT_TIMESTAMP_STEP_MS), })); return db.class_subjects.bulkCreate(class_subjectsData, { transaction }); } static async update( id: string, data: ClassSubjectsData, options?: DbApiOptions, ): Promise { const currentUser = options?.currentUser ?? NO_USER; const transaction = options?.transaction; const globalAccess = currentUser.app_role?.globalAccess; const class_subjects = await findOwnedByPk(db.class_subjects, id, options); if (!class_subjects) { return null; } const updatePayload: Partial> = {}; if (data.status !== undefined) updatePayload.status = data.status; updatePayload.updatedById = currentUser.id; await class_subjects.update(updatePayload, { transaction }); if (data.organization !== undefined) { const orgId = globalAccess ? data.organization : currentUser.organizationId; await class_subjects.setOrganization(orgId ?? undefined, { transaction }); } if (data.class !== undefined) { await class_subjects.setClass(data.class ?? undefined, { transaction }); } if (data.subject !== undefined) { await class_subjects.setSubject(data.subject ?? undefined, { transaction, }); } if (data.teacher !== undefined) { await class_subjects.setTeacher(data.teacher ?? undefined, { transaction, }); } return class_subjects; } static async deleteByIds( ids: string[], options?: DbApiOptions, ): Promise { return deleteRecordsByIds(db.class_subjects, ids, options); } static async remove( id: string, options?: DbApiOptions, ): Promise { return removeRecord(db.class_subjects, id, options); } static async findBy( where: WhereAttributeHash, options?: DbApiOptions, ): Promise | null> { const transaction = options?.transaction; const class_subjects = await db.class_subjects.findOne({ where: { ...where, ...tenantWhere(options?.currentUser) }, transaction, }); if (!class_subjects) { return null; } const output: Record = class_subjects.get({ plain: true }); const [ timetable_periods_class_subject, attendance_sessions_class_subject, assessments_class_subject, organization, class_, subject, teacher, ] = await Promise.all([ class_subjects.getTimetable_periods_class_subject({ transaction }), class_subjects.getAttendance_sessions_class_subject({ transaction }), class_subjects.getAssessments_class_subject({ transaction }), class_subjects.getOrganization({ transaction }), class_subjects.getClass({ transaction }), class_subjects.getSubject({ transaction }), class_subjects.getTeacher({ transaction }), ]); output.timetable_periods_class_subject = timetable_periods_class_subject; output.attendance_sessions_class_subject = attendance_sessions_class_subject; output.assessments_class_subject = assessments_class_subject; output.organization = organization; output.class = class_; output.subject = subject; output.teacher = teacher; return output; } static async findAll( filter: ClassSubjectsFilter, globalAccess: boolean, options?: DbApiOptions, ): Promise<{ rows: ClassSubjects[]; count: number }> { const { limit, offset } = resolvePagination(filter.limit, filter.page); let where: WhereAttributeHash = {}; const userOrganizations = options?.currentUser?.organizations?.id ?? null; if (userOrganizations && options?.currentUser?.organizationId) { where.organizationId = options.currentUser.organizationId; } const include: Includeable[] = [ { model: db.organizations, as: 'organization' }, { model: db.classes, as: 'class', where: filter.class ? { [Op.or]: [ { id: { [Op.in]: filter.class.split('|').map((t) => Utils.uuid(t)), }, }, { name: { [Op.or]: filter.class .split('|') .map((t) => ({ [Op.iLike]: `%${t}%` })), }, }, ], } : {}, }, { model: db.subjects, as: 'subject', where: filter.subject ? { [Op.or]: [ { id: { [Op.in]: filter.subject.split('|').map((t) => Utils.uuid(t)), }, }, { name: { [Op.or]: filter.subject .split('|') .map((t) => ({ [Op.iLike]: `%${t}%` })), }, }, ], } : {}, }, { model: db.staff, as: 'teacher', where: filter.teacher ? { [Op.or]: [ { id: { [Op.in]: filter.teacher.split('|').map((t) => Utils.uuid(t)), }, }, { employee_number: { [Op.or]: filter.teacher .split('|') .map((t) => ({ [Op.iLike]: `%${t}%` })), }, }, ], } : {}, }, ]; if (filter.id) { where = { ...where, id: Utils.uuid(filter.id) }; } if (filter.active !== undefined) { where = { ...where, active: filter.active === true || filter.active === 'true', }; } if (filter.status) { where = { ...where, status: filter.status }; } if (filter.organization) { const listItems = filter.organization .split('|') .map((item) => Utils.uuid(item)); where = { ...where, organizationId: { [Op.or]: listItems } }; } if (filter.createdAtRange) { const [start, end] = filter.createdAtRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, createdAt: { [Op.gte]: start } }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, createdAt: { ...(typeof where.createdAt === 'object' ? where.createdAt : {}), [Op.lte]: end, }, }; } } if (globalAccess) { delete where.organizationId; } const order: [string, string][] = filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']]; const { rows, count } = await db.class_subjects.findAndCountAll({ where, include, distinct: true, order, transaction: options?.transaction, limit: !options?.countOnly && limit ? limit : undefined, offset: !options?.countOnly && offset ? offset : undefined, }); return { rows: options?.countOnly ? [] : rows, count }; } static async findAllAutocomplete( query: string | undefined, limit: number | undefined, offset: number | undefined, globalAccess: boolean, organizationId: string | undefined, ): Promise> { return autocompleteByField( db.class_subjects, 'status', query, limit, offset, globalAccess, organizationId, ); } } export default Class_subjectsDBApi;