diff --git a/migrations/.snapshot-dbildungs-iam-server.json b/migrations/.snapshot-dbildungs-iam-server.json index 2984c0601..1b9d4d113 100644 --- a/migrations/.snapshot-dbildungs-iam-server.json +++ b/migrations/.snapshot-dbildungs-iam-server.json @@ -2418,6 +2418,16 @@ "nullable": false, "default": "'1'", "mappedType": "string" + }, + "befristung": { + "name": "befristung", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" } }, "name": "personenkontext", diff --git a/migrations/Migration20240829100726.ts b/migrations/Migration20240829100726.ts new file mode 100644 index 000000000..1443f4620 --- /dev/null +++ b/migrations/Migration20240829100726.ts @@ -0,0 +1,11 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240829100726 extends Migration { + public up(): void { + this.addSql('alter table "personenkontext" add column "befristung" timestamptz null;'); + } + + public override down(): void { + this.addSql('alter table "personenkontext" drop column "befristung";'); + } +} diff --git a/seeding/dev/01/03_service-provider.json b/seeding/dev/01/03_service-provider.json index 6cdbb5b42..ff30f0d8e 100644 --- a/seeding/dev/01/03_service-provider.json +++ b/seeding/dev/01/03_service-provider.json @@ -143,6 +143,18 @@ "providedOnSchulstrukturknoten": 0, "keycloakGroup": "Psychosoziales Beratungsangebot-service", "keycloakRole": "Psychosoziales Beratungsangebot-user" + }, + { + "id": 12, + "name": "LeOniE.SH", + "target": "URL", + "url": "https://www.leonie-sh.de/", + "kategorie": "HINWEISE", + "logoMimeType": "image/png", + "logoBase64": "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", + "providedOnSchulstrukturknoten": 0, + "keycloakGroup": "LeOniE-service", + "keycloakRole": "LeOniE-user" } ] } diff --git a/seeding/dev/01/04_rolle.json b/seeding/dev/01/04_rolle.json index 285acb994..e40f45eb8 100644 --- a/seeding/dev/01/04_rolle.json +++ b/seeding/dev/01/04_rolle.json @@ -27,7 +27,8 @@ 8, 9, 10, - 11 + 11, + 12 ] }, { @@ -61,7 +62,8 @@ 8, 9, 10, - 11 + 11, + 12 ] }, { @@ -85,7 +87,8 @@ 8, 9, 10, - 11 + 11, + 12 ] }, { diff --git a/src/core/ldap/domain/ldap-client.service.spec.ts b/src/core/ldap/domain/ldap-client.service.spec.ts index 7675c0b69..479da58a6 100644 --- a/src/core/ldap/domain/ldap-client.service.spec.ts +++ b/src/core/ldap/domain/ldap-client.service.spec.ts @@ -22,6 +22,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { LdapClient } from './ldap-client.js'; import { Client, Entry, SearchResult } from 'ldapts'; import { KennungRequiredForSchuleError } from '../../../modules/organisation/specification/error/kennung-required-for-schule.error.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; describe('LDAP Client Service', () => { let app: INestApplication; @@ -199,6 +200,29 @@ describe('LDAP Client Service', () => { expect(result.ok).toBeTruthy(); }); + it('when called with extra entryUUID should return truthy result', async () => { + ldapClientMock.getClient.mockImplementation(() => { + clientMock.bind.mockResolvedValue(); + clientMock.add.mockResolvedValueOnce(); + clientMock.search.mockResolvedValueOnce( + createMock({ searchEntries: [createMock()] }), + ); //mock existsSchule + clientMock.search.mockResolvedValueOnce(createMock()); //mock existsLehrer + + return clientMock; + }); + const testLehrer: PersonData = { + id: faker.string.uuid(), + vorname: faker.person.firstName(), + familienname: faker.person.lastName(), + referrer: faker.lorem.word(), + ldapEntryUUID: faker.string.uuid(), + }; + const result: Result = await ldapClientService.createLehrer(testLehrer, organisation); + + expect(result.ok).toBeTruthy(); + }); + it('when called with valid person and an organisation without kennung should return error result', async () => { ldapClientMock.getClient.mockImplementation(() => { clientMock.bind.mockResolvedValue(); @@ -337,7 +361,7 @@ describe('LDAP Client Service', () => { return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeTruthy(); }); @@ -354,7 +378,7 @@ describe('LDAP Client Service', () => { return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeFalsy(); }); @@ -365,7 +389,7 @@ describe('LDAP Client Service', () => { clientMock.add.mockResolvedValueOnce(); return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeFalsy(); }); diff --git a/src/core/ldap/domain/ldap-client.service.ts b/src/core/ldap/domain/ldap-client.service.ts index 8d978f69f..83b14f538 100644 --- a/src/core/ldap/domain/ldap-client.service.ts +++ b/src/core/ldap/domain/ldap-client.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { ClassLogger } from '../../logging/class-logger.js'; -import { Client, SearchResult } from 'ldapts'; +import { Client, Control, SearchResult } from 'ldapts'; import { LdapEntityType, LdapOrganisationEntry, LdapPersonEntry, LdapRoleEntry } from './ldap.types.js'; import { KennungRequiredForSchuleError } from '../../../modules/organisation/specification/error/kennung-required-for-schule.error.js'; import { LdapClient } from './ldap-client.js'; @@ -8,12 +8,14 @@ import { LdapInstanceConfig } from '../ldap-instance-config.js'; import { UsernameRequiredError } from '../../../modules/person/domain/username-required.error.js'; import { Mutex } from 'async-mutex'; import { LdapSearchError } from '../error/ldap-search.error.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; export type PersonData = { vorname: string; familienname: string; id: string; referrer?: string; + ldapEntryUUID?: string; }; type OrganisationData = { @@ -153,7 +155,15 @@ export class LdapClientService { mail: [`${person.referrer}@schule-sh.de`], objectclass: ['inetOrgPerson'], }; - await client.add(lehrerUid, entry); + + const controls: Control[] = []; + if (person.ldapEntryUUID) { + const relaxRulesControlOID: string = '1.3.6.1.4.1.4203.666.5.12'; + entry.entryUUID = person.ldapEntryUUID; + controls.push(new Control(relaxRulesControlOID)); + } + + await client.add(lehrerUid, entry, controls); this.logger.info(`LDAP: Successfully created lehrer ${lehrerUid}`); return { ok: true, value: person }; @@ -164,7 +174,7 @@ export class LdapClientService { return `uid=${referrer},cn=lehrer,ou=${orgaKennung},ou=oeffentlicheSchulen,dc=schule-sh,dc=de`; } - public async deleteLehrerByPersonId(person: PersonData): Promise> { + public async deleteLehrerByPersonId(personId: PersonID): Promise> { return this.mutex.runExclusive(async () => { this.logger.info('LDAP: deleteLehrer'); const client: Client = this.ldapClient.getClient(); @@ -173,7 +183,7 @@ export class LdapClientService { const searchResultLehrer: SearchResult = await client.search(`ou=oeffentlicheSchulen,dc=schule-sh,dc=de`, { scope: 'sub', - filter: `(employeeNumber=${person.id})`, + filter: `(employeeNumber=${personId})`, }); if (!searchResultLehrer.searchEntries[0]) { return { @@ -182,9 +192,9 @@ export class LdapClientService { }; } await client.del(searchResultLehrer.searchEntries[0].dn); - this.logger.info(`LDAP: Successfully deleted lehrer by personId:${person.id}`); + this.logger.info(`LDAP: Successfully deleted lehrer by personId:${personId}`); - return { ok: true, value: person }; + return { ok: true, value: personId }; }); } diff --git a/src/core/ldap/domain/ldap-event-handler.spec.ts b/src/core/ldap/domain/ldap-event-handler.spec.ts index 7c8e1a9e4..90934e47f 100644 --- a/src/core/ldap/domain/ldap-event-handler.spec.ts +++ b/src/core/ldap/domain/ldap-event-handler.spec.ts @@ -13,7 +13,7 @@ import { GlobalValidationPipe } from '../../../shared/validation/global-validati import { LdapModule } from '../ldap.module.js'; import { LdapEventHandler } from './ldap-event-handler.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { LdapClientService, PersonData } from './ldap-client.service.js'; +import { LdapClientService } from './ldap-client.service.js'; import { SchuleCreatedEvent } from '../../../shared/events/schule-created.event.js'; import { PersonRepository } from '../../../modules/person/persistence/person.repository.js'; import { RolleRepo } from '../../../modules/rolle/repo/rolle.repo.js'; @@ -25,9 +25,11 @@ import { DBiamPersonenkontextRepo } from '../../../modules/personenkontext/persi import { PersonenkontextFactory } from '../../../modules/personenkontext/domain/personenkontext.factory.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; import { ClassLogger } from '../../logging/class-logger.js'; -import { PersonenkontextDeletedEvent } from '../../../shared/events/personenkontext-deleted.event.js'; -import { KennungRequiredForSchuleError } from '../../../modules/organisation/specification/error/kennung-required-for-schule.error.js'; import { RootDirectChildrenType } from '../../../modules/organisation/domain/organisation.enums.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; +import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js'; +import { LdapSearchError } from '../error/ldap-search.error.js'; +import { LdapEntityType } from './ldap.types.js'; describe('LDAP Event Handler', () => { let app: INestApplication; @@ -195,36 +197,31 @@ describe('LDAP Event Handler', () => { }); }); - describe('handlePersonenkontextDeletedEvent', () => { - describe('when calling LdapClientService.deleteLehrer is successful', () => { + describe('handlePersonDeletedEvent', () => { + describe('when calling LdapClientService.deleteLehrerByPersonId is successful', () => { it('should NOT log errors', async () => { - const deletionResult: Result = { + const deletionResult: Result = { ok: true, - value: { - vorname: faker.person.firstName(), - familienname: faker.person.lastName(), - id: faker.string.uuid(), - referrer: faker.internet.userName(), - }, + value: faker.string.uuid(), }; - ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce(deletionResult); + ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce(deletionResult); - await ldapEventHandler.handlePersonenkontextDeletedEvent(createMock()); + await ldapEventHandler.handlePersonDeletedEvent(createMock()); expect(loggerMock.error).toHaveBeenCalledTimes(0); }); }); - describe('when calling LdapClientService.deleteLehrer is return error', () => { + describe('when calling LdapClientService.deleteLehrerByPersonId is return error', () => { it('should log errors', async () => { - const error: KennungRequiredForSchuleError = new KennungRequiredForSchuleError(); - const deletionResult: Result = { + const error: LdapSearchError = new LdapSearchError(LdapEntityType.LEHRER); + const deletionResult: Result = { ok: false, error: error, }; - ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce(deletionResult); + ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce(deletionResult); - await ldapEventHandler.handlePersonenkontextDeletedEvent(createMock()); + await ldapEventHandler.handlePersonDeletedEvent(createMock()); expect(loggerMock.error).toHaveBeenCalledTimes(1); expect(loggerMock.error).toHaveBeenCalledWith(error.message); @@ -296,7 +293,7 @@ describe('LDAP Event Handler', () => { await ldapEventHandler.handlePersonenkontextUpdatedEvent(event); - expect(ldapClientServiceMock.deleteLehrerByPersonId).toHaveBeenCalledTimes(1); + expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(1); }); describe('when ldap client fails', () => { @@ -348,14 +345,14 @@ describe('LDAP Event Handler', () => { ], [], ); - ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce({ + ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce({ ok: false, error: new Error('Error'), }); await ldapEventHandler.handlePersonenkontextUpdatedEvent(event); - expect(ldapClientServiceMock.deleteLehrerByPersonId).toHaveBeenCalledTimes(1); + expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/core/ldap/domain/ldap-event-handler.ts b/src/core/ldap/domain/ldap-event-handler.ts index 90c7b922f..50bc76f17 100644 --- a/src/core/ldap/domain/ldap-event-handler.ts +++ b/src/core/ldap/domain/ldap-event-handler.ts @@ -6,8 +6,9 @@ import { ClassLogger } from '../../logging/class-logger.js'; import { RollenArt } from '../../../modules/rolle/domain/rolle.enums.js'; import { SchuleDeletedEvent } from '../../../shared/events/schule-deleted.event.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; -import { PersonenkontextDeletedEvent } from '../../../shared/events/personenkontext-deleted.event.js'; import { PersonenkontextEventKontextData } from '../../../shared/events/personenkontext-event.types.js'; +import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; @Injectable() export class LdapEventHandler { @@ -51,14 +52,10 @@ export class LdapEventHandler { } } - @EventHandler(PersonenkontextDeletedEvent) - public async handlePersonenkontextDeletedEvent(event: PersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personData.id}, orgaId:${event.kontextData.orgaId}, rolleId:${event.kontextData.rolleId}`, - ); - const deletionResult: Result = await this.ldapClientService.deleteLehrer(event.personData, { - kennung: event.kontextData.orgaKennung, - }); + @EventHandler(PersonDeletedEvent) + public async handlePersonDeletedEvent(event: PersonDeletedEvent): Promise { + this.logger.info(`Received PersonenkontextDeletedEvent, personId:${event.personId}`); + const deletionResult: Result = await this.ldapClientService.deleteLehrerByPersonId(event.personId); if (!deletionResult.ok) { this.logger.error(deletionResult.error.message); } @@ -76,9 +73,9 @@ export class LdapEventHandler { .filter((pk: PersonenkontextEventKontextData) => pk.rolle === RollenArt.LEHR) .map(async (pk: PersonenkontextEventKontextData) => { this.logger.info(`Call LdapClientService because rollenArt is LEHR, pkId: ${pk.id}`); - const deletionResult: Result = await this.ldapClientService.deleteLehrerByPersonId( - event.person, - ); + const deletionResult: Result = await this.ldapClientService.deleteLehrer(event.person, { + kennung: pk.orgaKennung, + }); if (!deletionResult.ok) { this.logger.error(deletionResult.error.message); } diff --git a/src/core/ldap/domain/ldap.types.ts b/src/core/ldap/domain/ldap.types.ts index d47d8faa4..cd68b8b80 100644 --- a/src/core/ldap/domain/ldap.types.ts +++ b/src/core/ldap/domain/ldap.types.ts @@ -4,6 +4,7 @@ export type LdapPersonEntry = { employeeNumber: string; mail: string[]; objectclass: string[]; + entryUUID?: string; }; export type LdapOrganisationEntry = { diff --git a/src/modules/email/domain/email-event-handler.spec.ts b/src/modules/email/domain/email-event-handler.spec.ts index 7ca0dc172..b5edd94e3 100644 --- a/src/modules/email/domain/email-event-handler.spec.ts +++ b/src/modules/email/domain/email-event-handler.spec.ts @@ -12,7 +12,6 @@ import { EmailEventHandler } from './email-event-handler.js'; import { faker } from '@faker-js/faker'; import { EmailModule } from '../email.module.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { ServiceProviderRepo } from '../../service-provider/repo/service-provider.repo.js'; @@ -30,7 +29,6 @@ import { PersonRenamedEvent } from '../../../shared/events/person-renamed-event. import { RolleUpdatedEvent } from '../../../shared/events/rolle-updated.event.js'; import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { DBiamPersonenkontextRepo } from '../../personenkontext/persistence/dbiam-personenkontext.repo.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; import { EmailAddress } from './email-address.js'; import { PersonID, RolleID } from '../../../shared/types/index.js'; import { Personenkontext } from '../../personenkontext/domain/personenkontext.js'; @@ -71,10 +69,6 @@ describe('Email Event Handler', () => { DatabaseTestModule.forRoot({ isDatabaseRequired: false }), ], providers: [ - { - provide: ClassLogger, - useValue: createMock(), - }, { provide: APP_PIPE, useClass: GlobalValidationPipe, @@ -97,12 +91,12 @@ describe('Email Event Handler', () => { .useValue(createMock()) .overrideProvider(DBiamPersonenkontextRepo) .useValue(createMock()) - .overrideProvider(ClassLogger) - .useValue(createMock()) .overrideProvider(EmailEventHandler) .useClass(EmailEventHandler) .overrideProvider(EventService) .useClass(EventService) + .overrideProvider(ClassLogger) + .useValue(createMock()) .compile(); emailEventHandler = module.get(EmailEventHandler); @@ -138,11 +132,11 @@ describe('Email Event Handler', () => { }); } - describe('handlePersonenkontextCreatedEvent', () => { + describe('handlePersonenkontextUpdatedEvent', () => { let fakePersonId: PersonID; let fakeRolleId: RolleID; let fakeEmailAddressString: string; - let event: PersonenkontextCreatedEvent; + let event: PersonenkontextUpdatedEvent; let personenkontexte: Personenkontext[]; let rolle: Rolle; let rolleMap: Map>; @@ -154,7 +148,7 @@ describe('Email Event Handler', () => { fakePersonId = faker.string.uuid(); fakeRolleId = faker.string.uuid(); fakeEmailAddressString = faker.internet.email(); - event = new PersonenkontextCreatedEvent(fakePersonId, faker.string.uuid(), faker.string.uuid()); + event = createMock({ person: { id: fakePersonId } }); personenkontexte = [createMock>()]; rolle = createMock>({ serviceProviderIds: [] }); @@ -185,7 +179,7 @@ describe('Email Event Handler', () => { ); }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Existing email for personId:${fakePersonId} already enabled`, @@ -214,7 +208,7 @@ describe('Email Event Handler', () => { const persistedEmail: EmailAddress = getEmail(); emailRepoMock.save.mockResolvedValueOnce(persistedEmail); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Enabled and saved address:${persistedEmail.currentAddress}`, @@ -242,7 +236,7 @@ describe('Email Event Handler', () => { emailRepoMock.save.mockResolvedValueOnce(new EmailAddressNotFoundError(fakeEmailAddressString)); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not enable email, error is requested EmailAddress with the address:${fakeEmailAddressString} was not found`, @@ -277,7 +271,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Successfully persisted email with new address:${persistenceResult.currentAddress}`, @@ -301,7 +295,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not create email, error is requested Person with the following ID ${fakePersonId} was not found`, @@ -333,7 +327,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not persist email, error is requested EmailAddress with the address:${fakeEmailAddressString} was not found`, @@ -439,62 +433,6 @@ describe('Email Event Handler', () => { }); }); - describe('handlePersonenkontextDeletedEvent', () => { - describe('when rolle exists and service provider with kategorie email is found', () => { - it('should execute without errors', async () => { - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - ); - - const rolle: Rolle = createMock>({ serviceProviderIds: [] }); - const sp: ServiceProvider = createMock>({ - kategorie: ServiceProviderKategorie.EMAIL, - }); - const spMap: Map> = new Map>(); - spMap.set(sp.id, sp); - rolleRepoMock.findById.mockResolvedValueOnce(rolle); - serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap); - - const result: void = await emailEventHandler.handlePersonenkontextDeletedEvent(event); - - expect(result).toBeUndefined(); - }); - }); - - describe('when rolle does NOT exists', () => { - it('should execute without errors', async () => { - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - ); - - rolleRepoMock.findById.mockResolvedValueOnce(undefined); - const result: void = await emailEventHandler.handlePersonenkontextDeletedEvent(event); - - expect(result).toBeUndefined(); - }); - }); - }); - - describe('handlePersonenkontextUpdatedEvent', () => { - describe('when called', () => { - it('should log info', async () => { - const event: PersonenkontextUpdatedEvent = createMock(); - - await emailEventHandler.handlePersonenkontextUpdatedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received handlePersonenkontextUpdatedEvent, personId:${event.person.id}`, - ); - }); - }); - }); - describe('handlePersonDeletedEvent', () => { let personId: string; let emailAddress: string; diff --git a/src/modules/email/domain/email-event-handler.ts b/src/modules/email/domain/email-event-handler.ts index 3b5ca2307..45b21b7c6 100644 --- a/src/modules/email/domain/email-event-handler.ts +++ b/src/modules/email/domain/email-event-handler.ts @@ -1,8 +1,6 @@ import { Injectable } from '@nestjs/common'; import { ClassLogger } from '../../../core/logging/class-logger.js'; import { EventHandler } from '../../../core/eventbus/decorators/event-handler.decorator.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { ServiceProviderRepo } from '../../service-provider/repo/service-provider.repo.js'; @@ -36,14 +34,6 @@ export class EmailEventHandler { private readonly eventService: EventService, ) {} - @EventHandler(PersonenkontextCreatedEvent) - public async handlePersonenkontextCreatedEvent(event: PersonenkontextCreatedEvent): Promise { - this.logger.info( - `Received PersonenkontextCreatedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - await this.handlePerson(event.personId); - } - @EventHandler(PersonRenamedEvent) // eslint-disable-next-line @typescript-eslint/require-await public async handlePersonRenamedEvent(event: PersonRenamedEvent): Promise { @@ -85,20 +75,12 @@ export class EmailEventHandler { }); } - @EventHandler(SimplePersonenkontextDeletedEvent) - // eslint-disable-next-line @typescript-eslint/require-await - public async handlePersonenkontextDeletedEvent(event: SimplePersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - // currently receiving of this event is not causing a deletion of email and the related addresses for the affected user, this is intentional - } - @EventHandler(PersonenkontextUpdatedEvent) - // eslint-disable-next-line @typescript-eslint/require-await + // currently receiving of this event is not causing a deletion of email and the related addresses for the affected user, this is intentional public async handlePersonenkontextUpdatedEvent(event: PersonenkontextUpdatedEvent): Promise { this.logger.info(`Received handlePersonenkontextUpdatedEvent, personId:${event.person.id}`); - // // TODO implement handlePersonenkontextUpdatedEvent + + await this.handlePerson(event.person.id); } // this method cannot make use of handlePerson(personId) method, because personId is already null when event is received diff --git a/src/modules/person/api/personenuebersicht/dbiam-personenzuordnung.response.ts b/src/modules/person/api/personenuebersicht/dbiam-personenzuordnung.response.ts index 13d66486d..66d30d367 100644 --- a/src/modules/person/api/personenuebersicht/dbiam-personenzuordnung.response.ts +++ b/src/modules/person/api/personenuebersicht/dbiam-personenzuordnung.response.ts @@ -31,6 +31,9 @@ export class DBiamPersonenzuordnungResponse { @ApiProperty({ type: Boolean }) public readonly editable: boolean; + @ApiProperty({ type: Date }) + public readonly befristung?: Date; + @ApiProperty({ enum: RollenMerkmal, enumName: RollenMerkmalTypName, nullable: true }) public readonly merkmale?: RollenMerkmal[]; @@ -50,5 +53,6 @@ export class DBiamPersonenzuordnungResponse { this.typ = organisation.typ; this.editable = editable; this.merkmale = rolle.merkmale; + this.befristung = personenkontext.befristung; } } diff --git a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.integration-spec.ts b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.integration-spec.ts index af853564d..9c4fc52b5 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.integration-spec.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.integration-spec.ts @@ -39,6 +39,7 @@ import { Organisation } from '../../organisation/domain/organisation.js'; import { PersonFactory } from '../../person/domain/person.factory.js'; import { PersonenkontextCreationService } from '../domain/personenkontext-creation.service.js'; import { DuplicatePersonalnummerError } from '../../../shared/error/duplicate-personalnummer.error.js'; +import { PersonenkontexteUpdateError } from '../domain/error/personenkontexte-update.error.js'; function createRolle(this: void, rolleFactory: RolleFactory, params: Partial> = {}): Rolle { const rolle: Rolle | DomainError = rolleFactory.createNew( @@ -159,6 +160,7 @@ describe('DbiamPersonenkontextWorkflowController Integration Test', () => { DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisation.id, rollenart: RollenArt.LEHR, + merkmale: [RollenMerkmal.KOPERS_PFLICHT], }), ); @@ -185,6 +187,7 @@ describe('DbiamPersonenkontextWorkflowController Integration Test', () => { DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisation.id, rollenart: RollenArt.LEHR, + merkmale: [RollenMerkmal.KOPERS_PFLICHT], }), ); @@ -310,6 +313,38 @@ describe('DbiamPersonenkontextWorkflowController Integration Test', () => { new DuplicatePersonalnummerError('Duplicate Kopers'), ); + const response: Response = await request(app.getHttpServer() as App) + .post('/personenkontext-workflow') + .send({ + familienname: faker.person.lastName(), + vorname: faker.person.firstName(), + organisationId: organisation.id, + rolleId: rolle.id, + personalnummer: '1234567', + }); + + expect(response.status).toBe(400); + }); + it('should return error with status-code 400 if PersonenkontexteUpdateError is thrown', async () => { + const organisation: Organisation = await organisationRepo.save( + DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), + ); + const rolle: Rolle = await rolleRepo.save( + DoFactory.createRolle(false, { + administeredBySchulstrukturknoten: organisation.id, + rollenart: RollenArt.LEHR, + }), + ); + + const personpermissions: DeepMocked = createMock(); + personpermissions.hasSystemrechtAtOrganisation.mockResolvedValue(true); + personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); + + // Mock the service to throw DuplicatePersonalnummerError + jest.spyOn(personenkontextService, 'createPersonWithPersonenkontext').mockResolvedValueOnce( + new PersonenkontexteUpdateError('Error'), + ); + const response: Response = await request(app.getHttpServer() as App) .post('/personenkontext-workflow') .send({ diff --git a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts index 5d759d70e..eed64bc9d 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts @@ -221,11 +221,15 @@ export class DbiamPersonenkontextWorkflowController { params.organisationId, params.rolleId, params.personalnummer || undefined, + params.befristung || undefined, ); if (savedPersonWithPersonenkontext instanceof PersonenkontextSpecificationError) { throw savedPersonWithPersonenkontext; } + if (savedPersonWithPersonenkontext instanceof PersonenkontexteUpdateError) { + throw savedPersonWithPersonenkontext; + } if (savedPersonWithPersonenkontext instanceof DuplicatePersonalnummerError) { throw savedPersonWithPersonenkontext; diff --git a/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts b/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts index 9d75e963c..8e7f87495 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts @@ -22,7 +22,6 @@ import { PassportUser } from '../../authentication/types/user.js'; import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; -import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { Personenkontext } from '../domain/personenkontext.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; import { RollenArt } from '../../rolle/domain/rolle.enums.js'; @@ -36,6 +35,9 @@ import { DomainError } from '../../../shared/error/domain.error.js'; import { Organisation } from '../../organisation/domain/organisation.js'; import { PersonFactory } from '../../person/domain/person.factory.js'; import { UsernameGeneratorService } from '../../person/domain/username-generator.service.js'; +import { DbiamPersonenkontextFactory } from '../domain/dbiam-personenkontext.factory.js'; +import { PersonenkontexteUpdate } from '../domain/personenkontexte-update.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; describe('dbiam Personenkontext API', () => { let app: INestApplication; @@ -45,7 +47,7 @@ describe('dbiam Personenkontext API', () => { let personRepo: PersonRepository; let organisationRepo: OrganisationRepository; let rolleRepo: RolleRepo; - let personenkontextFactory: PersonenkontextFactory; + let personenkontextFactory: DbiamPersonenkontextFactory; let personpermissionsRepoMock: DeepMocked; let personFactory: PersonFactory; @@ -94,7 +96,7 @@ describe('dbiam Personenkontext API', () => { personRepo = module.get(PersonRepository); organisationRepo = module.get(OrganisationRepository); rolleRepo = module.get(RolleRepo); - personenkontextFactory = module.get(PersonenkontextFactory); + personenkontextFactory = module.get(DbiamPersonenkontextFactory); personpermissionsRepoMock = module.get(PersonPermissionsRepo); personFactory = module.get(PersonFactory); @@ -224,6 +226,7 @@ describe('dbiam Personenkontext API', () => { DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisation.id, rollenart: RollenArt.LEHR, + merkmale: [], }), ); @@ -239,46 +242,37 @@ describe('dbiam Personenkontext API', () => { expect(response.status).toBe(201); }); - it('should return created personenkontext when Klasse specifications are met', async () => { - //create lehrer on Schule - const lehrer: Person = await createPerson(); - if (lehrer instanceof DomainError) { - throw lehrer; - } - const schuleDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.SCHULE, - }); - const schule: Organisation = await organisationRepo.save(schuleDo); - const schuelerRolleDummy: Rolle = DoFactory.createRolle(false, { - rollenart: RollenArt.LERN, - administeredBySchulstrukturknoten: schule.id, - }); - const schuelerRolle: Rolle = await rolleRepo.save(schuelerRolleDummy); - await personenkontextRepo.save(personenkontextFactory.createNew(lehrer.id, schule.id, schuelerRolle.id)); - - const klasseDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - administriertVon: schule.id, - }); - const klasse: Organisation = await organisationRepo.save(klasseDo); + it('should return error if PersonenkontexteUpdateError occurs', async () => { + const person: Person = await createPerson(); + const organisation: Organisation = await organisationRepo.save( + DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), + ); + const rolle: Rolle = await rolleRepo.save( + DoFactory.createRolle(false, { + administeredBySchulstrukturknoten: organisation.id, + rollenart: RollenArt.LEHR, + }), + ); - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([schule.id, klasse.id]); - personpermissions.hasSystemrechteAtRootOrganisation.mockResolvedValueOnce(true); + const personenkontextUpdateMock: DeepMocked = createMock(); + personenkontextUpdateMock.update.mockResolvedValueOnce(new PersonenkontextCommitError()); + jest.spyOn(personenkontextFactory, 'createNewPersonenkontexteUpdate').mockReturnValueOnce( + personenkontextUpdateMock, + ); const response: Response = await request(app.getHttpServer() as App) .post('/dbiam/personenkontext') .send({ - personId: lehrer.id, - organisationId: klasse.id, - rolleId: schuelerRolle.id, + personId: person.id, + organisationId: organisation.id, + rolleId: rolle.id, }); - expect(response.status).toBe(201); + expect(response.status).toBe(400); + expect(response.text).toBe('{"code":400,"i18nKey":"PERSONENKONTEXTE_UPDATE_ERROR"}'); }); - it('should return error if personenkontext already exists', async () => { + it('should return error, if personenkontext was not added', async () => { const person: Person = await createPerson(); const organisation: Organisation = await organisationRepo.save( DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), @@ -289,176 +283,24 @@ describe('dbiam Personenkontext API', () => { rollenart: RollenArt.LEHR, }), ); - const personenkontext: Personenkontext = await personenkontextRepo.save( - personenkontextFactory.createNew(person.id, organisation.id, rolle.id), + + // Error can only occur when database write fails, therefore it needs to be mocked + const personenkontextUpdateMock: DeepMocked = createMock(); + personenkontextUpdateMock.update.mockResolvedValueOnce([]); + jest.spyOn(personenkontextFactory, 'createNewPersonenkontexteUpdate').mockReturnValueOnce( + personenkontextUpdateMock, ); - const permissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValueOnce(permissions); - permissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(true); - permissions.canModifyPerson.mockResolvedValueOnce(true); const response: Response = await request(app.getHttpServer() as App) .post('/dbiam/personenkontext') .send({ - personId: personenkontext.personId, - organisationId: personenkontext.organisationId, - rolleId: personenkontext.rolleId, + personId: person.id, + organisationId: organisation.id, + rolleId: rolle.id, }); expect(response.status).toBe(400); - }); - - it('should return error if references do not exist', async () => { - const personenkontext: Personenkontext = DoFactory.createPersonenkontext(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: personenkontext.personId, - organisationId: personenkontext.organisationId, - rolleId: personenkontext.rolleId, - }); - - expect(response.status).toBe(400); // TODO: Fix - }); - - describe('should return error if specifications are not satisfied', () => { - it('when organisation is not found', async () => { - const person: Person = await createPerson(); - const rolle: Rolle = await rolleRepo.save(DoFactory.createRolle(false)); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: faker.string.uuid(), - rolleId: rolle.id, - }); - - expect(response.status).toBe(400); - }); - - it('when rolle is not found', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false), - ); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: organisation.id, - rolleId: faker.string.uuid(), - }); - - expect(response.status).toBe(404); - }); - - it('when rollenart of rolle is not LEHR or LERN', async () => { - const orgaDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - }); - const rolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.SYSADMIN }); - - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save(orgaDo); - const rolle: Rolle = await rolleRepo.save(rolleDummy); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: organisation.id, - rolleId: rolle.id, - }); - - expect(response.status).toBe(400); - }); - - it('when rollenart for Schule and Klasse are not equal', async () => { - //create admin on Schule - const admin: Person = await createPerson(); - const schuleDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.SCHULE, - }); - const adminRolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.ORGADMIN }); - - const schule: Organisation = await organisationRepo.save(schuleDo); - const adminRolle: Rolle = await rolleRepo.save(adminRolleDummy); - await personenkontextRepo.save(personenkontextFactory.createNew(admin.id, schule.id, adminRolle.id)); - - const klasseDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - administriertVon: schule.id, - }); - const lehrRolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.LEHR }); - const lehrer: Person = admin; - const klasse: Organisation = await organisationRepo.save(klasseDo); - const lehrRolle: Rolle = await rolleRepo.save(lehrRolleDummy); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: lehrer.id, - organisationId: klasse.id, - rolleId: lehrRolle.id, - }); - - expect(response.status).toBe(400); - }); - }); - - describe('when user is not authorized', () => { - it('should return error', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), - ); - const rolle: Rolle = await rolleRepo.save( - DoFactory.createRolle(false, { - administeredBySchulstrukturknoten: organisation.id, - rollenart: RollenArt.LEHR, - }), - ); - - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ personId: person.id, organisationId: organisation.id, rolleId: rolle.id }); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - code: 404, - subcode: '01', - titel: 'Angefragte Entität existiert nicht', - beschreibung: 'Die angeforderte Entität existiert nicht', - }); - }); - }); - - describe('when OrganisationMatchesRollenart is not satisfied', () => { - it('should return error and map to 400', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), - ); - const rolle: Rolle = await rolleRepo.save( - DoFactory.createRolle(false, { - administeredBySchulstrukturknoten: organisation.id, - rollenart: RollenArt.SYSADMIN, - }), - ); - - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ personId: person.id, organisationId: organisation.id, rolleId: rolle.id }); - - expect(response.status).toBe(400); - }); + expect(response.text).toBe('{"code":400,"i18nKey":"PERSONENKONTEXTE_UPDATE_ERROR"}'); }); }); }); diff --git a/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts b/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts index b0ce0662c..1983c3b41 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts @@ -15,19 +15,19 @@ import { SchulConnexErrorMapper } from '../../../shared/error/schul-connex-error import { SchulConnexValidationErrorFilter } from '../../../shared/error/schulconnex-validation-error.filter.js'; import { Permissions } from '../../authentication/api/permissions.decorator.js'; import { PersonPermissions } from '../../authentication/domain/person-permissions.js'; -import { DBiamPersonenkontextService } from '../domain/dbiam-personenkontext.service.js'; -import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { Personenkontext } from '../domain/personenkontext.js'; import { DBiamPersonenkontextResponse } from './response/dbiam-personenkontext.response.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; -import { PersonenkontextSpecificationError } from '../specification/error/personenkontext-specification.error.js'; import { DbiamPersonenkontextBodyParams } from './param/dbiam-personenkontext.body.params.js'; import { DBiamFindPersonenkontexteByPersonIdParams } from './param/dbiam-find-personenkontext-by-personid.params.js'; import { DbiamPersonenkontextError } from './dbiam-personenkontext.error.js'; import { PersonenkontextExceptionFilter } from './personenkontext-exception-filter.js'; import { PersonenkontexteUpdateExceptionFilter } from './personenkontexte-update-exception-filter.js'; -import { OrganisationMatchesRollenartError } from '../specification/error/organisation-matches-rollenart.error.js'; import { AuthenticationExceptionFilter } from '../../authentication/api/authentication-exception-filter.js'; +import { PersonenkontexteUpdate } from '../domain/personenkontexte-update.js'; +import { DbiamPersonenkontextFactory } from '../domain/dbiam-personenkontext.factory.js'; +import { PersonenkontexteUpdateError } from '../domain/error/personenkontexte-update.error.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; @UseFilters( new SchulConnexValidationErrorFilter(), @@ -42,8 +42,7 @@ import { AuthenticationExceptionFilter } from '../../authentication/api/authenti export class DBiamPersonenkontextController { public constructor( private readonly personenkontextRepo: DBiamPersonenkontextRepo, - private readonly dbiamPersonenkontextService: DBiamPersonenkontextService, - private readonly personenkontextFactory: PersonenkontextFactory, + private readonly personenkontextFactory: DbiamPersonenkontextFactory, ) {} @Get(':personId') @@ -87,42 +86,47 @@ export class DBiamPersonenkontextController { @Body() params: DbiamPersonenkontextBodyParams, @Permissions() permissions: PersonPermissions, ): Promise { - // Construct new personenkontext - const newPersonenkontext: Personenkontext = this.personenkontextFactory.createNew( + // Get existing personenkontexte + const existingPKs: DbiamPersonenkontextBodyParams[] = await this.personenkontextRepo.findByPerson( params.personId, - params.organisationId, - params.rolleId, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, ); - //Check specifications - const specificationCheckError: Option = - await this.dbiamPersonenkontextService.checkSpecifications(newPersonenkontext); - if (specificationCheckError) { - throw specificationCheckError; + // Add new PK to list + existingPKs.push({ + personId: params.personId, + organisationId: params.organisationId, + rolleId: params.rolleId, + befristung: params.befristung, + }); + + // Update + const personenkontextUpdate: PersonenkontexteUpdate = + this.personenkontextFactory.createNewPersonenkontexteUpdate( + params.personId, + new Date(), + existingPKs.length - 1, + existingPKs, + permissions, + ); + + const updateResult: Personenkontext[] | PersonenkontexteUpdateError = + await personenkontextUpdate.update(); + + if (updateResult instanceof DomainError) { + throw updateResult; } - // Save personenkontext - const saveResult: Result, DomainError> = await this.personenkontextRepo.saveAuthorized( - newPersonenkontext, - permissions, + const newPersonenkontext: Personenkontext | undefined = updateResult.find( + (pk: Personenkontext) => + pk.personId === params.personId && + pk.organisationId === params.organisationId && + pk.rolleId === params.rolleId, ); - if (!saveResult.ok) { - if (saveResult.error instanceof OrganisationMatchesRollenartError) { - throw saveResult.error; - } - - throw SchulConnexErrorMapper.mapSchulConnexErrorToHttpException( - SchulConnexErrorMapper.mapDomainErrorToSchulConnexError(saveResult.error), - ); + if (!newPersonenkontext) { + throw new PersonenkontextCommitError(); } - return new DBiamPersonenkontextResponse(saveResult.value); + return new DBiamPersonenkontextResponse(newPersonenkontext); } } diff --git a/src/modules/personenkontext/api/dbiam-personenkontexte-update.error.ts b/src/modules/personenkontext/api/dbiam-personenkontexte-update.error.ts index cda49b3cc..9f45ca6ec 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontexte-update.error.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontexte-update.error.ts @@ -10,6 +10,7 @@ export enum PersonenkontexteUpdateErrorI18nTypes { PERSON_ID_MISMATCH = 'PERSON_ID_MISMATCH', PERSON_NOT_FOUND = 'PERSON_NOT_FOUND', INVALID_PERSONENKONTEXT_FOR_PERSON_WITH_ROLLENART_LERN = 'INVALID_PERSONENKONTEXT_FOR_PERSON_WITH_ROLLENART_LERN', + BEFRISTUNG_REQUIRED_FOR_PERSONENKONTEXT = ' BEFRISTUNG_REQUIRED_FOR_PERSONENKONTEXT', } export type DbiamPersonenkontexteUpdateErrorProps = DbiamErrorProps & { i18nKey: PersonenkontexteUpdateErrorI18nTypes; diff --git a/src/modules/personenkontext/api/param/dbiam-create-person-with-context.body.params.ts b/src/modules/personenkontext/api/param/dbiam-create-person-with-context.body.params.ts index deeb768ff..4d80b2b47 100644 --- a/src/modules/personenkontext/api/param/dbiam-create-person-with-context.body.params.ts +++ b/src/modules/personenkontext/api/param/dbiam-create-person-with-context.body.params.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsOptional, IsString, MinLength } from 'class-validator'; +import { IsDate, IsNotEmpty, IsOptional, IsString, MinLength } from 'class-validator'; import { IsDIN91379A } from '../../../../shared/util/din-91379-validation.js'; import { OrganisationID, RolleID } from '../../../../shared/types/aggregate-ids.types.js'; @@ -16,6 +16,11 @@ export class DbiamCreatePersonWithContextBodyParams { @ApiProperty({ required: true }) public readonly vorname!: string; + @IsDate() + @IsOptional() + @ApiProperty({ required: false }) + public readonly befristung?: Date; + @IsString() @IsOptional() @ApiProperty({ required: false }) diff --git a/src/modules/personenkontext/api/param/dbiam-personenkontext.body.params.ts b/src/modules/personenkontext/api/param/dbiam-personenkontext.body.params.ts index 6d9e0c3af..35717f924 100644 --- a/src/modules/personenkontext/api/param/dbiam-personenkontext.body.params.ts +++ b/src/modules/personenkontext/api/param/dbiam-personenkontext.body.params.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; +import { IsDate, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { OrganisationID, PersonID, RolleID } from '../../../../shared/types/index.js'; @@ -21,4 +21,9 @@ export class DbiamPersonenkontextBodyParams { @IsUUID() @ApiProperty({ type: String }) public readonly rolleId!: RolleID; + + @IsDate() + @IsOptional() + @ApiProperty({ required: false }) + public readonly befristung?: Date; } diff --git a/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts b/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts index 879e4a272..a07045bbf 100644 --- a/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts +++ b/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts @@ -14,6 +14,8 @@ import { PersonenkontexteUpdateError } from '../domain/error/personenkontexte-up import { UpdateInvalidLastModifiedError } from '../domain/error/update-invalid-last-modified.error.js'; import { UpdatePersonNotFoundError } from '../domain/error/update-person-not-found.error.js'; import { UpdateInvalidRollenartForLernError } from '../domain/error/update-invalid-rollenart-for-lern.error.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; +import { PersonenkontextBefristungRequiredError } from '../domain/error/personenkontext-befristung-required.error.js'; @Catch(PersonenkontexteUpdateError) export class PersonenkontexteUpdateExceptionFilter implements ExceptionFilter { @@ -67,6 +69,20 @@ export class PersonenkontexteUpdateExceptionFilter implements ExceptionFilter) { this.personId = personenkontext.personId; this.organisationId = personenkontext.organisationId; this.rolleId = personenkontext.rolleId; + this.befristung = personenkontext.befristung; } } diff --git a/src/modules/personenkontext/domain/dbiam-personenkontext.factory.ts b/src/modules/personenkontext/domain/dbiam-personenkontext.factory.ts index 9670b74aa..5d862d771 100644 --- a/src/modules/personenkontext/domain/dbiam-personenkontext.factory.ts +++ b/src/modules/personenkontext/domain/dbiam-personenkontext.factory.ts @@ -9,12 +9,14 @@ import { PersonRepository } from '../../person/persistence/person.repository.js' import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { IPersonPermissions } from '../../../shared/permissions/person-permissions.interface.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; @Injectable() export class DbiamPersonenkontextFactory { public constructor( private personenkontextFactory: PersonenkontextFactory, private readonly eventService: EventService, + private readonly logger: ClassLogger, private readonly dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, private readonly personRepo: PersonRepository, private readonly rolleRepo: RolleRepo, @@ -30,6 +32,7 @@ export class DbiamPersonenkontextFactory { ): PersonenkontexteUpdate { return PersonenkontexteUpdate.createNew( this.eventService, + this.logger, this.dBiamPersonenkontextRepo, this.personRepo, this.rolleRepo, diff --git a/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts b/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts index 2629e6fc1..c44bb4ded 100644 --- a/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts +++ b/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts @@ -8,6 +8,7 @@ import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { PersonenkontextSpecificationError } from '../specification/error/personenkontext-specification.error.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { CheckRollenartLernSpecification } from '../specification/nur-rolle-lern.js'; +import { CheckBefristungSpecification } from '../specification/befristung-required-bei-rolle-befristungspflicht.js'; @Injectable() export class DBiamPersonenkontextService { @@ -38,10 +39,15 @@ export class DBiamPersonenkontextService { this.personenkontextRepo, this.rolleRepo, ); + + // Checks if the sent kontext has a Rolle that is Befristungspflicht. If yes and there is no befristung set then throw an exception + const befristungRequired: CheckBefristungSpecification = new CheckBefristungSpecification(this.rolleRepo); + const pkKlasseSpecification: PersonenkontextKlasseSpecification = new PersonenkontextKlasseSpecification( nurLehrUndLernAnKlasse, gleicheRolleAnKlasseWieSchule, nurRollenartLern, + befristungRequired, ); return pkKlasseSpecification.returnsError(personenkontext); diff --git a/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.spec.ts b/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.spec.ts new file mode 100644 index 000000000..d7f83e16d --- /dev/null +++ b/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.spec.ts @@ -0,0 +1,10 @@ +import { PersonenkontextBefristungRequiredError } from './personenkontext-befristung-required.error.js'; + +describe('PersonenkontextBefristungRequiredError', () => { + it('should create an error with the correct message and code', () => { + const error: PersonenkontextBefristungRequiredError = new PersonenkontextBefristungRequiredError(); + + expect(error).toBeInstanceOf(PersonenkontextBefristungRequiredError); + expect(error.details).toBeUndefined(); + }); +}); diff --git a/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.ts b/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.ts new file mode 100644 index 000000000..962d9c6a4 --- /dev/null +++ b/src/modules/personenkontext/domain/error/personenkontext-befristung-required.error.ts @@ -0,0 +1,7 @@ +import { PersonenkontexteUpdateError } from './personenkontexte-update.error.js'; + +export class PersonenkontextBefristungRequiredError extends PersonenkontexteUpdateError { + public constructor(details?: unknown[] | Record) { + super(`Personenkontexte could not be saved because the Befristung is missing for at least 1 Kontext!`, details); + } +} diff --git a/src/modules/personenkontext/domain/error/personenkontext-commit.error.spec.ts b/src/modules/personenkontext/domain/error/personenkontext-commit.error.spec.ts index 3792a91a0..b4a2e8733 100644 --- a/src/modules/personenkontext/domain/error/personenkontext-commit.error.spec.ts +++ b/src/modules/personenkontext/domain/error/personenkontext-commit.error.spec.ts @@ -2,25 +2,23 @@ import { PersonenkontextCommitError } from './personenkontext-commit.error.js'; describe('PersonenkontextCommitError', () => { it('should create an error with the correct message and code', () => { - const message: string = 'An error occurred'; - const error: PersonenkontextCommitError = new PersonenkontextCommitError(message); + const error: PersonenkontextCommitError = new PersonenkontextCommitError(); expect(error).toBeInstanceOf(PersonenkontextCommitError); - expect(error.message).toBe(message); - expect(error.code).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.message).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.code).toBe('ENTITIES_COULD_NOT_BE_UPDATED'); expect(error.details).toBeUndefined(); }); it('should create an error with details as an array', () => { - const message: string = 'An error occurred'; const details: { key: string; }[] = [{ key: 'value' }]; - const error: PersonenkontextCommitError = new PersonenkontextCommitError(message, details); + const error: PersonenkontextCommitError = new PersonenkontextCommitError(details); expect(error).toBeInstanceOf(PersonenkontextCommitError); - expect(error.message).toBe(message); - expect(error.code).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.message).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.code).toBe('ENTITIES_COULD_NOT_BE_UPDATED'); expect(error.details).toEqual(details); }); }); diff --git a/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts b/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts index 5268d226c..5212d6aec 100644 --- a/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts +++ b/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts @@ -1,10 +1,7 @@ -import { DomainError } from '../../../../shared/error/index.js'; +import { PersonenkontexteUpdateError } from './personenkontexte-update.error.js'; -export class PersonenkontextCommitError extends DomainError { - public constructor( - public override readonly message: string, - details?: unknown[] | Record, - ) { - super(message, 'PERSONENKONTEXT_COULD_NOT_BE_COMMITED', details); +export class PersonenkontextCommitError extends PersonenkontexteUpdateError { + public constructor(details?: unknown[] | Record) { + super('PERSONENKONTEXT_COULD_NOT_BE_COMMITED', details); } } diff --git a/src/modules/personenkontext/domain/personenkontext-creation.service.ts b/src/modules/personenkontext/domain/personenkontext-creation.service.ts index 1fe247c9d..5f5951a84 100644 --- a/src/modules/personenkontext/domain/personenkontext-creation.service.ts +++ b/src/modules/personenkontext/domain/personenkontext-creation.service.ts @@ -33,6 +33,7 @@ export class PersonenkontextCreationService { organisationId: string, rolleId: string, personalnummer?: string, + befristung?: Date, ): Promise { const person: Person | DomainError = await this.personFactory.createNew({ vorname: vorname, @@ -68,6 +69,7 @@ export class PersonenkontextCreationService { personId: savedPerson.id, organisationId, rolleId, + befristung, }, ], new PermissionsOverride(permissions).grantPersonModifyPermission(savedPerson.id), diff --git a/src/modules/personenkontext/domain/personenkontext.factory.ts b/src/modules/personenkontext/domain/personenkontext.factory.ts index 8df6203ea..06b330d5a 100644 --- a/src/modules/personenkontext/domain/personenkontext.factory.ts +++ b/src/modules/personenkontext/domain/personenkontext.factory.ts @@ -28,6 +28,7 @@ export class PersonenkontextFactory { jahrgangsstufe?: Jahrgangsstufe, sichtfreigabe?: SichtfreigabeType, loeschungZeitpunkt?: Date, + befristung?: Date, ): Personenkontext { return Personenkontext.construct( this.personRepository, @@ -46,6 +47,7 @@ export class PersonenkontextFactory { sichtfreigabe, loeschungZeitpunkt, revision, + befristung, ); } @@ -59,6 +61,7 @@ export class PersonenkontextFactory { jahrgangsstufe: Jahrgangsstufe | undefined = undefined, sichtfreigabe: SichtfreigabeType | undefined = undefined, loeschungZeitpunkt: Date | undefined = undefined, + befristung: Date | undefined = undefined, ): Personenkontext { return Personenkontext.createNew( this.personRepository, @@ -73,6 +76,7 @@ export class PersonenkontextFactory { jahrgangsstufe, sichtfreigabe, loeschungZeitpunkt, + befristung, ); } } diff --git a/src/modules/personenkontext/domain/personenkontext.ts b/src/modules/personenkontext/domain/personenkontext.ts index cbf9a118f..c070ad6b0 100644 --- a/src/modules/personenkontext/domain/personenkontext.ts +++ b/src/modules/personenkontext/domain/personenkontext.ts @@ -47,6 +47,7 @@ export class Personenkontext { public readonly sichtfreigabe: SichtfreigabeType | undefined, public readonly loeschungZeitpunkt: Date | undefined, public readonly revision: Persisted, + public readonly befristung: Date | undefined, ) {} public static construct( @@ -67,6 +68,7 @@ export class Personenkontext { sichtfreigabe: SichtfreigabeType | undefined = undefined, loeschungZeitpunkt: Date | undefined = undefined, revision: Persisted = '1', + befristung: Date | undefined = undefined, ): Personenkontext { return new Personenkontext( personRepository, @@ -86,6 +88,7 @@ export class Personenkontext { sichtfreigabe, loeschungZeitpunkt, revision, + befristung, ); } @@ -103,6 +106,7 @@ export class Personenkontext { jahrgangsstufe: Jahrgangsstufe | undefined = undefined, sichtfreigabe: SichtfreigabeType | undefined = undefined, loeschungZeitpunkt: Date | undefined = undefined, + befristung: Date | undefined = undefined, ): Personenkontext { return new Personenkontext( personRepository, @@ -122,6 +126,7 @@ export class Personenkontext { sichtfreigabe, loeschungZeitpunkt, undefined, + befristung, ); } diff --git a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts index c8216e32a..1018af595 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts @@ -24,8 +24,10 @@ import { MissingPermissionsError } from '../../../shared/error/missing-permissio import { DoFactory, PersonPermissionsMock } from '../../../../test/utils/index.js'; import { Person } from '../../person/domain/person.js'; import { Rolle } from '../../rolle/domain/rolle.js'; -import { RollenArt } from '../../rolle/domain/rolle.enums.js'; +import { RollenArt, RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; import { UpdateInvalidRollenartForLernError } from './error/update-invalid-rollenart-for-lern.error.js'; +import { PersonenkontextBefristungRequiredError } from './error/personenkontext-befristung-required.error.js'; +import { CheckBefristungSpecification } from '../specification/befristung-required-bei-rolle-befristungspflicht.js'; function createPKBodyParams(personId: PersonID): DbiamPersonenkontextBodyParams[] { const firstCreatePKBodyParams: DbiamPersonenkontextBodyParams = createMock({ @@ -57,6 +59,7 @@ describe('PersonenkontexteUpdate', () => { let pk2: Personenkontext; let personPermissionsMock: PersonPermissionsMock; let rolleRepoMock: DeepMocked; + let loggerMock: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -110,6 +113,7 @@ describe('PersonenkontexteUpdate', () => { personId: bodyParam1.personId, organisationId: bodyParam1.organisationId, rolleId: bodyParam1.rolleId, + befristung: undefined, }); pk2 = createMock>({ updatedAt: faker.date.past(), @@ -119,6 +123,7 @@ describe('PersonenkontexteUpdate', () => { }); personPermissionsMock = new PersonPermissionsMock(); rolleRepoMock = module.get(RolleRepo); + loggerMock = module.get(ClassLogger); }); afterAll(async () => { @@ -155,6 +160,18 @@ describe('PersonenkontexteUpdate', () => { dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); // mock while checking the existing PKs dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); //mock the return values in the end of update method + const mapRollen: Map> = new Map(); + mapRollen.set( + faker.string.uuid(), + DoFactory.createRolle(true, { + rollenart: RollenArt.LEHR, + merkmale: [RollenMerkmal.KOPERS_PFLICHT], + id: pk1.rolleId, + }), + ); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -162,6 +179,43 @@ describe('PersonenkontexteUpdate', () => { }); }); + describe('when personenkontext could not be saved', () => { + beforeAll(() => { + sut = dbiamPersonenkontextFactory.createNewPersonenkontexteUpdate( + personId, + lastModified, + 0, + [pk1], + personPermissionsMock, + ); + }); + + it('should log error', async () => { + dBiamPersonenkontextRepoMock.find.mockResolvedValue(null); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(null); //mock pk1 is not found => therefore handled as new + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([]); //person has no existing PKs + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([]); //CheckRollenartLernSpecification: person has no existing PKs + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); //CheckRollenartLernSpecification + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); //CheckRollenartLernSpecification + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); // mock while checking the existing PKs + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); + const newPerson: Person = createMock>(); + personRepoMock.findById.mockResolvedValueOnce(newPerson); + + const error: Error = new Error('DB Error'); + dBiamPersonenkontextRepoMock.save.mockRejectedValueOnce(error); + + const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); + + expect(updateResult).toBeInstanceOf(Array); + expect(loggerMock.error).toHaveBeenCalledWith( + expect.stringMatching(/Personenkontext with \(.*\) could not be added!/), + error, + ); + }); + }); + describe('when sent personenkontexte contain personenkontext with mismatching personId', () => { beforeAll(() => { const count: number = 2; @@ -176,6 +230,15 @@ describe('PersonenkontexteUpdate', () => { it('should return UpdatePersonIdMismatchError', async () => { const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); + const mapRollen: Map> = new Map(); + mapRollen.set( + faker.string.uuid(), + DoFactory.createRolle(true, { + rollenart: RollenArt.LEHR, + merkmale: [RollenMerkmal.KOPERS_PFLICHT], + }), + ); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); expect(updateError).toBeInstanceOf(UpdatePersonIdMismatchError); }); @@ -205,6 +268,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -237,6 +301,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -266,6 +331,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -296,6 +362,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -329,6 +396,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -351,6 +419,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); expect(dBiamPersonenkontextRepoMock.delete).toHaveBeenCalledTimes(0); @@ -380,6 +449,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -410,6 +480,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -431,6 +502,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const permissions: DeepMocked = createMock(); @@ -458,6 +530,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); personRepoMock.findById.mockResolvedValue(undefined); const permissions: DeepMocked = createMock(); @@ -501,6 +574,7 @@ describe('PersonenkontexteUpdate', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); @@ -543,6 +617,74 @@ describe('PersonenkontexteUpdate', () => { mapRollenExisting.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenExisting); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + + const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); + + expect(updateError).toBeDefined(); + }); + it('should return PersonenkontextBefristungRequiredError if new personenkontext roles mix LERN with other types', async () => { + const newPerson: Person = createMock>(); + personRepoMock.findById.mockResolvedValueOnce(newPerson); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(pk1); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(pk2); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1]); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1]); + + const mapRollen: Map> = new Map(); + mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + + const mapRollenExisting: Map> = new Map(); + mapRollenExisting.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenExisting); + + const mapRollenBefristung: Map> = new Map(); + mapRollenBefristung.set( + pk1.rolleId, + DoFactory.createRolle(true, { + id: pk1.rolleId, + rollenart: RollenArt.LERN, + merkmale: [RollenMerkmal.BEFRISTUNG_PFLICHT], + }), + ); + mapRollenBefristung.set( + pk2.rolleId, + DoFactory.createRolle(true, { + id: pk2.rolleId, + rollenart: RollenArt.LERN, + merkmale: [RollenMerkmal.BEFRISTUNG_PFLICHT], + }), + ); + + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenBefristung); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenBefristung); + + const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); + + expect(updateError).toBeInstanceOf(PersonenkontextBefristungRequiredError); + }); + it('Should not throw any PersonenkontextBefristungRequiredError', async () => { + const newPerson: Person = createMock>(); + personRepoMock.findById.mockResolvedValueOnce(newPerson); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(pk1); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(pk2); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1]); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1]); + + const mapRollen: Map> = new Map(); + mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + + const mapRollenExisting: Map> = new Map(); + mapRollenExisting.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); + + mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); + + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenExisting); + rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); + + jest.spyOn(CheckBefristungSpecification.prototype, 'checkBefristung').mockResolvedValue(true); const updateError: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); diff --git a/src/modules/personenkontext/domain/personenkontexte-update.ts b/src/modules/personenkontext/domain/personenkontexte-update.ts index 309840405..1d296f8e1 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.ts @@ -8,7 +8,6 @@ import { UpdatePersonIdMismatchError } from './error/update-person-id-mismatch.e import { PersonenkontexteUpdateError } from './error/personenkontexte-update.error.js'; import { PersonenkontextFactory } from './personenkontext.factory.js'; import { EventService } from '../../../core/eventbus/index.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; import { UpdatePersonNotFoundError } from './error/update-person-not-found.error.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; import { PersonRepository } from '../../person/persistence/person.repository.js'; @@ -23,10 +22,14 @@ import { MissingPermissionsError } from '../../../shared/error/missing-permissio import { UpdateInvalidRollenartForLernError } from './error/update-invalid-rollenart-for-lern.error.js'; import { IPersonPermissions } from '../../../shared/permissions/person-permissions.interface.js'; import { CheckRollenartLernSpecification } from '../specification/nur-rolle-lern.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; +import { CheckBefristungSpecification } from '../specification/befristung-required-bei-rolle-befristungspflicht.js'; +import { PersonenkontextBefristungRequiredError } from './error/personenkontext-befristung-required.error.js'; export class PersonenkontexteUpdate { private constructor( private readonly eventService: EventService, + private readonly logger: ClassLogger, private readonly dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, private readonly personRepo: PersonRepository, private readonly rolleRepo: RolleRepo, @@ -41,6 +44,7 @@ export class PersonenkontexteUpdate { public static createNew( eventService: EventService, + logger: ClassLogger, dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, personRepo: PersonRepository, rolleRepo: RolleRepo, @@ -54,6 +58,7 @@ export class PersonenkontexteUpdate { ): PersonenkontexteUpdate { return new PersonenkontexteUpdate( eventService, + logger, dBiamPersonenkontextRepo, personRepo, rolleRepo, @@ -89,6 +94,7 @@ export class PersonenkontexteUpdate { undefined, undefined, undefined, + pkBodyParam.befristung, ); personenKontexte.push(newPK); // New } else { @@ -207,16 +213,12 @@ export class PersonenkontexteUpdate { pk.rolleId == existingPK.rolleId, ) ) { - await this.dBiamPersonenkontextRepo.delete(existingPK); - deletedPKs.push(existingPK); - this.eventService.publish( - new SimplePersonenkontextDeletedEvent( - existingPK.id, - existingPK.personId, - existingPK.organisationId, - existingPK.rolleId, - ), - ); + try { + await this.dBiamPersonenkontextRepo.delete(existingPK).then(() => {}); + deletedPKs.push(existingPK); + } catch (err) { + this.logger.error(`Personenkontext with ID ${existingPK.id} could not be deleted!`, err); + } } } @@ -238,11 +240,15 @@ export class PersonenkontexteUpdate { existingPK.rolleId == sentPK.rolleId, ) ) { - const savedPK: Personenkontext = await this.dBiamPersonenkontextRepo.save(sentPK); - createdPKs.push(savedPK); - /*this.eventService.publish( - new PersonenkontextCreatedEvent(sentPK.personId, sentPK.organisationId, sentPK.rolleId), - );*/ + try { + const savedPK: Personenkontext = await this.dBiamPersonenkontextRepo.save(sentPK); + createdPKs.push(savedPK); + } catch (err) { + this.logger.error( + `Personenkontext with (person: ${sentPK.personId}, organisation: ${sentPK.organisationId}, rolle: ${sentPK.rolleId}) could not be added!`, + err, + ); + } } } @@ -264,18 +270,38 @@ export class PersonenkontexteUpdate { return undefined; } - public async update(): Promise[] | PersonenkontexteUpdateError> { + private async checkBefristungSpecification( + sentPKs: Personenkontext[], + ): Promise> { + const isSatisfied: boolean = await new CheckBefristungSpecification(this.rolleRepo).checkBefristung(sentPKs); + + if (!isSatisfied) { + return new PersonenkontextBefristungRequiredError(); + } + + return undefined; + } + + public async update(ldapEntryUUID?: string): Promise[] | PersonenkontexteUpdateError> { + //If first lehrer kontext is created and a UUID is passed as ldapEntryUUID it is used as internal LDAP entryUUID (needed for migration, can be build back afterwards) const sentPKs: Personenkontext[] | PersonenkontexteUpdateError = await this.getSentPersonenkontexte(); if (sentPKs instanceof PersonenkontexteUpdateError) { return sentPKs; } const existingPKs: Personenkontext[] = await this.dBiamPersonenkontextRepo.findByPerson(this.personId); + const validationForLernError: Option = await this.checkRollenartLernSpecification(sentPKs); if (validationForLernError) { return validationForLernError; } + + const validationForBefristung: Option = + await this.checkBefristungSpecification(sentPKs); + if (validationForBefristung) { + return validationForBefristung; + } const validationError: Option = await this.validate(existingPKs); if (validationError) { return validationError; @@ -293,7 +319,7 @@ export class PersonenkontexteUpdate { this.personId, ); - await this.publishEvent(deletedPKs, createdPKs, existingPKsAfterUpdate); + await this.publishEvent(deletedPKs, createdPKs, existingPKsAfterUpdate, ldapEntryUUID); return existingPKsAfterUpdate; } @@ -302,6 +328,7 @@ export class PersonenkontexteUpdate { deletedPKs: Personenkontext[], createdPKs: Personenkontext[], existingPKs: Personenkontext[], + ldapEntryUUID?: string, ): Promise { const deletedRollenIDs: RolleID[] = deletedPKs.map((pk: Personenkontext) => pk.rolleId); const createdRollenIDs: RolleID[] = createdPKs.map((pk: Personenkontext) => pk.rolleId); @@ -324,6 +351,9 @@ export class PersonenkontexteUpdate { ]); if (!person) { + this.logger.error( + `Could not find person with ID ${this.personId} while building PersonenkontextUpdatedEvent`, + ); return; // Person can not be found } @@ -345,6 +375,7 @@ export class PersonenkontexteUpdate { orgas.get(pk.organisationId)!, rollen.get(pk.rolleId)!, ]), + ldapEntryUUID, ), ); } diff --git a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts index bfe606dfc..e8c994d96 100644 --- a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts +++ b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts @@ -16,9 +16,6 @@ import { MissingPermissionsError } from '../../../shared/error/missing-permissio import { EntityAlreadyExistsError } from '../../../shared/error/entity-already-exists.error.js'; import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { MismatchedRevisionError } from '../../../shared/error/mismatched-revision.error.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; -import { EventService } from '../../../core/eventbus/index.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; export function mapAggregateToData( personenKontext: Personenkontext, @@ -30,6 +27,7 @@ export function mapAggregateToData( organisationId: personenKontext.organisationId, rolleId: rel(RolleEntity, personenKontext.rolleId), rolle: Rolle.LERNENDER, // Placeholder, until rolle is removed from entity + befristung: personenKontext.befristung, }; } @@ -51,6 +49,7 @@ function mapEntityToAggregate( entity.jahrgangsstufe, entity.sichtfreigabe, entity.loeschungZeitpunkt, + entity.befristung, ); } @@ -58,7 +57,6 @@ function mapEntityToAggregate( export class DBiamPersonenkontextRepo { public constructor( private readonly em: EntityManager, - private readonly eventService: EventService, private readonly personenkontextFactory: PersonenkontextFactory, ) {} @@ -204,6 +202,9 @@ export class DBiamPersonenkontextRepo { return !!personenKontext; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async save(personenKontext: Personenkontext): Promise> { if (personenKontext.id) { return this.update(personenKontext); @@ -212,6 +213,9 @@ export class DBiamPersonenkontextRepo { } } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async saveAuthorized( personenkontext: Personenkontext, permissions: PersonPermissions, @@ -257,19 +261,16 @@ export class DBiamPersonenkontextRepo { ); await this.em.persistAndFlush(personenKontextEntity); - this.eventService.publish( - new PersonenkontextCreatedEvent( - personenkontext.personId, - personenkontext.organisationId, - personenkontext.rolleId, - ), - ); + return { ok: true, value: mapEntityToAggregate(personenKontextEntity, this.personenkontextFactory), }; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async deleteAuthorized( id: PersonenkontextID, revision: string, @@ -296,22 +297,22 @@ export class DBiamPersonenkontextRepo { return; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ private async create(personenKontext: Personenkontext): Promise> { const personenKontextEntity: PersonenkontextEntity = this.em.create( PersonenkontextEntity, mapAggregateToData(personenKontext), ); await this.em.persistAndFlush(personenKontextEntity); - this.eventService.publish( - new PersonenkontextCreatedEvent( - personenKontext.personId, - personenKontext.organisationId, - personenKontext.rolleId, - ), - ); + return mapEntityToAggregate(personenKontextEntity, this.personenkontextFactory); } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ private async update(personenKontext: Personenkontext): Promise> { const personenKontextEntity: Loaded = await this.em.findOneOrFail( PersonenkontextEntity, @@ -336,6 +337,9 @@ export class DBiamPersonenkontextRepo { return organisationIDs.includes(entity.organisationId); } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async delete(personenKontext: Personenkontext): Promise { const personId: PersonID = personenKontext.personId; const organisationId: OrganisationID = personenKontext.organisationId; @@ -346,14 +350,6 @@ export class DBiamPersonenkontextRepo { organisationId: organisationId, rolleId: rolleId, }); - this.eventService.publish( - new SimplePersonenkontextDeletedEvent( - personenKontext.id, - personenKontext.personId, - personenKontext.organisationId, - personenKontext.rolleId, - ), - ); } public async hasSystemrechtAtOrganisation( @@ -390,6 +386,9 @@ export class DBiamPersonenkontextRepo { return result[0].has_systemrecht_at_orga as boolean; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async deleteById(id: string): Promise { const deletedPersons: number = await this.em.nativeDelete(PersonenkontextEntity, { id }); return deletedPersons > 0; diff --git a/src/modules/personenkontext/persistence/personenkontext.entity.ts b/src/modules/personenkontext/persistence/personenkontext.entity.ts index e233b6f8b..4c521cac2 100644 --- a/src/modules/personenkontext/persistence/personenkontext.entity.ts +++ b/src/modules/personenkontext/persistence/personenkontext.entity.ts @@ -68,4 +68,8 @@ export class PersonenkontextEntity extends TimestampedEntity { @AutoMap(() => String) @Property({ nullable: false, default: '1' }) public revision!: string & Opt; + + @AutoMap(() => Date) + @Property({ nullable: true, type: DateTimeType }) + public readonly befristung?: Date; } diff --git a/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts b/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts new file mode 100644 index 000000000..c2caabe38 --- /dev/null +++ b/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts @@ -0,0 +1,30 @@ +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { Rolle } from '../../rolle/domain/rolle.js'; + +export class CheckBefristungSpecification { + public constructor(private readonly rolleRepo: RolleRepo) {} + + public async checkBefristung(sentPKs: Personenkontext[]): Promise { + // Early return if all Personenkontext have befristung defined + if (sentPKs.every((pk: Personenkontext) => pk.befristung !== undefined)) { + return true; + } + // Extract unique Rolle IDs from sentPKs + const uniqueRolleIds: Set = new Set(sentPKs.map((pk: Personenkontext) => pk.rolleId)); + + const mapRollen: Map> = await this.rolleRepo.findByIds(Array.from(uniqueRolleIds)); + + for (const pk of sentPKs) { + const rolle: Rolle | undefined = mapRollen.get(pk.rolleId); + if (rolle && rolle.merkmale.includes(RollenMerkmal.BEFRISTUNG_PFLICHT)) { + // Check if befristung is set + if (pk.befristung === undefined) { + return false; + } + } + } + return true; + } +} diff --git a/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts new file mode 100644 index 000000000..2d52eeb70 --- /dev/null +++ b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts @@ -0,0 +1,168 @@ +import { faker } from '@faker-js/faker'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; + +import { DoFactory } from '../../../../test/utils/index.js'; +import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; +import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; +import { GleicheRolleAnKlasseWieSchule } from './gleiche-rolle-an-klasse-wie-schule.js'; + +describe('GleicheRolleAnKlasseWieSchule specification', () => { + const organisationRepoMock: DeepMocked = createMock(); + const personenkontextRepoMock: DeepMocked = createMock(); + const rolleRepoMock: DeepMocked = createMock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return true, if all checks pass', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + const rolleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { id: rolleId })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false, { rolleId }); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is not Klasse', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return false, if organisation does not exist', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if organisation does not have administriertVon', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: undefined }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if parent organisation can not be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: faker.string.uuid() }), + ); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if no matching personenkontext can be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: faker.string.uuid() }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([DoFactory.createPersonenkontext(true)]); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if no rolle can be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if rollen ids do not match', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true)); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); +}); diff --git a/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts new file mode 100644 index 000000000..fced65db5 --- /dev/null +++ b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts @@ -0,0 +1,73 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { DoFactory } from '../../../../test/utils/do-factory.js'; +import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; +import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; +import { RollenArt } from '../../rolle/domain/rolle.enums.js'; +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { NurLehrUndLernAnKlasse } from './nur-lehr-und-lern-an-klasse.js'; + +describe('NurLehrUndLernAnKlasse specification', () => { + const organisationRepoMock: DeepMocked = createMock(); + const rolleRepoMock: DeepMocked = createMock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return true, if organisation is klasse and rolle is LEHR', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is klasse and rolle is LERN', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is not KLASSE', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return false, if organisation is not found', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return true, if rolle is not found', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(false); + }); +}); diff --git a/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts b/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts index 89a621bd0..71ce22bc0 100644 --- a/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts +++ b/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts @@ -13,12 +13,15 @@ import { OrganisationRepository } from '../../organisation/persistence/organisat import { PersonRepository } from '../../person/persistence/person.repository.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; +import { CheckBefristungSpecification } from './befristung-required-bei-rolle-befristungspflicht.js'; +import { PersonenkontextBefristungRequiredError } from '../domain/error/personenkontext-befristung-required.error.js'; describe('PersonenkontextKlasseSpecification Integration', () => { let specification: PersonenkontextKlasseSpecification; let nurLehrUndLernAnKlasseMock: DeepMocked; let gleicheRolleAnKlasseWieSchuleMock: DeepMocked; let checkRollenartLernSpecificationMock: DeepMocked; + let befristungRequiredMock: DeepMocked; let module: TestingModule; beforeEach(async () => { @@ -36,6 +39,10 @@ describe('PersonenkontextKlasseSpecification Integration', () => { provide: CheckRollenartLernSpecification, useValue: createMock(), }, + { + provide: CheckBefristungSpecification, + useValue: createMock(), + }, { provide: OrganisationRepository, useValue: createMock(), @@ -60,6 +67,7 @@ describe('PersonenkontextKlasseSpecification Integration', () => { nurLehrUndLernAnKlasseMock = module.get(NurLehrUndLernAnKlasse); gleicheRolleAnKlasseWieSchuleMock = module.get(GleicheRolleAnKlasseWieSchule); checkRollenartLernSpecificationMock = module.get(CheckRollenartLernSpecification); + befristungRequiredMock = module.get(CheckBefristungSpecification); }); beforeEach(() => { @@ -79,12 +87,14 @@ describe('PersonenkontextKlasseSpecification Integration', () => { nurLehrUndLernAnKlasseMock, gleicheRolleAnKlasseWieSchuleMock, checkRollenartLernSpecificationMock, + befristungRequiredMock, ); const personenkontextMock: DeepMocked> = createMock>(); checkRollenartLernSpecificationMock.checkRollenartLern.mockResolvedValueOnce(false); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValueOnce(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValueOnce(true); + befristungRequiredMock.checkBefristung.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); @@ -96,12 +106,14 @@ describe('PersonenkontextKlasseSpecification Integration', () => { nurLehrUndLernAnKlasseMock, gleicheRolleAnKlasseWieSchuleMock, checkRollenartLernSpecificationMock, + befristungRequiredMock, ); const personenkontextMock: DeepMocked> = createMock>(); checkRollenartLernSpecificationMock.checkRollenartLern.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(false); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(true); + befristungRequiredMock.checkBefristung.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); @@ -113,11 +125,13 @@ describe('PersonenkontextKlasseSpecification Integration', () => { nurLehrUndLernAnKlasseMock, gleicheRolleAnKlasseWieSchuleMock, checkRollenartLernSpecificationMock, + befristungRequiredMock, ); const personenkontextMock: DeepMocked> = createMock>(); checkRollenartLernSpecificationMock.checkRollenartLern.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(true); + befristungRequiredMock.checkBefristung.mockResolvedValue(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(false); const result: Option = await specification.returnsError(personenkontextMock); @@ -130,15 +144,35 @@ describe('PersonenkontextKlasseSpecification Integration', () => { nurLehrUndLernAnKlasseMock, gleicheRolleAnKlasseWieSchuleMock, checkRollenartLernSpecificationMock, + befristungRequiredMock, ); const personenkontextMock: DeepMocked> = createMock>(); checkRollenartLernSpecificationMock.checkRollenartLern.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(true); + befristungRequiredMock.checkBefristung.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); expect(result).toBeUndefined(); }); + it('should return befristungRequiredMock when checkRollenartLern fails', async () => { + specification = new PersonenkontextKlasseSpecification( + nurLehrUndLernAnKlasseMock, + gleicheRolleAnKlasseWieSchuleMock, + checkRollenartLernSpecificationMock, + befristungRequiredMock, + ); + const personenkontextMock: DeepMocked> = createMock>(); + + checkRollenartLernSpecificationMock.checkRollenartLern.mockResolvedValueOnce(true); + nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValueOnce(true); + gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValueOnce(true); + befristungRequiredMock.checkBefristung.mockResolvedValue(false); + + const result: Option = await specification.returnsError(personenkontextMock); + + expect(result).toBeInstanceOf(PersonenkontextBefristungRequiredError); + }); }); diff --git a/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts b/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts index 0f2ad3c7e..3eac98ab3 100644 --- a/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts +++ b/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts @@ -6,6 +6,8 @@ import { NurLehrUndLernAnKlasseError } from './error/nur-lehr-und-lern-an-klasse import { GleicheRolleAnKlasseWieSchuleError } from './error/gleiche-rolle-an-klasse-wie-schule.error.js'; import { CheckRollenartLernSpecification } from './nur-rolle-lern.js'; import { UpdateInvalidRollenartForLernError } from '../domain/error/update-invalid-rollenart-for-lern.error.js'; +import { CheckBefristungSpecification } from './befristung-required-bei-rolle-befristungspflicht.js'; +import { PersonenkontextBefristungRequiredError } from '../domain/error/personenkontext-befristung-required.error.js'; /** * 'This specification is not extending CompositeSpecification, but combines specifications for Personenkontexte @@ -16,12 +18,16 @@ export class PersonenkontextKlasseSpecification { protected readonly nurLehrUndLernAnKlasse: NurLehrUndLernAnKlasse, protected readonly gleicheRolleAnKlasseWieSchule: GleicheRolleAnKlasseWieSchule, protected readonly nurRollenartLern: CheckRollenartLernSpecification, + protected readonly befristungRequired: CheckBefristungSpecification, ) {} public async returnsError(p: Personenkontext): Promise> { if (!(await this.nurRollenartLern.checkRollenartLern([p]))) { return new UpdateInvalidRollenartForLernError(); } + if (!(await this.befristungRequired.checkBefristung([p]))) { + return new PersonenkontextBefristungRequiredError(); + } if (!(await this.nurLehrUndLernAnKlasse.isSatisfiedBy(p))) { return new NurLehrUndLernAnKlasseError(); } diff --git a/src/modules/utility/event-adapter.spec.ts b/src/modules/utility/event-adapter.spec.ts deleted file mode 100644 index 3056671a9..000000000 --- a/src/modules/utility/event-adapter.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { - ConfigTestModule, - DatabaseTestModule, - DEFAULT_TIMEOUT_FOR_TESTCONTAINERS, - MapperTestModule, -} from '../../../test/utils/index.js'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { PersonRepository } from '../person/persistence/person.repository.js'; -import { RolleRepo } from '../rolle/repo/rolle.repo.js'; -import { faker } from '@faker-js/faker'; -import { OrganisationRepository } from '../organisation/persistence/organisation.repository.js'; -import { SimplePersonenkontextDeletedEvent } from '../../shared/events/simple-personenkontext-deleted.event.js'; -import { EventAdapter } from './event-adapter.js'; -import { ClassLogger } from '../../core/logging/class-logger.js'; -import { APP_PIPE } from '@nestjs/core'; -import { GlobalValidationPipe } from '../../shared/validation/global-validation.pipe.js'; -import { EventService } from '../../core/eventbus/services/event.service.js'; -import { Person } from '../person/domain/person.js'; -import { Organisation } from '../organisation/domain/organisation.js'; -import { Rolle } from '../rolle/domain/rolle.js'; -import { RollenArt } from '../rolle/domain/rolle.enums.js'; -import { OrganisationsTyp } from '../organisation/domain/organisation.enums.js'; -import { UtilityModule } from './utility.module.js'; - -describe('Event Adapter', () => { - let app: INestApplication; - - let sut: EventAdapter; - - let eventServiceMock: DeepMocked; - let personRepositoryMock: DeepMocked; - let organisationRepositoryMock: DeepMocked; - let rolleRepoMock: DeepMocked; - let loggerMock: DeepMocked; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigTestModule, - MapperTestModule, - DatabaseTestModule.forRoot({ isDatabaseRequired: false }), - UtilityModule, - ], - providers: [ - { - provide: APP_PIPE, - useClass: GlobalValidationPipe, - }, - { - provide: ClassLogger, - useValue: createMock(), - }, - ], - }) - .overrideProvider(ClassLogger) - .useValue(createMock()) - .overrideProvider(EventService) - .useValue(createMock()) - .overrideProvider(PersonRepository) - .useValue(createMock()) - .overrideProvider(OrganisationRepository) - .useValue(createMock()) - .overrideProvider(RolleRepo) - .useValue(createMock()) - .compile(); - - eventServiceMock = module.get(EventService); - personRepositoryMock = module.get(PersonRepository); - organisationRepositoryMock = module.get(OrganisationRepository); - rolleRepoMock = module.get(RolleRepo); - loggerMock = module.get(ClassLogger); - - sut = module.get(EventAdapter); - - app = module.createNestApplication(); - await app.init(); - }, DEFAULT_TIMEOUT_FOR_TESTCONTAINERS); - - afterAll(async () => { - await app.close(); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('handlePersonenkontextDeletedEvent', () => { - let fakePKId: string; - let fakePersonId: string; - let fakeOrgaId: string; - let fakeRolleId: string; - - beforeEach(() => { - fakePKId = faker.string.uuid(); - fakePersonId = faker.string.uuid(); - fakeOrgaId = faker.string.uuid(); - fakeRolleId = faker.string.uuid(); - }); - - describe('when every entity is found in DB', () => { - it('should log info and trigger PersonenkontextDeletedEvent', async () => { - const fakePerson: Person = createMock>({ - id: fakePersonId, - vorname: faker.person.firstName(), - familienname: faker.person.lastName(), - referrer: faker.internet.userName(), - }); - const fakeOrga: Organisation = createMock>({ - id: fakeOrgaId, - typ: OrganisationsTyp.SCHULE, - kennung: faker.string.alpha({ length: 6 }), - }); - const fakeRolle: Rolle = createMock>({ - id: fakeRolleId, - rollenart: RollenArt.LEHR, - }); - personRepositoryMock.findById.mockResolvedValueOnce(fakePerson); - organisationRepositoryMock.findById.mockResolvedValueOnce(fakeOrga); - rolleRepoMock.findById.mockResolvedValueOnce(fakeRolle); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(eventServiceMock.publish).toHaveBeenCalledWith( - expect.objectContaining({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - personData: expect.objectContaining({ - id: fakePerson.id, - vorname: fakePerson.vorname, - familienname: fakePerson.familienname, - referrer: fakePerson.referrer, - }), - kontextData: { - id: fakePKId, - rolleId: fakeRolle.id, - rolle: fakeRolle.rollenart, - orgaId: fakeOrga.id, - orgaTyp: fakeOrga.typ, - orgaKennung: fakeOrga.kennung, - }, - }), - ); - }); - }); - - describe('when person cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith(`Could not find person for personId:${event.personId}`); - }); - }); - - describe('when organisation cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith( - `Could not find organisation for orgaId:${event.organisationId}`, - ); - }); - }); - - describe('when rolle cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - organisationRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - rolleRepoMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith(`Could not find rolle for rolleId:${event.rolleId}`); - }); - }); - }); -}); diff --git a/src/modules/utility/event-adapter.ts b/src/modules/utility/event-adapter.ts deleted file mode 100644 index 83f4797df..000000000 --- a/src/modules/utility/event-adapter.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { EventHandler } from '../../core/eventbus/decorators/event-handler.decorator.js'; -import { ClassLogger } from '../../core/logging/class-logger.js'; -import { Organisation } from '../organisation/domain/organisation.js'; -import { OrganisationRepository } from '../organisation/persistence/organisation.repository.js'; -import { SimplePersonenkontextDeletedEvent } from '../../shared/events/simple-personenkontext-deleted.event.js'; -import { RolleRepo } from '../rolle/repo/rolle.repo.js'; -import { PersonRepository } from '../person/persistence/person.repository.js'; -import { Person } from '../person/domain/person.js'; -import { Rolle } from '../rolle/domain/rolle.js'; -import { EventService } from '../../core/eventbus/services/event.service.js'; -import { PersonenkontextDeletedEvent } from '../../shared/events/personenkontext-deleted.event.js'; -import { - PersonenkontextEventKontextData, - PersonenkontextEventPersonData, -} from '../../shared/events/personenkontext-event.types.js'; - -@Injectable() -export class EventAdapter { - public constructor( - private readonly logger: ClassLogger, - private readonly eventService: EventService, - private readonly personRepository: PersonRepository, - private readonly organisationRepository: OrganisationRepository, - private readonly rolleRepo: RolleRepo, - ) {} - - @EventHandler(SimplePersonenkontextDeletedEvent) - public async handlePersonenkontextDeletedEvent(event: SimplePersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - - const person: Option> = await this.personRepository.findById(event.personId); - if (!person) { - return this.logger.error(`Could not find person for personId:${event.personId}`); - } - const orga: Option> = await this.organisationRepository.findById(event.organisationId); - if (!orga) { - return this.logger.error(`Could not find organisation for orgaId:${event.organisationId}`); - } - const rolle: Option> = await this.rolleRepo.findById(event.rolleId); - if (!rolle) { - return this.logger.error(`Could not find rolle for rolleId:${event.rolleId}`); - } - const personData: PersonenkontextEventPersonData = { - id: person.id, - vorname: person.vorname, - familienname: person.familienname, - referrer: person.referrer, - email: person.email, - }; - const kontextData: PersonenkontextEventKontextData = { - id: event.personenkontextID, - rolleId: rolle.id, - rolle: rolle.rollenart, - orgaId: orga.id, - orgaTyp: orga.typ, - orgaKennung: orga.kennung, - }; - - const personenkontextDeletedEvent: PersonenkontextDeletedEvent = new PersonenkontextDeletedEvent( - personData, - kontextData, - ); - - this.eventService.publish(personenkontextDeletedEvent); - } -} diff --git a/src/modules/utility/utility.module.ts b/src/modules/utility/utility.module.ts deleted file mode 100644 index 8ef8d4d03..000000000 --- a/src/modules/utility/utility.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RolleModule } from '../../modules/rolle/rolle.module.js'; -import { OrganisationModule } from '../../modules/organisation/organisation.module.js'; -import { PersonModule } from '../../modules/person/person.module.js'; -import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js'; -import { EventAdapter } from './event-adapter.js'; -import { LoggerModule } from '../../core/logging/logger.module.js'; - -@Module({ - imports: [ - LoggerModule.register(UtilityModule.name), - RolleModule, - PersonModule, - OrganisationModule, - PersonenKontextModule, - ], - providers: [EventAdapter], - exports: [EventAdapter], -}) -export class UtilityModule {} diff --git a/src/server/server.module.ts b/src/server/server.module.ts index c0936d51a..9b31406f4 100644 --- a/src/server/server.module.ts +++ b/src/server/server.module.ts @@ -33,7 +33,6 @@ import { EventModule } from '../core/eventbus/index.js'; import { ItsLearningModule } from '../modules/itslearning/itslearning.module.js'; import { LdapModule } from '../core/ldap/ldap.module.js'; import { EmailModule } from '../modules/email/email.module.js'; -import { UtilityModule } from '../modules/utility/utility.module.js'; import { OxModule } from '../modules/ox/ox.module.js'; @Module({ @@ -90,7 +89,6 @@ import { OxModule } from '../modules/ox/ox.module.js'; EmailModule, OxModule, PrivacyIdeaAdministrationModule, - UtilityModule, //necessary to enable event-adapter to transform events ], providers: [ { diff --git a/src/shared/events/personenkontext-created.event.ts b/src/shared/events/personenkontext-created.event.ts deleted file mode 100644 index 155ead722..000000000 --- a/src/shared/events/personenkontext-created.event.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { OrganisationID, PersonID, RolleID } from '../types/index.js'; - -export class PersonenkontextCreatedEvent extends BaseEvent { - public constructor( - public readonly personId: PersonID, - public readonly organisationId: OrganisationID, - public readonly rolleId: RolleID, - ) { - super(); - } -} diff --git a/src/shared/events/personenkontext-deleted.event.ts b/src/shared/events/personenkontext-deleted.event.ts deleted file mode 100644 index 0224a2fe6..000000000 --- a/src/shared/events/personenkontext-deleted.event.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { PersonenkontextEventKontextData, PersonenkontextEventPersonData } from './personenkontext-event.types.js'; - -export class PersonenkontextDeletedEvent extends BaseEvent { - public constructor( - public readonly personData: PersonenkontextEventPersonData, - public readonly kontextData: PersonenkontextEventKontextData, - ) { - super(); - } -} diff --git a/src/shared/events/personenkontext-event.types.ts b/src/shared/events/personenkontext-event.types.ts index ecccda5a9..83244a68c 100644 --- a/src/shared/events/personenkontext-event.types.ts +++ b/src/shared/events/personenkontext-event.types.ts @@ -8,6 +8,7 @@ export type PersonenkontextEventPersonData = { familienname: string; referrer?: string; email?: string; + ldapEntryUUID?: string; }; export type PersonenkontextEventKontextData = { diff --git a/src/shared/events/personenkontext-updated.event.ts b/src/shared/events/personenkontext-updated.event.ts index 6f134695c..436833f4d 100644 --- a/src/shared/events/personenkontext-updated.event.ts +++ b/src/shared/events/personenkontext-updated.event.ts @@ -10,12 +10,13 @@ export type PersonenkontextUpdatedPersonData = PersonenkontextEventPersonData; export type PersonenkontextUpdatedData = PersonenkontextEventKontextData; -function mapPersonToData(person: Person): PersonenkontextUpdatedPersonData { +function mapPersonToData(person: Person, ldapEntryUUID?: string): PersonenkontextUpdatedPersonData { return { id: person.id, vorname: person.vorname, familienname: person.familienname, referrer: person.referrer, + ldapEntryUUID: ldapEntryUUID, email: person.email, }; } @@ -50,9 +51,10 @@ export class PersonenkontextUpdatedEvent extends BaseEvent { newKontexte: [Personenkontext, Organisation, Rolle][], removedKontexte: [Personenkontext, Organisation, Rolle][], currentKontexte: [Personenkontext, Organisation, Rolle][], + ldapEntryUUID?: string, ): PersonenkontextUpdatedEvent { return new PersonenkontextUpdatedEvent( - mapPersonToData(person), + mapPersonToData(person, ldapEntryUUID), newKontexte.map(mapPersonenkontextAndRolleAggregateToData), removedKontexte.map(mapPersonenkontextAndRolleAggregateToData), currentKontexte.map(mapPersonenkontextAndRolleAggregateToData), diff --git a/src/shared/events/simple-personenkontext-deleted.event.ts b/src/shared/events/simple-personenkontext-deleted.event.ts deleted file mode 100644 index dfa8d76ae..000000000 --- a/src/shared/events/simple-personenkontext-deleted.event.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { OrganisationID, PersonenkontextID, PersonID, RolleID } from '../types/index.js'; - -export class SimplePersonenkontextDeletedEvent extends BaseEvent { - public constructor( - public readonly personenkontextID: PersonenkontextID, - public readonly personId: PersonID, - public readonly organisationId: OrganisationID, - public readonly rolleId: RolleID, - ) { - super(); - } -}