Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[N21-2242] Server: course sync add handling for substitute teachers #5352

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum SchulconnexGroupRole {
TEACHER = 'Lehr',
SUBSTITUTE_TEACHER = 'Vertretung',
STUDENT = 'Lern',
CLASS_LEADER = 'KlLeit',
SUPPORT_TEACHER = 'Foerd',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export const schulconnexResponseFactory = Factory.define<SchulconnexResponse>(()
rollen: [SchulconnexGroupRole.STUDENT],
ktid: 'ktid',
},
{
rollen: [SchulconnexGroupRole.SUBSTITUTE_TEACHER],
ktid: 'ktid1',
},
],
},
],
Expand Down
21 changes: 21 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration20241121180221.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241121180221 extends Migration {
async up(): Promise<void> {
await this.driver.nativeInsert('roles', {
name: 'groupSubstitutionTeacher',
roles: [],
permissions: [],
});

// eslint-disable-next-line no-console
console.info('Added groupSubstitutionTeacher role');
}

async down(): Promise<void> {
await this.driver.nativeDelete('roles', { name: 'groupSubstitutionTeacher' });

// eslint-disable-next-line no-console
console.info('Rollback: Removed groupSubstitutionTeacher role');
}
}
8 changes: 8 additions & 0 deletions apps/server/src/modules/group/repo/group.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ describe(GroupRepo.name, () => {
userId: group.users[1].user.id,
roleId: group.users[1].role.id,
}),
new GroupUser({
userId: group.users[2].user.id,
roleId: group.users[2].role.id,
}),
],
organizationId: group.organization?.id,
validPeriod: group.validPeriod,
Expand Down Expand Up @@ -849,6 +853,10 @@ describe(GroupRepo.name, () => {
userId: groupEntity.users[1].user.id,
roleId: groupEntity.users[1].role.id,
}),
new GroupUser({
userId: groupEntity.users[2].user.id,
roleId: groupEntity.users[2].role.id,
}),
],
organizationId: groupEntity.organization?.id,
validPeriod: groupEntity.validPeriod,
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/modules/learnroom/domain/do/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export class Course extends DomainObject<CourseProps> {
return this.props.substitutionTeacherIds;
}

set substitutionTeachers(value: EntityId[]) {
this.props.substitutionTeacherIds = value;
}

