From 71f3abcf3e85ee270cd2b6ecd9de38e90648bba8 Mon Sep 17 00:00:00 2001 From: Valtteri Kantanen Date: Thu, 14 Nov 2024 16:56:49 +0200 Subject: [PATCH] [Close to graduation] Add earliest HOPS attainment and number of absent semesters --- .../services/populations/closeToGraduation.ts | 48 ++++++++++++------- .../components/CloseToGraduation/index.jsx | 24 +++++++++- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/services/backend/src/services/populations/closeToGraduation.ts b/services/backend/src/services/populations/closeToGraduation.ts index bacc18fb26..9f0ab0f14d 100644 --- a/services/backend/src/services/populations/closeToGraduation.ts +++ b/services/backend/src/services/populations/closeToGraduation.ts @@ -2,15 +2,16 @@ import { col, Op, where } from 'sequelize' import { Course, Credit, Student, Studyplan, SISStudyRight, SISStudyRightElement } from '../../models' import { Name } from '../../shared/types' -import { CreditTypeCode, DegreeProgrammeType, ExtentCode, SemesterEnrollment } from '../../types' +import { CreditTypeCode, DegreeProgrammeType, EnrollmentType, ExtentCode, SemesterEnrollment } from '../../types' import { redisClient } from '../redis' import { getCurriculumVersion } from './shared' export const CLOSE_TO_GRADUATION_REDIS_KEY = 'CLOSE_TO_GRADUATION_DATA' -type LatestAttainmentDates = { - total?: Date - hops?: Date +type AttainmentDates = { + latestTotal?: Date + latestHops?: Date + earliestHops?: Date } type AccumulatorType = { @@ -39,7 +40,8 @@ type AccumulatorType = { startedAt: Date degreeProgrammeType: DegreeProgrammeType } - latestAttainmentDates: LatestAttainmentDates + attainmentDates: AttainmentDates + numberOfAbsentSemesters: number curriculumPeriod: string | null credits: { hops: number @@ -47,14 +49,14 @@ type AccumulatorType = { } } -const findThesisAndLatestAttainments = ( +const findThesisAndLatestAndEarliestAttainments = ( studyPlan: Studyplan, attainments: Credit[], degreeProgrammeType: DegreeProgrammeType, studyRightId: string ) => { let thesisData: Credit | undefined - const latestAttainmentDates: LatestAttainmentDates = {} + const attainmentDates: AttainmentDates = {} const thesisCodes = { [DegreeProgrammeType.BACHELOR]: 'urn:code:course-unit-type:bachelors-thesis', [DegreeProgrammeType.MASTER]: 'urn:code:course-unit-type:masters-thesis', @@ -67,25 +69,29 @@ const findThesisAndLatestAttainments = ( ) { thesisData = attainment } - if (!latestAttainmentDates.total) { - latestAttainmentDates.total = attainment.attainment_date + if (!attainmentDates.latestTotal) { + attainmentDates.latestTotal = attainment.attainment_date } - if (!latestAttainmentDates.hops && studyPlan.included_courses.includes(attainment.course?.code)) { - latestAttainmentDates.hops = attainment.attainment_date - } - if (thesisData && latestAttainmentDates.total && latestAttainmentDates.hops) { - break + if (studyPlan.included_courses.includes(attainment.course?.code)) { + if (!attainmentDates.latestHops) { + attainmentDates.latestHops = attainment.attainment_date + } + if ( + !attainmentDates.earliestHops || + attainment.attainment_date.getTime() < attainmentDates.earliestHops.getTime() + ) { + attainmentDates.earliestHops = attainment.attainment_date + } } } - return { latestAttainmentDates, thesisData } + return { attainmentDates, thesisData } } const formatStudent = (student: Student) => { const { studentnumber: studentNumber, abbreviatedname: name, - sis_person_id, email, phone_number: phoneNumber, @@ -101,6 +107,11 @@ const formatStudent = (student: Student) => { semesterEnrollments, } = studyRight + const numberOfAbsentSemesters = (semesterEnrollments ?? []).reduce( + (acc, enrollment) => (enrollment.type === EnrollmentType.ABSENT ? acc + 1 : acc), + 0 + ) + const { code: programmeCode, name: programmeName, @@ -108,7 +119,7 @@ const formatStudent = (student: Student) => { startDate: programmeStartDate, degreeProgrammeType, } = studyRightElements[0] - const { latestAttainmentDates, thesisData } = findThesisAndLatestAttainments( + const { attainmentDates, thesisData } = findThesisAndLatestAndEarliestAttainments( studyPlan, student.credits, degreeProgrammeType, @@ -136,7 +147,8 @@ const formatStudent = (student: Student) => { startedAt: programmeStartDate, degreeProgrammeType, }, - latestAttainmentDates, + attainmentDates, + numberOfAbsentSemesters, curriculumPeriod: getCurriculumVersion(studyPlan.curriculum_period_id), credits: { hops: studyPlan.completed_credits, diff --git a/services/frontend/src/components/CloseToGraduation/index.jsx b/services/frontend/src/components/CloseToGraduation/index.jsx index 242fa94e1d..49bb261444 100644 --- a/services/frontend/src/components/CloseToGraduation/index.jsx +++ b/services/frontend/src/components/CloseToGraduation/index.jsx @@ -144,6 +144,13 @@ const getColumns = ( getRowContent: row => getSemesterEnrollmentsContent(row.student, row.studyright), getRowVal: row => getSemesterEnrollmentsVal(row.student, row.studyright), }, + { + key: 'semestersAbsent', + title: 'Semesters\nabsent', + getRowVal: row => row.numberOfAbsentSemesters, + helpText: + 'The number of semesters the student has been absent (both statutory and non-statutory absences) during their study right', + }, { key: 'semesterEnrollmentsForExcel', title: 'Enrollment status', @@ -188,19 +195,32 @@ const getColumns = ( { key: 'latestAttainmentHops', title: 'HOPS', - getRowVal: row => reformatDate(row.latestAttainmentDates.hops, ISO_DATE_FORMAT), + getRowVal: row => reformatDate(row.attainmentDates.latestHops, ISO_DATE_FORMAT), filterType: 'date', helpText: 'The date when the student last completed a course in their primary study plan', }, { key: 'latestAttainmentTotal', title: 'Total', - getRowVal: row => reformatDate(row.latestAttainmentDates.total, ISO_DATE_FORMAT), + getRowVal: row => reformatDate(row.attainmentDates.latestTotal, ISO_DATE_FORMAT), filterType: 'date', helpText: 'The date when the student last completed any course at the university', }, ], }, + { + key: 'earliestAttainmentDates', + title: 'Earliest\nattainment date', + children: [ + { + key: 'earliestAttainmentHops', + title: 'HOPS', + getRowVal: row => reformatDate(row.attainmentDates.earliestHops, ISO_DATE_FORMAT), + filterType: 'date', + helpText: 'The date when the student first completed a course in their primary study plan', + }, + ], + }, ] }