diff --git a/package-lock.json b/package-lock.json index fc9d315af..70b7185bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dbildungs-iam-server", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dbildungs-iam-server", - "version": "0.6.0", + "version": "0.7.0", "license": "EUPL-1.2", "dependencies": { "@automapper/classes": "^8.8.1", diff --git a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts index 21df5af5a..19678131c 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext-workflow.controller.ts @@ -126,7 +126,7 @@ export class DbiamPersonenkontextWorkflowController { type: PersonenkontexteUpdateResponse, }) @ApiBadRequestResponse({ - description: 'The personenkontexte could not be updated, may due to unsatisfied specifications.', + description: 'The personenkontexte could not be updated, may be due to unsatisfied specifications.', type: DbiamPersonenkontexteUpdateError, }) @ApiConflictResponse({ description: 'Changes are conflicting with current state of personenkontexte.' }) diff --git a/src/modules/personenkontext/domain/dbiam-personenkontext.service.spec.ts b/src/modules/personenkontext/domain/dbiam-personenkontext.service.spec.ts index 5f22acb14..521afa6cc 100644 --- a/src/modules/personenkontext/domain/dbiam-personenkontext.service.spec.ts +++ b/src/modules/personenkontext/domain/dbiam-personenkontext.service.spec.ts @@ -1,4 +1,5 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +// eslint-disable-next-line max-classes-per-file +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; import { Personenkontext } from './personenkontext.js'; @@ -11,6 +12,11 @@ import { DBiamPersonenkontextService } from './dbiam-personenkontext.service.js' import { DoFactory } from '../../../../test/utils/do-factory.js'; import { RollenArt, RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; import { Rolle } from '../../rolle/domain/rolle.js'; +import { RolleModule } from '../../rolle/rolle.module.js'; +import { Module } from '@nestjs/common'; +import { OrganisationModule } from '../../organisation/organisation.module.js'; +import { PersonenkontextPersistenceModule } from '../persistence/PersonenkontextPersistenceModule.js'; +import { PersonenkontextSpecificationsModule } from '../specification/personenkontext-specification.module.js'; describe('DBiamPersonenkontextService', () => { let module: TestingModule; @@ -21,28 +27,56 @@ describe('DBiamPersonenkontextService', () => { let personenkontextFactory: PersonenkontextFactory; beforeAll(async () => { - module = await Test.createTestingModule({ + @Module({ providers: [ - DBiamPersonenkontextService, - PersonenkontextFactory, - { - provide: PersonRepository, - useValue: createMock(), - }, { provide: RolleRepo, useValue: createMock(), }, + ], + exports: [RolleRepo], + }) + class RolleTestModule {} + + @Module({ + providers: [ { provide: OrganisationRepository, useValue: createMock(), }, + ], + exports: [OrganisationRepository], + }) + class OrganisationTestModule {} + + @Module({ + imports: [OrganisationModule, RolleModule], + providers: [ { provide: DBiamPersonenkontextRepo, useValue: createMock(), }, + { + provide: PersonRepository, + useValue: createMock(), + }, + PersonenkontextFactory, ], - }).compile(); + exports: [DBiamPersonenkontextRepo, PersonenkontextFactory, PersonRepository], + }) + class PersonenkontextPersistenceTestModule {} + + module = await Test.createTestingModule({ + imports: [PersonenkontextSpecificationsModule, PersonenkontextPersistenceModule, RolleModule], + providers: [DBiamPersonenkontextService], + }) + .overrideModule(RolleModule) + .useModule(RolleTestModule) + .overrideModule(OrganisationModule) + .useModule(OrganisationTestModule) + .overrideModule(PersonenkontextPersistenceModule) + .useModule(PersonenkontextPersistenceTestModule) + .compile(); sut = module.get(DBiamPersonenkontextService); dbiamPersonenKontextRepoMock = module.get(DBiamPersonenkontextRepo); rolleRepoMock = module.get(RolleRepo); diff --git a/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts b/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts index 61e577553..e284dfd0e 100644 --- a/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts +++ b/src/modules/personenkontext/domain/dbiam-personenkontext.service.ts @@ -1,14 +1,9 @@ import { Injectable } from '@nestjs/common'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; import { Personenkontext } from './personenkontext.js'; -import { NurLehrUndLernAnKlasse } from '../specification/nur-lehr-und-lern-an-klasse.js'; -import { GleicheRolleAnKlasseWieSchule } from '../specification/gleiche-rolle-an-klasse-wie-schule.js'; import { PersonenkontextKlasseSpecification } from '../specification/personenkontext-klasse-specification.js'; 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 { CheckRollenartSpecification } from '../specification/nur-gleiche-rolle.js'; -import { CheckBefristungSpecification } from '../specification/befristung-required-bei-rolle-befristungspflicht.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; @@ -16,43 +11,14 @@ import { RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; export class DBiamPersonenkontextService { public constructor( private readonly dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, - private readonly organisationRepository: OrganisationRepository, private readonly rolleRepo: RolleRepo, + private readonly personenkontextKlasseSpecification: PersonenkontextKlasseSpecification, ) {} public async checkSpecifications( personenkontext: Personenkontext, ): Promise> { - //Check that only teachers and students are added to classes. - const nurLehrUndLernAnKlasse: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse( - this.organisationRepository, - this.rolleRepo, - ); - //Check that person has same role on parent-organisation, if organisation is a class. - const gleicheRolleAnKlasseWieSchule: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( - this.organisationRepository, - this.dBiamPersonenkontextRepo, - this.rolleRepo, - ); - - // Checks that the sent personnekontext is of type LERN - //(Only returns an error if the person has some kontext of type LERN already and the sent PK isn't) - const nurRollenartLern: CheckRollenartSpecification = new CheckRollenartSpecification( - this.dBiamPersonenkontextRepo, - 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); + return this.personenkontextKlasseSpecification.returnsError(personenkontext); } public async isPersonalnummerRequiredForAnyPersonenkontextForPerson(personId: string): Promise { diff --git a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts index cb14f349c..ebe7f51b0 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts @@ -723,7 +723,7 @@ describe('PersonenkontexteUpdate', () => { rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollenExisting); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); - jest.spyOn(CheckBefristungSpecification.prototype, 'checkBefristung').mockResolvedValue(true); + jest.spyOn(CheckBefristungSpecification.prototype, 'isSatisfiedBy').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 bb2ba0357..cea158cdb 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.ts @@ -274,7 +274,7 @@ export class PersonenkontexteUpdate { const isSatisfied: boolean = await new CheckRollenartSpecification( this.dBiamPersonenkontextRepo, this.rolleRepo, - ).checkRollenart(sentPKs); + ).isSatisfiedBy(sentPKs); if (!isSatisfied) { return new UpdateInvalidRollenartForLernError(); @@ -286,7 +286,7 @@ export class PersonenkontexteUpdate { private async checkBefristungSpecification( sentPKs: Personenkontext[], ): Promise> { - const isSatisfied: boolean = await new CheckBefristungSpecification(this.rolleRepo).checkBefristung(sentPKs); + const isSatisfied: boolean = await new CheckBefristungSpecification(this.rolleRepo).isSatisfiedBy(sentPKs); if (!isSatisfied) { return new PersonenkontextBefristungRequiredError(); diff --git a/src/modules/personenkontext/persistence/PersonenkontextPersistenceModule.ts b/src/modules/personenkontext/persistence/PersonenkontextPersistenceModule.ts new file mode 100644 index 000000000..f56f587a1 --- /dev/null +++ b/src/modules/personenkontext/persistence/PersonenkontextPersistenceModule.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { DBiamPersonenkontextRepo } from './dbiam-personenkontext.repo.js'; +import { PersonenkontextRepo } from './personenkontext.repo.js'; +import { DBiamPersonenkontextRepoInternal } from './internal-dbiam-personenkontext.repo.js'; +import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; +import { PersonModule } from '../../person/person.module.js'; +import { OrganisationModule } from '../../organisation/organisation.module.js'; +import { RolleModule } from '../../rolle/rolle.module.js'; + +@Module({ + imports: [PersonModule, OrganisationModule, RolleModule], + providers: [ + DBiamPersonenkontextRepo, + PersonenkontextRepo, + DBiamPersonenkontextRepoInternal, + PersonenkontextFactory, + ], + exports: [DBiamPersonenkontextRepo, PersonenkontextRepo, DBiamPersonenkontextRepoInternal, PersonenkontextFactory], +}) +export class PersonenkontextPersistenceModule {} diff --git a/src/modules/personenkontext/personenkontext.module.ts b/src/modules/personenkontext/personenkontext.module.ts index d717c0429..d884d2a35 100644 --- a/src/modules/personenkontext/personenkontext.module.ts +++ b/src/modules/personenkontext/personenkontext.module.ts @@ -1,18 +1,20 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '../../core/logging/logger.module.js'; -import { PersonenkontextRepo } from '../personenkontext/persistence/personenkontext.repo.js'; import { PersonenkontextService } from '../personenkontext/domain/personenkontext.service.js'; import { PersonModule } from '../person/person.module.js'; -import { DBiamPersonenkontextRepo } from './persistence/dbiam-personenkontext.repo.js'; import { RolleModule } from '../rolle/rolle.module.js'; import { OrganisationModule } from '../organisation/organisation.module.js'; import { DBiamPersonenkontextService } from './domain/dbiam-personenkontext.service.js'; import { DbiamPersonenkontextFactory } from './domain/dbiam-personenkontext.factory.js'; -import { PersonenkontextFactory } from './domain/personenkontext.factory.js'; import { EventModule } from '../../core/eventbus/index.js'; -import { DBiamPersonenkontextRepoInternal } from './persistence/internal-dbiam-personenkontext.repo.js'; +import { PersonenkontextPersistenceModule } from './persistence/PersonenkontextPersistenceModule.js'; import { PersonenkontextCreationService } from './domain/personenkontext-creation.service.js'; import { PersonenkontextWorkflowFactory } from './domain/personenkontext-workflow.factory.js'; +import { PersonenkontextRepo } from './persistence/personenkontext.repo.js'; +import { DBiamPersonenkontextRepo } from './persistence/dbiam-personenkontext.repo.js'; +import { DBiamPersonenkontextRepoInternal } from './persistence/internal-dbiam-personenkontext.repo.js'; +import { PersonenkontextFactory } from './domain/personenkontext.factory.js'; +import { PersonenkontextSpecificationsModule } from './specification/personenkontext-specification.module.js'; @Module({ imports: [ @@ -20,6 +22,8 @@ import { PersonenkontextWorkflowFactory } from './domain/personenkontext-workflo PersonModule, RolleModule, OrganisationModule, + PersonenkontextSpecificationsModule, + PersonenkontextPersistenceModule, LoggerModule.register(PersonenKontextModule.name), ], providers: [ @@ -35,12 +39,10 @@ import { PersonenkontextWorkflowFactory } from './domain/personenkontext-workflo ], exports: [ PersonenkontextService, - PersonenkontextRepo, DBiamPersonenkontextService, - DBiamPersonenkontextRepo, DbiamPersonenkontextFactory, - DBiamPersonenkontextRepoInternal, // TODO: Needed by seeding - PersonenkontextFactory, + PersonenkontextPersistenceModule, + PersonenkontextSpecificationsModule, PersonenkontextCreationService, PersonenkontextWorkflowFactory, ], diff --git a/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts b/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts index c2caabe38..85ade738a 100644 --- a/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts +++ b/src/modules/personenkontext/specification/befristung-required-bei-rolle-befristungspflicht.ts @@ -2,11 +2,16 @@ 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'; +import { Injectable } from '@nestjs/common'; +import { CompositeSpecification } from '../../specification/specifications.js'; -export class CheckBefristungSpecification { - public constructor(private readonly rolleRepo: RolleRepo) {} +@Injectable() +export class CheckBefristungSpecification extends CompositeSpecification[]> { + public constructor(private readonly rolleRepo: RolleRepo) { + super(); + } - public async checkBefristung(sentPKs: Personenkontext[]): Promise { + public async isSatisfiedBy(sentPKs: Personenkontext[]): Promise { // Early return if all Personenkontext have befristung defined if (sentPKs.every((pk: Personenkontext) => pk.befristung !== undefined)) { return true; diff --git a/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.ts b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.ts index cfcfbd6ad..f5cec95e4 100644 --- a/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.ts +++ b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.ts @@ -6,12 +6,14 @@ import { Rolle } from '../../rolle/domain/rolle.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { Organisation } from '../../organisation/domain/organisation.js'; +import { Injectable } from '@nestjs/common'; /** * Only needs to be checked when referenced organisation is of type KLASSE. * Used to check, that a person already owns identical rolle at schule, when creating Personenkontext * for that person with a rolle on a klasse. */ +@Injectable() export class GleicheRolleAnKlasseWieSchule extends CompositeSpecification> { public constructor( private readonly organisationRepo: OrganisationRepository, diff --git a/src/modules/personenkontext/specification/nur-gleiche-rolle.ts b/src/modules/personenkontext/specification/nur-gleiche-rolle.ts index 06e7779f7..1492cc307 100644 --- a/src/modules/personenkontext/specification/nur-gleiche-rolle.ts +++ b/src/modules/personenkontext/specification/nur-gleiche-rolle.ts @@ -4,14 +4,17 @@ import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { Personenkontext } from '../domain/personenkontext.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { ExistingRolleUndefined } from '../domain/error/existing-rolle-undefined.error.js'; +import { CompositeSpecification } from '../../specification/specifications.js'; -export class CheckRollenartSpecification { +export class CheckRollenartSpecification extends CompositeSpecification[]> { public constructor( private readonly personenkontextRepo: DBiamPersonenkontextRepo, private readonly rolleRepo: RolleRepo, - ) {} + ) { + super(); + } - public async checkRollenart(sentPKs: Personenkontext[]): Promise { + public async isSatisfiedBy(sentPKs: Personenkontext[]): Promise { // Fetch all personenkontexte for the person let existingPKs: Personenkontext[] = []; diff --git a/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.ts b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.ts index 18c59b8b5..8fced4ef8 100644 --- a/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.ts +++ b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.ts @@ -6,10 +6,12 @@ import { Rolle } from '../../rolle/domain/rolle.js'; import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { Organisation } from '../../organisation/domain/organisation.js'; +import { Injectable } from '@nestjs/common'; /** * Only needs to be checked when referenced organisation is of type KLASSE. */ +@Injectable() export class NurLehrUndLernAnKlasse extends CompositeSpecification> { public constructor( private readonly organisationRepository: OrganisationRepository, diff --git a/src/modules/personenkontext/specification/organisation-matches-rollenart.ts b/src/modules/personenkontext/specification/organisation-matches-rollenart.ts index a65aeed6b..233cfb7d0 100644 --- a/src/modules/personenkontext/specification/organisation-matches-rollenart.ts +++ b/src/modules/personenkontext/specification/organisation-matches-rollenart.ts @@ -2,11 +2,18 @@ import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.j import { Rolle } from '../../rolle/domain/rolle.js'; import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { Organisation } from '../../organisation/domain/organisation.js'; +import { Injectable } from '@nestjs/common'; /** - * Only needs to be checked when referenced organisation is of type KLASSE. - * Needs to be refactored into a specification + * Checks if the organisation matches the rollenart. + * + * Sysadmins can only exist on LAND or ROOT organisations. + * LEIT can only exist on Schools. + * LERN (students) only exist on forms + * LEHR (teachers) only exist on schools. */ + +@Injectable() export class OrganisationMatchesRollenart { public isSatisfiedBy(organisation: Organisation, rolle: Rolle): boolean { if (rolle.rollenart === RollenArt.SYSADMIN) diff --git a/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts b/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts index 07a025f7f..9db9d3e0d 100644 --- a/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts +++ b/src/modules/personenkontext/specification/personenkontext-klasse-specification.spec.ts @@ -91,10 +91,10 @@ describe('PersonenkontextKlasseSpecification Integration', () => { ); const personenkontextMock: DeepMocked> = createMock>(); - checkRollenartSpecificationMock.checkRollenart.mockResolvedValueOnce(false); + checkRollenartSpecificationMock.isSatisfiedBy.mockResolvedValueOnce(false); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValueOnce(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValueOnce(true); - befristungRequiredMock.checkBefristung.mockResolvedValue(true); + befristungRequiredMock.isSatisfiedBy.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); @@ -110,10 +110,10 @@ describe('PersonenkontextKlasseSpecification Integration', () => { ); const personenkontextMock: DeepMocked> = createMock>(); - checkRollenartSpecificationMock.checkRollenart.mockResolvedValue(true); + checkRollenartSpecificationMock.isSatisfiedBy.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(false); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(true); - befristungRequiredMock.checkBefristung.mockResolvedValue(true); + befristungRequiredMock.isSatisfiedBy.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); @@ -129,9 +129,9 @@ describe('PersonenkontextKlasseSpecification Integration', () => { ); const personenkontextMock: DeepMocked> = createMock>(); - checkRollenartSpecificationMock.checkRollenart.mockResolvedValue(true); + checkRollenartSpecificationMock.isSatisfiedBy.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(true); - befristungRequiredMock.checkBefristung.mockResolvedValue(true); + befristungRequiredMock.isSatisfiedBy.mockResolvedValue(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(false); const result: Option = await specification.returnsError(personenkontextMock); @@ -148,10 +148,10 @@ describe('PersonenkontextKlasseSpecification Integration', () => { ); const personenkontextMock: DeepMocked> = createMock>(); - checkRollenartSpecificationMock.checkRollenart.mockResolvedValue(true); + checkRollenartSpecificationMock.isSatisfiedBy.mockResolvedValue(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValue(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValue(true); - befristungRequiredMock.checkBefristung.mockResolvedValue(true); + befristungRequiredMock.isSatisfiedBy.mockResolvedValue(true); const result: Option = await specification.returnsError(personenkontextMock); @@ -166,10 +166,10 @@ describe('PersonenkontextKlasseSpecification Integration', () => { ); const personenkontextMock: DeepMocked> = createMock>(); - checkRollenartSpecificationMock.checkRollenart.mockResolvedValueOnce(true); + checkRollenartSpecificationMock.isSatisfiedBy.mockResolvedValueOnce(true); nurLehrUndLernAnKlasseMock.isSatisfiedBy.mockResolvedValueOnce(true); gleicheRolleAnKlasseWieSchuleMock.isSatisfiedBy.mockResolvedValueOnce(true); - befristungRequiredMock.checkBefristung.mockResolvedValue(false); + befristungRequiredMock.isSatisfiedBy.mockResolvedValue(false); const result: Option = await specification.returnsError(personenkontextMock); diff --git a/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts b/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts index b423bd4fc..1eec42e1c 100644 --- a/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts +++ b/src/modules/personenkontext/specification/personenkontext-klasse-specification.ts @@ -8,11 +8,13 @@ import { CheckRollenartSpecification } from './nur-gleiche-rolle.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'; +import { Injectable } from '@nestjs/common'; /** * 'This specification is not extending CompositeSpecification, but combines specifications for Personenkontexte * referencing Klassen and returns dedicated errors instead of simply true or false.' */ +@Injectable() export class PersonenkontextKlasseSpecification { public constructor( protected readonly nurLehrUndLernAnKlasse: NurLehrUndLernAnKlasse, @@ -22,10 +24,10 @@ export class PersonenkontextKlasseSpecification { ) {} public async returnsError(p: Personenkontext): Promise> { - if (!(await this.nurGleicheRolle.checkRollenart([p]))) { + if (!(await this.nurGleicheRolle.isSatisfiedBy([p]))) { return new UpdateInvalidRollenartForLernError(); } - if (!(await this.befristungRequired.checkBefristung([p]))) { + if (!(await this.befristungRequired.isSatisfiedBy([p]))) { return new PersonenkontextBefristungRequiredError(); } if (!(await this.nurLehrUndLernAnKlasse.isSatisfiedBy(p))) { diff --git a/src/modules/personenkontext/specification/personenkontext-specification.module.ts b/src/modules/personenkontext/specification/personenkontext-specification.module.ts new file mode 100644 index 000000000..2f698ed03 --- /dev/null +++ b/src/modules/personenkontext/specification/personenkontext-specification.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { CheckBefristungSpecification } from './befristung-required-bei-rolle-befristungspflicht.js'; +import { GleicheRolleAnKlasseWieSchule } from './gleiche-rolle-an-klasse-wie-schule.js'; +import { NurLehrUndLernAnKlasse } from './nur-lehr-und-lern-an-klasse.js'; +import { OrganisationMatchesRollenart } from './organisation-matches-rollenart.js'; +import { PersonenkontextKlasseSpecification } from './personenkontext-klasse-specification.js'; +import { RolleModule } from '../../rolle/rolle.module.js'; +import { OrganisationModule } from '../../organisation/organisation.module.js'; +import { PersonenkontextPersistenceModule } from '../persistence/PersonenkontextPersistenceModule.js'; +import { PersonenkontextSpecification } from './personenkontext-specification.js'; +import { CheckRollenartSpecification } from './nur-gleiche-rolle.js'; + +@Module({ + imports: [RolleModule, OrganisationModule, PersonenkontextPersistenceModule], + providers: [ + CheckBefristungSpecification, + GleicheRolleAnKlasseWieSchule, + NurLehrUndLernAnKlasse, + CheckRollenartSpecification, + OrganisationMatchesRollenart, + PersonenkontextKlasseSpecification, + PersonenkontextSpecification, + ], + exports: [PersonenkontextKlasseSpecification], +}) +export class PersonenkontextSpecificationsModule {} diff --git a/src/modules/personenkontext/specification/personenkontext-specification.ts b/src/modules/personenkontext/specification/personenkontext-specification.ts new file mode 100644 index 000000000..03d95adbb --- /dev/null +++ b/src/modules/personenkontext/specification/personenkontext-specification.ts @@ -0,0 +1,53 @@ +import { CheckBefristungSpecification } from './befristung-required-bei-rolle-befristungspflicht.js'; +import { GleicheRolleAnKlasseWieSchule } from './gleiche-rolle-an-klasse-wie-schule.js'; +import { NurLehrUndLernAnKlasse } from './nur-lehr-und-lern-an-klasse.js'; +import { OrganisationMatchesRollenart } from './organisation-matches-rollenart.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { Injectable } from '@nestjs/common'; +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; +import { Rolle } from '../../rolle/domain/rolle.js'; +import { Organisation } from '../../organisation/domain/organisation.js'; +import { CheckRollenartSpecification } from './nur-gleiche-rolle.js'; + +@Injectable() +export class PersonenkontextSpecification { + public constructor( + private checkBefristung: CheckBefristungSpecification, + private gleicheRolleAnKlasseWieSchule: GleicheRolleAnKlasseWieSchule, + private nurLehNurLehrUndLernAnKlasse: NurLehrUndLernAnKlasse, + private checkRollenartLern: CheckRollenartSpecification, + private organisationMatchesRollenart: OrganisationMatchesRollenart, + private rolleRepo: RolleRepo, + private organisationRepo: OrganisationRepository, + ) {} + + public async checkCompliance(personenkontexte: Personenkontext[]): Promise { + return [ + await this.checkBefristung.and(this.checkRollenartLern).isSatisfiedBy(personenkontexte), + + await personenkontexte + .map((pk: Personenkontext) => + this.gleicheRolleAnKlasseWieSchule.and(this.nurLehNurLehrUndLernAnKlasse).isSatisfiedBy(pk), + ) + .reduce(async (l: Promise, r: Promise) => (await l) && (await r)), + await personenkontexte + .map((pk: Personenkontext) => this.checkOrgaRolleCompliance(pk)) + .reduce(async (l: Promise, r: Promise) => (await l) && (await r)), + ].every((v: boolean) => v); + } + + private async checkOrgaRolleCompliance(pk: Personenkontext): Promise { + const organisation: Option> = await this.organisationRepo.findById(pk.organisationId); + const rolle: Option> = await this.rolleRepo.findById(pk.rolleId); + + if (organisation == null) { + return false; + } + if (rolle == null) { + return false; + } + + return this.organisationMatchesRollenart.isSatisfiedBy(organisation, rolle); + } +} diff --git a/src/modules/personenkontext/specification/personenkontext-specifications-mocked-repos.spec.ts b/src/modules/personenkontext/specification/personenkontext-specifications-mocked-repos.spec.ts index 1b929f806..f46776d45 100644 --- a/src/modules/personenkontext/specification/personenkontext-specifications-mocked-repos.spec.ts +++ b/src/modules/personenkontext/specification/personenkontext-specifications-mocked-repos.spec.ts @@ -223,7 +223,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { mapRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(mapRollen); - const result: boolean = await specification.checkRollenart([personenkontext]); + const result: boolean = await specification.isSatisfiedBy([personenkontext]); expect(result).toBe(true); }); @@ -245,8 +245,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { const newRollen: Map> = new Map(); newRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); - - const result: boolean = await specification.checkRollenart([personenkontext]); + const result: boolean = await specification.isSatisfiedBy([personenkontext]); expect(result).toBe(true); }); @@ -269,7 +268,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { newRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); - const result: boolean = await specification.checkRollenart([personenkontext]); + const result: boolean = await specification.isSatisfiedBy([personenkontext]); expect(result).toBe(false); }); @@ -294,7 +293,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { newRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); - const result: boolean = await specification.checkRollenart([personenkontext1, personenkontext2]); + const result: boolean = await specification.isSatisfiedBy([personenkontext1, personenkontext2]); expect(result).toBe(true); }); @@ -319,7 +318,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { newRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); - const result: boolean = await specification.checkRollenart([personenkontext1, personenkontext2]); + const result: boolean = await specification.isSatisfiedBy([personenkontext1, personenkontext2]); expect(result).toBe(true); }); @@ -346,7 +345,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { newRollen.set(faker.string.uuid(), DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); // Different role type rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); - const result: boolean = await specification.checkRollenart([personenkontext1, personenkontext2]); + const result: boolean = await specification.isSatisfiedBy([personenkontext1, personenkontext2]); expect(result).toBe(false); // Expecting false because of different role types }); @@ -374,7 +373,7 @@ describe('PersonenkontextSpecificationsMockedReposTest', () => { rolleRepoMock.findByIds.mockResolvedValueOnce(newRollen); // Expect the function to throw an error due to the undefined value in existingRollen - await expect(specification.checkRollenart([personenkontext1, personenkontext2])).rejects.toThrow( + await expect(specification.isSatisfiedBy([personenkontext1, personenkontext2])).rejects.toThrow( 'Expected existingRollen to contain valid roles, but found undefined.', ); }); diff --git a/src/modules/specification/specifications.ts b/src/modules/specification/specifications.ts index 1f330443f..34bc3fea1 100644 --- a/src/modules/specification/specifications.ts +++ b/src/modules/specification/specifications.ts @@ -1,12 +1,18 @@ /* eslint-disable max-classes-per-file */ + /* eslint-disable @typescript-eslint/no-use-before-define */ export interface Specification { isSatisfiedBy(t: T): Promise; + and(other: Specification): Specification; + or(other: Specification): Specification; + not(): Specification; + andNot(other: Specification): Specification; + orNot(other: Specification): Specification; } @@ -49,8 +55,8 @@ export class AndNotSpecification extends CompositeSpecification { export class AndSpecification extends CompositeSpecification { public constructor( - private readonly leftCondition: Specification, - private readonly rightCondition: Specification, + private readonly leftCondition: CompositeSpecification, + private readonly rightCondition: CompositeSpecification, ) { super(); }