set classes(value: EntityId[]) {
this.props.classIds = value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,74 @@ describe(CourseSyncService.name, () => {
};
};

it('should synchronize with the new group', async () => {
const { course, newGroup, studentId, teacherId } = setup();

await service.synchronizeCourseWithGroup(newGroup);

expect(courseRepo.saveAll).toHaveBeenCalledWith<[Course[]]>([
new Course({
...course.getProps(),
syncedWithGroup: newGroup.id,
startDate: newGroup.validPeriod?.from,
untilDate: newGroup.validPeriod?.until,
studentIds: [studentId],
teacherIds: [teacherId],
classIds: [],
groupIds: [],
substitutionTeacherIds: [],
}),
]);
});
});
describe('when synchronizing with a new group with substitute teacher', () => {
const setup = () => {
const studentId: string = new ObjectId().toHexString();
const teacherId: string = new ObjectId().toHexString();
const substituteTeacherId: string = new ObjectId().toHexString();
const studentRoleId: string = new ObjectId().toHexString();
const teacherRoleId: string = new ObjectId().toHexString();
const substituteTeacherRoleId: string = new ObjectId().toHexString();
const studentRole: RoleDto = roleDtoFactory.build({ id: studentRoleId });
const teacherRole: RoleDto = roleDtoFactory.build({ id: teacherRoleId });
const substituteTeacherRole: RoleDto = roleDtoFactory.build({ id: substituteTeacherRoleId });
const newGroup: Group = groupFactory.build({
users: [
{
userId: studentId,
roleId: studentRoleId,
},
{
userId: teacherId,
roleId: teacherRoleId,
},
{
userId: substituteTeacherId,
roleId: substituteTeacherRoleId,
},
],
});
const course: Course = courseFactory.build({
classIds: [new ObjectId().toHexString()],
groupIds: [new ObjectId().toHexString()],
substitutionTeacherIds: [],
});

courseRepo.findBySyncedGroup.mockResolvedValueOnce([new Course(course.getProps())]);
roleService.findByName
.mockResolvedValueOnce(studentRole)
.mockResolvedValueOnce(teacherRole)
.mockResolvedValueOnce(substituteTeacherRole);

return {
course,
newGroup,
studentId,
teacherId,
substituteTeacherId,
};
};

it('should synchronize with the new group', async () => {
const { course, newGroup, studentId, teacherId, substituteTeacherId } = setup();

Expand All @@ -405,12 +473,10 @@ describe(CourseSyncService.name, () => {

describe('when the course name is the same as the old group name', () => {
const setup = () => {
const substituteTeacherId = new ObjectId().toHexString();
const course: Course = courseFactory.build({
name: 'Course Name',
classIds: [new ObjectId().toHexString()],
groupIds: [new ObjectId().toHexString()],
substitutionTeacherIds: [substituteTeacherId],
});
const studentRole: RoleDto = roleDtoFactory.build();
const teacherRole: RoleDto = roleDtoFactory.build();
Expand All @@ -428,12 +494,11 @@ describe(CourseSyncService.name, () => {
course,
newGroup,
oldGroup,
substituteTeacherId,
};
};

it('should synchronize the group name', async () => {
const { course, newGroup, oldGroup, substituteTeacherId } = setup();
const { course, newGroup, oldGroup } = setup();

await service.synchronizeCourseWithGroup(newGroup, oldGroup);
expect(courseRepo.saveAll).toHaveBeenCalledWith<[Course[]]>([
Expand All @@ -447,20 +512,18 @@ describe(CourseSyncService.name, () => {
teacherIds: [],
classIds: [],
groupIds: [],
substitutionTeacherIds: [substituteTeacherId],
substitutionTeacherIds: [],
}),
]);
});
});

describe('when the course name is different from the old group name', () => {
const setup = () => {
const substituteTeacherId = new ObjectId().toHexString();
const course: Course = courseFactory.build({
name: 'Custom Course Name',
classIds: [new ObjectId().toHexString()],
groupIds: [new ObjectId().toHexString()],
substitutionTeacherIds: [substituteTeacherId],
});
const studentRole: RoleDto = roleDtoFactory.build();
const teacherRole: RoleDto = roleDtoFactory.build();
Expand All @@ -478,12 +541,11 @@ describe(CourseSyncService.name, () => {
course,
newGroup,
oldGroup,
substituteTeacherId,
};
};

it('should keep the old course name', async () => {
const { course, newGroup, oldGroup, substituteTeacherId } = setup();
const { course, newGroup, oldGroup } = setup();

await service.synchronizeCourseWithGroup(newGroup, oldGroup);
expect(courseRepo.saveAll).toHaveBeenCalledWith<[Course[]]>([
Expand All @@ -497,15 +559,14 @@ describe(CourseSyncService.name, () => {
teacherIds: [],
classIds: [],
groupIds: [],
substitutionTeacherIds: [substituteTeacherId],
substitutionTeacherIds: [],
}),
]);
});
});

describe('when the teachers are not synced from group', () => {
const setup = () => {
const substituteTeacherId = new ObjectId().toHexString();
const studentUserId = new ObjectId().toHexString();
const teacherUserId = new ObjectId().toHexString();
const studentRoleId: string = new ObjectId().toHexString();
Expand All @@ -523,7 +584,6 @@ describe(CourseSyncService.name, () => {

const course: Course = courseFactory.build({
syncedWithGroup: newGroup.id,
substitutionTeacherIds: [substituteTeacherId],
teacherIds: [teacherUserId],
excludeFromSync: [],
});
Expand All @@ -535,13 +595,12 @@ describe(CourseSyncService.name, () => {
course,
newGroup,
teacherUserId,
substituteTeacherId,
studentUserId,
};
};

it('should not sync group students', async () => {
const { course, newGroup, teacherUserId, substituteTeacherId } = setup();
const { course, newGroup, teacherUserId } = setup();

await service.synchronizeCourseWithGroup(newGroup);
expect(courseRepo.saveAll).toHaveBeenCalledWith<[Course[]]>([
Expand All @@ -556,7 +615,7 @@ describe(CourseSyncService.name, () => {
classIds: [],
groupIds: [],
excludeFromSync: [],
substitutionTeacherIds: [substituteTeacherId],
substitutionTeacherIds: [],
}),
]);
});
Expand Down Expand Up @@ -604,7 +663,7 @@ describe(CourseSyncService.name, () => {
};

it('should not sync group teachers', async () => {
const { course, newGroup, substituteTeacherId, teacherUserId, studentUserId } = setup();
const { course, newGroup, teacherUserId, studentUserId } = setup();

await service.synchronizeCourseWithGroup(newGroup);
expect(courseRepo.saveAll).toHaveBeenCalledWith<[Course[]]>([
Expand All @@ -619,7 +678,7 @@ describe(CourseSyncService.name, () => {
classIds: [],
groupIds: [],
excludeFromSync: [SyncAttribute.TEACHERS],
substitutionTeacherIds: [substituteTeacherId],
substitutionTeacherIds: [],
}),
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ export class CourseSyncService {
}

private async synchronize(courses: Course[], group: Group, oldGroup?: Group): Promise<void> {
const [studentRole, teacherRole] = await Promise.all([
const [studentRole, teacherRole, substituteTeacherRole] = await Promise.all([
this.roleService.findByName(RoleName.STUDENT),
this.roleService.findByName(RoleName.TEACHER),
this.roleService.findByName(RoleName.GROUPSUBSTITUTIONTEACHER),
]);

const studentIds = group.users
Expand All @@ -69,12 +70,17 @@ export class CourseSyncService {
.filter((user: GroupUser) => user.roleId === teacherRole.id)
.map((teacher) => teacher.userId);

const substituteTeacherIds = group.users
.filter((user: GroupUser) => user.roleId === substituteTeacherRole.id)
.map((substituteTeacher) => substituteTeacher.userId);

for (const course of courses) {
course.syncedWithGroup = group.id;
course.startDate = group.validPeriod?.from;
course.untilDate = group.validPeriod?.until;
course.classes = [];
course.groups = [];
course.substitutionTeachers = substituteTeacherIds;

if (oldGroup?.name === course.name) {
course.name = group.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,19 @@ describe(SchulconnexResponseMapper.name, () => {
const personenkontext: SchulconnexPersonenkontextResponse = schulconnexResponse.personenkontexte[0];
const group: SchulconnexGruppenResponse = personenkontext.gruppen![0];
const otherParticipant: SchulconnexSonstigeGruppenzugehoerigeResponse = group.sonstige_gruppenzugehoerige![0];
const otherParticipant1: SchulconnexSonstigeGruppenzugehoerigeResponse = group.sonstige_gruppenzugehoerige![1];

return {
schulconnexResponse,
group,
personenkontext,
otherParticipant,
otherParticipant1,
};
};

it('should map the schulconnex response to external group dtos', () => {
const { schulconnexResponse, group, personenkontext, otherParticipant } = setup();
const { schulconnexResponse, group, personenkontext, otherParticipant, otherParticipant1 } = setup();

const result: ExternalGroupDto[] | undefined = mapper.mapToExternalGroupDtos(schulconnexResponse);

Expand All @@ -180,6 +182,10 @@ describe(SchulconnexResponseMapper.name, () => {
externalUserId: otherParticipant.ktid,
roleName: RoleName.STUDENT,
},
{
externalUserId: otherParticipant1.ktid,
roleName: RoleName.GROUPSUBSTITUTIONTEACHER,
},
],
from: new Date('2024-08-01'),
until: new Date('2025-07-31'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const RoleMapping: Record<SchulconnexRole, RoleName> = {

const GroupRoleMapping: Partial<Record<SchulconnexGroupRole, RoleName>> = {
[SchulconnexGroupRole.TEACHER]: RoleName.TEACHER,
[SchulconnexGroupRole.SUBSTITUTE_TEACHER]: RoleName.GROUPSUBSTITUTIONTEACHER,
[SchulconnexGroupRole.STUDENT]: RoleName.STUDENT,
};

Expand Down
1 change: 1 addition & 0 deletions apps/server/src/shared/domain/interface/rolename.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum RoleName {
COURSEADMINISTRATOR = 'courseAdministrator',
COURSESTUDENT = 'courseStudent',
COURSESUBSTITUTIONTEACHER = 'courseSubstitutionTeacher',
GROUPSUBSTITUTIONTEACHER = 'groupSubstitutionTeacher',
COURSETEACHER = 'courseTeacher',
DEMO = 'demo',
DEMOSTUDENT = 'demoStudent',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const groupEntityFactory = BaseFactory.define<GroupEntity, GroupEntityPro
user: userFactory.buildWithId(),
role: roleFactory.buildWithId({ name: RoleName.TEACHER }),
},
{
user: userFactory.buildWithId(),
role: roleFactory.buildWithId({ name: RoleName.GROUPSUBSTITUTIONTEACHER }),
},
],
validPeriod: new GroupValidPeriodEmbeddable({
from: new Date(2023, 1),
Expand Down
Loading
Loading