diff --git a/src/categories/services/categories-formations.service.ts b/src/categories/services/categories-formations.service.ts index fab26f580d98135ce25f086a37136de2e2f9e095..7e9e34c0ef5eb7effc05bcac0f26ad5249da7675 100644 --- a/src/categories/services/categories-formations.service.ts +++ b/src/categories/services/categories-formations.service.ts @@ -19,4 +19,7 @@ export class CategoriesFormationsService { public findOne(categoryId: string): Promise<CategoriesFormations> { return this.structureModel.findOne({ id: categoryId }).select({ 'modules.id': 1 }).exec(); } + public findOneComplete(categoryId: string): Promise<CategoriesFormations> { + return this.structureModel.findOne({ id: categoryId }).exec(); + } } diff --git a/src/migrations/scripts/1643031535742-change-formations-categories.ts b/src/migrations/scripts/1643031535742-change-formations-categories.ts new file mode 100644 index 0000000000000000000000000000000000000000..d026b9a8ab8e8b9f56a1fde8ab77298b0cd2ca16 --- /dev/null +++ b/src/migrations/scripts/1643031535742-change-formations-categories.ts @@ -0,0 +1,84 @@ +import { Db } from 'mongodb'; +import { getDb } from '../migrations-utils/db'; + +export const up = async () => { + const db: Db = await getDb(); + + const cursor = db.collection('categoriesformations').find({}); + let document; + while ((document = await cursor.next())) { + if (document.id == 'baseSkills') { + const newDoc = { + surname: 'ordinateur, smartphone, internet', + name: 'Compétences de base', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'accessRight') { + const newDoc = { + surname: 'Pôle emploi, CAF', + name: 'Accès aux droits', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'parentingHelp') { + const newDoc = { + surname: 'temps d’écran, scolarité', + name: 'Aide à la parentalité', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'socialAndProfessional') { + const newDoc = { + surname: 'CV, tableur', + name: 'Insertion professionnelle', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'digitalCultureSecurity') { + const newDoc = { + surname: 'réseaux sociaux, visio', + name: 'Culture numérique', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } + } + console.log(`Update done`); +}; + +export const down = async () => { + const db: Db = await getDb(); + + const cursor = db.collection('categoriesformations').find({}); + let document; + while ((document = await cursor.next())) { + if (document.id == 'socialAndProfessional') { + const newDoc = { + surname: null, + name: 'Insertion sociale et professionnelle', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'baseSkills') { + const newDoc = { + surname: null, + name: 'Les compétences de base', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'parentingHelp') { + const newDoc = { + surname: null, + name: 'Aide à la parentalité', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'digitalCultureSecurity') { + const newDoc = { + surname: null, + name: 'Culture et sécurité numérique', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } else if (document.id == 'accessRight') { + const newDoc = { + surname: null, + name: 'Accès aux droits', + }; + await db.collection('categoriesformations').updateOne({ _id: document._id }, [{ $set: newDoc }]); + } + } + console.log(`Update done`); +}; diff --git a/src/structures/services/structure.service.spec.ts b/src/structures/services/structure.service.spec.ts index 87160583d02726edf20689229bbbad5c37ed9232..a3521419a7ad8f94b5f5f19455bb5c5bff83c674 100644 --- a/src/structures/services/structure.service.spec.ts +++ b/src/structures/services/structure.service.spec.ts @@ -1,16 +1,16 @@ -import { HttpModule, HttpStatus } from '@nestjs/common'; +import { HttpModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { ElasticsearchService } from '@nestjs/elasticsearch'; import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; +import { CategoriesFormationsServiceMock } from '../../../test/mock/services/categoriesFormations.mock.service'; import { UsersServiceMock } from '../../../test/mock/services/user.mock.service'; +import { CategoriesFormationsService } from '../../categories/services/categories-formations.service'; import { ConfigurationService } from '../../configuration/configuration.service'; import { MailerModule } from '../../mailer/mailer.module'; -import { MailerService } from '../../mailer/mailer.service'; import { SearchModule } from '../../search/search.module'; import { UsersService } from '../../users/users.service'; import { structureDto } from '../dto/structure.dto'; -import { Structure } from '../schemas/structure.schema'; +import { Structure, StructureDocument } from '../schemas/structure.schema'; import { StructuresSearchService } from './structures-search.service'; import { StructuresService } from './structures.service'; describe('StructuresService', () => { @@ -25,17 +25,210 @@ describe('StructuresService', () => { find: jest.fn(), }; + const structuresSearchServiceMock = { + search: jest.fn().mockReturnValue([ + { + _id: '6903ba0e2ab5775cfc01ed4d', + structureId: '6903ba0e2ab5775cfc01ed4d', + structureType: null, + digitalCultureSecurity: ['2', '5', '9', '28', '34', '39', '42', '51', '52', '54', '65', '96', '97', '98'], + parentingHelp: ['3', '22', '82', '94'], + socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'], + accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], + proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'], + publics: ['toutPublic'], + labelsQualifications: ['passNumerique', 'espacePublicNumeriqueepn'], + accessModality: ['accesLibre', 'telephoneVisio', 'surRdv'], + freeWorkShop: false, + createdAt: '2020-11-16T09:30:00.000Z', + updatedAt: '2021-04-12T08:48:00.000Z', + structureName: "L'Atelier Numérique", + description: + "L'Atelier Numérique est l'Espace Public Numérique des Centres Sociaux de Rillieux-la-Pape, ayant pour mission la médiation numérique pour toutes et tous.", + lockdownActivity: + 'accesLibres, permanences numériques téléphoniques, cours et ateliers à distance, formations professionnelles.', + contactPhone: '', + contactMail: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + pmrAccess: true, + exceptionalClosures: '', + jaccompagneLesUsagersDansLeursDemarchesEnLigne: true, + publicsAccompaniment: [], + autresAccompagnements: '', + nbComputers: 16, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + hours: { + monday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + { + closing: '17:00', + opening: '13:30', + }, + ], + }, + tuesday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + { + closing: '17:00', + opening: '13:30', + }, + ], + }, + wednesday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + { + closing: '17:00', + opening: '13:30', + }, + ], + }, + thursday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + { + closing: '17:00', + opening: '13:30', + }, + ], + }, + friday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + ], + }, + saturday: { + open: false, + time: [], + }, + sunday: { + open: false, + time: [], + }, + }, + __v: 0, + address: { + numero: '30 bis', + street: 'Avenue Leclerc', + commune: 'Rillieux-la-Pape', + }, + coord: [4.9036773, 45.8142196], + accountVerified: true, + linkedin: null, + nbScanners: 1, + otherDescription: null, + }, + ]), + }; + + const mockCategoriesFormationsService = { + findOneComplete: jest.fn().mockReturnValue({ + _id: '5fbb934180a5c257dc0161f6', + modules: [ + { + id: '260', + display_id: '260', + display_name: 'Modules APTIC - n°260', + digest: 'Maitrise de l’environnement d’un ordinateur (clavier, souris)', + text: 'Maitrise de l’environnement d’un ordinateur (clavier, souris)', + }, + { + id: '1', + display_id: '1', + display_name: 'Modules APTIC - n°1', + digest: 'Composantes et facettes de l’identité numérique', + text: 'Composantes et facettes de l’identité numérique', + }, + { + id: '11', + display_id: '11', + display_name: 'Modules APTIC - n°11', + digest: 'Internet : fonctionnement et outils de navigation web', + text: 'Internet : fonctionnement et outils de navigation web', + }, + { + id: '38', + display_id: '38', + display_name: 'Modules APTIC - n°38', + digest: 'Le smartphone : principes de fonctionnement', + text: 'Le smartphone : principes de fonctionnement', + }, + { + id: '48', + display_id: '48', + display_name: 'Modules APTIC - n°48', + digest: 'Internet : envoyer, recevoir, gérer ses emails', + text: 'Internet : envoyer, recevoir, gérer ses emails', + }, + { + id: '74', + display_id: '74', + display_name: 'Modules APTIC - n°74', + digest: 'Smartphones et Tablettes sous Androïd', + text: 'Smartphones et Tablettes sous Androïd', + }, + { + id: '77', + display_id: '77', + display_name: 'Modules APTIC - n°77', + digest: "Smartphone : Les principaux gestes pour l'écran tactile", + text: "Smartphone : Les principaux gestes pour l'écran tactile", + }, + ], + name: 'Les compétences de base', + id: 'baseSkills', + __v: 0, + }), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [HttpModule, MailerModule, SearchModule, ConfigModule], providers: [ StructuresService, ConfigurationService, - StructuresSearchService, + { + provide: StructuresSearchService, + useValue: structuresSearchServiceMock, + }, + CategoriesFormationsService, { provide: getModelToken(Structure.name), useValue: mockStructureModel, }, + { + provide: CategoriesFormationsService, + useValue: mockCategoriesFormationsService, + }, { provide: UsersService, useClass: UsersServiceMock, @@ -56,11 +249,92 @@ describe('StructuresService', () => { expect(res).toBeTruthy(); }); - it('should searchForStructures', () => { - let res = service.searchForStructures('a', [{ nbPrinters: '1' }]); - expect(res).toBeTruthy(); - res = service.searchForStructures('a'); - expect(res).toBeTruthy(); + describe('should searchForStructures', () => { + jest.setTimeout(30000); + mockStructureModel.find.mockReturnThis(); + mockStructureModel.exec.mockResolvedValue([ + { + _id: '6903ba0e2ab5775cfc01ed4d', + structureId: '6903ba0e2ab5775cfc01ed4d', + structureType: null, + digitalCultureSecurity: ['2', '5', '9', '28', '34', '39', '42', '51', '52', '54', '65', '96', '97', '98'], + parentingHelp: ['3', '22', '82', '94'], + socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'], + accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], + proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'], + publics: ['toutPublic'], + labelsQualifications: ['passNumerique', 'espacePublicNumeriqueepn'], + accessModality: ['accesLibre', 'telephoneVisio', 'surRdv'], + freeWorkShop: false, + createdAt: '2020-11-16T09:30:00.000Z', + updatedAt: '2021-04-12T08:48:00.000Z', + structureName: "L'Atelier Numérique", + description: + "L'Atelier Numérique est l'Espace Public Numérique des Centres Sociaux de Rillieux-la-Pape, ayant pour mission la médiation numérique pour toutes et tous.", + lockdownActivity: + 'accesLibres, permanences numériques téléphoniques, cours et ateliers à distance, formations professionnelles.', + contactPhone: '', + contactMail: '', + website: '', + facebook: null, + twitter: null, + instagram: null, + pmrAccess: true, + exceptionalClosures: '', + jaccompagneLesUsagersDansLeursDemarchesEnLigne: true, + publicsAccompaniment: [], + autresAccompagnements: '', + nbComputers: 16, + nbPrinters: 1, + nbTablets: 1, + nbNumericTerminal: 1, + hours: { + monday: { + open: true, + time: [ + { + closing: '12:30', + opening: '9:00', + }, + { + closing: '17:00', + opening: '13:30', + }, + ], + }, + }, + __v: 0, + address: { + numero: '30 bis', + street: 'Avenue Leclerc', + commune: 'Rillieux-la-Pape', + }, + coord: [4.9036773, 45.8142196], + accountVerified: true, + linkedin: null, + nbScanners: 1, + otherDescription: null, + }, + ]); + + it('should find 1 structure', async () => { + const res = await service.searchForStructures('a', [{ nbPrinters: '1' }]); + expect(res.length).toBe(1); + }); + it('should find 1 structure', async () => { + const res = await service.searchForStructures('a', [{ nbPrinters: '1' }, { '': 'baseSkills' }]); + expect(res.length).toBe(1); + }); + it('should find 1 structure', async () => { + const res = await service.searchForStructures('a', [{ '': 'baseSkills' }]); + expect(res.length).toBe(1); + }); + it('should find 1 structure', async () => { + const res = await service.searchForStructures('a'); + expect(res.length).toBe(1); + }); }); it('should create structure', () => { diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts index afa5d0542a4efccfd80833b953f1c19a9fd93cd2..bd590256d66b573335226eaaf6c599579ed1bde9 100644 --- a/src/structures/services/structures.service.ts +++ b/src/structures/services/structures.service.ts @@ -22,6 +22,7 @@ import { CategoriesFormations } from '../../categories/schemas/categoriesFormati import { CategoriesOthers } from '../../categories/schemas/categoriesOthers.schema'; import { UnclaimedStructureDto } from '../../admin/dto/unclaimed-structure-dto'; import { depRegex } from '../common/regex'; +import { CategoriesFormationsService } from '../../categories/services/categories-formations.service'; @Injectable() export class StructuresService { @@ -30,6 +31,7 @@ export class StructuresService { private readonly userService: UsersService, private readonly mailerService: MailerService, private structuresSearchService: StructuresSearchService, + private categoriesFormationsService: CategoriesFormationsService, @InjectModel(Structure.name) private structureModel: Model<StructureDocument> ) {} @@ -40,22 +42,57 @@ export class StructuresService { return this.populateES(); } + public fillFilters(filters: Array<any>): Promise<{}[]>[] { + return filters?.map(async (elem) => { + const key = Object.keys(elem)[0]; + const modules = (await this.categoriesFormationsService.findOneComplete(elem[key])).modules; + return modules.map((module) => { + return { [elem[key]]: module.id }; + }); + }); + } + async searchForStructures(text: string, filters?: Array<any>): Promise<StructureDocument[]> { const results = await this.structuresSearchService.search(text); const ids = results.map((result) => result.structureId); + let multipleFilters = filters ? filters.filter((elem) => Object.keys(elem)[0].length == 0) : null; + filters = filters?.filter((elem) => Object.keys(elem)[0].length != 0); + if (multipleFilters) { + const filtersArrays = await Promise.all(this.fillFilters(multipleFilters)); + multipleFilters = [].concat.apply([], filtersArrays); + } if (!ids.length) { return []; } //we match ids from Elasticsearch with ids from mongoDB (and filters) and sort the result according to ElasticSearch order. - if (filters.length > 0) { + if (filters?.length > 0 && multipleFilters?.length == 0) { + return ( + await this.structureModel + .find({ + _id: { $in: ids }, + $and: [...this.parseFilter(filters), { deletedAt: { $exists: false }, accountVerified: true }], + }) + .exec() + ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); + } else if (filters?.length > 0 && multipleFilters?.length > 0) { return ( await this.structureModel .find({ _id: { $in: ids }, + $or: [...this.parseFilter(multipleFilters)], $and: [...this.parseFilter(filters), { deletedAt: { $exists: false }, accountVerified: true }], }) .exec() ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); + } else if (filters?.length == 0 && multipleFilters?.length > 0) { + return ( + await this.structureModel + .find({ + _id: { $in: ids }, + $or: [...this.parseFilter(multipleFilters), { deletedAt: { $exists: false }, accountVerified: true }], + }) + .exec() + ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); } else { return ( await this.structureModel diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index e65b3965aa24378b580c213e074b100d9e96d158..cb86a1fa4f522493f84f6fbc43d8a5a7d24db2e5 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -12,6 +12,9 @@ import { TempUserService } from '../temp-user/temp-user.service'; import { User } from './schemas/user.schema'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; +import { CategoriesFormationsService } from '../categories/services/categories-formations.service'; +import { CategoriesModule } from '../categories/categories.module'; +import { CategoriesFormationsServiceMock } from '../../test/mock/services/categoriesFormations.mock.service'; describe('UsersController', () => { let controller: UsersController; @@ -25,6 +28,10 @@ describe('UsersController', () => { StructuresSearchService, MailerService, TempUserService, + { + provide: CategoriesFormationsService, + useValue: CategoriesFormationsServiceMock, + }, { provide: getModelToken('TempUser'), useValue: TempUser, diff --git a/test/mock/services/structures-for-search.mock.service.ts b/test/mock/services/structures-for-search.mock.service.ts index 766f721fd8bb675ea1b141944dd31533aed588e0..89e3da33c367dd4b435307fc36670958452488d8 100644 --- a/test/mock/services/structures-for-search.mock.service.ts +++ b/test/mock/services/structures-for-search.mock.service.ts @@ -10,6 +10,7 @@ export class StructuresForSearchServiceMock { street: 'Avenue Edouard Aynard', commune: 'Écully', }, + nbPrinters: 1, description: 'Nous sommes une équipe de 6 personnes accompagnant les usagers dans leur démarche de découverte des outils numériques, mettant à disposition sous forme de prêt des liseuses et du livre numérique et organisant des ateliers individuels de prise en main des outils numériques et tentant de répondre aux questions des usages sur des sujets divers.', }, @@ -22,6 +23,7 @@ export class StructuresForSearchServiceMock { street: " Place de l'Abbe Launay", commune: 'Grézieu-la-Varenne', }, + nbPrinters: 1, description: null, }, { @@ -33,6 +35,7 @@ export class StructuresForSearchServiceMock { street: 'Place de la Mairie', commune: 'La Tour-de-Salvagny', }, + nbPrinters: 1, description: null, }, { @@ -44,6 +47,7 @@ export class StructuresForSearchServiceMock { street: 'Chemin Jean-Marie Vianney', commune: 'Écully', }, + nbPrinters: 1, description: null, }, { @@ -56,6 +60,9 @@ export class StructuresForSearchServiceMock { commune: 'Oullins', }, description: null, + nbPrinters: 1, + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], }, ]; } diff --git a/test/mock/services/structures.mock.service.ts b/test/mock/services/structures.mock.service.ts index 5482c2ffee4658dce4c411021836f32d220cdeba..d758744af32628e4f347d29f0f041d4cb6268f20 100644 --- a/test/mock/services/structures.mock.service.ts +++ b/test/mock/services/structures.mock.service.ts @@ -214,6 +214,7 @@ export class StructuresServiceMock { socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'], accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'], baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'], publics: ['toutPublic'], labelsQualifications: ['passNumerique', 'espacePublicNumeriqueepn'], @@ -237,7 +238,6 @@ export class StructuresServiceMock { jaccompagneLesUsagersDansLeursDemarchesEnLigne: true, publicsAccompaniment: [], autresAccompagnements: '', - equipmentsAndServices: ['ordinateurs', 'tablettes'], nbComputers: 16, nbPrinters: 1, nbTablets: 1, @@ -335,12 +335,12 @@ export class StructuresServiceMock { { _id: '6093ba0e2ab5775cfc01abce', coord: [4.8498155, 45.7514817], - equipmentsAndServices: ['wifiEnAccesLibre'], digitalCultureSecurity: [], parentingHelp: [], socialAndProfessional: [], accessRight: [], - baseSkills: [], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: [], publicsAccompaniment: [], publics: ['adultes'], @@ -409,12 +409,12 @@ export class StructuresServiceMock { { _id: '6093ba0e2ab5775cfc01fffe', coord: [4.8498155, 45.7514817], - equipmentsAndServices: ['wifiEnAccesLibre'], digitalCultureSecurity: [], parentingHelp: [], socialAndProfessional: [], accessRight: [], - baseSkills: [], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: [], publicsAccompaniment: [], publics: ['adultes'], @@ -562,12 +562,12 @@ export class StructuresServiceMock { { _id: '6093ba0e2ab5775cfc01ffff', coord: [4.8498155, 45.7514817], - equipmentsAndServices: ['wifiEnAccesLibre'], digitalCultureSecurity: [], parentingHelp: [], socialAndProfessional: [], accessRight: [], - baseSkills: [], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: [], publicsAccompaniment: [], publics: ['adultes'], @@ -715,12 +715,12 @@ export class StructuresServiceMock { { _id: '6093ba0e2ab5775cfc01ffff', coord: [4.8498155, 45.7514817], - equipmentsAndServices: ['wifiEnAccesLibre'], digitalCultureSecurity: [], parentingHelp: [], socialAndProfessional: [], accessRight: [], - baseSkills: [], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: [], publicsAccompaniment: [], publics: ['adultes'], @@ -793,12 +793,12 @@ export class StructuresServiceMock { return { _id: '6093ba0e2ab5775cfc01ed3e', coord: [4.8498155, 45.7514817], - equipmentsAndServices: ['wifiEnAccesLibre'], digitalCultureSecurity: [], parentingHelp: [], socialAndProfessional: [], accessRight: [], - baseSkills: [], + baseSkills: ['260', '1', '11', '38', '48', '74', '77'], + equipmentsAndServices: ['ordinateurs', 'imprimantes'], proceduresAccompaniment: [], publicsAccompaniment: [], publics: ['adultes'],