diff --git a/common/courses/states.ts b/common/courses/states.ts index 53b97ebca..ac8ec5462 100644 --- a/common/courses/states.ts +++ b/common/courses/states.ts @@ -21,16 +21,17 @@ import { ConversationInfos, markConversationAsReadOnlyForPupils, markConversationAsWriteable, + removeParticipantFromCourseChat, sendSystemMessage, updateConversation, } from '../chat'; import systemMessages from '../chat/localization'; import { cancelAppointment } from '../appointment/cancel'; import { User, userForStudent } from '../user'; -import { addGroupAppointmentsOrganizer } from '../appointment/participants'; +import { addGroupAppointmentsOrganizer, removeGroupAppointmentsOrganizer } from '../appointment/participants'; import { sendPupilCoursePromotion, sendSubcourseCancelNotifications } from './notifications'; import * as Notification from '../../common/notification'; -import { deleteAchievementsForSubcourse } from '../../common/achievement/delete'; +import { deleteAchievementsForSubcourse, deleteCourseAchievementsForStudents } from '../../common/achievement/delete'; import { ValidationError } from 'apollo-server-express'; import { getContextForGroupAppointmentReminder } from '../appointment/util'; import { isSubcourseSilent } from './util'; @@ -225,9 +226,9 @@ export async function canCancel(subcourse: Subcourse): Promise { return { allowed: true }; } -export async function cancelSubcourse(user: User, subcourse: Subcourse) { +export async function cancelSubcourse(user: User, subcourse: Subcourse, force?: boolean) { const can = await canCancel(subcourse); - if (!can.allowed) { + if (!can.allowed && !force) { throw new Error(`Cannot cancel Subcourse(${subcourse.id}), reason: ${can.reason}`); } @@ -328,3 +329,19 @@ export async function addSubcourseInstructor(user: User | null, subcourse: Subco }); logger.info(`Student (${newInstructor.id}) was added as an instructor to Subcourse(${subcourse.id}) by User(${user?.userID})`); } + +export async function deleteSubcourseInstructor(user: User | null, subcourse: Subcourse, instructorToBeRemoved: Student) { + const subcourseId = subcourse.id; + const studentId = instructorToBeRemoved.id; + const instructorUser = userForStudent(instructorToBeRemoved); + await prisma.subcourse_instructors_student.delete({ where: { subcourseId_studentId: { subcourseId, studentId } } }); + await removeGroupAppointmentsOrganizer(subcourseId, instructorUser.userID, instructorUser.email); + if (subcourse.conversationId) { + await removeParticipantFromCourseChat(instructorUser, subcourse.conversationId); + } + logger.info(`Student(${studentId}) was deleted from Subcourse(${subcourseId}) by User(${user?.userID})`); + + await deleteCourseAchievementsForStudents(subcourseId, [instructorUser.userID]); + + return true; +} diff --git a/common/student/activation.ts b/common/student/activation.ts index 4420c8fef..1bfed2f6f 100644 --- a/common/student/activation.ts +++ b/common/student/activation.ts @@ -10,6 +10,7 @@ import { isZoomFeatureActive } from '../zoom/util'; import { userForStudent } from '../user'; import { CertificateState } from '../certificate'; import { removeAllPushSubcriptions } from '../notification/channels/push'; +import { cancelSubcourse, deleteSubcourseInstructor, subcourseOver } from '../courses/states'; export async function deactivateStudent( student: Student, @@ -27,7 +28,12 @@ export async function deactivateStudent( await Notification.cancelRemindersFor(userForStudent(student)); // Setting 'active' to false will not send out any notifications during deactivation - student.active = false; + const updatedStudent = await prisma.student.update({ + data: { active: false }, + where: { id: student.id }, + }); + + await removeAllPushSubcriptions(userForStudent(student)); // Dissolve matches for the student. const matches = await prisma.match.findMany({ @@ -46,52 +52,27 @@ export async function deactivateStudent( data: { state: CertificateState.manual }, }); - //Delete course records for the student. - const courses = await prisma.course.findMany({ + // Cancel subcourses + const subcourses = await prisma.subcourse.findMany({ where: { - course_instructors_student: { + cancelled: false, + subcourse_instructors_student: { some: { studentId: student.id, }, }, }, include: { - course_instructors_student: true, + subcourse_instructors_student: true, }, }); - for (let i = 0; i < courses.length; i++) { - if (courses[i].course_instructors_student.length > 1) { - await prisma.course.update({ - where: { - id: courses[i].id, - }, - data: { - course_instructors_student: { - deleteMany: { - studentId: student.id, - }, - }, - }, - }); - } else { - await prisma.course.update({ - where: { - id: courses[i].id, - }, - data: { - subcourse: { - updateMany: { - where: {}, - data: { - cancelled: true, - }, - }, - }, - courseState: course_coursestate_enum.cancelled, - }, - }); - // TODO Notify participants + for (const subcourse of subcourses) { + // There are multiple instructors, so just remove the student from the subcourse + if (subcourse.subcourse_instructors_student.length > 1) { + await deleteSubcourseInstructor(userForStudent(student), subcourse, student); + } else if (!(await subcourseOver(subcourse))) { + await cancelSubcourse(userForStudent(student), subcourse, true); } } @@ -99,13 +80,6 @@ export async function deactivateStudent( await deleteZoomUser(student); } - await removeAllPushSubcriptions(userForStudent(student)); - - const updatedStudent = await prisma.student.update({ - data: { active: false }, - where: { id: student.id }, - }); - await logTransaction('deActivate', userForStudent(student), { newStatus: false, deactivationReason: reason }); return updatedStudent; diff --git a/graphql/subcourse/mutations.ts b/graphql/subcourse/mutations.ts index 5926ac3bd..0d82bd9cb 100644 --- a/graphql/subcourse/mutations.ts +++ b/graphql/subcourse/mutations.ts @@ -11,7 +11,14 @@ import { leaveSubcourseWaitinglist, mentorLeaveSubcourse, } from '../../common/courses/participants'; -import { addSubcourseInstructor, cancelSubcourse, deleteSubcourse, editSubcourse, publishSubcourse } from '../../common/courses/states'; +import { + addSubcourseInstructor, + cancelSubcourse, + deleteSubcourse, + deleteSubcourseInstructor, + editSubcourse, + publishSubcourse, +} from '../../common/courses/states'; import { getLogger } from '../../common/logger/logger'; import { prisma } from '../../common/prisma'; import { userForPupil, userForStudent } from '../../common/user'; @@ -155,15 +162,7 @@ export class MutateSubcourseResolver { const subcourse = await getSubcourse(subcourseId); await hasAccess(context, 'Subcourse', subcourse); const instructorToBeRemoved = await getStudent(studentId); - const instructorUser = userForStudent(instructorToBeRemoved); - await prisma.subcourse_instructors_student.delete({ where: { subcourseId_studentId: { subcourseId, studentId } } }); - await removeGroupAppointmentsOrganizer(subcourseId, instructorUser.userID, instructorUser.email); - if (subcourse.conversationId) { - await removeParticipantFromCourseChat(instructorUser, subcourse.conversationId); - } - logger.info(`Student(${studentId}) was deleted from Subcourse(${subcourseId}) by User(${context.user.userID})`); - - await deleteCourseAchievementsForStudents(subcourseId, [instructorUser.userID]); + await deleteSubcourseInstructor(context.user, subcourse, instructorToBeRemoved); return true; }