diff --git a/src/structures/services/structures.service.spec.ts b/src/structures/services/structures.service.spec.ts index 2836627dcca9f7de919c592c4a4b3af026d5ed40..362e8df5110198fa4906041ab5575c77037fef0e 100644 --- a/src/structures/services/structures.service.spec.ts +++ b/src/structures/services/structures.service.spec.ts @@ -365,6 +365,9 @@ describe('StructuresService', () => { nbScanners: 1, otherDescription: null, personalOffers: [], + categories: { + onlineProcedures: ['caf'], + }, }, ]); @@ -373,12 +376,12 @@ describe('StructuresService', () => { expect(res.length).toBe(1); }); it('should find 1 structure', async () => { - const res = await structureService.searchForStructures('a', [[{ nbPrinters: '1' }, { '': 'baseSkills' }]]); + const res = await structureService.searchForStructures('a', [[{ nbPrinters: '1' }, { onlineProcedures: 'caf' }]]); expect(res.length).toBe(1); }); - it('should find 1 structure', async () => { - const res = await structureService.searchForStructures('a', [[{ '': 'baseSkills' }]]); - expect(res.length).toBe(1); + it('should find 0 structure with personalOffer', async () => { + const res = await structureService.searchForStructures('a', [[{ onlineProcedures: 'caf' }]], null, true); + expect(res.length).toBe(0); }); it('should find 1 structure', async () => { const res = await structureService.searchForStructures('a'); diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts index efc500f24cbb7a15fb33868a23d98eda61fb9c3e..5b8c5651275ed8b9acfbdc929f551abe39022ba7 100644 --- a/src/structures/services/structures.service.ts +++ b/src/structures/services/structures.service.ts @@ -27,6 +27,7 @@ import { Address } from '../schemas/address.schema'; import { Structure, StructureDocument } from '../schemas/structure.schema'; import { StructuresSearchService } from './structures-search.service'; import { JobsService } from '../../users/services/jobs.service'; +import { User } from '../../users/schemas/user.schema'; @Injectable() export class StructuresService { @@ -53,7 +54,7 @@ export class StructuresService { text: string, filters?: Array<any>, fields?: string[], - useStructureOffers?: boolean, + onlyOffersWithAppointment?: boolean, limit?: number ): Promise<StructureDocument[]> { this.logger.debug( @@ -120,7 +121,9 @@ export class StructuresService { } // Filter offers using structure offers and structure members personalOffers - structures = this.filterOnOffers(structures, andFiltersOnOffers, useStructureOffers); + if (andFiltersOnOffers.length) { + structures = await this.filterOnOffers(structures, andFiltersOnOffers, onlyOffersWithAppointment); + } return structures.sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); } @@ -131,16 +134,11 @@ export class StructuresService { * @param andFiltersOnOffers * @returns StructureDocument[] */ - private filterOnOffers( + private async filterOnOffers( structures: StructureDocument[], andFiltersOnOffers: Array<any>, - useStructureOffers: boolean - ): StructureDocument[] { - structures.forEach((structure) => { - // set structure offers and social workers personalOffers in structure.categoriesWithPersonalOffers - this.setCategoriesWithPersonalOffers(structure, useStructureOffers); - }); - + onlyOffersWithAppointment: boolean + ): Promise<StructureDocument[]> { // Build filters on offers from andFiltersOnOffers const filtersOnOffers = { onlineProcedures: [], baseSkills: [], advancedSkills: [] }; andFiltersOnOffers.forEach((element) => { @@ -149,6 +147,12 @@ export class StructuresService { if ('advancedSkills' in element) filtersOnOffers.advancedSkills.push(element.advancedSkills); }); + // Set structure offers and social workers personalOffers in structure.categoriesWithPersonalOffers + const setCategoriesPromises = structures.map((structure) => + this.setCategoriesWithPersonalOffers(structure, onlyOffersWithAppointment) + ); + await Promise.all(setCategoriesPromises); + // Filter structures on offers checking structure offers and structure social workers offers return structures.filter( (structure) => @@ -168,43 +172,68 @@ export class StructuresService { * set structure offers and structure social workers personalOffers in non-persistant property structure.categoriesWithPersonalOffers * @param structure */ - public setCategoriesWithPersonalOffers(structure: StructureDocument, useStructureOffers: boolean) { - // Get structure offers - if (useStructureOffers) { - structure.categoriesWithPersonalOffers = { - onlineProcedures: structure.categories?.onlineProcedures || [], - baseSkills: structure.categories?.baseSkills || [], - advancedSkills: structure.categories?.advancedSkills || [], - }; - } else { + public async setCategoriesWithPersonalOffers(structure: StructureDocument, onlyOffersWithAppointment: boolean) { + if (onlyOffersWithAppointment) { // Ignore structure offers (for orientation rdv, structure offers must be ignored: we filter only on personalOffers of the structure) structure.categoriesWithPersonalOffers = { onlineProcedures: [], baseSkills: [], advancedSkills: [], }; + } else { + // Get structure offers + structure.categoriesWithPersonalOffers = { + onlineProcedures: structure.categories?.onlineProcedures || [], + baseSkills: structure.categories?.baseSkills || [], + advancedSkills: structure.categories?.advancedSkills || [], + }; } - // Get offers for each structure social worker personalOffers - structure.personalOffers?.forEach((personalOffer) => { - if (!personalOffer.categories) { - throw new Error(`personalOffer not populated for structure ${structure.structureName} : ${personalOffer}`); - } + // Promise.all to wait for execution of all asynchronous operations + await Promise.all( + (structure.personalOffers || []).map(async (personalOffer) => { + if (!personalOffer.categories) { + throw new Error(`personalOffer not populated for structure ${structure.structureName} : ${personalOffer}`); + } - // use lodash _.union fonction to concat array without duplicates - structure.categoriesWithPersonalOffers.onlineProcedures = _.union( - structure.categoriesWithPersonalOffers.onlineProcedures, - personalOffer.categories.onlineProcedures - ); - structure.categoriesWithPersonalOffers.baseSkills = _.union( - structure.categoriesWithPersonalOffers.baseSkills, - personalOffer.categories.baseSkills - ); - structure.categoriesWithPersonalOffers.advancedSkills = _.union( - structure.categoriesWithPersonalOffers.advancedSkills, - personalOffer.categories.advancedSkills - ); - }); + // If we only want personalOffers from user with appointment + if (onlyOffersWithAppointment) { + const user = await this.userService.findByPersonalOfferId(personalOffer._id); + if (!user) { + Logger.log( + `No user with personalOffer ${personalOffer._id} for structure ${structure.structureName} (${structure._id}) !`, + StructuresService.name + ); + return; + } + if (!user.structuresLink.includes(structure._id)) { + Logger.log( + `Inconsistent personalOffer skipped: user ${user.name} ${user.surname} (${user._id}) has personalOffer ${personalOffer._id} for structure ${structure.structureName} (${structure._id}) but is not (anymore) member of the structure !`, + StructuresService.name + ); + return; + } + // If user is without appointment, skip this personalOffer + if (!User.getWithAppointment(user)) { + return; + } + } + + // use lodash _.union fonction to concat array without duplicates + structure.categoriesWithPersonalOffers.onlineProcedures = _.union( + structure.categoriesWithPersonalOffers.onlineProcedures, + personalOffer.categories.onlineProcedures + ); + structure.categoriesWithPersonalOffers.baseSkills = _.union( + structure.categoriesWithPersonalOffers.baseSkills, + personalOffer.categories.baseSkills + ); + structure.categoriesWithPersonalOffers.advancedSkills = _.union( + structure.categoriesWithPersonalOffers.advancedSkills, + personalOffer.categories.advancedSkills + ); + }) + ); } public async create(email: string, structure: StructureDto): Promise<Structure> { @@ -1265,9 +1294,8 @@ export class StructuresService { structure.hasUserWithAppointmentDN = false; let conseillerNumFranceServices = false; - owners.forEach((owner) => { - // Check if owner has withAppointment and a job with personnalOffer (user may have changed his job after choosing withAppointment) - if (owner.withAppointment && owner.job?.hasPersonalOffer) { + owners.forEach((owner: User) => { + if (User.getWithAppointment(owner)) { structure.hasUserWithAppointmentDN = true; } // If user has job 'Conseiller numérique' ou 'Conseillère numérique' diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index 71e4be67e9fd0d6265b5d58df47f7c406134ed82..7f467b9b7b4e7c085d6978352be2c729b74a96a9 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -101,7 +101,7 @@ export class StructuresController { query.query, body ? body.filters : null, null, - body?.useStructureOffers || null, + body?.onlyOffersWithAppointment || false, body?.limit || null ); } diff --git a/src/users/schemas/user.schema.ts b/src/users/schemas/user.schema.ts index 462e719803bc55a90de3577d85ea05a1323ab6f1..c850621c819deb256743bb8edaa1a4d18115c7a5 100644 --- a/src/users/schemas/user.schema.ts +++ b/src/users/schemas/user.schema.ts @@ -2,7 +2,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Types } from 'mongoose'; import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema'; import { Employer } from './employer.schema'; -import { Job, JobDocument } from './job.schema'; +import { JobDocument } from './job.schema'; import { UserRole } from '../enum/user-role.enum'; import { pendingStructuresLink } from '../interfaces/pendingStructure'; @@ -73,6 +73,19 @@ export class User { @Prop({ default: null }) lastLoginDate: Date; + + // Document methods + // static because object method would need to create a constructor and get an actual User instance (cf. https://stackoverflow.com/a/42899705 ) + + // Get if owner can have appointment + static getWithAppointment(user: User) { + if (user.job && !user.job.name) { + throw new Error(`job not populated for user ${user.surname} : ${user.job}`); + } + // Also check user has a job with personnalOffer (user may have changed his job after choosing withAppointment) + // and also check if user has personalOffers (otherwise they can't have appointment) + return user.withAppointment && user.job?.hasPersonalOffer && user.personalOffers?.length; + } } export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/users/services/users.service.ts b/src/users/services/users.service.ts index 37777eced25c36e317e89384669cd911971914d9..f4100c58eb6b15bca86b58549dbd98f90f279ca3 100644 --- a/src/users/services/users.service.ts +++ b/src/users/services/users.service.ts @@ -972,6 +972,7 @@ export class UsersService { .findOne({ personalOffers: { $all: [new Types.ObjectId(id)] }, }) + .populate('job') .populate('personalOffers') .exec(); }